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/object.rb CHANGED
@@ -1,16 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/author'
4
+ require 'git/diff'
5
+ require 'git/errors'
6
+ require 'git/log'
7
+
1
8
  module Git
2
-
3
- class GitTagNameDoesNotExist< StandardError
4
- end
5
-
6
9
  # represents a git object
7
10
  class Object
8
-
11
+ # A base class for all Git objects
9
12
  class AbstractObject
10
13
  attr_accessor :objectish, :type, :mode
11
14
 
12
15
  attr_writer :size
13
-
16
+
14
17
  def initialize(base, objectish)
15
18
  @base = base
16
19
  @objectish = objectish.to_s
@@ -21,140 +24,133 @@ module Git
21
24
  end
22
25
 
23
26
  def sha
24
- @sha ||= @base.lib.revparse(@objectish)
27
+ @sha ||= @base.lib.rev_parse(@objectish)
25
28
  end
26
-
29
+
27
30
  def size
28
- @size ||= @base.lib.object_size(@objectish)
31
+ @size ||= @base.lib.cat_file_size(@objectish)
29
32
  end
30
-
33
+
31
34
  # Get the object's contents.
32
35
  # If no block is given, the contents are cached in memory and returned as a string.
33
36
  # If a block is given, it yields an IO object (via IO::popen) which could be used to
34
37
  # read a large file in chunks.
35
38
  #
36
39
  # Use this for large files so that they are not held in memory.
37
- def contents(&block)
40
+ def contents(&)
38
41
  if block_given?
39
- @base.lib.object_contents(@objectish, &block)
42
+ @base.lib.cat_file_contents(@objectish, &)
40
43
  else
41
- @contents ||= @base.lib.object_contents(@objectish)
44
+ @contents ||= @base.lib.cat_file_contents(@objectish)
42
45
  end
43
46
  end
44
-
47
+
45
48
  def contents_array
46
- self.contents.split("\n")
49
+ contents.split("\n")
47
50
  end
48
-
51
+
49
52
  def to_s
50
53
  @objectish
51
54
  end
52
-
55
+
53
56
  def grep(string, path_limiter = nil, opts = {})
54
- opts = {:object => sha, :path_limiter => path_limiter}.merge(opts)
57
+ opts = { object: sha, path_limiter: path_limiter }.merge(opts)
55
58
  @base.lib.grep(string, opts)
56
59
  end
57
-
60
+
58
61
  def diff(objectish)
59
62
  Git::Diff.new(@base, @objectish, objectish)
60
63
  end
61
-
64
+
62
65
  def log(count = 30)
63
66
  Git::Log.new(@base, count).object(@objectish)
64
67
  end
65
-
68
+
66
69
  # creates an archive of this object (tree)
67
70
  def archive(file = nil, opts = {})
68
71
  @base.lib.archive(@objectish, file, opts)
69
72
  end
70
-
71
- def tree?; false; end
72
-
73
- def blob?; false; end
74
-
75
- def commit?; false; end
76
73
 
77
- def tag?; false; end
78
-
74
+ def tree? = false
75
+
76
+ def blob? = false
77
+
78
+ def commit? = false
79
+
80
+ def tag? = false
79
81
  end
80
-
81
-
82
+
83
+ # A Git blob object
82
84
  class Blob < AbstractObject
83
-
84
85
  def initialize(base, sha, mode = nil)
85
86
  super(base, sha)
86
87
  @mode = mode
87
88
  end
88
-
89
+
89
90
  def blob?
90
91
  true
91
92
  end
92
-
93
93
  end
94
-
94
+
95
+ # A Git tree object
95
96
  class Tree < AbstractObject
96
-
97
97
  def initialize(base, sha, mode = nil)
98
98
  super(base, sha)
99
99
  @mode = mode
100
100
  @trees = nil
101
101
  @blobs = nil
102
102
  end
103
-
103
+
104
104
  def children
105
105
  blobs.merge(subtrees)
106
106
  end
107
-
107
+
108
108
  def blobs
109
109
  @blobs ||= check_tree[:blobs]
110
110
  end
111
- alias_method :files, :blobs
112
-
111
+ alias files blobs
112
+
113
113
  def trees
114
114
  @trees ||= check_tree[:trees]
115
115
  end
116
- alias_method :subtrees, :trees
117
- alias_method :subdirectories, :trees
118
-
116
+ alias subtrees trees
117
+ alias subdirectories trees
118
+
119
119
  def full_tree
120
120
  @base.lib.full_tree(@objectish)
121
121
  end
122
-
122
+
123
123
  def depth
124
124
  @base.lib.tree_depth(@objectish)
125
125
  end
126
-
126
+
127
127
  def tree?
128
128
  true
129
129
  end
130
-
130
+
131
131
  private
132
132
 
133
- # actually run the git command
134
- def check_tree
135
- @trees = {}
136
- @blobs = {}
137
-
138
- data = @base.lib.ls_tree(@objectish)
139
-
140
- data['tree'].each do |key, tree|
141
- @trees[key] = Git::Object::Tree.new(@base, tree[:sha], tree[:mode])
142
- end
143
-
144
- data['blob'].each do |key, blob|
145
- @blobs[key] = Git::Object::Blob.new(@base, blob[:sha], blob[:mode])
146
- end
147
-
148
- {
149
- :trees => @trees,
150
- :blobs => @blobs
151
- }
133
+ # actually run the git command
134
+ def check_tree
135
+ @trees = {}
136
+ @blobs = {}
137
+
138
+ data = @base.lib.ls_tree(@objectish)
139
+
140
+ data['tree'].each do |key, tree|
141
+ @trees[key] = Git::Object::Tree.new(@base, tree[:sha], tree[:mode])
142
+ end
143
+
144
+ data['blob'].each do |key, blob|
145
+ @blobs[key] = Git::Object::Blob.new(@base, blob[:sha], blob[:mode])
152
146
  end
153
-
147
+
148
+ { trees: @trees, blobs: @blobs }
149
+ end
154
150
  end
155
-
151
+
152
+ # A Git commit object
156
153
  class Commit < AbstractObject
157
-
158
154
  def initialize(base, sha, init = nil)
159
155
  super(base, sha)
160
156
  @tree = nil
@@ -162,111 +158,143 @@ module Git
162
158
  @author = nil
163
159
  @committer = nil
164
160
  @message = nil
165
- if init
166
- set_commit(init)
167
- end
161
+ return unless init
162
+
163
+ from_data(init)
168
164
  end
169
-
165
+
170
166
  def message
171
167
  check_commit
172
168
  @message
173
169
  end
174
-
170
+
175
171
  def name
176
- @base.lib.namerev(sha)
172
+ @base.lib.name_rev(sha)
177
173
  end
178
-
174
+
179
175
  def gtree
180
176
  check_commit
181
177
  Tree.new(@base, @tree)
182
178
  end
183
-
179
+
184
180
  def parent
185
181
  parents.first
186
182
  end
187
-
183
+
188
184
  # array of all parent commits
189
185
  def parents
190
186
  check_commit
191
- @parents
187
+ @parents
192
188
  end
193
-
189
+
194
190
  # git author
195
- def author
191
+ def author
196
192
  check_commit
197
193
  @author
198
194
  end
199
-
195
+
200
196
  def author_date
201
197
  author.date
202
198
  end
203
-
199
+
204
200
  # git author
205
201
  def committer
206
202
  check_commit
207
203
  @committer
208
204
  end
209
-
210
- def committer_date
205
+
206
+ def committer_date
211
207
  committer.date
212
208
  end
213
- alias_method :date, :committer_date
209
+ alias date committer_date
214
210
 
215
211
  def diff_parent
216
212
  diff(parent)
217
213
  end
218
-
219
- def set_commit(data)
214
+
215
+ def set_commit(data) # rubocop:disable Naming/AccessorMethodName
216
+ Git::Deprecation.warn(
217
+ 'Git::Object::Commit#set_commit is deprecated and will be removed in a future version. ' \
218
+ 'Use #from_data instead.'
219
+ )
220
+ from_data(data)
221
+ end
222
+
223
+ def from_data(data)
220
224
  @sha ||= data['sha']
221
225
  @committer = Git::Author.new(data['committer'])
222
226
  @author = Git::Author.new(data['author'])
223
227
  @tree = Git::Object::Tree.new(@base, data['tree'])
224
- @parents = data['parent'].map{ |sha| Git::Object::Commit.new(@base, sha) }
228
+ @parents = data['parent'].map { |sha| Git::Object::Commit.new(@base, sha) }
225
229
  @message = data['message'].chomp
226
230
  end
227
-
231
+
228
232
  def commit?
229
233
  true
230
234
  end
231
235
 
232
236
  private
233
-
234
- # see if this object has been initialized and do so if not
235
- def check_commit
236
- return if @tree
237
-
238
- data = @base.lib.commit_data(@objectish)
239
- set_commit(data)
240
- end
241
-
237
+
238
+ # see if this object has been initialized and do so if not
239
+ def check_commit
240
+ return if @tree
241
+
242
+ data = @base.lib.cat_file_commit(@objectish)
243
+ from_data(data)
244
+ end
242
245
  end
243
-
246
+
247
+ # A Git tag object
248
+ #
249
+ # This class represents a tag in Git, which can be either annotated or lightweight.
250
+ #
251
+ # Annotated tags contain additional metadata such as the tagger's name, email, and
252
+ # the date when the tag was created, along with a message.
253
+ #
254
+ # TODO: Annotated tags are not objects
255
+ #
244
256
  class Tag < AbstractObject
245
257
  attr_accessor :name
246
-
247
- def initialize(base, sha, name)
258
+
259
+ # @overload initialize(base, name)
260
+ # @param base [Git::Base] The Git base object
261
+ # @param name [String] The name of the tag
262
+ #
263
+ # @overload initialize(base, sha, name)
264
+ # @param base [Git::Base] The Git base object
265
+ # @param sha [String] The SHA of the tag object
266
+ # @param name [String] The name of the tag
267
+ #
268
+ def initialize(base, sha, name = nil)
269
+ if name.nil?
270
+ name = sha
271
+ sha = base.lib.tag_sha(name)
272
+ raise Git::UnexpectedResultError, "Tag '#{name}' does not exist." if sha == ''
273
+ end
274
+
248
275
  super(base, sha)
276
+
249
277
  @name = name
250
278
  @annotated = nil
251
279
  @loaded = false
252
280
  end
253
281
 
254
282
  def annotated?
255
- @annotated ||= (@base.lib.object_type(self.name) == 'tag')
283
+ @annotated = @annotated.nil? ? (@base.lib.cat_file_type(name) == 'tag') : @annotated
256
284
  end
257
285
 
258
286
  def message
259
- check_tag()
260
- return @message
287
+ check_tag
288
+ @message
261
289
  end
262
-
290
+
263
291
  def tag?
264
292
  true
265
293
  end
266
294
 
267
295
  def tagger
268
- check_tag()
269
- return @tagger
296
+ check_tag
297
+ @tagger
270
298
  end
271
299
 
272
300
  private
@@ -274,39 +302,37 @@ module Git
274
302
  def check_tag
275
303
  return if @loaded
276
304
 
277
- if !self.annotated?
278
- @message = @tagger = nil
279
- else
280
- tdata = @base.lib.tag_data(@name)
305
+ if annotated?
306
+ tdata = @base.lib.cat_file_tag(@name)
281
307
  @message = tdata['message'].chomp
282
308
  @tagger = Git::Author.new(tdata['tagger'])
309
+ else
310
+ @message = @tagger = nil
283
311
  end
284
312
 
285
313
  @loaded = true
286
314
  end
287
-
288
315
  end
289
-
316
+
290
317
  # if we're calling this, we don't know what type it is yet
291
318
  # so this is our little factory method
292
- def self.new(base, objectish, type = nil, is_tag = false)
293
- if is_tag
294
- sha = base.lib.tag_sha(objectish)
295
- if sha == ''
296
- raise Git::GitTagNameDoesNotExist.new(objectish)
297
- end
298
- return Git::Object::Tag.new(base, sha, objectish)
299
- end
300
-
301
- type ||= base.lib.object_type(objectish)
319
+ def self.new(base, objectish, type = nil, is_tag = false) # rubocop:disable Style/OptionalBooleanParameter
320
+ return new_tag(base, objectish) if is_tag
321
+
322
+ type ||= base.lib.cat_file_type(objectish)
323
+ # TODO: why not handle tag case here too?
302
324
  klass =
303
325
  case type
304
- when /blob/ then Blob
326
+ when /blob/ then Blob
305
327
  when /commit/ then Commit
306
328
  when /tree/ then Tree
307
329
  end
308
330
  klass.new(base, objectish)
309
331
  end
310
-
332
+
333
+ private_class_method def self.new_tag(base, objectish)
334
+ Git::Deprecation.warn('Git::Object.new with is_tag argument is deprecated. Use Git::Object::Tag.new instead.')
335
+ Git::Object::Tag.new(base, objectish)
336
+ end
311
337
  end
312
338
  end
data/lib/git/path.rb CHANGED
@@ -1,19 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Git
2
-
3
- class Path
4
-
5
- attr_accessor :path
6
-
7
- def initialize(path, check_path=true)
4
+ # A base class that represents and validates a filesystem path
5
+ #
6
+ # Use for tracking things relevant to a Git repository, such as the working
7
+ # directory or index file.
8
+ #
9
+ class Path
10
+ def path
11
+ Git::Deprecation.warn(
12
+ 'The .path accessor is deprecated and will be removed in v5.0. ' \
13
+ 'Use .to_s instead.'
14
+ )
15
+ @path
16
+ end
17
+
18
+ def initialize(path, must_exist: true)
8
19
  path = File.expand_path(path)
9
-
10
- if check_path && !File.exist?(path)
11
- raise ArgumentError, 'path does not exist', [path]
12
- end
13
-
20
+
21
+ raise ArgumentError, 'path does not exist', [path] if must_exist && !File.exist?(path)
22
+
14
23
  @path = path
15
24
  end
16
-
25
+
17
26
  def readable?
18
27
  File.readable?(@path)
19
28
  end
@@ -21,11 +30,9 @@ module Git
21
30
  def writable?
22
31
  File.writable?(@path)
23
32
  end
24
-
33
+
25
34
  def to_s
26
35
  @path
27
36
  end
28
-
29
- end
30
-
37
+ end
31
38
  end
data/lib/git/remote.rb CHANGED
@@ -1,6 +1,8 @@
1
- module Git
2
- class Remote < Path
1
+ # frozen_string_literal: true
3
2
 
3
+ module Git
4
+ # A remote in a Git repository
5
+ class Remote
4
6
  attr_accessor :name, :url, :fetch_opts
5
7
 
6
8
  def initialize(base, name)
@@ -11,7 +13,7 @@ module Git
11
13
  @fetch_opts = config['fetch']
12
14
  end
13
15
 
14
- def fetch(opts={})
16
+ def fetch(opts = {})
15
17
  @base.fetch(@name, opts)
16
18
  end
17
19
 
@@ -33,6 +35,5 @@ module Git
33
35
  def to_s
34
36
  @name
35
37
  end
36
-
37
38
  end
38
39
  end
@@ -1,6 +1,6 @@
1
- module Git
1
+ # frozen_string_literal: true
2
2
 
3
+ module Git
3
4
  class Repository < Path
4
5
  end
5
-
6
6
  end
data/lib/git/stash.rb CHANGED
@@ -1,27 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Git
4
+ # A stash in a Git repository
2
5
  class Stash
3
-
4
- def initialize(base, message, existing=false)
6
+ def initialize(base, message, save: false)
5
7
  @base = base
6
8
  @message = message
7
- save unless existing
9
+ self.save unless save
8
10
  end
9
-
11
+
10
12
  def save
11
13
  @saved = @base.lib.stash_save(@message)
12
14
  end
13
-
15
+
14
16
  def saved?
15
17
  @saved
16
18
  end
17
-
18
- def message
19
- @message
20
- end
21
-
19
+
20
+ attr_reader :message
21
+
22
22
  def to_s
23
23
  message
24
24
  end
25
-
26
25
  end
27
- end
26
+ end
data/lib/git/stashes.rb CHANGED
@@ -1,16 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Git
2
-
3
4
  # object that holds all the available stashes
4
5
  class Stashes
5
6
  include Enumerable
6
-
7
+
7
8
  def initialize(base)
8
9
  @stashes = []
9
-
10
+
10
11
  @base = base
11
-
12
- @base.lib.stashes_all.each do |id, message|
13
- @stashes.unshift(Git::Stash.new(@base, message, true))
12
+
13
+ @base.lib.stashes_all.each do |indexed_message|
14
+ _index, message = indexed_message
15
+ @stashes.unshift(Git::Stash.new(@base, message, save: true))
14
16
  end
15
17
  end
16
18
 
@@ -24,16 +26,16 @@ module Git
24
26
  def all
25
27
  @base.lib.stashes_all
26
28
  end
27
-
29
+
28
30
  def save(message)
29
31
  s = Git::Stash.new(@base, message)
30
32
  @stashes.unshift(s) if s.saved?
31
33
  end
32
-
33
- def apply(index=nil)
34
+
35
+ def apply(index = nil)
34
36
  @base.lib.stash_apply(index)
35
37
  end
36
-
38
+
37
39
  def clear
38
40
  @base.lib.stash_clear
39
41
  @stashes = []
@@ -42,14 +44,13 @@ module Git
42
44
  def size
43
45
  @stashes.size
44
46
  end
45
-
46
- def each(&block)
47
- @stashes.each(&block)
47
+
48
+ def each(&)
49
+ @stashes.each(&)
48
50
  end
49
-
51
+
50
52
  def [](index)
51
53
  @stashes[index.to_i]
52
54
  end
53
-
54
55
  end
55
56
  end