git 1.6.0 → 1.9.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 +45 -0
- data/.gitignore +10 -0
- data/.yardopts +11 -0
- data/CHANGELOG.md +25 -0
- data/CONTRIBUTING.md +14 -7
- data/Gemfile +4 -0
- data/ISSUE_TEMPLATE.md +15 -0
- data/MAINTAINERS.md +5 -0
- data/PULL_REQUEST_TEMPLATE.md +9 -0
- data/README.md +268 -215
- data/RELEASING.md +62 -0
- data/Rakefile +56 -0
- data/git.gemspec +46 -0
- data/lib/git.rb +180 -41
- data/lib/git/base.rb +198 -152
- data/lib/git/base/factory.rb +40 -22
- data/lib/git/diff.rb +6 -5
- data/lib/git/lib.rb +222 -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 +86 -33
data/lib/git/base/factory.rb
CHANGED
@@ -3,74 +3,92 @@ 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
|
|
71
88
|
# Find as good common ancestors as possible for a merge
|
72
89
|
# example: g.merge_base('master', 'some_branch', 'some_sha', octopus: true)
|
73
|
-
#
|
90
|
+
#
|
91
|
+
# @return [Array<Git::Object::Commit>] a collection of common ancestors
|
74
92
|
def merge_base(*args)
|
75
93
|
shas = self.lib.merge_base(*args)
|
76
94
|
shas.map { |sha| gcommit(sha) }
|
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
|
@@ -132,7 +133,7 @@ module Git
|
|
132
133
|
current_file = m[1]
|
133
134
|
final[current_file] = defaults.merge({:patch => line, :path => current_file})
|
134
135
|
else
|
135
|
-
if m = /^index (
|
136
|
+
if m = /^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/.match(line)
|
136
137
|
final[current_file][:src] = m[1]
|
137
138
|
final[current_file][:dst] = m[2]
|
138
139
|
final[current_file][:mode] = m[3].strip if m[3]
|
data/lib/git/lib.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rchardet'
|
2
2
|
require 'tempfile'
|
3
|
+
require 'zlib'
|
3
4
|
|
4
5
|
module Git
|
5
6
|
|
@@ -10,6 +11,43 @@ module Git
|
|
10
11
|
|
11
12
|
@@semaphore = Mutex.new
|
12
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
|
+
#
|
13
51
|
def initialize(base = nil, logger = nil)
|
14
52
|
@git_dir = nil
|
15
53
|
@git_index_file = nil
|
@@ -38,14 +76,11 @@ module Git
|
|
38
76
|
arr_opts = []
|
39
77
|
arr_opts << '--bare' if opts[:bare]
|
40
78
|
|
41
|
-
command('init', arr_opts
|
79
|
+
command('init', arr_opts)
|
42
80
|
end
|
43
81
|
|
44
82
|
# tries to clone the given repo
|
45
83
|
#
|
46
|
-
# returns {:repository} (if bare)
|
47
|
-
# {:working_directory} otherwise
|
48
|
-
#
|
49
84
|
# accepts options:
|
50
85
|
# :bare:: no working directory
|
51
86
|
# :branch:: name of branch to track (rather than 'master')
|
@@ -57,6 +92,8 @@ module Git
|
|
57
92
|
#
|
58
93
|
# TODO - make this work with SSH password or auth_key
|
59
94
|
#
|
95
|
+
# @return [Hash] the options to pass to {Git::Base.new}
|
96
|
+
#
|
60
97
|
def clone(repository, name, opts = {})
|
61
98
|
@path = opts[:path] || '.'
|
62
99
|
clone_dir = opts[:path] ? File.join(@path, name) : name
|
@@ -77,9 +114,16 @@ module Git
|
|
77
114
|
|
78
115
|
command('clone', arr_opts)
|
79
116
|
|
80
|
-
(
|
117
|
+
return_base_opts_from_clone(clone_dir, opts)
|
81
118
|
end
|
82
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
|
83
127
|
|
84
128
|
## READ COMMANDS ##
|
85
129
|
|
@@ -114,12 +158,12 @@ module Git
|
|
114
158
|
arr_opts << '--always' if opts[:always]
|
115
159
|
arr_opts << '--exact-match' if opts[:exact_match] || opts[:"exact-match"]
|
116
160
|
|
117
|
-
arr_opts << '--dirty' if opts[
|
118
|
-
arr_opts << "--dirty=#{opts[
|
161
|
+
arr_opts << '--dirty' if opts[:dirty] == true
|
162
|
+
arr_opts << "--dirty=#{opts[:dirty]}" if opts[:dirty].is_a?(String)
|
119
163
|
|
120
|
-
arr_opts << "--abbrev=#{opts[
|
121
|
-
arr_opts << "--candidates=#{opts[
|
122
|
-
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]
|
123
167
|
|
124
168
|
arr_opts << committish if committish
|
125
169
|
|
@@ -133,7 +177,7 @@ module Git
|
|
133
177
|
|
134
178
|
arr_opts += log_path_options(opts)
|
135
179
|
|
136
|
-
command_lines('log', arr_opts
|
180
|
+
command_lines('log', arr_opts).map { |l| l.split.first }
|
137
181
|
end
|
138
182
|
|
139
183
|
def full_log_commits(opts={})
|
@@ -144,7 +188,7 @@ module Git
|
|
144
188
|
|
145
189
|
arr_opts += log_path_options(opts)
|
146
190
|
|
147
|
-
full_log = command_lines('log', arr_opts
|
191
|
+
full_log = command_lines('log', arr_opts)
|
148
192
|
|
149
193
|
process_commit_log_data(full_log)
|
150
194
|
end
|
@@ -165,17 +209,17 @@ module Git
|
|
165
209
|
end
|
166
210
|
|
167
211
|
def object_type(sha)
|
168
|
-
command('cat-file',
|
212
|
+
command('cat-file', '-t', sha)
|
169
213
|
end
|
170
214
|
|
171
215
|
def object_size(sha)
|
172
|
-
command('cat-file',
|
216
|
+
command('cat-file', '-s', sha).to_i
|
173
217
|
end
|
174
218
|
|
175
219
|
# returns useful array of raw commit object data
|
176
220
|
def commit_data(sha)
|
177
221
|
sha = sha.to_s
|
178
|
-
cdata = command_lines('cat-file',
|
222
|
+
cdata = command_lines('cat-file', 'commit', sha)
|
179
223
|
process_commit_data(cdata, sha, 0)
|
180
224
|
end
|
181
225
|
|
@@ -205,7 +249,7 @@ module Git
|
|
205
249
|
|
206
250
|
def tag_data(name)
|
207
251
|
sha = sha.to_s
|
208
|
-
tdata = command_lines('cat-file',
|
252
|
+
tdata = command_lines('cat-file', 'tag', name)
|
209
253
|
process_tag_data(tdata, name, 0)
|
210
254
|
end
|
211
255
|
|
@@ -243,6 +287,8 @@ module Git
|
|
243
287
|
next
|
244
288
|
end
|
245
289
|
|
290
|
+
in_message = false if in_message && line[0..3] != " "
|
291
|
+
|
246
292
|
if in_message
|
247
293
|
hsh['message'] << "#{line[4..-1]}\n"
|
248
294
|
next
|
@@ -268,7 +314,7 @@ module Git
|
|
268
314
|
end
|
269
315
|
|
270
316
|
def object_contents(sha, &block)
|
271
|
-
command('cat-file',
|
317
|
+
command('cat-file', '-p', sha, &block)
|
272
318
|
end
|
273
319
|
|
274
320
|
def ls_tree(sha)
|
@@ -284,11 +330,11 @@ module Git
|
|
284
330
|
end
|
285
331
|
|
286
332
|
def mv(file1, file2)
|
287
|
-
command_lines('mv',
|
333
|
+
command_lines('mv', '--', file1, file2)
|
288
334
|
end
|
289
335
|
|
290
336
|
def full_tree(sha)
|
291
|
-
command_lines('ls-tree',
|
337
|
+
command_lines('ls-tree', '-r', sha)
|
292
338
|
end
|
293
339
|
|
294
340
|
def tree_depth(sha)
|
@@ -296,7 +342,7 @@ module Git
|
|
296
342
|
end
|
297
343
|
|
298
344
|
def change_head_branch(branch_name)
|
299
|
-
command('symbolic-ref',
|
345
|
+
command('symbolic-ref', 'HEAD', "refs/heads/#{branch_name}")
|
300
346
|
end
|
301
347
|
|
302
348
|
def branches_all
|
@@ -308,6 +354,39 @@ module Git
|
|
308
354
|
arr
|
309
355
|
end
|
310
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
|
+
|
311
390
|
def list_files(ref_dir)
|
312
391
|
dir = File.join(@git_dir, 'refs', ref_dir)
|
313
392
|
files = []
|
@@ -403,7 +482,7 @@ module Git
|
|
403
482
|
def ls_files(location=nil)
|
404
483
|
location ||= '.'
|
405
484
|
hsh = {}
|
406
|
-
command_lines('ls-files',
|
485
|
+
command_lines('ls-files', '--stage', location).each do |line|
|
407
486
|
(info, file) = line.split("\t")
|
408
487
|
(mode, sha, stage) = info.split
|
409
488
|
file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
|
@@ -412,10 +491,13 @@ module Git
|
|
412
491
|
hsh
|
413
492
|
end
|
414
493
|
|
415
|
-
def ls_remote(location=nil)
|
416
|
-
|
494
|
+
def ls_remote(location=nil, opts={})
|
495
|
+
arr_opts = []
|
496
|
+
arr_opts << ['--refs'] if opts[:refs]
|
497
|
+
arr_opts << (location || '.')
|
498
|
+
|
417
499
|
Hash.new{ |h,k| h[k] = {} }.tap do |hsh|
|
418
|
-
command_lines('ls-remote',
|
500
|
+
command_lines('ls-remote', arr_opts).each do |line|
|
419
501
|
(sha, info) = line.split("\t")
|
420
502
|
(ref, type, name) = info.split('/', 3)
|
421
503
|
type ||= 'head'
|
@@ -427,7 +509,7 @@ module Git
|
|
427
509
|
end
|
428
510
|
|
429
511
|
def ignored_files
|
430
|
-
command_lines('ls-files',
|
512
|
+
command_lines('ls-files', '--others', '-i', '--exclude-standard')
|
431
513
|
end
|
432
514
|
|
433
515
|
|
@@ -442,8 +524,8 @@ module Git
|
|
442
524
|
end
|
443
525
|
|
444
526
|
def config_get(name)
|
445
|
-
do_get =
|
446
|
-
command('config',
|
527
|
+
do_get = Proc.new do |path|
|
528
|
+
command('config', '--get', name)
|
447
529
|
end
|
448
530
|
|
449
531
|
if @git_dir
|
@@ -454,12 +536,12 @@ module Git
|
|
454
536
|
end
|
455
537
|
|
456
538
|
def global_config_get(name)
|
457
|
-
command('config',
|
539
|
+
command('config', '--global', '--get', name)
|
458
540
|
end
|
459
541
|
|
460
542
|
def config_list
|
461
|
-
build_list =
|
462
|
-
parse_config_list command_lines('config',
|
543
|
+
build_list = Proc.new do |path|
|
544
|
+
parse_config_list command_lines('config', '--list')
|
463
545
|
end
|
464
546
|
|
465
547
|
if @git_dir
|
@@ -470,7 +552,7 @@ module Git
|
|
470
552
|
end
|
471
553
|
|
472
554
|
def global_config_list
|
473
|
-
parse_config_list command_lines('config',
|
555
|
+
parse_config_list command_lines('config', '--global', '--list')
|
474
556
|
end
|
475
557
|
|
476
558
|
def parse_config_list(lines)
|
@@ -483,7 +565,7 @@ module Git
|
|
483
565
|
end
|
484
566
|
|
485
567
|
def parse_config(file)
|
486
|
-
parse_config_list command_lines('config',
|
568
|
+
parse_config_list command_lines('config', '--list', '--file', file)
|
487
569
|
end
|
488
570
|
|
489
571
|
# Shows objects
|
@@ -496,17 +578,21 @@ module Git
|
|
496
578
|
|
497
579
|
arr_opts << (path ? "#{objectish}:#{path}" : objectish)
|
498
580
|
|
499
|
-
command('show', arr_opts.compact)
|
581
|
+
command('show', arr_opts.compact, chomp: false)
|
500
582
|
end
|
501
583
|
|
502
584
|
## WRITE COMMANDS ##
|
503
585
|
|
504
|
-
def config_set(name, value)
|
505
|
-
|
586
|
+
def config_set(name, value, options = {})
|
587
|
+
if options[:file].to_s.empty?
|
588
|
+
command('config', name, value)
|
589
|
+
else
|
590
|
+
command('config', '--file', options[:file], name, value)
|
591
|
+
end
|
506
592
|
end
|
507
593
|
|
508
594
|
def global_config_set(name, value)
|
509
|
-
command('config',
|
595
|
+
command('config', '--global', name, value)
|
510
596
|
end
|
511
597
|
|
512
598
|
# updates the repository index using the working directory content
|
@@ -550,6 +636,20 @@ module Git
|
|
550
636
|
command('rm', arr_opts)
|
551
637
|
end
|
552
638
|
|
639
|
+
# Takes the commit message with the options and executes the commit command
|
640
|
+
#
|
641
|
+
# accepts options:
|
642
|
+
# :amend
|
643
|
+
# :all
|
644
|
+
# :allow_empty
|
645
|
+
# :author
|
646
|
+
# :date
|
647
|
+
# :no_verify
|
648
|
+
# :allow_empty_message
|
649
|
+
# :gpg_sign
|
650
|
+
#
|
651
|
+
# @param [String] message the commit message to be used
|
652
|
+
# @param [Hash] opts the commit options to be used
|
553
653
|
def commit(message, opts = {})
|
554
654
|
arr_opts = []
|
555
655
|
arr_opts << "--message=#{message}" if message
|
@@ -558,6 +658,16 @@ module Git
|
|
558
658
|
arr_opts << '--allow-empty' if opts[:allow_empty]
|
559
659
|
arr_opts << "--author=#{opts[:author]}" if opts[:author]
|
560
660
|
arr_opts << "--date=#{opts[:date]}" if opts[:date].is_a? String
|
661
|
+
arr_opts << '--no-verify' if opts[:no_verify]
|
662
|
+
arr_opts << '--allow-empty-message' if opts[:allow_empty_message]
|
663
|
+
if opts[:gpg_sign]
|
664
|
+
arr_opts <<
|
665
|
+
if opts[:gpg_sign] == true
|
666
|
+
'--gpg-sign'
|
667
|
+
else
|
668
|
+
"--gpg-sign=#{opts[:gpg_sign]}"
|
669
|
+
end
|
670
|
+
end
|
561
671
|
|
562
672
|
command('commit', arr_opts)
|
563
673
|
end
|
@@ -616,13 +726,13 @@ module Git
|
|
616
726
|
end
|
617
727
|
|
618
728
|
def stash_save(message)
|
619
|
-
output = command('stash save',
|
729
|
+
output = command('stash save', message)
|
620
730
|
output =~ /HEAD is now at/
|
621
731
|
end
|
622
732
|
|
623
733
|
def stash_apply(id = nil)
|
624
734
|
if id
|
625
|
-
command('stash apply',
|
735
|
+
command('stash apply', id)
|
626
736
|
else
|
627
737
|
command('stash apply')
|
628
738
|
end
|
@@ -641,7 +751,7 @@ module Git
|
|
641
751
|
end
|
642
752
|
|
643
753
|
def branch_delete(branch)
|
644
|
-
command('branch',
|
754
|
+
command('branch', '-D', branch)
|
645
755
|
end
|
646
756
|
|
647
757
|
def checkout(branch, opts = {})
|
@@ -660,8 +770,9 @@ module Git
|
|
660
770
|
command('checkout', arr_opts)
|
661
771
|
end
|
662
772
|
|
663
|
-
def merge(branch, message = nil)
|
773
|
+
def merge(branch, message = nil, opts = {})
|
664
774
|
arr_opts = []
|
775
|
+
arr_opts << '--no-ff' if opts[:no_ff]
|
665
776
|
arr_opts << '-m' << message if message
|
666
777
|
arr_opts += [branch]
|
667
778
|
command('merge', arr_opts)
|
@@ -684,7 +795,7 @@ module Git
|
|
684
795
|
|
685
796
|
def unmerged
|
686
797
|
unmerged = []
|
687
|
-
command_lines('diff',
|
798
|
+
command_lines('diff', "--cached").each do |line|
|
688
799
|
unmerged << $1 if line =~ /^\* Unmerged path (.*)/
|
689
800
|
end
|
690
801
|
unmerged
|
@@ -692,11 +803,15 @@ module Git
|
|
692
803
|
|
693
804
|
def conflicts # :yields: file, your, their
|
694
805
|
self.unmerged.each do |f|
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
806
|
+
your_tempfile = Tempfile.new("YOUR-#{File.basename(f)}")
|
807
|
+
your = your_tempfile.path
|
808
|
+
your_tempfile.close # free up file for git command process
|
809
|
+
command('show', ":2:#{f}", redirect: "> #{escape your}")
|
810
|
+
|
811
|
+
their_tempfile = Tempfile.new("THEIR-#{File.basename(f)}")
|
812
|
+
their = their_tempfile.path
|
813
|
+
their_tempfile.close # free up file for git command process
|
814
|
+
command('show', ":3:#{f}", redirect: "> #{escape their}")
|
700
815
|
yield(f, your, their)
|
701
816
|
end
|
702
817
|
end
|
@@ -721,7 +836,7 @@ module Git
|
|
721
836
|
end
|
722
837
|
|
723
838
|
def remote_remove(name)
|
724
|
-
command('remote',
|
839
|
+
command('remote', 'rm', name)
|
725
840
|
end
|
726
841
|
|
727
842
|
def remotes
|
@@ -787,22 +902,22 @@ module Git
|
|
787
902
|
end
|
788
903
|
|
789
904
|
def pull(remote='origin', branch='master')
|
790
|
-
command('pull',
|
905
|
+
command('pull', remote, branch)
|
791
906
|
end
|
792
907
|
|
793
908
|
def tag_sha(tag_name)
|
794
909
|
head = File.join(@git_dir, 'refs', 'tags', tag_name)
|
795
910
|
return File.read(head).chomp if File.exist?(head)
|
796
911
|
|
797
|
-
command('show-ref',
|
912
|
+
command('show-ref', '--tags', '-s', tag_name)
|
798
913
|
end
|
799
914
|
|
800
915
|
def repack
|
801
|
-
command('repack',
|
916
|
+
command('repack', '-a', '-d')
|
802
917
|
end
|
803
918
|
|
804
919
|
def gc
|
805
|
-
command('gc',
|
920
|
+
command('gc', '--prune', '--aggressive', '--auto')
|
806
921
|
end
|
807
922
|
|
808
923
|
# reads a tree into the current index file
|
@@ -827,11 +942,11 @@ module Git
|
|
827
942
|
arr_opts << tree
|
828
943
|
arr_opts << '-p' << opts[:parent] if opts[:parent]
|
829
944
|
arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents]
|
830
|
-
command('commit-tree', arr_opts,
|
945
|
+
command('commit-tree', arr_opts, redirect: "< #{escape t.path}")
|
831
946
|
end
|
832
947
|
|
833
948
|
def update_ref(branch, commit)
|
834
|
-
command('update-ref',
|
949
|
+
command('update-ref', branch, commit)
|
835
950
|
end
|
836
951
|
|
837
952
|
def checkout_index(opts = {})
|
@@ -873,13 +988,19 @@ module Git
|
|
873
988
|
arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
|
874
989
|
arr_opts << sha
|
875
990
|
arr_opts << '--' << opts[:path] if opts[:path]
|
876
|
-
command('archive', arr_opts,
|
991
|
+
command('archive', arr_opts, redirect: " > #{escape file}")
|
992
|
+
if opts[:add_gzip]
|
993
|
+
file_content = File.read(file)
|
994
|
+
Zlib::GzipWriter.open(file) do |gz|
|
995
|
+
gz.write(file_content)
|
996
|
+
end
|
997
|
+
end
|
877
998
|
return file
|
878
999
|
end
|
879
1000
|
|
880
1001
|
# returns the current version of git, as an Array of Fixnums.
|
881
1002
|
def current_command_version
|
882
|
-
output = command('version'
|
1003
|
+
output = command('version')
|
883
1004
|
version = output[/\d+\.\d+(\.\d+)+/]
|
884
1005
|
version.split('.').collect {|i| i.to_i}
|
885
1006
|
end
|
@@ -900,8 +1021,14 @@ module Git
|
|
900
1021
|
# @return [<String>] the names of the EVN variables involved in the git commands
|
901
1022
|
ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
|
902
1023
|
|
903
|
-
def command_lines(cmd, opts
|
904
|
-
command(cmd, opts
|
1024
|
+
def command_lines(cmd, *opts)
|
1025
|
+
cmd_op = command(cmd, *opts)
|
1026
|
+
if cmd_op.encoding.name != "UTF-8"
|
1027
|
+
op = cmd_op.encode("UTF-8", "binary", :invalid => :replace, :undef => :replace)
|
1028
|
+
else
|
1029
|
+
op = cmd_op
|
1030
|
+
end
|
1031
|
+
op.split("\n")
|
905
1032
|
end
|
906
1033
|
|
907
1034
|
# Takes the current git's system ENV variables and store them.
|
@@ -941,16 +1068,25 @@ module Git
|
|
941
1068
|
restore_git_system_env_variables()
|
942
1069
|
end
|
943
1070
|
|
944
|
-
def command(cmd, opts
|
1071
|
+
def command(cmd, *opts, &block)
|
1072
|
+
command_opts = { chomp: true, redirect: '' }
|
1073
|
+
if opts.last.is_a?(Hash)
|
1074
|
+
command_opts.merge!(opts.pop)
|
1075
|
+
end
|
1076
|
+
command_opts.keys.each do |k|
|
1077
|
+
raise ArgumentError.new("Unsupported option: #{k}") unless [:chomp, :redirect].include?(k)
|
1078
|
+
end
|
1079
|
+
|
945
1080
|
global_opts = []
|
946
1081
|
global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
|
947
1082
|
global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
|
1083
|
+
global_opts << ["-c", "color.ui=false"]
|
948
1084
|
|
949
1085
|
opts = [opts].flatten.map {|s| escape(s) }.join(' ')
|
950
1086
|
|
951
1087
|
global_opts = global_opts.flatten.map {|s| escape(s) }.join(' ')
|
952
1088
|
|
953
|
-
git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{cmd} #{opts} #{redirect} 2>&1"
|
1089
|
+
git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{cmd} #{opts} #{command_opts[:redirect]} 2>&1"
|
954
1090
|
|
955
1091
|
output = nil
|
956
1092
|
|
@@ -971,11 +1107,12 @@ module Git
|
|
971
1107
|
@logger.debug(output)
|
972
1108
|
end
|
973
1109
|
|
974
|
-
|
975
|
-
|
976
|
-
|
1110
|
+
raise Git::GitExecuteError, "#{git_cmd}:#{output}" if
|
1111
|
+
exitstatus > 1 || (exitstatus == 1 && output != '')
|
1112
|
+
|
1113
|
+
output.chomp! if output && command_opts[:chomp] && !block_given?
|
977
1114
|
|
978
|
-
|
1115
|
+
output
|
979
1116
|
end
|
980
1117
|
|
981
1118
|
# Takes the diff command line output (as Array) and parse it into a Hash
|
@@ -984,6 +1121,8 @@ module Git
|
|
984
1121
|
# @param [Array] opts the diff options to be used
|
985
1122
|
# @return [Hash] the diff as Hash
|
986
1123
|
def diff_as_hash(diff_command, opts=[])
|
1124
|
+
# update index before diffing to avoid spurious diffs
|
1125
|
+
command('status')
|
987
1126
|
command_lines(diff_command, opts).inject({}) do |memo, line|
|
988
1127
|
info, file = line.split("\t")
|
989
1128
|
mode_src, mode_dest, sha_src, sha_dest, type = info.split
|
@@ -1010,6 +1149,7 @@ module Git
|
|
1010
1149
|
|
1011
1150
|
arr_opts << "-#{opts[:count]}" if opts[:count]
|
1012
1151
|
arr_opts << "--no-color"
|
1152
|
+
arr_opts << "--cherry" if opts[:cherry]
|
1013
1153
|
arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
|
1014
1154
|
arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
|
1015
1155
|
arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
|
@@ -1049,25 +1189,36 @@ module Git
|
|
1049
1189
|
end
|
1050
1190
|
|
1051
1191
|
def normalize_encoding(str)
|
1052
|
-
return str if str.valid_encoding? && str.encoding == default_encoding
|
1192
|
+
return str if str.valid_encoding? && str.encoding.name == default_encoding
|
1053
1193
|
|
1054
|
-
return str.encode(default_encoding, str.encoding, encoding_options) if str.valid_encoding?
|
1194
|
+
return str.encode(default_encoding, str.encoding, **encoding_options) if str.valid_encoding?
|
1055
1195
|
|
1056
|
-
str.encode(default_encoding, detected_encoding(str), encoding_options)
|
1196
|
+
str.encode(default_encoding, detected_encoding(str), **encoding_options)
|
1057
1197
|
end
|
1058
1198
|
|
1059
1199
|
def run_command(git_cmd, &block)
|
1060
1200
|
return IO.popen(git_cmd, &block) if block_given?
|
1061
1201
|
|
1062
|
-
`#{git_cmd}`.
|
1202
|
+
`#{git_cmd}`.lines.map { |l| normalize_encoding(l) }.join
|
1063
1203
|
end
|
1064
1204
|
|
1065
1205
|
def escape(s)
|
1066
|
-
|
1206
|
+
windows_platform? ? escape_for_windows(s) : escape_for_sh(s)
|
1207
|
+
end
|
1208
|
+
|
1209
|
+
def escape_for_sh(s)
|
1210
|
+
"'#{s && s.to_s.gsub('\'','\'"\'"\'')}'"
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
def escape_for_windows(s)
|
1214
|
+
# Windows does not need single quote escaping inside double quotes
|
1215
|
+
%Q{"#{s}"}
|
1216
|
+
end
|
1067
1217
|
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1218
|
+
def windows_platform?
|
1219
|
+
# Check if on Windows via RUBY_PLATFORM (CRuby) and RUBY_DESCRIPTION (JRuby)
|
1220
|
+
win_platform_regex = /mingw|mswin/
|
1221
|
+
RUBY_PLATFORM =~ win_platform_regex || RUBY_DESCRIPTION =~ win_platform_regex
|
1071
1222
|
end
|
1072
1223
|
|
1073
1224
|
end
|