10000 feat: add Log#execute to run the log and return an immutable result · ruby-git/ruby-git@ded54c4 · GitHub
[go: up one dir, main page]

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< 10000 span class="diff-text-marker">+
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)
0