opsb-git 1.2.5

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