git 1.6.0 → 1.9.1

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) }
data/lib/git/diff.rb CHANGED
@@ -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]
data/lib/git/lib.rb CHANGED
@@ -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
 
@@ -114,12 +158,12 @@ module Git
114
158
  arr_opts << '--always' if opts[:always]
115
159
  arr_opts << '--exact-match' if opts[:exact_match] || opts[:"exact-match"]
116
160
 
117
- arr_opts << '--dirty' if opts['dirty'] == true
118
- arr_opts << "--dirty=#{opts['dirty']}" if opts['dirty'].is_a?(String)
161
+ arr_opts << '--dirty' if opts[:dirty] == true
162
+ arr_opts << "--dirty=#{opts[:dirty]}" if opts[:dirty].is_a?(String)
119
163
 
120
- arr_opts << "--abbrev=#{opts['abbrev']}" if opts[:abbrev]
121
- arr_opts << "--candidates=#{opts['candidates']}" if opts[:candidates]
122
- arr_opts << "--match=#{opts['match']}" if opts[:match]
164
+ arr_opts << "--abbrev=#{opts[:abbrev]}" if opts[:abbrev]
165
+ arr_opts << "--candidates=#{opts[:candidates]}" if opts[:candidates]
166
+ arr_opts << "--match=#{opts[:match]}" if opts[:match]
123
167
 
124
168
  arr_opts << committish if committish
125
169
 
@@ -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,21 @@ 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
- def config_set(name, value)
505
- command('config', [name, value])
586
+ def config_set(name, value, options = {})
587
+ if options[:file].to_s.empty?
588
+ command('config', name, value)
589
+ else
590
+ command('config', '--file', options[:file], name, value)
591
+ end
506
592
  end
507
593
 
508
594
  def global_config_set(name, value)
509
- command('config', ['--global', name, value], false)
595
+ command('config', '--global', name, value)
510
596
  end
511
597
 
512
598
  # updates the repository index using the working directory content
@@ -550,6 +636,20 @@ module Git
550
636
  command('rm', arr_opts)
551
637
  end
552
638
 
639
+ # Takes the commit message with the options and executes the commit command
640
+ #
641
+ # accepts options:
642
+ # :amend
643
+ # :all
644
+ # :allow_empty
645
+ # :author
646
+ # :date
647
+ # :no_verify
648
+ # :allow_empty_message
649
+ # :gpg_sign
650
+ #
651
+ # @param [String] message the commit message to be used
652
+ # @param [Hash] opts the commit options to be used
553
653
  def commit(message, opts = {})
554
654
  arr_opts = []
555
655
  arr_opts << "--message=#{message}" if message
@@ -558,6 +658,16 @@ module Git
558
658
  arr_opts << '--allow-empty' if opts[:allow_empty]
559
659
  arr_opts << "--author=#{opts[:author]}" if opts[:author]
560
660
  arr_opts << "--date=#{opts[:date]}" if opts[:date].is_a? String
661
+ arr_opts << '--no-verify' if opts[:no_verify]
662
+ arr_opts << '--allow-empty-message' if opts[:allow_empty_message]
663
+ if opts[:gpg_sign]
664
+ arr_opts <<
665
+ if opts[:gpg_sign] == true
666
+ '--gpg-sign'
667
+ else
668
+ "--gpg-sign=#{opts[:gpg_sign]}"
669
+ end
670
+ end
561
671
 
562
672
  command('commit', arr_opts)
563
673
  end
@@ -616,13 +726,13 @@ module Git
616
726
  end
617
727
 
618
728
  def stash_save(message)
619
- output = command('stash save', ['--', message])
729
+ output = command('stash save', message)
620
730
  output =~ /HEAD is now at/
621
731
  end
622
732
 
623
733
  def stash_apply(id = nil)
624
734
  if id
625
- command('stash apply', [id])
735
+ command('stash apply', id)
626
736
  else
627
737
  command('stash apply')
628
738
  end
@@ -641,7 +751,7 @@ module Git
641
751
  end
642
752
 
643
753
  def branch_delete(branch)
644
- command('branch', ['-D', branch])
754
+ command('branch', '-D', branch)
645
755
  end
646
756
 
647
757
  def checkout(branch, opts = {})
@@ -660,8 +770,9 @@ module Git
660
770
  command('checkout', arr_opts)
661
771
  end
662
772
 
663
- def merge(branch, message = nil)
773
+ def merge(branch, message = nil, opts = {})
664
774
  arr_opts = []
775
+ arr_opts << '--no-ff' if opts[:no_ff]
665
776
  arr_opts << '-m' << message if message
666
777
  arr_opts += [branch]
667
778
  command('merge', arr_opts)
@@ -684,7 +795,7 @@ module Git
684
795
 
685
796
  def unmerged
686
797
  unmerged = []
687
- command_lines('diff', ["--cached"]).each do |line|
798
+ command_lines('diff', "--cached").each do |line|
688
799
  unmerged << $1 if line =~ /^\* Unmerged path (.*)/
689
800
  end
690
801
  unmerged
@@ -692,11 +803,15 @@ module Git
692
803
 
693
804
  def conflicts # :yields: file, your, their
694
805
  self.unmerged.each do |f|
695
- your = Tempfile.new("YOUR-#{File.basename(f)}").path
696
- command('show', ":2:#{f}", true, "> #{escape your}")
697
-
698
- their = Tempfile.new("THEIR-#{File.basename(f)}").path
699
- command('show', ":3:#{f}", true, "> #{escape their}")
806
+ your_tempfile = Tempfile.new("YOUR-#{File.basename(f)}")
807
+ your = your_tempfile.path
808
+ your_tempfile.close # free up file for git command process
809
+ command('show', ":2:#{f}", redirect: "> #{escape your}")
810
+
811
+ their_tempfile = Tempfile.new("THEIR-#{File.basename(f)}")
812
+ their = their_tempfile.path
813
+ their_tempfile.close # free up file for git command process
814
+ command('show', ":3:#{f}", redirect: "> #{escape their}")
700
815
  yield(f, your, their)
701
816
  end
702
817
  end
@@ -721,7 +836,7 @@ module Git
721
836
  end
722
837
 
723
838
  def remote_remove(name)
724
- command('remote', ['rm', name])
839
+ command('remote', 'rm', name)
725
840
  end
726
841
 
727
842
  def remotes
@@ -787,22 +902,22 @@ module Git
787
902
  end
788
903
 
789
904
  def pull(remote='origin', branch='master')
790
- command('pull', [remote, branch])
905
+ command('pull', remote, branch)
791
906
  end
792
907
 
793
908
  def tag_sha(tag_name)
794
909
  head = File.join(@git_dir, 'refs', 'tags', tag_name)
795
910
  return File.read(head).chomp if File.exist?(head)
796
911
 
797
- command('show-ref', ['--tags', '-s', tag_name])
912
+ command('show-ref', '--tags', '-s', tag_name)
798
913
  end
799
914
 
800
915
  def repack
801
- command('repack', ['-a', '-d'])
916
+ command('repack', '-a', '-d')
802
917
  end
803
918
 
804
919
  def gc
805
- command('gc', ['--prune', '--aggressive', '--auto'])
920
+ command('gc', '--prune', '--aggressive', '--auto')
806
921
  end
807
922
 
808
923
  # reads a tree into the current index file
@@ -827,11 +942,11 @@ module Git
827
942
  arr_opts << tree
828
943
  arr_opts << '-p' << opts[:parent] if opts[:parent]
829
944
  arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
830
- command('commit-tree', arr_opts, true, "< #{escape t.path}")
945
+ command('commit-tree', arr_opts, redirect: "< #{escape t.path}")
831
946
  end
832
947
 
833
948
  def update_ref(branch, commit)
834
- command('update-ref', [branch, commit])
949
+ command('update-ref', branch, commit)
835
950
  end
836
951
 
837
952
  def checkout_index(opts = {})
@@ -873,13 +988,19 @@ module Git
873
988
  arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
874
989
  arr_opts << sha
875
990
  arr_opts << '--' << opts[:path] if opts[:path]
876
- command('archive', arr_opts, true, (opts[:add_gzip] ? '| gzip' : '') + " > #{escape file}")
991
+ command('archive', arr_opts, redirect: " > #{escape file}")
992
+ if opts[:add_gzip]
993
+ file_content = File.read(file)
994
+ Zlib::GzipWriter.open(file) do |gz|
995
+ gz.write(file_content)
996
+ end
997
+ end
877
998
  return file
878
999
  end
879
1000
 
880
1001
  # returns the current version of git, as an Array of Fixnums.
881
1002
  def current_command_version
882
- output = command('version', [], false)
1003
+ output = command('version')
883
1004
  version = output[/\d+\.\d+(\.\d+)+/]
884
1005
  version.split('.').collect {|i| i.to_i}
885
1006
  end
@@ -900,8 +1021,14 @@ module Git
900
1021
  # @return [<String>] the names of the EVN variables involved in the git commands
901
1022
  ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
902
1023
 
903
- def command_lines(cmd, opts = [], chdir = true, redirect = '')
904
- command(cmd, opts, chdir).lines.map(&:chomp)
1024
+ def command_lines(cmd, *opts)
1025
+ cmd_op = command(cmd, *opts)
1026
+ if cmd_op.encoding.name != "UTF-8"
1027
+ op = cmd_op.encode("UTF-8", "binary", :invalid => :replace, :undef => :replace)
1028
+ else
1029
+ op = cmd_op
1030
+ end
1031
+ op.split("\n")
905
1032
  end
906
1033
 
907
1034
  # Takes the current git's system ENV variables and store them.
@@ -941,16 +1068,25 @@ module Git
941
1068
  restore_git_system_env_variables()
942
1069
  end
943
1070
 
944
- def command(cmd, opts = [], chdir = true, redirect = '', &block)
1071
+ def command(cmd, *opts, &block)
1072
+ command_opts = { chomp: true, redirect: '' }
1073
+ if opts.last.is_a?(Hash)
1074
+ command_opts.merge!(opts.pop)
1075
+ end
1076
+ command_opts.keys.each do |k|
1077
+ raise ArgumentError.new("Unsupported option: #{k}") unless [:chomp, :redirect].include?(k)
1078
+ end
1079
+
945
1080
  global_opts = []
946
1081
  global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
947
1082
  global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
1083
+ global_opts << ["-c", "color.ui=false"]
948
1084
 
949
1085
  opts = [opts].flatten.map {|s| escape(s) }.join(' ')
950
1086
 
951
1087
  global_opts = global_opts.flatten.map {|s| escape(s) }.join(' ')
952
1088
 
953
- git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{cmd} #{opts} #{redirect} 2>&1"
1089
+ git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{cmd} #{opts} #{command_opts[:redirect]} 2>&1"
954
1090
 
955
1091
  output = nil
956
1092
 
@@ -971,11 +1107,12 @@ module Git
971
1107
  @logger.debug(output)
972
1108
  end
973
1109
 
974
- if exitstatus > 1 || (exitstatus == 1 && output != '')
975
- raise Git::GitExecuteError.new(git_cmd + ':' + output.to_s)
976
- end
1110
+ raise Git::GitExecuteError, "#{git_cmd}:#{output}" if
1111
+ exitstatus > 1 || (exitstatus == 1 && output != '')
1112
+
1113
+ output.chomp! if output && command_opts[:chomp] && !block_given?
977
1114
 
978
- return output
1115
+ output
979
1116
  end
980
1117
 
981
1118
  # Takes the diff command line output (as Array) and parse it into a Hash
@@ -984,6 +1121,8 @@ module Git
984
1121
  # @param [Array] opts the diff options to be used
985
1122
  # @return [Hash] the diff as Hash
986
1123
  def diff_as_hash(diff_command, opts=[])
1124
+ # update index before diffing to avoid spurious diffs
1125
+ command('status')
987
1126
  command_lines(diff_command, opts).inject({}) do |memo, line|
988
1127
  info, file = line.split("\t")
989
1128
  mode_src, mode_dest, sha_src, sha_dest, type = info.split
@@ -1010,6 +1149,7 @@ module Git
1010
1149
 
1011
1150
  arr_opts << "-#{opts[:count]}" if opts[:count]
1012
1151
  arr_opts << "--no-color"
1152
+ arr_opts << "--cherry" if opts[:cherry]
1013
1153
  arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
1014
1154
  arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
1015
1155
  arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
@@ -1049,25 +1189,36 @@ module Git
1049
1189
  end
1050
1190
 
1051
1191
  def normalize_encoding(str)
1052
- return str if str.valid_encoding? && str.encoding == default_encoding
1192
+ return str if str.valid_encoding? && str.encoding.name == default_encoding
1053
1193
 
1054
- return str.encode(default_encoding, str.encoding, encoding_options) if str.valid_encoding?
1194
+ return str.encode(default_encoding, str.encoding, **encoding_options) if str.valid_encoding?
1055
1195
 
1056
- str.encode(default_encoding, detected_encoding(str), encoding_options)
1196
+ str.encode(default_encoding, detected_encoding(str), **encoding_options)
1057
1197
  end
1058
1198
 
1059
1199
  def run_command(git_cmd, &block)
1060
1200
  return IO.popen(git_cmd, &block) if block_given?
1061
1201
 
1062
- `#{git_cmd}`.chomp.lines.map { |l| normalize_encoding(l) }.join
1202
+ `#{git_cmd}`.lines.map { |l| normalize_encoding(l) }.join
1063
1203
  end
1064
1204
 
1065
1205
  def escape(s)
1066
- return "'#{s && s.to_s.gsub('\'','\'"\'"\'')}'" if RUBY_PLATFORM !~ /mingw|mswin/
1206
+ windows_platform? ? escape_for_windows(s) : escape_for_sh(s)
1207
+ end
1208
+
1209
+ def escape_for_sh(s)
1210
+ "'#{s && s.to_s.gsub('\'','\'"\'"\'')}'"
1211
+ end
1212
+
1213
+ def escape_for_windows(s)
1214
+ # Windows does not need single quote escaping inside double quotes
1215
+ %Q{"#{s}"}
1216
+ end
1067
1217
 
1068
- # Keeping the old escape format for windows users
1069
- escaped = s.to_s.gsub('\'', '\'\\\'\'')
1070
- return %Q{"#{escaped}"}
1218
+ def windows_platform?
1219
+ # Check if on Windows via RUBY_PLATFORM (CRuby) and RUBY_DESCRIPTION (JRuby)
1220
+ win_platform_regex = /mingw|mswin/
1221
+ RUBY_PLATFORM =~ win_platform_regex || RUBY_DESCRIPTION =~ win_platform_regex
1071
1222
  end
1072
1223
 
1073
1224
  end