gitreport 0.0.2 → 0.0.3

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