git 1.4.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,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
@@ -60,6 +60,10 @@ module Git
60
60
  determine_current
61
61
  end
62
62
 
63
+ def contains?(commit)
64
+ !@base.lib.branch_contains(commit, self.name).empty?
65
+ end
66
+
63
67
  def merge(branch = nil, message = nil)
64
68
  if branch
65
69
  in_branch do
@@ -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,16 +128,12 @@ module Git
127
128
  }
128
129
  final = {}
129
130
  current_file = nil
130
- full_diff_utf8_encoded = @full_diff.encode("UTF-8", "binary", {
131
- :invalid => :replace,
132
- :undef => :replace
133
- })
134
- full_diff_utf8_encoded.split("\n").each do |line|
131
+ @full_diff.split("\n").each do |line|
135
132
  if m = /^diff --git a\/(.*?) b\/(.*?)/.match(line)
136
133
  current_file = m[1]
137
134
  final[current_file] = defaults.merge({:patch => line, :path => current_file})
138
135
  else
139
- if m = /^index (.......)\.\.(.......)( ......)*/.match(line)
136
+ if m = /^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/.match(line)
140
137
  final[current_file][:src] = m[1]
141
138
  final[current_file][:dst] = m[2]
142
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 = []
@@ -318,6 +398,9 @@ module Git
318
398
  branches_all.select { |b| b[1] }.first[0] rescue nil
319
399
  end
320
400
 
401
+ def branch_contains(commit, branch_name="")
402
+ command("branch", [branch_name, "--contains", commit])
403
+ end
321
404
 
322
405
  # returns hash
323
406
  # [tree-ish] = [[line_no, match], [line_no, match2]]
@@ -399,7 +482,7 @@ module Git
399
482
  def ls_files(location=nil)
400
483
  location ||= '.'
401
484
  hsh = {}
402
- command_lines('ls-files', ['--stage', location]).each do |line|
485
+ command_lines('ls-files', '--stage', location).each do |line|
403
486
  (info, file) = line.split("\t")
404
487
  (mode, sha, stage) = info.split
405
488
  file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
@@ -408,10 +491,13 @@ module Git
408
491
  hsh
409
492
  end
410
493
 
411
- def ls_remote(location=nil)
412
- location ||= '.'
494
+ def ls_remote(location=nil, opts={})
495
+ arr_opts = []
496
+ arr_opts << ['--refs'] if opts[:refs]
497
+ arr_opts << (location || '.')
498
+
413
499
  Hash.new{ |h,k| h[k] = {} }.tap do |hsh|
414
- command_lines('ls-remote', [location], false).each do |line|
500
+ command_lines('ls-remote', arr_opts).each do |line|
415
501
  (sha, info) = line.split("\t")
416
502
  (ref, type, name) = info.split('/', 3)
417
503
  type ||= 'head'
@@ -423,7 +509,7 @@ module Git
423
509
  end
424
510
 
425
511
  def ignored_files
426
- command_lines('ls-files', ['--others', '-i', '--exclude-standard'])
512
+ command_lines('ls-files', '--others', '-i', '--exclude-standard')
427
513
  end
428
514
 
429
515
 
@@ -438,24 +524,24 @@ module Git
438
524
  end
439
525
 
440
526
  def config_get(name)
441
- do_get = lambda do |path|
442
- command('config', ['--get', name])
527
+ do_get = Proc.new do |path|
528
+ command('config', '--get', name)
443
529
  end
444
530
 
445
531
  if @git_dir
446
532
  Dir.chdir(@git_dir, &do_get)
447
533
  else
448
- build_list.call
534
+ do_get.call
449
535
  end
450
536
  end
451
537
 
452
538
  def global_config_get(name)
453
- command('config', ['--global', '--get', name], false)
539
+ command('config', '--global', '--get', name)
454
540
  end
455
541
 
456
542
  def config_list
457
- build_list = lambda do |path|
458
- parse_config_list command_lines('config', ['--list'])
543
+ build_list = Proc.new do |path|
544
+ parse_config_list command_lines('config', '--list')
459
545
  end
460
546
 
461
547
  if @git_dir
@@ -466,7 +552,7 @@ module Git
466
552
  end
467
553
 
468
554
  def global_config_list
469
- parse_config_list command_lines('config', ['--global', '--list'], false)
555
+ parse_config_list command_lines('config', '--global', '--list')
470
556
  end
471
557
 
472
558
  def parse_config_list(lines)
@@ -479,7 +565,7 @@ module Git
479
565
  end
480
566
 
481
567
  def parse_config(file)
482
- parse_config_list command_lines('config', ['--list', '--file', file], false)
568
+ parse_config_list command_lines('config', '--list', '--file', file)
483
569
  end
484
570
 
485
571
  # Shows objects
@@ -492,17 +578,17 @@ module Git
492
578
 
493
579
  arr_opts << (path ? "#{objectish}:#{path}" : objectish)
494
580
 
495
- command('show', arr_opts.compact)
581
+ command('show', arr_opts.compact, chomp: false)
496
582
  end
497
583
 
498
584
  ## WRITE COMMANDS ##
499
585
 
500
586
  def config_set(name, value)
501
- command('config', [name, value])
587
+ command('config', name, value)
502
588
  end
503
589
 
504
590
  def global_config_set(name, value)
505
- command('config', ['--global', name, value], false)
591
+ command('config', '--global', name, value)
506
592
  end
507
593
 
508
594
  # updates the repository index using the working directory content
@@ -546,6 +632,19 @@ module Git
546
632
  command('rm', arr_opts)
547
633
  end
548
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
549
648
  def commit(message, opts = {})
550
649
  arr_opts = []
551
650
  arr_opts << "--message=#{message}" if message
@@ -553,6 +652,9 @@ module Git
553
652
  arr_opts << '--all' if opts[:add_all] || opts[:all]
554
653
  arr_opts << '--allow-empty' if opts[:allow_empty]
555
654
  arr_opts << "--author=#{opts[:author]}" if opts[:author]
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]
556
658
 
557
659
  command('commit', arr_opts)
558
660
  end
@@ -600,22 +702,24 @@ module Git
600
702
  arr = []
601
703
  filename = File.join(@git_dir, 'logs/refs/stash')
602
704
  if File.exist?(filename)
603
- File.open(filename).each_with_index { |line, i|
604
- m = line.match(/:(.*)$/)
605
- arr << [i, m[1].strip]
606
- }
705
+ File.open(filename) do |f|
706
+ f.each_with_index do |line, i|
707
+ m = line.match(/:(.*)$/)
708
+ arr << [i, m[1].strip]
709
+ end
710
+ end
607
711
  end
608
712
  arr
609
713
  end
610
714
 
611
715
  def stash_save(message)
612
- output = command('stash save', ['--', message])
716
+ output = command('stash save', message)
613
717
  output =~ /HEAD is now at/
614
718
  end
615
719
 
616
720
  def stash_apply(id = nil)
617
721
  if id
618
- command('stash apply', [id])
722
+ command('stash apply', id)
619
723
  else
620
724
  command('stash apply')
621
725
  end
@@ -634,7 +738,7 @@ module Git
634
738
  end
635
739
 
636
740
  def branch_delete(branch)
637
- command('branch', ['-D', branch])
741
+ command('branch', '-D', branch)
638
742
  end
639
743
 
640
744
  def checkout(branch, opts = {})
@@ -653,16 +757,32 @@ module Git
653
757
  command('checkout', arr_opts)
654
758
  end
655
759
 
656
- def merge(branch, message = nil)
760
+ def merge(branch, message = nil, opts = {})
657
761
  arr_opts = []
762
+ arr_opts << '--no-ff' if opts[:no_ff]
658
763
  arr_opts << '-m' << message if message
659
764
  arr_opts += [branch]
660
765
  command('merge', arr_opts)
661
766
  end
662
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
+
663
783
  def unmerged
664
784
  unmerged = []
665
- command_lines('diff', ["--cached"]).each do |line|
785
+ command_lines('diff', "--cached").each do |line|
666
786
  unmerged << $1 if line =~ /^\* Unmerged path (.*)/
667
787
  end
668
788
  unmerged
@@ -670,11 +790,15 @@ module Git
670
790
 
671
791
  def conflicts # :yields: file, your, their
672
792
  self.unmerged.each do |f|
673
- your = Tempfile.new("YOUR-#{File.basename(f)}").path
674
- command('show', ":2:#{f}", true, "> #{escape your}")
675
-
676
- their = Tempfile.new("THEIR-#{File.basename(f)}").path
677
- 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}")
678
802
  yield(f, your, their)
679
803
  end
680
804
  end
@@ -699,7 +823,7 @@ module Git
699
823
  end
700
824
 
701
825
  def remote_remove(name)
702
- command('remote', ['rm', name])
826
+ command('remote', 'rm', name)
703
827
  end
704
828
 
705
829
  def remotes
@@ -727,7 +851,10 @@ module Git
727
851
  arr_opts << '-d' if opts[:d] || opts[:delete]
728
852
  arr_opts << name
729
853
  arr_opts << target if target
730
- arr_opts << "-m #{opts[:m] || opts[:message]}" if opts[:m] || opts[:message]
854
+
855
+ if opts[:m] || opts[:message]
856
+ arr_opts << '-m' << (opts[:m] || opts[:message])
857
+ end
731
858
 
732
859
  command('tag', arr_opts)
733
860
  end
@@ -735,8 +862,10 @@ module Git
735
862
 
736
863
  def fetch(remote, opts)
737
864
  arr_opts = [remote]
865
+ arr_opts << opts[:ref] if opts[:ref]
738
866
  arr_opts << '--tags' if opts[:t] || opts[:tags]
739
867
  arr_opts << '--prune' if opts[:p] || opts[:prune]
868
+ arr_opts << '--unshallow' if opts[:unshallow]
740
869
 
741
870
  command('fetch', arr_opts)
742
871
  end
@@ -747,6 +876,7 @@ module Git
747
876
 
748
877
  arr_opts = []
749
878
  arr_opts << '--mirror' if opts[:mirror]
879
+ arr_opts << '--delete' if opts[:delete]
750
880
  arr_opts << '--force' if opts[:force] || opts[:f]
751
881
  arr_opts << remote
752
882
 
@@ -759,22 +889,22 @@ module Git
759
889
  end
760
890
 
761
891
  def pull(remote='origin', branch='master')
762
- command('pull', [remote, branch])
892
+ command('pull', remote, branch)
763
893
  end
764
894
 
765
895
  def tag_sha(tag_name)
766
896
  head = File.join(@git_dir, 'refs', 'tags', tag_name)
767
897
  return File.read(head).chomp if File.exist?(head)
768
898
 
769
- command('show-ref', ['--tags', '-s', tag_name])
899
+ command('show-ref', '--tags', '-s', tag_name)
770
900
  end
771
901
 
772
902
  def repack
773
- command('repack', ['-a', '-d'])
903
+ command('repack', '-a', '-d')
774
904
  end
775
905
 
776
906
  def gc
777
- command('gc', ['--prune', '--aggressive', '--auto'])
907
+ command('gc', '--prune', '--aggressive', '--auto')
778
908
  end
779
909
 
780
910
  # reads a tree into the current index file
@@ -799,11 +929,11 @@ module Git
799
929
  arr_opts << tree
800
930
  arr_opts << '-p' << opts[:parent] if opts[:parent]
801
931
  arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
802
- command('commit-tree', arr_opts, true, "< #{escape t.path}")
932
+ command('commit-tree', arr_opts, redirect: "< #{escape t.path}")
803
933
  end
804
934
 
805
935
  def update_ref(branch, commit)
806
- command('update-ref', [branch, commit])
936
+ command('update-ref', branch, commit)
807
937
  end
808
938
 
809
939
  def checkout_index(opts = {})
@@ -845,13 +975,19 @@ module Git
845
975
  arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
846
976
  arr_opts << sha
847
977
  arr_opts << '--' << opts[:path] if opts[:path]
848
- 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
849
985
  return file
850
986
  end
851
987
 
852
988
  # returns the current version of git, as an Array of Fixnums.
853
989
  def current_command_version
854
- output = command('version', [], false)
990
+ output = command('version')
855
991
  version = output[/\d+\.\d+(\.\d+)+/]
856
992
  version.split('.').collect {|i| i.to_i}
857
993
  end
@@ -872,13 +1008,10 @@ module Git
872
1008
  # @return [<String>] the names of the EVN variables involved in the git commands
873
1009
  ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
874
1010
 
875
- def command_lines(cmd, opts = [], chdir = true, redirect = '')
876
- cmd_op = command(cmd, opts, chdir)
1011
+ def command_lines(cmd, *opts)
1012
+ cmd_op = command(cmd, *opts)
877
1013
  if cmd_op.encoding.name != "UTF-8"
878
- op = cmd_op.encode("UTF-8", "binary", {
879
- :invalid => :replace,
880
- :undef => :replace
881
- })
1014
+ op = cmd_op.encode("UTF-8", "binary", :invalid => :replace, :undef => :replace)
882
1015
  else
883
1016
  op = cmd_op
884
1017
  end
@@ -922,16 +1055,25 @@ module Git
922
1055
  restore_git_system_env_variables()
923
1056
  end
924
1057
 
925
- 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
+
926
1067
  global_opts = []
927
1068
  global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
928
1069
  global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
1070
+ global_opts << ["-c", "color.ui=false"]
929
1071
 
930
1072
  opts = [opts].flatten.map {|s| escape(s) }.join(' ')
931
1073
 
932
1074
  global_opts = global_opts.flatten.map {|s| escape(s) }.join(' ')
933
1075
 
934
- 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"
935
1077
 
936
1078
  output = nil
937
1079
 
@@ -952,11 +1094,12 @@ module Git
952
1094
  @logger.debug(output)
953
1095
  end
954
1096
 
955
- if exitstatus > 1 || (exitstatus == 1 && output != '')
956
- raise Git::GitExecuteError.new(git_cmd + ':' + output.to_s)
957
- end
1097
+ raise Git::GitExecuteError, "#{git_cmd}:#{output}" if
1098
+ exitstatus > 1 || (exitstatus == 1 && output != '')
958
1099
 
959
- return output
1100
+ output.chomp! if output && command_opts[:chomp] && !block_given?
1101
+
1102
+ output
960
1103
  end
961
1104
 
962
1105
  # Takes the diff command line output (as Array) and parse it into a Hash
@@ -965,6 +1108,8 @@ module Git
965
1108
  # @param [Array] opts the diff options to be used
966
1109
  # @return [Hash] the diff as Hash
967
1110
  def diff_as_hash(diff_command, opts=[])
1111
+ # update index before diffing to avoid spurious diffs
1112
+ command('status')
968
1113
  command_lines(diff_command, opts).inject({}) do |memo, line|
969
1114
  info, file = line.split("\t")
970
1115
  mode_src, mode_dest, sha_src, sha_dest, type = info.split
@@ -991,6 +1136,7 @@ module Git
991
1136
 
992
1137
  arr_opts << "-#{opts[:count]}" if opts[:count]
993
1138
  arr_opts << "--no-color"
1139
+ arr_opts << "--cherry" if opts[:cherry]
994
1140
  arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
995
1141
  arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
996
1142
  arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
@@ -1012,18 +1158,54 @@ module Git
1012
1158
  arr_opts
1013
1159
  end
1014
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
+
1015
1186
  def run_command(git_cmd, &block)
1016
1187
  return IO.popen(git_cmd, &block) if block_given?
1017
1188
 
1018
- `#{git_cmd}`.chomp
1189
+ `#{git_cmd}`.lines.map { |l| normalize_encoding(l) }.join
1019
1190
  end
1020
1191
 
1021
1192
  def escape(s)
1022
- 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
1023
1204
 
1024
- # Keeping the old escape format for windows users
1025
- escaped = s.to_s.gsub('\'', '\'\\\'\'')
1026
- 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
1027
1209
  end
1028
1210
 
1029
1211
  end