git 1.4.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of git might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/{CHANGELOG → CHANGELOG.md} +41 -10
- data/CONTRIBUTING.md +149 -0
- data/MAINTAINERS.md +14 -0
- data/README.md +260 -212
- data/lib/git.rb +186 -46
- data/lib/git/base.rb +192 -147
- data/lib/git/base/factory.rb +47 -21
- data/lib/git/branch.rb +5 -1
- data/lib/git/config.rb +1 -1
- data/lib/git/diff.rb +7 -10
- data/lib/git/lib.rb +259 -77
- data/lib/git/log.rb +8 -1
- data/lib/git/stashes.rb +11 -0
- data/lib/git/status.rb +1 -1
- data/lib/git/version.rb +1 -1
- metadata +91 -36
- data/VERSION +0 -2
data/lib/git/base/factory.rb
CHANGED
@@ -3,71 +3,97 @@ module Git
|
|
3
3
|
class Base
|
4
4
|
|
5
5
|
module Factory
|
6
|
-
|
7
|
-
#
|
6
|
+
|
7
|
+
# @return [Git::Branch] an object for branch_name
|
8
8
|
def branch(branch_name = 'master')
|
9
9
|
Git::Branch.new(self, branch_name)
|
10
10
|
end
|
11
11
|
|
12
|
-
#
|
13
|
-
#
|
12
|
+
# @return [Git::Branches] a collection of all the branches in the repository.
|
13
|
+
# Each branch is represented as a {Git::Branch}.
|
14
14
|
def branches
|
15
15
|
Git::Branches.new(self)
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
|
+
# returns a Git::Worktree object for dir, commitish
|
19
|
+
def worktree(dir, commitish = nil)
|
20
|
+
Git::Worktree.new(self, dir, commitish)
|
21
|
+
end
|
22
|
+
|
23
|
+
# returns a Git::worktrees object of all the Git::Worktrees
|
24
|
+
# objects for this repo
|
25
|
+
def worktrees
|
26
|
+
Git::Worktrees.new(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Git::Object::Commit] a commit object
|
18
30
|
def commit_tree(tree = nil, opts = {})
|
19
31
|
Git::Object::Commit.new(self, self.lib.commit_tree(tree, opts))
|
20
32
|
end
|
21
33
|
|
22
|
-
#
|
34
|
+
# @return [Git::Diff] a Git::Diff object
|
23
35
|
def diff(objectish = 'HEAD', obj2 = nil)
|
24
36
|
Git::Diff.new(self, objectish, obj2)
|
25
37
|
end
|
26
|
-
|
38
|
+
|
39
|
+
# @return [Git::Object] a Git object
|
27
40
|
def gblob(objectish)
|
28
41
|
Git::Object.new(self, objectish, 'blob')
|
29
42
|
end
|
30
|
-
|
43
|
+
|
44
|
+
# @return [Git::Object] a Git object
|
31
45
|
def gcommit(objectish)
|
32
46
|
Git::Object.new(self, objectish, 'commit')
|
33
47
|
end
|
34
48
|
|
49
|
+
# @return [Git::Object] a Git object
|
35
50
|
def gtree(objectish)
|
36
51
|
Git::Object.new(self, objectish, 'tree')
|
37
52
|
end
|
38
|
-
|
39
|
-
#
|
53
|
+
|
54
|
+
# @return [Git::Log] a log with the specified number of commits
|
40
55
|
def log(count = 30)
|
41
56
|
Git::Log.new(self, count)
|
42
57
|
end
|
43
|
-
|
58
|
+
|
44
59
|
# returns a Git::Object of the appropriate type
|
45
|
-
# you can also call @git.gtree('tree'), but that's
|
60
|
+
# you can also call @git.gtree('tree'), but that's
|
46
61
|
# just for readability. If you call @git.gtree('HEAD') it will
|
47
|
-
# still return a Git::Object::Commit object.
|
62
|
+
# still return a Git::Object::Commit object.
|
48
63
|
#
|
49
|
-
#
|
50
|
-
# on the objectish and determine the type of the object and return
|
51
|
-
# an appropriate object for that type
|
64
|
+
# object calls a factory method that will run a rev-parse
|
65
|
+
# on the objectish and determine the type of the object and return
|
66
|
+
# an appropriate object for that type
|
67
|
+
#
|
68
|
+
# @return [Git::Object] an instance of the appropriate type of Git::Object
|
52
69
|
def object(objectish)
|
53
70
|
Git::Object.new(self, objectish)
|
54
71
|
end
|
55
|
-
|
56
|
-
#
|
72
|
+
|
73
|
+
# @return [Git::Remote] a remote of the specified name
|
57
74
|
def remote(remote_name = 'origin')
|
58
75
|
Git::Remote.new(self, remote_name)
|
59
76
|
end
|
60
77
|
|
61
|
-
#
|
78
|
+
# @return [Git::Status] a status object
|
62
79
|
def status
|
63
80
|
Git::Status.new(self)
|
64
81
|
end
|
65
|
-
|
66
|
-
#
|
82
|
+
|
83
|
+
# @return [Git::Object::Tag] a tag object
|
67
84
|
def tag(tag_name)
|
68
85
|
Git::Object.new(self, tag_name, 'tag', true)
|
69
86
|
end
|
70
87
|
|
88
|
+
# Find as good common ancestors as possible for a merge
|
89
|
+
# example: g.merge_base('master', 'some_branch', 'some_sha', octopus: true)
|
90
|
+
#
|
91
|
+
# @return [Array<Git::Object::Commit>] a collection of common ancestors
|
92
|
+
def merge_base(*args)
|
93
|
+
shas = self.lib.merge_base(*args)
|
94
|
+
shas.map { |sha| gcommit(sha) }
|
95
|
+
end
|
96
|
+
|
71
97
|
end
|
72
98
|
|
73
99
|
end
|
data/lib/git/branch.rb
CHANGED
@@ -37,7 +37,7 @@ module Git
|
|
37
37
|
# # do other stuff
|
38
38
|
# return true # auto commits and switches back
|
39
39
|
# end
|
40
|
-
def in_branch
|
40
|
+
def in_branch(message = 'in branch work')
|
41
41
|
old_current = @base.lib.branch_current
|
42
42
|
checkout
|
43
43
|
if yield
|
@@ -60,6 +60,10 @@ module Git
|
|
60
60
|
determine_current
|
61
61
|
end
|
62
62
|
|
63
|
+
def contains?(commit)
|
64
|
+
!@base.lib.branch_contains(commit, self.name).empty?
|
65
|
+
end
|
66
|
+
|
63
67
|
def merge(branch = nil, message = nil)
|
64
68
|
if branch
|
65
69
|
in_branch do
|
data/lib/git/config.rb
CHANGED
data/lib/git/diff.rb
CHANGED
@@ -72,6 +72,7 @@ module Git
|
|
72
72
|
class DiffFile
|
73
73
|
attr_accessor :patch, :path, :mode, :src, :dst, :type
|
74
74
|
@base = nil
|
75
|
+
NIL_BLOB_REGEXP = /\A0{4,40}\z/.freeze
|
75
76
|
|
76
77
|
def initialize(base, hash)
|
77
78
|
@base = base
|
@@ -89,10 +90,10 @@ module Git
|
|
89
90
|
end
|
90
91
|
|
91
92
|
def blob(type = :dst)
|
92
|
-
if type == :src
|
93
|
-
@base.object(@src)
|
94
|
-
|
95
|
-
@base.object(@dst)
|
93
|
+
if type == :src && !NIL_BLOB_REGEXP.match(@src)
|
94
|
+
@base.object(@src)
|
95
|
+
elsif !NIL_BLOB_REGEXP.match(@dst)
|
96
|
+
@base.object(@dst)
|
96
97
|
end
|
97
98
|
end
|
98
99
|
end
|
@@ -127,16 +128,12 @@ module Git
|
|
127
128
|
}
|
128
129
|
final = {}
|
129
130
|
current_file = nil
|
130
|
-
|
131
|
-
:invalid => :replace,
|
132
|
-
:undef => :replace
|
133
|
-
})
|
134
|
-
full_diff_utf8_encoded.split("\n").each do |line|
|
131
|
+
@full_diff.split("\n").each do |line|
|
135
132
|
if m = /^diff --git a\/(.*?) b\/(.*?)/.match(line)
|
136
133
|
current_file = m[1]
|
137
134
|
final[current_file] = defaults.merge({:patch => line, :path => current_file})
|
138
135
|
else
|
139
|
-
if m = /^index (
|
136
|
+
if m = /^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/.match(line)
|
140
137
|
final[current_file][:src] = m[1]
|
141
138
|
final[current_file][:dst] = m[2]
|
142
139
|
final[current_file][:mode] = m[3].strip if m[3]
|
data/lib/git/lib.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
require 'rchardet'
|
1
2
|
require 'tempfile'
|
3
|
+
require 'zlib'
|
2
4
|
|
3
5
|
module Git
|
4
6
|
|
@@ -9,6 +11,43 @@ module Git
|
|
9
11
|
|
10
12
|
@@semaphore = Mutex.new
|
11
13
|
|
14
|
+
# The path to the Git working copy. The default is '"./.git"'.
|
15
|
+
#
|
16
|
+
# @return [Pathname] the path to the Git working copy.
|
17
|
+
#
|
18
|
+
# @see [Git working tree](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefworkingtreeaworkingtree)
|
19
|
+
#
|
20
|
+
attr_reader :git_work_dir
|
21
|
+
|
22
|
+
# The path to the Git repository directory. The default is
|
23
|
+
# `"#{git_work_dir}/.git"`.
|
24
|
+
#
|
25
|
+
# @return [Pathname] the Git repository directory.
|
26
|
+
#
|
27
|
+
# @see [Git repository](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefrepositoryarepository)
|
28
|
+
#
|
29
|
+
attr_reader :git_dir
|
30
|
+
|
31
|
+
# The Git index file used to stage changes (using `git add`) before they
|
32
|
+
# are committed.
|
33
|
+
#
|
34
|
+
# @return [Pathname] the Git index file
|
35
|
+
#
|
36
|
+
# @see [Git index file](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefindexaindex)
|
37
|
+
#
|
38
|
+
attr_reader :git_index_file
|
39
|
+
|
40
|
+
# Create a new Git::Lib object
|
41
|
+
#
|
42
|
+
# @param [Git::Base, Hash] base An object that passes in values for
|
43
|
+
# @git_work_dir, @git_dir, and @git_index_file
|
44
|
+
#
|
45
|
+
# @param [Logger] logger
|
46
|
+
#
|
47
|
+
# @option base [Pathname] :working_directory
|
48
|
+
# @option base [Pathname] :repository
|
49
|
+
# @option base [Pathname] :index
|
50
|
+
#
|
12
51
|
def initialize(base = nil, logger = nil)
|
13
52
|
@git_dir = nil
|
14
53
|
@git_index_file = nil
|
@@ -37,14 +76,11 @@ module Git
|
|
37
76
|
arr_opts = []
|
38
77
|
arr_opts << '--bare' if opts[:bare]
|
39
78
|
|
40
|
-
command('init', arr_opts
|
79
|
+
command('init', arr_opts)
|
41
80
|
end
|
42
81
|
|
43
82
|
# tries to clone the given repo
|
44
83
|
#
|
45
|
-
# returns {:repository} (if bare)
|
46
|
-
# {:working_directory} otherwise
|
47
|
-
#
|
48
84
|
# accepts options:
|
49
85
|
# :bare:: no working directory
|
50
86
|
# :branch:: name of branch to track (rather than 'master')
|
@@ -56,6 +92,8 @@ module Git
|
|
56
92
|
#
|
57
93
|
# TODO - make this work with SSH password or auth_key
|
58
94
|
#
|
95
|
+
# @return [Hash] the options to pass to {Git::Base.new}
|
96
|
+
#
|
59
97
|
def clone(repository, name, opts = {})
|
60
98
|
@path = opts[:path] || '.'
|
61
99
|
clone_dir = opts[:path] ? File.join(@path, name) : name
|
@@ -76,9 +114,16 @@ module Git
|
|
76
114
|
|
77
115
|
command('clone', arr_opts)
|
78
116
|
|
79
|
-
(
|
117
|
+
return_base_opts_from_clone(clone_dir, opts)
|
80
118
|
end
|
81
119
|
|
120
|
+
def return_base_opts_from_clone(clone_dir, opts)
|
121
|
+
base_opts = {}
|
122
|
+
base_opts[:repository] = clone_dir if (opts[:bare] || opts[:mirror])
|
123
|
+
base_opts[:working_directory] = clone_dir unless (opts[:bare] || opts[:mirror])
|
124
|
+
base_opts[:log] = opts[:log] if opts[:log]
|
125
|
+
base_opts
|
126
|
+
end
|
82
127
|
|
83
128
|
## READ COMMANDS ##
|
84
129
|
|
@@ -113,12 +158,12 @@ module Git
|
|
113
158
|
arr_opts << '--always' if opts[:always]
|
114
159
|
arr_opts << '--exact-match' if opts[:exact_match] || opts[:"exact-match"]
|
115
160
|
|
116
|
-
arr_opts << '--dirty' if opts[
|
117
|
-
arr_opts << "--dirty=#{opts[
|
161
|
+
arr_opts << '--dirty' if opts[:dirty] == true
|
162
|
+
arr_opts << "--dirty=#{opts[:dirty]}" if opts[:dirty].is_a?(String)
|
118
163
|
|
119
|
-
arr_opts << "--abbrev=#{opts[
|
120
|
-
arr_opts << "--candidates=#{opts[
|
121
|
-
arr_opts << "--match=#{opts[
|
164
|
+
arr_opts << "--abbrev=#{opts[:abbrev]}" if opts[:abbrev]
|
165
|
+
arr_opts << "--candidates=#{opts[:candidates]}" if opts[:candidates]
|
166
|
+
arr_opts << "--match=#{opts[:match]}" if opts[:match]
|
122
167
|
|
123
168
|
arr_opts << committish if committish
|
124
169
|
|
@@ -132,7 +177,7 @@ module Git
|
|
132
177
|
|
133
178
|
arr_opts += log_path_options(opts)
|
134
179
|
|
135
|
-
command_lines('log', arr_opts
|
180
|
+
command_lines('log', arr_opts).map { |l| l.split.first }
|
136
181
|
end
|
137
182
|
|
138
183
|
def full_log_commits(opts={})
|
@@ -143,7 +188,7 @@ module Git
|
|
143
188
|
|
144
189
|
arr_opts += log_path_options(opts)
|
145
190
|
|
146
|
-
full_log = command_lines('log', arr_opts
|
191
|
+
full_log = command_lines('log', arr_opts)
|
147
192
|
|
148
193
|
process_commit_log_data(full_log)
|
149
194
|
end
|
@@ -164,17 +209,17 @@ module Git
|
|
164
209
|
end
|
165
210
|
|
166
211
|
def object_type(sha)
|
167
|
-
command('cat-file',
|
212
|
+
command('cat-file', '-t', sha)
|
168
213
|
end
|
169
214
|
|
170
215
|
def object_size(sha)
|
171
|
-
command('cat-file',
|
216
|
+
command('cat-file', '-s', sha).to_i
|
172
217
|
end
|
173
218
|
|
174
219
|
# returns useful array of raw commit object data
|
175
220
|
def commit_data(sha)
|
176
221
|
sha = sha.to_s
|
177
|
-
cdata = command_lines('cat-file',
|
222
|
+
cdata = command_lines('cat-file', 'commit', sha)
|
178
223
|
process_commit_data(cdata, sha, 0)
|
179
224
|
end
|
180
225
|
|
@@ -204,7 +249,7 @@ module Git
|
|
204
249
|
|
205
250
|
def tag_data(name)
|
206
251
|
sha = sha.to_s
|
207
|
-
tdata = command_lines('cat-file',
|
252
|
+
tdata = command_lines('cat-file', 'tag', name)
|
208
253
|
process_tag_data(tdata, name, 0)
|
209
254
|
end
|
210
255
|
|
@@ -242,6 +287,8 @@ module Git
|
|
242
287
|
next
|
243
288
|
end
|
244
289
|
|
290
|
+
in_message = false if in_message && line[0..3] != " "
|
291
|
+
|
245
292
|
if in_message
|
246
293
|
hsh['message'] << "#{line[4..-1]}\n"
|
247
294
|
next
|
@@ -267,7 +314,7 @@ module Git
|
|
267
314
|
end
|
268
315
|
|
269
316
|
def object_contents(sha, &block)
|
270
|
-
command('cat-file',
|
317
|
+
command('cat-file', '-p', sha, &block)
|
271
318
|
end
|
272
319
|
|
273
320
|
def ls_tree(sha)
|
@@ -283,11 +330,11 @@ module Git
|
|
283
330
|
end
|
284
331
|
|
285
332
|
def mv(file1, file2)
|
286
|
-
command_lines('mv',
|
333
|
+
command_lines('mv', '--', file1, file2)
|
287
334
|
end
|
288
335
|
|
289
336
|
def full_tree(sha)
|
290
|
-
command_lines('ls-tree',
|
337
|
+
command_lines('ls-tree', '-r', sha)
|
291
338
|
end
|
292
339
|
|
293
340
|
def tree_depth(sha)
|
@@ -295,7 +342,7 @@ module Git
|
|
295
342
|
end
|
296
343
|
|
297
344
|
def change_head_branch(branch_name)
|
298
|
-
command('symbolic-ref',
|
345
|
+
command('symbolic-ref', 'HEAD', "refs/heads/#{branch_name}")
|
299
346
|
end
|
300
347
|
|
301
348
|
def branches_all
|
@@ -307,6 +354,39 @@ module Git
|
|
307
354
|
arr
|
308
355
|
end
|
309
356
|
|
357
|
+
def worktrees_all
|
358
|
+
arr = []
|
359
|
+
directory = ''
|
360
|
+
# Output example for `worktree list --porcelain`:
|
361
|
+
# worktree /code/public/ruby-git
|
362
|
+
# HEAD 4bef5abbba073c77b4d0ccc1ffcd0ed7d48be5d4
|
363
|
+
# branch refs/heads/master
|
364
|
+
#
|
365
|
+
# worktree /tmp/worktree-1
|
366
|
+
# HEAD b8c63206f8d10f57892060375a86ae911fad356e
|
367
|
+
# detached
|
368
|
+
#
|
369
|
+
command_lines('worktree',['list', '--porcelain']).each do |w|
|
370
|
+
s = w.split("\s")
|
371
|
+
directory = s[1] if s[0] == 'worktree'
|
372
|
+
arr << [directory, s[1]] if s[0] == 'HEAD'
|
373
|
+
end
|
374
|
+
arr
|
375
|
+
end
|
376
|
+
|
377
|
+
def worktree_add(dir, commitish = nil)
|
378
|
+
return command('worktree', ['add', dir, commitish]) if !commitish.nil?
|
379
|
+
command('worktree', ['add', dir])
|
380
|
+
end
|
381
|
+
|
382
|
+
def worktree_remove(dir)
|
383
|
+
command('worktree', ['remove', dir])
|
384
|
+
end
|
385
|
+
|
386
|
+
def worktree_prune
|
387
|
+
command('worktree', ['prune'])
|
388
|
+
end
|
389
|
+
|
310
390
|
def list_files(ref_dir)
|
311
391
|
dir = File.join(@git_dir, 'refs', ref_dir)
|
312
392
|
files = []
|
@@ -318,6 +398,9 @@ module Git
|
|
318
398
|
branches_all.select { |b| b[1] }.first[0] rescue nil
|
319
399
|
end
|
320
400
|
|
401
|
+
def branch_contains(commit, branch_name="")
|
402
|
+
command("branch", [branch_name, "--contains", commit])
|
403
|
+
end
|
321
404
|
|
322
405
|
# returns hash
|
323
406
|
# [tree-ish] = [[line_no, match], [line_no, match2]]
|
@@ -399,7 +482,7 @@ module Git
|
|
399
482
|
def ls_files(location=nil)
|
400
483
|
location ||= '.'
|
401
484
|
hsh = {}
|
402
|
-
command_lines('ls-files',
|
485
|
+
command_lines('ls-files', '--stage', location).each do |line|
|
403
486
|
(info, file) = line.split("\t")
|
404
487
|
(mode, sha, stage) = info.split
|
405
488
|
file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
|
@@ -408,10 +491,13 @@ module Git
|
|
408
491
|
hsh
|
409
492
|
end
|
410
493
|
|
411
|
-
def ls_remote(location=nil)
|
412
|
-
|
494
|
+
def ls_remote(location=nil, opts={})
|
495
|
+
arr_opts = []
|
496
|
+
arr_opts << ['--refs'] if opts[:refs]
|
497
|
+
arr_opts << (location || '.')
|
498
|
+
|
413
499
|
Hash.new{ |h,k| h[k] = {} }.tap do |hsh|
|
414
|
-
command_lines('ls-remote',
|
500
|
+
command_lines('ls-remote', arr_opts).each do |line|
|
415
501
|
(sha, info) = line.split("\t")
|
416
502
|
(ref, type, name) = info.split('/', 3)
|
417
503
|
type ||= 'head'
|
@@ -423,7 +509,7 @@ module Git
|
|
423
509
|
end
|
424
510
|
|
425
511
|
def ignored_files
|
426
|
-
command_lines('ls-files',
|
512
|
+
command_lines('ls-files', '--others', '-i', '--exclude-standard')
|
427
513
|
end
|
428
514
|
|
429
515
|
|
@@ -438,24 +524,24 @@ module Git
|
|
438
524
|
end
|
439
525
|
|
440
526
|
def config_get(name)
|
441
|
-
do_get =
|
442
|
-
command('config',
|
527
|
+
do_get = Proc.new do |path|
|
528
|
+
command('config', '--get', name)
|
443
529
|
end
|
444
530
|
|
445
531
|
if @git_dir
|
446
532
|
Dir.chdir(@git_dir, &do_get)
|
447
533
|
else
|
448
|
-
|
534
|
+
do_get.call
|
449
535
|
end
|
450
536
|
end
|
451
537
|
|
452
538
|
def global_config_get(name)
|
453
|
-
command('config',
|
539
|
+
command('config', '--global', '--get', name)
|
454
540
|
end
|
455
541
|
|
456
542
|
def config_list
|
457
|
-
build_list =
|
458
|
-
parse_config_list command_lines('config',
|
543
|
+
build_list = Proc.new do |path|
|
544
|
+
parse_config_list command_lines('config', '--list')
|
459
545
|
end
|
460
546
|
|
461
547
|
if @git_dir
|
@@ -466,7 +552,7 @@ module Git
|
|
466
552
|
end
|
467
553
|
|
468
554
|
def global_config_list
|
469
|
-
parse_config_list command_lines('config',
|
555
|
+
parse_config_list command_lines('config', '--global', '--list')
|
470
556
|
end
|
471
557
|
|
472
558
|
def parse_config_list(lines)
|
@@ -479,7 +565,7 @@ module Git
|
|
479
565
|
end
|
480
566
|
|
481
567
|
def parse_config(file)
|
482
|
-
parse_config_list command_lines('config',
|
568
|
+
parse_config_list command_lines('config', '--list', '--file', file)
|
483
569
|
end
|
484
570
|
|
485
571
|
# Shows objects
|
@@ -492,17 +578,17 @@ module Git
|
|
492
578
|
|
493
579
|
arr_opts << (path ? "#{objectish}:#{path}" : objectish)
|
494
580
|
|
495
|
-
command('show', arr_opts.compact)
|
581
|
+
command('show', arr_opts.compact, chomp: false)
|
496
582
|
end
|
497
583
|
|
498
584
|
## WRITE COMMANDS ##
|
499
585
|
|
500
586
|
def config_set(name, value)
|
501
|
-
command('config',
|
587
|
+
command('config', name, value)
|
502
588
|
end
|
503
589
|
|
504
590
|
def global_config_set(name, value)
|
505
|
-
command('config',
|
591
|
+
command('config', '--global', name, value)
|
506
592
|
end
|
507
593
|
|
508
594
|
# updates the repository index using the working directory content
|
@@ -546,6 +632,19 @@ module Git
|
|
546
632
|
command('rm', arr_opts)
|
547
633
|
end
|
548
634
|
|
635
|
+
# Takes the commit message with the options and executes the commit command
|
636
|
+
#
|
637
|
+
# accepts options:
|
638
|
+
# :amend
|
639
|
+
# :all
|
640
|
+
# :allow_empty
|
641
|
+
# :author
|
642
|
+
# :date
|
643
|
+
# :no_verify
|
644
|
+
# :allow_empty_message
|
645
|
+
#
|
646
|
+
# @param [String] message the commit message to be used
|
647
|
+
# @param [Hash] opts the commit options to be used
|
549
648
|
def commit(message, opts = {})
|
550
649
|
arr_opts = []
|
551
650
|
arr_opts << "--message=#{message}" if message
|
@@ -553,6 +652,9 @@ module Git
|
|
553
652
|
arr_opts << '--all' if opts[:add_all] || opts[:all]
|
554
653
|
arr_opts << '--allow-empty' if opts[:allow_empty]
|
555
654
|
arr_opts << "--author=#{opts[:author]}" if opts[:author]
|
655
|
+
arr_opts << "--date=#{opts[:date]}" if opts[:date].is_a? String
|
656
|
+
arr_opts << '--no-verify' if opts[:no_verify]
|
657
|
+
arr_opts << '--allow-empty-message' if opts[:allow_empty_message]
|
556
658
|
|
557
659
|
command('commit', arr_opts)
|
558
660
|
end
|
@@ -600,22 +702,24 @@ module Git
|
|
600
702
|
arr = []
|
601
703
|
filename = File.join(@git_dir, 'logs/refs/stash')
|
602
704
|
if File.exist?(filename)
|
603
|
-
File.open(filename)
|
604
|
-
|
605
|
-
|
606
|
-
|
705
|
+
File.open(filename) do |f|
|
706
|
+
f.each_with_index do |line, i|
|
707
|
+
m = line.match(/:(.*)$/)
|
708
|
+
arr << [i, m[1].strip]
|
709
|
+
end
|
710
|
+
end
|
607
711
|
end
|
608
712
|
arr
|
609
713
|
end
|
610
714
|
|
611
715
|
def stash_save(message)
|
612
|
-
output = command('stash save',
|
716
|
+
output = command('stash save', message)
|
613
717
|
output =~ /HEAD is now at/
|
614
718
|
end
|
615
719
|
|
616
720
|
def stash_apply(id = nil)
|
617
721
|
if id
|
618
|
-
command('stash apply',
|
722
|
+
command('stash apply', id)
|
619
723
|
else
|
620
724
|
command('stash apply')
|
621
725
|
end
|
@@ -634,7 +738,7 @@ module Git
|
|
634
738
|
end
|
635
739
|
|
636
740
|
def branch_delete(branch)
|
637
|
-
command('branch',
|
741
|
+
command('branch', '-D', branch)
|
638
742
|
end
|
639
743
|
|
640
744
|
def checkout(branch, opts = {})
|
@@ -653,16 +757,32 @@ module Git
|
|
653
757
|
command('checkout', arr_opts)
|
654
758
|
end
|
655
759
|
|
656
|
-
def merge(branch, message = nil)
|
760
|
+
def merge(branch, message = nil, opts = {})
|
657
761
|
arr_opts = []
|
762
|
+
arr_opts << '--no-ff' if opts[:no_ff]
|
658
763
|
arr_opts << '-m' << message if message
|
659
764
|
arr_opts += [branch]
|
660
765
|
command('merge', arr_opts)
|
661
766
|
end
|
662
767
|
|
768
|
+
def merge_base(*args)
|
769
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
770
|
+
|
771
|
+
arg_opts = []
|
772
|
+
|
773
|
+
arg_opts << '--octopus' if opts[:octopus]
|
774
|
+
arg_opts << '--independent' if opts[:independent]
|
775
|
+
arg_opts << '--fork-point' if opts[:fork_point]
|
776
|
+
arg_opts << '--all' if opts[:all]
|
777
|
+
|
778
|
+
arg_opts += args
|
779
|
+
|
780
|
+
command('merge-base', arg_opts).lines.map(&:strip)
|
781
|
+
end
|
782
|
+
|
663
783
|
def unmerged
|
664
784
|
unmerged = []
|
665
|
-
command_lines('diff',
|
785
|
+
command_lines('diff', "--cached").each do |line|
|
666
786
|
unmerged << $1 if line =~ /^\* Unmerged path (.*)/
|
667
787
|
end
|
668
788
|
unmerged
|
@@ -670,11 +790,15 @@ module Git
|
|
670
790
|
|
671
791
|
def conflicts # :yields: file, your, their
|
672
792
|
self.unmerged.each do |f|
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
793
|
+
your_tempfile = Tempfile.new("YOUR-#{File.basename(f)}")
|
794
|
+
your = your_tempfile.path
|
795
|
+
your_tempfile.close # free up file for git command process
|
796
|
+
command('show', ":2:#{f}", redirect: "> #{escape your}")
|
797
|
+
|
798
|
+
their_tempfile = Tempfile.new("THEIR-#{File.basename(f)}")
|
799
|
+
their = their_tempfile.path
|
800
|
+
their_tempfile.close # free up file for git command process
|
801
|
+
command('show', ":3:#{f}", redirect: "> #{escape their}")
|
678
802
|
yield(f, your, their)
|
679
803
|
end
|
680
804
|
end
|
@@ -699,7 +823,7 @@ module Git
|
|
699
823
|
end
|
700
824
|
|
701
825
|
def remote_remove(name)
|
702
|
-
command('remote',
|
826
|
+
command('remote', 'rm', name)
|
703
827
|
end
|
704
828
|
|
705
829
|
def remotes
|
@@ -727,7 +851,10 @@ module Git
|
|
727
851
|
arr_opts << '-d' if opts[:d] || opts[:delete]
|
728
852
|
arr_opts << name
|
729
853
|
arr_opts << target if target
|
730
|
-
|
854
|
+
|
855
|
+
if opts[:m] || opts[:message]
|
856
|
+
arr_opts << '-m' << (opts[:m] || opts[:message])
|
857
|
+
end
|
731
858
|
|
732
859
|
command('tag', arr_opts)
|
733
860
|
end
|
@@ -735,8 +862,10 @@ module Git
|
|
735
862
|
|
736
863
|
def fetch(remote, opts)
|
737
864
|
arr_opts = [remote]
|
865
|
+
arr_opts << opts[:ref] if opts[:ref]
|
738
866
|
arr_opts << '--tags' if opts[:t] || opts[:tags]
|
739
867
|
arr_opts << '--prune' if opts[:p] || opts[:prune]
|
868
|
+
arr_opts << '--unshallow' if opts[:unshallow]
|
740
869
|
|
741
870
|
command('fetch', arr_opts)
|
742
871
|
end
|
@@ -747,6 +876,7 @@ module Git
|
|
747
876
|
|
748
877
|
arr_opts = []
|
749
878
|
arr_opts << '--mirror' if opts[:mirror]
|
879
|
+
arr_opts << '--delete' if opts[:delete]
|
750
880
|
arr_opts << '--force' if opts[:force] || opts[:f]
|
751
881
|
arr_opts << remote
|
752
882
|
|
@@ -759,22 +889,22 @@ module Git
|
|
759
889
|
end
|
760
890
|
|
761
891
|
def pull(remote='origin', branch='master')
|
762
|
-
command('pull',
|
892
|
+
command('pull', remote, branch)
|
763
893
|
end
|
764
894
|
|
765
895
|
def tag_sha(tag_name)
|
766
896
|
head = File.join(@git_dir, 'refs', 'tags', tag_name)
|
767
897
|
return File.read(head).chomp if File.exist?(head)
|
768
898
|
|
769
|
-
command('show-ref',
|
899
|
+
command('show-ref', '--tags', '-s', tag_name)
|
770
900
|
end
|
771
901
|
|
772
902
|
def repack
|
773
|
-
command('repack',
|
903
|
+
command('repack', '-a', '-d')
|
774
904
|
end
|
775
905
|
|
776
906
|
def gc
|
777
|
-
command('gc',
|
907
|
+
command('gc', '--prune', '--aggressive', '--auto')
|
778
908
|
end
|
779
909
|
|
780
910
|
# reads a tree into the current index file
|
@@ -799,11 +929,11 @@ module Git
|
|
799
929
|
arr_opts << tree
|
800
930
|
arr_opts << '-p' << opts[:parent] if opts[:parent]
|
801
931
|
arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
|
802
|
-
command('commit-tree', arr_opts,
|
932
|
+
command('commit-tree', arr_opts, redirect: "< #{escape t.path}")
|
803
933
|
end
|
804
934
|
|
805
935
|
def update_ref(branch, commit)
|
806
|
-
command('update-ref',
|
936
|
+
command('update-ref', branch, commit)
|
807
937
|
end
|
808
938
|
|
809
939
|
def checkout_index(opts = {})
|
@@ -845,13 +975,19 @@ module Git
|
|
845
975
|
arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
|
846
976
|
arr_opts << sha
|
847
977
|
arr_opts << '--' << opts[:path] if opts[:path]
|
848
|
-
command('archive', arr_opts,
|
978
|
+
command('archive', arr_opts, redirect: " > #{escape file}")
|
979
|
+
if opts[:add_gzip]
|
980
|
+
file_content = File.read(file)
|
981
|
+
Zlib::GzipWriter.open(file) do |gz|
|
982
|
+
gz.write(file_content)
|
983
|
+
end
|
984
|
+
end
|
849
985
|
return file
|
850
986
|
end
|
851
987
|
|
852
988
|
# returns the current version of git, as an Array of Fixnums.
|
853
989
|
def current_command_version
|
854
|
-
output = command('version'
|
990
|
+
output = command('version')
|
855
991
|
version = output[/\d+\.\d+(\.\d+)+/]
|
856
992
|
version.split('.').collect {|i| i.to_i}
|
857
993
|
end
|
@@ -872,13 +1008,10 @@ module Git
|
|
872
1008
|
# @return [<String>] the names of the EVN variables involved in the git commands
|
873
1009
|
ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
|
874
1010
|
|
875
|
-
def command_lines(cmd, opts
|
876
|
-
cmd_op = command(cmd, opts
|
1011
|
+
def command_lines(cmd, *opts)
|
1012
|
+
cmd_op = command(cmd, *opts)
|
877
1013
|
if cmd_op.encoding.name != "UTF-8"
|
878
|
-
op = cmd_op.encode("UTF-8", "binary",
|
879
|
-
:invalid => :replace,
|
880
|
-
:undef => :replace
|
881
|
-
})
|
1014
|
+
op = cmd_op.encode("UTF-8", "binary", :invalid => :replace, :undef => :replace)
|
882
1015
|
else
|
883
1016
|
op = cmd_op
|
884
1017
|
end
|
@@ -922,16 +1055,25 @@ module Git
|
|
922
1055
|
restore_git_system_env_variables()
|
923
1056
|
end
|
924
1057
|
|
925
|
-
def command(cmd, opts
|
1058
|
+
def command(cmd, *opts, &block)
|
1059
|
+
command_opts = { chomp: true, redirect: '' }
|
1060
|
+
if opts.last.is_a?(Hash)
|
1061
|
+
command_opts.merge!(opts.pop)
|
1062
|
+
end
|
1063
|
+
command_opts.keys.each do |k|
|
1064
|
+
raise ArgumentError.new("Unsupported option: #{k}") unless [:chomp, :redirect].include?(k)
|
1065
|
+
end
|
1066
|
+
|
926
1067
|
global_opts = []
|
927
1068
|
global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
|
928
1069
|
global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
|
1070
|
+
global_opts << ["-c", "color.ui=false"]
|
929
1071
|
|
930
1072
|
opts = [opts].flatten.map {|s| escape(s) }.join(' ')
|
931
1073
|
|
932
1074
|
global_opts = global_opts.flatten.map {|s| escape(s) }.join(' ')
|
933
1075
|
|
934
|
-
git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{cmd} #{opts} #{redirect} 2>&1"
|
1076
|
+
git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{cmd} #{opts} #{command_opts[:redirect]} 2>&1"
|
935
1077
|
|
936
1078
|
output = nil
|
937
1079
|
|
@@ -952,11 +1094,12 @@ module Git
|
|
952
1094
|
@logger.debug(output)
|
953
1095
|
end
|
954
1096
|
|
955
|
-
|
956
|
-
|
957
|
-
end
|
1097
|
+
raise Git::GitExecuteError, "#{git_cmd}:#{output}" if
|
1098
|
+
exitstatus > 1 || (exitstatus == 1 && output != '')
|
958
1099
|
|
959
|
-
|
1100
|
+
output.chomp! if output && command_opts[:chomp] && !block_given?
|
1101
|
+
|
1102
|
+
output
|
960
1103
|
end
|
961
1104
|
|
962
1105
|
# Takes the diff command line output (as Array) and parse it into a Hash
|
@@ -965,6 +1108,8 @@ module Git
|
|
965
1108
|
# @param [Array] opts the diff options to be used
|
966
1109
|
# @return [Hash] the diff as Hash
|
967
1110
|
def diff_as_hash(diff_command, opts=[])
|
1111
|
+
# update index before diffing to avoid spurious diffs
|
1112
|
+
command('status')
|
968
1113
|
command_lines(diff_command, opts).inject({}) do |memo, line|
|
969
1114
|
info, file = line.split("\t")
|
970
1115
|
mode_src, mode_dest, sha_src, sha_dest, type = info.split
|
@@ -991,6 +1136,7 @@ module Git
|
|
991
1136
|
|
992
1137
|
arr_opts << "-#{opts[:count]}" if opts[:count]
|
993
1138
|
arr_opts << "--no-color"
|
1139
|
+
arr_opts << "--cherry" if opts[:cherry]
|
994
1140
|
arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
|
995
1141
|
arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
|
996
1142
|
arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
|
@@ -1012,18 +1158,54 @@ module Git
|
|
1012
1158
|
arr_opts
|
1013
1159
|
end
|
1014
1160
|
|
1161
|
+
def default_encoding
|
1162
|
+
__ENCODING__.name
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
def best_guess_encoding
|
1166
|
+
# Encoding::ASCII_8BIT.name
|
1167
|
+
Encoding::UTF_8.name
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
def detected_encoding(str)
|
1171
|
+
CharDet.detect(str)['encoding'] || best_guess_encoding
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
def encoding_options
|
1175
|
+
{ invalid: :replace, undef: :replace }
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
def normalize_encoding(str)
|
1179
|
+
return str if str.valid_encoding? && str.encoding.name == default_encoding
|
1180
|
+
|
1181
|
+
return str.encode(default_encoding, str.encoding, **encoding_options) if str.valid_encoding?
|
1182
|
+
|
1183
|
+
str.encode(default_encoding, detected_encoding(str), **encoding_options)
|
1184
|
+
end
|
1185
|
+
|
1015
1186
|
def run_command(git_cmd, &block)
|
1016
1187
|
return IO.popen(git_cmd, &block) if block_given?
|
1017
1188
|
|
1018
|
-
`#{git_cmd}`.
|
1189
|
+
`#{git_cmd}`.lines.map { |l| normalize_encoding(l) }.join
|
1019
1190
|
end
|
1020
1191
|
|
1021
1192
|
def escape(s)
|
1022
|
-
|
1193
|
+
windows_platform? ? escape_for_windows(s) : escape_for_sh(s)
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
def escape_for_sh(s)
|
1197
|
+
"'#{s && s.to_s.gsub('\'','\'"\'"\'')}'"
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
def escape_for_windows(s)
|
1201
|
+
# Windows does not need single quote escaping inside double quotes
|
1202
|
+
%Q{"#{s}"}
|
1203
|
+
end
|
1023
1204
|
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1205
|
+
def windows_platform?
|
1206
|
+
# Check if on Windows via RUBY_PLATFORM (CRuby) and RUBY_DESCRIPTION (JRuby)
|
1207
|
+
win_platform_regex = /mingw|mswin/
|
1208
|
+
RUBY_PLATFORM =~ win_platform_regex || RUBY_DESCRIPTION =~ win_platform_regex
|
1027
1209
|
end
|
1028
1210
|
|
1029
1211
|
end
|