git 1.5.0 → 1.8.1
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/.github/stale.yml +25 -0
- data/.github/workflows/continuous_integration.yml +42 -0
- data/.gitignore +10 -0
- data/.yardopts +11 -0
- data/CHANGELOG.md +25 -0
- data/CONTRIBUTING.md +94 -39
- data/Gemfile +4 -0
- data/ISSUE_TEMPLATE.md +15 -0
- data/MAINTAINERS.md +8 -2
- data/PULL_REQUEST_TEMPLATE.md +9 -0
- data/README.md +260 -214
- data/RELEASING.md +62 -0
- data/Rakefile +44 -0
- data/git.gemspec +46 -0
- 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 +1 -1
- data/lib/git/config.rb +1 -1
- data/lib/git/diff.rb +7 -11
- data/lib/git/lib.rb +242 -71
- data/lib/git/log.rb +8 -1
- data/lib/git/status.rb +1 -1
- data/lib/git/version.rb +1 -1
- data/lib/git/worktree.rb +38 -0
- data/lib/git/worktrees.rb +47 -0
- metadata +100 -34
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
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,17 +128,12 @@ module Git
|
|
127
128
|
}
|
128
129
|
final = {}
|
129
130
|
current_file = nil
|
130
|
-
|
131
|
-
full_diff_utf8_encoded = @full_diff.encode("UTF-8", "binary", { :invalid => :replace, :undef => :replace })
|
132
|
-
else
|
133
|
-
full_diff_utf8_encoded = @full_diff
|
134
|
-
end
|
135
|
-
full_diff_utf8_encoded.split("\n").each do |line|
|
131
|
+
@full_diff.split("\n").each do |line|
|
136
132
|
if m = /^diff --git a\/(.*?) b\/(.*?)/.match(line)
|
137
133
|
current_file = m[1]
|
138
134
|
final[current_file] = defaults.merge({:patch => line, :path => current_file})
|
139
135
|
else
|
140
|
-
if m = /^index (
|
136
|
+
if m = /^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/.match(line)
|
141
137
|
final[current_file][:src] = m[1]
|
142
138
|
final[current_file][:dst] = m[2]
|
143
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 = []
|
@@ -402,7 +482,7 @@ module Git
|
|
402
482
|
def ls_files(location=nil)
|
403
483
|
location ||= '.'
|
404
484
|
hsh = {}
|
405
|
-
command_lines('ls-files',
|
485
|
+
command_lines('ls-files', '--stage', location).each do |line|
|
406
486
|
(info, file) = line.split("\t")
|
407
487
|
(mode, sha, stage) = info.split
|
408
488
|
file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
|
@@ -411,10 +491,13 @@ module Git
|
|
411
491
|
hsh
|
412
492
|
end
|
413
493
|
|
414
|
-
def ls_remote(location=nil)
|
415
|
-
|
494
|
+
def ls_remote(location=nil, opts={})
|
495
|
+
arr_opts = []
|
496
|
+
arr_opts << ['--refs'] if opts[:refs]
|
497
|
+
arr_opts << (location || '.')
|
498
|
+
|
416
499
|
Hash.new{ |h,k| h[k] = {} }.tap do |hsh|
|
417
|
-
command_lines('ls-remote',
|
500
|
+
command_lines('ls-remote', arr_opts).each do |line|
|
418
501
|
(sha, info) = line.split("\t")
|
419
502
|
(ref, type, name) = info.split('/', 3)
|
420
503
|
type ||= 'head'
|
@@ -426,7 +509,7 @@ module Git
|
|
426
509
|
end
|
427
510
|
|
428
511
|
def ignored_files
|
429
|
-
command_lines('ls-files',
|
512
|
+
command_lines('ls-files', '--others', '-i', '--exclude-standard')
|
430
513
|
end
|
431
514
|
|
432
515
|
|
@@ -441,8 +524,8 @@ module Git
|
|
441
524
|
end
|
442
525
|
|
443
526
|
def config_get(name)
|
444
|
-
do_get =
|
445
|
-
command('config',
|
527
|
+
do_get = Proc.new do |path|
|
528
|
+
command('config', '--get', name)
|
446
529
|
end
|
447
530
|
|
448
531
|
if @git_dir
|
@@ -453,12 +536,12 @@ module Git
|
|
453
536
|
end
|
454
537
|
|
455
538
|
def global_config_get(name)
|
456
|
-
command('config',
|
539
|
+
command('config', '--global', '--get', name)
|
457
540
|
end
|
458
541
|
|
459
542
|
def config_list
|
460
|
-
build_list =
|
461
|
-
parse_config_list command_lines('config',
|
543
|
+
build_list = Proc.new do |path|
|
544
|
+
parse_config_list command_lines('config', '--list')
|
462
545
|
end
|
463
546
|
|
464
547
|
if @git_dir
|
@@ -469,7 +552,7 @@ module Git
|
|
469
552
|
end
|
470
553
|
|
471
554
|
def global_config_list
|
472
|
-
parse_config_list command_lines('config',
|
555
|
+
parse_config_list command_lines('config', '--global', '--list')
|
473
556
|
end
|
474
557
|
|
475
558
|
def parse_config_list(lines)
|
@@ -482,7 +565,7 @@ module Git
|
|
482
565
|
end
|
483
566
|
|
484
567
|
def parse_config(file)
|
485
|
-
parse_config_list command_lines('config',
|
568
|
+
parse_config_list command_lines('config', '--list', '--file', file)
|
486
569
|
end
|
487
570
|
|
488
571
|
# Shows objects
|
@@ -495,17 +578,17 @@ module Git
|
|
495
578
|
|
496
579
|
arr_opts << (path ? "#{objectish}:#{path}" : objectish)
|
497
580
|
|
498
|
-
command('show', arr_opts.compact)
|
581
|
+
command('show', arr_opts.compact, chomp: false)
|
499
582
|
end
|
500
583
|
|
501
584
|
## WRITE COMMANDS ##
|
502
585
|
|
503
586
|
def config_set(name, value)
|
504
|
-
command('config',
|
587
|
+
command('config', name, value)
|
505
588
|
end
|
506
589
|
|
507
590
|
def global_config_set(name, value)
|
508
|
-
command('config',
|
591
|
+
command('config', '--global', name, value)
|
509
592
|
end
|
510
593
|
|
511
594
|
# updates the repository index using the working directory content
|
@@ -549,6 +632,19 @@ module Git
|
|
549
632
|
command('rm', arr_opts)
|
550
633
|
end
|
551
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
|
552
648
|
def commit(message, opts = {})
|
553
649
|
arr_opts = []
|
554
650
|
arr_opts << "--message=#{message}" if message
|
@@ -557,6 +653,8 @@ module Git
|
|
557
653
|
arr_opts << '--allow-empty' if opts[:allow_empty]
|
558
654
|
arr_opts << "--author=#{opts[:author]}" if opts[:author]
|
559
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]
|
560
658
|
|
561
659
|
command('commit', arr_opts)
|
562
660
|
end
|
@@ -615,13 +713,13 @@ module Git
|
|
615
713
|
end
|
616
714
|
|
617
715
|
def stash_save(message)
|
618
|
-
output = command('stash save',
|
716
|
+
output = command('stash save', message)
|
619
717
|
output =~ /HEAD is now at/
|
620
718
|
end
|
621
719
|
|
622
720
|
def stash_apply(id = nil)
|
623
721
|
if id
|
624
|
-
command('stash apply',
|
722
|
+
command('stash apply', id)
|
625
723
|
else
|
626
724
|
command('stash apply')
|
627
725
|
end
|
@@ -640,7 +738,7 @@ module Git
|
|
640
738
|
end
|
641
739
|
|
642
740
|
def branch_delete(branch)
|
643
|
-
command('branch',
|
741
|
+
command('branch', '-D', branch)
|
644
742
|
end
|
645
743
|
|
646
744
|
def checkout(branch, opts = {})
|
@@ -659,16 +757,32 @@ module Git
|
|
659
757
|
command('checkout', arr_opts)
|
660
758
|
end
|
661
759
|
|
662
|
-
def merge(branch, message = nil)
|
760
|
+
def merge(branch, message = nil, opts = {})
|
663
761
|
arr_opts = []
|
762
|
+
arr_opts << '--no-ff' if opts[:no_ff]
|
664
763
|
arr_opts << '-m' << message if message
|
665
764
|
arr_opts += [branch]
|
666
765
|
command('merge', arr_opts)
|
667
766
|
end
|
668
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
|
+
|
669
783
|
def unmerged
|
670
784
|
unmerged = []
|
671
|
-
command_lines('diff',
|
785
|
+
command_lines('diff', "--cached").each do |line|
|
672
786
|
unmerged << $1 if line =~ /^\* Unmerged path (.*)/
|
673
787
|
end
|
674
788
|
unmerged
|
@@ -676,11 +790,15 @@ module Git
|
|
676
790
|
|
677
791
|
def conflicts # :yields: file, your, their
|
678
792
|
self.unmerged.each do |f|
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
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}")
|
684
802
|
yield(f, your, their)
|
685
803
|
end
|
686
804
|
end
|
@@ -705,7 +823,7 @@ module Git
|
|
705
823
|
end
|
706
824
|
|
707
825
|
def remote_remove(name)
|
708
|
-
command('remote',
|
826
|
+
command('remote', 'rm', name)
|
709
827
|
end
|
710
828
|
|
711
829
|
def remotes
|
@@ -747,6 +865,7 @@ module Git
|
|
747
865
|
arr_opts << opts[:ref] if opts[:ref]
|
748
866
|
arr_opts << '--tags' if opts[:t] || opts[:tags]
|
749
867
|
arr_opts << '--prune' if opts[:p] || opts[:prune]
|
868
|
+
arr_opts << '--unshallow' if opts[:unshallow]
|
750
869
|
|
751
870
|
command('fetch', arr_opts)
|
752
871
|
end
|
@@ -770,22 +889,22 @@ module Git
|
|
770
889
|
end
|
771
890
|
|
772
891
|
def pull(remote='origin', branch='master')
|
773
|
-
command('pull',
|
892
|
+
command('pull', remote, branch)
|
774
893
|
end
|
775
894
|
|
776
895
|
def tag_sha(tag_name)
|
777
896
|
head = File.join(@git_dir, 'refs', 'tags', tag_name)
|
778
897
|
return File.read(head).chomp if File.exist?(head)
|
779
898
|
|
780
|
-
command('show-ref',
|
899
|
+
command('show-ref', '--tags', '-s', tag_name)
|
781
900
|
end
|
782
901
|
|
783
902
|
def repack
|
784
|
-
command('repack',
|
903
|
+
command('repack', '-a', '-d')
|
785
904
|
end
|
786
905
|
|
787
906
|
def gc
|
788
|
-
command('gc',
|
907
|
+
command('gc', '--prune', '--aggressive', '--auto')
|
789
908
|
end
|
790
909
|
|
791
910
|
# reads a tree into the current index file
|
@@ -810,11 +929,11 @@ module Git
|
|
810
929
|
arr_opts << tree
|
811
930
|
arr_opts << '-p' << opts[:parent] if opts[:parent]
|
812
931
|
arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
|
813
|
-
command('commit-tree', arr_opts,
|
932
|
+
command('commit-tree', arr_opts, redirect: "< #{escape t.path}")
|
814
933
|
end
|
815
934
|
|
816
935
|
def update_ref(branch, commit)
|
817
|
-
command('update-ref',
|
936
|
+
command('update-ref', branch, commit)
|
818
937
|
end
|
819
938
|
|
820
939
|
def checkout_index(opts = {})
|
@@ -856,13 +975,19 @@ module Git
|
|
856
975
|
arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
|
857
976
|
arr_opts << sha
|
858
977
|
arr_opts << '--' << opts[:path] if opts[:path]
|
859
|
-
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
|
860
985
|
return file
|
861
986
|
end
|
862
987
|
|
863
988
|
# returns the current version of git, as an Array of Fixnums.
|
864
989
|
def current_command_version
|
865
|
-
output = command('version'
|
990
|
+
output = command('version')
|
866
991
|
version = output[/\d+\.\d+(\.\d+)+/]
|
867
992
|
version.split('.').collect {|i| i.to_i}
|
868
993
|
end
|
@@ -883,13 +1008,10 @@ module Git
|
|
883
1008
|
# @return [<String>] the names of the EVN variables involved in the git commands
|
884
1009
|
ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
|
885
1010
|
|
886
|
-
def command_lines(cmd, opts
|
887
|
-
cmd_op = command(cmd, opts
|
1011
|
+
def command_lines(cmd, *opts)
|
1012
|
+
cmd_op = command(cmd, *opts)
|
888
1013
|
if cmd_op.encoding.name != "UTF-8"
|
889
|
-
op = cmd_op.encode("UTF-8", "binary",
|
890
|
-
:invalid => :replace,
|
891
|
-
:undef => :replace
|
892
|
-
})
|
1014
|
+
op = cmd_op.encode("UTF-8", "binary", :invalid => :replace, :undef => :replace)
|
893
1015
|
else
|
894
1016
|
op = cmd_op
|
895
1017
|
end
|
@@ -933,16 +1055,25 @@ module Git
|
|
933
1055
|
restore_git_system_env_variables()
|
934
1056
|
end
|
935
1057
|
|
936
|
-
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
|
+
|
937
1067
|
global_opts = []
|
938
1068
|
global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
|
939
1069
|
global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
|
1070
|
+
global_opts << ["-c", "color.ui=false"]
|
940
1071
|
|
941
1072
|
opts = [opts].flatten.map {|s| escape(s) }.join(' ')
|
942
1073
|
|
943
1074
|
global_opts = global_opts.flatten.map {|s| escape(s) }.join(' ')
|
944
1075
|
|
945
|
-
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"
|
946
1077
|
|
947
1078
|
output = nil
|
948
1079
|
|
@@ -963,11 +1094,12 @@ module Git
|
|
963
1094
|
@logger.debug(output)
|
964
1095
|
end
|
965
1096
|
|
966
|
-
|
967
|
-
|
968
|
-
|
1097
|
+
raise Git::GitExecuteError, "#{git_cmd}:#{output}" if
|
1098
|
+
exitstatus > 1 || (exitstatus == 1 && output != '')
|
1099
|
+
|
1100
|
+
output.chomp! if output && command_opts[:chomp] && !block_given?
|
969
1101
|
|
970
|
-
|
1102
|
+
output
|
971
1103
|
end
|
972
1104
|
|
973
1105
|
# Takes the diff command line output (as Array) and parse it into a Hash
|
@@ -976,6 +1108,8 @@ module Git
|
|
976
1108
|
# @param [Array] opts the diff options to be used
|
977
1109
|
# @return [Hash] the diff as Hash
|
978
1110
|
def diff_as_hash(diff_command, opts=[])
|
1111
|
+
# update index before diffing to avoid spurious diffs
|
1112
|
+
command('status')
|
979
1113
|
command_lines(diff_command, opts).inject({}) do |memo, line|
|
980
1114
|
info, file = line.split("\t")
|
981
1115
|
mode_src, mode_dest, sha_src, sha_dest, type = info.split
|
@@ -1002,6 +1136,7 @@ module Git
|
|
1002
1136
|
|
1003
1137
|
arr_opts << "-#{opts[:count]}" if opts[:count]
|
1004
1138
|
arr_opts << "--no-color"
|
1139
|
+
arr_opts << "--cherry" if opts[:cherry]
|
1005
1140
|
arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
|
1006
1141
|
arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
|
1007
1142
|
arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
|
@@ -1023,18 +1158,54 @@ module Git
|
|
1023
1158
|
arr_opts
|
1024
1159
|
end
|
1025
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
|
+
|
1026
1186
|
def run_command(git_cmd, &block)
|
1027
1187
|
return IO.popen(git_cmd, &block) if block_given?
|
1028
1188
|
|
1029
|
-
`#{git_cmd}`.
|
1189
|
+
`#{git_cmd}`.lines.map { |l| normalize_encoding(l) }.join
|
1030
1190
|
end
|
1031
1191
|
|
1032
1192
|
def escape(s)
|
1033
|
-
|
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
|
1034
1204
|
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
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
|
1038
1209
|
end
|
1039
1210
|
|
1040
1211
|
end
|