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