clc-git 1.2.8

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.
@@ -0,0 +1,122 @@
1
+ require 'git/path'
2
+
3
+ module Git
4
+
5
+ class Branch < Path
6
+
7
+ attr_accessor :full, :remote, :name
8
+
9
+ def initialize(base, name)
10
+ @full = name
11
+ @base = base
12
+ @gcommit = nil
13
+ @stashes = nil
14
+ @remote, @name = parse_name(name)
15
+ end
16
+
17
+ def gcommit
18
+ @gcommit ||= @base.gcommit(@full)
19
+ @gcommit
20
+ end
21
+
22
+ def stashes
23
+ @stashes ||= Git::Stashes.new(@base)
24
+ end
25
+
26
+ def checkout
27
+ check_if_create
28
+ @base.checkout(@full)
29
+ end
30
+
31
+ def archive(file, opts = {})
32
+ @base.lib.archive(@full, file, opts)
33
+ end
34
+
35
+ # g.branch('new_branch').in_branch do
36
+ # # create new file
37
+ # # do other stuff
38
+ # return true # auto commits and switches back
39
+ # end
40
+ def in_branch (message = 'in branch work')
41
+ old_current = @base.lib.branch_current
42
+ checkout
43
+ if yield
44
+ @base.commit_all(message)
45
+ else
46
+ @base.reset_hard
47
+ end
48
+ @base.checkout(old_current)
49
+ end
50
+
51
+ def create
52
+ check_if_create
53
+ end
54
+
55
+ def delete
56
+ @base.lib.branch_delete(@name)
57
+ end
58
+
59
+ def current
60
+ determine_current
61
+ end
62
+
63
+ def merge(branch = nil, message = nil)
64
+ if branch
65
+ in_branch do
66
+ @base.merge(branch, message)
67
+ false
68
+ end
69
+ # merge a branch into this one
70
+ else
71
+ # merge this branch into the current one
72
+ @base.merge(@name)
73
+ end
74
+ end
75
+
76
+ def update_ref(commit)
77
+ @base.lib.update_ref(@full, commit)
78
+ end
79
+
80
+ def to_a
81
+ [@full]
82
+ end
83
+
84
+ def to_s
85
+ @full
86
+ end
87
+
88
+ private
89
+
90
+ def check_if_create
91
+ @base.lib.branch_new(@name) rescue nil
92
+ end
93
+
94
+ def determine_current
95
+ @base.lib.branch_current == @name
96
+ end
97
+
98
+ # Given a full branch name return an Array containing the remote and branch names.
99
+ #
100
+ # Removes 'remotes' from the beggining of the name (if present).
101
+ # Takes the second part (splittign by '/') as the remote name.
102
+ # Takes the rest as the repo name (can also hold one or more '/').
103
+ #
104
+ # Example:
105
+ # parse_name('master') #=> [nil, 'master']
106
+ # parse_name('origin/master') #=> ['origin', 'master']
107
+ # parse_name('remotes/origin/master') #=> ['origin', 'master']
108
+ # parse_name('origin/master/v2') #=> ['origin', 'master/v2']
109
+ #
110
+ # param [String] name branch full name.
111
+ # return [<Git::Remote,NilClass,String>] an Array containing the remote and branch names.
112
+ def parse_name(name)
113
+ if name.match(/^(?:remotes)?\/([^\/]+)\/(.+)/)
114
+ return [Git::Remote.new(@base, $1), $2]
115
+ end
116
+
117
+ return [nil, name]
118
+ end
119
+
120
+ end
121
+
122
+ end
@@ -0,0 +1,71 @@
1
+ module Git
2
+
3
+ # object that holds all the available branches
4
+ class Branches
5
+
6
+ include Enumerable
7
+
8
+ def initialize(base)
9
+ @branches = {}
10
+
11
+ @base = base
12
+
13
+ @base.lib.branches_all.each do |b|
14
+ @branches[b[0]] = Git::Branch.new(@base, b[0])
15
+ end
16
+ end
17
+
18
+ def local
19
+ self.select { |b| !b.remote }
20
+ end
21
+
22
+ def remote
23
+ self.select { |b| b.remote }
24
+ end
25
+
26
+ # array like methods
27
+
28
+ def size
29
+ @branches.size
30
+ end
31
+
32
+ def each(&block)
33
+ @branches.values.each(&block)
34
+ end
35
+
36
+ # Returns the target branch
37
+ #
38
+ # Example:
39
+ # Given (git branch -a):
40
+ # master
41
+ # remotes/working/master
42
+ #
43
+ # g.branches['master'].full #=> 'master'
44
+ # g.branches['working/master'].full => 'remotes/working/master'
45
+ # g.branches['remotes/working/master'].full => 'remotes/working/master'
46
+ #
47
+ # @param [#to_s] branch_name the target branch name.
48
+ # @return [Git::Branch] the target branch.
49
+ def [](branch_name)
50
+ @branches.values.inject(@branches) do |branches, branch|
51
+ branches[branch.full] ||= branch
52
+
53
+ # This is how Git (version 1.7.9.5) works.
54
+ # Lets you ignore the 'remotes' if its at the beginning of the branch full name (even if is not a real remote branch).
55
+ branches[branch.full.sub('remotes/', '')] ||= branch if branch.full =~ /^remotes\/.+/
56
+
57
+ branches
58
+ end[branch_name.to_s]
59
+ end
60
+
61
+ def to_s
62
+ out = ''
63
+ @branches.each do |k, b|
64
+ out << (b.current ? '* ' : ' ') << b.to_s << "\n"
65
+ end
66
+ out
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ module Git
2
+ class Index < Git::Path
3
+
4
+ end
5
+ end
@@ -0,0 +1,844 @@
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
+ # creates or reinitializes the repository
29
+ #
30
+ # options:
31
+ # :bare
32
+ # :working_directory
33
+ #
34
+ def init(opts={})
35
+ arr_opts = []
36
+ arr_opts << '--bare' if opts[:bare]
37
+
38
+ command('init', arr_opts, false)
39
+ end
40
+
41
+ # tries to clone the given repo
42
+ #
43
+ # returns {:repository} (if bare)
44
+ # {:working_directory} otherwise
45
+ #
46
+ # accepts options:
47
+ # :remote:: name of remote (rather than 'origin')
48
+ # :bare:: no working directory
49
+ # :recursive:: after the clone is created, initialize all submodules within, using their default settings.
50
+ # :depth:: the number of commits back to pull
51
+ #
52
+ # TODO - make this work with SSH password or auth_key
53
+ #
54
+ def clone(repository, name, opts = {})
55
+ @path = opts[:path] || '.'
56
+ clone_dir = opts[:path] ? File.join(@path, name) : name
57
+
58
+ arr_opts = []
59
+ arr_opts << "--bare" if opts[:bare]
60
+ arr_opts << "--recursive" if opts[:recursive]
61
+ arr_opts << "-o" << opts[:remote] if opts[:remote]
62
+ arr_opts << "--depth" << opts[:depth].to_i if opts[:depth] && opts[:depth].to_i > 0
63
+ arr_opts << "--config" << opts[:config] if opts[:config]
64
+
65
+ arr_opts << '--'
66
+ arr_opts << repository
67
+ arr_opts << clone_dir
68
+
69
+ command('clone', arr_opts)
70
+
71
+ opts[:bare] ? {:repository => clone_dir} : {:working_directory => clone_dir}
72
+ end
73
+
74
+
75
+ ## READ COMMANDS ##
76
+
77
+ def log_commits(opts={})
78
+ arr_opts = log_common_options(opts)
79
+
80
+ arr_opts << '--pretty=oneline'
81
+
82
+ arr_opts += log_path_options(opts)
83
+
84
+ command_lines('log', arr_opts, true).map { |l| l.split.first }
85
+ end
86
+
87
+ def full_log_commits(opts={})
88
+ arr_opts = log_common_options(opts)
89
+
90
+ arr_opts << '--pretty=raw'
91
+ arr_opts << "--skip=#{opts[:skip]}" if opts[:skip]
92
+
93
+ arr_opts += log_path_options(opts)
94
+
95
+ full_log = command_lines('log', arr_opts, true)
96
+
97
+ process_commit_log_data(full_log)
98
+ end
99
+
100
+ def revparse(string)
101
+ return string if string =~ /^[A-Fa-f0-9]{40}$/ # passing in a sha - just no-op it
102
+ rev = ['head', 'remotes', 'tags'].map do |d|
103
+ File.join(@git_dir, 'refs', d, string)
104
+ end.find do |path|
105
+ File.file?(path)
106
+ end
107
+ return File.read(rev).chomp if rev
108
+ command('rev-parse', string)
109
+ end
110
+
111
+ def namerev(string)
112
+ command('name-rev', string).split[1]
113
+ end
114
+
115
+ def object_type(sha)
116
+ command('cat-file', ['-t', sha])
117
+ end
118
+
119
+ def object_size(sha)
120
+ command('cat-file', ['-s', sha]).to_i
121
+ end
122
+
123
+ # returns useful array of raw commit object data
124
+ def commit_data(sha)
125
+ sha = sha.to_s
126
+ cdata = command_lines('cat-file', ['commit', sha])
127
+ process_commit_data(cdata, sha, 0)
128
+ end
129
+
130
+ def process_commit_data(data, sha = nil, indent = 4)
131
+ hsh = {
132
+ 'sha' => sha,
133
+ 'message' => '',
134
+ 'parent' => []
135
+ }
136
+
137
+ loop do
138
+ key, *value = data.shift.split
139
+
140
+ break if key.nil?
141
+
142
+ if key == 'parent'
143
+ hsh['parent'] << value.join(' ')
144
+ else
145
+ hsh[key] = value.join(' ')
146
+ end
147
+ end
148
+
149
+ hsh['message'] = data.collect {|line| line[indent..-1]}.join("\n") + "\n"
150
+
151
+ return hsh
152
+ end
153
+
154
+ def process_commit_log_data(data)
155
+ in_message = false
156
+
157
+ hsh_array = []
158
+
159
+ hsh = nil
160
+
161
+ data.each do |line|
162
+ line = line.chomp
163
+
164
+ if line[0].nil?
165
+ in_message = !in_message
166
+ next
167
+ end
168
+
169
+ if in_message
170
+ hsh['message'] << "#{line[4..-1]}\n"
171
+ next
172
+ end
173
+
174
+ key, *value = line.split
175
+ value = value.join(' ')
176
+
177
+ case key
178
+ when 'commit'
179
+ hsh_array << hsh if hsh
180
+ hsh = {'sha' => value, 'message' => '', 'parent' => []}
181
+ when 'parent'
182
+ hsh['parent'] << value
183
+ else
184
+ hsh[key] = value
185
+ end
186
+ end
187
+
188
+ hsh_array << hsh if hsh
189
+
190
+ return hsh_array
191
+ end
192
+
193
+ def object_contents(sha, &block)
194
+ command('cat-file', ['-p', sha], &block)
195
+ end
196
+
197
+ def ls_tree(sha)
198
+ data = {'blob' => {}, 'tree' => {}}
199
+
200
+ command_lines('ls-tree', sha).each do |line|
201
+ (info, filenm) = line.split("\t")
202
+ (mode, type, sha) = info.split
203
+ data[type][filenm] = {:mode => mode, :sha => sha}
204
+ end
205
+
206
+ data
207
+ end
208
+
209
+ def mv(file1, file2)
210
+ command_lines('mv', ['--', file1, file2])
211
+ end
212
+
213
+ def full_tree(sha)
214
+ command_lines('ls-tree', ['-r', sha])
215
+ end
216
+
217
+ def tree_depth(sha)
218
+ full_tree(sha).size
219
+ end
220
+
221
+ def change_head_branch(branch_name)
222
+ command('symbolic-ref', ['HEAD', "refs/heads/#{branch_name}"])
223
+ end
224
+
225
+ def branches_all
226
+ arr = []
227
+ command_lines('branch', '-a').each do |b|
228
+ current = (b[0, 2] == '* ')
229
+ arr << [b.gsub('* ', '').strip, current]
230
+ end
231
+ arr
232
+ end
233
+
234
+ def list_files(ref_dir)
235
+ dir = File.join(@git_dir, 'refs', ref_dir)
236
+ files = []
237
+ Dir.chdir(dir) { files = Dir.glob('**/*').select { |f| File.file?(f) } } rescue nil
238
+ files
239
+ end
240
+
241
+ def branch_current
242
+ branches_all.select { |b| b[1] }.first[0] rescue nil
243
+ end
244
+
245
+
246
+ # returns hash
247
+ # [tree-ish] = [[line_no, match], [line_no, match2]]
248
+ # [tree-ish] = [[line_no, match], [line_no, match2]]
249
+ def grep(string, opts = {})
250
+ opts[:object] ||= 'HEAD'
251
+
252
+ grep_opts = ['-n']
253
+ grep_opts << '-i' if opts[:ignore_case]
254
+ grep_opts << '-v' if opts[:invert_match]
255
+ grep_opts << '-e'
256
+ grep_opts << string
257
+ grep_opts << opts[:object] if opts[:object].is_a?(String)
258
+ grep_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
259
+
260
+ hsh = {}
261
+ command_lines('grep', grep_opts).each do |line|
262
+ if m = /(.*)\:(\d+)\:(.*)/.match(line)
263
+ hsh[m[1]] ||= []
264
+ hsh[m[1]] << [m[2].to_i, m[3]]
265
+ end
266
+ end
267
+ hsh
268
+ end
269
+
270
+ def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
271
+ diff_opts = ['-p']
272
+ diff_opts << obj1
273
+ diff_opts << obj2 if obj2.is_a?(String)
274
+ diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
275
+
276
+ command('diff', diff_opts)
277
+ end
278
+
279
+ def diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {})
280
+ diff_opts = ['--numstat']
281
+ diff_opts << obj1
282
+ diff_opts << obj2 if obj2.is_a?(String)
283
+ diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
284
+
285
+ hsh = {:total => {:insertions => 0, :deletions => 0, :lines => 0, :files => 0}, :files => {}}
286
+
287
+ command_lines('diff', diff_opts).each do |file|
288
+ (insertions, deletions, filename) = file.split("\t")
289
+ hsh[:total][:insertions] += insertions.to_i
290
+ hsh[:total][:deletions] += deletions.to_i
291
+ hsh[:total][:lines] = (hsh[:total][:deletions] + hsh[:total][:insertions])
292
+ hsh[:total][:files] += 1
293
+ hsh[:files][filename] = {:insertions => insertions.to_i, :deletions => deletions.to_i}
294
+ end
295
+
296
+ hsh
297
+ end
298
+
299
+ # compares the index and the working directory
300
+ def diff_files
301
+ diff_as_hash('diff-files')
302
+ end
303
+
304
+ # compares the index and the repository
305
+ def diff_index(treeish)
306
+ diff_as_hash('diff-index', treeish)
307
+ end
308
+
309
+ def ls_files(location=nil)
310
+ hsh = {}
311
+ command_lines('ls-files', ['--stage', location]).each do |line|
312
+ (info, file) = line.split("\t")
313
+ (mode, sha, stage) = info.split
314
+ file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
315
+ hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
316
+ end
317
+ hsh
318
+ end
319
+
320
+
321
+ def ignored_files
322
+ command_lines('ls-files', ['--others', '-i', '--exclude-standard'])
323
+ end
324
+
325
+
326
+ def config_remote(name)
327
+ hsh = {}
328
+ config_list.each do |key, value|
329
+ if /remote.#{name}/.match(key)
330
+ hsh[key.gsub("remote.#{name}.", '')] = value
331
+ end
332
+ end
333
+ hsh
334
+ end
335
+
336
+ def config_get(name)
337
+ do_get = lambda do |path|
338
+ command('config', ['--get', name])
339
+ end
340
+
341
+ if @git_dir
342
+ Dir.chdir(@git_dir, &do_get)
343
+ else
344
+ build_list.call
345
+ end
346
+ end
347
+
348
+ def global_config_get(name)
349
+ command('config', ['--global', '--get', name], false)
350
+ end
351
+
352
+ def config_list
353
+ build_list = lambda do |path|
354
+ parse_config_list command_lines('config', ['--list'])
355
+ end
356
+
357
+ if @git_dir
358
+ Dir.chdir(@git_dir, &build_list)
359
+ else
360
+ build_list.call
361
+ end
362
+ end
363
+
364
+ def global_config_list
365
+ parse_config_list command_lines('config', ['--global', '--list'], false)
366
+ end
367
+
368
+ def parse_config_list(lines)
369
+ hsh = {}
370
+ lines.each do |line|
371
+ (key, *values) = line.split('=')
372
+ hsh[key] = values.join('=')
373
+ end
374
+ hsh
375
+ end
376
+
377
+ def parse_config(file)
378
+ parse_config_list command_lines('config', ['--list', '--file', file], false)
379
+ end
380
+
381
+ ## WRITE COMMANDS ##
382
+
383
+ def config_set(name, value)
384
+ command('config', [name, value])
385
+ end
386
+
387
+ def global_config_set(name, value)
388
+ command('config', ['--global', name, value], false)
389
+ end
390
+
391
+ # updates the repository index using the workig dorectory content
392
+ #
393
+ # lib.add('path/to/file')
394
+ # lib.add(['path/to/file1','path/to/file2'])
395
+ # lib.add(:all => true)
396
+ #
397
+ # options:
398
+ # :all => true
399
+ # :force => true
400
+ #
401
+ # @param [String,Array] paths files paths to be added to the repository
402
+ # @param [Hash] options
403
+ def add(paths='.',options={})
404
+ arr_opts = []
405
+
406
+ arr_opts << '--all' if options[:all]
407
+ arr_opts << '--force' if options[:force]
408
+
409
+ arr_opts << '--'
410
+
411
+ arr_opts << paths
412
+
413
+ arr_opts.flatten!
414
+
415
+ command('add', arr_opts)
416
+ end
417
+
418
+ def remove(path = '.', opts = {})
419
+ arr_opts = ['-f'] # overrides the up-to-date check by default
420
+ arr_opts << ['-r'] if opts[:recursive]
421
+ arr_opts << '--'
422
+ if path.is_a?(Array)
423
+ arr_opts += path
424
+ else
425
+ arr_opts << path
426
+ end
427
+
428
+ command('rm', arr_opts)
429
+ end
430
+
431
+ def commit(message, opts = {})
432
+ arr_opts = []
433
+ arr_opts << "--message=#{message}" if message
434
+ arr_opts << '--amend' << '--no-edit' if opts[:amend]
435
+ arr_opts << '--all' if opts[:add_all] || opts[:all]
436
+ arr_opts << '--allow-empty' if opts[:allow_empty]
437
+ arr_opts << "--author=#{opts[:author]}" if opts[:author]
438
+
439
+ command('commit', arr_opts)
440
+ end
441
+
442
+ def reset(commit, opts = {})
443
+ arr_opts = []
444
+ arr_opts << '--hard' if opts[:hard]
445
+ arr_opts << commit if commit
446
+ command('reset', arr_opts)
447
+ end
448
+
449
+ def clean(opts = {})
450
+ arr_opts = []
451
+ arr_opts << '--force' if opts[:force]
452
+ arr_opts << '-d' if opts[:d]
453
+ arr_opts << '-x' if opts[:x]
454
+
455
+ command('clean', arr_opts)
456
+ end
457
+
458
+ def revert(commitish, opts = {})
459
+ # Forcing --no-edit as default since it's not an interactive session.
460
+ opts = {:no_edit => true}.merge(opts)
461
+
462
+ arr_opts = []
463
+ arr_opts << '--no-edit' if opts[:no_edit]
464
+ arr_opts << commitish
465
+
466
+ command('revert', arr_opts)
467
+ end
468
+
469
+ def apply(patch_file)
470
+ arr_opts = []
471
+ arr_opts << '--' << patch_file if patch_file
472
+ command('apply', arr_opts)
473
+ end
474
+
475
+ def apply_mail(patch_file)
476
+ arr_opts = []
477
+ arr_opts << '--' << patch_file if patch_file
478
+ command('am', arr_opts)
479
+ end
480
+
481
+ def stashes_all
482
+ arr = []
483
+ filename = File.join(@git_dir, 'logs/refs/stash')
484
+ if File.exist?(filename)
485
+ File.open(filename).each_with_index { |line, i|
486
+ m = line.match(/:(.*)$/)
487
+ arr << [i, m[1].strip]
488
+ }
489
+ end
490
+ arr
491
+ end
492
+
493
+ def stash_save(message)
494
+ output = command('stash save', ['--', message])
495
+ output =~ /HEAD is now at/
496
+ end
497
+
498
+ def stash_apply(id = nil)
499
+ if id
500
+ command('stash apply', [id])
501
+ else
502
+ command('stash apply')
503
+ end
504
+ end
505
+
506
+ def stash_clear
507
+ command('stash clear')
508
+ end
509
+
510
+ def stash_list
511
+ command('stash list')
512
+ end
513
+
514
+ def branch_new(branch)
515
+ command('branch', branch)
516
+ end
517
+
518
+ def branch_delete(branch)
519
+ command('branch', ['-D', branch])
520
+ end
521
+
522
+ def checkout(branch, opts = {})
523
+ arr_opts = []
524
+ arr_opts << '-f' if opts[:force]
525
+ arr_opts << '-b' << opts[:new_branch] if opts[:new_branch]
526
+ arr_opts << branch
527
+
528
+ command('checkout', arr_opts)
529
+ end
530
+
531
+ def checkout_file(version, file)
532
+ arr_opts = []
533
+ arr_opts << version
534
+ arr_opts << file
535
+ command('checkout', arr_opts)
536
+ end
537
+
538
+ def merge(branch, message = nil)
539
+ arr_opts = []
540
+ arr_opts << '-m' << message if message
541
+ arr_opts += [branch]
542
+ command('merge', arr_opts)
543
+ end
544
+
545
+ def unmerged
546
+ unmerged = []
547
+ command_lines('diff', ["--cached"]).each do |line|
548
+ unmerged << $1 if line =~ /^\* Unmerged path (.*)/
549
+ end
550
+ unmerged
551
+ end
552
+
553
+ def conflicts # :yields: file, your, their
554
+ self.unmerged.each do |f|
555
+ your = Tempfile.new("YOUR-#{File.basename(f)}").path
556
+ command('show', ":2:#{f}", true, "> #{escape your}")
557
+
558
+ their = Tempfile.new("THEIR-#{File.basename(f)}").path
559
+ command('show', ":3:#{f}", true, "> #{escape their}")
560
+ yield(f, your, their)
561
+ end
562
+ end
563
+
564
+ def remote_add(name, url, opts = {})
565
+ arr_opts = ['add']
566
+ arr_opts << '-f' if opts[:with_fetch] || opts[:fetch]
567
+ arr_opts << '-t' << opts[:track] if opts[:track]
568
+ arr_opts << '--'
569
+ arr_opts << name
570
+ arr_opts << url
571
+
572
+ command('remote', arr_opts)
573
+ end
574
+
575
+ def remote_remove(name)
576
+ command('remote', ['rm', name])
577
+ end
578
+
579
+ def remotes
580
+ command_lines('remote')
581
+ end
582
+
583
+ def tags
584
+ command_lines('tag')
585
+ end
586
+
587
+ def tag(name, *opts)
588
+ target = opts[0].instance_of?(String) ? opts[0] : nil
589
+
590
+ opts = opts.last.instance_of?(Hash) ? opts.last : {}
591
+
592
+ if (opts[:a] || opts[:annotate]) && !(opts[:m] || opts[:message])
593
+ raise "Can not create an [:a|:annotate] tag without the precense of [:m|:message]."
594
+ end
595
+
596
+ arr_opts = []
597
+
598
+ arr_opts << '-f' if opts[:force] || opts[:f]
599
+ arr_opts << '-a' if opts[:a] || opts[:annotate]
600
+ arr_opts << '-s' if opts[:s] || opts[:sign]
601
+ arr_opts << '-d' if opts[:d] || opts[:delete]
602
+ arr_opts << name
603
+ arr_opts << target if target
604
+ arr_opts << "-m #{opts[:m] || opts[:message]}" if opts[:m] || opts[:message]
605
+
606
+ command('tag', arr_opts)
607
+ end
608
+
609
+
610
+ def fetch(remote, opts)
611
+ arr_opts = [remote]
612
+ arr_opts << '--tags' if opts[:t] || opts[:tags]
613
+
614
+ command('fetch', arr_opts)
615
+ end
616
+
617
+ def push(remote, branch = 'master', opts = {})
618
+ # Small hack to keep backwards compatibility with the 'push(remote, branch, tags)' method signature.
619
+ opts = {:tags => opts} if [true, false].include?(opts)
620
+
621
+ arr_opts = []
622
+ arr_opts << '--force' if opts[:force] || opts[:f]
623
+ arr_opts << remote
624
+
625
+ command('push', arr_opts + [branch])
626
+ command('push', ['--tags'] + arr_opts) if opts[:tags]
627
+ end
628
+
629
+ def pull(remote='origin', branch='master')
630
+ command('pull', [remote, branch])
631
+ end
632
+
633
+ def tag_sha(tag_name)
634
+ head = File.join(@git_dir, 'refs', 'tags', tag_name)
635
+ return File.read(head).chomp if File.exist?(head)
636
+
637
+ command('show-ref', ['--tags', '-s', tag_name])
638
+ end
639
+
640
+ def repack
641
+ command('repack', ['-a', '-d'])
642
+ end
643
+
644
+ def gc
645
+ command('gc', ['--prune', '--aggressive', '--auto'])
646
+ end
647
+
648
+ # reads a tree into the current index file
649
+ def read_tree(treeish, opts = {})
650
+ arr_opts = []
651
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
652
+ arr_opts += [treeish]
653
+ command('read-tree', arr_opts)
654
+ end
655
+
656
+ def write_tree
657
+ command('write-tree')
658
+ end
659
+
660
+ def commit_tree(tree, opts = {})
661
+ opts[:message] ||= "commit tree #{tree}"
662
+ t = Tempfile.new('commit-message')
663
+ t.write(opts[:message])
664
+ t.close
665
+
666
+ arr_opts = []
667
+ arr_opts << tree
668
+ arr_opts << '-p' << opts[:parent] if opts[:parent]
669
+ arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
670
+ command('commit-tree', arr_opts, true, "< #{escape t.path}")
671
+ end
672
+
673
+ def update_ref(branch, commit)
674
+ command('update-ref', [branch, commit])
675
+ end
676
+
677
+ def checkout_index(opts = {})
678
+ arr_opts = []
679
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
680
+ arr_opts << "--force" if opts[:force]
681
+ arr_opts << "--all" if opts[:all]
682
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
683
+
684
+ command('checkout-index', arr_opts)
685
+ end
686
+
687
+ # creates an archive file
688
+ #
689
+ # options
690
+ # :format (zip, tar)
691
+ # :prefix
692
+ # :remote
693
+ # :path
694
+ def archive(sha, file = nil, opts = {})
695
+ opts[:format] ||= 'zip'
696
+
697
+ if opts[:format] == 'tgz'
698
+ opts[:format] = 'tar'
699
+ opts[:add_gzip] = true
700
+ end
701
+
702
+ file ||= Tempfile.new('archive').path
703
+
704
+ arr_opts = []
705
+ arr_opts << "--format=#{opts[:format]}" if opts[:format]
706
+ arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
707
+ arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
708
+ arr_opts << sha
709
+ arr_opts << '--' << opts[:path] if opts[:path]
710
+ command('archive', arr_opts, true, (opts[:add_gzip] ? '| gzip' : '') + " > #{escape file}")
711
+ return file
712
+ end
713
+
714
+ # returns the current version of git, as an Array of Fixnums.
715
+ def current_command_version
716
+ output = command('version', [], false)
717
+ version = output[/\d+\.\d+(\.\d+)+/]
718
+ version.split('.').collect {|i| i.to_i}
719
+ end
720
+
721
+ def required_command_version
722
+ [1, 6]
723
+ end
724
+
725
+ def meets_required_version?
726
+ (self.current_command_version <=> self.required_command_version) >= 0
727
+ end
728
+
729
+
730
+ private
731
+
732
+ def command_lines(cmd, opts = [], chdir = true, redirect = '')
733
+ command(cmd, opts, chdir).split("\n")
734
+ end
735
+
736
+ def command(cmd, opts = [], chdir = true, redirect = '', &block)
737
+ old_git_dir = ENV['GIT_DIR']
738
+ old_git_work_dir = ENV['GIT_WORK_TREE']
739
+ old_git_index_file = ENV['GIT_INDEX_FILE']
740
+
741
+ ENV['GIT_DIR'] = @git_dir
742
+ ENV['GIT_WORK_TREE'] = @git_work_dir
743
+ ENV['GIT_INDEX_FILE'] = @git_index_file
744
+
745
+ path = @git_work_dir || @git_dir || @path
746
+
747
+ opts = [opts].flatten.map {|s| escape(s) }.join(' ')
748
+
749
+ git_cmd = "git #{cmd} #{opts} #{redirect} 2>&1"
750
+
751
+ output = nil
752
+
753
+ if chdir && (Dir.getwd != path)
754
+ Dir.chdir(path) { output = run_command(git_cmd, &block) }
755
+ else
756
+ output = run_command(git_cmd, &block)
757
+ end
758
+
759
+ if @logger
760
+ @logger.info(git_cmd)
761
+ @logger.debug(output)
762
+ end
763
+
764
+ if $?.exitstatus > 1 || ($?.exitstatus == 1 && output != '')
765
+ raise Git::GitExecuteError.new(git_cmd + ':' + output.to_s)
766
+ end
767
+
768
+ return output
769
+ ensure
770
+ ENV['GIT_DIR'] = old_git_dir
771
+ ENV['GIT_WORK_TREE'] = old_git_work_dir
772
+ ENV['GIT_INDEX_FILE'] = old_git_index_file
773
+ end
774
+
775
+ # Takes the diff command line output (as Array) and parse it into a Hash
776
+ #
777
+ # @param [String] diff_command the diff commadn to be used
778
+ # @param [Array] opts the diff options to be used
779
+ # @return [Hash] the diff as Hash
780
+ def diff_as_hash(diff_command, opts=[])
781
+ command_lines(diff_command, opts).inject({}) do |memo, line|
782
+ info, file = line.split("\t")
783
+ mode_src, mode_dest, sha_src, sha_dest, type = info.split
784
+
785
+ memo[file] = {
786
+ :mode_index => mode_dest,
787
+ :mode_repo => mode_src.to_s[1, 7],
788
+ :path => file,
789
+ :sha_repo => sha_src,
790
+ :sha_index => sha_dest,
791
+ :type => type
792
+ }
793
+
794
+ memo
795
+ end
796
+ end
797
+
798
+ # Returns an array holding the common options for the log commands
799
+ #
800
+ # @param [Hash] opts the given options
801
+ # @return [Array] the set of common options that the log command will use
802
+ def log_common_options(opts)
803
+ arr_opts = []
804
+
805
+ arr_opts << "-#{opts[:count]}" if opts[:count]
806
+ arr_opts << "--no-color"
807
+ arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
808
+ arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
809
+ arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
810
+ arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
811
+ arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
812
+
813
+ arr_opts
814
+ end
815
+
816
+ # Retrurns an array holding path options for the log commands
817
+ #
818
+ # @param [Hash] opts the given options
819
+ # @return [Array] the set of path options that the log command will use
820
+ def log_path_options(opts)
821
+ arr_opts = []
822
+
823
+ arr_opts << opts[:object] if opts[:object].is_a? String
824
+ arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter]
825
+
826
+ arr_opts
827
+ end
828
+
829
+ def run_command(git_cmd, &block)
830
+ return IO.popen(git_cmd, &block) if block_given?
831
+
832
+ `#{git_cmd}`.chomp
833
+ end
834
+
835
+ def escape(s)
836
+ return "'#{s && s.to_s.gsub('\'','\'"\'"\'')}'" if RUBY_PLATFORM !~ /mingw|mswin/
837
+
838
+ # Keeping the old escape format for windows users
839
+ escaped = s.to_s.gsub('\'', '\'\\\'\'')
840
+ return %Q{"#{escaped}"}
841
+ end
842
+
843
+ end
844
+ end