blackwinter-git 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/git/lib.rb ADDED
@@ -0,0 +1,743 @@
1
+ require 'tempfile'
2
+ require 'shellwords'
3
+
4
+ class String # :nodoc:
5
+ def shellescape
6
+ return "''" if empty?
7
+
8
+ str = dup
9
+ str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
10
+ str.gsub!(/\n/, "'\n'")
11
+ str
12
+ end
13
+ end unless ''.respond_to?(:shellescape)
14
+
15
+ module Git
16
+
17
+ class GitExecuteError < StandardError
18
+ end
19
+
20
+ class Lib
21
+
22
+ def initialize(base = nil, logger = nil)
23
+ @git_dir = nil
24
+ @git_index_file = nil
25
+ @git_work_dir = nil
26
+ @path = nil
27
+
28
+ if base.is_a?(Git::Base)
29
+ @git_dir = base.repo.path
30
+ @git_index_file = base.index.path if base.index
31
+ @git_work_dir = base.dir.path if base.dir
32
+ elsif base.is_a?(Hash)
33
+ @git_dir = base[:repository]
34
+ @git_index_file = base[:index]
35
+ @git_work_dir = base[:working_directory]
36
+ end
37
+ @logger = logger
38
+ end
39
+
40
+ def init
41
+ command('init')
42
+ end
43
+
44
+ # tries to clone the given repo
45
+ #
46
+ # returns {:repository} (if bare)
47
+ # {:working_directory} otherwise
48
+ #
49
+ # accepts options:
50
+ # :remote:: name of remote (rather than 'origin')
51
+ # :bare:: no working directory
52
+ # :depth:: the number of commits back to pull
53
+ #
54
+ # TODO - make this work with SSH password or auth_key
55
+ #
56
+ def clone(repository, name, opts = {})
57
+ @path = opts[:path] || '.'
58
+ clone_dir = opts[:path] ? File.join(@path, name) : name
59
+
60
+ arr_opts = []
61
+ arr_opts << "--bare" if opts[:bare]
62
+ arr_opts << "-o" << opts[:remote] if opts[:remote]
63
+ arr_opts << "--depth" << opts[:depth].to_i if opts[:depth] && opts[:depth].to_i > 0
64
+
65
+ arr_opts << '--'
66
+ arr_opts << repository
67
+ arr_opts << clone_dir
68
+
69
+ command('clone', arr_opts)
70
+
71
+ opts[:bare] ? {:repository => clone_dir} : {:working_directory => clone_dir}
72
+ end
73
+
74
+
75
+ ## READ COMMANDS ##
76
+
77
+
78
+ def log_commits(opts = {})
79
+ arr_opts = ['--pretty=oneline']
80
+ arr_opts << "-#{opts[:count]}" if opts[:count]
81
+ arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
82
+ arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
83
+ arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
84
+ arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
85
+ arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
86
+ arr_opts << opts[:object] if opts[:object].is_a? String
87
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
88
+
89
+ command_lines('log', arr_opts, true).map { |l| l.split.first }
90
+ end
91
+
92
+ def full_log_commits(opts = {})
93
+ arr_opts = ['--pretty=raw']
94
+ arr_opts << "-#{opts[:count]}" if opts[:count]
95
+ arr_opts << "--skip=#{opts[:skip]}" if opts[:skip]
96
+ arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
97
+ arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
98
+ arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
99
+ arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
100
+ arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
101
+ arr_opts << opts[:object] if opts[:object].is_a? String
102
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
103
+
104
+ full_log = command_lines('log', arr_opts, true)
105
+ process_commit_data(full_log)
106
+ end
107
+
108
+ def revparse(string)
109
+ return string if string =~ /[A-Fa-f0-9]{40}/ # passing in a sha - just no-op it
110
+ rev = ['head', 'remotes', 'tags'].map do |d|
111
+ File.join(@git_dir, 'refs', d, string)
112
+ end.find do |path|
113
+ File.file?(path)
114
+ end
115
+ return File.read(rev).chomp if rev
116
+ command('rev-parse', string)
117
+ end
118
+
119
+ def namerev(string)
120
+ command('name-rev', string).split[1]
121
+ end
122
+
123
+ def object_type(sha)
124
+ command('cat-file', ['-t', sha])
125
+ end
126
+
127
+ def object_size(sha)
128
+ command('cat-file', ['-s', sha]).to_i
129
+ end
130
+
131
+ # returns useful array of raw commit object data
132
+ def commit_data(sha)
133
+ sha = sha.to_s
134
+ cdata = command_lines('cat-file', ['commit', sha])
135
+ process_commit_data(cdata, sha, 0)
136
+ end
137
+
138
+ def process_commit_data(data, sha = nil, indent = 4)
139
+ in_message = false
140
+
141
+ if sha
142
+ hsh = {'sha' => sha, 'message' => '', 'parent' => []}
143
+ else
144
+ hsh_array = []
145
+ end
146
+
147
+ data.each do |line|
148
+ line = line.chomp
149
+ if line == ''
150
+ in_message = !in_message
151
+ elsif in_message
152
+ hsh['message'] << line[indent..-1] << "\n"
153
+ else
154
+ data = line.split
155
+ key = data.shift
156
+ value = data.join(' ')
157
+ if key == 'commit'
158
+ sha = value
159
+ hsh_array << hsh if hsh
160
+ hsh = {'sha' => sha, 'message' => '', 'parent' => []}
161
+ end
162
+ if key == 'parent'
163
+ hsh[key] << value
164
+ else
165
+ hsh[key] = value
166
+ end
167
+ end
168
+ end
169
+
170
+ if hsh_array
171
+ hsh_array << hsh if hsh
172
+ hsh_array
173
+ else
174
+ hsh
175
+ end
176
+ end
177
+
178
+ def object_contents(sha, &block)
179
+ command('cat-file', ['-p', sha], &block)
180
+ end
181
+
182
+ def ls_tree(sha)
183
+ data = {'blob' => {}, 'tree' => {}}
184
+
185
+ command_lines('ls-tree', sha).each do |line|
186
+ (info, filenm) = line.split("\t")
187
+ (mode, type, sha) = info.split
188
+ data[type][filenm] = {:mode => mode, :sha => sha}
189
+ end
190
+
191
+ data
192
+ end
193
+
194
+ def mv(file1, file2)
195
+ command_lines('mv', ['--', file1, file2])
196
+ end
197
+
198
+ def full_tree(sha)
199
+ command_lines('ls-tree', ['-r', sha])
200
+ end
201
+
202
+ def tree_depth(sha)
203
+ full_tree(sha).size
204
+ end
205
+
206
+ def change_head_branch(branch_name)
207
+ command('symbolic-ref', ['HEAD', "refs/heads/#{branch_name}"])
208
+ end
209
+
210
+ def branches_all
211
+ arr = []
212
+ command_lines('branch', '-a').each do |b|
213
+ current = (b[0, 2] == '* ')
214
+ arr << [b.gsub('* ', '').strip, current]
215
+ end
216
+ arr
217
+ end
218
+
219
+ def list_files(ref_dir)
220
+ dir = File.join(@git_dir, 'refs', ref_dir)
221
+ files = []
222
+ Dir.chdir(dir) { files = Dir.glob('**/*').select { |f| File.file?(f) } } rescue nil
223
+ files
224
+ end
225
+
226
+ def branch_current
227
+ branches_all.select { |b| b[1] }.first[0] rescue nil
228
+ end
229
+
230
+
231
+ # returns hash
232
+ # [tree-ish] = [[line_no, match], [line_no, match2]]
233
+ # [tree-ish] = [[line_no, match], [line_no, match2]]
234
+ def grep(string, opts = {})
235
+ opts[:object] ||= 'HEAD'
236
+
237
+ grep_opts = ['-n']
238
+ grep_opts << '-i' if opts[:ignore_case]
239
+ grep_opts << '-v' if opts[:invert_match]
240
+ grep_opts << '-e'
241
+ grep_opts << string
242
+ grep_opts << opts[:object] if opts[:object].is_a?(String)
243
+ grep_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
244
+
245
+ hsh = {}
246
+ command_lines('grep', grep_opts).each do |line|
247
+ if m = /(.*)\:(\d+)\:(.*)/.match(line)
248
+ hsh[m[1]] ||= []
249
+ hsh[m[1]] << [m[2].to_i, m[3]]
250
+ end
251
+ end
252
+ hsh
253
+ end
254
+
255
+ def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
256
+ diff_opts = ['-p']
257
+ diff_opts << obj1
258
+ diff_opts << obj2 if obj2.is_a?(String)
259
+ diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
260
+
261
+ command('diff', diff_opts)
262
+ end
263
+
264
+ def _diff_stats(diff_cmd = 'diff', obj1 = 'HEAD', obj2 = nil, opts = {})
265
+ diff_opts = ['--numstat']
266
+ diff_opts << obj1
267
+ diff_opts << obj2 if obj2.is_a?(String)
268
+ diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
269
+
270
+ hsh = {:total => {:insertions => 0, :deletions => 0, :lines => 0, :files => 0}, :files => {}}
271
+
272
+ command_lines(diff_cmd, diff_opts).each do |file|
273
+ (insertions, deletions, filename) = file.split("\t")
274
+ hsh[:total][:insertions] += insertions.to_i
275
+ hsh[:total][:deletions] += deletions.to_i
276
+ hsh[:total][:lines] = (hsh[:total][:deletions] + hsh[:total][:insertions])
277
+ hsh[:total][:files] += 1
278
+ hsh[:files][filename] = {:insertions => insertions.to_i, :deletions => deletions.to_i}
279
+ end
280
+
281
+ hsh
282
+ end
283
+
284
+ private :_diff_stats
285
+
286
+ def diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {})
287
+ _diff_stats('diff', obj1, obj2, opts)
288
+ end
289
+
290
+ def diff_index_stats(treeish = 'HEAD', opts = {})
291
+ _diff_stats('diff-index', treeish, nil, opts)
292
+ end
293
+
294
+ # compares the index and the working directory
295
+ def diff_files
296
+ hsh = {}
297
+ command_lines('diff-files').each do |line|
298
+ (info, file) = line.split("\t")
299
+ (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
300
+ hsh[file] = {:path => file, :mode_file => mode_src.to_s[1, 7], :mode_index => mode_dest,
301
+ :sha_file => sha_src, :sha_index => sha_dest, :type => type}
302
+ end
303
+ hsh
304
+ end
305
+
306
+ # compares the index and the repository
307
+ def diff_index(treeish)
308
+ hsh = {}
309
+ command_lines('diff-index', treeish).each do |line|
310
+ (info, file) = line.split("\t")
311
+ (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
312
+ hsh[file] = {:path => file, :mode_repo => mode_src.to_s[1, 7], :mode_index => mode_dest,
313
+ :sha_repo => sha_src, :sha_index => sha_dest, :type => type}
314
+ end
315
+ hsh
316
+ end
317
+
318
+ def ls_files(location=nil)
319
+ hsh = {}
320
+ command_lines('ls-files', ['--stage', location]).each do |line|
321
+ (info, file) = line.split("\t")
322
+ (mode, sha, stage) = info.split
323
+ file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
324
+ hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
325
+ end
326
+ hsh
327
+ end
328
+
329
+
330
+ def ignored_files
331
+ command_lines('ls-files', ['--others', '-i', '--exclude-standard'])
332
+ end
333
+
334
+
335
+ def config_remote(name)
336
+ hsh = {}
337
+ config_list.each do |key, value|
338
+ if /remote.#{name}/.match(key)
339
+ hsh[key.gsub("remote.#{name}.", '')] = value
340
+ end
341
+ end
342
+ hsh
343
+ end
344
+
345
+ def config_get(name)
346
+ do_get = lambda do
347
+ command('config', ['--get', name])
348
+ end
349
+
350
+ if @git_dir
351
+ Dir.chdir(@git_dir, &do_get)
352
+ else
353
+ build_list.call
354
+ end
355
+ end
356
+
357
+ def global_config_get(name)
358
+ command('config', ['--global', '--get', name], false)
359
+ end
360
+
361
+ def config_list
362
+ build_list = lambda do |path|
363
+ parse_config_list command_lines('config', ['--list'])
364
+ end
365
+
366
+ if @git_dir
367
+ Dir.chdir(@git_dir, &build_list)
368
+ else
369
+ build_list.call
370
+ end
371
+ end
372
+
373
+ def global_config_list
374
+ parse_config_list command_lines('config', ['--global', '--list'], false)
375
+ end
376
+
377
+ def parse_config_list(lines)
378
+ hsh = {}
379
+ lines.each do |line|
380
+ (key, *values) = line.split('=')
381
+ hsh[key] = values.join('=')
382
+ end
383
+ hsh
384
+ end
385
+
386
+ def parse_config(file)
387
+ hsh = {}
388
+ parse_config_list command_lines('config', ['--list', '--file', file], false)
389
+ #hsh = {}
390
+ #file = File.expand_path(file)
391
+ #if File.file?(file)
392
+ # current_section = nil
393
+ # File.readlines(file).each do |line|
394
+ # if m = /\[(\w+)\]/.match(line)
395
+ # current_section = m[1]
396
+ # elsif m = /\[(\w+?) "(.*?)"\]/.match(line)
397
+ # current_section = "#{m[1]}.#{m[2]}"
398
+ # elsif m = /(\w+?) = (.*)/.match(line)
399
+ # key = "#{current_section}.#{m[1]}"
400
+ # hsh[key] = m[2]
401
+ # end
402
+ # end
403
+ #end
404
+ #hsh
405
+ end
406
+
407
+ ## WRITE COMMANDS ##
408
+
409
+ def config_set(name, value)
410
+ command('config', [name, value])
411
+ end
412
+
413
+ def global_config_set(name, value)
414
+ command('config', ['--global', name, value], false)
415
+ end
416
+
417
+ def add(path = '.')
418
+ arr_opts = ['--']
419
+ if path.is_a?(Array)
420
+ arr_opts += path
421
+ else
422
+ arr_opts << path
423
+ end
424
+ command('add', arr_opts)
425
+ end
426
+
427
+ def remove(path = '.', opts = {})
428
+ arr_opts = ['-f'] # overrides the up-to-date check by default
429
+ arr_opts << ['-r'] if opts[:recursive]
430
+ arr_opts << '--'
431
+ if path.is_a?(Array)
432
+ arr_opts += path
433
+ else
434
+ arr_opts << path
435
+ end
436
+
437
+ command('rm', arr_opts)
438
+ end
439
+
440
+ def commit(message, opts = {})
441
+ arr_opts = ['-m', message]
442
+ arr_opts << '-a' if opts[:add_all]
443
+ arr_opts << '--allow-empty' if opts[:allow_empty]
444
+ arr_opts << "--author" << opts[:author] if opts[:author]
445
+ command('commit', arr_opts)
446
+ end
447
+
448
+ def reset(commit, opts = {})
449
+ arr_opts = []
450
+ arr_opts << '--hard' if opts[:hard]
451
+ arr_opts << '--quiet' if opts[:quiet]
452
+ arr_opts << commit if commit
453
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
454
+ command('reset', arr_opts)
455
+ end
456
+
457
+ def apply(patch_file)
458
+ arr_opts = []
459
+ arr_opts << '--' << patch_file if patch_file
460
+ command('apply', arr_opts)
461
+ end
462
+
463
+ def apply_mail(patch_file)
464
+ arr_opts = []
465
+ arr_opts << '--' << patch_file if patch_file
466
+ command('am', arr_opts)
467
+ end
468
+
469
+ def stashes_all
470
+ arr = []
471
+ filename = File.join(@git_dir, 'logs/refs/stash')
472
+ if File.exist?(filename)
473
+ File.open(filename).each_with_index { |line, i|
474
+ m = line.match(/:(.*)$/)
475
+ arr << [i, m[1].strip]
476
+ }
477
+ end
478
+ arr
479
+ end
480
+
481
+ def stash_save(message)
482
+ output = command('stash save', ['--', message])
483
+ output =~ /HEAD is now at/
484
+ end
485
+
486
+ def stash_apply(id = nil)
487
+ if id
488
+ command('stash apply', [id])
489
+ else
490
+ command('stash apply')
491
+ end
492
+ end
493
+
494
+ def stash_clear
495
+ command('stash clear')
496
+ end
497
+
498
+ def stash_list
499
+ command('stash list')
500
+ end
501
+
502
+ def branch_new(branch)
503
+ command('branch', branch)
504
+ end
505
+
506
+ def branch_delete(branch)
507
+ command('branch', ['-D', branch])
508
+ end
509
+
510
+ def checkout(branch, opts = {})
511
+ arr_opts = []
512
+ arr_opts << '-f' if opts[:force]
513
+ arr_opts << '-b' << opts[:new_branch] if opts[:new_branch]
514
+ arr_opts << branch
515
+
516
+ command('checkout', arr_opts)
517
+ end
518
+
519
+ def checkout_file(version, file)
520
+ arr_opts = []
521
+ arr_opts << version
522
+ arr_opts << file
523
+ command('checkout', arr_opts)
524
+ end
525
+
526
+ def merge(branch, message = nil)
527
+ arr_opts = []
528
+ arr_opts << '-m' << message if message
529
+ arr_opts += [branch]
530
+ command('merge', arr_opts)
531
+ end
532
+
533
+ def unmerged
534
+ unmerged = []
535
+ command_lines('diff', ["--cached"]).each do |line|
536
+ unmerged << $1 if line =~ /^\* Unmerged path (.*)/
537
+ end
538
+ unmerged
539
+ end
540
+
541
+ def conflicts # :yields: file, your, their
542
+ self.unmerged.each do |f|
543
+ your = Tempfile.new("YOUR-#{File.basename(f)}").path
544
+ command('show', ":2:#{f}", true, "> #{escape your}")
545
+
546
+ their = Tempfile.new("THEIR-#{File.basename(f)}").path
547
+ command('show', ":3:#{f}", true, "> #{escape their}")
548
+ yield(f, your, their)
549
+ end
550
+ end
551
+
552
+ def remote_add(name, url, opts = {})
553
+ arr_opts = ['add']
554
+ arr_opts << '-f' if opts[:with_fetch]
555
+ arr_opts << '--'
556
+ arr_opts << name
557
+ arr_opts << url
558
+
559
+ command('remote', arr_opts)
560
+ end
561
+
562
+ # this is documented as such, but seems broken for some reason
563
+ # i'll try to get around it some other way later
564
+ def remote_remove(name)
565
+ command('remote', ['rm', '--', name])
566
+ end
567
+
568
+ def remotes
569
+ command_lines('remote')
570
+ end
571
+
572
+ def tags
573
+ command_lines('tag')
574
+ end
575
+
576
+ def tag(tag)
577
+ command('tag', tag)
578
+ end
579
+
580
+
581
+ def fetch(remote)
582
+ command('fetch', remote)
583
+ end
584
+
585
+ def push(remote, branch = 'master', tags = false)
586
+ command('push', [remote, branch])
587
+ command('push', ['--tags', remote]) if tags
588
+ end
589
+
590
+ def tag_sha(tag_name)
591
+ head = File.join(@git_dir, 'refs', 'tags', tag_name)
592
+ return File.read(head).chomp if File.exists?(head)
593
+
594
+ command('show-ref', ['--tags', '-s', tag_name])
595
+ end
596
+
597
+ def repack
598
+ command('repack', ['-a', '-d'])
599
+ end
600
+
601
+ def gc
602
+ command('gc', ['--prune', '--aggressive', '--auto'])
603
+ end
604
+
605
+ # reads a tree into the current index file
606
+ def read_tree(treeish, opts = {})
607
+ arr_opts = []
608
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
609
+ arr_opts += [treeish]
610
+ command('read-tree', arr_opts)
611
+ end
612
+
613
+ def write_tree
614
+ command('write-tree')
615
+ end
616
+
617
+ def commit_tree(tree, opts = {})
618
+ opts[:message] ||= "commit tree #{tree}"
619
+ t = Tempfile.new('commit-message')
620
+ t.write(opts[:message])
621
+ t.close
622
+
623
+ arr_opts = []
624
+ arr_opts << tree
625
+ arr_opts << '-p' << opts[:parent] if opts[:parent]
626
+ arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
627
+ command('commit-tree', arr_opts, true, "< #{escape t.path}")
628
+ end
629
+
630
+ def update_ref(branch, commit)
631
+ command('update-ref', [branch, commit])
632
+ end
633
+
634
+ def checkout_index(opts = {})
635
+ arr_opts = []
636
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
637
+ arr_opts << "--index" if opts[:index]
638
+ arr_opts << "--force" if opts[:force]
639
+ arr_opts << "--all" if opts[:all]
640
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
641
+
642
+ command('checkout-index', arr_opts)
643
+ end
644
+
645
+ # creates an archive file
646
+ #
647
+ # options
648
+ # :format (zip, tar)
649
+ # :prefix
650
+ # :remote
651
+ # :path
652
+ def archive(sha, file = nil, opts = {})
653
+ opts[:format] ||= 'zip'
654
+
655
+ if opts[:format] == 'tgz'
656
+ opts[:format] = 'tar'
657
+ opts[:add_gzip] = true
658
+ end
659
+
660
+ file ||= Tempfile.new('archive').path
661
+
662
+ arr_opts = []
663
+ arr_opts << "--format=#{opts[:format]}" if opts[:format]
664
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
665
+ arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
666
+ arr_opts << sha
667
+ arr_opts << '--' << opts[:path] if opts[:path]
668
+ command('archive', arr_opts, true, (opts[:add_gzip] ? '| gzip' : '') + " > #{escape file}")
669
+ return file
670
+ end
671
+
672
+ # returns the current version of git, as an Array of Fixnums.
673
+ def current_command_version
674
+ output = command('version', [], false)
675
+ version = output[/\d+\.\d+(\.\d+)+/]
676
+ version.split('.').collect {|i| i.to_i}
677
+ end
678
+
679
+ def required_command_version
680
+ [1, 6, 0, 0]
681
+ end
682
+
683
+ def meets_required_version?
684
+ current_version = self.current_command_version
685
+ required_version = self.required_command_version
686
+
687
+ return current_version[0] >= required_version[0] &&
688
+ current_version[1] >= required_version[1] &&
689
+ (current_version[2] ? current_version[2] >= required_version[2] : true) &&
690
+ (current_version[3] ? current_version[3] >= required_version[3] : true)
691
+ end
692
+
693
+
694
+ private
695
+
696
+ def command_lines(cmd, opts = [], chdir = true, redirect = '')
697
+ command(cmd, opts, chdir).split("\n")
698
+ end
699
+
700
+ def command(cmd, opts = [], chdir = true, redirect = '', &block)
701
+ ENV['GIT_DIR'] = @git_dir
702
+ ENV['GIT_INDEX_FILE'] = @git_index_file
703
+ ENV['GIT_WORK_TREE'] = @git_work_dir
704
+ path = @git_work_dir || @git_dir || @path
705
+
706
+ opts = [opts].flatten.map {|s| escape(s) }.join(' ')
707
+ git_cmd = "git #{cmd} #{opts} #{redirect} 2>&1"
708
+
709
+ out = nil
710
+ if chdir && (Dir.getwd != path)
711
+ Dir.chdir(path) { out = run_command(git_cmd, &block) }
712
+ else
713
+ out = run_command(git_cmd, &block)
714
+ end
715
+
716
+ if @logger
717
+ @logger.info(git_cmd)
718
+ @logger.debug(out)
719
+ end
720
+
721
+ if $?.exitstatus > 0
722
+ if $?.exitstatus == 1 && out == ''
723
+ return ''
724
+ end
725
+ raise Git::GitExecuteError.new(git_cmd + ':' + out.to_s)
726
+ end
727
+ out
728
+ end
729
+
730
+ def run_command(git_cmd, &block)
731
+ if block_given?
732
+ IO.popen(git_cmd, &block)
733
+ else
734
+ `#{git_cmd}`.chomp
735
+ end
736
+ end
737
+
738
+ def escape(s)
739
+ s.to_s.shellescape
740
+ end
741
+
742
+ end
743
+ end