git 1.19.1 → 4.4.5

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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.commitlintrc.yml +38 -0
  3. data/.github/copilot-instructions.md +2733 -0
  4. data/.github/pull_request_template.md +17 -0
  5. data/.github/workflows/continuous_integration.yml +92 -21
  6. data/.github/workflows/enforce_conventional_commits.yml +29 -0
  7. data/.github/workflows/experimental_continuous_integration.yml +59 -0
  8. data/.github/workflows/release.yml +53 -0
  9. data/.gitignore +5 -0
  10. data/.husky/commit-msg +1 -0
  11. data/.release-please-manifest.json +3 -0
  12. data/.rubocop.yml +55 -0
  13. data/.rubocop_todo.yml +12 -0
  14. data/.yardopts +4 -1
  15. data/AI_POLICY.md +24 -0
  16. data/CHANGELOG.md +501 -0
  17. data/CODE_OF_CONDUCT.md +25 -0
  18. data/CONTRIBUTING.md +323 -102
  19. data/GOVERNANCE.md +106 -0
  20. data/LICENSE +1 -1
  21. data/MAINTAINERS.md +17 -4
  22. data/README.md +575 -246
  23. data/Rakefile +13 -55
  24. data/git.gemspec +36 -30
  25. data/lib/git/args_builder.rb +111 -0
  26. data/lib/git/author.rb +9 -7
  27. data/lib/git/base.rb +602 -173
  28. data/lib/git/branch.rb +318 -38
  29. data/lib/git/branches.rb +21 -24
  30. data/lib/git/command_line.rb +330 -0
  31. data/lib/git/command_line_result.rb +9 -3
  32. data/lib/git/config.rb +10 -6
  33. data/lib/git/diff.rb +149 -81
  34. data/lib/git/diff_path_status.rb +46 -0
  35. data/lib/git/diff_stats.rb +59 -0
  36. data/lib/git/errors.rb +212 -0
  37. data/lib/git/escaped_path.rb +2 -2
  38. data/lib/git/fsck_object.rb +48 -0
  39. data/lib/git/fsck_result.rb +121 -0
  40. data/lib/git/index.rb +2 -1
  41. data/lib/git/lib.rb +1648 -643
  42. data/lib/git/log.rb +143 -106
  43. data/lib/git/object.rb +151 -125
  44. data/lib/git/path.rb +23 -16
  45. data/lib/git/remote.rb +5 -4
  46. data/lib/git/repository.rb +2 -2
  47. data/lib/git/stash.rb +11 -12
  48. data/lib/git/stashes.rb +16 -15
  49. data/lib/git/status.rb +104 -143
  50. data/lib/git/url.rb +3 -3
  51. data/lib/git/version.rb +3 -1
  52. data/lib/git/working_directory.rb +2 -0
  53. data/lib/git/worktree.rb +6 -5
  54. data/lib/git/worktrees.rb +6 -6
  55. data/lib/git.rb +131 -28
  56. data/package.json +10 -0
  57. data/redesign/1_architecture_existing.md +66 -0
  58. data/redesign/2_architecture_redesign.md +130 -0
  59. data/redesign/3_architecture_implementation.md +138 -0
  60. data/redesign/index.md +34 -0
  61. data/release-please-config.json +36 -0
  62. data/tasks/gem_tasks.rake +10 -0
  63. data/tasks/rubocop.rake +12 -0
  64. data/tasks/test.rake +13 -0
  65. data/tasks/test_gem.rake +12 -0
  66. data/tasks/yard.rake +23 -0
  67. metadata +114 -37
  68. data/.github/stale.yml +0 -25
  69. data/Dockerfile.changelog-rs +0 -12
  70. data/PULL_REQUEST_TEMPLATE.md +0 -9
  71. data/RELEASING.md +0 -70
  72. data/lib/git/base/factory.rb +0 -99
  73. data/lib/git/failed_error.rb +0 -53
  74. data/lib/git/git_execute_error.rb +0 -7
  75. data/lib/git/signaled_error.rb +0 -50
  76. /data/{ISSUE_TEMPLATE.md → .github/issue_template.md} +0 -0
data/lib/git/status.rb CHANGED
@@ -1,108 +1,46 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Git
4
+ # The Status class gets the status of a git repository. It identifies which
5
+ # files have been modified, added, or deleted, including untracked files.
6
+ # The Status object is an Enumerable of StatusFile objects.
2
7
  #
3
- # A class for git status
8
+ # @api public
4
9
  #
5
10
  class Status
6
11
  include Enumerable
7
12
 
13
+ # @param base [Git::Base] The base git object
8
14
  def initialize(base)
9
15
  @base = base
10
- construct_status
11
- end
12
-
13
- #
14
- # Returns an Enumerable containing files that have changed from the
15
- # git base directory
16
- #
17
- # @return [Enumerable]
18
- def changed
19
- @files.select { |_k, f| f.type == 'M' }
20
- end
21
-
22
- #
23
- # Determines whether the given file has been changed.
24
- # File path starts at git base directory
25
- #
26
- # @param file [String] The name of the file.
27
- # @example Check if lib/git.rb has changed.
28
- # changed?('lib/git.rb')
29
- # @return [Boolean]
30
- def changed?(file)
31
- changed.member?(file)
16
+ # The factory returns a hash of file paths to StatusFile objects.
17
+ @files = StatusFileFactory.new(base).construct_files
32
18
  end
33
19
 
34
- #
35
- # Returns an Enumerable containing files that have been added.
36
- # File path starts at git base directory
37
- #
38
- # @return [Enumerable]
39
- def added
40
- @files.select { |_k, f| f.type == 'A' }
41
- end
20
+ # File status collections, memoized for performance.
21
+ def changed = @changed ||= select_files { |f| f.type == 'M' }
22
+ def added = @added ||= select_files { |f| f.type == 'A' }
23
+ def deleted = @deleted ||= select_files { |f| f.type == 'D' }
24
+ # This works with `true` or `nil`
25
+ def untracked = @untracked ||= select_files(&:untracked)
42
26
 
43
- #
44
- # Determines whether the given file has been added to the repository
45
- # File path starts at git base directory
46
- #
47
- # @param file [String] The name of the file.
48
- # @example Check if lib/git.rb is added.
49
- # added?('lib/git.rb')
50
- # @return [Boolean]
51
- def added?(file)
52
- added.member?(file)
53
- end
27
+ # Predicate methods to check the status of a specific file.
28
+ def changed?(file) = file_in_collection?(:changed, file)
29
+ def added?(file) = file_in_collection?(:added, file)
30
+ def deleted?(file) = file_in_collection?(:deleted, file)
31
+ def untracked?(file) = file_in_collection?(:untracked, file)
54
32
 
55
- #
56
- # Returns an Enumerable containing files that have been deleted.
57
- # File path starts at git base directory
58
- #
59
- # @return [Enumerable]
60
- def deleted
61
- @files.select { |_k, f| f.type == 'D' }
62
- end
63
-
64
- #
65
- # Determines whether the given file has been deleted from the repository
66
- # File path starts at git base directory
67
- #
68
- # @param file [String] The name of the file.
69
- # @example Check if lib/git.rb is deleted.
70
- # deleted?('lib/git.rb')
71
- # @return [Boolean]
72
- def deleted?(file)
73
- deleted.member?(file)
74
- end
75
-
76
- #
77
- # Returns an Enumerable containing files that are not tracked in git.
78
- # File path starts at git base directory
79
- #
80
- # @return [Enumerable]
81
- def untracked
82
- @files.select { |_k, f| f.untracked }
83
- end
84
-
85
- #
86
- # Determines whether the given file has is tracked by git.
87
- # File path starts at git base directory
88
- #
89
- # @param file [String] The name of the file.
90
- # @example Check if lib/git.rb is an untracked file.
91
- # untracked?('lib/git.rb')
92
- # @return [Boolean]
93
- def untracked?(file)
94
- untracked.member?(file)
95
- end
33
+ # Access a status file by path, or iterate over all status files.
34
+ def [](file) = @files[file]
35
+ def each(&) = @files.values.each(&)
96
36
 
37
+ # Returns a formatted string representation of the status.
97
38
  def pretty
98
- out = ''
99
- each do |file|
100
- out << pretty_file(file)
101
- end
102
- out << "\n"
103
- out
39
+ map { |file| pretty_file(file) }.join << "\n"
104
40
  end
105
41
 
42
+ private
43
+
106
44
  def pretty_file(file)
107
45
  <<~FILE
108
46
  #{file.path}
@@ -114,84 +52,107 @@ module Git
114
52
  FILE
115
53
  end
116
54
 
117
- # enumerable method
55
+ def select_files(&block)
56
+ @files.select { |_path, file| block.call(file) }
57
+ end
58
+
59
+ def file_in_collection?(collection_name, file_path)
60
+ collection = public_send(collection_name)
61
+ if ignore_case?
62
+ downcased_keys(collection_name).include?(file_path.downcase)
63
+ else
64
+ collection.key?(file_path)
65
+ end
66
+ end
118
67
 
119
- def [](file)
120
- @files[file]
68
+ def downcased_keys(collection_name)
69
+ @_downcased_keys ||= {}
70
+ @_downcased_keys[collection_name] ||=
71
+ public_send(collection_name).keys.to_set(&:downcase)
121
72
  end
122
73
 
123
- def each(&block)
124
- @files.values.each(&block)
74
+ def ignore_case?
75
+ return @_ignore_case if defined?(@_ignore_case)
76
+
77
+ @_ignore_case = (@base.config('core.ignoreCase') == 'true')
78
+ rescue Git::FailedError
79
+ @_ignore_case = false
125
80
  end
126
81
 
127
- # subclass that does heavy lifting
82
+ # Represents a single file's status in the git repository. Each instance
83
+ # holds information about a file's state in the index and working tree.
128
84
  class StatusFile
129
- attr_accessor :path, :type, :stage, :untracked
130
- attr_accessor :mode_index, :mode_repo
131
- attr_accessor :sha_index, :sha_repo
85
+ attr_reader :path, :type, :stage, :mode_index, :mode_repo,
86
+ :sha_index, :sha_repo, :untracked
132
87
 
133
88
  def initialize(base, hash)
134
- @base = base
135
- @path = hash[:path]
136
- @type = hash[:type]
137
- @stage = hash[:stage]
89
+ @base = base
90
+ @path = hash[:path]
91
+ @type = hash[:type]
92
+ @stage = hash[:stage]
138
93
  @mode_index = hash[:mode_index]
139
- @mode_repo = hash[:mode_repo]
140
- @sha_index = hash[:sha_index]
141
- @sha_repo = hash[:sha_repo]
142
- @untracked = hash[:untracked]
94
+ @mode_repo = hash[:mode_repo]
95
+ @sha_index = hash[:sha_index]
96
+ @sha_repo = hash[:sha_repo]
97
+ @untracked = hash[:untracked]
143
98
  end
144
99
 
100
+ # Returns a Git::Object::Blob for either the index or repo version of the file.
145
101
  def blob(type = :index)
146
- if type == :repo
147
- @base.object(@sha_repo)
148
- else
149
- begin
150
- @base.object(@sha_index)
151
- rescue
152
- @base.object(@sha_repo)
153
- end
154
- end
102
+ sha = type == :repo ? sha_repo : (sha_index || sha_repo)
103
+ @base.object(sha) if sha
155
104
  end
156
105
  end
157
106
 
158
- private
159
-
160
- def construct_status
161
- @files = @base.lib.ls_files
162
-
163
- fetch_untracked
164
- fetch_modified
165
- fetch_added
107
+ # A factory class responsible for fetching git status data and building
108
+ # a hash of StatusFile objects.
109
+ # @api private
110
+ class StatusFileFactory
111
+ def initialize(base)
112
+ @base = base
113
+ @lib = base.lib
114
+ end
166
115
 
167
- @files.each do |k, file_hash|
168
- @files[k] = StatusFile.new(@base, file_hash)
116
+ # Gathers all status data and builds a hash of file paths to
117
+ # StatusFile objects.
118
+ def construct_files
119
+ files_data = fetch_all_files_data
120
+ files_data.transform_values do |data|
121
+ StatusFile.new(@base, data)
122
+ end
169
123
  end
170
- end
171
124
 
172
- def fetch_untracked
173
- ignore = @base.lib.ignored_files
125
+ private
174
126
 
175
- root_dir = @base.dir.path
176
- Dir.glob('**/*', File::FNM_DOTMATCH, base: root_dir) do |file|
177
- next if @files[file] || File.directory?(File.join(root_dir, file)) ||
178
- ignore.include?(file) || file =~ %r{^.git\/.+}
127
+ # Fetches and merges status information from multiple git commands.
128
+ def fetch_all_files_data
129
+ files = @lib.ls_files # Start with files tracked in the index.
130
+ merge_untracked_files(files)
131
+ merge_modified_files(files)
132
+ merge_head_diffs(files)
133
+ files
134
+ end
179
135
 
180
- @files[file] = { path: file, untracked: true }
136
+ def merge_untracked_files(files)
137
+ @lib.untracked_files.each do |file|
138
+ files[file] = { path: file, untracked: true }
139
+ end
181
140
  end
182
- end
183
141
 
184
- def fetch_modified
185
- # find modified in tree
186
- @base.lib.diff_files.each do |path, data|
187
- @files[path] ? @files[path].merge!(data) : @files[path] = data
142
+ def merge_modified_files(files)
143
+ # Merge changes between the index and the working directory.
144
+ @lib.diff_files.each do |path, data|
145
+ (files[path] ||= {}).merge!(data)
146
+ end
188
147
  end
189
- end
190
148
 
191
- def fetch_added
192
- # find added but not committed - new files
193
- @base.lib.diff_index('HEAD').each do |path, data|
194
- @files[path] ? @files[path].merge!(data) : @files[path] = data
149
+ def merge_head_diffs(files)
150
+ return if @lib.empty?
151
+
152
+ # Merge changes between HEAD and the index.
153
+ @lib.diff_index('HEAD').each do |path, data|
154
+ (files[path] ||= {}).merge!(data)
155
+ end
195
156
  end
196
157
  end
197
158
  end
data/lib/git/url.rb CHANGED
@@ -23,7 +23,7 @@ module Git
23
23
  :(?!/) # : serparator is required, but must not be followed by /
24
24
  (?<path>.*?) # path is required
25
25
  $
26
- }x.freeze
26
+ }x
27
27
 
28
28
  # Parse a Git URL and return an Addressable::URI object
29
29
  #
@@ -118,9 +118,9 @@ module Git
118
118
  #
119
119
  def to_s
120
120
  if user
121
- "#{user}@#{host}:#{path[1..-1]}"
121
+ "#{user}@#{host}:#{path[1..]}"
122
122
  else
123
- "#{host}:#{path[1..-1]}"
123
+ "#{host}:#{path[1..]}"
124
124
  end
125
125
  end
126
126
  end
data/lib/git/version.rb CHANGED
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Git
2
4
  # The current gem version
3
5
  # @return [String] the current gem version.
4
- VERSION='1.19.1'
6
+ VERSION = '4.4.5'
5
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Git
2
4
  class WorkingDirectory < Git::Path
3
5
  end
data/lib/git/worktree.rb CHANGED
@@ -1,14 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'git/path'
2
4
 
3
5
  module Git
4
-
5
- class Worktree < Path
6
-
7
- attr_accessor :full, :dir, :gcommit
6
+ # A worktree in a Git repository
7
+ class Worktree
8
+ attr_accessor :full, :dir
8
9
 
9
10
  def initialize(base, dir, gcommit = nil)
10
11
  @full = dir
11
- @full += ' ' + gcommit if !gcommit.nil?
12
+ @full += " #{gcommit}" unless gcommit.nil?
12
13
  @base = base
13
14
  @dir = dir
14
15
  @gcommit = gcommit
data/lib/git/worktrees.rb CHANGED
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Git
2
4
  # object that holds all the available worktrees
3
5
  class Worktrees
4
-
5
6
  include Enumerable
6
7
 
7
8
  def initialize(base)
@@ -21,20 +22,19 @@ module Git
21
22
  @worktrees.size
22
23
  end
23
24
 
24
- def each(&block)
25
- @worktrees.values.each(&block)
25
+ def each(&)
26
+ @worktrees.values.each(&)
26
27
  end
27
28
 
28
29
  def [](worktree_name)
29
- @worktrees.values.inject(@worktrees) do |worktrees, worktree|
30
+ @worktrees.values.each_with_object(@worktrees) do |worktree, worktrees|
30
31
  worktrees[worktree.full] ||= worktree
31
- worktrees
32
32
  end[worktree_name.to_s]
33
33
  end
34
34
 
35
35
  def to_s
36
36
  out = ''
37
- @worktrees.each do |k, b|
37
+ @worktrees.each_value do |b|
38
38
  out << b.to_s << "\n"
39
39
  end
40
40
  out
data/lib/git.rb CHANGED
@@ -1,19 +1,44 @@
1
- # Add the directory containing this file to the start of the load path if it
2
- # isn't there already.
3
- $:.unshift(File.dirname(__FILE__)) unless
4
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/deprecation'
4
+
5
+ # Define Git::Deprecation before requiring the rest of the library to ensure that
6
+ # any deprecation warnings emitted during the loading of the library are properly
7
+ # configured according to the GIT_DEPRECATION_BEHAVIOR environment variable.
8
+ #
9
+ module Git
10
+ # The deprecation instance used to emit deprecation warnings for the Git gem
11
+ #
12
+ # @api public
13
+ Deprecation = ActiveSupport::Deprecation.new('5.0.0', 'Git')
14
+
15
+ if (behavior = ENV.fetch('GIT_DEPRECATION_BEHAVIOR', nil))
16
+ behavior = behavior.strip
17
+ allowed_behaviors = ActiveSupport::Deprecation::DEFAULT_BEHAVIORS.keys.map(&:to_s)
18
+
19
+ unless allowed_behaviors.include?(behavior)
20
+ raise ArgumentError,
21
+ "Invalid GIT_DEPRECATION_BEHAVIOR=#{behavior.inspect}; " \
22
+ "expected one of: #{allowed_behaviors.join(', ')}"
23
+ end
24
+
25
+ Deprecation.behavior = behavior.to_sym
26
+ end
27
+ end
5
28
 
6
29
  require 'git/author'
7
30
  require 'git/base'
8
31
  require 'git/branch'
9
32
  require 'git/branches'
10
33
  require 'git/command_line_result'
34
+ require 'git/command_line'
11
35
  require 'git/config'
12
36
  require 'git/diff'
13
37
  require 'git/encoding_utils'
38
+ require 'git/errors'
14
39
  require 'git/escaped_path'
15
- require 'git/failed_error'
16
- require 'git/git_execute_error'
40
+ require 'git/fsck_object'
41
+ require 'git/fsck_result'
17
42
  require 'git/index'
18
43
  require 'git/lib'
19
44
  require 'git/log'
@@ -21,7 +46,6 @@ require 'git/object'
21
46
  require 'git/path'
22
47
  require 'git/remote'
23
48
  require 'git/repository'
24
- require 'git/signaled_error'
25
49
  require 'git/status'
26
50
  require 'git/stash'
27
51
  require 'git/stashes'
@@ -38,17 +62,33 @@ require 'git/worktrees'
38
62
  #
39
63
  # @author Scott Chacon (mailto:schacon@gmail.com)
40
64
  #
41
- module Git
42
- #g.config('user.name', 'Scott Chacon') # sets value
43
- #g.config('user.email', 'email@email.com') # sets value
44
- #g.config('user.name') # returns 'Scott Chacon'
45
- #g.config # returns whole config hash
65
+ module Git # rubocop:disable Style/OneClassPerFile
66
+ # Internal alias for Git::Lib, used by the gem itself after the public constant
67
+ # is deprecated. Code outside the gem should not reference this constant.
68
+ # @api private
69
+ LibImpl = remove_const(:Lib)
70
+
71
+ # @api private
72
+ def self.const_missing(name)
73
+ return super unless name == :Lib
74
+
75
+ Git::Deprecation.warn(
76
+ 'Git::Lib is deprecated and will be removed in version 5.x. ' \
77
+ 'Use the #lib accessor on the object returned by Git.init, Git.open, or Git.clone instead.'
78
+ )
79
+ const_set(:Lib, LibImpl)
80
+ end
81
+
82
+ # g.config('user.name', 'Scott Chacon') # sets value
83
+ # g.config('user.email', 'email@email.com') # sets value
84
+ # g.config('user.name') # returns 'Scott Chacon'
85
+ # g.config # returns whole config hash
46
86
  def config(name = nil, value = nil)
47
- lib = Git::Lib.new
48
- if(name && value)
87
+ lib = LibImpl.new
88
+ if name && value
49
89
  # set value
50
90
  lib.config_set(name, value)
51
- elsif (name)
91
+ elsif name
52
92
  # return value
53
93
  lib.config_get(name)
54
94
  else
@@ -62,7 +102,7 @@ module Git
62
102
  end
63
103
 
64
104
  def self.config
65
- return Base.config
105
+ Base.config
66
106
  end
67
107
 
68
108
  def global_config(name = nil, value = nil)
@@ -90,6 +130,12 @@ module Git
90
130
  # @param [Hash] options The options for this command (see list of valid
91
131
  # options below)
92
132
  #
133
+ # @option options [String, nil] :git_ssh An optional custom SSH command
134
+ #
135
+ # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
136
+ # - If nil, disables SSH for this instance.
137
+ # - If a non-empty string, uses that value for this instance.
138
+ #
93
139
  # @option options [Logger] :log A logger to use for Git operations. Git commands
94
140
  # are logged at the `:info` level. Additional logging is done at the `:debug`
95
141
  # level.
@@ -142,6 +188,20 @@ module Git
142
188
  # @option options [String] :filter Request that the server send a partial
143
189
  # clone according to the given filter
144
190
  #
191
+ # @option options [Boolean, nil] :single_branch Control whether the clone
192
+ # limits fetch refspecs to a single branch.
193
+ # - If nil (default), no flag is passed and the Git default is used.
194
+ # - If true, `--single-branch` is passed to limit the refspec to the
195
+ # checkout branch.
196
+ # - If false, `--no-single-branch` is passed to broaden the refspec (useful
197
+ # for shallow clones that should include all branches).
198
+ #
199
+ # @option options [String, nil] :git_ssh An optional custom SSH command
200
+ #
201
+ # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
202
+ # - If nil, disables SSH for this instance.
203
+ # - If a non-empty string, uses that value for this instance.
204
+ #
145
205
  # @option options [Logger] :log A logger to use for Git operations. Git
146
206
  # commands are logged at the `:info` level. Additional logging is done
147
207
  # at the `:debug` level.
@@ -184,11 +244,18 @@ module Git
184
244
  # config: ['user.name=John Doe', 'user.email=john@doe.com']
185
245
  # )
186
246
  #
247
+ # @example Clone using a specific SSH key
248
+ # git = Git.clone(
249
+ # 'git@github.com:ruby-git/ruby-git.git',
250
+ # 'local-dir',
251
+ # git_ssh: 'ssh -i /path/to/private_key'
252
+ # )
253
+ #
187
254
  # @return [Git::Base] an object that can execute git commands in the context
188
255
  # of the cloned local working copy or cloned repository.
189
256
  #
190
257
  def self.clone(repository_url, directory = nil, options = {})
191
- clone_to_options = options.select { |key, _value| %i[bare mirror].include?(key) }
258
+ clone_to_options = options.slice(:bare, :mirror)
192
259
  directory ||= Git::URL.clone_to(repository_url, **clone_to_options)
193
260
  Base.clone(repository_url, directory, options)
194
261
  end
@@ -213,7 +280,8 @@ module Git
213
280
  # @example with the logging option
214
281
  # logger = Logger.new(STDOUT, level: Logger::INFO)
215
282
  # Git.default_branch('.', log: logger) # => 'master'
216
- # I, [2022-04-13T16:01:33.221596 #18415] INFO -- : git '-c' 'core.quotePath=true' '-c' 'color.ui=false' ls-remote '--symref' '--' '.' 'HEAD' 2>&1
283
+ # I, [2022-04-13T16:01:33.221596 #18415] INFO -- : git '-c' 'core.quotePath=true'
284
+ # '-c' 'color.ui=false' ls-remote '--symref' '--' '.' 'HEAD' 2>&1
217
285
  #
218
286
  # @param repository [URI, Pathname, String] The (possibly remote) repository to get the default branch name for
219
287
  #
@@ -240,25 +308,37 @@ module Git
240
308
  # See +clone+ for options. Does not obey the <tt>:remote</tt> option,
241
309
  # since the .git info will be deleted anyway; always uses the default
242
310
  # remote, 'origin.'
311
+ #
312
+ # <tt>options[:branch]</tt> is the short name of the ref: a branch name such as
313
+ # 'main' or a tag name such as 'v1.0.0'. A full ref path such as
314
+ # 'refs/tags/v1.0.0' or a commit SHA is not accepted.
315
+ #
316
+ # Removing +.git+ is not atomic. If it fails, the exported files are complete and
317
+ # usable, but the directory keeps whatever part of +.git+ could not be deleted.
318
+ # Nothing is cleaned up, because the exported files are the deliverable and the
319
+ # leftover has to be removed by hand once the cause of the failure is fixed.
320
+ #
321
+ # @raise [SystemCallError] if the +.git+ directory cannot be removed. The exported
322
+ # files are left in place, and the directory keeps whatever part of +.git+ could
323
+ # not be deleted.
243
324
  def self.export(repository, name, options = {})
244
325
  options.delete(:remote)
245
- repo = clone(repository, name, {:depth => 1}.merge(options))
246
- repo.checkout("origin/#{options[:branch]}") if options[:branch]
326
+ repo = clone(repository, name, { depth: 1 }.merge(options))
247
327
  FileUtils.rm_r File.join(repo.dir.to_s, '.git')
248
328
  end
249
329
 
250
330
  # Same as g.config, but forces it to be at the global level
251
331
  #
252
- #g.config('user.name', 'Scott Chacon') # sets value
253
- #g.config('user.email', 'email@email.com') # sets value
254
- #g.config('user.name') # returns 'Scott Chacon'
255
- #g.config # returns whole config hash
332
+ # g.config('user.name', 'Scott Chacon') # sets value
333
+ # g.config('user.email', 'email@email.com') # sets value
334
+ # g.config('user.name') # returns 'Scott Chacon'
335
+ # g.config # returns whole config hash
256
336
  def self.global_config(name = nil, value = nil)
257
- lib = Git::Lib.new(nil, nil)
258
- if(name && value)
337
+ lib = LibImpl.new(nil, nil)
338
+ if name && value
259
339
  # set value
260
340
  lib.global_config_set(name, value)
261
- elsif (name)
341
+ elsif name
262
342
  # return value
263
343
  lib.global_config_get(name)
264
344
  else
@@ -296,6 +376,12 @@ module Git
296
376
  # and converted to an absolute path using
297
377
  # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
298
378
  #
379
+ # @option options [String, nil] :git_ssh An optional custom SSH command
380
+ #
381
+ # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
382
+ # - If nil, disables SSH for this instance.
383
+ # - If a non-empty string, uses that value for this instance.
384
+ #
299
385
  # @option options [Logger] :log A logger to use for Git operations. Git
300
386
  # commands are logged at the `:info` level. Additional logging is done
301
387
  # at the `:debug` level.
@@ -330,7 +416,7 @@ module Git
330
416
  # @param [String|NilClass] location the target repository location or nil for '.'
331
417
  # @return [{String=>Hash}] the available references of the target repo.
332
418
  def self.ls_remote(location = nil, options = {})
333
- Git::Lib.new.ls_remote(location, options)
419
+ LibImpl.new.ls_remote(location, options)
334
420
  end
335
421
 
336
422
  # Open a an existing Git working directory
@@ -370,6 +456,12 @@ module Git
370
456
  # @option options [Pathname] :index used to specify a non-standard path to an
371
457
  # index file. The default is `"#{working_dir}/.git/index"`
372
458
  #
459
+ # @option options [String, nil] :git_ssh An optional custom SSH command
460
+ #
461
+ # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
462
+ # - If nil, disables SSH for this instance.
463
+ # - If a non-empty string, uses that value for this instance.
464
+ #
373
465
  # @option options [Logger] :log A logger to use for Git operations. Git
374
466
  # commands are logged at the `:info` level. Additional logging is done
375
467
  # at the `:debug` level.
@@ -380,4 +472,15 @@ module Git
380
472
  def self.open(working_dir, options = {})
381
473
  Base.open(working_dir, options)
382
474
  end
475
+
476
+ # Return the version of the git binary
477
+ #
478
+ # @example
479
+ # Git.binary_version # => [2, 46, 0]
480
+ #
481
+ # @return [Array<Integer>] the version of the git binary
482
+ #
483
+ def self.binary_version(binary_path = Git::Base.config.binary_path)
484
+ Base.binary_version(binary_path)
485
+ end
383
486
  end
data/package.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "devDependencies": {
3
+ "@commitlint/cli": "^19.8.0",
4
+ "@commitlint/config-conventional": "^19.8.0",
5
+ "husky": "^9.1.7"
6
+ },
7
+ "scripts": {
8
+ "prepare": "husky"
9
+ }
10
+ }