Skip to content

Commit ded54c4

Browse files
committed
feat: add Log#execute to run the log and return an immutable result
This partially implements #813 Log data access methods directly on the Log class will return a deprecation warning since they will be removed in the future.
1 parent 28e07ae commit ded54c4

File tree

4 files changed

+260
-4
lines changed

4 files changed

+260
-4
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- [Major Objects](#major-objects)
1919
- [Errors Raised By This Gem](#errors-raised-by-this-gem)
2020
- [Specifying And Handling Timeouts](#specifying-and-handling-timeouts)
21+
- [Deprecations](#deprecations)
2122
- [Examples](#examples)
2223
- [Ruby version support policy](#ruby-version-support-policy)
2324
- [License](#license)
@@ -202,6 +203,24 @@ rescue Git::TimeoutError => e
202203
end
203204
```
204205

206+
## Deprecations
207+
208+
This gem uses ActiveSupport's deprecation mechanism to report deprecation warnings.
209+
210+
You can silence deprecation warnings by adding this line to your source code:
211+
212+
```ruby
213+
Git::Deprecation.behavior = :silence
214+
```
215+
216+
See [the Active Support Deprecation
217+
documentation](https://api.rubyonrails.org/classes/ActiveSupport/Deprecation.html)
218+
for more details.
219+
220+
If deprecation warnings are silenced, you should reenable them before upgrading the
221+
git gem to the next major version. This will make it easier to identify changes
222+
needed for the upgrade.
223+
205224
## Examples
206225

207226
Here are a bunch of examples of how to use the Ruby/Git package.

lib/git/log.rb

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,76 @@ module Git
66
#
77
# @example The last (default number) of commits
88
# git = Git.open('.')
9-
# Git::Log.new(git) #=> Enumerable of the last 30 commits
9+
# Git::Log.new(git).execute #=> Enumerable of the last 30 commits
1010
#
1111
# @example The last n commits
12-
# Git::Log.new(git).max_commits(50) #=> Enumerable of last 50 commits
12+
# Git::Log.new(git).max_commits(50).execute #=> Enumerable of last 50 commits
1313
#
1414
# @example All commits returned by `git log`
15-
# Git::Log.new(git).max_count(:all) #=> Enumerable of all commits
15+
# Git::Log.new(git).max_count(:all).execute #=> Enumerable of all commits
1616
#
1717
# @example All commits that match complex criteria
1818
# Git::Log.new(git)
1919
# .max_count(:all)
2020
# .object('README.md')
2121
# .since('10 years ago')
2222
# .between('v1.0.7', 'HEAD')
23+
# .execute
2324
#
2425
# @api public
2526
#
2627
class Log
2728
include Enumerable
2829

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+
#
36+
# @api public
37+
class Result
38+
include Enumerable
39+
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
77+
end
78+
2979
# Create a new Git::Log object
3080
#
3181
# @example
@@ -44,6 +94,25 @@ def initialize(base, max_count = 30)
4494
max_count(max_count)
4595
end
4696

97+
# Executes the git log command and returns an immutable result object.
98+
#
99+
# This is the preferred way to get log data. It separates the query
100+
# building from the execution, making the API more predictable.
101+
#
102+
# @example
103+
# query = g.log.since('2 weeks ago').author('Scott')
104+
# results = query.execute
105+
# puts "Found #{results.size} commits"
106+
# results.each do |commit|
107+
# # ...
108+
# end
109+
#
110+
# @return [Git::Log::Result] an object containing the log results
111+
def execute
112+
run_log
113+
Result.new(@commits)
114+
end
115+
47116
# The maximum number of commits to return
48117
#
49118
# @example All commits returned by `git log`
@@ -140,39 +209,50 @@ def merges
140209
end
141210

142211
def to_s
143-
self.map { |c| c.to_s }.join("\n")
212+
deprecate_method(__method__)
213+
check_log
214+
@commits.map { |c| c.to_s }.join("\n")
144215
end
145216

146217
# forces git log to run
147218

148219
def size
220+
deprecate_method(__method__)
149221
check_log
150222
@commits.size rescue nil
151223
end
152224

153225
def each(&block)
226+
deprecate_method(__method__)
154227
check_log
155228
@commits.each(&block)
156229
end
157230

158231
def first
232+
deprecate_method(__method__)
159233
check_log
160234
@commits.first rescue nil
161235
end
162236

163237
def last
238+
deprecate_method(__method__)
164239
check_log
165240
@commits.last rescue nil
166241
end
167242

168243
def [](index)
244+
deprecate_method(__method__)
169245
check_log
170246
@commits[index] rescue nil
171247
end
172248

173249

174250
private
175251

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+
176256
def dirty_log
177257
@dirty_flag = true
178258
end

tests/test_helper.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
$stdout.sync = true
1313
$stderr.sync = true
1414

15+
# Silence deprecation warnings during tests
16+
Git::Deprecation.behavior = :silence
17+
1518
class Test::Unit::TestCase
1619

1720
TEST_ROOT = File.expand_path(__dir__)

tests/units/test_log_execute.rb

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# frozen_string_literal: true
2+
3+
require 'logger'
4+
require 'test_helper'
5+
6+
# Tests for the Git::Log#execute method
7+
class TestLogExecute < Test::Unit::TestCase
8+
def setup
9+
clone_working_repo
10+
#@git = Git.open(@wdir, :log => Logger.new(STDOUT))
11+
@git = Git.open(@wdir)
12+
end
13+
14+
def test_log_max_count_default
15+
assert_equal(30, @git.log.execute.size)
16+
end
17+
18+
# In these tests, note that @git.log(n) is equivalent to @git.log.max_count(n)
19+
def test_log_max_count_20
20+
assert_equal(20, @git.log(20).execute.size)
21+
assert_equal(20, @git.log.max_count(20).execute.size)
22+
end
23+
24+
def test_log_max_count_nil
25+
assert_equal(72, @git.log(nil).execute.size)
26+
assert_equal(72, @git.log.max_count(nil).execute.size)
27+
end
28+
29+
def test_log_max_count_all
30+
assert_equal(72, @git.log(:all).execute.size)
31+
assert_equal(72, @git.log.max_count(:all).execute.size)
32+
end
33+
34+
# Note that @git.log.all does not control the number of commits returned. For that,
35+
# use @git.log.max_count(n)
36+
def test_log_all
37+
assert_equal(72, @git.log(100).execute.size)
38+
assert_equal(76, @git.log(100).all.execute.size)
39+
end
40+
41+
def test_log_non_integer_count
42+
assert_raises(ArgumentError) { @git.log('foo').execute }
43+
end
44+
45+
def test_get_first_and_last_entries
46+
log = @git.log.execute
47+
assert(log.first.is_a?(Git::Object::Commit))
48+
assert_equal('46abbf07e3c564c723c7c039a43ab3a39e5d02dd', log.first.objectish)
49+
50+
assert(log.last.is_a?(Git::Object::Commit))
51+
assert_equal('b03003311ad3fa368b475df58390353868e13c91', log.last.objectish)
52+
end
53+
54+
def test_get_log_entries
55+
assert_equal(30, @git.log.execute.size)
56+
assert_equal(50, @git.log(50).execute.size)
57+
assert_equal(10, @git.log(10).execute.size)
58+
end
59+
60+
def test_get_log_to_s
61+
log = @git.log.execute
62+
assert_equal(log.to_s.split("\n").first, log.first.sha)
63+
end
64+
65+
def test_log_skip
66+
three1 = @git.log(3).execute.to_a[-1]
67+
three2 = @git.log(2).skip(1).execute.to_a[-1]
68+
three3 = @git.log(1).skip(2).execute.to_a[-1]
69+
assert_equal(three2.sha, three3.sha)
70+
assert_equal(three1.sha, three2.sha)
71+
end
72+
73+
def test_get_log_since
74+
l = @git.log.since("2 seconds ago").execute
75+
assert_equal(0, l.size)
76+
77+
l = @git.log.since("#{Date.today.year - 2006} years ago").execute
78+
assert_equal(30, l.size)
79+
end
80+
81+
def test_get_log_grep
82+
l = @git.log.grep("search").execute
83+
assert_equal(2, l.size)
84+
end
85+
86+
def test_get_log_author
87+
l = @git.log(5).author("chacon").execute
88+
assert_equal(5, l.size)
89+
l = @git.log(5).author("lazySusan").execute
90+
assert_equal(0, l.size)
91+
end
92+
93+
def test_get_log_since_file
94+
l = @git.log.path('example.txt').execute
95+
assert_equal(30, l.size)
96+
97+
l = @git.log.between('v2.5', 'test').path('example.txt').execute
98+
assert_equal(1, l.size)
99+
end
100+
101+
def test_get_log_path
102+
log = @git.log.path('example.txt').execute
103+
assert_equal(30, log.size)
104+
log = @git.log.path('example*').execute
105+
assert_equal(30, log.size)
106+
log = @git.log.path(['example.txt','scott/text.txt']).execute
107+
assert_equal(30, log.size)
108+
end
109+
110+
def test_log_file_noexist
111+
assert_raise Git::FailedError do
112+
@git.log.object('no-exist.txt').execute
113+
end
114+
end
115+
116+
def test_log_with_empty_commit_message
117+
Dir.mktmpdir do |dir|
118+
git = Git.init(dir)
119+
expected_message = 'message'
120+
git.commit(expected_message, { allow_empty: true })
121+
git.commit('', { allow_empty: true, allow_empty_message: true })
122+
log = git.log.execute
123+
assert_equal(2, log.to_a.size)
124+
assert_equal('', log[0].message)
125+
assert_equal(expected_message, log[1].message)
126+
end
127+
end
128+
129+
def test_log_cherry
130+
l = @git.log.between( 'master', 'cherry').cherry.execute
131+
assert_equal( 1, l.size )
132+
end
133+
134+
def test_log_merges
135+
expected_command_line = ['log', '--max-count=30', '--no-color', '--pretty=raw', '--merges', {chdir: nil}]
136+
assert_command_line_eq(expected_command_line) { |git| git.log.merges.execute }
137+
end
138+
139+
def test_execute_returns_immutable_results
140+
log_query = @git.log(10)
141+
initial_results = log_query.execute
142+
assert_equal(10, initial_results.size)
143+
144+
# Modify the original query object
145+
log_query.max_count(5)
146+
new_results = log_query.execute
147+
148+
# The initial result set should not have changed
149+
assert_equal(10, initial_results.size)
150+
151+
# The new result set should reflect the change
152+
assert_equal(5, new_results.size)
153+
end
154+
end

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy