git 1.7.0 → 1.8.0

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

Potentially problematic release.


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

@@ -3,74 +3,92 @@ module Git
3
3
  class Base
4
4
 
5
5
  module Factory
6
-
7
- # returns a Git::Branch object for branch_name
6
+
7
+ # @return [Git::Branch] an object for branch_name
8
8
  def branch(branch_name = 'master')
9
9
  Git::Branch.new(self, branch_name)
10
10
  end
11
11
 
12
- # returns a Git::Branches object of all the Git::Branch
13
- # objects for this repo
12
+ # @return [Git::Branches] a collection of all the branches in the repository.
13
+ # Each branch is represented as a {Git::Branch}.
14
14
  def branches
15
15
  Git::Branches.new(self)
16
16
  end
17
-
17
+
18
+ # returns a Git::Worktree object for dir, commitish
19
+ def worktree(dir, commitish = nil)
20
+ Git::Worktree.new(self, dir, commitish)
21
+ end
22
+
23
+ # returns a Git::worktrees object of all the Git::Worktrees
24
+ # objects for this repo
25
+ def worktrees
26
+ Git::Worktrees.new(self)
27
+ end
28
+
29
+ # @return [Git::Object::Commit] a commit object
18
30
  def commit_tree(tree = nil, opts = {})
19
31
  Git::Object::Commit.new(self, self.lib.commit_tree(tree, opts))
20
32
  end
21
33
 
22
- # returns a Git::Diff object
34
+ # @return [Git::Diff] a Git::Diff object
23
35
  def diff(objectish = 'HEAD', obj2 = nil)
24
36
  Git::Diff.new(self, objectish, obj2)
25
37
  end
26
-
38
+
39
+ # @return [Git::Object] a Git object
27
40
  def gblob(objectish)
28
41
  Git::Object.new(self, objectish, 'blob')
29
42
  end
30
-
43
+
44
+ # @return [Git::Object] a Git object
31
45
  def gcommit(objectish)
32
46
  Git::Object.new(self, objectish, 'commit')
33
47
  end
34
48
 
49
+ # @return [Git::Object] a Git object
35
50
  def gtree(objectish)
36
51
  Git::Object.new(self, objectish, 'tree')
37
52
  end
38
-
39
- # returns a Git::Log object with count commits
53
+
54
+ # @return [Git::Log] a log with the specified number of commits
40
55
  def log(count = 30)
41
56
  Git::Log.new(self, count)
42
57
  end
43
-
58
+
44
59
  # returns a Git::Object of the appropriate type
45
- # you can also call @git.gtree('tree'), but that's
60
+ # you can also call @git.gtree('tree'), but that's
46
61
  # just for readability. If you call @git.gtree('HEAD') it will
47
- # still return a Git::Object::Commit object.
62
+ # still return a Git::Object::Commit object.
48
63
  #
49
- # @git.object calls a factory method that will run a rev-parse
50
- # on the objectish and determine the type of the object and return
51
- # an appropriate object for that type
64
+ # object calls a factory method that will run a rev-parse
65
+ # on the objectish and determine the type of the object and return
66
+ # an appropriate object for that type
67
+ #
68
+ # @return [Git::Object] an instance of the appropriate type of Git::Object
52
69
  def object(objectish)
53
70
  Git::Object.new(self, objectish)
54
71
  end
55
-
56
- # returns a Git::Remote object
72
+
73
+ # @return [Git::Remote] a remote of the specified name
57
74
  def remote(remote_name = 'origin')
58
75
  Git::Remote.new(self, remote_name)
59
76
  end
60
77
 
61
- # returns a Git::Status object
78
+ # @return [Git::Status] a status object
62
79
  def status
63
80
  Git::Status.new(self)
64
81
  end
65
-
66
- # returns a Git::Tag object
82
+
83
+ # @return [Git::Object::Tag] a tag object
67
84
  def tag(tag_name)
68
85
  Git::Object.new(self, tag_name, 'tag', true)
69
86
  end
70
87
 
71
88
  # Find as good common ancestors as possible for a merge
72
89
  # example: g.merge_base('master', 'some_branch', 'some_sha', octopus: true)
73
- # returns Array<Git::Object::Commit>
90
+ #
91
+ # @return [Array<Git::Object::Commit>] a collection of common ancestors
74
92
  def merge_base(*args)
75
93
  shas = self.lib.merge_base(*args)
76
94
  shas.map { |sha| gcommit(sha) }
@@ -72,6 +72,7 @@ module Git
72
72
  class DiffFile
73
73
  attr_accessor :patch, :path, :mode, :src, :dst, :type
74
74
  @base = nil
75
+ NIL_BLOB_REGEXP = /\A0{4,40}\z/.freeze
75
76
 
76
77
  def initialize(base, hash)
77
78
  @base = base
@@ -89,10 +90,10 @@ module Git
89
90
  end
90
91
 
91
92
  def blob(type = :dst)
92
- if type == :src
93
- @base.object(@src) if @src != '0000000'
94
- else
95
- @base.object(@dst) if @dst != '0000000'
93
+ if type == :src && !NIL_BLOB_REGEXP.match(@src)
94
+ @base.object(@src)
95
+ elsif !NIL_BLOB_REGEXP.match(@dst)
96
+ @base.object(@dst)
96
97
  end
97
98
  end
98
99
  end
@@ -132,7 +133,7 @@ module Git
132
133
  current_file = m[1]
133
134
  final[current_file] = defaults.merge({:patch => line, :path => current_file})
134
135
  else
135
- if m = /^index (.......)\.\.(.......)( ......)*/.match(line)
136
+ if m = /^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/.match(line)
136
137
  final[current_file][:src] = m[1]
137
138
  final[current_file][:dst] = m[2]
138
139
  final[current_file][:mode] = m[3].strip if m[3]
@@ -1,5 +1,6 @@
1
1
  require 'rchardet'
2
2
  require 'tempfile'
3
+ require 'zlib'
3
4
 
4
5
  module Git
5
6
 
@@ -10,6 +11,43 @@ module Git
10
11
 
11
12
  @@semaphore = Mutex.new
12
13
 
14
+ # The path to the Git working copy. The default is '"./.git"'.
15
+ #
16
+ # @return [Pathname] the path to the Git working copy.
17
+ #
18
+ # @see [Git working tree](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefworkingtreeaworkingtree)
19
+ #
20
+ attr_reader :git_work_dir
21
+
22
+ # The path to the Git repository directory. The default is
23
+ # `"#{git_work_dir}/.git"`.
24
+ #
25
+ # @return [Pathname] the Git repository directory.
26
+ #
27
+ # @see [Git repository](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefrepositoryarepository)
28
+ #
29
+ attr_reader :git_dir
30
+
31
+ # The Git index file used to stage changes (using `git add`) before they
32
+ # are committed.
33
+ #
34
+ # @return [Pathname] the Git index file
35
+ #
36
+ # @see [Git index file](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefindexaindex)
37
+ #
38
+ attr_reader :git_index_file
39
+
40
+ # Create a new Git::Lib object
41
+ #
42
+ # @param [Git::Base, Hash] base An object that passes in values for
43
+ # @git_work_dir, @git_dir, and @git_index_file
44
+ #
45
+ # @param [Logger] logger
46
+ #
47
+ # @option base [Pathname] :working_directory
48
+ # @option base [Pathname] :repository
49
+ # @option base [Pathname] :index
50
+ #
13
51
  def initialize(base = nil, logger = nil)
14
52
  @git_dir = nil
15
53
  @git_index_file = nil
@@ -38,14 +76,11 @@ module Git
38
76
  arr_opts = []
39
77
  arr_opts << '--bare' if opts[:bare]
40
78
 
41
- command('init', arr_opts, false)
79
+ command('init', arr_opts)
42
80
  end
43
81
 
44
82
  # tries to clone the given repo
45
83
  #
46
- # returns {:repository} (if bare)
47
- # {:working_directory} otherwise
48
- #
49
84
  # accepts options:
50
85
  # :bare:: no working directory
51
86
  # :branch:: name of branch to track (rather than 'master')
@@ -57,6 +92,8 @@ module Git
57
92
  #
58
93
  # TODO - make this work with SSH password or auth_key
59
94
  #
95
+ # @return [Hash] the options to pass to {Git::Base.new}
96
+ #
60
97
  def clone(repository, name, opts = {})
61
98
  @path = opts[:path] || '.'
62
99
  clone_dir = opts[:path] ? File.join(@path, name) : name
@@ -77,9 +114,16 @@ module Git
77
114
 
78
115
  command('clone', arr_opts)
79
116
 
80
- (opts[:bare] or opts[:mirror]) ? {:repository => clone_dir} : {:working_directory => clone_dir}
117
+ return_base_opts_from_clone(clone_dir, opts)
81
118
  end
82
119
 
120
+ def return_base_opts_from_clone(clone_dir, opts)
121
+ base_opts = {}
122
+ base_opts[:repository] = clone_dir if (opts[:bare] || opts[:mirror])
123
+ base_opts[:working_directory] = clone_dir unless (opts[:bare] || opts[:mirror])
124
+ base_opts[:log] = opts[:log] if opts[:log]
125
+ base_opts
126
+ end
83
127
 
84
128
  ## READ COMMANDS ##
85
129
 
@@ -133,7 +177,7 @@ module Git
133
177
 
134
178
  arr_opts += log_path_options(opts)
135
179
 
136
- command_lines('log', arr_opts, true).map { |l| l.split.first }
180
+ command_lines('log', arr_opts).map { |l| l.split.first }
137
181
  end
138
182
 
139
183
  def full_log_commits(opts={})
@@ -144,7 +188,7 @@ module Git
144
188
 
145
189
  arr_opts += log_path_options(opts)
146
190
 
147
- full_log = command_lines('log', arr_opts, true)
191
+ full_log = command_lines('log', arr_opts)
148
192
 
149
193
  process_commit_log_data(full_log)
150
194
  end
@@ -165,17 +209,17 @@ module Git
165
209
  end
166
210
 
167
211
  def object_type(sha)
168
- command('cat-file', ['-t', sha])
212
+ command('cat-file', '-t', sha)
169
213
  end
170
214
 
171
215
  def object_size(sha)
172
- command('cat-file', ['-s', sha]).to_i
216
+ command('cat-file', '-s', sha).to_i
173
217
  end
174
218
 
175
219
  # returns useful array of raw commit object data
176
220
  def commit_data(sha)
177
221
  sha = sha.to_s
178
- cdata = command_lines('cat-file', ['commit', sha])
222
+ cdata = command_lines('cat-file', 'commit', sha)
179
223
  process_commit_data(cdata, sha, 0)
180
224
  end
181
225
 
@@ -205,7 +249,7 @@ module Git
205
249
 
206
250
  def tag_data(name)
207
251
  sha = sha.to_s
208
- tdata = command_lines('cat-file', ['tag', name])
252
+ tdata = command_lines('cat-file', 'tag', name)
209
253
  process_tag_data(tdata, name, 0)
210
254
  end
211
255
 
@@ -243,6 +287,8 @@ module Git
243
287
  next
244
288
  end
245
289
 
290
+ in_message = false if in_message && line[0..3] != " "
291
+
246
292
  if in_message
247
293
  hsh['message'] << "#{line[4..-1]}\n"
248
294
  next
@@ -268,7 +314,7 @@ module Git
268
314
  end
269
315
 
270
316
  def object_contents(sha, &block)
271
- command('cat-file', ['-p', sha], &block)
317
+ command('cat-file', '-p', sha, &block)
272
318
  end
273
319
 
274
320
  def ls_tree(sha)
@@ -284,11 +330,11 @@ module Git
284
330
  end
285
331
 
286
332
  def mv(file1, file2)
287
- command_lines('mv', ['--', file1, file2])
333
+ command_lines('mv', '--', file1, file2)
288
334
  end
289
335
 
290
336
  def full_tree(sha)
291
- command_lines('ls-tree', ['-r', sha])
337
+ command_lines('ls-tree', '-r', sha)
292
338
  end
293
339
 
294
340
  def tree_depth(sha)
@@ -296,7 +342,7 @@ module Git
296
342
  end
297
343
 
298
344
  def change_head_branch(branch_name)
299
- command('symbolic-ref', ['HEAD', "refs/heads/#{branch_name}"])
345
+ command('symbolic-ref', 'HEAD', "refs/heads/#{branch_name}")
300
346
  end
301
347
 
302
348
  def branches_all
@@ -308,6 +354,39 @@ module Git
308
354
  arr
309
355
  end
310
356
 
357
+ def worktrees_all
358
+ arr = []
359
+ directory = ''
360
+ # Output example for `worktree list --porcelain`:
361
+ # worktree /code/public/ruby-git
362
+ # HEAD 4bef5abbba073c77b4d0ccc1ffcd0ed7d48be5d4
363
+ # branch refs/heads/master
364
+ #
365
+ # worktree /tmp/worktree-1
366
+ # HEAD b8c63206f8d10f57892060375a86ae911fad356e
367
+ # detached
368
+ #
369
+ command_lines('worktree',['list', '--porcelain']).each do |w|
370
+ s = w.split("\s")
371
+ directory = s[1] if s[0] == 'worktree'
372
+ arr << [directory, s[1]] if s[0] == 'HEAD'
373
+ end
374
+ arr
375
+ end
376
+
377
+ def worktree_add(dir, commitish = nil)
378
+ return command('worktree', ['add', dir, commitish]) if !commitish.nil?
379
+ command('worktree', ['add', dir])
380
+ end
381
+
382
+ def worktree_remove(dir)
383
+ command('worktree', ['remove', dir])
384
+ end
385
+
386
+ def worktree_prune
387
+ command('worktree', ['prune'])
388
+ end
389
+
311
390
  def list_files(ref_dir)
312
391
  dir = File.join(@git_dir, 'refs', ref_dir)
313
392
  files = []
@@ -403,7 +482,7 @@ module Git
403
482
  def ls_files(location=nil)
404
483
  location ||= '.'
405
484
  hsh = {}
406
- command_lines('ls-files', ['--stage', location]).each do |line|
485
+ command_lines('ls-files', '--stage', location).each do |line|
407
486
  (info, file) = line.split("\t")
408
487
  (mode, sha, stage) = info.split
409
488
  file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
@@ -412,10 +491,13 @@ module Git
412
491
  hsh
413
492
  end
414
493
 
415
- def ls_remote(location=nil)
416
- location ||= '.'
494
+ def ls_remote(location=nil, opts={})
495
+ arr_opts = []
496
+ arr_opts << ['--refs'] if opts[:refs]
497
+ arr_opts << (location || '.')
498
+
417
499
  Hash.new{ |h,k| h[k] = {} }.tap do |hsh|
418
- command_lines('ls-remote', [location], false).each do |line|
500
+ command_lines('ls-remote', arr_opts).each do |line|
419
501
  (sha, info) = line.split("\t")
420
502
  (ref, type, name) = info.split('/', 3)
421
503
  type ||= 'head'
@@ -427,7 +509,7 @@ module Git
427
509
  end
428
510
 
429
511
  def ignored_files
430
- command_lines('ls-files', ['--others', '-i', '--exclude-standard'])
512
+ command_lines('ls-files', '--others', '-i', '--exclude-standard')
431
513
  end
432
514
 
433
515
 
@@ -442,8 +524,8 @@ module Git
442
524
  end
443
525
 
444
526
  def config_get(name)
445
- do_get = lambda do |path|
446
- command('config', ['--get', name])
527
+ do_get = Proc.new do |path|
528
+ command('config', '--get', name)
447
529
  end
448
530
 
449
531
  if @git_dir
@@ -454,12 +536,12 @@ module Git
454
536
  end
455
537
 
456
538
  def global_config_get(name)
457
- command('config', ['--global', '--get', name], false)
539
+ command('config', '--global', '--get', name)
458
540
  end
459
541
 
460
542
  def config_list
461
- build_list = lambda do |path|
462
- parse_config_list command_lines('config', ['--list'])
543
+ build_list = Proc.new do |path|
544
+ parse_config_list command_lines('config', '--list')
463
545
  end
464
546
 
465
547
  if @git_dir
@@ -470,7 +552,7 @@ module Git
470
552
  end
471
553
 
472
554
  def global_config_list
473
- parse_config_list command_lines('config', ['--global', '--list'], false)
555
+ parse_config_list command_lines('config', '--global', '--list')
474
556
  end
475
557
 
476
558
  def parse_config_list(lines)
@@ -483,7 +565,7 @@ module Git
483
565
  end
484
566
 
485
567
  def parse_config(file)
486
- parse_config_list command_lines('config', ['--list', '--file', file], false)
568
+ parse_config_list command_lines('config', '--list', '--file', file)
487
569
  end
488
570
 
489
571
  # Shows objects
@@ -496,17 +578,17 @@ module Git
496
578
 
497
579
  arr_opts << (path ? "#{objectish}:#{path}" : objectish)
498
580
 
499
- command('show', arr_opts.compact)
581
+ command('show', arr_opts.compact, chomp: false)
500
582
  end
501
583
 
502
584
  ## WRITE COMMANDS ##
503
585
 
504
586
  def config_set(name, value)
505
- command('config', [name, value])
587
+ command('config', name, value)
506
588
  end
507
589
 
508
590
  def global_config_set(name, value)
509
- command('config', ['--global', name, value], false)
591
+ command('config', '--global', name, value)
510
592
  end
511
593
 
512
594
  # updates the repository index using the working directory content
@@ -559,9 +641,10 @@ module Git
559
641
  # :author
560
642
  # :date
561
643
  # :no_verify
644
+ # :allow_empty_message
562
645
  #
563
646
  # @param [String] message the commit message to be used
564
- # @param [Array] opts the commit options to be used
647
+ # @param [Hash] opts the commit options to be used
565
648
  def commit(message, opts = {})
566
649
  arr_opts = []
567
650
  arr_opts << "--message=#{message}" if message
@@ -571,6 +654,7 @@ module Git
571
654
  arr_opts << "--author=#{opts[:author]}" if opts[:author]
572
655
  arr_opts << "--date=#{opts[:date]}" if opts[:date].is_a? String
573
656
  arr_opts << '--no-verify' if opts[:no_verify]
657
+ arr_opts << '--allow-empty-message' if opts[:allow_empty_message]
574
658
 
575
659
  command('commit', arr_opts)
576
660
  end
@@ -629,13 +713,13 @@ module Git
629
713
  end
630
714
 
631
715
  def stash_save(message)
632
- output = command('stash save', [message])
716
+ output = command('stash save', message)
633
717
  output =~ /HEAD is now at/
634
718
  end
635
719
 
636
720
  def stash_apply(id = nil)
637
721
  if id
638
- command('stash apply', [id])
722
+ command('stash apply', id)
639
723
  else
640
724
  command('stash apply')
641
725
  end
@@ -654,7 +738,7 @@ module Git
654
738
  end
655
739
 
656
740
  def branch_delete(branch)
657
- command('branch', ['-D', branch])
741
+ command('branch', '-D', branch)
658
742
  end
659
743
 
660
744
  def checkout(branch, opts = {})
@@ -673,8 +757,9 @@ module Git
673
757
  command('checkout', arr_opts)
674
758
  end
675
759
 
676
- def merge(branch, message = nil)
760
+ def merge(branch, message = nil, opts = {})
677
761
  arr_opts = []
762
+ arr_opts << '--no-ff' if opts[:no_ff]
678
763
  arr_opts << '-m' << message if message
679
764
  arr_opts += [branch]
680
765
  command('merge', arr_opts)
@@ -697,7 +782,7 @@ module Git
697
782
 
698
783
  def unmerged
699
784
  unmerged = []
700
- command_lines('diff', ["--cached"]).each do |line|
785
+ command_lines('diff', "--cached").each do |line|
701
786
  unmerged << $1 if line =~ /^\* Unmerged path (.*)/
702
787
  end
703
788
  unmerged
@@ -705,11 +790,15 @@ module Git
705
790
 
706
791
  def conflicts # :yields: file, your, their
707
792
  self.unmerged.each do |f|
708
- your = Tempfile.new("YOUR-#{File.basename(f)}").path
709
- command('show', ":2:#{f}", true, "> #{escape your}")
710
-
711
- their = Tempfile.new("THEIR-#{File.basename(f)}").path
712
- command('show', ":3:#{f}", true, "> #{escape their}")
793
+ your_tempfile = Tempfile.new("YOUR-#{File.basename(f)}")
794
+ your = your_tempfile.path
795
+ your_tempfile.close # free up file for git command process
796
+ command('show', ":2:#{f}", redirect: "> #{escape your}")
797
+
798
+ their_tempfile = Tempfile.new("THEIR-#{File.basename(f)}")
799
+ their = their_tempfile.path
800
+ their_tempfile.close # free up file for git command process
801
+ command('show', ":3:#{f}", redirect: "> #{escape their}")
713
802
  yield(f, your, their)
714
803
  end
715
804
  end
@@ -734,7 +823,7 @@ module Git
734
823
  end
735
824
 
736
825
  def remote_remove(name)
737
- command('remote', ['rm', name])
826
+ command('remote', 'rm', name)
738
827
  end
739
828
 
740
829
  def remotes
@@ -800,22 +889,22 @@ module Git
800
889
  end
801
890
 
802
891
  def pull(remote='origin', branch='master')
803
- command('pull', [remote, branch])
892
+ command('pull', remote, branch)
804
893
  end
805
894
 
806
895
  def tag_sha(tag_name)
807
896
  head = File.join(@git_dir, 'refs', 'tags', tag_name)
808
897
  return File.read(head).chomp if File.exist?(head)
809
898
 
810
- command('show-ref', ['--tags', '-s', tag_name])
899
+ command('show-ref', '--tags', '-s', tag_name)
811
900
  end
812
901
 
813
902
  def repack
814
- command('repack', ['-a', '-d'])
903
+ command('repack', '-a', '-d')
815
904
  end
816
905
 
817
906
  def gc
818
- command('gc', ['--prune', '--aggressive', '--auto'])
907
+ command('gc', '--prune', '--aggressive', '--auto')
819
908
  end
820
909
 
821
910
  # reads a tree into the current index file
@@ -840,11 +929,11 @@ module Git
840
929
  arr_opts << tree
841
930
  arr_opts << '-p' << opts[:parent] if opts[:parent]
842
931
  arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
843
- command('commit-tree', arr_opts, true, "< #{escape t.path}")
932
+ command('commit-tree', arr_opts, redirect: "< #{escape t.path}")
844
933
  end
845
934
 
846
935
  def update_ref(branch, commit)
847
- command('update-ref', [branch, commit])
936
+ command('update-ref', branch, commit)
848
937
  end
849
938
 
850
939
  def checkout_index(opts = {})
@@ -886,13 +975,19 @@ module Git
886
975
  arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
887
976
  arr_opts << sha
888
977
  arr_opts << '--' << opts[:path] if opts[:path]
889
- command('archive', arr_opts, true, (opts[:add_gzip] ? '| gzip' : '') + " > #{escape file}")
978
+ command('archive', arr_opts, redirect: " > #{escape file}")
979
+ if opts[:add_gzip]
980
+ file_content = File.read(file)
981
+ Zlib::GzipWriter.open(file) do |gz|
982
+ gz.write(file_content)
983
+ end
984
+ end
890
985
  return file
891
986
  end
892
987
 
893
988
  # returns the current version of git, as an Array of Fixnums.
894
989
  def current_command_version
895
- output = command('version', [], false)
990
+ output = command('version')
896
991
  version = output[/\d+\.\d+(\.\d+)+/]
897
992
  version.split('.').collect {|i| i.to_i}
898
993
  end
@@ -913,8 +1008,14 @@ module Git
913
1008
  # @return [<String>] the names of the EVN variables involved in the git commands
914
1009
  ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
915
1010
 
916
- def command_lines(cmd, opts = [], chdir = true, redirect = '')
917
- command(cmd, opts, chdir).lines.map(&:chomp)
1011
+ def command_lines(cmd, *opts)
1012
+ cmd_op = command(cmd, *opts)
1013
+ if cmd_op.encoding.name != "UTF-8"
1014
+ op = cmd_op.encode("UTF-8", "binary", :invalid => :replace, :undef => :replace)
1015
+ else
1016
+ op = cmd_op
1017
+ end
1018
+ op.split("\n")
918
1019
  end
919
1020
 
920
1021
  # Takes the current git's system ENV variables and store them.
@@ -954,7 +1055,15 @@ module Git
954
1055
  restore_git_system_env_variables()
955
1056
  end
956
1057
 
957
- def command(cmd, opts = [], chdir = true, redirect = '', &block)
1058
+ def command(cmd, *opts, &block)
1059
+ command_opts = { chomp: true, redirect: '' }
1060
+ if opts.last.is_a?(Hash)
1061
+ command_opts.merge!(opts.pop)
1062
+ end
1063
+ command_opts.keys.each do |k|
1064
+ raise ArgumentError.new("Unsupported option: #{k}") unless [:chomp, :redirect].include?(k)
1065
+ end
1066
+
958
1067
  global_opts = []
959
1068
  global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
960
1069
  global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
@@ -964,7 +1073,7 @@ module Git
964
1073
 
965
1074
  global_opts = global_opts.flatten.map {|s| escape(s) }.join(' ')
966
1075
 
967
- git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{cmd} #{opts} #{redirect} 2>&1"
1076
+ git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{cmd} #{opts} #{command_opts[:redirect]} 2>&1"
968
1077
 
969
1078
  output = nil
970
1079
 
@@ -985,11 +1094,12 @@ module Git
985
1094
  @logger.debug(output)
986
1095
  end
987
1096
 
988
- if exitstatus > 1 || (exitstatus == 1 && output != '')
989
- raise Git::GitExecuteError.new(git_cmd + ':' + output.to_s)
990
- end
1097
+ raise Git::GitExecuteError, "#{git_cmd}:#{output}" if
1098
+ exitstatus > 1 || (exitstatus == 1 && output != '')
991
1099
 
992
- return output
1100
+ output.chomp! if output && command_opts[:chomp] && !block_given?
1101
+
1102
+ output
993
1103
  end
994
1104
 
995
1105
  # Takes the diff command line output (as Array) and parse it into a Hash
@@ -998,6 +1108,8 @@ module Git
998
1108
  # @param [Array] opts the diff options to be used
999
1109
  # @return [Hash] the diff as Hash
1000
1110
  def diff_as_hash(diff_command, opts=[])
1111
+ # update index before diffing to avoid spurious diffs
1112
+ command('status')
1001
1113
  command_lines(diff_command, opts).inject({}) do |memo, line|
1002
1114
  info, file = line.split("\t")
1003
1115
  mode_src, mode_dest, sha_src, sha_dest, type = info.split
@@ -1024,6 +1136,7 @@ module Git
1024
1136
 
1025
1137
  arr_opts << "-#{opts[:count]}" if opts[:count]
1026
1138
  arr_opts << "--no-color"
1139
+ arr_opts << "--cherry" if opts[:cherry]
1027
1140
  arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
1028
1141
  arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
1029
1142
  arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
@@ -1073,15 +1186,26 @@ module Git
1073
1186
  def run_command(git_cmd, &block)
1074
1187
  return IO.popen(git_cmd, &block) if block_given?
1075
1188
 
1076
- `#{git_cmd}`.chomp.lines.map { |l| normalize_encoding(l) }.join
1189
+ `#{git_cmd}`.lines.map { |l| normalize_encoding(l) }.join
1077
1190
  end
1078
1191
 
1079
1192
  def escape(s)
1080
- return "'#{s && s.to_s.gsub('\'','\'"\'"\'')}'" if RUBY_PLATFORM !~ /mingw|mswin/
1193
+ windows_platform? ? escape_for_windows(s) : escape_for_sh(s)
1194
+ end
1195
+
1196
+ def escape_for_sh(s)
1197
+ "'#{s && s.to_s.gsub('\'','\'"\'"\'')}'"
1198
+ end
1199
+
1200
+ def escape_for_windows(s)
1201
+ # Windows does not need single quote escaping inside double quotes
1202
+ %Q{"#{s}"}
1203
+ end
1081
1204
 
1082
- # Keeping the old escape format for windows users
1083
- escaped = s.to_s.gsub('\'', '\'\\\'\'')
1084
- return %Q{"#{escaped}"}
1205
+ def windows_platform?
1206
+ # Check if on Windows via RUBY_PLATFORM (CRuby) and RUBY_DESCRIPTION (JRuby)
1207
+ win_platform_regex = /mingw|mswin/
1208
+ RUBY_PLATFORM =~ win_platform_regex || RUBY_DESCRIPTION =~ win_platform_regex
1085
1209
  end
1086
1210
 
1087
1211
  end