git 3.1.1 → 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/branch.rb CHANGED
@@ -3,7 +3,8 @@
3
3
  require 'git/path'
4
4
 
5
5
  module Git
6
- class Branch < Path
6
+ # Represents a Git branch
7
+ class Branch
7
8
  attr_accessor :full, :remote, :name
8
9
 
9
10
  def initialize(base, name)
@@ -56,12 +57,12 @@ module Git
56
57
  @base.lib.branch_delete(@name)
57
58
  end
58
59
 
59
- def current
60
- determine_current
60
+ def current # rubocop:disable Naming/PredicateMethod
61
+ @base.lib.branch_current == @name
61
62
  end
62
63
 
63
64
  def contains?(commit)
64
- !@base.lib.branch_contains(commit, self.name).empty?
65
+ !@base.lib.branch_contains(commit, name).empty?
65
66
  end
66
67
 
67
68
  def merge(branch = nil, message = nil)
@@ -93,16 +94,6 @@ module Git
93
94
  @full
94
95
  end
95
96
 
96
- private
97
-
98
- def check_if_create
99
- @base.lib.branch_new(@name) rescue nil
100
- end
101
-
102
- def determine_current
103
- @base.lib.branch_current == @name
104
- end
105
-
106
97
  BRANCH_NAME_REGEXP = %r{
107
98
  ^
108
99
  # Optional 'refs/remotes/' at the beggining to specify a remote tracking branch
@@ -114,6 +105,8 @@ module Git
114
105
  $
115
106
  }x
116
107
 
108
+ private
109
+
117
110
  # Given a full branch name return an Array containing the remote and branch names.
118
111
  #
119
112
  # Removes 'remotes' from the beggining of the name (if present).
@@ -139,7 +132,13 @@ module Git
139
132
  match = name.match(BRANCH_NAME_REGEXP)
140
133
  remote = match[:remote_name] ? Git::Remote.new(@base, match[:remote_name]) : nil
141
134
  branch_name = match[:branch_name]
142
- [ remote, branch_name ]
135
+ [remote, branch_name]
136
+ end
137
+
138
+ def check_if_create
139
+ @base.lib.branch_new(@name)
140
+ rescue StandardError
141
+ nil
143
142
  end
144
143
  end
145
144
  end
data/lib/git/branches.rb CHANGED
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Git
4
-
5
4
  # object that holds all the available branches
6
5
  class Branches
7
-
8
6
  include Enumerable
9
7
 
10
8
  def initialize(base)
@@ -18,11 +16,11 @@ module Git
18
16
  end
19
17
 
20
18
  def local
21
- self.select { |b| !b.remote }
19
+ reject(&:remote)
22
20
  end
23
21
 
24
22
  def remote
25
- self.select { |b| b.remote }
23
+ self.select(&:remote)
26
24
  end
27
25
 
28
26
  # array like methods
@@ -31,8 +29,8 @@ module Git
31
29
  @branches.size
32
30
  end
33
31
 
34
- def each(&block)
35
- @branches.values.each(&block)
32
+ def each(&)
33
+ @branches.values.each(&)
36
34
  end
37
35
 
38
36
  # Returns the target branch
@@ -49,24 +47,22 @@ module Git
49
47
  # @param [#to_s] branch_name the target branch name.
50
48
  # @return [Git::Branch] the target branch.
51
49
  def [](branch_name)
52
- @branches.values.inject(@branches) do |branches, branch|
50
+ @branches.values.each_with_object(@branches) do |branch, branches|
53
51
  branches[branch.full] ||= branch
54
52
 
55
53
  # This is how Git (version 1.7.9.5) works.
56
- # Lets you ignore the 'remotes' if its at the beginning of the branch full name (even if is not a real remote branch).
57
- branches[branch.full.sub('remotes/', '')] ||= branch if branch.full =~ /^remotes\/.+/
58
-
59
- branches
54
+ # Lets you ignore the 'remotes' if its at the beginning of the branch full
55
+ # name (even if is not a real remote branch).
56
+ branches[branch.full.sub('remotes/', '')] ||= branch if branch.full =~ %r{^remotes/.+}
60
57
  end[branch_name.to_s]
61
58
  end
62
59
 
63
60
  def to_s
64
61
  out = ''
65
- @branches.each do |k, b|
62
+ @branches.each_value do |b|
66
63
  out << (b.current ? '* ' : ' ') << b.to_s << "\n"
67
64
  end
68
65
  out
69
66
  end
70
67
  end
71
-
72
68
  end
@@ -97,6 +97,10 @@ module Git
97
97
 
98
98
  # Execute a git command, wait for it to finish, and return the result
99
99
  #
100
+ # Non-option the command line arguements to pass to git. If you collect
101
+ # the command line arguments in an array, make sure you splat the array
102
+ # into the parameter list.
103
+ #
100
104
  # NORMALIZATION
101
105
  #
102
106
  # The command output is returned as a Unicde string containing the binary output
@@ -142,11 +146,9 @@ module Git
142
146
  # stderr.string #=> "unknown revision or path not in the working tree.\n"
143
147
  # end
144
148
  #
145
- # @param args [Array<String>] the command line arguements to pass to git
146
- #
147
- # This array should be splatted into the parameter list.
149
+ # @param options_hash [Hash] the options to pass to the command
148
150
  #
149
- # @param out [#write, nil] the object to write stdout to or nil to ignore stdout
151
+ # @option options_hash [#write, nil] :out the object to write stdout to or nil to ignore stdout
150
152
  #
151
153
  # If this is a 'StringIO' object, then `stdout_writer.string` will be returned.
152
154
  #
@@ -154,20 +156,20 @@ module Git
154
156
  # stdout to a file or some other object that responds to `#write`. The default
155
157
  # behavior will return the output of the command.
156
158
  #
157
- # @param err [#write] the object to write stderr to or nil to ignore stderr
159
+ # @option options_hash [#write, nil] :err the object to write stderr to or nil to ignore stderr
158
160
  #
159
161
  # If this is a 'StringIO' object and `merged_output` is `true`, then
160
162
  # `stderr_writer.string` will be merged into the output returned by this method.
161
163
  #
162
- # @param normalize [Boolean] whether to normalize the output to a valid encoding
164
+ # @option options_hash [Boolean] :normalize whether to normalize the output of stdout and stderr
163
165
  #
164
- # @param chomp [Boolean] whether to chomp the output
166
+ # @option options_hash [Boolean] :chomp whether to chomp both stdout and stderr output
165
167
  #
166
- # @param merge [Boolean] whether to merge stdout and stderr in the string returned
168
+ # @option options_hash [Boolean] :merge whether to merge stdout and stderr in the string returned
167
169
  #
168
- # @param chdir [String] the directory to run the command in
170
+ # @option options_hash [String, nil] :chdir the directory to run the command in
169
171
  #
170
- # @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
172
+ # @option options_hash [Numeric, nil] :timeout the maximum seconds to wait for the command to complete
171
173
  #
172
174
  # If timeout is zero, the timeout will not be enforced.
173
175
  #
@@ -189,16 +191,50 @@ module Git
189
191
  #
190
192
  # @raise [Git::TimeoutError] if the command times out
191
193
  #
192
- def run(*args, out: nil, err: nil, normalize:, chomp:, merge:, chdir: nil, timeout: nil)
194
+ def run(*, **options_hash)
195
+ options_hash = RUN_ARGS.merge(options_hash)
196
+ extra_options = options_hash.keys - RUN_ARGS.keys
197
+ raise ArgumentError, "Unknown options: #{extra_options.join(', ')}" if extra_options.any?
198
+
199
+ result = run_with_capture(*, **options_hash)
200
+ process_result(result, options_hash[:normalize], options_hash[:chomp], options_hash[:timeout])
201
+ end
202
+
203
+ # @return [Git::CommandLineResult] the result of running the command
204
+ #
205
+ # @api private
206
+ #
207
+ def run_with_capture(*args, **options_hash)
193
208
  git_cmd = build_git_cmd(args)
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
197
- raise Git::ProcessIOError.new(e.message), cause: e.exception.cause
209
+ options = run_with_capture_options(**options_hash)
210
+ ProcessExecuter.run_with_capture(env, *git_cmd, **options)
211
+ rescue ProcessExecuter::ProcessIOError => e
212
+ raise Git::ProcessIOError.new(e.message), cause: e.exception.cause
213
+ end
214
+
215
+ def run_with_capture_options(**options_hash)
216
+ chdir = options_hash[:chdir] || :not_set
217
+ timeout_after = options_hash[:timeout]
218
+ out = options_hash[:out]
219
+ err = options_hash[:err]
220
+ merge_output = options_hash[:merge] || false
221
+
222
+ { chdir:, timeout_after:, merge_output:, raise_errors: false }.tap do |options|
223
+ options[:out] = out unless out.nil?
224
+ options[:err] = err unless err.nil?
198
225
  end
199
- process_result(result, normalize, chomp, timeout)
200
226
  end
201
227
 
228
+ RUN_ARGS = {
229
+ normalize: false,
230
+ chomp: false,
231
+ merge: false,
232
+ out: nil,
233
+ err: nil,
234
+ chdir: nil,
235
+ timeout: nil
236
+ }.freeze
237
+
202
238
  private
203
239
 
204
240
  # Build the git command line from the available sources to send to `Process.spawn`
@@ -206,9 +242,9 @@ module Git
206
242
  # @api private
207
243
  #
208
244
  def build_git_cmd(args)
209
- raise ArgumentError.new('The args array can not contain an array') if args.any? { |a| a.is_a?(Array) }
245
+ raise ArgumentError, 'The args array can not contain an array' if args.any? { |a| a.is_a?(Array) }
210
246
 
211
- [binary_path, *global_opts, *args].map { |e| e.to_s }
247
+ [binary_path, *global_opts, *args].map(&:to_s)
212
248
  end
213
249
 
214
250
  # Process the result of the command and return a Git::CommandLineResult
@@ -216,71 +252,78 @@ module Git
216
252
  # Post process output, log the command and result, and raise an error if the
217
253
  # command failed.
218
254
  #
219
- # @param result [ProcessExecuter::Command::Result] the result it is a Process::Status and include command, stdout, and stderr
255
+ # @param result [ProcessExecuter::Command::Result] the result it is a
256
+ # Process::Status and include command, stdout, and stderr
257
+ #
220
258
  # @param normalize [Boolean] whether to normalize the output of each writer
259
+ #
221
260
  # @param chomp [Boolean] whether to chomp the output of each writer
222
- # @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
223
261
  #
224
- # @return [Git::CommandLineResult] the result of the command to return to the caller
262
+ # @param timeout [Numeric, nil] the maximum seconds to wait for the command to
263
+ # complete
264
+ #
265
+ # @return [Git::CommandLineResult] the result of the command to return to the
266
+ # caller
225
267
  #
226
268
  # @raise [Git::FailedError] if the command failed
269
+ #
227
270
  # @raise [Git::SignaledError] if the command was signaled
271
+ #
228
272
  # @raise [Git::TimeoutError] if the command times out
229
- # @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
273
+ #
274
+ # @raise [Git::ProcessIOError] if an exception was raised while collecting
275
+ # subprocess output
230
276
  #
231
277
  # @api private
232
278
  #
233
279
  def process_result(result, normalize, chomp, timeout)
234
280
  command = result.command
235
- processed_out, processed_err = post_process_all([result.stdout, result.stderr], normalize, chomp)
281
+ processed_out, processed_err = post_process_output(result, normalize, chomp)
282
+ log_result(result, command, processed_out, processed_err)
283
+ command_line_result(command, result, processed_out, processed_err, timeout)
284
+ end
285
+
286
+ def log_result(result, command, processed_out, processed_err)
236
287
  logger.info { "#{command} exited with status #{result}" }
237
288
  logger.debug { "stdout:\n#{processed_out.inspect}\nstderr:\n#{processed_err.inspect}" }
289
+ end
290
+
291
+ def command_line_result(command, result, processed_out, processed_err, timeout)
238
292
  Git::CommandLineResult.new(command, result, processed_out, processed_err).tap do |processed_result|
239
293
  raise Git::TimeoutError.new(processed_result, timeout) if result.timeout?
240
- raise Git::SignaledError.new(processed_result) if result.signaled?
241
- raise Git::FailedError.new(processed_result) unless result.success?
294
+
295
+ raise Git::SignaledError, processed_result if result.signaled?
296
+
297
+ raise Git::FailedError, processed_result unless result.success?
242
298
  end
243
299
  end
244
300
 
245
- # Post-process command output and return an array of the results
301
+ # Post-process and return an array of raw output strings
246
302
  #
247
- # @param raw_outputs [Array] the output to post-process
248
- # @param normalize [Boolean] whether to normalize the output of each writer
249
- # @param chomp [Boolean] whether to chomp the output of each writer
303
+ # For each raw output string:
250
304
  #
251
- # @return [Array<String, nil>] the processed output of each command output object that supports `#string`
305
+ # * If normalize: is true, normalize the encoding by transcoding each line from
306
+ # the detected encoding to UTF-8.
307
+ # * If chomp: is true chomp the output after normalization.
252
308
  #
253
- # @api private
254
- #
255
- def post_process_all(raw_outputs, normalize, chomp)
256
- Array.new.tap do |result|
257
- raw_outputs.each { |raw_output| result << post_process(raw_output, normalize, chomp) }
258
- end
259
- end
260
-
261
- # Determine the output to return in the `CommandLineResult`
309
+ # Even if no post-processing is done based on the options, the strings returned
310
+ # are a copy of the raw output strings. The raw output strings are not modified.
262
311
  #
263
- # If the writer can return the output by calling `#string` (such as a StringIO),
264
- # then return the result of normalizing the encoding and chomping the output
265
- # as requested.
312
+ # @param result [ProcessExecuter::ResultWithCapture] the command's output to post-process
266
313
  #
267
- # If the writer does not support `#string`, then return nil. The output is
268
- # assumed to be collected by the writer itself such as when the writer
269
- # is a file instead of a StringIO.
314
+ # @param normalize [Boolean] whether to normalize the output of each writer
315
+ # @param chomp [Boolean] whether to chomp the output of each writer
270
316
  #
271
- # @param raw_output [#string] the output to post-process
272
- # @return [String, nil]
317
+ # @return [Array<String>]
273
318
  #
274
319
  # @api private
275
320
  #
276
- def post_process(raw_output, normalize, chomp)
277
- if raw_output.respond_to?(:string)
278
- output = raw_output.string.dup
321
+ def post_process_output(result, normalize, chomp)
322
+ [result.stdout, result.stderr].map do |raw_output|
323
+ output = raw_output.dup
279
324
  output = output.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join if normalize
280
325
  output.chomp! if chomp
281
326
  output
282
- else
283
- nil
284
327
  end
285
328
  end
286
329
  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/config.rb CHANGED
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Git
4
-
4
+ # The global configuration for this gem
5
5
  class Config
6
-
7
6
  attr_writer :binary_path, :git_ssh, :timeout
8
7
 
9
8
  def initialize
@@ -13,16 +12,15 @@ module Git
13
12
  end
14
13
 
15
14
  def binary_path
16
- @binary_path || ENV['GIT_PATH'] && File.join(ENV['GIT_PATH'], 'git') || 'git'
15
+ @binary_path || (ENV.fetch('GIT_PATH', nil) && File.join(ENV.fetch('GIT_PATH', nil), 'git')) || 'git'
17
16
  end
18
17
 
19
18
  def git_ssh
20
- @git_ssh || ENV['GIT_SSH']
19
+ @git_ssh || ENV.fetch('GIT_SSH', nil)
21
20
  end
22
21
 
23
22
  def timeout
24
- @timeout || (ENV['GIT_TIMEOUT'] && ENV['GIT_TIMEOUT'].to_i)
23
+ @timeout || (ENV.fetch('GIT_TIMEOUT', nil) && ENV['GIT_TIMEOUT'].to_i)
25
24
  end
26
25
  end
27
-
28
26
  end
data/lib/git/diff.rb CHANGED
@@ -1,80 +1,87 @@
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
 
9
11
  def initialize(base, from = nil, to = nil)
10
12
  @base = base
11
- @from = from && from.to_s
12
- @to = to && to.to_s
13
+ @from = from&.to_s
14
+ @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 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(&)
37
+ process_full
38
+ @full_diff_files.map { |file| file[1] }.each(&)
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
+ def lines
56
+ Git::Deprecation.warn('Git::Diff#lines is deprecated. Use Git::Base#diff_stats(...).lines instead.')
57
+ stats_provider.lines
59
58
  end
60
- alias_method :to_s, :patch
61
59
 
62
- # enumerable methods
60
+ def deletions
61
+ Git::Deprecation.warn('Git::Diff#deletions is deprecated. Use Git::Base#diff_stats(...).deletions instead.')
62
+ stats_provider.deletions
63
+ end
63
64
 
64
- def [](key)
65
- process_full
66
- @full_diff_files.assoc(key)[1]
65
+ def insertions
66
+ Git::Deprecation.warn('Git::Diff#insertions is deprecated. Use Git::Base#diff_stats(...).insertions instead.')
67
+ stats_provider.insertions
67
68
  end
68
69
 
69
- def each(&block) # :yields: each Git::DiffFile in turn
70
- process_full
71
- @full_diff_files.map { |file| file[1] }.each(&block)
70
+ def stats
71
+ Git::Deprecation.warn('Git::Diff#stats is deprecated. Use Git::Base#diff_stats instead.')
72
+ # CORRECTED: Re-create the original hash structure for backward compatibility
73
+ {
74
+ files: stats_provider.files,
75
+ total: stats_provider.total
76
+ }
72
77
  end
73
78
 
79
+ # The changes for a single file within a diff
74
80
  class DiffFile
75
81
  attr_accessor :patch, :path, :mode, :src, :dst, :type
82
+
76
83
  @base = nil
77
- NIL_BLOB_REGEXP = /\A0{4,40}\z/.freeze
84
+ NIL_BLOB_REGEXP = /\A0{4,40}\z/
78
85
 
79
86
  def initialize(base, hash)
80
87
  @base = base
@@ -102,56 +109,84 @@ 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})
112
+ def process_full
113
+ return if @full_diff_files
114
+
115
+ @full_diff_files = process_full_diff
116
+ end
117
+
118
+ def path_status_provider
119
+ @path_status_provider ||= Git::DiffPathStatus.new(@base, @from, @to, @path)
120
+ end
121
+
122
+ def stats_provider
123
+ @stats_provider ||= Git::DiffStats.new(@base, @from, @to, @path)
124
+ end
125
+
126
+ def process_full_diff
127
+ FullDiffParser.new(@base, patch).parse
128
+ end
129
+
130
+ # A private parser class to process the output of `git diff`
131
+ # @api private
132
+ class FullDiffParser
133
+ def initialize(base, patch_text)
134
+ @base = base
135
+ @patch_text = patch_text
136
+ @final_files = {}
137
+ @current_file_data = nil
138
+ @defaults = { mode: '', src: '', dst: '', type: 'modified', binary: false }
107
139
  end
108
140
 
109
- def process_full
110
- return if @full_diff_files
111
- cache_full
112
- @full_diff_files = process_full_diff
141
+ def parse
142
+ @patch_text.split("\n").each { |line| process_line(line) }
143
+ @final_files.map { |filename, data| [filename, DiffFile.new(@base, data)] }
113
144
  end
114
145
 
115
- def cache_stats
116
- @stats ||= @base.lib.diff_stats(@from, @to, {:path_limiter => @path})
146
+ private
147
+
148
+ def process_line(line)
149
+ if (new_file_match = line.match(%r{\Adiff --git ("?)a/(.+?)\1 ("?)b/(.+?)\3\z}))
150
+ start_new_file(new_file_match, line)
151
+ else
152
+ append_to_current_file(line)
153
+ end
117
154
  end
118
155
 
119
- def cache_name_status
120
- @name_status ||= @base.lib.diff_name_status(@from, @to, {:path => @path})
156
+ def start_new_file(match, line)
157
+ filename = Git::EscapedPath.new(match[2]).unescape
158
+ @current_file_data = @defaults.merge({ patch: line, path: filename })
159
+ @final_files[filename] = @current_file_data
121
160
  end
122
161
 
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
151
- end
152
- end
153
- final.map { |e| [e[0], DiffFile.new(@base, e[1])] }
162
+ def append_to_current_file(line)
163
+ return unless @current_file_data
164
+
165
+ parse_index_line(line)
166
+ parse_file_mode_line(line)
167
+ check_for_binary(line)
168
+
169
+ @current_file_data[:patch] << "\n#{line}"
170
+ end
171
+
172
+ def parse_index_line(line)
173
+ return unless (match = line.match(/^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/))
174
+
175
+ @current_file_data[:src] = match[1]
176
+ @current_file_data[:dst] = match[2]
177
+ @current_file_data[:mode] = match[3].strip if match[3]
178
+ end
179
+
180
+ def parse_file_mode_line(line)
181
+ return unless (match = line.match(/^([[:alpha:]]*?) file mode (......)/))
182
+
183
+ @current_file_data[:type] = match[1]
184
+ @current_file_data[:mode] = match[2]
154
185
  end
155
186
 
187
+ def check_for_binary(line)
188
+ @current_file_data[:binary] = true if line.match?(/^Binary files /)
189
+ end
190
+ end
156
191
  end
157
192
  end