git 1.5.0 → 1.8.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,71 +3,97 @@ 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
 
88
+ # Find as good common ancestors as possible for a merge
89
+ # example: g.merge_base('master', 'some_branch', 'some_sha', octopus: true)
90
+ #
91
+ # @return [Array<Git::Object::Commit>] a collection of common ancestors
92
+ def merge_base(*args)
93
+ shas = self.lib.merge_base(*args)
94
+ shas.map { |sha| gcommit(sha) }
95
+ end
96
+
71
97
  end
72
98
 
73
99
  end
@@ -37,7 +37,7 @@ module Git
37
37
  # # do other stuff
38
38
  # return true # auto commits and switches back
39
39
  # end
40
- def in_branch (message = 'in branch work')
40
+ def in_branch(message = 'in branch work')
41
41
  old_current = @base.lib.branch_current
42
42
  checkout
43
43
  if yield
@@ -10,7 +10,7 @@ module Git
10
10
  end
11
11
 
12
12
  def binary_path
13
- @binary_path || 'git'
13
+ @binary_path || ENV['GIT_PATH'] && File.join(ENV['GIT_PATH'], 'git') || 'git'
14
14
  end
15
15
 
16
16
  def git_ssh
@@ -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
@@ -127,17 +128,12 @@ module Git
127
128
  }
128
129
  final = {}
129
130
  current_file = nil
130
- if @full_diff.encoding.name != "UTF-8"
131
- full_diff_utf8_encoded = @full_diff.encode("UTF-8", "binary", { :invalid => :replace, :undef => :replace })
132
- else
133
- full_diff_utf8_encoded = @full_diff
134
- end
135
- full_diff_utf8_encoded.split("\n").each do |line|
131
+ @full_diff.split("\n").each do |line|
136
132
  if m = /^diff --git a\/(.*?) b\/(.*?)/.match(line)
137
133
  current_file = m[1]
138
134
  final[current_file] = defaults.merge({:patch => line, :path => current_file})
139
135
  else
140
- if m = /^index (.......)\.\.(.......)( ......)*/.match(line)
136
+ if m = /^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/.match(line)
141
137
  final[current_file][:src] = m[1]
142
138
  final[current_file][:dst] = m[2]
143
139
  final[current_file][:mode] = m[3].strip if m[3]
@@ -1,4 +1,6 @@
1
+ require 'rchardet'
1
2
  require 'tempfile'
3
+ require 'zlib'
2
4
 
3
5
  module Git
4
6
 
@@ -9,6 +11,43 @@ module Git
9
11
 
10
12
  @@semaphore = Mutex.new
11
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
+ #
12
51
  def initialize(base = nil, logger = nil)
13
52
  @git_dir = nil
14
53
  @git_index_file = nil
@@ -37,14 +76,11 @@ module Git
37
76
  arr_opts = []
38
77
  arr_opts << '--bare' if opts[:bare]
39
78
 
40
- command('init', arr_opts, false)
79
+ command('init', arr_opts)
41
80
  end
42
81
 
43
82
  # tries to clone the given repo
44
83
  #
45
- # returns {:repository} (if bare)
46
- # {:working_directory} otherwise
47
- #
48
84
  # accepts options:
49
85
  # :bare:: no working directory
50
86
  # :branch:: name of branch to track (rather than 'master')
@@ -56,6 +92,8 @@ module Git
56
92
  #
57
93
  # TODO - make this work with SSH password or auth_key
58
94
  #
95
+ # @return [Hash] the options to pass to {Git::Base.new}
96
+ #
59
97
  def clone(repository, name, opts = {})
60
98
  @path = opts[:path] || '.'
61
99
  clone_dir = opts[:path] ? File.join(@path, name) : name
@@ -76,9 +114,16 @@ module Git
76
114
 
77
115
  command('clone', arr_opts)
78
116
 
79
- (opts[:bare] or opts[:mirror]) ? {:repository => clone_dir} : {:working_directory => clone_dir}
117
+ return_base_opts_from_clone(clone_dir, opts)
80
118
  end
81
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
82
127
 
83
128
  ## READ COMMANDS ##
84
129
 
@@ -113,12 +158,12 @@ module Git
113
158
  arr_opts << '--always' if opts[:always]
114
159
  arr_opts << '--exact-match' if opts[:exact_match] || opts[:"exact-match"]
115
160
 
116
- arr_opts << '--dirty' if opts['dirty'] == true
117
- 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)
118
163
 
119
- arr_opts << "--abbrev=#{opts['abbrev']}" if opts[:abbrev]
120
- arr_opts << "--candidates=#{opts['candidates']}" if opts[:candidates]
121
- 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]
122
167
 
123
168
  arr_opts << committish if committish
124
169
 
@@ -132,7 +177,7 @@ module Git
132
177
 
133
178
  arr_opts += log_path_options(opts)
134
179
 
135
- command_lines('log', arr_opts, true).map { |l| l.split.first }
180
+ command_lines('log', arr_opts).map { |l| l.split.first }
136
181
  end
137
182
 
138
183
  def full_log_commits(opts={})
@@ -143,7 +188,7 @@ module Git
143
188
 
144
189
  arr_opts += log_path_options(opts)
145
190
 
146
- full_log = command_lines('log', arr_opts, true)
191
+ full_log = command_lines('log', arr_opts)
147
192
 
148
193
  process_commit_log_data(full_log)
149
194
  end
@@ -164,17 +209,17 @@ module Git
164
209
  end
165
210
 
166
211
  def object_type(sha)
167
- command('cat-file', ['-t', sha])
212
+ command('cat-file', '-t', sha)
168
213
  end
169
214
 
170
215
  def object_size(sha)
171
- command('cat-file', ['-s', sha]).to_i
216
+ command('cat-file', '-s', sha).to_i
172
217
  end
173
218
 
174
219
  # returns useful array of raw commit object data
175
220
  def commit_data(sha)
176
221
  sha = sha.to_s
177
- cdata = command_lines('cat-file', ['commit', sha])
222
+ cdata = command_lines('cat-file', 'commit', sha)
178
223
  process_commit_data(cdata, sha, 0)
179
224
  end
180
225
 
@@ -204,7 +249,7 @@ module Git
204
249
 
205
250
  def tag_data(name)
206
251
  sha = sha.to_s
207
- tdata = command_lines('cat-file', ['tag', name])
252
+ tdata = command_lines('cat-file', 'tag', name)
208
253
  process_tag_data(tdata, name, 0)
209
254
  end
210
255
 
@@ -242,6 +287,8 @@ module Git
242
287
  next
243
288
  end
244
289
 
290
+ in_message = false if in_message && line[0..3] != " "
291
+
245
292
  if in_message
246
293
  hsh['message'] << "#{line[4..-1]}\n"
247
294
  next
@@ -267,7 +314,7 @@ module Git
267
314
  end
268
315
 
269
316
  def object_contents(sha, &block)
270
- command('cat-file', ['-p', sha], &block)
317
+ command('cat-file', '-p', sha, &block)
271
318
  end
272
319
 
273
320
  def ls_tree(sha)
@@ -283,11 +330,11 @@ module Git
283
330
  end
284
331
 
285
332
  def mv(file1, file2)
286
- command_lines('mv', ['--', file1, file2])
333
+ command_lines('mv', '--', file1, file2)
287
334
  end
288
335
 
289
336
  def full_tree(sha)
290
- command_lines('ls-tree', ['-r', sha])
337
+ command_lines('ls-tree', '-r', sha)
291
338
  end
292
339
 
293
340
  def tree_depth(sha)
@@ -295,7 +342,7 @@ module Git
295
342
  end
296
343
 
297
344
  def change_head_branch(branch_name)
298
- command('symbolic-ref', ['HEAD', "refs/heads/#{branch_name}"])
345
+ command('symbolic-ref', 'HEAD', "refs/heads/#{branch_name}")
299
346
  end
300
347
 
301
348
  def branches_all
@@ -307,6 +354,39 @@ module Git
307
354
  arr
308
355
  end
309
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
+
310
390
  def list_files(ref_dir)
311
391
  dir = File.join(@git_dir, 'refs', ref_dir)
312
392
  files = []
@@ -402,7 +482,7 @@ module Git
402
482
  def ls_files(location=nil)
403
483
  location ||= '.'
404
484
  hsh = {}
405
- command_lines('ls-files', ['--stage', location]).each do |line|
485
+ command_lines('ls-files', '--stage', location).each do |line|
406
486
  (info, file) = line.split("\t")
407
487
  (mode, sha, stage) = info.split
408
488
  file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
@@ -411,10 +491,13 @@ module Git
411
491
  hsh
412
492
  end
413
493
 
414
- def ls_remote(location=nil)
415
- location ||= '.'
494
+ def ls_remote(location=nil, opts={})
495
+ arr_opts = []
496
+ arr_opts << ['--refs'] if opts[:refs]
497
+ arr_opts << (location || '.')
498
+
416
499
  Hash.new{ |h,k| h[k] = {} }.tap do |hsh|
417
- command_lines('ls-remote', [location], false).each do |line|
500
+ command_lines('ls-remote', arr_opts).each do |line|
418
501
  (sha, info) = line.split("\t")
419
502
  (ref, type, name) = info.split('/', 3)
420
503
  type ||= 'head'
@@ -426,7 +509,7 @@ module Git
426
509
  end
427
510
 
428
511
  def ignored_files
429
- command_lines('ls-files', ['--others', '-i', '--exclude-standard'])
512
+ command_lines('ls-files', '--others', '-i', '--exclude-standard')
430
513
  end
431
514
 
432
515
 
@@ -441,8 +524,8 @@ module Git
441
524
  end
442
525
 
443
526
  def config_get(name)
444
- do_get = lambda do |path|
445
- command('config', ['--get', name])
527
+ do_get = Proc.new do |path|
528
+ command('config', '--get', name)
446
529
  end
447
530
 
448
531
  if @git_dir
@@ -453,12 +536,12 @@ module Git
453
536
  end
454
537
 
455
538
  def global_config_get(name)
456
- command('config', ['--global', '--get', name], false)
539
+ command('config', '--global', '--get', name)
457
540
  end
458
541
 
459
542
  def config_list
460
- build_list = lambda do |path|
461
- parse_config_list command_lines('config', ['--list'])
543
+ build_list = Proc.new do |path|
544
+ parse_config_list command_lines('config', '--list')
462
545
  end
463
546
 
464
547
  if @git_dir
@@ -469,7 +552,7 @@ module Git
469
552
  end
470
553
 
471
554
  def global_config_list
472
- parse_config_list command_lines('config', ['--global', '--list'], false)
555
+ parse_config_list command_lines('config', '--global', '--list')
473
556
  end
474
557
 
475
558
  def parse_config_list(lines)
@@ -482,7 +565,7 @@ module Git
482
565
  end
483
566
 
484
567
  def parse_config(file)
485
- parse_config_list command_lines('config', ['--list', '--file', file], false)
568
+ parse_config_list command_lines('config', '--list', '--file', file)
486
569
  end
487
570
 
488
571
  # Shows objects
@@ -495,17 +578,17 @@ module Git
495
578
 
496
579
  arr_opts << (path ? "#{objectish}:#{path}" : objectish)
497
580
 
498
- command('show', arr_opts.compact)
581
+ command('show', arr_opts.compact, chomp: false)
499
582
  end
500
583
 
501
584
  ## WRITE COMMANDS ##
502
585
 
503
586
  def config_set(name, value)
504
- command('config', [name, value])
587
+ command('config', name, value)
505
588
  end
506
589
 
507
590
  def global_config_set(name, value)
508
- command('config', ['--global', name, value], false)
591
+ command('config', '--global', name, value)
509
592
  end
510
593
 
511
594
  # updates the repository index using the working directory content
@@ -549,6 +632,19 @@ module Git
549
632
  command('rm', arr_opts)
550
633
  end
551
634
 
635
+ # Takes the commit message with the options and executes the commit command
636
+ #
637
+ # accepts options:
638
+ # :amend
639
+ # :all
640
+ # :allow_empty
641
+ # :author
642
+ # :date
643
+ # :no_verify
644
+ # :allow_empty_message
645
+ #
646
+ # @param [String] message the commit message to be used
647
+ # @param [Hash] opts the commit options to be used
552
648
  def commit(message, opts = {})
553
649
  arr_opts = []
554
650
  arr_opts << "--message=#{message}" if message
@@ -557,6 +653,8 @@ module Git
557
653
  arr_opts << '--allow-empty' if opts[:allow_empty]
558
654
  arr_opts << "--author=#{opts[:author]}" if opts[:author]
559
655
  arr_opts << "--date=#{opts[:date]}" if opts[:date].is_a? String
656
+ arr_opts << '--no-verify' if opts[:no_verify]
657
+ arr_opts << '--allow-empty-message' if opts[:allow_empty_message]
560
658
 
561
659
  command('commit', arr_opts)
562
660
  end
@@ -615,13 +713,13 @@ module Git
615
713
  end
616
714
 
617
715
  def stash_save(message)
618
- output = command('stash save', ['--', message])
716
+ output = command('stash save', message)
619
717
  output =~ /HEAD is now at/
620
718
  end
621
719
 
622
720
  def stash_apply(id = nil)
623
721
  if id
624
- command('stash apply', [id])
722
+ command('stash apply', id)
625
723
  else
626
724
  command('stash apply')
627
725
  end
@@ -640,7 +738,7 @@ module Git
640
738
  end
641
739
 
642
740
  def branch_delete(branch)
643
- command('branch', ['-D', branch])
741
+ command('branch', '-D', branch)
644
742
  end
645
743
 
646
744
  def checkout(branch, opts = {})
@@ -659,16 +757,32 @@ module Git
659
757
  command('checkout', arr_opts)
660
758
  end
661
759
 
662
- def merge(branch, message = nil)
760
+ def merge(branch, message = nil, opts = {})
663
761
  arr_opts = []
762
+ arr_opts << '--no-ff' if opts[:no_ff]
664
763
  arr_opts << '-m' << message if message
665
764
  arr_opts += [branch]
666
765
  command('merge', arr_opts)
667
766
  end
668
767
 
768
+ def merge_base(*args)
769
+ opts = args.last.is_a?(Hash) ? args.pop : {}
770
+
771
+ arg_opts = []
772
+
773
+ arg_opts << '--octopus' if opts[:octopus]
774
+ arg_opts << '--independent' if opts[:independent]
775
+ arg_opts << '--fork-point' if opts[:fork_point]
776
+ arg_opts << '--all' if opts[:all]
777
+
778
+ arg_opts += args
779
+
780
+ command('merge-base', arg_opts).lines.map(&:strip)
781
+ end
782
+
669
783
  def unmerged
670
784
  unmerged = []
671
- command_lines('diff', ["--cached"]).each do |line|
785
+ command_lines('diff', "--cached").each do |line|
672
786
  unmerged << $1 if line =~ /^\* Unmerged path (.*)/
673
787
  end
674
788
  unmerged
@@ -676,11 +790,15 @@ module Git
676
790
 
677
791
  def conflicts # :yields: file, your, their
678
792
  self.unmerged.each do |f|
679
- your = Tempfile.new("YOUR-#{File.basename(f)}").path
680
- command('show', ":2:#{f}", true, "> #{escape your}")
681
-
682
- their = Tempfile.new("THEIR-#{File.basename(f)}").path
683
- 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}")
684
802
  yield(f, your, their)
685
803
  end
686
804
  end
@@ -705,7 +823,7 @@ module Git
705
823
  end
706
824
 
707
825
  def remote_remove(name)
708
- command('remote', ['rm', name])
826
+ command('remote', 'rm', name)
709
827
  end
710
828
 
711
829
  def remotes
@@ -747,6 +865,7 @@ module Git
747
865
  arr_opts << opts[:ref] if opts[:ref]
748
866
  arr_opts << '--tags' if opts[:t] || opts[:tags]
749
867
  arr_opts << '--prune' if opts[:p] || opts[:prune]
868
+ arr_opts << '--unshallow' if opts[:unshallow]
750
869
 
751
870
  command('fetch', arr_opts)
752
871
  end
@@ -770,22 +889,22 @@ module Git
770
889
  end
771
890
 
772
891
  def pull(remote='origin', branch='master')
773
- command('pull', [remote, branch])
892
+ command('pull', remote, branch)
774
893
  end
775
894
 
776
895
  def tag_sha(tag_name)
777
896
  head = File.join(@git_dir, 'refs', 'tags', tag_name)
778
897
  return File.read(head).chomp if File.exist?(head)
779
898
 
780
- command('show-ref', ['--tags', '-s', tag_name])
899
+ command('show-ref', '--tags', '-s', tag_name)
781
900
  end
782
901
 
783
902
  def repack
784
- command('repack', ['-a', '-d'])
903
+ command('repack', '-a', '-d')
785
904
  end
786
905
 
787
906
  def gc
788
- command('gc', ['--prune', '--aggressive', '--auto'])
907
+ command('gc', '--prune', '--aggressive', '--auto')
789
908
  end
790
909
 
791
910
  # reads a tree into the current index file
@@ -810,11 +929,11 @@ module Git
810
929
  arr_opts << tree
811
930
  arr_opts << '-p' << opts[:parent] if opts[:parent]
812
931
  arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
813
- command('commit-tree', arr_opts, true, "< #{escape t.path}")
932
+ command('commit-tree', arr_opts, redirect: "< #{escape t.path}")
814
933
  end
815
934
 
816
935
  def update_ref(branch, commit)
817
- command('update-ref', [branch, commit])
936
+ command('update-ref', branch, commit)
818
937
  end
819
938
 
820
939
  def checkout_index(opts = {})
@@ -856,13 +975,19 @@ module Git
856
975
  arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
857
976
  arr_opts << sha
858
977
  arr_opts << '--' << opts[:path] if opts[:path]
859
- 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
860
985
  return file
861
986
  end
862
987
 
863
988
  # returns the current version of git, as an Array of Fixnums.
864
989
  def current_command_version
865
- output = command('version', [], false)
990
+ output = command('version')
866
991
  version = output[/\d+\.\d+(\.\d+)+/]
867
992
  version.split('.').collect {|i| i.to_i}
868
993
  end
@@ -883,13 +1008,10 @@ module Git
883
1008
  # @return [<String>] the names of the EVN variables involved in the git commands
884
1009
  ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
885
1010
 
886
- def command_lines(cmd, opts = [], chdir = true, redirect = '')
887
- cmd_op = command(cmd, opts, chdir)
1011
+ def command_lines(cmd, *opts)
1012
+ cmd_op = command(cmd, *opts)
888
1013
  if cmd_op.encoding.name != "UTF-8"
889
- op = cmd_op.encode("UTF-8", "binary", {
890
- :invalid => :replace,
891
- :undef => :replace
892
- })
1014
+ op = cmd_op.encode("UTF-8", "binary", :invalid => :replace, :undef => :replace)
893
1015
  else
894
1016
  op = cmd_op
895
1017
  end
@@ -933,16 +1055,25 @@ module Git
933
1055
  restore_git_system_env_variables()
934
1056
  end
935
1057
 
936
- 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
+
937
1067
  global_opts = []
938
1068
  global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
939
1069
  global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
1070
+ global_opts << ["-c", "color.ui=false"]
940
1071
 
941
1072
  opts = [opts].flatten.map {|s| escape(s) }.join(' ')
942
1073
 
943
1074
  global_opts = global_opts.flatten.map {|s| escape(s) }.join(' ')
944
1075
 
945
- 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"
946
1077
 
947
1078
  output = nil
948
1079
 
@@ -963,11 +1094,12 @@ module Git
963
1094
  @logger.debug(output)
964
1095
  end
965
1096
 
966
- if exitstatus > 1 || (exitstatus == 1 && output != '')
967
- raise Git::GitExecuteError.new(git_cmd + ':' + output.to_s)
968
- end
1097
+ raise Git::GitExecuteError, "#{git_cmd}:#{output}" if
1098
+ exitstatus > 1 || (exitstatus == 1 && output != '')
1099
+
1100
+ output.chomp! if output && command_opts[:chomp] && !block_given?
969
1101
 
970
- return output
1102
+ output
971
1103
  end
972
1104
 
973
1105
  # Takes the diff command line output (as Array) and parse it into a Hash
@@ -976,6 +1108,8 @@ module Git
976
1108
  # @param [Array] opts the diff options to be used
977
1109
  # @return [Hash] the diff as Hash
978
1110
  def diff_as_hash(diff_command, opts=[])
1111
+ # update index before diffing to avoid spurious diffs
1112
+ command('status')
979
1113
  command_lines(diff_command, opts).inject({}) do |memo, line|
980
1114
  info, file = line.split("\t")
981
1115
  mode_src, mode_dest, sha_src, sha_dest, type = info.split
@@ -1002,6 +1136,7 @@ module Git
1002
1136
 
1003
1137
  arr_opts << "-#{opts[:count]}" if opts[:count]
1004
1138
  arr_opts << "--no-color"
1139
+ arr_opts << "--cherry" if opts[:cherry]
1005
1140
  arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
1006
1141
  arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
1007
1142
  arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
@@ -1023,18 +1158,54 @@ module Git
1023
1158
  arr_opts
1024
1159
  end
1025
1160
 
1161
+ def default_encoding
1162
+ __ENCODING__.name
1163
+ end
1164
+
1165
+ def best_guess_encoding
1166
+ # Encoding::ASCII_8BIT.name
1167
+ Encoding::UTF_8.name
1168
+ end
1169
+
1170
+ def detected_encoding(str)
1171
+ CharDet.detect(str)['encoding'] || best_guess_encoding
1172
+ end
1173
+
1174
+ def encoding_options
1175
+ { invalid: :replace, undef: :replace }
1176
+ end
1177
+
1178
+ def normalize_encoding(str)
1179
+ return str if str.valid_encoding? && str.encoding.name == default_encoding
1180
+
1181
+ return str.encode(default_encoding, str.encoding, **encoding_options) if str.valid_encoding?
1182
+
1183
+ str.encode(default_encoding, detected_encoding(str), **encoding_options)
1184
+ end
1185
+
1026
1186
  def run_command(git_cmd, &block)
1027
1187
  return IO.popen(git_cmd, &block) if block_given?
1028
1188
 
1029
- `#{git_cmd}`.chomp
1189
+ `#{git_cmd}`.lines.map { |l| normalize_encoding(l) }.join
1030
1190
  end
1031
1191
 
1032
1192
  def escape(s)
1033
- 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
1034
1204
 
1035
- # Keeping the old escape format for windows users
1036
- escaped = s.to_s.gsub('\'', '\'\\\'\'')
1037
- 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
1038
1209
  end
1039
1210
 
1040
1211
  end