grit 2.4.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grit might be problematic. Click here for more details.

@@ -1,7 +1,10 @@
1
1
  require 'tempfile'
2
+ require 'posix-spawn'
2
3
  module Grit
3
4
 
4
5
  class Git
6
+ include POSIX::Spawn
7
+
5
8
  class GitTimeout < RuntimeError
6
9
  attr_accessor :command
7
10
  attr_accessor :bytes_read
@@ -23,11 +26,17 @@ module Grit
23
26
  # Everything output on the command's stderr as a String.
24
27
  attr_reader :err
25
28
 
26
- def initialize(command, exitstatus, err='')
27
- @command = command
28
- @exitstatus = exitstatus
29
- @err = err
30
- super "Command exited with #{exitstatus}: #{command}"
29
+ def initialize(command, exitstatus=nil, err='')
30
+ if exitstatus
31
+ @command = command
32
+ @exitstatus = exitstatus
33
+ @err = err
34
+ message = "Command failed [#{exitstatus}]: #{command}"
35
+ message << "\n\n" << err unless err.nil? || err.empty?
36
+ super message
37
+ else
38
+ super command
39
+ end
31
40
  end
32
41
  end
33
42
 
@@ -43,6 +52,14 @@ module Grit
43
52
  ruby_git.put_raw_object(content, type)
44
53
  end
45
54
 
55
+ def get_raw_object(object_id)
56
+ ruby_git.get_raw_object_by_sha1(object_id).content
57
+ end
58
+
59
+ def get_git_object(object_id)
60
+ ruby_git.get_raw_object_by_sha1(object_id).to_hash
61
+ end
62
+
46
63
  def object_exists?(object_id)
47
64
  ruby_git.object_exists?(object_id)
48
65
  end
@@ -67,7 +84,7 @@ module Grit
67
84
  self.git_timeout = 10
68
85
  self.git_max_size = 5242880 # 5.megabytes
69
86
 
70
- def self.with_timeout(timeout = 10.seconds)
87
+ def self.with_timeout(timeout = 10)
71
88
  old_timeout = Grit::Git.git_timeout
72
89
  Grit::Git.git_timeout = timeout
73
90
  yield
@@ -180,16 +197,23 @@ module Grit
180
197
 
181
198
  # Checks if the patch of a commit can be applied to the given head.
182
199
  #
200
+ # options - grit command options hash
183
201
  # head_sha - String SHA or ref to check the patch against.
184
202
  # applies_sha - String SHA of the patch. The patch itself is retrieved
185
203
  # with #get_patch.
186
204
  #
187
205
  # Returns 0 if the patch applies cleanly (according to `git apply`), or
188
206
  # an Integer that is the sum of the failed exit statuses.
189
- def check_applies(head_sha, applies_sha)
207
+ def check_applies(options={}, head_sha=nil, applies_sha=nil)
208
+ options, head_sha, applies_sha = {}, options, head_sha if !options.is_a?(Hash)
209
+ options = options.dup
210
+ options[:env] &&= options[:env].dup
211
+
190
212
  git_index = create_tempfile('index', true)
191
- options = {:env => {'GIT_INDEX_FILE' => git_index}, :raise => true}
192
- status = 0
213
+ (options[:env] ||= {}).merge!('GIT_INDEX_FILE' => git_index)
214
+ options[:raise] = true
215
+
216
+ status = 0
193
217
  begin
194
218
  native(:read_tree, options.dup, head_sha)
195
219
  stdin = native(:diff, options.dup, "#{applies_sha}^", applies_sha)
@@ -202,27 +226,38 @@ module Grit
202
226
 
203
227
  # Gets a patch for a given SHA using `git diff`.
204
228
  #
229
+ # options - grit command options hash
205
230
  # applies_sha - String SHA to get the patch from, using this command:
206
231
  # `git diff #{applies_sha}^ #{applies_sha}`
207
232
  #
208
233
  # Returns the String patch from `git diff`.
209
- def get_patch(applies_sha)
234
+ def get_patch(options={}, applies_sha=nil)
235
+ options, applies_sha = {}, options if !options.is_a?(Hash)
236
+ options = options.dup
237
+ options[:env] &&= options[:env].dup
238
+
210
239
  git_index = create_tempfile('index', true)
211
- native(:diff, {
212
- :env => {'GIT_INDEX_FILE' => git_index}},
213
- "#{applies_sha}^", applies_sha)
240
+ (options[:env] ||= {}).merge!('GIT_INDEX_FILE' => git_index)
241
+
242
+ native(:diff, options, "#{applies_sha}^", applies_sha)
214
243
  end
215
244
 
216
245
  # Applies the given patch against the given SHA of the current repo.
217
246
  #
247
+ # options - grit command hash
218
248
  # head_sha - String SHA or ref to apply the patch to.
219
249
  # patch - The String patch to apply. Get this from #get_patch.
220
250
  #
221
251
  # Returns the String Tree SHA on a successful patch application, or false.
222
- def apply_patch(head_sha, patch)
252
+ def apply_patch(options={}, head_sha=nil, patch=nil)
253
+ options, head_sha, patch = {}, options, head_sha if !options.is_a?(Hash)
254
+ options = options.dup
255
+ options[:env] &&= options[:env].dup
256
+ options[:raise] = true
257
+
223
258
  git_index = create_tempfile('index', true)
259
+ (options[:env] ||= {}).merge!('GIT_INDEX_FILE' => git_index)
224
260
 
225
- options = {:env => {'GIT_INDEX_FILE' => git_index}, :raise => true}
226
261
  begin
227
262
  native(:read_tree, options.dup, head_sha)
228
263
  native(:apply, options.merge(:cached => true, :input => patch))
@@ -252,6 +287,9 @@ module Grit
252
287
  # :raise - When set true, commands that exit with a non-zero status
253
288
  # raise a CommandFailed exception. This option is available only on
254
289
  # platforms that support fork(2).
290
+ # :process_info - By default, a single string with output written to
291
+ # the process's stdout is returned. Setting this option to true
292
+ # results in a [exitstatus, out, err] tuple being returned instead.
255
293
  # args - Non-option arguments passed on the command line.
256
294
  #
257
295
  # Optionally yields to the block an IO object attached to the child
@@ -260,7 +298,12 @@ module Grit
260
298
  # Examples
261
299
  # git.native(:rev_list, {:max_count => 10, :header => true}, "master")
262
300
  #
263
- # Returns a String with all output written to the child process's stdout.
301
+ # Returns a String with all output written to the child process's stdout
302
+ # when the :process_info option is not set.
303
+ # Returns a [exitstatus, out, err] tuple when the :process_info option is
304
+ # set. The exitstatus is an small integer that was the process's exit
305
+ # status. The out and err elements are the data written to stdout and
306
+ # stderr as Strings.
264
307
  # Raises Grit::Git::GitTimeout when the timeout is exceeded or when more
265
308
  # than Grit::Git.git_max_size bytes are output.
266
309
  # Raises Grit::Git::CommandFailed when the :raise option is set true and the
@@ -269,12 +312,13 @@ module Grit
269
312
  # detail about the error.
270
313
  def native(cmd, options = {}, *args, &block)
271
314
  args = args.first if args.size == 1 && args[0].is_a?(Array)
272
- args.map! { |a| a.to_s.strip }
315
+ args.map! { |a| a.to_s }
273
316
  args.reject! { |a| a.empty? }
274
317
 
275
318
  # special option arguments
276
319
  env = options.delete(:env) || {}
277
320
  raise_errors = options.delete(:raise)
321
+ process_info = options.delete(:process_info)
278
322
 
279
323
  # fall back to using a shell when the last argument looks like it wants to
280
324
  # start a pipeline for compatibility with previous versions of grit.
@@ -298,21 +342,24 @@ module Grit
298
342
  Grit.log(argv.join(' ')) if Grit.debug
299
343
 
300
344
  process =
301
- Grit::Process.new(argv, env,
345
+ Child.new(env, *(argv + [{
302
346
  :input => input,
303
347
  :chdir => chdir,
304
348
  :timeout => (Grit::Git.git_timeout if timeout == true),
305
349
  :max => (Grit::Git.git_max_size if timeout == true)
306
- )
307
- status = process.status
350
+ }]))
308
351
  Grit.log(process.out) if Grit.debug
309
352
  Grit.log(process.err) if Grit.debug
353
+
354
+ status = process.status
310
355
  if raise_errors && !status.success?
311
356
  raise CommandFailed.new(argv.join(' '), status.exitstatus, process.err)
357
+ elsif process_info
358
+ [status.exitstatus, process.out, process.err]
312
359
  else
313
360
  process.out
314
361
  end
315
- rescue Grit::Process::TimeoutExceeded, Grit::Process::MaximumOutputExceeded
362
+ rescue TimeoutExceeded, MaximumOutputExceeded
316
363
  raise GitTimeout, argv.join(' ')
317
364
  end
318
365
 
@@ -404,18 +451,18 @@ module Grit
404
451
 
405
452
  def sh(command, &block)
406
453
  process =
407
- Grit::Process.new(
408
- command, {},
454
+ Child.new(
455
+ command,
409
456
  :timeout => Git.git_timeout,
410
457
  :max => Git.git_max_size
411
458
  )
412
459
  [process.out, process.err]
413
- rescue Grit::Process::TimeoutExceeded, Grit::Process::MaximumOutputExceeded
460
+ rescue TimeoutExceeded, MaximumOutputExceeded
414
461
  raise GitTimeout, command
415
462
  end
416
463
 
417
464
  def wild_sh(command, &block)
418
- process = Grit::Process.new(command)
465
+ process = Child.new(command)
419
466
  [process.out, process.err]
420
467
  end
421
468
 
@@ -12,6 +12,10 @@ module Grit
12
12
  # which the next commit will be based.
13
13
  attr_accessor :current_tree
14
14
 
15
+ # Public: if a tree or commit is written, this stores the size of that object
16
+ attr_reader :last_tree_size
17
+ attr_reader :last_commit_size
18
+
15
19
  # Initialize a new Index object.
16
20
  #
17
21
  # repo - The Grit::Repo to which the index belongs.
@@ -71,16 +75,16 @@ module Grit
71
75
  # :parents - Array of String commit SHA1s or Grit::Commit
72
76
  # objects to attach this commit to to form a
73
77
  # new head (default: nil).
74
- # :actor - The Grit::Actor details of the user making
78
+ # :actor - The Grit::Actor details of the user making
75
79
  # the commit (default: nil).
76
80
  # :last_tree - The String SHA1 of a tree to compare with
77
- # in order to avoid making empty commits
81
+ # in order to avoid making empty commits
78
82
  # (default: nil).
79
83
  # :head - The String branch name to write this head to
80
- # (default: "master").
81
- # :committed_date - The Time that the commit was made.
84
+ # (default: nil).
85
+ # :committed_date - The Time that the commit was made.
82
86
  # (Default: Time.now)
83
- # :authored_date - The Time that the commit was authored.
87
+ # :authored_date - The Time that the commit was authored.
84
88
  # (Default: committed_date)
85
89
  #
86
90
  # The legacy argument style looks like:
@@ -97,7 +101,9 @@ module Grit
97
101
  #
98
102
  # Returns a String of the SHA1 of the new commit.
99
103
  def commit(message, parents = nil, actor = nil, last_tree = nil, head = 'master')
104
+ commit_tree_sha = nil
100
105
  if parents.is_a?(Hash)
106
+ commit_tree_sha = parents[:commit_tree_sha]
101
107
  actor = parents[:actor]
102
108
  committer = parents[:committer]
103
109
  author = parents[:author]
@@ -111,7 +117,11 @@ module Grit
111
117
  committer ||= actor
112
118
  author ||= committer
113
119
 
114
- tree_sha1 = write_tree(self.tree, self.current_tree)
120
+ if commit_tree_sha
121
+ tree_sha1 = commit_tree_sha
122
+ else
123
+ tree_sha1 = write_tree(self.tree, self.current_tree)
124
+ end
115
125
 
116
126
  # don't write identical commits
117
127
  return false if tree_sha1 == last_tree
@@ -135,9 +145,12 @@ module Grit
135
145
  contents << ''
136
146
  contents << message
137
147
 
138
- commit_sha1 = self.repo.git.put_raw_object(contents.join("\n"), 'commit')
148
+ contents = contents.join("\n")
149
+ @last_commit_size = contents.size
150
+ commit_sha1 = self.repo.git.put_raw_object(contents, 'commit')
139
151
 
140
- self.repo.update_ref(head, commit_sha1)
152
+ self.repo.update_ref(head, commit_sha1) if head
153
+ commit_sha1
141
154
  end
142
155
 
143
156
  # Recursively write a tree to the index.
@@ -149,10 +162,12 @@ module Grit
149
162
  # this tree will be based (default: nil).
150
163
  #
151
164
  # Returns the String SHA1 String of the tree.
152
- def write_tree(tree, now_tree = nil)
165
+ def write_tree(tree = nil, now_tree = nil)
166
+ tree = self.tree if !tree
153
167
  tree_contents = {}
154
168
 
155
169
  # fill in original tree
170
+ now_tree = read_tree(now_tree) if(now_tree && now_tree.is_a?(String))
156
171
  now_tree.contents.each do |obj|
157
172
  sha = [obj.id].pack("H*")
158
173
  k = obj.name
@@ -164,6 +179,15 @@ module Grit
164
179
  # overwrite with new tree contents
165
180
  tree.each do |k, v|
166
181
  case v
182
+ when Array
183
+ sha, mode = v
184
+ if sha.size == 40 # must be a sha
185
+ sha = [sha].pack("H*")
186
+ mode = mode.to_i.to_s # leading 0s not allowed
187
+ k = k.split('/').last # slashes not allowed
188
+ str = "%s %s\0%s" % [mode, k, sha]
189
+ tree_contents[k] = str
190
+ end
167
191
  when String
168
192
  sha = write_blob(v)
169
193
  sha = [sha].pack("H*")
@@ -181,6 +205,7 @@ module Grit
181
205
  end
182
206
 
183
207
  tr = tree_contents.sort.map { |k, v| v }.join('')
208
+ @last_tree_size = tr.size
184
209
  self.repo.git.put_raw_object(tr, 'tree')
185
210
  end
186
211
 
@@ -183,6 +183,7 @@ module Grit
183
183
  sha, type, size = line.split(" ", 3)
184
184
  parser = BATCH_PARSERS[type]
185
185
  if type == 'missing' || !parser
186
+ io.seek(size.to_i + 1, IO::SEEK_CUR)
186
187
  objects << nil
187
188
  next
188
189
  end
@@ -345,6 +346,21 @@ module Grit
345
346
  [ Head.find_all(self), Tag.find_all(self), Remote.find_all(self) ].flatten
346
347
  end
347
348
 
349
+ # returns an array of hashes representing all references
350
+ def refs_list
351
+ refs = self.git.for_each_ref
352
+ refarr = refs.split("\n").map do |line|
353
+ shatype, ref = line.split("\t")
354
+ sha, type = shatype.split(' ')
355
+ [ref, sha, type]
356
+ end
357
+ refarr
358
+ end
359
+
360
+ def delete_ref(ref)
361
+ self.git.native(:update_ref, {:d => true}, ref)
362
+ end
363
+
348
364
  def commit_stats(start = 'master', max_count = 10, skip = 0)
349
365
  options = {:max_count => max_count,
350
366
  :skip => skip}
@@ -375,6 +391,11 @@ module Grit
375
391
  Commit.find_all(self, "#{from}..#{to}").reverse
376
392
  end
377
393
 
394
+ def fast_forwardable?(to, from)
395
+ mb = self.git.native(:merge_base, {}, [to, from]).strip
396
+ mb == from
397
+ end
398
+
378
399
  # The Commits objects that are newer than the specified date.
379
400
  # Commits are returned in chronological order.
380
401
  # +start+ is the branch/commit name (default 'master')
@@ -415,23 +436,20 @@ module Grit
415
436
  repo_refs = self.git.rev_list({}, ref).strip.split("\n")
416
437
  other_repo_refs = other_repo.git.rev_list({}, other_ref).strip.split("\n")
417
438
 
418
- (other_repo_refs - repo_refs).map do |ref|
419
- Commit.find_all(other_repo, ref, {:max_count => 1}).first
439
+ (other_repo_refs - repo_refs).map do |refn|
440
+ Commit.find_all(other_repo, refn, {:max_count => 1}).first
420
441
  end
421
442
  end
422
443
 
423
444
  def objects(refs)
424
- Grit.no_quote = true
425
- obj = self.git.rev_list({:objects => true, :timeout => false}, refs).split("\n").map { |a| a[0, 40] }
426
- Grit.no_quote = false
427
- obj
445
+ refs = refs.split(/\s+/) if refs.respond_to?(:to_str)
446
+ self.git.rev_list({:objects => true, :timeout => false}, *refs).
447
+ split("\n").map { |a| a[0, 40] }
428
448
  end
429
449
 
430
450
  def commit_objects(refs)
431
- Grit.no_quote = true
432
- obj = self.git.rev_list({:timeout => false}, refs).split("\n").map { |a| a[0, 40] }
433
- Grit.no_quote = false
434
- obj
451
+ refs = refs.split(/\s+/) if refs.respond_to?(:to_str)
452
+ self.git.rev_list({:timeout => false}, *refs).split("\n").map { |a| a[0, 40] }
435
453
  end
436
454
 
437
455
  def objects_between(ref1, ref2 = nil)
@@ -448,11 +466,10 @@ module Grit
448
466
  Grit.no_quote = true
449
467
  if parents
450
468
  # PARENTS:
451
- cmd = "-r -t -m #{commit_sha}"
452
- revs = self.git.diff_tree({:timeout => false}, cmd).strip.split("\n").map{ |a| r = a.split(' '); r[3] if r[1] != '160000' }
469
+ revs = self.git.diff_tree({:timeout => false, :r => true, :t => true, :m => true}, commit_sha).
470
+ strip.split("\n").map{ |a| r = a.split(' '); r[3] if r[1] != '160000' }
453
471
  else
454
472
  # NO PARENTS:
455
- cmd = "-r -t #{commit_sha}"
456
473
  revs = self.git.native(:ls_tree, {:timeout => false, :r => true, :t => true}, commit_sha).
457
474
  split("\n").map{ |a| a.split("\t").first.split(' ')[2] }
458
475
  end
@@ -463,7 +480,7 @@ module Grit
463
480
 
464
481
  # The Tree object for the given treeish reference
465
482
  # +treeish+ is the reference (default 'master')
466
- # +paths+ is an optional Array of directory paths to restrict the tree (deafult [])
483
+ # +paths+ is an optional Array of directory paths to restrict the tree (default [])
467
484
  #
468
485
  # Examples
469
486
  # repo.tree('master', ['lib/'])
@@ -473,6 +490,43 @@ module Grit
473
490
  Tree.construct(self, treeish, paths)
474
491
  end
475
492
 
493
+ # quick way to get a simple array of hashes of the entries
494
+ # of a single tree or recursive tree listing from a given
495
+ # sha or reference
496
+ # +treeish+ is the reference (default 'master')
497
+ # +options+ is a hash or options - currently only takes :recursive
498
+ #
499
+ # Examples
500
+ # repo.lstree('master', :recursive => true)
501
+ #
502
+ # Returns array of hashes - one per tree entry
503
+ def lstree(treeish = 'master', options = {})
504
+ # check recursive option
505
+ opts = {:timeout => false, :l => true, :t => true}
506
+ if options[:recursive]
507
+ opts[:r] = true
508
+ end
509
+ # mode, type, sha, size, path
510
+ revs = self.git.native(:ls_tree, opts, treeish)
511
+ lines = revs.split("\n")
512
+ revs = lines.map do |a|
513
+ stuff, path = a.split("\t")
514
+ mode, type, sha, size = stuff.split(" ")
515
+ entry = {:mode => mode, :type => type, :sha => sha, :path => path}
516
+ entry[:size] = size.strip.to_i if size.strip != '-'
517
+ entry
518
+ end
519
+ revs
520
+ end
521
+
522
+ def object(sha)
523
+ obj = git.get_git_object(sha)
524
+ raw = Grit::GitRuby::Internal::RawObject.new(obj[:type], obj[:content])
525
+ object = Grit::GitRuby::GitObject.from_raw(raw)
526
+ object.sha = sha
527
+ object
528
+ end
529
+
476
530
  # The Blob object for the given id
477
531
  # +id+ is the SHA1 id of the blob
478
532
  #