From ab1e20773c6a300b546841f79adf8dd6e707250e Mon Sep 17 00:00:00 2001 From: James Couball Date: Tue, 8 Jul 2025 16:36:34 -0700 Subject: [PATCH 1/7] fix: remove deprecation from Git::Path This class is internal only as well as the classes that inherit from it (Git::Index and Git::WorkingTree). Remove the deprecation warning and just remove the deprecated code. --- lib/git/path.rb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/git/path.rb b/lib/git/path.rb index 32b3baa4..066f39db 100644 --- a/lib/git/path.rb +++ b/lib/git/path.rb @@ -9,17 +9,7 @@ module Git class Path attr_accessor :path - def initialize(path, check_path = nil, must_exist: nil) - unless check_path.nil? - Git::Deprecation.warn( - 'The "check_path" argument is deprecated and ' \ - 'will be removed in a future version. Use "must_exist:" instead.' - ) - end - - # default is true - must_exist = must_exist.nil? && check_path.nil? ? true : must_exist || check_path - + def initialize(path, must_exist: true) path = File.expand_path(path) raise ArgumentError, 'path does not exist', [path] if must_exist && !File.exist?(path) From 9da1e9112e38c0e964dd2bc638bda7aebe45ba91 Mon Sep 17 00:00:00 2001 From: James Couball Date: Tue, 8 Jul 2025 16:37:48 -0700 Subject: [PATCH 2/7] fix: remove deprecation from Git::Stash Since Git::Stash#initialize is an internal only method, remove the deprecation warning and removed the deprecated code. --- lib/git/stash.rb | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/git/stash.rb b/lib/git/stash.rb index 2e9af43b..4762fe2a 100644 --- a/lib/git/stash.rb +++ b/lib/git/stash.rb @@ -3,16 +3,7 @@ module Git # A stash in a Git repository class Stash - def initialize(base, message, existing = nil, save: nil) - unless existing.nil? - Git::Deprecation.warn( - 'The "existing" argument is deprecated and will be removed in a future version. Use "save:" instead.' - ) - end - - # default is false - save = existing.nil? && save.nil? ? false : save | existing - + def initialize(base, message, save: false) @base = base @message = message self.save unless save From abb0efbdb3b6bb49352d097b1fece708477d4362 Mon Sep 17 00:00:00 2001 From: James Couball Date: Tue, 8 Jul 2025 16:39:23 -0700 Subject: [PATCH 3/7] test: verify deprecated Git::Log methods emit a deprecation warning --- tests/test_helper.rb | 6 +- tests/units/test_log_deprecations.rb | 82 ++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 tests/units/test_log_deprecations.rb diff --git a/tests/test_helper.rb b/tests/test_helper.rb index aa42eedd..0e6ac89f 100644 --- a/tests/test_helper.rb +++ b/tests/test_helper.rb @@ -13,14 +13,14 @@ $stderr.sync = true # Make tests that emit a deprecation warning fail - +# # Deprecation warnings should not be ignored. - +# # This is important so that: # * when a user sees a deprecation warning, they can be confident it is coming from # their code and not this gem # * test output is clean and does not contain noisey deprecation warnings - +# # Tests whose purpose is to test that a deprecation warning is issued in the right # circumstance should mock Git::Deprecation#warn to avoid raising an error. # diff --git a/tests/units/test_log_deprecations.rb b/tests/units/test_log_deprecations.rb new file mode 100644 index 00000000..ed945a99 --- /dev/null +++ b/tests/units/test_log_deprecations.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'git' +require 'fileutils' +require 'tmpdir' + +# A test case to verify the deprecation warnings for methods on the Git::Log class. +class LogDeprecationsTest < Test::Unit::TestCase + # Set up a temporary Git repository with a single commit before each test. + def setup + @repo_path = Dir.mktmpdir('git_test') + @repo = Git.init(@repo_path) + + # Create a commit so the log has an entry to work with. + Dir.chdir(@repo_path) do + File.write('file.txt', 'content') + @repo.add('file.txt') + @repo.commit('Initial commit') + end + + @log = @repo.log + @first_commit = @repo.gcommit('HEAD') + end + + # Clean up the temporary repository after each test. + def teardown + FileUtils.rm_rf(@repo_path) + end + + # Test the deprecation warning and functionality of Git::Log#each + def test_each_deprecation + Git::Deprecation.expects(:warn).with( + 'Calling Git::Log#each is deprecated. Call #execute and then #each on the result object.' + ) + + commits = @log.map { |c| c } + + assert_equal(1, commits.size, 'The #each method should still yield the correct number of commits.') + assert_equal(@first_commit.sha, commits.first.sha, 'The yielded commit should have the correct SHA.') + end + + # Test the deprecation warning and functionality of Git::Log#size + def test_size_deprecation + Git::Deprecation.expects(:warn).with( + 'Calling Git::Log#size is deprecated. Call #execute and then #size on the result object.' + ) + assert_equal(1, @log.size, 'The #size method should still return the correct number of commits.') + end + + # Test the deprecation warning and functionality of Git::Log#to_s + def test_to_s_deprecation + Git::Deprecation.expects(:warn).with( + 'Calling Git::Log#to_s is deprecated. Call #execute and then #to_s on the result object.' + ) + assert_equal(@first_commit.sha, @log.to_s, 'The #to_s method should return the commit SHA.') + end + + # Test the deprecation warning and functionality of Git::Log#first + def test_first_deprecation + Git::Deprecation.expects(:warn).with( + 'Calling Git::Log#first is deprecated. Call #execute and then #first on the result object.' + ) + assert_equal(@first_commit.sha, @log.first.sha, 'The #first method should return the correct commit.') + end + + # Test the deprecation warning and functionality of Git::Log#last + def test_last_deprecation + Git::Deprecation.expects(:warn).with( + 'Calling Git::Log#last is deprecated. Call #execute and then #last on the result object.' + ) + assert_equal(@first_commit.sha, @log.last.sha, 'The #last method should return the correct commit.') + end + + # Test the deprecation warning and functionality of Git::Log#[] + def test_indexer_deprecation + Git::Deprecation.expects(:warn).with( + 'Calling Git::Log#[] is deprecated. Call #execute and then #[] on the result object.' + ) + assert_equal(@first_commit.sha, @log[0].sha, 'The #[] method should return the correct commit at the specified index.') + end +end From ab17621d65a02b70844fde3127c9cbb219add7f5 Mon Sep 17 00:00:00 2001 From: James Couball Date: Tue, 8 Jul 2025 16:40:38 -0700 Subject: [PATCH 4/7] test: add tests to verify Git::Object.new creates the right type of object --- tests/units/test_object_new.rb | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/units/test_object_new.rb diff --git a/tests/units/test_object_new.rb b/tests/units/test_object_new.rb new file mode 100644 index 00000000..052bf728 --- /dev/null +++ b/tests/units/test_object_new.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'git' +require 'fileutils' +require 'tmpdir' + +# A test case to verify the functionality of the Git::Object.new factory method. +class ObjectNewTest < Test::Unit::TestCase + # Set up a temporary Git repository with objects of different types. + def setup + @repo_path = Dir.mktmpdir('git_test') + @repo = Git.init(@repo_path) + + Dir.chdir(@repo_path) do + File.write('file.txt', 'This is a test file.') + @repo.add('file.txt') + @repo.commit('Initial commit') + @repo.add_tag('v1.0', message: 'Version 1.0', annotate: true) + end + + @commit = @repo.gcommit('HEAD') + @tree = @commit.gtree + @blob = @tree.blobs['file.txt'] + @tag = @repo.tag('v1.0') + end + + # Clean up the temporary repository after each test. + def teardown + FileUtils.rm_rf(@repo_path) + end + + # Test that the factory method creates a Git::Object::Commit for a commit SHA. + def test_new_creates_commit_object + object = Git::Object.new(@repo, @commit.sha) + assert_instance_of(Git::Object::Commit, object, 'Should create a Commit object.') + assert(object.commit?, 'Object#commit? should be true.') + end + + # Test that the factory method creates a Git::Object::Tree for a tree SHA. + def test_new_creates_tree_object + object = Git::Object.new(@repo, @tree.sha) + assert_instance_of(Git::Object::Tree, object, 'Should create a Tree object.') + assert(object.tree?, 'Object#tree? should be true.') + end + + # Test that the factory method creates a Git::Object::Blob for a blob SHA. + def test_new_creates_blob_object + object = Git::Object.new(@repo, @blob.sha) + assert_instance_of(Git::Object::Blob, object, 'Should create a Blob object.') + assert(object.blob?, 'Object#blob? should be true.') + end + + # Test that using the deprecated `is_tag` argument creates a Tag object + # and issues a deprecation warning. + def test_new_with_is_tag_deprecation + # Set up the mock expectation for the deprecation warning. + Git::Deprecation.expects(:warn).with( + 'Git::Object.new with is_tag argument is deprecated. Use Git::Object::Tag.new instead.' + ) + + # Action: Call the factory method with the deprecated argument. + # The `objectish` here is the tag name, as was the old pattern. + tag_object = Git::Object.new(@repo, 'v1.0', nil, true) + + # Verification + assert_instance_of(Git::Object::Tag, tag_object, 'Should create a Tag object.') + assert(tag_object.tag?, 'Object#tag? should be true.') + assert_equal('v1.0', tag_object.name, 'Tag object should have the correct name.') + # Mocha automatically verifies the expectation at the end of the test. + end +end From e6ccb11830a794f12235e47032235c3284c84cf6 Mon Sep 17 00:00:00 2001 From: James Couball Date: Tue, 8 Jul 2025 16:41:08 -0700 Subject: [PATCH 5/7] test: add tests for Git::Base#set_index including deprecation --- tests/units/test_set_index.rb | 167 ++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 tests/units/test_set_index.rb diff --git a/tests/units/test_set_index.rb b/tests/units/test_set_index.rb new file mode 100644 index 00000000..e8721bfa --- /dev/null +++ b/tests/units/test_set_index.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'git' +require 'fileutils' +require 'tmpdir' + +# A test case to demonstrate the use of Git::Base#set_index +# +# This test case will to programmatically create a new commit without affecting the +# main working directory or index. +# +class SetIndexTest < Test::Unit::TestCase + # Set up a temporary Git repository before each test. + def setup + # Create a temporary directory for the repository + @repo_path = Dir.mktmpdir('git_test') + + # Initialize a new Git repository in the temporary directory + @repo = Git.init(@repo_path) + + # Change into the repo directory to perform file operations + Dir.chdir(@repo_path) do + # Create and commit an initial file to establish a HEAD and a root tree. + # This gives us a base state to work from. + File.write('file1.txt', 'This is the first file.') + @repo.add('file1.txt') + @repo.commit('Initial commit') + end + end + + attr_reader :repo_path, :repo + + # Clean up the temporary repository after each test. + def teardown + FileUtils.rm_rf(repo_path) + end + + # Tests that `set_index` can point to a new, non-existent index file + # when `must_exist: false` is specified. + def test_set_index_with_must_exist_false_for_new_path + custom_index_path = File.join(repo_path, 'custom_index') + assert(!File.exist?(custom_index_path), 'Precondition: Custom index file should not exist.') + + # Action: Set the index to a new path, allowing it to not exist. + repo.set_index(custom_index_path, must_exist: false) + + # Verification: The repo object should now point to the new index path. + assert_equal(custom_index_path, repo.index.path, 'Index path should be updated to the custom path.') + end + + # Tests that `set_index` successfully points to an existing index file + # when `must_exist: true` is specified. + def test_set_index_with_must_exist_true_for_existing_path + original_index_path = repo.index.path + assert(File.exist?(original_index_path), 'Precondition: Original index file should exist.') + + # Action: Set the index to the same, existing path, explicitly requiring it to exist. + repo.set_index(original_index_path, must_exist: true) + + # Verification: The index path should remain unchanged. + assert_equal(original_index_path, repo.index.path, 'Index path should still be the original path.') + end + + # Tests that `set_index` raises an ArgumentError when trying to point to a + # non-existent index file with the default behavior (`must_exist: true`). + def test_set_index_with_must_exist_true_raises_error_for_new_path + non_existent_path = File.join(repo_path, 'no_such_file') + assert(!File.exist?(non_existent_path), 'Precondition: The target index path should not exist.') + + # Action & Verification: Assert that an ArgumentError is raised. + assert_raise(ArgumentError, 'Should raise ArgumentError for a non-existent index path.') do + repo.set_index(non_existent_path) # must_exist defaults to true + end + end + + # Tests that using the deprecated `check` argument issues a warning via mocking. + def test_set_index_with_deprecated_check_argument + custom_index_path = File.join(repo_path, 'custom_index') + assert(!File.exist?(custom_index_path), 'Precondition: Custom index file should not exist.') + + # Set up the mock expectation. + # We expect Git::Deprecation.warn to be called once with a message + # matching the expected deprecation warning. + Git::Deprecation.expects(:warn).with( + regexp_matches(/The "check" argument is deprecated/) + ) + + # Action: Use the deprecated positional argument `check = false` + repo.set_index(custom_index_path, false) + + # Verification: The repo object should still point to the new index path. + assert_equal(custom_index_path, repo.index.path, 'Index path should be updated even with deprecated argument.') + # Mocha automatically verifies the expectation at the end of the test. + end + + # This test demonstrates creating a new commit on a new branch by + # manipulating a custom, temporary index file. This allows for building + # commits programmatically without touching the working directory or the + # default .git/index. + def test_programmatic_commit_with_set_index + # 1. Get the initial commit object to use as a parent for our new commit. + main_commit = repo.gcommit('main') + assert(!main_commit.nil?, 'Initial commit should exist.') + + # 2. Define a path for a new, temporary index file within the repo directory. + custom_index_path = File.join(repo_path, 'custom_index') + assert(!File.exist?(custom_index_path), 'Custom index file should not exist yet.') + + # 3. Point the git object to use our custom index file. + # Since the file doesn't exist yet, we must pass `must_exist: false`. + repo.set_index(custom_index_path, must_exist: false) + assert_equal(custom_index_path, repo.index.path, 'The git object should now be using the custom index.') + + # 4. Populate the new index by reading the tree from our initial commit into it. + # This stages all the files from the 'main' commit in our custom index. + repo.read_tree(main_commit.gtree.sha) + + # 5. Programmatically create a new file blob and add it to our custom index. + # This simulates `git add` for a new file, but operates directly on the index. + new_content = 'This is a brand new file.' + blob_sha = Tempfile.create('new_blob_content') do |file| + file.write(new_content) + file.rewind + # Use `git hash-object -w` to write the blob to the object database and get its SHA + repo.lib.send(:command, 'hash-object', '-w', file.path) + end + repo.lib.send(:command, 'update-index', '--add', '--cacheinfo', "100644,#{blob_sha},new_file.txt") + + # 6. Write the state of the custom index to a new tree object in the Git database. + new_tree_sha = repo.write_tree + assert_match(/^[0-9a-f]{40}$/, new_tree_sha, 'A new tree SHA should be created.') + + # 7. Create a new commit object from the new tree. + # This commit will have the initial commit as its parent. + new_commit = repo.commit_tree( + new_tree_sha, + parents: [main_commit.sha], + message: 'Commit created programmatically via custom index' + ) + assert(new_commit.commit?, 'A new commit object should be created.') + + # 8. Create a new branch pointing to our new commit. + repo.branch('feature-branch').update_ref(new_commit) + assert(repo.branch('feature-branch').gcommit.sha == new_commit.sha, 'feature-branch should point to the new commit.') + + # --- Verification --- + # Verify the history of the new branch + feature_log = repo.log.object('feature-branch').execute + main_commit_sha = repo.rev_parse('main') # Get SHA directly for reliable comparison + + assert_equal(2, feature_log.size, 'Feature branch should have two commits.') + assert_equal(new_commit.sha, feature_log.first.sha, 'HEAD of feature-branch should be our new commit.') + assert_equal(main_commit_sha, feature_log.last.sha, 'Parent of new commit should be the initial commit.') + + # Verify that the main branch is unchanged + main_log = repo.log.object('main').execute + assert_equal(1, main_log.size, 'Main branch should still have one commit.') + assert_equal(main_commit_sha, main_log.first.sha, 'Main branch should still point to the initial commit.') + + # Verify the contents of the new commit's tree + new_commit_tree = new_commit.gtree + assert(new_commit_tree.blobs.key?('file1.txt'), 'Original file should exist in the new tree.') + assert(new_commit_tree.blobs.key?('new_file.txt'), 'New file should exist in the new tree.') + assert_equal(new_content, new_commit_tree.blobs['new_file.txt'].contents, 'Content of new file should match.') + end +end From ee1113706a8e34e9631f0e2d89bd602bca87f05f Mon Sep 17 00:00:00 2001 From: James Couball Date: Tue, 8 Jul 2025 16:41:25 -0700 Subject: [PATCH 6/7] test: add tests for Git::Base#set_working including deprecation --- tests/units/test_set_working.rb | 83 +++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tests/units/test_set_working.rb diff --git a/tests/units/test_set_working.rb b/tests/units/test_set_working.rb new file mode 100644 index 00000000..dfe781e1 --- /dev/null +++ b/tests/units/test_set_working.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'git' +require 'fileutils' +require 'tmpdir' + +# A test case to demonstrate the use of Git::Base#set_working +class SetWorkingTest < Test::Unit::TestCase + # Set up a temporary Git repository before each test. + def setup + # Create a temporary directory for the repository + @repo_path = Dir.mktmpdir('git_test') + + # Initialize a new Git repository in the temporary directory + @repo = Git.init(@repo_path) + end + + attr_reader :repo_path, :repo + + # Clean up the temporary repository after each test. + def teardown + FileUtils.rm_rf(repo_path) + end + + # Tests that `set_working` can point to a new, non-existent directory + # when `must_exist: false` is specified. + def test_set_working_with_must_exist_false_for_new_path + custom_working_path = File.join(repo_path, 'custom_work_dir') + assert(!File.exist?(custom_working_path), 'Precondition: Custom working directory should not exist.') + + # Action: Set the working directory to a new path, allowing it to not exist. + repo.set_working(custom_working_path, must_exist: false) + + # Verification: The repo object should now point to the new working directory path. + assert_equal(custom_working_path, repo.dir.path, 'Working directory path should be updated to the custom path.') + end + + # Tests that `set_working` successfully points to an existing directory + # when `must_exist: true` is specified. + def test_set_working_with_must_exist_true_for_existing_path + original_working_path = repo.dir.path + assert(File.exist?(original_working_path), 'Precondition: Original working directory should exist.') + + # Action: Set the working directory to the same, existing path, explicitly requiring it to exist. + repo.set_working(original_working_path, must_exist: true) + + # Verification: The working directory path should remain unchanged. + assert_equal(original_working_path, repo.dir.path, 'Working directory path should still be the original path.') + end + + # Tests that `set_working` raises an ArgumentError when trying to point to a + # non-existent directory with the default behavior (`must_exist: true`). + def test_set_working_with_must_exist_true_raises_error_for_new_path + non_existent_path = File.join(repo_path, 'no_such_dir') + assert(!File.exist?(non_existent_path), 'Precondition: The target working directory path should not exist.') + + # Action & Verification: Assert that an ArgumentError is raised. + assert_raise(ArgumentError, 'Should raise ArgumentError for a non-existent working directory path.') do + repo.set_working(non_existent_path) # must_exist defaults to true + end + end + + # Tests that using the deprecated `check` argument issues a warning via mocking. + def test_set_working_with_deprecated_check_argument + custom_working_path = File.join(repo_path, 'custom_work_dir') + assert(!File.exist?(custom_working_path), 'Precondition: Custom working directory should not exist.') + + # Set up the mock expectation. + # We expect Git::Deprecation.warn to be called once with a message + # matching the expected deprecation warning. + Git::Deprecation.expects(:warn).with( + regexp_matches(/The "check" argument is deprecated/) + ) + + # Action: Use the deprecated positional argument `check = false` + repo.set_working(custom_working_path, false) + + # Verification: The repo object should still point to the new working directory path. + assert_equal(custom_working_path, repo.dir.path, 'Working directory path should be updated even with deprecated argument.') + # Mocha automatically verifies the expectation at the end of the test. + end +end From df3ea35ebfa007809ff0c700c505781b38be74c5 Mon Sep 17 00:00:00 2001 From: James Couball Date: Tue, 8 Jul 2025 17:18:48 -0700 Subject: [PATCH 7/7] chore: release v4.0.4 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 16 ++++++++++++++++ lib/git/version.rb | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 453aad70..b3c3305f 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.0.3" + ".": "4.0.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 154ee8e8..439b428a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ # Change Log +## [4.0.4](https://github.com/ruby-git/ruby-git/compare/v4.0.3...v4.0.4) (2025-07-09) + + +### Bug Fixes + +* Remove deprecation from Git::Path ([ab1e207](https://github.com/ruby-git/ruby-git/commit/ab1e20773c6a300b546841f79adf8dd6e707250e)) +* Remove deprecation from Git::Stash ([9da1e91](https://github.com/ruby-git/ruby-git/commit/9da1e9112e38c0e964dd2bc638bda7aebe45ba91)) + + +### Other Changes + +* Add tests for Git::Base#set_index including deprecation ([e6ccb11](https://github.com/ruby-git/ruby-git/commit/e6ccb11830a794f12235e47032235c3284c84cf6)) +* Add tests for Git::Base#set_working including deprecation ([ee11137](https://github.com/ruby-git/ruby-git/commit/ee1113706a8e34e9631f0e2d89bd602bca87f05f)) +* Add tests to verify Git::Object.new creates the right type of object ([ab17621](https://github.com/ruby-git/ruby-git/commit/ab17621d65a02b70844fde3127c9cbb219add7f5)) +* Verify deprecated Git::Log methods emit a deprecation warning ([abb0efb](https://github.com/ruby-git/ruby-git/commit/abb0efbdb3b6bb49352d097b1fece708477d4362)) + ## [4.0.3](https://github.com/ruby-git/ruby-git/compare/v4.0.2...v4.0.3) (2025-07-08) diff --git a/lib/git/version.rb b/lib/git/version.rb index 0b43866a..dde1e521 100644 --- a/lib/git/version.rb +++ b/lib/git/version.rb @@ -3,5 +3,5 @@ module Git # The current gem version # @return [String] the current gem version. - VERSION = '4.0.3' + VERSION = '4.0.4' end