git 4.0.0 → 4.0.1

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/log.rb CHANGED
@@ -1,79 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Git
4
-
5
- # Return the last n commits that match the specified criteria
6
- #
7
- # @example The last (default number) of commits
8
- # git = Git.open('.')
9
- # Git::Log.new(git).execute #=> Enumerable of the last 30 commits
10
- #
11
- # @example The last n commits
12
- # Git::Log.new(git).max_commits(50).execute #=> Enumerable of last 50 commits
4
+ # Builds and executes a `git log` query.
13
5
  #
14
- # @example All commits returned by `git log`
15
- # Git::Log.new(git).max_count(:all).execute #=> Enumerable of all commits
6
+ # This class provides a fluent interface for building complex `git log` queries.
7
+ # The query is lazily executed when results are requested either via the modern
8
+ # `#execute` method or the deprecated Enumerable methods.
16
9
  #
17
- # @example All commits that match complex criteria
18
- # Git::Log.new(git)
19
- # .max_count(:all)
20
- # .object('README.md')
21
- # .since('10 years ago')
22
- # .between('v1.0.7', 'HEAD')
23
- # .execute
10
+ # @example Using the modern `execute` API
11
+ # log = git.log.max_count(50).between('v1.0', 'v1.1').author('Scott')
12
+ # results = log.execute
13
+ # puts "Found #{results.size} commits."
14
+ # results.each { |commit| puts commit.sha }
24
15
  #
25
16
  # @api public
26
17
  #
27
18
  class Log
28
19
  include Enumerable
29
20
 
30
- # An immutable collection of commits returned by Git::Log#execute
31
- #
32
- # This object is an Enumerable that contains Git::Object::Commit objects.
33
- # It provides methods to access the commit data without executing any
34
- # further git commands.
35
- #
21
+ # An immutable, Enumerable collection of `Git::Object::Commit` objects.
22
+ # Returned by `Git::Log#execute`.
36
23
  # @api public
37
- class Result
24
+ Result = Data.define(:commits) do
38
25
  include Enumerable
39
26
 
40
- # @private
41
- def initialize(commits)
42
- @commits = commits
43
- end
44
-
45
- # @return [Integer] the number of commits in the result set
46
- def size
47
- @commits.size
48
- end
49
-
50
- # Iterates over each commit in the result set
51
- #
52
- # @yield [Git::Object::Commit]
53
- def each(&block)
54
- @commits.each(&block)
55
- end
56
-
57
- # @return [Git::Object::Commit, nil] the first commit in the result set
58
- def first
59
- @commits.first
60
- end
61
-
62
- # @return [Git::Object::Commit, nil] the last commit in the result set
63
- def last
64
- @commits.last
65
- end
66
-
67
- # @param index [Integer] the index of the commit to return
68
- # @return [Git::Object::Commit, nil] the commit at the given index
69
- def [](index)
70
- @commits[index]
71
- end
72
-
73
- # @return [String] a string representation of the log
74
- def to_s
75
- map { |c| c.to_s }.join("\n")
76
- end
27
+ def each(&block) = commits.each(&block)
28
+ def last = commits.last
29
+ def [](index) = commits[index]
30
+ def to_s = map(&:to_s).join("\n")
31
+ def size = commits.size
77
32
  end
78
33
 
79
34
  # Create a new Git::Log object
@@ -89,12 +44,29 @@ module Git
89
44
  # Passing max_count to {#initialize} is equivalent to calling {#max_count} on the object.
90
45
  #
91
46
  def initialize(base, max_count = 30)
92
- dirty_log
93
47
  @base = base
94
- max_count(max_count)
48
+ @options = {}
49
+ @dirty = true
50
+ self.max_count(max_count)
95
51
  end
96
52
 
97
- # Executes the git log command and returns an immutable result object.
53
+ # Set query options using a fluent interface.
54
+ # Each method returns `self` to allow for chaining.
55
+ #
56
+ def max_count(num) = set_option(:count, num == :all ? nil : num)
57
+ def all = set_option(:all, true)
58
+ def object(objectish) = set_option(:object, objectish)
59
+ def author(regex) = set_option(:author, regex)
60
+ def grep(regex) = set_option(:grep, regex)
61
+ def path(path) = set_option(:path_limiter, path)
62
+ def skip(num) = set_option(:skip, num)
63
+ def since(date) = set_option(:since, date)
64
+ def until(date) = set_option(:until, date)
65
+ def between(val1, val2 = nil) = set_option(:between, [val1, val2])
66
+ def cherry = set_option(:cherry, true)
67
+ def merges = set_option(:merges, true)
68
+
69
+ # Executes the git log command and returns an immutable result object
98
70
  #
99
71
  # This is the preferred way to get log data. It separates the query
100
72
  # building from the execution, making the API more predictable.
@@ -108,172 +80,64 @@ module Git
108
80
  # end
109
81
  #
110
82
  # @return [Git::Log::Result] an object containing the log results
83
+ #
111
84
  def execute
112
- run_log
85
+ run_log_if_dirty
113
86
  Result.new(@commits)
114
87
  end
115
88
 
116
- # The maximum number of commits to return
117
- #
118
- # @example All commits returned by `git log`
119
- # git = Git.open('.')
120
- # Git::Log.new(git).max_count(:all)
121
- #
122
- # @param num_or_all [Integer, Symbol, nil] the number of commits to return, or
123
- # `:all` or `nil` to return all
124
- #
125
- # @return [self]
126
- #
127
- def max_count(num_or_all)
128
- dirty_log
129
- @max_count = (num_or_all == :all) ? nil : num_or_all
130
- self
131
- end
132
-
133
- # Adds the --all flag to the git log command
134
- #
135
- # This asks for the logs of all refs (basically all commits reachable by HEAD,
136
- # branches, and tags). This does not control the maximum number of commits
137
- # returned. To control how many commits are returned, call {#max_count}.
138
- #
139
- # @example Return the last 50 commits reachable by all refs
140
- # git = Git.open('.')
141
- # Git::Log.new(git).max_count(50).all
142
- #
143
- # @return [self]
144
- #
145
- def all
146
- dirty_log
147
- @all = true
148
- self
149
- end
89
+ # @!group Deprecated Enumerable Interface
150
90
 
151
- def object(objectish)
152
- dirty_log
153
- @object = objectish
154
- self
155
- end
156
-
157
- def author(regex)
158
- dirty_log
159
- @author = regex
160
- self
91
+ # @deprecated Use {#execute} and call `each` on the result.
92
+ def each(&)
93
+ deprecate_and_run
94
+ @commits.each(&)
161
95
  end
162
96
 
163
- def grep(regex)
164
- dirty_log
165
- @grep = regex
166
- self
167
- end
168
-
169
- def path(path)
170
- dirty_log
171
- @path = path
172
- self
173
- end
174
-
175
- def skip(num)
176
- dirty_log
177
- @skip = num
178
- self
97
+ # @deprecated Use {#execute} and call `size` on the result.
98
+ def size
99
+ deprecate_and_run
100
+ @commits&.size
179
101
  end
180
102
 
181
- def since(date)
182
- dirty_log
183
- @since = date
184
- self
103
+ # @deprecated Use {#execute} and call `to_s` on the result.
104
+ def to_s
105
+ deprecate_and_run
106
+ @commits&.map(&:to_s)&.join("\n")
185
107
  end
186
108
 
187
- def until(date)
188
- dirty_log
189
- @until = date
190
- self
109
+ # @deprecated Use {#execute} and call the method on the result.
110
+ %i[first last []].each do |method_name|
111
+ define_method(method_name) do |*args|
112
+ deprecate_and_run
113
+ @commits&.public_send(method_name, *args)
114
+ end
191
115
  end
192
116
 
193
- def between(sha1, sha2 = nil)
194
- dirty_log
195
- @between = [sha1, sha2]
196
- self
197
- end
117
+ # @!endgroup
198
118
 
199
- def cherry
200
- dirty_log
201
- @cherry = true
202
- self
203
- end
119
+ private
204
120
 
205
- def merges
206
- dirty_log
207
- @merges = true
121
+ def set_option(key, value)
122
+ @dirty = true
123
+ @options[key] = value
208
124
  self
209
125
  end
210
126
 
211
- def to_s
212
- deprecate_method(__method__)
213
- check_log
214
- @commits.map { |c| c.to_s }.join("\n")
215
- end
216
-
217
- # forces git log to run
127
+ def run_log_if_dirty
128
+ return unless @dirty
218
129
 
219
- def size
220
- deprecate_method(__method__)
221
- check_log
222
- @commits.size rescue nil
223
- end
224
-
225
- def each(&block)
226
- deprecate_method(__method__)
227
- check_log
228
- @commits.each(&block)
229
- end
230
-
231
- def first
232
- deprecate_method(__method__)
233
- check_log
234
- @commits.first rescue nil
130
+ log_data = @base.lib.full_log_commits(@options)
131
+ @commits = log_data.map { |c| Git::Object::Commit.new(@base, c['sha'], c) }
132
+ @dirty = false
235
133
  end
236
134
 
237
- def last
238
- deprecate_method(__method__)
239
- check_log
240
- @commits.last rescue nil
135
+ def deprecate_and_run(method = caller_locations(1, 1)[0].label)
136
+ Git::Deprecation.warn(
137
+ "Calling Git::Log##{method} is deprecated. " \
138
+ "Call #execute and then ##{method} on the result object."
139
+ )
140
+ run_log_if_dirty
241
141
  end
242
-
243
- def [](index)
244
- deprecate_method(__method__)
245
- check_log
246
- @commits[index] rescue nil
247
- end
248
-
249
-
250
- private
251
-
252
- def deprecate_method(method_name)
253
- Git::Deprecation.warn("Calling Git::Log##{method_name} is deprecated and will be removed in a future version. Call #execute and then ##{method_name} on the result object.")
254
- end
255
-
256
- def dirty_log
257
- @dirty_flag = true
258
- end
259
-
260
- def check_log
261
- if @dirty_flag
262
- run_log
263
- @dirty_flag = false
264
- end
265
- end
266
-
267
- # actually run the 'git log' command
268
- def run_log
269
- log = @base.lib.full_log_commits(
270
- count: @max_count, all: @all, object: @object, path_limiter: @path, since: @since,
271
- author: @author, grep: @grep, skip: @skip, until: @until, between: @between,
272
- cherry: @cherry, merges: @merges
273
- )
274
- @commits = log.map { |c| Git::Object::Commit.new(@base, c['sha'], c) }
275
- end
276
-
277
142
  end
278
-
279
143
  end
data/lib/git/object.rb CHANGED
@@ -6,10 +6,9 @@ require 'git/errors'
6
6
  require 'git/log'
7
7
 
8
8
  module Git
9
-
10
9
  # represents a git object
11
10
  class Object
12
-
11
+ # A base class for all Git objects
13
12
  class AbstractObject
14
13
  attr_accessor :objectish, :type, :mode
15
14
 
@@ -38,16 +37,16 @@ module Git
38
37
  # read a large file in chunks.
39
38
  #
40
39
  # Use this for large files so that they are not held in memory.
41
- def contents(&block)
40
+ def contents(&)
42
41
  if block_given?
43
- @base.lib.cat_file_contents(@objectish, &block)
42
+ @base.lib.cat_file_contents(@objectish, &)
44
43
  else
45
44
  @contents ||= @base.lib.cat_file_contents(@objectish)
46
45
  end
47
46
  end
48
47
 
49
48
  def contents_array
50
- self.contents.split("\n")
49
+ contents.split("\n")
51
50
  end
52
51
 
53
52
  def to_s
@@ -55,7 +54,7 @@ module Git
55
54
  end
56
55
 
57
56
  def grep(string, path_limiter = nil, opts = {})
58
- opts = {:object => sha, :path_limiter => path_limiter}.merge(opts)
57
+ opts = { object: sha, path_limiter: path_limiter }.merge(opts)
59
58
  @base.lib.grep(string, opts)
60
59
  end
61
60
 
@@ -72,19 +71,17 @@ module Git
72
71
  @base.lib.archive(@objectish, file, opts)
73
72
  end
74
73
 
75
- def tree?; false; end
74
+ def tree? = false
76
75
 
77
- def blob?; false; end
76
+ def blob? = false
78
77
 
79
- def commit?; false; end
80
-
81
- def tag?; false; end
78
+ def commit? = false
82
79
 
80
+ def tag? = false
83
81
  end
84
82
 
85
-
83
+ # A Git blob object
86
84
  class Blob < AbstractObject
87
-
88
85
  def initialize(base, sha, mode = nil)
89
86
  super(base, sha)
90
87
  @mode = mode
@@ -93,11 +90,10 @@ module Git
93
90
  def blob?
94
91
  true
95
92
  end
96
-
97
93
  end
98
94
 
95
+ # A Git tree object
99
96
  class Tree < AbstractObject
100
-
101
97
  def initialize(base, sha, mode = nil)
102
98
  super(base, sha)
103
99
  @mode = mode
@@ -112,13 +108,13 @@ module Git
112
108
  def blobs
113
109
  @blobs ||= check_tree[:blobs]
114
110
  end
115
- alias_method :files, :blobs
111
+ alias files blobs
116
112
 
117
113
  def trees
118
114
  @trees ||= check_tree[:trees]
119
115
  end
120
- alias_method :subtrees, :trees
121
- alias_method :subdirectories, :trees
116
+ alias subtrees trees
117
+ alias subdirectories trees
122
118
 
123
119
  def full_tree
124
120
  @base.lib.full_tree(@objectish)
@@ -134,31 +130,27 @@ module Git
134
130
 
135
131
  private
136
132
 
137
- # actually run the git command
138
- def check_tree
139
- @trees = {}
140
- @blobs = {}
141
-
142
- data = @base.lib.ls_tree(@objectish)
133
+ # actually run the git command
134
+ def check_tree
135
+ @trees = {}
136
+ @blobs = {}
143
137
 
144
- data['tree'].each do |key, tree|
145
- @trees[key] = Git::Object::Tree.new(@base, tree[:sha], tree[:mode])
146
- end
138
+ data = @base.lib.ls_tree(@objectish)
147
139
 
148
- data['blob'].each do |key, blob|
149
- @blobs[key] = Git::Object::Blob.new(@base, blob[:sha], blob[:mode])
150
- end
140
+ data['tree'].each do |key, tree|
141
+ @trees[key] = Git::Object::Tree.new(@base, tree[:sha], tree[:mode])
142
+ end
151
143
 
152
- {
153
- :trees => @trees,
154
- :blobs => @blobs
155
- }
144
+ data['blob'].each do |key, blob|
145
+ @blobs[key] = Git::Object::Blob.new(@base, blob[:sha], blob[:mode])
156
146
  end
157
147
 
148
+ { trees: @trees, blobs: @blobs }
149
+ end
158
150
  end
159
151
 
152
+ # A Git commit object
160
153
  class Commit < AbstractObject
161
-
162
154
  def initialize(base, sha, init = nil)
163
155
  super(base, sha)
164
156
  @tree = nil
@@ -166,9 +158,9 @@ module Git
166
158
  @author = nil
167
159
  @committer = nil
168
160
  @message = nil
169
- if init
170
- set_commit(init)
171
- end
161
+ return unless init
162
+
163
+ from_data(init)
172
164
  end
173
165
 
174
166
  def message
@@ -214,18 +206,23 @@ module Git
214
206
  def committer_date
215
207
  committer.date
216
208
  end
217
- alias_method :date, :committer_date
209
+ alias date committer_date
218
210
 
219
211
  def diff_parent
220
212
  diff(parent)
221
213
  end
222
214
 
223
- def set_commit(data)
215
+ def set_commit(data) # rubocop:disable Naming/AccessorMethodName
216
+ Git.deprecation('Git::Object::Commit#set_commit is deprecated. Use #from_data instead.')
217
+ from_data(data)
218
+ end
219
+
220
+ def from_data(data)
224
221
  @sha ||= data['sha']
225
222
  @committer = Git::Author.new(data['committer'])
226
223
  @author = Git::Author.new(data['author'])
227
224
  @tree = Git::Object::Tree.new(@base, data['tree'])
228
- @parents = data['parent'].map{ |sha| Git::Object::Commit.new(@base, sha) }
225
+ @parents = data['parent'].map { |sha| Git::Object::Commit.new(@base, sha) }
229
226
  @message = data['message'].chomp
230
227
  end
231
228
 
@@ -235,33 +232,57 @@ module Git
235
232
 
236
233
  private
237
234
 
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
- set_commit(data)
244
- end
235
+ # see if this object has been initialized and do so if not
236
+ def check_commit
237
+ return if @tree
245
238
 
239
+ data = @base.lib.cat_file_commit(@objectish)
240
+ from_data(data)
241
+ end
246
242
  end
247
243
 
244
+ # A Git tag object
245
+ #
246
+ # This class represents a tag in Git, which can be either annotated or lightweight.
247
+ #
248
+ # Annotated tags contain additional metadata such as the tagger's name, email, and
249
+ # the date when the tag was created, along with a message.
250
+ #
251
+ # TODO: Annotated tags are not objects
252
+ #
248
253
  class Tag < AbstractObject
249
254
  attr_accessor :name
250
255
 
251
- def initialize(base, sha, name)
256
+ # @overload initialize(base, name)
257
+ # @param base [Git::Base] The Git base object
258
+ # @param name [String] The name of the tag
259
+ #
260
+ # @overload initialize(base, sha, name)
261
+ # @param base [Git::Base] The Git base object
262
+ # @param sha [String] The SHA of the tag object
263
+ # @param name [String] The name of the tag
264
+ #
265
+ def initialize(base, sha, name = nil)
266
+ if name.nil?
267
+ name = sha
268
+ sha = base.lib.tag_sha(name)
269
+ raise Git::UnexpectedResultError, "Tag '#{name}' does not exist." if sha == ''
270
+ end
271
+
252
272
  super(base, sha)
273
+
253
274
  @name = name
254
275
  @annotated = nil
255
276
  @loaded = false
256
277
  end
257
278
 
258
279
  def annotated?
259
- @annotated ||= (@base.lib.cat_file_type(self.name) == 'tag')
280
+ @annotated = @annotated.nil? ? (@base.lib.cat_file_type(name) == 'tag') : @annotated
260
281
  end
261
282
 
262
283
  def message
263
- check_tag()
264
- return @message
284
+ check_tag
285
+ @message
265
286
  end
266
287
 
267
288
  def tag?
@@ -269,8 +290,8 @@ module Git
269
290
  end
270
291
 
271
292
  def tagger
272
- check_tag()
273
- return @tagger
293
+ check_tag
294
+ @tagger
274
295
  end
275
296
 
276
297
  private
@@ -278,31 +299,25 @@ module Git
278
299
  def check_tag
279
300
  return if @loaded
280
301
 
281
- if !self.annotated?
282
- @message = @tagger = nil
283
- else
302
+ if annotated?
284
303
  tdata = @base.lib.cat_file_tag(@name)
285
304
  @message = tdata['message'].chomp
286
305
  @tagger = Git::Author.new(tdata['tagger'])
306
+ else
307
+ @message = @tagger = nil
287
308
  end
288
309
 
289
310
  @loaded = true
290
311
  end
291
-
292
312
  end
293
313
 
294
314
  # if we're calling this, we don't know what type it is yet
295
315
  # so this is our little factory method
296
- def self.new(base, objectish, type = nil, is_tag = false)
297
- if is_tag
298
- sha = base.lib.tag_sha(objectish)
299
- if sha == ''
300
- raise Git::UnexpectedResultError.new("Tag '#{objectish}' does not exist.")
301
- end
302
- return Git::Object::Tag.new(base, sha, objectish)
303
- end
316
+ def self.new(base, objectish, type = nil, is_tag = false) # rubocop:disable Style/OptionalBooleanParameter
317
+ return new_tag(base, objectish) if is_tag
304
318
 
305
319
  type ||= base.lib.cat_file_type(objectish)
320
+ # TODO: why not handle tag case here too?
306
321
  klass =
307
322
  case type
308
323
  when /blob/ then Blob
@@ -312,5 +327,9 @@ module Git
312
327
  klass.new(base, objectish)
313
328
  end
314
329
 
330
+ private_class_method def self.new_tag(base, objectish)
331
+ Git::Deprecation.warn('Git::Object.new with is_tag argument is deprecated. Use Git::Object::Tag.new instead.')
332
+ Git::Object::Tag.new(base, objectish)
333
+ end
315
334
  end
316
335
  end
data/lib/git/path.rb CHANGED
@@ -1,17 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Git
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
+ attr_accessor :path
4
11
 
5
- class Path
12
+ def initialize(path, check_path = nil, must_exist: nil)
13
+ unless check_path.nil?
14
+ Git::Deprecation.warn(
15
+ 'The "check_path" argument is deprecated and ' \
16
+ 'will be removed in a future version. Use "must_exist:" instead.'
17
+ )
18
+ end
6
19
 
7
- attr_accessor :path
20
+ # default is true
21
+ must_exist = must_exist.nil? && check_path.nil? ? true : must_exist || check_path
8
22
 
9
- def initialize(path, check_path=true)
10
23
  path = File.expand_path(path)
11
24
 
12
- if check_path && !File.exist?(path)
13
- raise ArgumentError, 'path does not exist', [path]
14
- end
25
+ raise ArgumentError, 'path does not exist', [path] if must_exist && !File.exist?(path)
15
26
 
16
27
  @path = path
17
28
  end
@@ -27,6 +38,5 @@ module Git
27
38
  def to_s
28
39
  @path
29
40
  end
30
- end
31
-
41
+ end
32
42
  end