git 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of git might be problematic. Click here for more details.

@@ -0,0 +1,620 @@
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)
170
+ command('cat-file', ['-p', sha])
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 full_tree(sha)
186
+ command_lines('ls-tree', ['-r', sha.to_s])
187
+ end
188
+
189
+ def tree_depth(sha)
190
+ full_tree(sha).size
191
+ end
192
+
193
+ def branches_all
194
+ head = File.read(File.join(@git_dir, 'HEAD'))
195
+ arr = []
196
+
197
+ if m = /ref: refs\/heads\/(.*)/.match(head)
198
+ current = m[1]
199
+ end
200
+ arr += list_files('heads').map { |f| [f, f == current] }
201
+ arr += list_files('remotes').map { |f| [f, false] }
202
+
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
+
209
+ arr
210
+ end
211
+
212
+ def list_files(ref_dir)
213
+ dir = File.join(@git_dir, 'refs', ref_dir)
214
+ files = nil
215
+ Dir.chdir(dir) { files = Dir.glob('**/*').select { |f| File.file?(f) } }
216
+ files
217
+ end
218
+
219
+ def branch_current
220
+ branches_all.select { |b| b[1] }.first[0] rescue nil
221
+ end
222
+
223
+
224
+ # returns hash
225
+ # [tree-ish] = [[line_no, match], [line_no, match2]]
226
+ # [tree-ish] = [[line_no, match], [line_no, match2]]
227
+ def grep(string, opts = {})
228
+ opts[:object] = 'HEAD' if !opts[:object]
229
+
230
+ grep_opts = ['-n']
231
+ grep_opts << '-i' if opts[:ignore_case]
232
+ grep_opts << '-v' if opts[:invert_match]
233
+ grep_opts << "-e '#{string}'"
234
+ grep_opts << opts[:object] if opts[:object].is_a?(String)
235
+ grep_opts << ('-- ' + opts[:path_limiter]) if opts[:path_limiter].is_a? String
236
+ hsh = {}
237
+ command_lines('grep', grep_opts).each do |line|
238
+ if m = /(.*)\:(\d+)\:(.*)/.match(line)
239
+ hsh[m[1]] ||= []
240
+ hsh[m[1]] << [m[2].to_i, m[3]]
241
+ end
242
+ end
243
+ hsh
244
+ end
245
+
246
+ def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
247
+ diff_opts = ['-p']
248
+ diff_opts << obj1
249
+ diff_opts << obj2 if obj2.is_a?(String)
250
+ diff_opts << ('-- ' + opts[:path_limiter]) if opts[:path_limiter].is_a? String
251
+
252
+ command('diff', diff_opts)
253
+ end
254
+
255
+ def diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {})
256
+ diff_opts = ['--numstat']
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
+ hsh = {:total => {:insertions => 0, :deletions => 0, :lines => 0, :files => 0}, :files => {}}
262
+
263
+ command_lines('diff', diff_opts).each do |file|
264
+ (insertions, deletions, filename) = file.split("\t")
265
+ hsh[:total][:insertions] += insertions.to_i
266
+ hsh[:total][:deletions] += deletions.to_i
267
+ hsh[:total][:lines] = (hsh[:total][:deletions] + hsh[:total][:insertions])
268
+ hsh[:total][:files] += 1
269
+ hsh[:files][filename] = {:insertions => insertions.to_i, :deletions => deletions.to_i}
270
+ end
271
+
272
+ hsh
273
+ end
274
+
275
+ # compares the index and the working directory
276
+ def diff_files
277
+ hsh = {}
278
+ command_lines('diff-files').each do |line|
279
+ (info, file) = line.split("\t")
280
+ (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
281
+ hsh[file] = {:path => file, :mode_file => mode_src.to_s[1, 7], :mode_index => mode_dest,
282
+ :sha_file => sha_src, :sha_index => sha_dest, :type => type}
283
+ end
284
+ hsh
285
+ end
286
+
287
+ # compares the index and the repository
288
+ def diff_index(treeish)
289
+ hsh = {}
290
+ command_lines('diff-index', treeish).each do |line|
291
+ (info, file) = line.split("\t")
292
+ (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
293
+ hsh[file] = {:path => file, :mode_repo => mode_src.to_s[1, 7], :mode_index => mode_dest,
294
+ :sha_repo => sha_src, :sha_index => sha_dest, :type => type}
295
+ end
296
+ hsh
297
+ end
298
+
299
+ def ls_files
300
+ hsh = {}
301
+ command_lines('ls-files', '--stage').each do |line|
302
+ (info, file) = line.split("\t")
303
+ (mode, sha, stage) = info.split
304
+ hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
305
+ end
306
+ hsh
307
+ end
308
+
309
+
310
+ def config_remote(name)
311
+ hsh = {}
312
+ config_list.each do |key, value|
313
+ if /remote.#{name}/.match(key)
314
+ hsh[key.gsub("remote.#{name}.", '')] = value
315
+ end
316
+ end
317
+ hsh
318
+ end
319
+
320
+ def config_get(name)
321
+ c = config_list
322
+ c[name]
323
+ #command('config', ['--get', name])
324
+ end
325
+
326
+ def config_list
327
+ config = {}
328
+ config.merge!(parse_config('~/.gitconfig'))
329
+ config.merge!(parse_config(File.join(@git_dir, 'config')))
330
+ #hsh = {}
331
+ #command_lines('config', ['--list']).each do |line|
332
+ # (key, value) = line.split('=')
333
+ # hsh[key] = value
334
+ #end
335
+ #hsh
336
+ end
337
+
338
+ def parse_config(file)
339
+ hsh = {}
340
+ file = File.expand_path(file)
341
+ if File.file?(file)
342
+ current_section = nil
343
+ File.readlines(file).each do |line|
344
+ if m = /\[(\w+)\]/.match(line)
345
+ current_section = m[1]
346
+ elsif m = /\[(\w+?) "(.*?)"\]/.match(line)
347
+ current_section = "#{m[1]}.#{m[2]}"
348
+ elsif m = /(\w+?) = (.*)/.match(line)
349
+ key = "#{current_section}.#{m[1]}"
350
+ hsh[key] = m[2]
351
+ end
352
+ end
353
+ end
354
+ hsh
355
+ end
356
+
357
+ ## WRITE COMMANDS ##
358
+
359
+ def config_set(name, value)
360
+ command('config', [name, "'#{value}'"])
361
+ end
362
+
363
+ def add(path = '.')
364
+ path = path.join(' ') if path.is_a?(Array)
365
+ command('add', path)
366
+ end
367
+
368
+ def remove(path = '.', opts = {})
369
+ path = path.join(' ') if path.is_a?(Array)
370
+
371
+ arr_opts = ['-f'] # overrides the up-to-date check by default
372
+ arr_opts << ['-r'] if opts[:recursive]
373
+ arr_opts << path
374
+
375
+ command('rm', arr_opts)
376
+ end
377
+
378
+ def commit(message, opts = {})
379
+ arr_opts = ["-m '#{message}'"]
380
+ arr_opts << '-a' if opts[:add_all]
381
+ command('commit', arr_opts)
382
+ end
383
+
384
+ def reset(commit, opts = {})
385
+ arr_opts = []
386
+ arr_opts << '--hard' if opts[:hard]
387
+ arr_opts << commit.to_s if commit
388
+ command('reset', arr_opts)
389
+ end
390
+
391
+ def stashes_all
392
+ arr = []
393
+ filename = File.join(@git_dir, 'logs/refs/stash')
394
+ if File.exist?(filename)
395
+ File.open(filename).each_with_index { |line, i|
396
+ m = line.match(/:(.*)$/)
397
+ arr << [i, m[1].strip]
398
+ }
399
+ end
400
+ arr
401
+ end
402
+
403
+ def stash_save(message)
404
+ output = command('stash save', [message])
405
+ return false unless output.match(/HEAD is now at/)
406
+ return true
407
+ end
408
+
409
+ def stash_apply(id)
410
+ command('stash apply', [id])
411
+ # Already uptodate! ---???? What then
412
+ end
413
+
414
+ def stash_clear
415
+ command('stash clear')
416
+ end
417
+
418
+ def stash_list
419
+ command('stash list')
420
+ end
421
+
422
+ def branch_new(branch)
423
+ command('branch', branch)
424
+ end
425
+
426
+ def branch_delete(branch)
427
+ command('branch', ['-D', branch])
428
+ end
429
+
430
+ def checkout(branch, opts = {})
431
+ arr_opts = []
432
+ arr_opts << '-f' if opts[:force]
433
+ arr_opts << branch.to_s
434
+
435
+ command('checkout', arr_opts)
436
+ end
437
+
438
+ def merge(branch, message = nil)
439
+ arr_opts = []
440
+ arr_opts << ["-m '#{message}'"] if message
441
+ arr_opts << branch.to_a.join(' ')
442
+ command('merge', arr_opts)
443
+ end
444
+
445
+ def unmerged
446
+ unmerged = []
447
+ command_lines('diff', ["--cached"]).each do |line|
448
+ unmerged << $1 if line =~ /^\* Unmerged path (.*)/
449
+ end
450
+ unmerged
451
+ end
452
+
453
+ def conflicts #yields :file, :your, :their
454
+ self.unmerged.each do |f|
455
+ your = Tempfile.new("YOUR-#{File.basename(f)}").path
456
+ arr_opts = [":2:#{f}", ">#{your}"]
457
+ command('show', arr_opts)
458
+
459
+ their = Tempfile.new("THEIR-#{File.basename(f)}").path
460
+ arr_opts = [":3:#{f}", ">#{their}"]
461
+ command('show', arr_opts)
462
+ yield(f, your, their)
463
+ end
464
+ end
465
+
466
+ def remote_add(name, url, opts = {})
467
+ arr_opts = ['add']
468
+ arr_opts << '-f' if opts[:with_fetch]
469
+ arr_opts << name
470
+ arr_opts << url
471
+
472
+ command('remote', arr_opts)
473
+ end
474
+
475
+ # this is documented as such, but seems broken for some reason
476
+ # i'll try to get around it some other way later
477
+ def remote_remove(name)
478
+ command('remote', ['rm', name])
479
+ end
480
+
481
+ def remotes
482
+ command_lines('remote')
483
+ end
484
+
485
+ def tags
486
+ command_lines('tag')
487
+ end
488
+
489
+ def tag(tag)
490
+ command('tag', tag)
491
+ end
492
+
493
+
494
+ def fetch(remote)
495
+ command('fetch', remote.to_s)
496
+ end
497
+
498
+ def push(remote, branch = 'master')
499
+ command('push', [remote.to_s, branch.to_s])
500
+ end
501
+
502
+ def tag_sha(tag_name)
503
+ head = File.join(@git_dir, 'refs', 'tags', tag_name)
504
+ return File.read(head).chomp if File.exists?(head)
505
+
506
+ command('show-ref', ['--tags', '-s', tag_name])
507
+ end
508
+
509
+ def repack
510
+ command('repack', ['-a', '-d'])
511
+ end
512
+
513
+ # reads a tree into the current index file
514
+ def read_tree(treeish, opts = {})
515
+ arr_opts = []
516
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
517
+ arr_opts << treeish.to_a.join(' ')
518
+ command('read-tree', arr_opts)
519
+ end
520
+
521
+ def write_tree
522
+ command('write-tree')
523
+ end
524
+
525
+ def commit_tree(tree, opts = {})
526
+ opts[:message] = "commit tree #{tree}" if !opts[:message]
527
+ t = Tempfile.new('commit-message')
528
+ t.write(opts[:message])
529
+ t.close
530
+
531
+ arr_opts = []
532
+ arr_opts << tree
533
+ arr_opts << "-p #{opts[:parent]}" if opts[:parent]
534
+ opts[:parents].each { |p| arr_opts << "-p #{p.to_s}" } if opts[:parents]
535
+ arr_opts << "< #{t.path}"
536
+ command('commit-tree', arr_opts)
537
+ end
538
+
539
+ def update_ref(branch, commit)
540
+ command('update-ref', [branch.to_s, commit.to_s])
541
+ end
542
+
543
+ def checkout_index(opts = {})
544
+ arr_opts = []
545
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
546
+ arr_opts << "--force" if opts[:force]
547
+ arr_opts << "--all" if opts[:all]
548
+ arr_opts << ('-- ' + opts[:path_limiter]) if opts[:path_limiter].is_a? String
549
+ command('checkout-index', arr_opts)
550
+ end
551
+
552
+ # creates an archive file
553
+ #
554
+ # options
555
+ # :format (zip, tar)
556
+ # :prefix
557
+ # :remote
558
+ # :path
559
+ def archive(sha, file = nil, opts = {})
560
+ opts[:format] = 'zip' if !opts[:format]
561
+
562
+ if opts[:format] == 'tgz'
563
+ opts[:format] = 'tar'
564
+ opts[:add_gzip] = true
565
+ end
566
+
567
+ if !file
568
+ file = Tempfile.new('archive').path
569
+ end
570
+
571
+ arr_opts = []
572
+ arr_opts << "--format=#{opts[:format]}" if opts[:format]
573
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
574
+ arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
575
+ arr_opts << sha
576
+ arr_opts << opts[:path] if opts[:path]
577
+ arr_opts << '| gzip' if opts[:add_gzip]
578
+ arr_opts << "> #{file.to_s}"
579
+ command('archive', arr_opts)
580
+ return file
581
+ end
582
+
583
+ private
584
+
585
+ def command_lines(cmd, opts = [], chdir = true)
586
+ command(cmd, opts, chdir).split("\n")
587
+ end
588
+
589
+ def command(cmd, opts = [], chdir = true)
590
+ ENV['GIT_DIR'] = @git_dir if (@git_dir != ENV['GIT_DIR'])
591
+ ENV['GIT_INDEX_FILE'] = @git_index_file if (@git_index_file != ENV['GIT_INDEX_FILE'])
592
+ ENV['GIT_WORK_TREE'] = @git_work_dir if (@git_work_dir != ENV['GIT_WORK_TREE'])
593
+ path = @git_work_dir || @git_dir || @path
594
+
595
+ opts = opts.to_a.join(' ')
596
+ git_cmd = "git #{cmd} #{opts}"
597
+
598
+ out = nil
599
+ if chdir && (Dir.getwd != path)
600
+ Dir.chdir(path) { out = `#{git_cmd} 2>&1`.chomp }
601
+ else
602
+ out = `#{git_cmd} 2>&1`.chomp
603
+ end
604
+
605
+ if @logger
606
+ @logger.info(git_cmd)
607
+ @logger.debug(out)
608
+ end
609
+
610
+ if $?.exitstatus > 0
611
+ if $?.exitstatus == 1 && out == ''
612
+ return ''
613
+ end
614
+ raise Git::GitExecuteError.new(git_cmd + ':' + out.to_s)
615
+ end
616
+ out
617
+ end
618
+
619
+ end
620
+ end