git 3.1.1 → 4.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ea6b964531a2d91cdc46c4e4c7a6d9f79251f246f2c8856c271b989c6d22800
4
- data.tar.gz: 3fd581082807719899cdbe85eea5ca8eca35803402f6822c66bd5b95b27de10b
3
+ metadata.gz: 93f63d19e697e7cc1f9d6f8fed0a34ff32015c5ed24357f183f2493dca636953
4
+ data.tar.gz: 8e7619c86fee8af92c11d60c6af450e3814bbf77d456fcb3c6d3c914d985403f
5
5
  SHA512:
6
- metadata.gz: ae34ed0370f1ac66b68c03e13ef16d9415e531f3ebe0b7af906dc6d9150c4fc0eec63da7dde987530f7a24bf17966cb3053b40723e0056c78652763340b8e71c
7
- data.tar.gz: ec675f610d8d193dac8c0debca9de1480d154b3d09c34e172dbb0e68d7a47625fdcb7d8bef7a5625e3fef8dfd10e029e1ff682ee033b698beebae43414b3454f
6
+ metadata.gz: 1f003e0ea707da5dc4a164c041407802650390e6a03b496d1960994dfd79cae393ab16da3ed062b0cbeaa6839e7d21931d8e9954d7c19fb8ec25cbbfb0f46fa7
7
+ data.tar.gz: 552efec7c3d8b2e09cfa1c943666e72a3d8aaa336a64252c7d955412cef092c5aede3e87ca7f17df0e8f673a9ed3b35cafcfcf04912b5c8e65ad0082ac415f46
@@ -22,18 +22,26 @@ jobs:
22
22
  fail-fast: false
23
23
  matrix:
24
24
  # Only the latest versions of JRuby and TruffleRuby are tested
25
- ruby: ["3.1", "3.2", "3.3", "3.4", "truffleruby-24.1.2", "jruby-9.4.12.0"]
25
+ ruby: ["3.2", "3.3", "3.4", "truffleruby-24.2.1", "jruby-10.0.0.1"]
26
26
  operating-system: [ubuntu-latest]
27
27
  experimental: [No]
28
+ java_version: [""]
28
29
  include:
29
- - # Only test with minimal Ruby version on Windows
30
- ruby: 3.1
30
+ - ruby: 3.2
31
31
  operating-system: windows-latest
32
+ experimental: No
32
33
 
33
34
  steps:
34
35
  - name: Checkout Code
35
36
  uses: actions/checkout@v4
36
37
 
38
+ - name: Setup Java
39
+ if: matrix.java_version != ''
40
+ uses: actions/setup-java@v4
41
+ with:
42
+ distribution: 'temurin'
43
+ java-version: ${{ matrix.java_version }}
44
+
37
45
  - name: Setup Ruby
38
46
  uses: ruby/setup-ruby@v1
39
47
  with:
@@ -27,16 +27,25 @@ jobs:
27
27
  ruby: head
28
28
  operating-system: ubuntu-latest
29
29
  experimental: Yes
30
+ java_version: ""
30
31
 
31
32
  - # Since JRuby on Windows is known to not work, consider this experimental
32
33
  ruby: jruby-head
33
34
  operating-system: windows-latest
34
35
  experimental: Yes
36
+ java_version: "21"
35
37
 
36
38
  steps:
37
39
  - name: Checkout Code
38
40
  uses: actions/checkout@v4
39
41
 
42
+ - name: Setup Java
43
+ if: matrix.java_version != ''
44
+ uses: actions/setup-java@v4
45
+ with:
46
+ distribution: 'temurin'
47
+ java-version: ${{ matrix.java_version }}
48
+
40
49
  - name: Setup Ruby
41
50
  uses: ruby/setup-ruby@v1
42
51
  with:
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "3.1.1"
2
+ ".": "4.0.0"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@
5
5
 
6
6
  # Change Log
7
7
 
8
+ ## [4.0.0](https://github.com/ruby-git/ruby-git/compare/v3.1.1...v4.0.0) (2025-07-02)
9
+
10
+
11
+ ### ⚠ BREAKING CHANGES
12
+
13
+ * Users will need to be on Ruby 3.2 or greater
14
+
15
+ ### Features
16
+
17
+ * Add Log#execute to run the log and return an immutable result ([ded54c4](https://github.com/ruby-git/ruby-git/commit/ded54c4b551aefb7de35b9505ce14f2061d1708c))
18
+ * **diff:** Refactor Git::Diff to separate concerns and improve AP ([e22eb10](https://github.com/ruby-git/ruby-git/commit/e22eb10bf2e4049f1a0fb325341ef7489f25e66e))
19
+ * Upgrade minimally supported Ruby to 3.2 ([fb93ef1](https://github.com/ruby-git/ruby-git/commit/fb93ef14def222d6eca29f49a5f810a3d6de5787))
20
+
21
+
22
+ ### Other Changes
23
+
24
+ * Remove unneeded explicit return statements ([28e07ae](https://github.com/ruby-git/ruby-git/commit/28e07ae2e91a8defd52549393bf6f3fcbede122e))
25
+ * Upgrade to ProcessExecuter 4.x ([5b00d3b](https://github.com/ruby-git/ruby-git/commit/5b00d3b9c4063c9988d844eec9ddedddb8c26446))
26
+
8
27
  ## [3.1.1](https://github.com/ruby-git/ruby-git/compare/v3.1.0...v3.1.1) (2025-07-02)
9
28
 
10
29
 
data/README.md CHANGED
@@ -18,6 +18,7 @@
18
18
  - [Major Objects](#major-objects)
19
19
  - [Errors Raised By This Gem](#errors-raised-by-this-gem)
20
20
  - [Specifying And Handling Timeouts](#specifying-and-handling-timeouts)
21
+ - [Deprecations](#deprecations)
21
22
  - [Examples](#examples)
22
23
  - [Ruby version support policy](#ruby-version-support-policy)
23
24
  - [License](#license)
@@ -202,6 +203,24 @@ rescue Git::TimeoutError => e
202
203
  end
203
204
  ```
204
205
 
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
+
205
224
  ## Examples
206
225
 
207
226
  Here are a bunch of examples of how to use the Ruby/Git package.
data/git.gemspec CHANGED
@@ -24,12 +24,12 @@ Gem::Specification.new do |s|
24
24
  s.metadata['documentation_uri'] = "https://rubydoc.info/gems/#{s.name}/#{s.version}"
25
25
 
26
26
  s.require_paths = ['lib']
27
- s.required_ruby_version = '>= 3.0.0'
27
+ s.required_ruby_version = '>= 3.2.0'
28
28
  s.requirements = ['git 2.28.0 or greater']
29
29
 
30
30
  s.add_runtime_dependency 'activesupport', '>= 5.0'
31
31
  s.add_runtime_dependency 'addressable', '~> 2.8'
32
- s.add_runtime_dependency 'process_executer', '~> 1.3'
32
+ s.add_runtime_dependency 'process_executer', '~> 4.0'
33
33
  s.add_runtime_dependency 'rchardet', '~> 1.9'
34
34
 
35
35
  s.add_development_dependency 'create_github_release', '~> 2.1'
data/lib/git/base.rb CHANGED
@@ -782,6 +782,27 @@ module Git
782
782
  shas.map { |sha| gcommit(sha) }
783
783
  end
784
784
 
785
+ # Returns a Git::Diff::Stats object for accessing diff statistics.
786
+ #
787
+ # @param objectish [String] The first commit or object to compare. Defaults to 'HEAD'.
788
+ # @param obj2 [String, nil] The second commit or object to compare.
789
+ # @return [Git::Diff::Stats]
790
+ def diff_stats(objectish = 'HEAD', obj2 = nil)
791
+ Git::DiffStats.new(self, objectish, obj2)
792
+ end
793
+
794
+ # Returns a Git::Diff::PathStatus object for accessing the name-status report.
795
+ #
796
+ # @param objectish [String] The first commit or object to compare. Defaults to 'HEAD'.
797
+ # @param obj2 [String, nil] The second commit or object to compare.
798
+ # @return [Git::Diff::PathStatus]
799
+ def diff_path_status(objectish = 'HEAD', obj2 = nil)
800
+ Git::DiffPathStatus.new(self, objectish, obj2)
801
+ end
802
+
803
+ # Provided for backwards compatibility
804
+ alias diff_name_status diff_path_status
805
+
785
806
  private
786
807
 
787
808
  # Normalize options before they are sent to Git::Base.new
@@ -192,8 +192,13 @@ module Git
192
192
  def run(*args, out: nil, err: nil, normalize:, chomp:, merge:, chdir: nil, timeout: nil)
193
193
  git_cmd = build_git_cmd(args)
194
194
  begin
195
- result = ProcessExecuter.run(env, *git_cmd, out: out, err: err, merge:, chdir: (chdir || :not_set), timeout: timeout, raise_errors: false)
196
- rescue ProcessExecuter::Command::ProcessIOError => e
195
+ options = { chdir: (chdir || :not_set), timeout_after: timeout, raise_errors: false }
196
+ options[:out] = out unless out.nil?
197
+ options[:err] = err unless err.nil?
198
+ options[:merge_output] = merge unless merge.nil?
199
+
200
+ result = ProcessExecuter.run_with_capture(env, *git_cmd, **options)
201
+ rescue ProcessExecuter::ProcessIOError => e
197
202
  raise Git::ProcessIOError.new(e.message), cause: e.exception.cause
198
203
  end
199
204
  process_result(result, normalize, chomp, timeout)
@@ -274,14 +279,10 @@ module Git
274
279
  # @api private
275
280
  #
276
281
  def post_process(raw_output, normalize, chomp)
277
- if raw_output.respond_to?(:string)
278
- output = raw_output.string.dup
279
- output = output.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join if normalize
280
- output.chomp! if chomp
281
- output
282
- else
283
- nil
284
- end
282
+ output = raw_output.dup
283
+ output = output.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join if normalize
284
+ output.chomp! if chomp
285
+ output
285
286
  end
286
287
  end
287
288
  end
@@ -19,15 +19,21 @@ module Git
19
19
  # result = Git::CommandLineResult.new(git_cmd, status, stdout, stderr)
20
20
  #
21
21
  # @param git_cmd [Array<String>] the git command that was executed
22
- # @param status [Process::Status] the status of the process
23
- # @param stdout [String] the output of the process
24
- # @param stderr [String] the error output of the process
22
+ # @param status [ProcessExecuter::ResultWithCapture] the status of the process
23
+ # @param stdout [String] the processed stdout of the process
24
+ # @param stderr [String] the processed stderr of the process
25
25
  #
26
26
  def initialize(git_cmd, status, stdout, stderr)
27
27
  @git_cmd = git_cmd
28
28
  @status = status
29
29
  @stdout = stdout
30
30
  @stderr = stderr
31
+
32
+ # ProcessExecuter::ResultWithCapture changed the timeout? method to timed_out?
33
+ # in version 4.x. This is a compatibility layer to maintain the old method name
34
+ # for backward compatibility.
35
+ #
36
+ status.define_singleton_method(:timeout?) { timed_out? }
31
37
  end
32
38
 
33
39
  # @attribute [r] git_cmd
data/lib/git/diff.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Git
3
+ require_relative 'diff_path_status'
4
+ require_relative 'diff_stats'
4
5
 
5
- # object that holds the last X commits on given branch
6
+ module Git
7
+ # object that holds the diff between two commits
6
8
  class Diff
7
9
  include Enumerable
8
10
 
@@ -12,63 +14,68 @@ module Git
12
14
  @to = to && to.to_s
13
15
 
14
16
  @path = nil
15
- @full_diff = nil
16
17
  @full_diff_files = nil
17
- @stats = nil
18
18
  end
19
19
  attr_reader :from, :to
20
20
 
21
- def name_status
22
- cache_name_status
23
- end
24
-
25
21
  def path(path)
26
22
  @path = path
27
- return self
23
+ self
28
24
  end
29
25
 
30
- def size
31
- cache_stats
32
- @stats[:total][:files]
26
+ def patch
27
+ @base.lib.diff_full(@from, @to, { path_limiter: @path })
33
28
  end
29
+ alias_method :to_s, :patch
34
30
 
35
- def lines
36
- cache_stats
37
- @stats[:total][:lines]
31
+ def [](key)
32
+ process_full
33
+ @full_diff_files.assoc(key)[1]
38
34
  end
39
35
 
40
- def deletions
41
- cache_stats
42
- @stats[:total][:deletions]
36
+ def each(&block)
37
+ process_full
38
+ @full_diff_files.map { |file| file[1] }.each(&block)
43
39
  end
44
40
 
45
- def insertions
46
- cache_stats
47
- @stats[:total][:insertions]
41
+ #
42
+ # DEPRECATED METHODS
43
+ #
44
+
45
+ def name_status
46
+ Git::Deprecation.warn("Git::Diff#name_status is deprecated. Use Git::Base#diff_path_status instead.")
47
+ path_status_provider.to_h
48
48
  end
49
49
 
50
- def stats
51
- cache_stats
52
- @stats
50
+ def size
51
+ Git::Deprecation.warn("Git::Diff#size is deprecated. Use Git::Base#diff_stats(...).total[:files] instead.")
52
+ stats_provider.total[:files]
53
53
  end
54
54
 
55
- # if file is provided and is writable, it will write the patch into the file
56
- def patch(file = nil)
57
- cache_full
58
- @full_diff
55
+
56
+
57
+ def lines
58
+ Git::Deprecation.warn("Git::Diff#lines is deprecated. Use Git::Base#diff_stats(...).lines instead.")
59
+ stats_provider.lines
59
60
  end
60
- alias_method :to_s, :patch
61
61
 
62
- # enumerable methods
62
+ def deletions
63
+ Git::Deprecation.warn("Git::Diff#deletions is deprecated. Use Git::Base#diff_stats(...).deletions instead.")
64
+ stats_provider.deletions
65
+ end
63
66
 
64
- def [](key)
65
- process_full
66
- @full_diff_files.assoc(key)[1]
67
+ def insertions
68
+ Git::Deprecation.warn("Git::Diff#insertions is deprecated. Use Git::Base#diff_stats(...).insertions instead.")
69
+ stats_provider.insertions
67
70
  end
68
71
 
69
- def each(&block) # :yields: each Git::DiffFile in turn
70
- process_full
71
- @full_diff_files.map { |file| file[1] }.each(&block)
72
+ def stats
73
+ Git::Deprecation.warn("Git::Diff#stats is deprecated. Use Git::Base#diff_stats instead.")
74
+ # CORRECTED: Re-create the original hash structure for backward compatibility
75
+ {
76
+ files: stats_provider.files,
77
+ total: stats_provider.total
78
+ }
72
79
  end
73
80
 
74
81
  class DiffFile
@@ -102,56 +109,48 @@ module Git
102
109
 
103
110
  private
104
111
 
105
- def cache_full
106
- @full_diff ||= @base.lib.diff_full(@from, @to, {:path_limiter => @path})
107
- end
108
-
109
- def process_full
110
- return if @full_diff_files
111
- cache_full
112
- @full_diff_files = process_full_diff
113
- end
112
+ def process_full
113
+ return if @full_diff_files
114
+ @full_diff_files = process_full_diff
115
+ end
114
116
 
115
- def cache_stats
116
- @stats ||= @base.lib.diff_stats(@from, @to, {:path_limiter => @path})
117
- end
117
+ # CORRECTED: Pass the @path variable to the new objects
118
+ def path_status_provider
119
+ @path_status_provider ||= Git::DiffPathStatus.new(@base, @from, @to, @path)
120
+ end
118
121
 
119
- def cache_name_status
120
- @name_status ||= @base.lib.diff_name_status(@from, @to, {:path => @path})
121
- end
122
+ # CORRECTED: Pass the @path variable to the new objects
123
+ def stats_provider
124
+ @stats_provider ||= Git::DiffStats.new(@base, @from, @to, @path)
125
+ end
122
126
 
123
- # break up @diff_full
124
- def process_full_diff
125
- defaults = {
126
- :mode => '',
127
- :src => '',
128
- :dst => '',
129
- :type => 'modified'
130
- }
131
- final = {}
132
- current_file = nil
133
- @full_diff.split("\n").each do |line|
134
- if m = %r{\Adiff --git ("?)a/(.+?)\1 ("?)b/(.+?)\3\z}.match(line)
135
- current_file = Git::EscapedPath.new(m[2]).unescape
136
- final[current_file] = defaults.merge({:patch => line, :path => current_file})
137
- else
138
- if m = /^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/.match(line)
139
- final[current_file][:src] = m[1]
140
- final[current_file][:dst] = m[2]
141
- final[current_file][:mode] = m[3].strip if m[3]
142
- end
143
- if m = /^([[:alpha:]]*?) file mode (......)/.match(line)
144
- final[current_file][:type] = m[1]
145
- final[current_file][:mode] = m[2]
146
- end
147
- if m = /^Binary files /.match(line)
148
- final[current_file][:binary] = true
149
- end
150
- final[current_file][:patch] << "\n" + line
127
+ def process_full_diff
128
+ defaults = {
129
+ mode: '', src: '', dst: '', type: 'modified'
130
+ }
131
+ final = {}
132
+ current_file = nil
133
+ patch.split("\n").each do |line|
134
+ if m = %r{\Adiff --git ("?)a/(.+?)\1 ("?)b/(.+?)\3\z}.match(line)
135
+ current_file = Git::EscapedPath.new(m[2]).unescape
136
+ final[current_file] = defaults.merge({ patch: line, path: current_file })
137
+ else
138
+ if m = /^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/.match(line)
139
+ final[current_file][:src] = m[1]
140
+ final[current_file][:dst] = m[2]
141
+ final[current_file][:mode] = m[3].strip if m[3]
142
+ end
143
+ if m = /^([[:alpha:]]*?) file mode (......)/.match(line)
144
+ final[current_file][:type] = m[1]
145
+ final[current_file][:mode] = m[2]
146
+ end
147
+ if m = /^Binary files /.match(line)
148
+ final[current_file][:binary] = true
151
149
  end
150
+ final[current_file][:patch] << "\n" + line
152
151
  end
153
- final.map { |e| [e[0], DiffFile.new(@base, e[1])] }
154
152
  end
155
-
153
+ final.map { |e| [e[0], DiffFile.new(@base, e[1])] }
154
+ end
156
155
  end
157
156
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ class DiffPathStatus
5
+ include Enumerable
6
+
7
+ # @private
8
+ def initialize(base, from, to, path_limiter = nil)
9
+ # Eagerly check for invalid arguments
10
+ [from, to].compact.each do |arg|
11
+ raise ArgumentError, "Invalid argument: '#{arg}'" if arg.start_with?('-')
12
+ end
13
+
14
+ @base = base
15
+ @from = from
16
+ @to = to
17
+ @path_limiter = path_limiter
18
+ @path_status = nil
19
+ end
20
+
21
+ # Iterates over each file's status.
22
+ #
23
+ # @yield [path, status]
24
+ def each(&block)
25
+ fetch_path_status.each(&block)
26
+ end
27
+
28
+ # Returns the name-status report as a Hash.
29
+ #
30
+ # @return [Hash<String, String>] A hash where keys are file paths
31
+ # and values are their status codes.
32
+ def to_h
33
+ fetch_path_status
34
+ end
35
+
36
+ private
37
+
38
+ # Lazily fetches and caches the path status from the git lib.
39
+ def fetch_path_status
40
+ @path_status ||= @base.lib.diff_path_status(
41
+ @from, @to, { path: @path_limiter }
42
+ )
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Provides access to the statistics of a diff between two commits,
5
+ # including insertions, deletions, and file-level details.
6
+ class DiffStats
7
+ # @private
8
+ def initialize(base, from, to, path_limiter = nil)
9
+ # Eagerly check for invalid arguments
10
+ [from, to].compact.each do |arg|
11
+ raise ArgumentError, "Invalid argument: '#{arg}'" if arg.start_with?('-')
12
+ end
13
+
14
+ @base = base
15
+ @from = from
16
+ @to = to
17
+ @path_limiter = path_limiter
18
+ @stats = nil
19
+ end
20
+
21
+ # Returns the total number of lines deleted.
22
+ def deletions
23
+ fetch_stats[:total][:deletions]
24
+ end
25
+
26
+ # Returns the total number of lines inserted.
27
+ def insertions
28
+ fetch_stats[:total][:insertions]
29
+ end
30
+
31
+ # Returns the total number of lines changed (insertions + deletions).
32
+ def lines
33
+ fetch_stats[:total][:lines]
34
+ end
35
+
36
+ # Returns a hash of statistics for each file in the diff.
37
+ #
38
+ # @return [Hash<String, {insertions: Integer, deletions: Integer}>]
39
+ def files
40
+ fetch_stats[:files]
41
+ end
42
+
43
+ # Returns a hash of the total statistics for the diff.
44
+ #
45
+ # @return [{insertions: Integer, deletions: Integer, lines: Integer, files: Integer}]
46
+ def total
47
+ fetch_stats[:total]
48
+ end
49
+
50
+ private
51
+
52
+ # Lazily fetches and caches the stats from the git lib.
53
+ def fetch_stats
54
+ @stats ||= @base.lib.diff_stats(
55
+ @from, @to, { path_limiter: @path_limiter }
56
+ )
57
+ end
58
+ end
59
+ end
data/lib/git/lib.rb CHANGED
@@ -217,7 +217,7 @@ module Git
217
217
 
218
218
  arr_opts << commit_ish if commit_ish
219
219
 
220
- return command('describe', *arr_opts)
220
+ command('describe', *arr_opts)
221
221
  end
222
222
 
223
223
  # Return the commits that are within the given revision range
@@ -472,7 +472,7 @@ module Git
472
472
 
473
473
  hsh['message'] = data.join("\n") + "\n"
474
474
 
475
- return hsh
475
+ hsh
476
476
  end
477
477
 
478
478
  CAT_FILE_HEADER_LINE = /\A(?<key>\w+) (?<value>.*)\z/
@@ -543,7 +543,7 @@ module Git
543
543
 
544
544
  hsh['message'] = data.join("\n") + "\n"
545
545
 
546
- return hsh
546
+ hsh
547
547
  end
548
548
 
549
549
  def process_commit_log_data(data)
@@ -584,7 +584,7 @@ module Git
584
584
 
585
585
  hsh_array << hsh if hsh
586
586
 
587
- return hsh_array
587
+ hsh_array
588
588
  end
589
589
 
590
590
  def ls_tree(sha, opts = {})
@@ -758,7 +758,7 @@ module Git
758
758
  :unborn
759
759
  end
760
760
 
761
- return HeadState.new(state, branch_name)
761
+ HeadState.new(state, branch_name)
762
762
  end
763
763
 
764
764
  def branch_current
@@ -848,7 +848,7 @@ module Git
848
848
  hsh
849
849
  end
850
850
 
851
- def diff_name_status(reference1 = nil, reference2 = nil, opts = {})
851
+ def diff_path_status(reference1 = nil, reference2 = nil, opts = {})
852
852
  assert_args_are_not_options('commit or commit range', reference1, reference2)
853
853
 
854
854
  opts_arr = ['--name-status']
@@ -1488,7 +1488,7 @@ module Git
1488
1488
  gz.write(file_content)
1489
1489
  end
1490
1490
  end
1491
- return file
1491
+ file
1492
1492
  end
1493
1493
 
1494
1494
  # returns the current version of git, as an Array of Fixnums.
data/lib/git/log.rb CHANGED
@@ -6,13 +6,13 @@ module Git
6
6
  #
7
7
  # @example The last (default number) of commits
8
8
  # 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
10
10
  #
11
11
  # @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
13
13
  #
14
14
  # @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
16
16
  #
17
17
  # @example All commits that match complex criteria
18
18
  # Git::Log.new(git)
@@ -20,12 +20,62 @@ module Git
20
20
  # .object('README.md')
21
21
  # .since('10 years ago')
22
22
  # .between('v1.0.7', 'HEAD')
23
+ # .execute
23
24
  #
24
25
  # @api public
25
26
  #
26
27
  class Log
27
28
  include Enumerable
28
29
 
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
+
29
79
  # Create a new Git::Log object
30
80
  #
31
81
  # @example
@@ -44,6 +94,25 @@ module Git
44
94
  max_count(max_count)
45
95
  end
46
96
 
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
+
47
116
  # The maximum number of commits to return
48
117
  #
49
118
  # @example All commits returned by `git log`
@@ -82,90 +151,97 @@ module Git
82
151
  def object(objectish)
83
152
  dirty_log
84
153
  @object = objectish
85
- return self
154
+ self
86
155
  end
87
156
 
88
157
  def author(regex)
89
158
  dirty_log
90
159
  @author = regex
91
- return self
160
+ self
92
161
  end
93
162
 
94
163
  def grep(regex)
95
164
  dirty_log
96
165
  @grep = regex
97
- return self
166
+ self
98
167
  end
99
168
 
100
169
  def path(path)
101
170
  dirty_log
102
171
  @path = path
103
- return self
172
+ self
104
173
  end
105
174
 
106
175
  def skip(num)
107
176
  dirty_log
108
177
  @skip = num
109
- return self
178
+ self
110
179
  end
111
180
 
112
181
  def since(date)
113
182
  dirty_log
114
183
  @since = date
115
- return self
184
+ self
116
185
  end
117
186
 
118
187
  def until(date)
119
188
  dirty_log
120
189
  @until = date
121
- return self
190
+ self
122
191
  end
123
192
 
124
193
  def between(sha1, sha2 = nil)
125
194
  dirty_log
126
195
  @between = [sha1, sha2]
127
- return self
196
+ self
128
197
  end
129
198
 
130
199
  def cherry
131
200
  dirty_log
132
201
  @cherry = true
133
- return self
202
+ self
134
203
  end
135
204
 
136
205
  def merges
137
206
  dirty_log
138
207
  @merges = true
139
- return self
208
+ self
140
209
  end
141
210
 
142
211
  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")
144
215
  end
145
216
 
146
217
  # forces git log to run
147
218
 
148
219
  def size
220
+ deprecate_method(__method__)
149
221
  check_log
150
222
  @commits.size rescue nil
151
223
  end
152
224
 
153
225
  def each(&block)
226
+ deprecate_method(__method__)
154
227
  check_log
155
228
  @commits.each(&block)
156
229
  end
157
230
 
158
231
  def first
232
+ deprecate_method(__method__)
159
233
  check_log
160
234
  @commits.first rescue nil
161
235
  end
162
236
 
163
237
  def last
238
+ deprecate_method(__method__)
164
239
  check_log
165
240
  @commits.last rescue nil
166
241
  end
167
242
 
168
243
  def [](index)
244
+ deprecate_method(__method__)
169
245
  check_log
170
246
  @commits[index] rescue nil
171
247
  end
@@ -173,6 +249,10 @@ module Git
173
249
 
174
250
  private
175
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
+
176
256
  def dirty_log
177
257
  @dirty_flag = true
178
258
  end
data/lib/git/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  module Git
4
4
  # The current gem version
5
5
  # @return [String] the current gem version.
6
- VERSION='3.1.1'
6
+ VERSION='4.0.0'
7
7
  end
data/lib/git.rb CHANGED
@@ -65,7 +65,7 @@ module Git
65
65
  end
66
66
 
67
67
  def self.config
68
- return Base.config
68
+ Base.config
69
69
  end
70
70
 
71
71
  def global_config(name = nil, value = nil)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.1
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chacon and others
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '1.3'
46
+ version: '4.0'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '1.3'
53
+ version: '4.0'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: rchardet
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -222,6 +222,8 @@ files:
222
222
  - lib/git/command_line_result.rb
223
223
  - lib/git/config.rb
224
224
  - lib/git/diff.rb
225
+ - lib/git/diff_path_status.rb
226
+ - lib/git/diff_stats.rb
225
227
  - lib/git/encoding_utils.rb
226
228
  - lib/git/errors.rb
227
229
  - lib/git/escaped_path.rb
@@ -248,8 +250,8 @@ licenses:
248
250
  metadata:
249
251
  homepage_uri: http://github.com/ruby-git/ruby-git
250
252
  source_code_uri: http://github.com/ruby-git/ruby-git
251
- changelog_uri: https://rubydoc.info/gems/git/3.1.1/file/CHANGELOG.md
252
- documentation_uri: https://rubydoc.info/gems/git/3.1.1
253
+ changelog_uri: https://rubydoc.info/gems/git/4.0.0/file/CHANGELOG.md
254
+ documentation_uri: https://rubydoc.info/gems/git/4.0.0
253
255
  rdoc_options: []
254
256
  require_paths:
255
257
  - lib
@@ -257,7 +259,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
257
259
  requirements:
258
260
  - - ">="
259
261
  - !ruby/object:Gem::Version
260
- version: 3.0.0
262
+ version: 3.2.0
261
263
  required_rubygems_version: !ruby/object:Gem::Requirement
262
264
  requirements:
263
265
  - - ">="