schacon-git 1.0.6

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