birkirb-git 1.1.1

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/branch.rb ADDED
@@ -0,0 +1,104 @@
1
+ module Git
2
+ class Branch < Path
3
+
4
+ attr_accessor :full, :remote, :name
5
+
6
+ def initialize(base, name)
7
+ @remote = nil
8
+ @full = name
9
+ @base = base
10
+ @gcommit = nil
11
+ @stashes = nil
12
+
13
+ parts = name.split('/')
14
+ if parts[1]
15
+ @remote = Git::Remote.new(@base, parts[0])
16
+ @name = parts[1]
17
+ else
18
+ @name = parts[0]
19
+ end
20
+ end
21
+
22
+ def gcommit
23
+ @gcommit ||= @base.gcommit(@full)
24
+ @gcommit
25
+ end
26
+
27
+ def stashes
28
+ @stashes ||= Git::Stashes.new(@base)
29
+ end
30
+
31
+ def checkout
32
+ check_if_create
33
+ @base.checkout(@full)
34
+ end
35
+
36
+ def archive(file, opts = {})
37
+ @base.lib.archive(@full, file, opts)
38
+ end
39
+
40
+ # g.branch('new_branch').in_branch do
41
+ # # create new file
42
+ # # do other stuff
43
+ # return true # auto commits and switches back
44
+ # end
45
+ def in_branch (message = 'in branch work')
46
+ old_current = @base.lib.branch_current
47
+ checkout
48
+ if yield
49
+ @base.commit_all(message)
50
+ else
51
+ @base.reset_hard
52
+ end
53
+ @base.checkout(old_current)
54
+ end
55
+
56
+ def create
57
+ check_if_create
58
+ end
59
+
60
+ def delete
61
+ @base.lib.branch_delete(@name)
62
+ end
63
+
64
+ def current
65
+ determine_current
66
+ end
67
+
68
+ def merge(branch = nil, message = nil)
69
+ if branch
70
+ in_branch do
71
+ @base.merge(branch, message)
72
+ false
73
+ end
74
+ # merge a branch into this one
75
+ else
76
+ # merge this branch into the current one
77
+ @base.merge(@name)
78
+ end
79
+ end
80
+
81
+ def update_ref(commit)
82
+ @base.lib.update_ref(@full, commit)
83
+ end
84
+
85
+ def to_a
86
+ [@full]
87
+ end
88
+
89
+ def to_s
90
+ @full
91
+ end
92
+
93
+ private
94
+
95
+ def check_if_create
96
+ @base.lib.branch_new(@name) rescue nil
97
+ end
98
+
99
+ def determine_current
100
+ @base.lib.branch_current == @name
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,48 @@
1
+ module Git
2
+
3
+ # object that holds all the available branches
4
+ class Branches
5
+ include Enumerable
6
+
7
+ def initialize(base)
8
+ @branches = {}
9
+
10
+ @base = base
11
+
12
+ @base.lib.branches_all.each do |b|
13
+ @branches[b[0]] = Git::Branch.new(@base, b[0])
14
+ end
15
+ end
16
+
17
+ def local
18
+ self.select { |b| !b.remote }
19
+ end
20
+
21
+ def remote
22
+ self.select { |b| b.remote }
23
+ end
24
+
25
+ # array like methods
26
+
27
+ def size
28
+ @branches.size
29
+ end
30
+
31
+ def each(&block)
32
+ @branches.values.each(&block)
33
+ end
34
+
35
+ def [](symbol)
36
+ @branches[symbol.to_s]
37
+ end
38
+
39
+ def to_s
40
+ out = ''
41
+ @branches.each do |k, b|
42
+ out << (b.current ? '* ' : ' ') << b.to_s << "\n"
43
+ end
44
+ out
45
+ end
46
+
47
+ end
48
+ end
data/lib/git/diff.rb ADDED
@@ -0,0 +1,146 @@
1
+ module Git
2
+
3
+ # object that holds the last X commits on given branch
4
+ class Diff
5
+ include Enumerable
6
+
7
+ def initialize(base, from = nil, to = nil)
8
+ @base = base
9
+ @from = from.to_s
10
+ @to = to.to_s
11
+
12
+ @path = nil
13
+ @full_diff = nil
14
+ @full_diff_files = nil
15
+ @stats = nil
16
+ end
17
+ attr_reader :from, :to
18
+
19
+ def path(path)
20
+ @path = path
21
+ return self
22
+ end
23
+
24
+ def size
25
+ cache_stats
26
+ @stats[:total][:files]
27
+ end
28
+
29
+ def lines
30
+ cache_stats
31
+ @stats[:total][:lines]
32
+ end
33
+
34
+ def deletions
35
+ cache_stats
36
+ @stats[:total][:deletions]
37
+ end
38
+
39
+ def insertions
40
+ cache_stats
41
+ @stats[:total][:insertions]
42
+ end
43
+
44
+ def stats
45
+ cache_stats
46
+ @stats
47
+ end
48
+
49
+ # if file is provided and is writable, it will write the patch into the file
50
+ def patch(file = nil)
51
+ cache_full
52
+ @full_diff
53
+ end
54
+ alias_method :to_s, :patch
55
+
56
+ # enumerable methods
57
+
58
+ def [](key)
59
+ process_full
60
+ @full_diff_files.assoc(key)[1]
61
+ end
62
+
63
+ def each(&block) # :yields: each Git::DiffFile in turn
64
+ process_full
65
+ @full_diff_files.map { |file| file[1] }.each(&block)
66
+ end
67
+
68
+ class DiffFile
69
+ attr_accessor :patch, :path, :mode, :src, :dst, :type
70
+ @base = nil
71
+
72
+ def initialize(base, hash)
73
+ @base = base
74
+ @patch = hash[:patch]
75
+ @path = hash[:path]
76
+ @mode = hash[:mode]
77
+ @src = hash[:src]
78
+ @dst = hash[:dst]
79
+ @type = hash[:type]
80
+ @binary = hash[:binary]
81
+ end
82
+
83
+ def binary?
84
+ !!@binary
85
+ end
86
+
87
+ def blob(type = :dst)
88
+ if type == :src
89
+ @base.object(@src) if @src != '0000000'
90
+ else
91
+ @base.object(@dst) if @dst != '0000000'
92
+ end
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def cache_full
99
+ unless @full_diff
100
+ @full_diff = @base.lib.diff_full(@from, @to, {:path_limiter => @path})
101
+ end
102
+ end
103
+
104
+ def process_full
105
+ unless @full_diff_files
106
+ cache_full
107
+ @full_diff_files = process_full_diff
108
+ end
109
+ end
110
+
111
+ def cache_stats
112
+ unless @stats
113
+ @stats = @base.lib.diff_stats(@from, @to, {:path_limiter => @path})
114
+ end
115
+ end
116
+
117
+ # break up @diff_full
118
+ def process_full_diff
119
+ final = {}
120
+ current_file = nil
121
+ @full_diff.split("\n").each do |line|
122
+ if m = /diff --git a\/(.*?) b\/(.*?)/.match(line)
123
+ current_file = m[1]
124
+ final[current_file] = {:patch => line, :path => current_file,
125
+ :mode => '', :src => '', :dst => '', :type => 'modified'}
126
+ else
127
+ if m = /index (.......)\.\.(.......)( ......)*/.match(line)
128
+ final[current_file][:src] = m[1]
129
+ final[current_file][:dst] = m[2]
130
+ final[current_file][:mode] = m[3].strip if m[3]
131
+ end
132
+ if m = /(.*?) file mode (......)/.match(line)
133
+ final[current_file][:type] = m[1]
134
+ final[current_file][:mode] = m[2]
135
+ end
136
+ if m = /^Binary files /.match(line)
137
+ final[current_file][:binary] = true
138
+ end
139
+ final[current_file][:patch] << "\n" + line
140
+ end
141
+ end
142
+ final.map { |e| [e[0], DiffFile.new(@base, e[1])] }
143
+ end
144
+
145
+ end
146
+ end
data/lib/git/index.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Git
2
+ class Index < Git::Path
3
+
4
+ end
5
+ end
data/lib/git/lib.rb ADDED
@@ -0,0 +1,712 @@
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
+ @logger = opts[:log]
48
+
49
+ arr_opts = []
50
+ arr_opts << "--bare" if opts[:bare]
51
+ arr_opts << "-o" << opts[:remote] if opts[:remote]
52
+ arr_opts << "--depth" << opts[:depth].to_i if opts[:depth] && opts[:depth].to_i > 0
53
+
54
+ arr_opts << '--'
55
+ arr_opts << repository
56
+ arr_opts << clone_dir
57
+
58
+ command('clone', arr_opts)
59
+
60
+ opts[:bare] ? {:repository => clone_dir, :log => opts[:log]} : {:working_directory => clone_dir, :log => opts[:log]}
61
+ end
62
+
63
+
64
+ ## READ COMMANDS ##
65
+
66
+
67
+ def log_commits(opts = {})
68
+ arr_opts = ['--pretty=oneline']
69
+ arr_opts << "-#{opts[:count]}" if opts[:count]
70
+ arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
71
+ arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
72
+ arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
73
+ arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
74
+ arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
75
+ arr_opts << opts[:object] if opts[:object].is_a? String
76
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
77
+
78
+ command_lines('log', arr_opts, true).map { |l| l.split.first }
79
+ end
80
+
81
+ def full_log_commits(opts = {})
82
+ arr_opts = ['--pretty=raw']
83
+ arr_opts << "-#{opts[:count]}" if opts[:count]
84
+ arr_opts << "--skip=#{opts[:skip]}" if opts[:skip]
85
+ arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
86
+ arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
87
+ arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
88
+ arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
89
+ arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
90
+ arr_opts << opts[:object] if opts[:object].is_a? String
91
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
92
+
93
+ full_log = command_lines('log', arr_opts, true)
94
+ process_commit_data(full_log)
95
+ end
96
+
97
+ def revparse(string)
98
+ return string if string =~ /[A-Fa-f0-9]{40}/ # passing in a sha - just no-op it
99
+ rev = ['head', 'remotes', 'tags'].map do |d|
100
+ File.join(@git_dir, 'refs', d, string)
101
+ end.find do |path|
102
+ File.file?(path)
103
+ end
104
+ return File.read(rev).chomp if rev
105
+ command('rev-parse', string)
106
+ end
107
+
108
+ def namerev(string)
109
+ command('name-rev', string).split[1]
110
+ end
111
+
112
+ def object_type(sha)
113
+ command('cat-file', ['-t', sha])
114
+ end
115
+
116
+ def object_size(sha)
117
+ command('cat-file', ['-s', sha]).to_i
118
+ end
119
+
120
+ # returns useful array of raw commit object data
121
+ def commit_data(sha)
122
+ sha = sha.to_s
123
+ cdata = command_lines('cat-file', ['commit', sha])
124
+ process_commit_data(cdata, sha, 0)
125
+ end
126
+
127
+ def process_commit_data(data, sha = nil, indent = 4)
128
+ in_message = false
129
+
130
+ if sha
131
+ hsh = {'sha' => sha, 'message' => '', 'parent' => []}
132
+ else
133
+ hsh_array = []
134
+ end
135
+
136
+ data.each do |line|
137
+ line = line.chomp
138
+ if line == ''
139
+ in_message = !in_message
140
+ elsif in_message
141
+ hsh['message'] << line[indent..-1] << "\n"
142
+ else
143
+ data = line.split
144
+ key = data.shift
145
+ value = data.join(' ')
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
298
+ hsh = {}
299
+ command_lines('ls-files', '--stage').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
+ config_list[name]
326
+ #command('config', ['--get', name])
327
+ end
328
+
329
+ def config_list
330
+ config = {}
331
+ config.merge!(parse_config('~/.gitconfig'))
332
+ config.merge!(parse_config(File.join(@git_dir, 'config')))
333
+ #hsh = {}
334
+ #command_lines('config', ['--list']).each do |line|
335
+ # (key, value) = line.split('=')
336
+ # hsh[key] = value
337
+ #end
338
+ #hsh
339
+ end
340
+
341
+ def parse_config(file)
342
+ hsh = {}
343
+ file = File.expand_path(file)
344
+ if File.file?(file)
345
+ current_section = nil
346
+ File.readlines(file).each do |line|
347
+ if m = /\[(\w+)\]/.match(line)
348
+ current_section = m[1]
349
+ elsif m = /\[(\w+?) "(.*?)"\]/.match(line)
350
+ current_section = "#{m[1]}.#{m[2]}"
351
+ elsif m = /(\w+?) = (.*)/.match(line)
352
+ key = "#{current_section}.#{m[1]}"
353
+ hsh[key] = m[2]
354
+ end
355
+ end
356
+ end
357
+ hsh
358
+ end
359
+
360
+ ## WRITE COMMANDS ##
361
+
362
+ def config_set(name, value)
363
+ command('config', [name, value])
364
+ end
365
+
366
+ def add(path = '.')
367
+ arr_opts = ['--']
368
+ if path.is_a?(Array)
369
+ arr_opts += path
370
+ else
371
+ arr_opts << path
372
+ end
373
+ command('add', arr_opts)
374
+ end
375
+
376
+ def remove(path = '.', opts = {})
377
+ arr_opts = ['-f'] # overrides the up-to-date check by default
378
+ arr_opts << ['-r'] if opts[:recursive]
379
+ arr_opts << '--'
380
+ if path.is_a?(Array)
381
+ arr_opts += path
382
+ else
383
+ arr_opts << path
384
+ end
385
+
386
+ command('rm', arr_opts)
387
+ end
388
+
389
+ def commit(message, opts = {})
390
+ arr_opts = ['-m', message]
391
+ arr_opts << '-a' if opts[:add_all]
392
+ arr_opts << '--allow-empty' if opts[:allow_empty]
393
+ arr_opts << "--author" << opts[:author] if opts[:author]
394
+ command('commit', arr_opts)
395
+ end
396
+
397
+ def reset(commit, opts = {})
398
+ arr_opts = []
399
+ arr_opts << '--hard' if opts[:hard]
400
+ arr_opts << commit if commit
401
+ command('reset', arr_opts)
402
+ end
403
+
404
+ def apply(patch_file)
405
+ arr_opts = []
406
+ arr_opts << '--' << patch_file if patch_file
407
+ command('apply', arr_opts)
408
+ end
409
+
410
+ def apply_mail(patch_file)
411
+ arr_opts = []
412
+ arr_opts << '--' << patch_file if patch_file
413
+ command('am', arr_opts)
414
+ end
415
+
416
+ def stashes_all
417
+ arr = []
418
+ filename = File.join(@git_dir, 'logs/refs/stash')
419
+ if File.exist?(filename)
420
+ File.open(filename).each_with_index { |line, i|
421
+ m = line.match(/:(.*)$/)
422
+ arr << [i, m[1].strip]
423
+ }
424
+ end
425
+ arr
426
+ end
427
+
428
+ def stash_save(message)
429
+ output = command('stash save', ['--', message])
430
+ output =~ /HEAD is now at/
431
+ end
432
+
433
+ def stash_apply(id = nil)
434
+ if id
435
+ command('stash apply', [id])
436
+ else
437
+ command('stash apply')
438
+ end
439
+ end
440
+
441
+ def stash_clear
442
+ command('stash clear')
443
+ end
444
+
445
+ def stash_list
446
+ command('stash list')
447
+ end
448
+
449
+ def branch_new(branch)
450
+ command('branch', branch)
451
+ end
452
+
453
+ def branch_delete(branch)
454
+ command('branch', ['-D', branch])
455
+ end
456
+
457
+ def checkout(branch, opts = {})
458
+ arr_opts = []
459
+ arr_opts << '-f' if opts[:force]
460
+ arr_opts << '-b' << opts[:new_branch] if opts[:new_branch]
461
+ arr_opts << branch
462
+
463
+ command('checkout', arr_opts)
464
+ end
465
+
466
+ def checkout_file(version, file)
467
+ arr_opts = []
468
+ arr_opts << version
469
+ arr_opts << file
470
+ command('checkout', arr_opts)
471
+ end
472
+
473
+ def merge(branch, message = nil)
474
+ arr_opts = []
475
+ arr_opts << '-m' << message if message
476
+ arr_opts += [branch]
477
+ command('merge', arr_opts)
478
+ end
479
+
480
+ def unmerged
481
+ unmerged = []
482
+ command_lines('diff', ["--cached"]).each do |line|
483
+ unmerged << $1 if line =~ /^\* Unmerged path (.*)/
484
+ end
485
+ unmerged
486
+ end
487
+
488
+ def conflicts # :yields: file, your, their
489
+ self.unmerged.each do |f|
490
+ your = Tempfile.new("YOUR-#{File.basename(f)}").path
491
+ command('show', ":2:#{f}", true, "> #{escape your}")
492
+
493
+ their = Tempfile.new("THEIR-#{File.basename(f)}").path
494
+ command('show', ":3:#{f}", true, "> #{escape their}")
495
+ yield(f, your, their)
496
+ end
497
+ end
498
+
499
+ def remote_add(name, url, opts = {})
500
+ arr_opts = ['add']
501
+ arr_opts << '-f' if opts[:with_fetch]
502
+ arr_opts << '--'
503
+ arr_opts << name
504
+ arr_opts << url
505
+
506
+ command('remote', arr_opts)
507
+ end
508
+
509
+ # this is documented as such, but seems broken for some reason
510
+ # i'll try to get around it some other way later
511
+ def remote_remove(name)
512
+ command('remote', ['rm', '--', name])
513
+ end
514
+
515
+ def remotes
516
+ command_lines('remote')
517
+ end
518
+
519
+ def tags
520
+ command_lines('tag')
521
+ end
522
+
523
+ def tag(tag)
524
+ command('tag', tag)
525
+ end
526
+
527
+
528
+ def fetch(remote)
529
+ command('fetch', remote)
530
+ end
531
+
532
+ def push(remote, branch = 'master', tags = false)
533
+ command('push', [remote, branch])
534
+ command('push', ['--tags', remote]) if tags
535
+ end
536
+
537
+ def tag_sha(tag_name)
538
+ head = File.join(@git_dir, 'refs', 'tags', tag_name)
539
+ return File.read(head).chomp if File.exists?(head)
540
+
541
+ command('show-ref', ['--tags', '-s', tag_name])
542
+ end
543
+
544
+ def repack
545
+ command('repack', ['-a', '-d'])
546
+ end
547
+
548
+ def gc
549
+ command('gc', ['--prune', '--aggressive', '--auto'])
550
+ end
551
+
552
+ # reads a tree into the current index file
553
+ def read_tree(treeish, opts = {})
554
+ arr_opts = []
555
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
556
+ arr_opts += [treeish]
557
+ command('read-tree', arr_opts)
558
+ end
559
+
560
+ def write_tree
561
+ command('write-tree')
562
+ end
563
+
564
+ def commit_tree(tree, opts = {})
565
+ opts[:message] ||= "commit tree #{tree}"
566
+ t = Tempfile.new('commit-message')
567
+ t.write(opts[:message])
568
+ t.close
569
+
570
+ arr_opts = []
571
+ arr_opts << tree
572
+ arr_opts << '-p' << opts[:parent] if opts[:parent]
573
+ arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
574
+ command('commit-tree', arr_opts, true, "< #{escape t.path}")
575
+ end
576
+
577
+ def update_ref(branch, commit)
578
+ command('update-ref', [branch, commit])
579
+ end
580
+
581
+ def checkout_index(opts = {})
582
+ arr_opts = []
583
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
584
+ arr_opts << "--force" if opts[:force]
585
+ arr_opts << "--all" if opts[:all]
586
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
587
+
588
+ command('checkout-index', arr_opts)
589
+ end
590
+
591
+ # creates an archive file
592
+ #
593
+ # options
594
+ # :format (zip, tar)
595
+ # :prefix
596
+ # :remote
597
+ # :path
598
+ def archive(sha, file = nil, opts = {})
599
+ opts[:format] ||= 'zip'
600
+
601
+ if opts[:format] == 'tgz'
602
+ opts[:format] = 'tar'
603
+ opts[:add_gzip] = true
604
+ end
605
+
606
+ file ||= Tempfile.new('archive').path
607
+
608
+ arr_opts = []
609
+ arr_opts << "--format=#{opts[:format]}" if opts[:format]
610
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
611
+ arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
612
+ arr_opts << sha
613
+ arr_opts << '--' << opts[:path] if opts[:path]
614
+ command('archive', arr_opts, true, (opts[:add_gzip] ? '| gzip' : '') + " > #{escape file}")
615
+ return file
616
+ end
617
+
618
+
619
+ def submodule_status(path = nil, opts = {})
620
+ arr_opts = []
621
+ arr_opts << '--quiet' if opts[:quiet]
622
+ arr_opts << '--cached' if opts[:cached]
623
+ arr_opts << path if path
624
+ submodule_command('status', arr_opts)
625
+ end
626
+
627
+ def submodule_add(repository, opts = {})
628
+ arr_opts = []
629
+ arr_opts << '--quiet' if opts[:quiet]
630
+ arr_opts << "-b #{opts[:branch]}" if opts[:branch]
631
+ arr_opts << '--'
632
+ arr_opts << repository
633
+ arr_opts << opts[:path] if opts[:path]
634
+ submodule_command('add', arr_opts)
635
+ end
636
+
637
+ def submodule_init(path, opts = {})
638
+ arr_opts = []
639
+ arr_opts << '--quiet' if opts[:quiet]
640
+ arr_opts << '--'
641
+ arr_opts << path
642
+ submodule_command('init', arr_opts)
643
+ end
644
+
645
+ def submodule_update(path, opts = {})
646
+ arr_opts = []
647
+ arr_opts << '--quiet' if opts[:quiet]
648
+ arr_opts << '--'
649
+ arr_opts << path
650
+ submodule_command('update', arr_opts)
651
+ end
652
+
653
+ private
654
+
655
+ def command_lines(cmd, opts = [], chdir = true, redirect = '')
656
+ command(cmd, opts, chdir).split("\n")
657
+ end
658
+
659
+ def command(cmd, opts = [], chdir = true, redirect = '', &block)
660
+ if cmd == 'submodule' && (opts.first == 'update' || opts.first == 'add')
661
+ ENV['GIT_DIR'] = @git_dir
662
+ ENV['GIT_INDEX_FILE'] = nil
663
+ ENV['GIT_WORK_TREE'] = nil
664
+ else
665
+ ENV['GIT_DIR'] = @git_dir
666
+ ENV['GIT_INDEX_FILE'] = @git_index_file
667
+ ENV['GIT_WORK_TREE'] = @git_work_dir
668
+ end
669
+ path = @git_work_dir || @git_dir || @path
670
+
671
+ opts = [opts].flatten.map {|s| escape(s) }.join(' ')
672
+ git_cmd = "git #{cmd} #{opts} #{redirect} 2>&1"
673
+
674
+ out = nil
675
+ if chdir && (Dir.getwd != path)
676
+ Dir.chdir(path) { out = run_command(git_cmd, &block) }
677
+ else
678
+ out = run_command(git_cmd, &block)
679
+ end
680
+
681
+ if @logger
682
+ @logger.info(git_cmd)
683
+ @logger.debug(out)
684
+ end
685
+
686
+ if $?.exitstatus > 0
687
+ if $?.exitstatus == 1 && out == ''
688
+ return ''
689
+ end
690
+ raise Git::GitExecuteError.new(git_cmd + ':' + out.to_s)
691
+ end
692
+ out
693
+ end
694
+
695
+ def submodule_command(cmd, opts)
696
+ command('submodule', [cmd].concat(opts))
697
+ end
698
+
699
+ def run_command(git_cmd, &block)
700
+ if block_given?
701
+ IO.popen(git_cmd, &block)
702
+ else
703
+ `#{git_cmd}`.chomp
704
+ end
705
+ end
706
+
707
+ def escape(s)
708
+ "'" + s.to_s.gsub('\'', '\'\\\'\'') + "'"
709
+ end
710
+
711
+ end
712
+ end