1
1
# frozen_string_literal: true
2
2
3
3
module Git
4
- # Return the last n commits that match the specified criteria
4
+ # Builds and executes a `git log` query.
5
5
#
6
- # @example The last (default number) of commits
7
- # git = Git.open('.')
8
- # Git::Log.new(git). execute #=> Enumerable of the last 30 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.
9
9
#
10
- # @example The last n commits
11
- # Git::Log.new(git).max_commits(50).execute #=> Enumerable of last 50 commits
12
- #
13
- # @example All commits returned by `git log`
14
- # Git::Log.new(git).max_count(:all).execute #=> Enumerable of all commits
15
- #
16
- # @example All commits that match complex criteria
17
- # Git::Log.new(git)
18
- # .max_count(:all)
19
- # .object('README.md')
20
- # .since('10 years ago')
21
- # .between('v1.0.7', 'HEAD')
22
- # .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 }
23
15
#
24
16
# @api public
25
17
#
26
18
class Log
27
19
include Enumerable
28
20
29
- # An immutable collection of commits returned by Git::Log#execute
30
- #
31
- # This object is an Enumerable that contains Git::Object::Commit objects.
32
- # It provides methods to access the commit data without executing any
33
- # further git commands.
34
- #
21
+ # An immutable, Enumerable collection of `Git::Object::Commit` objects.
22
+ # Returned by `Git::Log#execute`.
35
23
# @api public
36
- class Result
24
+ Result = Data . define ( :commits ) do
37
25
include Enumerable
38
26
39
- # @private
40
- def initialize ( commits )
41
- @commits = commits
42
- end
43
-
44
- # @return [Integer] the number of commits in the result set
45
- def size
46
- @commits . size
47
- end
48
-
49
- # Iterates over each commit in the result set
50
- #
51
- # @yield [Git::Object::Commit]
52
- def each ( &)
53
- @commits . each ( &)
54
- end
55
-
56
- # @return [Git::Object::Commit, nil] the first commit in the result set
57
- def first
58
- @commits . first
59
- end
60
-
61
- # @return [Git::Object::Commit, nil] the last commit in the result set
62
- def last
63
- @commits . last
64
- end
65
-
66
- # @param index [Integer] the index of the commit to return
67
- # @return [Git::Object::Commit, nil] the commit at the given index
68
- def []( index )
69
- @commits [ index ]
70
- end
71
-
72
- # @return [String] a string representation of the log
73
- def to_s
74
- map ( &:to_s ) . join ( "\n " )
75
- 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
76
32
end
77
33
78
34
# Create a new Git::Log object
@@ -88,12 +44,29 @@ def to_s
88
44
# Passing max_count to {#initialize} is equivalent to calling {#max_count} on the object.
89
45
#
90
46
def initialize ( base , max_count = 30 )
91
- dirty_log
92
47
@base = base
93
- max_count ( max_count )
48
+ @options = { }
49
+ @dirty = true
50
+ self . max_count ( max_count )
94
51
end
95
52
96
- # 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
97
70
#
98
71
# This is the preferred way to get log data. It separates the query
99
72
# building from the execution, making the API more predictable.
@@ -107,188 +80,64 @@ def initialize(base, max_count = 30)
107
80
# end
108
81
#
109
82
# @return [Git::Log::Result] an object containing the log results
83
+ #
110
84
def execute
111
- run_log
85
+ run_log_if_dirty
112
86
Result . new ( @commits )
113
87
end
114
88
115
- # The maximum number of commits to return
116
- #
117
- # @example All commits returned by `git log`
118
- # git = Git.open('.')
119
- # Git::Log.new(git).max_count(:all)
120
- #
121
- # @param num_or_all [Integer, Symbol, nil] the number of commits to return, or
122
- # `:all` or `nil` to return all
123
- #
124
- # @return [self]
125
- #
126
- def max_count ( num_or_all )
127
- dirty_log
128
- @max_count = num_or_all == :all ? nil : num_or_all
129
- self
130
- end
131
-
132
- # Adds the --all flag to the git log command
133
- #
134
- # This asks for the logs of all refs (basically all commits reachable by HEAD,
135
- # branches, and tags). This does not control the maximum number of commits
136
- # returned. To control how many commits are returned, call {#max_count}.
137
- #
138
- # @example Return the last 50 commits reachable by all refs
139
- # git = Git.open('.')
140
- # Git::Log.new(git).max_count(50).all
141
- #
142
- # @return [self]
143
- #
144
- def all
145
- dirty_log
146
- @all = true
147
- self
148
- end
149
-
150
- def object ( objectish )
151
- dirty_log
152
- @object = objectish
153
- self
154
- end
155
-
156
- def author ( regex )
157
- dirty_log
158
- @author = regex
159
- self
160
- end
161
-
162
- def grep ( regex )
163
- dirty_log
164
- @grep = regex
165
- self
166
- end
167
-
168
- def path ( path )
169
- dirty_log
170
- @path = path
171
- self
172
- end
173
-
174
- def skip ( num )
175
- dirty_log
176
- @skip = num
177
- self
178
- end
179
-
180
- def since ( date )
181
- dirty_log
182
- @since = date
183
- self
184
- end
185
-
186
- def until ( date )
187
- dirty_log
188
- @until = date
189
- self
190
- end
191
-
192
- def between ( sha1 , sha2 = nil )
193
- dirty_log
194
- @between = [ sha1 , sha2 ]
195
- self
196
- end
197
-
198
- def cherry
199
- dirty_log
200
- @cherry = true
201
- self
202
- end
203
-
204
- def merges
205
- dirty_log
206
- @merges = true
207
- self
208
- end
209
-
210
- def to_s
211
- deprecate_method ( __method__ )
212
- check_log
213
- @commits . map ( &:to_s ) . join ( "\n " )
214
- end
215
-
216
- # forces git log to run
217
-
218
- def size
219
- deprecate_method ( __method__ )
220
- check_log
221
- begin
222
- @commits . size
223
- rescue StandardError
224
- nil
225
- end
226
- end
89
+ # @!group Deprecated Enumerable Interface
227
90
91
+ # @deprecated Use {#execute} and call `each` on the result.
228
92
def each ( &)
229
- deprecate_method ( __method__ )
230
- check_log
93
+ deprecate_and_run
231
94
@commits . each ( &)
232
95
end
233
96
234
- def first
235
- deprecate_method ( __method__ )
236
- check_log
237
- begin
238
- @commits . first
239
- rescue StandardError
240
- nil
241
- end
97
+ # @deprecated Use {#execute} and call `size` on the result.
98
+ def size
99
+ deprecate_and_run
100
+ @commits &.size
242
101
end
243
102
244
- def last
245
- deprecate_method ( __method__ )
246
- check_log
247
- begin
248
- @commits . last
249
- rescue StandardError
250
- nil
251
- end
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 " )
252
107
end
253
108
254
- def []( index )
255
- deprecate_method ( __method__ )
256
- check_log
257
- begin
258
- @commits [ index ]
259
- rescue StandardError
260
- nil
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 )
261
114
end
262
115
end
263
116
264
- private
117
+ # @!endgroup
265
118
266
- def deprecate_method ( method_name )
267
- Git ::Deprecation . warn (
268
- "Calling Git::Log##{ method_name } is deprecated and will be removed in a future version. " \
269
- "Call #execute and then ##{ method_name } on the result object."
270
- )
271
- end
119
+ private
272
120
273
- def dirty_log
274
- @dirty_flag = true
121
+ def set_option ( key , value )
122
+ @dirty = true
123
+ @options [ key ] = value
124
+ self
275
125
end
276
126
277
- def check_log
278
- return unless @dirty_flag
127
+ def run_log_if_dirty
128
+ return unless @dirty
279
129
280
- run_log
281
- @dirty_flag = false
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
282
133
end
283
134
284
- # actually run the 'git log' command
285
- def run_log
286
- log = @base . lib . full_log_commits (
287
- count : @max_count , all : @all , object : @object , path_limiter : @path , since : @since ,
288
- author : @author , grep : @grep , skip : @skip , until : @until , between : @between ,
289
- cherry : @cherry , merges : @merges
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."
290
139
)
291
- @commits = log . map { | c | Git :: Object :: Commit . new ( @base , c [ 'sha' ] , c ) }
140
+ run_log_if_dirty
292
141
end
293
142
end
294
143
end
0 commit comments