peterwald-git 1.1.2

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,660 @@
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
297
+ hsh = {}
298
+ command_lines('ls-files', '--stage').each do |line|
299
+ (info, file) = line.split("\t")
300
+ (mode, sha, stage) = info.split
301
+ hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
302
+ end
303
+ hsh
304
+ end
305
+
306
+
307
+ def config_remote(name)
308
+ hsh = {}
309
+ config_list.each do |key, value|
310
+ if /remote.#{name}/.match(key)
311
+ hsh[key.gsub("remote.#{name}.", '')] = value
312
+ end
313
+ end
314
+ hsh
315
+ end
316
+
317
+ def config_get(name)
318
+ config_list[name]
319
+ #command('config', ['--get', name])
320
+ end
321
+
322
+ def config_list
323
+ config = {}
324
+ config.merge!(parse_config('~/.gitconfig'))
325
+ config.merge!(parse_config(File.join(@git_dir, 'config')))
326
+ #hsh = {}
327
+ #command_lines('config', ['--list']).each do |line|
328
+ # (key, value) = line.split('=')
329
+ # hsh[key] = value
330
+ #end
331
+ #hsh
332
+ end
333
+
334
+ def parse_config(file)
335
+ hsh = {}
336
+ file = File.expand_path(file)
337
+ if File.file?(file)
338
+ current_section = nil
339
+ File.readlines(file).each do |line|
340
+ if m = /\[(\w+)\]/.match(line)
341
+ current_section = m[1]
342
+ elsif m = /\[(\w+?) "(.*?)"\]/.match(line)
343
+ current_section = "#{m[1]}.#{m[2]}"
344
+ elsif m = /(\w+?) = (.*)/.match(line)
345
+ key = "#{current_section}.#{m[1]}"
346
+ hsh[key] = m[2]
347
+ end
348
+ end
349
+ end
350
+ hsh
351
+ end
352
+
353
+ ## WRITE COMMANDS ##
354
+
355
+ def config_set(name, value)
356
+ command('config', [name, value])
357
+ end
358
+
359
+ def add(path = '.')
360
+ arr_opts = ['--']
361
+ if path.is_a?(Array)
362
+ arr_opts += path
363
+ else
364
+ arr_opts << path
365
+ end
366
+ command('add', arr_opts)
367
+ end
368
+
369
+ def remove(path = '.', opts = {})
370
+ arr_opts = ['-f'] # overrides the up-to-date check by default
371
+ arr_opts << ['-r'] if opts[:recursive]
372
+ arr_opts << '--'
373
+ if path.is_a?(Array)
374
+ arr_opts += path
375
+ else
376
+ arr_opts << path
377
+ end
378
+
379
+ command('rm', arr_opts)
380
+ end
381
+
382
+ def commit(message, opts = {})
383
+ arr_opts = ['-m', message]
384
+ arr_opts << '-a' if opts[:add_all]
385
+ arr_opts << '--allow-empty' if opts[:allow_empty]
386
+ arr_opts << "--author" << opts[:author] if opts[:author]
387
+ command('commit', arr_opts)
388
+ end
389
+
390
+ def reset(commit, opts = {})
391
+ arr_opts = []
392
+ arr_opts << '--hard' if opts[:hard]
393
+ arr_opts << commit if commit
394
+ command('reset', arr_opts)
395
+ end
396
+
397
+ def apply(patch_file)
398
+ arr_opts = []
399
+ arr_opts << '--' << patch_file if patch_file
400
+ command('apply', arr_opts)
401
+ end
402
+
403
+ def apply_mail(patch_file)
404
+ arr_opts = []
405
+ arr_opts << '--' << patch_file if patch_file
406
+ command('am', arr_opts)
407
+ end
408
+
409
+ def stashes_all
410
+ arr = []
411
+ filename = File.join(@git_dir, 'logs/refs/stash')
412
+ if File.exist?(filename)
413
+ File.open(filename).each_with_index { |line, i|
414
+ m = line.match(/:(.*)$/)
415
+ arr << [i, m[1].strip]
416
+ }
417
+ end
418
+ arr
419
+ end
420
+
421
+ def stash_save(message)
422
+ output = command('stash save', ['--', message])
423
+ output =~ /HEAD is now at/
424
+ end
425
+
426
+ def stash_apply(id = nil)
427
+ if id
428
+ command('stash apply', [id])
429
+ else
430
+ command('stash apply')
431
+ end
432
+ end
433
+
434
+ def stash_clear
435
+ command('stash clear')
436
+ end
437
+
438
+ def stash_list
439
+ command('stash list')
440
+ end
441
+
442
+ def branch_new(branch)
443
+ command('branch', branch)
444
+ end
445
+
446
+ def branch_delete(branch)
447
+ command('branch', ['-D', branch])
448
+ end
449
+
450
+ def checkout(branch, opts = {})
451
+ arr_opts = []
452
+ arr_opts << '-f' if opts[:force]
453
+ arr_opts << '-b' << opts[:new_branch] if opts[:new_branch]
454
+ arr_opts << branch
455
+
456
+ command('checkout', arr_opts)
457
+ end
458
+
459
+ def checkout_file(version, file)
460
+ arr_opts = []
461
+ arr_opts << version
462
+ arr_opts << file
463
+ command('checkout', arr_opts)
464
+ end
465
+
466
+ def merge(branch, message = nil)
467
+ arr_opts = []
468
+ arr_opts << '-m' << message if message
469
+ arr_opts += [branch]
470
+ command('merge', arr_opts)
471
+ end
472
+
473
+ def unmerged
474
+ unmerged = []
475
+ command_lines('diff', ["--cached"]).each do |line|
476
+ unmerged << $1 if line =~ /^\* Unmerged path (.*)/
477
+ end
478
+ unmerged
479
+ end
480
+
481
+ def conflicts # :yields: file, your, their
482
+ self.unmerged.each do |f|
483
+ your = Tempfile.new("YOUR-#{File.basename(f)}").path
484
+ command('show', ":2:#{f}", true, "> #{escape your}")
485
+
486
+ their = Tempfile.new("THEIR-#{File.basename(f)}").path
487
+ command('show', ":3:#{f}", true, "> #{escape their}")
488
+ yield(f, your, their)
489
+ end
490
+ end
491
+
492
+ def remote_add(name, url, opts = {})
493
+ arr_opts = ['add']
494
+ arr_opts << '-f' if opts[:with_fetch]
495
+ arr_opts << '--'
496
+ arr_opts << name
497
+ arr_opts << url
498
+
499
+ command('remote', arr_opts)
500
+ end
501
+
502
+ # this is documented as such, but seems broken for some reason
503
+ # i'll try to get around it some other way later
504
+ def remote_remove(name)
505
+ command('remote', ['rm', '--', name])
506
+ end
507
+
508
+ def remotes
509
+ command_lines('remote')
510
+ end
511
+
512
+ def tags
513
+ command_lines('tag')
514
+ end
515
+
516
+ def tag(tag)
517
+ command('tag', tag)
518
+ end
519
+
520
+
521
+ def fetch(remote)
522
+ command('fetch', remote)
523
+ end
524
+
525
+ def push(remote, branch = 'master', tags = false)
526
+ command('push', [remote, branch])
527
+ command('push', ['--tags', remote]) if tags
528
+ end
529
+
530
+ def tag_sha(tag_name)
531
+ head = File.join(@git_dir, 'refs', 'tags', tag_name)
532
+ return File.read(head).chomp if File.exists?(head)
533
+
534
+ command('show-ref', ['--tags', '-s', tag_name])
535
+ end
536
+
537
+ def repack
538
+ command('repack', ['-a', '-d'])
539
+ end
540
+
541
+ def gc
542
+ command('gc', ['--prune', '--aggressive', '--auto'])
543
+ end
544
+
545
+ # reads a tree into the current index file
546
+ def read_tree(treeish, opts = {})
547
+ arr_opts = []
548
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
549
+ arr_opts += [treeish]
550
+ command('read-tree', arr_opts)
551
+ end
552
+
553
+ def write_tree
554
+ command('write-tree')
555
+ end
556
+
557
+ def commit_tree(tree, opts = {})
558
+ opts[:message] ||= "commit tree #{tree}"
559
+ t = Tempfile.new('commit-message')
560
+ t.write(opts[:message])
561
+ t.close
562
+
563
+ arr_opts = []
564
+ arr_opts << tree
565
+ arr_opts << '-p' << opts[:parent] if opts[:parent]
566
+ arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
567
+ command('commit-tree', arr_opts, true, "< #{escape t.path}")
568
+ end
569
+
570
+ def update_ref(branch, commit)
571
+ command('update-ref', [branch, commit])
572
+ end
573
+
574
+ def checkout_index(opts = {})
575
+ arr_opts = []
576
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
577
+ arr_opts << "--force" if opts[:force]
578
+ arr_opts << "--all" if opts[:all]
579
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
580
+
581
+ command('checkout-index', arr_opts)
582
+ end
583
+
584
+ # creates an archive file
585
+ #
586
+ # options
587
+ # :format (zip, tar)
588
+ # :prefix
589
+ # :remote
590
+ # :path
591
+ def archive(sha, file = nil, opts = {})
592
+ opts[:format] ||= 'zip'
593
+
594
+ if opts[:format] == 'tgz'
595
+ opts[:format] = 'tar'
596
+ opts[:add_gzip] = true
597
+ end
598
+
599
+ file ||= Tempfile.new('archive').path
600
+
601
+ arr_opts = []
602
+ arr_opts << "--format=#{opts[:format]}" if opts[:format]
603
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
604
+ arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
605
+ arr_opts << sha
606
+ arr_opts << '--' << opts[:path] if opts[:path]
607
+ command('archive', arr_opts, true, (opts[:add_gzip] ? '| gzip' : '') + " > #{escape file}")
608
+ return file
609
+ end
610
+
611
+ private
612
+
613
+ def command_lines(cmd, opts = [], chdir = true, redirect = '')
614
+ command(cmd, opts, chdir).split("\n")
615
+ end
616
+
617
+ def command(cmd, opts = [], chdir = true, redirect = '', &block)
618
+ ENV['GIT_DIR'] = @git_dir
619
+ ENV['GIT_INDEX_FILE'] = @git_index_file
620
+ ENV['GIT_WORK_TREE'] = @git_work_dir
621
+ path = @git_work_dir || @git_dir || @path
622
+
623
+ opts = [opts].flatten.map {|s| escape(s) }.join(' ')
624
+ git_cmd = "git #{cmd} #{opts} #{redirect} 2>&1"
625
+
626
+ out = nil
627
+ if chdir && (Dir.getwd != path)
628
+ Dir.chdir(path) { out = run_command(git_cmd, &block) }
629
+ else
630
+ out = run_command(git_cmd, &block)
631
+ end
632
+
633
+ if @logger
634
+ @logger.info(git_cmd)
635
+ @logger.debug(out)
636
+ end
637
+
638
+ if $?.exitstatus > 0
639
+ if $?.exitstatus == 1 && out == ''
640
+ return ''
641
+ end
642
+ raise Git::GitExecuteError.new(git_cmd + ':' + out.to_s)
643
+ end
644
+ out
645
+ end
646
+
647
+ def run_command(git_cmd, &block)
648
+ if block_given?
649
+ IO.popen(git_cmd, &block)
650
+ else
651
+ `#{git_cmd}`.chomp
652
+ end
653
+ end
654
+
655
+ def escape(s)
656
+ "'" + s.to_s.gsub('\'', '\'\\\'\'') + "'"
657
+ end
658
+
659
+ end
660
+ end