ruby-git-yz 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ module Git
2
+ class Index < Git::Path
3
+
4
+ end
5
+ end
@@ -0,0 +1,1018 @@
1
+ require 'tempfile'
2
+
3
+ module Git
4
+
5
+ class GitExecuteError < StandardError
6
+ end
7
+
8
+ class Lib
9
+
10
+ @@semaphore = Mutex.new
11
+
12
+ def initialize(base = nil, logger = nil)
13
+ @git_dir = nil
14
+ @git_index_file = nil
15
+ @git_work_dir = nil
16
+ @path = nil
17
+
18
+ if base.is_a?(Git::Base)
19
+ @git_dir = base.repo.path
20
+ @git_index_file = base.index.path if base.index
21
+ @git_work_dir = base.dir.path if base.dir
22
+ elsif base.is_a?(Hash)
23
+ @git_dir = base[:repository]
24
+ @git_index_file = base[:index]
25
+ @git_work_dir = base[:working_directory]
26
+ end
27
+ @logger = logger
28
+ end
29
+
30
+ # creates or reinitializes the repository
31
+ #
32
+ # options:
33
+ # :bare
34
+ # :working_directory
35
+ #
36
+ def init(opts={})
37
+ arr_opts = []
38
+ arr_opts << '--bare' if opts[:bare]
39
+
40
+ command('init', arr_opts, false)
41
+ end
42
+
43
+ # tries to clone the given repo
44
+ #
45
+ # returns {:repository} (if bare)
46
+ # {:working_directory} otherwise
47
+ #
48
+ # accepts options:
49
+ # :bare:: no working directory
50
+ # :branch:: name of branch to track (rather than 'master')
51
+ # :depth:: the number of commits back to pull
52
+ # :origin:: name of remote (same as remote)
53
+ # :path:: directory where the repo will be cloned
54
+ # :remote:: name of remote (rather than 'origin')
55
+ # :recursive:: after the clone is created, initialize all submodules within, using their default settings.
56
+ #
57
+ # TODO - make this work with SSH password or auth_key
58
+ #
59
+ def clone(repository, name, opts = {})
60
+ @path = opts[:path] || '.'
61
+ clone_dir = opts[:path] ? File.join(@path, name) : name
62
+
63
+ arr_opts = []
64
+ arr_opts << '--bare' if opts[:bare]
65
+ arr_opts << '--branch' << opts[:branch] if opts[:branch]
66
+ arr_opts << '--depth' << opts[:depth].to_i if opts[:depth] && opts[:depth].to_i > 0
67
+ arr_opts << '--config' << opts[:config] if opts[:config]
68
+ arr_opts << '--origin' << opts[:remote] || opts[:origin] if opts[:remote] || opts[:origin]
69
+ arr_opts << '--recursive' if opts[:recursive]
70
+
71
+ arr_opts << '--'
72
+
73
+ arr_opts << repository
74
+ arr_opts << clone_dir
75
+
76
+ command('clone', arr_opts)
77
+
78
+ opts[:bare] ? {:repository => clone_dir} : {:working_directory => clone_dir}
79
+ end
80
+
81
+
82
+ ## READ COMMANDS ##
83
+
84
+ #
85
+ # Returns most recent tag that is reachable from a commit
86
+ #
87
+ # accepts options:
88
+ # :all
89
+ # :tags
90
+ # :contains
91
+ # :debug
92
+ # :exact_match
93
+ # :dirty
94
+ # :abbrev
95
+ # :candidates
96
+ # :long
97
+ # :always
98
+ # :math
99
+ #
100
+ # @param [String|NilClass] committish target commit sha or object name
101
+ # @param [{Symbol=>Object}] opts the given options
102
+ # @return [String] the tag name
103
+ #
104
+ def describe(committish=nil, opts={})
105
+ arr_opts = []
106
+
107
+ arr_opts << '--all' if opts[:all]
108
+ arr_opts << '--tags' if opts[:tags]
109
+ arr_opts << '--contains' if opts[:contains]
110
+ arr_opts << '--debug' if opts[:debug]
111
+ arr_opts << '--long' if opts[:long]
112
+ arr_opts << '--always' if opts[:always]
113
+ arr_opts << '--exact-match' if opts[:exact_match] || opts[:"exact-match"]
114
+
115
+ arr_opts << '--dirty' if opts['dirty'] == true
116
+ arr_opts << "--dirty=#{opts['dirty']}" if opts['dirty'].is_a?(String)
117
+
118
+ arr_opts << "--abbrev=#{opts['abbrev']}" if opts[:abbrev]
119
+ arr_opts << "--candidates=#{opts['candidates']}" if opts[:candidates]
120
+ arr_opts << "--match=#{opts['match']}" if opts[:match]
121
+
122
+ arr_opts << committish if committish
123
+
124
+ return command('describe', arr_opts)
125
+ end
126
+
127
+ def log_commits(opts={})
128
+ arr_opts = log_common_options(opts)
129
+
130
+ arr_opts << '--pretty=oneline'
131
+
132
+ arr_opts += log_path_options(opts)
133
+
134
+ command_lines('log', arr_opts, true).map { |l| l.split.first }
135
+ end
136
+
137
+ def full_log_commits(opts={})
138
+ arr_opts = log_common_options(opts)
139
+
140
+ arr_opts << '--pretty=raw'
141
+ arr_opts << "--skip=#{opts[:skip]}" if opts[:skip]
142
+
143
+ arr_opts += log_path_options(opts)
144
+
145
+ full_log = command_lines('log', arr_opts, true)
146
+
147
+ process_commit_log_data(full_log)
148
+ end
149
+
150
+ def revparse(string)
151
+ return string if string =~ /^[A-Fa-f0-9]{40}$/ # passing in a sha - just no-op it
152
+ rev = ['head', 'remotes', 'tags'].map do |d|
153
+ File.join(@git_dir, 'refs', d, string)
154
+ end.find do |path|
155
+ File.file?(path)
156
+ end
157
+ return File.read(rev).chomp if rev
158
+ command('rev-parse', string)
159
+ end
160
+
161
+ def namerev(string)
162
+ command('name-rev', string).split[1]
163
+ end
164
+
165
+ def object_type(sha)
166
+ command('cat-file', ['-t', sha])
167
+ end
168
+
169
+ def object_size(sha)
170
+ command('cat-file', ['-s', sha]).to_i
171
+ end
172
+
173
+ # returns useful array of raw commit object data
174
+ def commit_data(sha)
175
+ sha = sha.to_s
176
+ cdata = command_lines('cat-file', ['commit', sha])
177
+ process_commit_data(cdata, sha, 0)
178
+ end
179
+
180
+ def process_commit_data(data, sha = nil, indent = 4)
181
+ hsh = {
182
+ 'sha' => sha,
183
+ 'message' => '',
184
+ 'parent' => []
185
+ }
186
+
187
+ loop do
188
+ key, *value = data.shift.split
189
+
190
+ break if key.nil?
191
+
192
+ if key == 'parent'
193
+ hsh['parent'] << value.join(' ')
194
+ else
195
+ hsh[key] = value.join(' ')
196
+ end
197
+ end
198
+
199
+ hsh['message'] = data.collect {|line| line[indent..-1]}.join("\n") + "\n"
200
+
201
+ return hsh
202
+ end
203
+
204
+ def tag_data(name)
205
+ sha = sha.to_s
206
+ tdata = command_lines('cat-file', ['tag', name])
207
+ process_tag_data(tdata, name, 0)
208
+ end
209
+
210
+ def process_tag_data(data, name, indent=4)
211
+ hsh = {
212
+ 'name' => name,
213
+ 'message' => ''
214
+ }
215
+
216
+ loop do
217
+ key, *value = data.shift.split
218
+
219
+ break if key.nil?
220
+
221
+ hsh[key] = value.join(' ')
222
+ end
223
+
224
+ hsh['message'] = data.collect {|line| line[indent..-1]}.join("\n") + "\n"
225
+
226
+ return hsh
227
+ end
228
+
229
+ def process_commit_log_data(data)
230
+ in_message = false
231
+
232
+ hsh_array = []
233
+
234
+ hsh = nil
235
+
236
+ data.each do |line|
237
+ line = line.chomp
238
+
239
+ if line[0].nil?
240
+ in_message = !in_message
241
+ next
242
+ end
243
+
244
+ if in_message
245
+ hsh['message'] << "#{line[4..-1]}\n"
246
+ next
247
+ end
248
+
249
+ key, *value = line.split
250
+ value = value.join(' ')
251
+
252
+ case key
253
+ when 'commit'
254
+ hsh_array << hsh if hsh
255
+ hsh = {'sha' => value, 'message' => '', 'parent' => []}
256
+ when 'parent'
257
+ hsh['parent'] << value
258
+ else
259
+ hsh[key] = value
260
+ end
261
+ end
262
+
263
+ hsh_array << hsh if hsh
264
+
265
+ return hsh_array
266
+ end
267
+
268
+ def object_contents(sha, &block)
269
+ command('cat-file', ['-p', sha], &block)
270
+ end
271
+
272
+ def ls_tree(sha)
273
+ data = {'blob' => {}, 'tree' => {}}
274
+
275
+ command_lines('ls-tree', sha).each do |line|
276
+ (info, filenm) = line.split("\t")
277
+ (mode, type, sha) = info.split
278
+ data[type][filenm] = {:mode => mode, :sha => sha}
279
+ end
280
+
281
+ data
282
+ end
283
+
284
+ def mv(file1, file2)
285
+ command_lines('mv', ['--', file1, file2])
286
+ end
287
+
288
+ def full_tree(sha)
289
+ command_lines('ls-tree', ['-r', sha])
290
+ end
291
+
292
+ def tree_depth(sha)
293
+ full_tree(sha).size
294
+ end
295
+
296
+ def change_head_branch(branch_name)
297
+ command('symbolic-ref', ['HEAD', "refs/heads/#{branch_name}"])
298
+ end
299
+
300
+ def branches_all
301
+ arr = []
302
+ command_lines('branch', '-a').each do |b|
303
+ current = (b[0, 2] == '* ')
304
+ arr << [b.gsub('* ', '').strip, current]
305
+ end
306
+ arr
307
+ end
308
+
309
+ def list_files(ref_dir)
310
+ dir = File.join(@git_dir, 'refs', ref_dir)
311
+ files = []
312
+ Dir.chdir(dir) { files = Dir.glob('**/*').select { |f| File.file?(f) } } rescue nil
313
+ files
314
+ end
315
+
316
+ def branch_current
317
+ branches_all.select { |b| b[1] }.first[0] rescue nil
318
+ end
319
+
320
+
321
+ # returns hash
322
+ # [tree-ish] = [[line_no, match], [line_no, match2]]
323
+ # [tree-ish] = [[line_no, match], [line_no, match2]]
324
+ def grep(string, opts = {})
325
+ opts[:object] ||= 'HEAD'
326
+
327
+ grep_opts = ['-n']
328
+ grep_opts << '-i' if opts[:ignore_case]
329
+ grep_opts << '-v' if opts[:invert_match]
330
+ grep_opts << '-e'
331
+ grep_opts << string
332
+ grep_opts << opts[:object] if opts[:object].is_a?(String)
333
+ grep_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
334
+
335
+ hsh = {}
336
+ command_lines('grep', grep_opts).each do |line|
337
+ if m = /(.*)\:(\d+)\:(.*)/.match(line)
338
+ hsh[m[1]] ||= []
339
+ hsh[m[1]] << [m[2].to_i, m[3]]
340
+ end
341
+ end
342
+ hsh
343
+ end
344
+
345
+ def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
346
+ diff_opts = ['-p']
347
+ diff_opts << "-M50%"
348
+ diff_opts << "--diff-filter=ADMR"
349
+ diff_opts << obj1
350
+ diff_opts << obj2 if obj2.is_a?(String)
351
+ diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
352
+
353
+ command('diff', diff_opts)
354
+ end
355
+
356
+ def diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {})
357
+ diff_opts = ['--numstat']
358
+ diff_opts << "-M50%"
359
+ diff_opts << "--diff-filter=ADMR"
360
+ diff_opts << obj1
361
+ diff_opts << obj2 if obj2.is_a?(String)
362
+ diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
363
+
364
+ hsh = {:total => {:insertions => 0, :deletions => 0, :lines => 0, :files => 0}, :files => {}}
365
+
366
+ command_lines('diff', diff_opts).each do |file|
367
+ (insertions, deletions, filename) = file.split("\t")
368
+ hsh[:total][:insertions] += insertions.to_i
369
+ hsh[:total][:deletions] += deletions.to_i
370
+ hsh[:total][:lines] = (hsh[:total][:deletions] + hsh[:total][:insertions])
371
+ hsh[:total][:files] += 1
372
+ hsh[:files][filename] = {:insertions => insertions.to_i, :deletions => deletions.to_i}
373
+ end
374
+
375
+ hsh
376
+ end
377
+
378
+ def diff_name_status(reference1 = nil, reference2 = nil, opts = {})
379
+ opts_arr = ['--name-status']
380
+ opts_arr << reference1 if reference1
381
+ opts_arr << reference2 if reference2
382
+
383
+ opts_arr << '--' << opts[:path] if opts[:path]
384
+
385
+ command_lines('diff', opts_arr).inject({}) do |memo, line|
386
+ status, path = line.split("\t")
387
+ memo[path] = status
388
+ memo
389
+ end
390
+ end
391
+
392
+ # compares the index and the working directory
393
+ def diff_files
394
+ diff_as_hash('diff-files')
395
+ end
396
+
397
+ # compares the index and the repository
398
+ def diff_index(treeish)
399
+ diff_as_hash('diff-index', treeish)
400
+ end
401
+
402
+ def ls_files(location=nil)
403
+ hsh = {}
404
+ command_lines('ls-files', ['--stage', location]).each do |line|
405
+ (info, file) = line.split("\t")
406
+ (mode, sha, stage) = info.split
407
+ file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
408
+ hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
409
+ end
410
+ hsh
411
+ end
412
+
413
+ def ls_remote(location=nil)
414
+ location ||= '.'
415
+ Hash.new{ |h,k| h[k] = {} }.tap do |hsh|
416
+ command_lines('ls-remote', [location], false).each do |line|
417
+ (sha, info) = line.split("\t")
418
+ (ref, type, name) = info.split('/', 3)
419
+ type ||= 'head'
420
+ type = 'branches' if type == 'heads'
421
+ value = {:ref => ref, :sha => sha}
422
+ hsh[type].update( name.nil? ? value : { name => value })
423
+ end
424
+ end
425
+ end
426
+
427
+ def ignored_files
428
+ command_lines('ls-files', ['--others', '-i', '--exclude-standard'])
429
+ end
430
+
431
+
432
+ def config_remote(name)
433
+ hsh = {}
434
+ config_list.each do |key, value|
435
+ if /remote.#{name}/.match(key)
436
+ hsh[key.gsub("remote.#{name}.", '')] = value
437
+ end
438
+ end
439
+ hsh
440
+ end
441
+
442
+ def config_get(name)
443
+ do_get = lambda do |path|
444
+ command('config', ['--get', name])
445
+ end
446
+
447
+ if @git_dir
448
+ Dir.chdir(@git_dir, &do_get)
449
+ else
450
+ build_list.call
451
+ end
452
+ end
453
+
454
+ def global_config_get(name)
455
+ command('config', ['--global', '--get', name], false)
456
+ end
457
+
458
+ def config_list
459
+ build_list = lambda do |path|
460
+ parse_config_list command_lines('config', ['--list'])
461
+ end
462
+
463
+ if @git_dir
464
+ Dir.chdir(@git_dir, &build_list)
465
+ else
466
+ build_list.call
467
+ end
468
+ end
469
+
470
+ def global_config_list
471
+ parse_config_list command_lines('config', ['--global', '--list'], false)
472
+ end
473
+
474
+ def parse_config_list(lines)
475
+ hsh = {}
476
+ lines.each do |line|
477
+ (key, *values) = line.split('=')
478
+ hsh[key] = values.join('=')
479
+ end
480
+ hsh
481
+ end
482
+
483
+ def parse_config(file)
484
+ parse_config_list command_lines('config', ['--list', '--file', file], false)
485
+ end
486
+
487
+ # Shows objects
488
+ #
489
+ # @param [String|NilClass] objectish the target object reference (nil == HEAD)
490
+ # @param [String|NilClass] path the path of the file to be shown
491
+ # @return [String] the object information
492
+ def show(objectish=nil, path=nil)
493
+ arr_opts = []
494
+
495
+ arr_opts << (path ? "#{objectish}:#{path}" : objectish)
496
+
497
+ command('show', arr_opts.compact)
498
+ end
499
+
500
+ ## WRITE COMMANDS ##
501
+
502
+ def config_set(name, value)
503
+ command('config', [name, value])
504
+ end
505
+
506
+ def global_config_set(name, value)
507
+ command('config', ['--global', name, value], false)
508
+ end
509
+
510
+ # updates the repository index using the working directory content
511
+ #
512
+ # lib.add('path/to/file')
513
+ # lib.add(['path/to/file1','path/to/file2'])
514
+ # lib.add(:all => true)
515
+ #
516
+ # options:
517
+ # :all => true
518
+ # :force => true
519
+ #
520
+ # @param [String,Array] paths files paths to be added to the repository
521
+ # @param [Hash] options
522
+ def add(paths='.',options={})
523
+ arr_opts = []
524
+
525
+ arr_opts << '--all' if options[:all]
526
+ arr_opts << '--force' if options[:force]
527
+
528
+ arr_opts << '--'
529
+
530
+ arr_opts << paths
531
+
532
+ arr_opts.flatten!
533
+
534
+ command('add', arr_opts)
535
+ end
536
+
537
+ def remove(path = '.', opts = {})
538
+ arr_opts = ['-f'] # overrides the up-to-date check by default
539
+ arr_opts << ['-r'] if opts[:recursive]
540
+ arr_opts << ['--cached'] if opts[:cached]
541
+ arr_opts << '--'
542
+ if path.is_a?(Array)
543
+ arr_opts += path
544
+ else
545
+ arr_opts << path
546
+ end
547
+
548
+ command('rm', arr_opts)
549
+ end
550
+
551
+ def commit(message, opts = {})
552
+ arr_opts = []
553
+ arr_opts << "--message=#{message}" if message
554
+ arr_opts << '--amend' << '--no-edit' if opts[:amend]
555
+ arr_opts << '--all' if opts[:add_all] || opts[:all]
556
+ arr_opts << '--allow-empty' if opts[:allow_empty]
557
+ arr_opts << "--author=#{opts[:author]}" if opts[:author]
558
+
559
+ command('commit', arr_opts)
560
+ end
561
+
562
+ def reset(commit, opts = {})
563
+ arr_opts = []
564
+ arr_opts << '--hard' if opts[:hard]
565
+ arr_opts << commit if commit
566
+ command('reset', arr_opts)
567
+ end
568
+
569
+ def clean(opts = {})
570
+ arr_opts = []
571
+ arr_opts << '--force' if opts[:force]
572
+ arr_opts << '-d' if opts[:d]
573
+ arr_opts << '-x' if opts[:x]
574
+
575
+ command('clean', arr_opts)
576
+ end
577
+
578
+ def revert(commitish, opts = {})
579
+ # Forcing --no-edit as default since it's not an interactive session.
580
+ opts = {:no_edit => true}.merge(opts)
581
+
582
+ arr_opts = []
583
+ arr_opts << '--no-edit' if opts[:no_edit]
584
+ arr_opts << commitish
585
+
586
+ command('revert', arr_opts)
587
+ end
588
+
589
+ def apply(patch_file)
590
+ arr_opts = []
591
+ arr_opts << '--' << patch_file if patch_file
592
+ command('apply', arr_opts)
593
+ end
594
+
595
+ def apply_mail(patch_file)
596
+ arr_opts = []
597
+ arr_opts << '--' << patch_file if patch_file
598
+ command('am', arr_opts)
599
+ end
600
+
601
+ def stashes_all
602
+ arr = []
603
+ filename = File.join(@git_dir, 'logs/refs/stash')
604
+ if File.exist?(filename)
605
+ File.open(filename).each_with_index { |line, i|
606
+ m = line.match(/:(.*)$/)
607
+ arr << [i, m[1].strip]
608
+ }
609
+ end
610
+ arr
611
+ end
612
+
613
+ def stash_save(message)
614
+ output = command('stash save', ['--', message])
615
+ output =~ /HEAD is now at/
616
+ end
617
+
618
+ def stash_apply(id = nil)
619
+ if id
620
+ command('stash apply', [id])
621
+ else
622
+ command('stash apply')
623
+ end
624
+ end
625
+
626
+ def stash_clear
627
+ command('stash clear')
628
+ end
629
+
630
+ def stash_list
631
+ command('stash list')
632
+ end
633
+
634
+ def branch_new(branch)
635
+ command('branch', branch)
636
+ end
637
+
638
+ def branch_delete(branch)
639
+ command('branch', ['-D', branch])
640
+ end
641
+
642
+ def checkout(branch, opts = {})
643
+ arr_opts = []
644
+ arr_opts << '-b' if opts[:new_branch] || opts[:b]
645
+ arr_opts << '--force' if opts[:force] || opts[:f]
646
+ arr_opts << branch
647
+
648
+ command('checkout', arr_opts)
649
+ end
650
+
651
+ def checkout_file(version, file)
652
+ arr_opts = []
653
+ arr_opts << version
654
+ arr_opts << file
655
+ command('checkout', arr_opts)
656
+ end
657
+
658
+ def merge(branch, message = nil)
659
+ arr_opts = []
660
+ arr_opts << '-m' << message if message
661
+ arr_opts += [branch]
662
+ command('merge', arr_opts)
663
+ end
664
+
665
+ def unmerged
666
+ unmerged = []
667
+ command_lines('diff', ["--cached"]).each do |line|
668
+ unmerged << $1 if line =~ /^\* Unmerged path (.*)/
669
+ end
670
+ unmerged
671
+ end
672
+
673
+ def conflicts # :yields: file, your, their
674
+ self.unmerged.each do |f|
675
+ your = Tempfile.new("YOUR-#{File.basename(f)}").path
676
+ command('show', ":2:#{f}", true, "> #{escape your}")
677
+
678
+ their = Tempfile.new("THEIR-#{File.basename(f)}").path
679
+ command('show', ":3:#{f}", true, "> #{escape their}")
680
+ yield(f, your, their)
681
+ end
682
+ end
683
+
684
+ def remote_add(name, url, opts = {})
685
+ arr_opts = ['add']
686
+ arr_opts << '-f' if opts[:with_fetch] || opts[:fetch]
687
+ arr_opts << '-t' << opts[:track] if opts[:track]
688
+ arr_opts << '--'
689
+ arr_opts << name
690
+ arr_opts << url
691
+
692
+ command('remote', arr_opts)
693
+ end
694
+
695
+ def remote_remove(name)
696
+ command('remote', ['rm', name])
697
+ end
698
+
699
+ def remotes
700
+ command_lines('remote')
701
+ end
702
+
703
+ def tags
704
+ command_lines('tag')
705
+ end
706
+
707
+ def tag(name, *opts)
708
+ target = opts[0].instance_of?(String) ? opts[0] : nil
709
+
710
+ opts = opts.last.instance_of?(Hash) ? opts.last : {}
711
+
712
+ if (opts[:a] || opts[:annotate]) && !(opts[:m] || opts[:message])
713
+ raise "Can not create an [:a|:annotate] tag without the precense of [:m|:message]."
714
+ end
715
+
716
+ arr_opts = []
717
+
718
+ arr_opts << '-f' if opts[:force] || opts[:f]
719
+ arr_opts << '-a' if opts[:a] || opts[:annotate]
720
+ arr_opts << '-s' if opts[:s] || opts[:sign]
721
+ arr_opts << '-d' if opts[:d] || opts[:delete]
722
+ arr_opts << name
723
+ arr_opts << target if target
724
+ arr_opts << "-m #{opts[:m] || opts[:message]}" if opts[:m] || opts[:message]
725
+
726
+ command('tag', arr_opts)
727
+ end
728
+
729
+
730
+ def fetch(remote, opts)
731
+ arr_opts = [remote]
732
+ arr_opts << '--tags' if opts[:t] || opts[:tags]
733
+ arr_opts << '--prune' if opts[:p] || opts[:prune]
734
+
735
+ command('fetch', arr_opts)
736
+ end
737
+
738
+ def push(remote, branch = 'master', opts = {})
739
+ # Small hack to keep backwards compatibility with the 'push(remote, branch, tags)' method signature.
740
+ opts = {:tags => opts} if [true, false].include?(opts)
741
+
742
+ arr_opts = []
743
+ arr_opts << '--force' if opts[:force] || opts[:f]
744
+ arr_opts << remote
745
+
746
+ command('push', arr_opts + [branch])
747
+ command('push', ['--tags'] + arr_opts) if opts[:tags]
748
+ end
749
+
750
+ def pull(remote='origin', branch='master')
751
+ command('pull', [remote, branch])
752
+ end
753
+
754
+ def tag_sha(tag_name)
755
+ head = File.join(@git_dir, 'refs', 'tags', tag_name)
756
+ return File.read(head).chomp if File.exist?(head)
757
+
758
+ command('show-ref', ['--tags', '-s', tag_name])
759
+ end
760
+
761
+ def repack
762
+ command('repack', ['-a', '-d'])
763
+ end
764
+
765
+ def gc
766
+ command('gc', ['--prune', '--aggressive', '--auto'])
767
+ end
768
+
769
+ # reads a tree into the current index file
770
+ def read_tree(treeish, opts = {})
771
+ arr_opts = []
772
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
773
+ arr_opts += [treeish]
774
+ command('read-tree', arr_opts)
775
+ end
776
+
777
+ def write_tree
778
+ command('write-tree')
779
+ end
780
+
781
+ def commit_tree(tree, opts = {})
782
+ opts[:message] ||= "commit tree #{tree}"
783
+ t = Tempfile.new('commit-message')
784
+ t.write(opts[:message])
785
+ t.close
786
+
787
+ arr_opts = []
788
+ arr_opts << tree
789
+ arr_opts << '-p' << opts[:parent] if opts[:parent]
790
+ arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
791
+ command('commit-tree', arr_opts, true, "< #{escape t.path}")
792
+ end
793
+
794
+ def update_ref(branch, commit)
795
+ command('update-ref', [branch, commit])
796
+ end
797
+
798
+ def checkout_index(opts = {})
799
+ arr_opts = []
800
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
801
+ arr_opts << "--force" if opts[:force]
802
+ arr_opts << "--all" if opts[:all]
803
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
804
+
805
+ command('checkout-index', arr_opts)
806
+ end
807
+
808
+ # creates an archive file
809
+ #
810
+ # options
811
+ # :format (zip, tar)
812
+ # :prefix
813
+ # :remote
814
+ # :path
815
+ def archive(sha, file = nil, opts = {})
816
+ opts[:format] ||= 'zip'
817
+
818
+ if opts[:format] == 'tgz'
819
+ opts[:format] = 'tar'
820
+ opts[:add_gzip] = true
821
+ end
822
+
823
+ if !file
824
+ tempfile = Tempfile.new('archive')
825
+ file = tempfile.path
826
+ # delete it now, before we write to it, so that Ruby doesn't delete it
827
+ # when it finalizes the Tempfile.
828
+ tempfile.close!
829
+ end
830
+
831
+ arr_opts = []
832
+ arr_opts << "--format=#{opts[:format]}" if opts[:format]
833
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
834
+ arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
835
+ arr_opts << sha
836
+ arr_opts << '--' << opts[:path] if opts[:path]
837
+ command('archive', arr_opts, true, (opts[:add_gzip] ? '| gzip' : '') + " > #{escape file}")
838
+ return file
839
+ end
840
+
841
+ # returns the current version of git, as an Array of Fixnums.
842
+ def current_command_version
843
+ output = command('version', [], false)
844
+ version = output[/\d+\.\d+(\.\d+)+/]
845
+ version.split('.').collect {|i| i.to_i}
846
+ end
847
+
848
+ def required_command_version
849
+ [1, 6]
850
+ end
851
+
852
+ def meets_required_version?
853
+ (self.current_command_version <=> self.required_command_version) >= 0
854
+ end
855
+
856
+
857
+ private
858
+
859
+ # Systen ENV variables involved in the git commands.
860
+ #
861
+ # @return [<String>] the names of the EVN variables involved in the git commands
862
+ ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
863
+
864
+ def command_lines(cmd, opts = [], chdir = true, redirect = '')
865
+ cmd_op = command(cmd, opts, chdir)
866
+ op = cmd_op.encode("UTF-8", "binary", {
867
+ :invalid => :replace,
868
+ :undef => :replace
869
+ })
870
+ op.split("\n")
871
+ end
872
+
873
+ # Takes the current git's system ENV variables and store them.
874
+ def store_git_system_env_variables
875
+ @git_system_env_variables = {}
876
+ ENV_VARIABLE_NAMES.each do |env_variable_name|
877
+ @git_system_env_variables[env_variable_name] = ENV[env_variable_name]
878
+ end
879
+ end
880
+
881
+ # Takes the previously stored git's ENV variables and set them again on ENV.
882
+ def restore_git_system_env_variables
883
+ ENV_VARIABLE_NAMES.each do |env_variable_name|
884
+ ENV[env_variable_name] = @git_system_env_variables[env_variable_name]
885
+ end
886
+ end
887
+
888
+ # Sets git's ENV variables to the custom values for the current instance.
889
+ def set_custom_git_env_variables
890
+ ENV['GIT_DIR'] = @git_dir
891
+ ENV['GIT_WORK_TREE'] = @git_work_dir
892
+ ENV['GIT_INDEX_FILE'] = @git_index_file
893
+ ENV['GIT_SSH'] = Git::Base.config.git_ssh
894
+ end
895
+
896
+ # Runs a block inside an environment with customized ENV variables.
897
+ # It restores the ENV after execution.
898
+ #
899
+ # @param [Proc] block block to be executed within the customized environment
900
+ def with_custom_env_variables(&block)
901
+ @@semaphore.synchronize do
902
+ store_git_system_env_variables()
903
+ set_custom_git_env_variables()
904
+ return block.call()
905
+ end
906
+ ensure
907
+ restore_git_system_env_variables()
908
+ end
909
+
910
+ def command(cmd, opts = [], chdir = true, redirect = '', &block)
911
+ global_opts = []
912
+ global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
913
+ global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
914
+
915
+ opts = [opts].flatten.map {|s| escape(s) }.join(' ')
916
+
917
+ global_opts = global_opts.flatten.map {|s| escape(s) }.join(' ')
918
+
919
+ git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{cmd} #{opts} #{redirect} 2>&1"
920
+
921
+ output = nil
922
+
923
+ command_thread = nil;
924
+
925
+ exitstatus = nil
926
+
927
+ with_custom_env_variables do
928
+ command_thread = Thread.new do
929
+ output = run_command(git_cmd, &block)
930
+ exitstatus = $?.exitstatus
931
+ end
932
+ command_thread.join
933
+ end
934
+
935
+ if @logger
936
+ @logger.info(git_cmd)
937
+ @logger.debug(output)
938
+ end
939
+
940
+ if exitstatus > 1 || (exitstatus == 1 && output != '')
941
+ raise Git::GitExecuteError.new(git_cmd + ':' + output.to_s)
942
+ end
943
+
944
+ return output
945
+ end
946
+
947
+ # Takes the diff command line output (as Array) and parse it into a Hash
948
+ #
949
+ # @param [String] diff_command the diff commadn to be used
950
+ # @param [Array] opts the diff options to be used
951
+ # @return [Hash] the diff as Hash
952
+ def diff_as_hash(diff_command, opts=[])
953
+ command_lines(diff_command, opts).inject({}) do |memo, line|
954
+ info, file = line.split("\t")
955
+ mode_src, mode_dest, sha_src, sha_dest, type = info.split
956
+
957
+ memo[file] = {
958
+ :mode_index => mode_dest,
959
+ :mode_repo => mode_src.to_s[1, 7],
960
+ :path => file,
961
+ :sha_repo => sha_src,
962
+ :sha_index => sha_dest,
963
+ :type => type
964
+ }
965
+
966
+ memo
967
+ end
968
+ end
969
+
970
+ # Returns an array holding the common options for the log commands
971
+ #
972
+ # @param [Hash] opts the given options
973
+ # @return [Array] the set of common options that the log command will use
974
+ def log_common_options(opts)
975
+ arr_opts = []
976
+
977
+ fw_count = opts[:count]
978
+ arr_opts << "-#{fw_count}" if fw_count
979
+ arr_opts << "--no-color"
980
+ arr_opts << "-M50%"
981
+ arr_opts << "--diff-filter=ADMR" if fw_count && fw_count != "-merges"
982
+ arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
983
+ arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
984
+ arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
985
+ arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
986
+ arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
987
+
988
+ arr_opts
989
+ end
990
+
991
+ # Retrurns an array holding path options for the log commands
992
+ #
993
+ # @param [Hash] opts the given options
994
+ # @return [Array] the set of path options that the log command will use
995
+ def log_path_options(opts)
996
+ arr_opts = []
997
+
998
+ arr_opts << opts[:object] if opts[:object].is_a? String
999
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter]
1000
+ arr_opts
1001
+ end
1002
+
1003
+ def run_command(git_cmd, &block)
1004
+ return IO.popen(git_cmd, &block) if block_given?
1005
+
1006
+ `#{git_cmd}`.chomp
1007
+ end
1008
+
1009
+ def escape(s)
1010
+ return "'#{s && s.to_s.gsub('\'','\'"\'"\'')}'" if RUBY_PLATFORM !~ /mingw|mswin/
1011
+
1012
+ # Keeping the old escape format for windows users
1013
+ escaped = s.to_s.gsub('\'', '\'\\\'\'')
1014
+ return %Q{"#{escaped}"}
1015
+ end
1016
+
1017
+ end
1018
+ end