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