git 4.0.7 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/copilot-instructions.md +2212 -330
- data/.release-please-manifest.json +1 -1
- data/.rubocop_todo.yml +1 -1
- data/CHANGELOG.md +24 -0
- data/README.md +52 -3
- data/lib/git/base.rb +83 -10
- data/lib/git/diff.rb +40 -2
- data/lib/git/diff_path_status.rb +1 -1
- data/lib/git/lib.rb +190 -14
- data/lib/git/version.rb +1 -1
- data/lib/git.rb +39 -0
- metadata +3 -3
data/.rubocop_todo.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@
|
|
|
5
5
|
|
|
6
6
|
# Change Log
|
|
7
7
|
|
|
8
|
+
## [4.1.0](https://github.com/ruby-git/ruby-git/compare/v4.0.7...v4.1.0) (2026-01-02)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* Add per-instance git_ssh configuration support ([26c1199](https://github.com/ruby-git/ruby-git/commit/26c119969ec71c23c965f55f0570471f8ddf333a))
|
|
14
|
+
* **clone:** Add single_branch option ([a6929bb](https://github.com/ruby-git/ruby-git/commit/a6929bb0bfd51cba3a595e47740897ca619da468))
|
|
15
|
+
* **diff:** Allow multiple paths in diff path limiter ([c663b62](https://github.com/ruby-git/ruby-git/commit/c663b62a0c9075a18c112e2cda3744f88f42ab7e))
|
|
16
|
+
* **remote:** Add remote set-branches helper ([a7dab2b](https://github.com/ruby-git/ruby-git/commit/a7dab2bdf9088f0610dfbf3e3b78677b90195f75))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* Prevent GIT_INDEX_FILE from corrupting worktree indexes ([27c0f16](https://github.com/ruby-git/ruby-git/commit/27c0f1629927ae23a5bb8efc4df79756a9e4406b))
|
|
22
|
+
* **test:** Use larger timeout values on JRuby to prevent flaky tests ([aa8fd8b](https://github.com/ruby-git/ruby-git/commit/aa8fd8b0435246f70579bfab3cde8d45bc23233a))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Other Changes
|
|
26
|
+
|
|
27
|
+
* Add git version support policy ([fbb0c60](https://github.com/ruby-git/ruby-git/commit/fbb0c60c56a01222133b61eb5267148773b4239c))
|
|
28
|
+
* **clone:** Simplify single_branch validator ([3900233](https://github.com/ruby-git/ruby-git/commit/39002330d42c4a2b3f0413ba920e6fd534880e03))
|
|
29
|
+
* Expand AI instructions with comprehensive workflows ([04907ed](https://github.com/ruby-git/ruby-git/commit/04907edd89dd716d85f190d828cbf6a0c43d47f6))
|
|
30
|
+
* Make env_overrides more flexible and idiomatic ([dc0b43b](https://github.com/ruby-git/ruby-git/commit/dc0b43bccbc9c57c445efc303a3e0f6a71cbd66f))
|
|
31
|
+
|
|
8
32
|
## [4.0.7](https://github.com/ruby-git/ruby-git/compare/v4.0.6...v4.0.7) (2025-12-29)
|
|
9
33
|
|
|
10
34
|
|
data/README.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- [Deprecations](#deprecations)
|
|
20
20
|
- [Examples](#examples)
|
|
21
21
|
- [Ruby version support policy](#ruby-version-support-policy)
|
|
22
|
+
- [Git version support policy](#git-version-support-policy)
|
|
22
23
|
- [License](#license)
|
|
23
24
|
- [📢 Project Announcements 📢](#-project-announcements-)
|
|
24
25
|
- [2025-07-09: Architectural Redesign](#2025-07-09-architectural-redesign)
|
|
@@ -33,9 +34,9 @@ command line.
|
|
|
33
34
|
|
|
34
35
|
Get started by obtaining a repository object by:
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
- opening an existing working copy with [Git.open](https://rubydoc.info/gems/git/Git#open-class_method)
|
|
38
|
+
- initializing a new repository with [Git.init](https://rubydoc.info/gems/git/Git#init-class_method)
|
|
39
|
+
- cloning a repository with [Git.clone](https://rubydoc.info/gems/git/Git#clone-class_method)
|
|
39
40
|
|
|
40
41
|
Methods that can be called on a repository object are documented in [Git::Base](https://rubydoc.info/gems/git/Git/Base)
|
|
41
42
|
|
|
@@ -223,6 +224,28 @@ end
|
|
|
223
224
|
|
|
224
225
|
_NOTE: Another way to specify where is the `git` binary is through the environment variable `GIT_PATH`_
|
|
225
226
|
|
|
227
|
+
**How SSH configuration is determined:**
|
|
228
|
+
|
|
229
|
+
- If `git_ssh` is not specified in the API call, the global config (`Git.configure { |c| c.git_ssh = ... }`) is used.
|
|
230
|
+
- If `git_ssh: nil` is specified, SSH is disabled for that instance (no SSH key or script will be used).
|
|
231
|
+
- If `git_ssh` is a non-empty string, it is used for that instance (overriding the global config).
|
|
232
|
+
|
|
233
|
+
You can also specify a custom SSH script on a per-repository basis:
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
# Use a specific SSH key for a single repository
|
|
237
|
+
git = Git.open('/path/to/repo', git_ssh: 'ssh -i /path/to/private_key')
|
|
238
|
+
|
|
239
|
+
# Or when cloning
|
|
240
|
+
git = Git.clone('git@github.com:user/repo.git', 'local-dir',
|
|
241
|
+
git_ssh: 'ssh -i /path/to/private_key')
|
|
242
|
+
|
|
243
|
+
# Or when initializing
|
|
244
|
+
git = Git.init('new-repo', git_ssh: 'ssh -i /path/to/private_key')
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
This is especially useful in multi-threaded applications where different repositories require different SSH credentials.
|
|
248
|
+
|
|
226
249
|
Here are the operations that need read permission only.
|
|
227
250
|
|
|
228
251
|
```ruby
|
|
@@ -296,6 +319,7 @@ g.diff(commit1, commit2).stats
|
|
|
296
319
|
g.diff(commit1, commit2).name_status
|
|
297
320
|
g.gtree('v2.5').diff('v2.6').insertions
|
|
298
321
|
g.diff('gitsearch1', 'v2.5').path('lib/')
|
|
322
|
+
g.diff('gitsearch1', 'v2.5').path('lib/', 'docs/', 'README.md') # multiple paths
|
|
299
323
|
g.diff('gitsearch1', @git.gtree('v2.5'))
|
|
300
324
|
g.diff('gitsearch1', 'v2.5').path('docs/').patch
|
|
301
325
|
g.gtree('v2.5').diff('v2.6').patch
|
|
@@ -368,6 +392,9 @@ g.config('user.email', 'email@email.com')
|
|
|
368
392
|
# Clone can take a filter to tell the serve to send a partial clone
|
|
369
393
|
g = Git.clone(git_url, name, :path => path, :filter => 'tree:0')
|
|
370
394
|
|
|
395
|
+
# Clone can control single-branch behavior (nil default keeps current git behavior)
|
|
396
|
+
g = Git.clone(git_url, name, :path => path, :depth => 1, :single_branch => false)
|
|
397
|
+
|
|
371
398
|
# Clone can take an optional logger
|
|
372
399
|
logger = Logger.new
|
|
373
400
|
g = Git.clone(git_url, NAME, :log => logger)
|
|
@@ -442,6 +469,9 @@ g.remote(name).remove
|
|
|
442
469
|
g.remote(name).merge
|
|
443
470
|
g.remote(name).merge(branch)
|
|
444
471
|
|
|
472
|
+
g.remote_set_branches('origin', '*', add: true) # append additional fetch refspecs
|
|
473
|
+
g.remote_set_branches('origin', 'feature', 'release/*') # replace fetch refspecs
|
|
474
|
+
|
|
445
475
|
g.fetch
|
|
446
476
|
g.fetch(g.remotes.first)
|
|
447
477
|
g.fetch('origin', {:ref => 'some/ref/head'} )
|
|
@@ -536,6 +566,25 @@ once the following JRuby bug is fixed:
|
|
|
536
566
|
|
|
537
567
|
jruby/jruby#7515
|
|
538
568
|
|
|
569
|
+
## Git version support policy
|
|
570
|
+
|
|
571
|
+
This gem requires git version 2.28.0 or greater as specified in the gemspec. This
|
|
572
|
+
requirement reflects:
|
|
573
|
+
|
|
574
|
+
- The minimum git version necessary to support all features provided by this gem
|
|
575
|
+
- A reasonable balance between supporting older systems and leveraging modern git
|
|
576
|
+
capabilities
|
|
577
|
+
- The practical limitations of testing across multiple git versions in CI
|
|
578
|
+
|
|
579
|
+
Git 2.28.0 was released on July 27, 2020. While this gem may work with earlier
|
|
580
|
+
versions of git, compatibility with versions prior to 2.28.0 is not tested or
|
|
581
|
+
guaranteed. Users on older git versions should upgrade to at least 2.28.0.
|
|
582
|
+
|
|
583
|
+
The supported git version may be increased in future major or minor releases of this
|
|
584
|
+
gem as new git features are adopted or as maintaining backward compatibility becomes
|
|
585
|
+
impractical. Such changes will be clearly documented in the CHANGELOG and release
|
|
586
|
+
notes.
|
|
587
|
+
|
|
539
588
|
## License
|
|
540
589
|
|
|
541
590
|
Licensed under MIT License Copyright (c) 2008 Scott Chacon. See LICENSE for further
|
data/lib/git/base.rb
CHANGED
|
@@ -21,7 +21,9 @@ module Git
|
|
|
21
21
|
|
|
22
22
|
# (see Git.clone)
|
|
23
23
|
def self.clone(repository_url, directory, options = {})
|
|
24
|
-
|
|
24
|
+
lib_options = {}
|
|
25
|
+
lib_options[:git_ssh] = options[:git_ssh] if options.key?(:git_ssh)
|
|
26
|
+
new_options = Git::Lib.new(lib_options, options[:log]).clone(repository_url, directory, options)
|
|
25
27
|
normalize_paths(new_options, bare: options[:bare] || options[:mirror])
|
|
26
28
|
new(new_options)
|
|
27
29
|
end
|
|
@@ -148,12 +150,20 @@ module Git
|
|
|
148
150
|
# commands are logged at the `:info` level. Additional logging is done
|
|
149
151
|
# at the `:debug` level.
|
|
150
152
|
#
|
|
153
|
+
# @option options [String, nil] :git_ssh Path to a custom SSH executable or script.
|
|
154
|
+
# Controls how SSH is configured for this {Git::Base} instance:
|
|
155
|
+
# - If this option is not provided, the global Git::Base.config.git_ssh setting is used.
|
|
156
|
+
# - If this option is explicitly set to nil, SSH is disabled for this instance.
|
|
157
|
+
# - If this option is a non-empty String, that value is used as the SSH command for
|
|
158
|
+
# this instance, overriding the global Git::Base.config.git_ssh setting.
|
|
159
|
+
#
|
|
151
160
|
# @return [Git::Base] an object that can execute git commands in the context
|
|
152
161
|
# of the opened working copy or bare repository
|
|
153
162
|
#
|
|
154
163
|
def initialize(options = {})
|
|
155
164
|
options = default_paths(options)
|
|
156
165
|
setup_logger(options[:log])
|
|
166
|
+
@git_ssh = options.key?(:git_ssh) ? options[:git_ssh] : :use_global_config
|
|
157
167
|
initialize_components(options)
|
|
158
168
|
end
|
|
159
169
|
|
|
@@ -328,6 +338,17 @@ module Git
|
|
|
328
338
|
@lib ||= Git::Lib.new(self, @logger)
|
|
329
339
|
end
|
|
330
340
|
|
|
341
|
+
# Returns the per-instance git_ssh configuration value.
|
|
342
|
+
#
|
|
343
|
+
# This may be:
|
|
344
|
+
# * a [String] path when an explicit git_ssh command has been configured
|
|
345
|
+
# * the Symbol `:use_global_config` when this instance is using the global config
|
|
346
|
+
# * `nil` when SSH has been explicitly disabled for this instance
|
|
347
|
+
#
|
|
348
|
+
# @return [String, Symbol, nil] the git_ssh configuration value for this instance
|
|
349
|
+
# @api private
|
|
350
|
+
attr_reader :git_ssh
|
|
351
|
+
|
|
331
352
|
# Run a grep for 'string' on the HEAD of the git repository
|
|
332
353
|
#
|
|
333
354
|
# @example Limit grep's scope by calling grep() from a specific object:
|
|
@@ -342,7 +363,8 @@ module Git
|
|
|
342
363
|
# end
|
|
343
364
|
#
|
|
344
365
|
# @param string [String] the string to search for
|
|
345
|
-
# @param path_limiter [String, Array] a path or array
|
|
366
|
+
# @param path_limiter [String, Pathname, Array<String, Pathname>] a path or array
|
|
367
|
+
# of paths to limit the search to or nil for no limit
|
|
346
368
|
# @param opts [Hash] options to pass to the underlying `git grep` command
|
|
347
369
|
#
|
|
348
370
|
# @option opts [Boolean] :ignore_case (false) ignore case when matching
|
|
@@ -508,8 +530,8 @@ module Git
|
|
|
508
530
|
# @param branch [String] the branch to pull from
|
|
509
531
|
# @param opts [Hash] options to pass to the pull command
|
|
510
532
|
#
|
|
511
|
-
# @option opts [Boolean] :allow_unrelated_histories (false) Merges histories of
|
|
512
|
-
# lives independently
|
|
533
|
+
# @option opts [Boolean] :allow_unrelated_histories (false) Merges histories of
|
|
534
|
+
# two projects that started their lives independently
|
|
513
535
|
# @example pulls from origin/master
|
|
514
536
|
# @git.pull
|
|
515
537
|
# @example pulls from upstream/master
|
|
@@ -541,6 +563,43 @@ module Git
|
|
|
541
563
|
Git::Remote.new(self, name)
|
|
542
564
|
end
|
|
543
565
|
|
|
566
|
+
# Configures which branches are fetched for a remote
|
|
567
|
+
#
|
|
568
|
+
# Uses `git remote set-branches` to set or append fetch refspecs. When the `add:`
|
|
569
|
+
# option is not given, the `--add` option is not passed to the git command
|
|
570
|
+
#
|
|
571
|
+
# @example Replace fetched branches with a single glob pattern
|
|
572
|
+
# git = Git.open('/path/to/repo')
|
|
573
|
+
# # Only fetch branches matching "feature/*" from origin
|
|
574
|
+
# git.remote_set_branches('origin', 'feature/*')
|
|
575
|
+
#
|
|
576
|
+
# @example Append a glob pattern to existing fetched branches
|
|
577
|
+
# git = Git.open('/path/to/repo')
|
|
578
|
+
# # Keep existing fetch refspecs and add all release branches
|
|
579
|
+
# git.remote_set_branches('origin', 'release/*', add: true)
|
|
580
|
+
#
|
|
581
|
+
# @example Configure multiple explicit branches
|
|
582
|
+
# git = Git.open('/path/to/repo')
|
|
583
|
+
# git.remote_set_branches('origin', 'main', 'development', 'hotfix')
|
|
584
|
+
#
|
|
585
|
+
# @param name [String] the remote name (for example, "origin")
|
|
586
|
+
# @param branches [Array<String>] branch names or globs (for example, '*')
|
|
587
|
+
# @param add [Boolean] when true, append to existing refspecs instead of replacing them
|
|
588
|
+
#
|
|
589
|
+
# @return [nil]
|
|
590
|
+
#
|
|
591
|
+
# @raise [ArgumentError] if no branches are provided @raise [Git::FailedError] if
|
|
592
|
+
# the underlying git command fails
|
|
593
|
+
#
|
|
594
|
+
def remote_set_branches(name, *branches, add: false)
|
|
595
|
+
branch_list = branches.flatten
|
|
596
|
+
raise ArgumentError, 'branches are required' if branch_list.empty?
|
|
597
|
+
|
|
598
|
+
lib.remote_set_branches(name, branch_list, add: add)
|
|
599
|
+
|
|
600
|
+
nil
|
|
601
|
+
end
|
|
602
|
+
|
|
544
603
|
# removes a remote from this repository
|
|
545
604
|
#
|
|
546
605
|
# @git.remove_remote('scott_git')
|
|
@@ -812,18 +871,32 @@ module Git
|
|
|
812
871
|
#
|
|
813
872
|
# @param objectish [String] The first commit or object to compare. Defaults to 'HEAD'.
|
|
814
873
|
# @param obj2 [String, nil] The second commit or object to compare.
|
|
815
|
-
# @
|
|
816
|
-
|
|
817
|
-
|
|
874
|
+
# @param opts [Hash] Options to filter the diff.
|
|
875
|
+
# @option opts [String, Pathname, Array<String, Pathname>] :path_limiter Limit stats to specified path(s).
|
|
876
|
+
# @return [Git::DiffStats]
|
|
877
|
+
def diff_stats(objectish = 'HEAD', obj2 = nil, opts = {})
|
|
878
|
+
Git::DiffStats.new(self, objectish, obj2, opts[:path_limiter])
|
|
818
879
|
end
|
|
819
880
|
|
|
820
881
|
# Returns a Git::Diff::PathStatus object for accessing the name-status report.
|
|
821
882
|
#
|
|
822
883
|
# @param objectish [String] The first commit or object to compare. Defaults to 'HEAD'.
|
|
823
884
|
# @param obj2 [String, nil] The second commit or object to compare.
|
|
824
|
-
# @
|
|
825
|
-
|
|
826
|
-
|
|
885
|
+
# @param opts [Hash] Options to filter the diff.
|
|
886
|
+
# @option opts [String, Pathname, Array<String, Pathname>] :path_limiter Limit status to specified path(s).
|
|
887
|
+
# @option opts [String, Pathname, Array<String, Pathname>] :path (deprecated) Legacy alias for :path_limiter.
|
|
888
|
+
# @return [Git::DiffPathStatus]
|
|
889
|
+
def diff_path_status(objectish = 'HEAD', obj2 = nil, opts = {})
|
|
890
|
+
path_limiter = if opts.key?(:path_limiter)
|
|
891
|
+
opts[:path_limiter]
|
|
892
|
+
elsif opts.key?(:path)
|
|
893
|
+
Git::Deprecation.warn(
|
|
894
|
+
'Git::Base#diff_path_status :path option is deprecated. Use :path_limiter instead.'
|
|
895
|
+
)
|
|
896
|
+
opts[:path]
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
Git::DiffPathStatus.new(self, objectish, obj2, path_limiter)
|
|
827
900
|
end
|
|
828
901
|
|
|
829
902
|
# Provided for backwards compatibility
|
data/lib/git/diff.rb
CHANGED
|
@@ -18,8 +18,38 @@ module Git
|
|
|
18
18
|
end
|
|
19
19
|
attr_reader :from, :to
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
# Limits the diff to the specified path(s)
|
|
22
|
+
#
|
|
23
|
+
# When called with no arguments (or only nil arguments), removes any existing
|
|
24
|
+
# path filter, showing all files in the diff. Internally stores a single path
|
|
25
|
+
# as a String and multiple paths as an Array for efficiency.
|
|
26
|
+
#
|
|
27
|
+
# @example Limit diff to a single path
|
|
28
|
+
# git.diff('HEAD~3', 'HEAD').path('lib/')
|
|
29
|
+
#
|
|
30
|
+
# @example Limit diff to multiple paths
|
|
31
|
+
# git.diff('HEAD~3', 'HEAD').path('src/', 'docs/', 'README.md')
|
|
32
|
+
#
|
|
33
|
+
# @example Remove path filtering (show all files)
|
|
34
|
+
# diff.path # or diff.path(nil)
|
|
35
|
+
#
|
|
36
|
+
# @param paths [String, Pathname] one or more paths to filter the diff. Pass no arguments to remove filtering.
|
|
37
|
+
# @return [self] returns self for method chaining
|
|
38
|
+
# @raise [ArgumentError] if any path is an Array (use splatted arguments instead)
|
|
39
|
+
#
|
|
40
|
+
def path(*paths)
|
|
41
|
+
validate_paths_not_arrays(paths)
|
|
42
|
+
|
|
43
|
+
cleaned_paths = paths.compact
|
|
44
|
+
|
|
45
|
+
@path = if cleaned_paths.empty?
|
|
46
|
+
nil
|
|
47
|
+
elsif cleaned_paths.length == 1
|
|
48
|
+
cleaned_paths.first
|
|
49
|
+
else
|
|
50
|
+
cleaned_paths
|
|
51
|
+
end
|
|
52
|
+
|
|
23
53
|
self
|
|
24
54
|
end
|
|
25
55
|
|
|
@@ -102,6 +132,14 @@ module Git
|
|
|
102
132
|
|
|
103
133
|
private
|
|
104
134
|
|
|
135
|
+
def validate_paths_not_arrays(paths)
|
|
136
|
+
return unless paths.any? { |p| p.is_a?(Array) }
|
|
137
|
+
|
|
138
|
+
raise ArgumentError,
|
|
139
|
+
'path expects individual arguments, not arrays. ' \
|
|
140
|
+
"Use path('lib/', 'docs/') not path(['lib/', 'docs/'])"
|
|
141
|
+
end
|
|
142
|
+
|
|
105
143
|
def process_full
|
|
106
144
|
return if @full_diff_files
|
|
107
145
|
|
data/lib/git/diff_path_status.rb
CHANGED