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.
data/lib/git/lib.rb CHANGED
@@ -5,6 +5,7 @@ require_relative 'args_builder'
5
5
  require 'git/command_line'
6
6
  require 'git/errors'
7
7
  require 'logger'
8
+ require 'pathname'
8
9
  require 'pp'
9
10
  require 'process_executer'
10
11
  require 'stringio'
@@ -64,6 +65,7 @@ module Git
64
65
  #
65
66
  def initialize(base = nil, logger = nil)
66
67
  @logger = logger || Logger.new(nil)
68
+ @git_ssh = :use_global_config
67
69
 
68
70
  case base
69
71
  when Git::Base
@@ -98,6 +100,21 @@ module Git
98
100
  { keys: [:filter], flag: '--filter', type: :valued_space },
99
101
  { keys: %i[remote origin], flag: '--origin', type: :valued_space },
100
102
  { keys: [:config], flag: '--config', type: :repeatable_valued_space },
103
+ {
104
+ keys: [:single_branch],
105
+ type: :custom,
106
+ validator: ->(value) { [nil, true, false].include?(value) },
107
+ builder: lambda do |value|
108
+ case value
109
+ when true
110
+ ['--single-branch']
111
+ when false
112
+ ['--no-single-branch']
113
+ else
114
+ []
115
+ end
116
+ end
117
+ },
101
118
  {
102
119
  keys: [:depth],
103
120
  type: :custom,
@@ -255,7 +272,8 @@ module Git
255
272
  #
256
273
  # Only :between or :object options can be used, not both.
257
274
  #
258
- # @option opts :path_limiter [Array<String>, String] only include commits that impact files from the specified paths
275
+ # @option opts :path_limiter [String, Pathname, Array<String, Pathname>] only
276
+ # include commits that impact files from the specified paths
259
277
  #
260
278
  # @return [Array<String>] the log output
261
279
  #
@@ -310,7 +328,7 @@ module Git
310
328
  #
311
329
  # Only :between or :object options can be used, not both.
312
330
  #
313
- # @option opts :path_limiter [Array<String>, String] only include commits that
331
+ # @option opts :path_limiter [String, Pathname, Array<String, Pathname>] only include commits that
314
332
  # impact files from the specified paths
315
333
  #
316
334
  # @option opts :skip [Integer]
@@ -719,17 +737,17 @@ module Git
719
737
  end
720
738
 
721
739
  def worktree_add(dir, commitish = nil)
722
- return command('worktree', 'add', dir, commitish) unless commitish.nil?
740
+ return worktree_command('worktree', 'add', dir, commitish) unless commitish.nil?
723
741
 
724
- command('worktree', 'add', dir)
742
+ worktree_command('worktree', 'add', dir)
725
743
  end
726
744
 
727
745
  def worktree_remove(dir)
728
- command('worktree', 'remove', dir)
746
+ worktree_command('worktree', 'remove', dir)
729
747
  end
730
748
 
731
749
  def worktree_prune
732
- command('worktree', 'prune')
750
+ worktree_command('worktree', 'prune')
733
751
  end
734
752
 
735
753
  def list_files(ref_dir)
@@ -824,6 +842,53 @@ module Git
824
842
  raise ArgumentError, "Invalid #{arg_name}: '#{invalid_args.join("', '")}'"
825
843
  end
826
844
 
845
+ # Normalizes path specifications for Git commands
846
+ #
847
+ # Converts a single path or array of paths into a consistent array format
848
+ # suitable for appending to Git command arguments after '--'. Empty strings
849
+ # are filtered out after conversion.
850
+ #
851
+ # @param pathspecs [String, Pathname, Array<String, Pathname>, nil] path(s) to normalize
852
+ # @param arg_name [String] name of the argument for error messages
853
+ # @return [Array<String>, nil] normalized array of path strings, or nil if empty/nil input
854
+ # @raise [ArgumentError] if any path is not a String or Pathname
855
+ #
856
+ def normalize_pathspecs(pathspecs, arg_name)
857
+ return nil unless pathspecs
858
+
859
+ normalized = Array(pathspecs)
860
+ validate_pathspec_types(normalized, arg_name)
861
+
862
+ normalized = normalized.map(&:to_s).reject(&:empty?)
863
+ return nil if normalized.empty?
864
+
865
+ normalized
866
+ end
867
+
868
+ # Validates that all pathspecs are String or Pathname objects
869
+ #
870
+ # @param pathspecs [Array] the pathspecs to validate
871
+ # @param arg_name [String] name of the argument for error messages
872
+ # @raise [ArgumentError] if any path is not a String or Pathname
873
+ #
874
+ def validate_pathspec_types(pathspecs, arg_name)
875
+ return if pathspecs.all? { |path| path.is_a?(String) || path.is_a?(Pathname) }
876
+
877
+ raise ArgumentError, "Invalid #{arg_name}: must be a String, Pathname, or Array of Strings/Pathnames"
878
+ end
879
+
880
+ # Handle deprecated :path option in favor of :path_limiter
881
+ def handle_deprecated_path_option(opts)
882
+ if opts.key?(:path_limiter)
883
+ opts[:path_limiter]
884
+ elsif opts.key?(:path)
885
+ Git::Deprecation.warn(
886
+ 'Git::Lib#diff_path_status :path option is deprecated. Use :path_limiter instead.'
887
+ )
888
+ opts[:path]
889
+ end
890
+ end
891
+
827
892
  DIFF_FULL_OPTION_MAP = [
828
893
  { type: :static, flag: '-p' },
829
894
  { keys: [:path_limiter], type: :validate_only }
@@ -836,8 +901,8 @@ module Git
836
901
  args = build_args(opts, DIFF_FULL_OPTION_MAP)
837
902
  args.push(obj1, obj2).compact!
838
903
 
839
- if (path = opts[:path_limiter]) && path.is_a?(String)
840
- args.push('--', path)
904
+ if (pathspecs = normalize_pathspecs(opts[:path_limiter], 'path limiter'))
905
+ args.push('--', *pathspecs)
841
906
  end
842
907
 
843
908
  command('diff', *args)
@@ -855,8 +920,8 @@ module Git
855
920
  args = build_args(opts, DIFF_STATS_OPTION_MAP)
856
921
  args.push(obj1, obj2).compact!
857
922
 
858
- if (path = opts[:path_limiter]) && path.is_a?(String)
859
- args.push('--', path)
923
+ if (pathspecs = normalize_pathspecs(opts[:path_limiter], 'path limiter'))
924
+ args.push('--', *pathspecs)
860
925
  end
861
926
 
862
927
  output_lines = command_lines('diff', *args)
@@ -865,6 +930,7 @@ module Git
865
930
 
866
931
  DIFF_PATH_STATUS_OPTION_MAP = [
867
932
  { type: :static, flag: '--name-status' },
933
+ { keys: [:path_limiter], type: :validate_only },
868
934
  { keys: [:path], type: :validate_only }
869
935
  ].freeze
870
936
 
@@ -874,7 +940,11 @@ module Git
874
940
 
875
941
  args = build_args(opts, DIFF_PATH_STATUS_OPTION_MAP)
876
942
  args.push(reference1, reference2).compact!
877
- args.push('--', opts[:path]) if opts[:path]
943
+
944
+ path_limiter = handle_deprecated_path_option(opts)
945
+ if (pathspecs = normalize_pathspecs(path_limiter, 'path limiter'))
946
+ args.push('--', *pathspecs)
947
+ end
878
948
 
879
949
  parse_diff_path_status(args)
880
950
  end
@@ -1319,6 +1389,20 @@ module Git
1319
1389
  command('remote', *command_args)
1320
1390
  end
1321
1391
 
1392
+ REMOTE_SET_BRANCHES_OPTION_MAP = [
1393
+ { keys: [:add], flag: '--add', type: :boolean }
1394
+ ].freeze
1395
+
1396
+ def remote_set_branches(name, branches, opts = {})
1397
+ ArgsBuilder.validate!(opts, REMOTE_SET_BRANCHES_OPTION_MAP)
1398
+
1399
+ flags = build_args(opts, REMOTE_SET_BRANCHES_OPTION_MAP)
1400
+ branch_args = Array(branches).flatten
1401
+ command_args = ['set-branches'] + flags + [name] + branch_args
1402
+
1403
+ command('remote', *command_args)
1404
+ end
1405
+
1322
1406
  def remote_set_url(name, url)
1323
1407
  arr_opts = ['set-url']
1324
1408
  arr_opts << name
@@ -1628,12 +1712,14 @@ module Git
1628
1712
  @git_dir = base_object.repo.path
1629
1713
  @git_index_file = base_object.index&.path
1630
1714
  @git_work_dir = base_object.dir&.path
1715
+ @git_ssh = base_object.git_ssh
1631
1716
  end
1632
1717
 
1633
1718
  def initialize_from_hash(base_hash)
1634
1719
  @git_dir = base_hash[:repository]
1635
1720
  @git_index_file = base_hash[:index]
1636
1721
  @git_work_dir = base_hash[:working_directory]
1722
+ @git_ssh = base_hash.key?(:git_ssh) ? base_hash[:git_ssh] : :use_global_config
1637
1723
  end
1638
1724
 
1639
1725
  def return_base_opts_from_clone(clone_dir, opts)
@@ -1641,6 +1727,7 @@ module Git
1641
1727
  base_opts[:repository] = clone_dir if opts[:bare] || opts[:mirror]
1642
1728
  base_opts[:working_directory] = clone_dir unless opts[:bare] || opts[:mirror]
1643
1729
  base_opts[:log] = opts[:log] if opts[:log]
1730
+ base_opts[:git_ssh] = opts[:git_ssh] if opts.key?(:git_ssh)
1644
1731
  base_opts
1645
1732
  end
1646
1733
 
@@ -1883,14 +1970,63 @@ module Git
1883
1970
  op.split("\n")
1884
1971
  end
1885
1972
 
1886
- def env_overrides
1973
+ # Returns a hash of environment variable overrides for git commands
1974
+ #
1975
+ # This method builds a hash of environment variables that control git's behavior,
1976
+ # such as the git directory, working tree, and index file locations.
1977
+ #
1978
+ # @param additional_overrides [Hash] additional environment variables to set or unset
1979
+ #
1980
+ # Keys should be environment variable names (String) and values should be either:
1981
+ # * A String value to set the environment variable
1982
+ # * `nil` to unset the environment variable
1983
+ #
1984
+ # Per Process.spawn semantics, setting a key to `nil` will unset that environment
1985
+ # variable, removing it from the environment passed to the git command.
1986
+ #
1987
+ # @return [Hash<String, String|nil>] environment variable overrides
1988
+ #
1989
+ # @example Basic usage with default environment variables
1990
+ # env_overrides
1991
+ # # => { 'GIT_DIR' => '/path/to/.git', 'GIT_WORK_TREE' => '/path/to/worktree', ... }
1992
+ #
1993
+ # @example Adding a custom environment variable
1994
+ # env_overrides('GIT_TRACE' => '1')
1995
+ # # => { 'GIT_DIR' => '/path/to/.git', ..., 'GIT_TRACE' => '1' }
1996
+ #
1997
+ # @example Unsetting an environment variable (used by worktree_command_line)
1998
+ # env_overrides('GIT_INDEX_FILE' => nil)
1999
+ # # => { 'GIT_DIR' => '/path/to/.git', 'GIT_WORK_TREE' => '/path/to/worktree',
2000
+ # # 'GIT_INDEX_FILE' => nil, 'GIT_SSH' => <git_ssh_value>, 'LC_ALL' => 'en_US.UTF-8' }
2001
+ # # When passed to Process.spawn, GIT_INDEX_FILE will be unset in the environment
2002
+ #
2003
+ # @see https://ruby-doc.org/core/Process.html#method-c-spawn Process.spawn
2004
+ #
2005
+ # @api private
2006
+ #
2007
+ def env_overrides(**additional_overrides)
1887
2008
  {
1888
2009
  'GIT_DIR' => @git_dir,
1889
2010
  'GIT_WORK_TREE' => @git_work_dir,
1890
2011
  'GIT_INDEX_FILE' => @git_index_file,
1891
- 'GIT_SSH' => Git::Base.config.git_ssh,
2012
+ 'GIT_SSH' => resolved_git_ssh,
1892
2013
  'LC_ALL' => 'en_US.UTF-8'
1893
- }
2014
+ }.merge(additional_overrides)
2015
+ end
2016
+
2017
+ # Resolve the git_ssh value to use for this instance
2018
+ #
2019
+ # @return [String, nil] the resolved git_ssh value
2020
+ #
2021
+ # Returns the global config value if @git_ssh is the sentinel :use_global_config,
2022
+ # otherwise returns @git_ssh (which may be nil or a string)
2023
+ #
2024
+ # @api private
2025
+ #
2026
+ def resolved_git_ssh
2027
+ return Git::Base.config.git_ssh if @git_ssh == :use_global_config
2028
+
2029
+ @git_ssh
1894
2030
  end
1895
2031
 
1896
2032
  def global_opts
@@ -1906,6 +2042,46 @@ module Git
1906
2042
  Git::CommandLine.new(env_overrides, Git::Base.config.binary_path, global_opts, @logger)
1907
2043
  end
1908
2044
 
2045
+ # Returns a command line instance without GIT_INDEX_FILE for worktree commands
2046
+ #
2047
+ # Git worktrees manage their own index files and setting GIT_INDEX_FILE
2048
+ # causes corruption of both the main worktree and new worktree indexes.
2049
+ #
2050
+ # @return [Git::CommandLine]
2051
+ # @api private
2052
+ #
2053
+ def worktree_command_line
2054
+ @worktree_command_line ||=
2055
+ Git::CommandLine.new(env_overrides('GIT_INDEX_FILE' => nil), Git::Base.config.binary_path, global_opts,
2056
+ @logger)
2057
+ end
2058
+
2059
+ # @overload worktree_command(*args, **options_hash)
2060
+ # Runs a git worktree command and returns the output
2061
+ #
2062
+ # This method is similar to #command but uses a command line instance
2063
+ # that excludes GIT_INDEX_FILE from the environment to prevent index corruption.
2064
+ #
2065
+ # @param args [Array<String>] the command arguments
2066
+ # @param options_hash [Hash] the options to pass to the command
2067
+ #
2068
+ # @return [String] the command's stdout
2069
+ #
2070
+ # @see #command
2071
+ #
2072
+ # @api private
2073
+ #
2074
+ def worktree_command(*, **options_hash)
2075
+ options_hash = COMMAND_ARG_DEFAULTS.merge(options_hash)
2076
+ options_hash[:timeout] ||= Git.config.timeout
2077
+
2078
+ extra_options = options_hash.keys - COMMAND_ARG_DEFAULTS.keys
2079
+ raise ArgumentError, "Unknown options: #{extra_options.join(', ')}" if extra_options.any?
2080
+
2081
+ result = worktree_command_line.run(*, **options_hash)
2082
+ result.stdout
2083
+ end
2084
+
1909
2085
  # Runs a git command and returns the output
1910
2086
  #
1911
2087
  # Additional args are passed to the command line. They should exclude the 'git'
data/lib/git/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  module Git
4
4
  # The current gem version
5
5
  # @return [String] the current gem version.
6
- VERSION = '4.0.7'
6
+ VERSION = '4.1.0'
7
7
  end
data/lib/git.rb CHANGED
@@ -93,6 +93,12 @@ module Git
93
93
  # @param [Hash] options The options for this command (see list of valid
94
94
  # options below)
95
95
  #
96
+ # @option options [String, nil] :git_ssh An optional custom SSH command
97
+ #
98
+ # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
99
+ # - If nil, disables SSH for this instance.
100
+ # - If a non-empty string, uses that value for this instance.
101
+ #
96
102
  # @option options [Logger] :log A logger to use for Git operations. Git commands
97
103
  # are logged at the `:info` level. Additional logging is done at the `:debug`
98
104
  # level.
@@ -145,6 +151,20 @@ module Git
145
151
  # @option options [String] :filter Request that the server send a partial
146
152
  # clone according to the given filter
147
153
  #
154
+ # @option options [Boolean, nil] :single_branch Control whether the clone
155
+ # limits fetch refspecs to a single branch.
156
+ # - If nil (default), no flag is passed and the Git default is used.
157
+ # - If true, `--single-branch` is passed to limit the refspec to the
158
+ # checkout branch.
159
+ # - If false, `--no-single-branch` is passed to broaden the refspec (useful
160
+ # for shallow clones that should include all branches).
161
+ #
162
+ # @option options [String, nil] :git_ssh An optional custom SSH command
163
+ #
164
+ # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
165
+ # - If nil, disables SSH for this instance.
166
+ # - If a non-empty string, uses that value for this instance.
167
+ #
148
168
  # @option options [Logger] :log A logger to use for Git operations. Git
149
169
  # commands are logged at the `:info` level. Additional logging is done
150
170
  # at the `:debug` level.
@@ -187,6 +207,13 @@ module Git
187
207
  # config: ['user.name=John Doe', 'user.email=john@doe.com']
188
208
  # )
189
209
  #
210
+ # @example Clone using a specific SSH key
211
+ # git = Git.clone(
212
+ # 'git@github.com:ruby-git/ruby-git.git',
213
+ # 'local-dir',
214
+ # git_ssh: 'ssh -i /path/to/private_key'
215
+ # )
216
+ #
190
217
  # @return [Git::Base] an object that can execute git commands in the context
191
218
  # of the cloned local working copy or cloned repository.
192
219
  #
@@ -300,6 +327,12 @@ module Git
300
327
  # and converted to an absolute path using
301
328
  # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
302
329
  #
330
+ # @option options [String, nil] :git_ssh An optional custom SSH command
331
+ #
332
+ # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
333
+ # - If nil, disables SSH for this instance.
334
+ # - If a non-empty string, uses that value for this instance.
335
+ #
303
336
  # @option options [Logger] :log A logger to use for Git operations. Git
304
337
  # commands are logged at the `:info` level. Additional logging is done
305
338
  # at the `:debug` level.
@@ -374,6 +407,12 @@ module Git
374
407
  # @option options [Pathname] :index used to specify a non-standard path to an
375
408
  # index file. The default is `"#{working_dir}/.git/index"`
376
409
  #
410
+ # @option options [String, nil] :git_ssh An optional custom SSH command
411
+ #
412
+ # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
413
+ # - If nil, disables SSH for this instance.
414
+ # - If a non-empty string, uses that value for this instance.
415
+ #
377
416
  # @option options [Logger] :log A logger to use for Git operations. Git
378
417
  # commands are logged at the `:info` level. Additional logging is done
379
418
  # at the `:debug` level.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.7
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chacon and others
@@ -305,8 +305,8 @@ licenses:
305
305
  metadata:
306
306
  homepage_uri: http://github.com/ruby-git/ruby-git
307
307
  source_code_uri: http://github.com/ruby-git/ruby-git
308
- changelog_uri: https://rubydoc.info/gems/git/4.0.7/file/CHANGELOG.md
309
- documentation_uri: https://rubydoc.info/gems/git/4.0.7
308
+ changelog_uri: https://rubydoc.info/gems/git/4.1.0/file/CHANGELOG.md
309
+ documentation_uri: https://rubydoc.info/gems/git/4.1.0
310
310
  rubygems_mfa_required: 'true'
311
311
  rdoc_options: []
312
312
  require_paths: