git 1.19.1 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/pull_request_template.md +8 -0
- data/.github/workflows/continuous_integration.yml +14 -20
- data/.github/workflows/experimental_continuous_integration.yml +43 -0
- data/CHANGELOG.md +88 -0
- data/CONTRIBUTING.md +22 -67
- data/README.md +166 -52
- data/RELEASING.md +49 -34
- data/git.gemspec +8 -8
- data/lib/git/base.rb +173 -47
- data/lib/git/command_line.rb +377 -0
- data/lib/git/config.rb +5 -1
- data/lib/git/errors.rb +206 -0
- data/lib/git/escaped_path.rb +1 -1
- data/lib/git/lib.rb +244 -177
- data/lib/git/log.rb +65 -4
- data/lib/git/object.rb +69 -67
- data/lib/git/status.rb +132 -24
- data/lib/git/version.rb +1 -1
- data/lib/git.rb +8 -7
- metadata +41 -30
- data/.github/stale.yml +0 -25
- data/Dockerfile.changelog-rs +0 -12
- data/PULL_REQUEST_TEMPLATE.md +0 -9
- data/lib/git/base/factory.rb +0 -99
- data/lib/git/failed_error.rb +0 -53
- data/lib/git/git_execute_error.rb +0 -7
- data/lib/git/signaled_error.rb +0 -50
- /data/{ISSUE_TEMPLATE.md → .github/issue_template.md} +0 -0
data/lib/git/lib.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
-
require 'git/
|
1
|
+
require 'git/command_line'
|
2
|
+
require 'git/errors'
|
2
3
|
require 'logger'
|
4
|
+
require 'pp'
|
5
|
+
require 'process_executer'
|
6
|
+
require 'stringio'
|
3
7
|
require 'tempfile'
|
4
8
|
require 'zlib'
|
5
9
|
require 'open3'
|
6
10
|
|
7
11
|
module Git
|
8
12
|
class Lib
|
9
|
-
|
10
|
-
@@semaphore = Mutex.new
|
11
|
-
|
12
13
|
# The path to the Git working copy. The default is '"./.git"'.
|
13
14
|
#
|
14
15
|
# @return [Pathname] the path to the Git working copy.
|
@@ -37,14 +38,23 @@ module Git
|
|
37
38
|
|
38
39
|
# Create a new Git::Lib object
|
39
40
|
#
|
40
|
-
# @
|
41
|
-
#
|
41
|
+
# @overload initialize(base, logger)
|
42
|
+
#
|
43
|
+
# @param base [Hash] the hash containing paths to the Git working copy,
|
44
|
+
# the Git repository directory, and the Git index file.
|
45
|
+
#
|
46
|
+
# @option base [Pathname] :working_directory
|
47
|
+
# @option base [Pathname] :repository
|
48
|
+
# @option base [Pathname] :index
|
42
49
|
#
|
43
|
-
#
|
50
|
+
# @param [Logger] logger
|
44
51
|
#
|
45
|
-
# @
|
46
|
-
#
|
47
|
-
#
|
52
|
+
# @overload initialize(base, logger)
|
53
|
+
#
|
54
|
+
# @param base [#dir, #repo, #index] an object with methods to get the Git worktree (#dir),
|
55
|
+
# the Git repository directory (#repo), and the Git index file (#index).
|
56
|
+
#
|
57
|
+
# @param [Logger] logger
|
48
58
|
#
|
49
59
|
def initialize(base = nil, logger = nil)
|
50
60
|
@git_dir = nil
|
@@ -79,22 +89,34 @@ module Git
|
|
79
89
|
command('init', *arr_opts)
|
80
90
|
end
|
81
91
|
|
82
|
-
#
|
92
|
+
# Clones a repository into a newly created directory
|
83
93
|
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
# :branch:: name of branch to track (rather than 'master')
|
87
|
-
# :depth:: the number of commits back to pull
|
88
|
-
# :filter:: specify partial clone
|
89
|
-
# :origin:: name of remote (same as remote)
|
90
|
-
# :path:: directory where the repo will be cloned
|
91
|
-
# :remote:: name of remote (rather than 'origin')
|
92
|
-
# :recursive:: after the clone is created, initialize all submodules within, using their default settings.
|
94
|
+
# @param [String] repository_url the URL of the repository to clone
|
95
|
+
# @param [String, nil] directory the directory to clone into
|
93
96
|
#
|
94
|
-
#
|
97
|
+
# If nil, the repository is cloned into a directory with the same name as
|
98
|
+
# the repository.
|
99
|
+
#
|
100
|
+
# @param [Hash] opts the options for this command
|
101
|
+
#
|
102
|
+
# @option opts [Boolean] :bare (false) if true, clone as a bare repository
|
103
|
+
# @option opts [String] :branch the branch to checkout
|
104
|
+
# @option opts [String, Array] :config one or more configuration options to set
|
105
|
+
# @option opts [Integer] :depth the number of commits back to pull
|
106
|
+
# @option opts [String] :filter specify partial clone
|
107
|
+
# @option opts [String] :mirror set up a mirror of the source repository
|
108
|
+
# @option opts [String] :origin the name of the remote
|
109
|
+
# @option opts [String] :path an optional prefix for the directory parameter
|
110
|
+
# @option opts [String] :remote the name of the remote
|
111
|
+
# @option opts [Boolean] :recursive after the clone is created, initialize all submodules within, using their default settings
|
112
|
+
# @option opts [Numeric, nil] :timeout the number of seconds to wait for the command to complete
|
113
|
+
#
|
114
|
+
# See {Git::Lib#command} for more information about :timeout
|
95
115
|
#
|
96
116
|
# @return [Hash] the options to pass to {Git::Base.new}
|
97
117
|
#
|
118
|
+
# @todo make this work with SSH password or auth_key
|
119
|
+
#
|
98
120
|
def clone(repository_url, directory, opts = {})
|
99
121
|
@path = opts[:path] || '.'
|
100
122
|
clone_dir = opts[:path] ? File.join(@path, directory) : directory
|
@@ -114,7 +136,7 @@ module Git
|
|
114
136
|
arr_opts << repository_url
|
115
137
|
arr_opts << clone_dir
|
116
138
|
|
117
|
-
command('clone', *arr_opts)
|
139
|
+
command('clone', *arr_opts, timeout: opts[:timeout])
|
118
140
|
|
119
141
|
return_base_opts_from_clone(clone_dir, opts)
|
120
142
|
end
|
@@ -142,7 +164,7 @@ module Git
|
|
142
164
|
match_data = output.match(%r{^ref: refs/heads/(?<default_branch>[^\t]+)\tHEAD$})
|
143
165
|
return match_data[:default_branch] if match_data
|
144
166
|
|
145
|
-
raise 'Unable to determine the default branch'
|
167
|
+
raise Git::UnexpectedResultError, 'Unable to determine the default branch'
|
146
168
|
end
|
147
169
|
|
148
170
|
## READ COMMANDS ##
|
@@ -337,7 +359,19 @@ module Git
|
|
337
359
|
end
|
338
360
|
|
339
361
|
def object_contents(sha, &block)
|
340
|
-
|
362
|
+
if block_given?
|
363
|
+
Tempfile.create do |file|
|
364
|
+
# If a block is given, write the output from the process to a temporary
|
365
|
+
# file and then yield the file to the block
|
366
|
+
#
|
367
|
+
command('cat-file', "-p", sha, out: file, err: file)
|
368
|
+
file.rewind
|
369
|
+
yield file
|
370
|
+
end
|
371
|
+
else
|
372
|
+
# If a block is not given, return stdout
|
373
|
+
command('cat-file', '-p', sha)
|
374
|
+
end
|
341
375
|
end
|
342
376
|
|
343
377
|
def ls_tree(sha)
|
@@ -395,7 +429,7 @@ module Git
|
|
395
429
|
def branches_all
|
396
430
|
command_lines('branch', '-a').map do |line|
|
397
431
|
match_data = line.match(BRANCH_LINE_REGEXP)
|
398
|
-
raise
|
432
|
+
raise Git::UnexpectedResultError, 'Unexpected branch line format' unless match_data
|
399
433
|
next nil if match_data[:not_a_branch] || match_data[:detached_ref]
|
400
434
|
[
|
401
435
|
match_data[:refname],
|
@@ -474,11 +508,15 @@ module Git
|
|
474
508
|
grep_opts.push('--', *opts[:path_limiter]) if opts[:path_limiter].is_a?(Array)
|
475
509
|
|
476
510
|
hsh = {}
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
511
|
+
begin
|
512
|
+
command_lines('grep', *grep_opts).each do |line|
|
513
|
+
if m = /(.*?)\:(\d+)\:(.*)/.match(line)
|
514
|
+
hsh[m[1]] ||= []
|
515
|
+
hsh[m[1]] << [m[2].to_i, m[3]]
|
516
|
+
end
|
481
517
|
end
|
518
|
+
rescue Git::FailedError => e
|
519
|
+
raise unless e.result.status.exitstatus == 1 && e.result.stderr == ''
|
482
520
|
end
|
483
521
|
hsh
|
484
522
|
end
|
@@ -536,18 +574,52 @@ module Git
|
|
536
574
|
diff_as_hash('diff-index', treeish)
|
537
575
|
end
|
538
576
|
|
577
|
+
# List all files that are in the index
|
578
|
+
#
|
579
|
+
# @param location [String] the location to list the files from
|
580
|
+
#
|
581
|
+
# @return [Hash<String, Hash>] a hash of files in the index
|
582
|
+
# * key: file [String] the file path
|
583
|
+
# * value: file_info [Hash] the file information containing the following keys:
|
584
|
+
# * :path [String] the file path
|
585
|
+
# * :mode_index [String] the file mode
|
586
|
+
# * :sha_index [String] the file sha
|
587
|
+
# * :stage [String] the file stage
|
588
|
+
#
|
539
589
|
def ls_files(location=nil)
|
540
590
|
location ||= '.'
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
591
|
+
{}.tap do |files|
|
592
|
+
command_lines('ls-files', '--stage', location).each do |line|
|
593
|
+
(info, file) = line.split("\t")
|
594
|
+
(mode, sha, stage) = info.split
|
595
|
+
files[unescape_quoted_path(file)] = {
|
596
|
+
:path => file, :mode_index => mode, :sha_index => sha, :stage => stage
|
597
|
+
}
|
547
598
|
end
|
548
|
-
hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
|
549
599
|
end
|
550
|
-
|
600
|
+
end
|
601
|
+
|
602
|
+
# Unescape a path if it is quoted
|
603
|
+
#
|
604
|
+
# Git commands that output paths (e.g. ls-files, diff), will escape unusual
|
605
|
+
# characters.
|
606
|
+
#
|
607
|
+
# @example
|
608
|
+
# lib.unescape_if_quoted('"quoted_file_\\342\\230\\240"') # => 'quoted_file_☠'
|
609
|
+
# lib.unescape_if_quoted('unquoted_file') # => 'unquoted_file'
|
610
|
+
#
|
611
|
+
# @param path [String] the path to unescape if quoted
|
612
|
+
#
|
613
|
+
# @return [String] the unescaped path if quoted otherwise the original path
|
614
|
+
#
|
615
|
+
# @api private
|
616
|
+
#
|
617
|
+
def unescape_quoted_path(path)
|
618
|
+
if path.start_with?('"') && path.end_with?('"')
|
619
|
+
Git::EscapedPath.new(path[1..-2]).unescape
|
620
|
+
else
|
621
|
+
path
|
622
|
+
end
|
551
623
|
end
|
552
624
|
|
553
625
|
def ls_remote(location=nil, opts={})
|
@@ -568,9 +640,12 @@ module Git
|
|
568
640
|
end
|
569
641
|
|
570
642
|
def ignored_files
|
571
|
-
command_lines('ls-files', '--others', '-i', '--exclude-standard')
|
643
|
+
command_lines('ls-files', '--others', '-i', '--exclude-standard').map { |f| unescape_quoted_path(f) }
|
572
644
|
end
|
573
645
|
|
646
|
+
def untracked_files
|
647
|
+
command_lines('ls-files', '--others', '--exclude-standard', chdir: @git_work_dir)
|
648
|
+
end
|
574
649
|
|
575
650
|
def config_remote(name)
|
576
651
|
hsh = {}
|
@@ -638,18 +713,20 @@ module Git
|
|
638
713
|
command('config', '--global', name, value)
|
639
714
|
end
|
640
715
|
|
641
|
-
|
642
|
-
#
|
643
|
-
# lib.add('path/to/file')
|
644
|
-
# lib.add(['path/to/file1','path/to/file2'])
|
645
|
-
# lib.add(:all => true)
|
716
|
+
|
717
|
+
# Update the index from the current worktree to prepare the for the next commit
|
646
718
|
#
|
647
|
-
#
|
648
|
-
#
|
649
|
-
#
|
719
|
+
# @example
|
720
|
+
# lib.add('path/to/file')
|
721
|
+
# lib.add(['path/to/file1','path/to/file2'])
|
722
|
+
# lib.add(:all => true)
|
650
723
|
#
|
651
|
-
# @param [String,Array] paths files
|
724
|
+
# @param [String, Array<String>] paths files to be added to the repository (relative to the worktree root)
|
652
725
|
# @param [Hash] options
|
726
|
+
#
|
727
|
+
# @option options [Boolean] :all Add, modify, and remove index entries to match the worktree
|
728
|
+
# @option options [Boolean] :force Allow adding otherwise ignored files
|
729
|
+
#
|
653
730
|
def add(paths='.',options={})
|
654
731
|
arr_opts = []
|
655
732
|
|
@@ -675,6 +752,19 @@ module Git
|
|
675
752
|
command('rm', *arr_opts)
|
676
753
|
end
|
677
754
|
|
755
|
+
# Returns true if the repository is empty (meaning it has no commits)
|
756
|
+
#
|
757
|
+
# @return [Boolean]
|
758
|
+
#
|
759
|
+
def empty?
|
760
|
+
command('rev-parse', '--verify', 'HEAD')
|
761
|
+
false
|
762
|
+
rescue Git::FailedError => e
|
763
|
+
raise unless e.result.status.exitstatus == 128 &&
|
764
|
+
e.result.stderr == 'fatal: Needed a single revision'
|
765
|
+
true
|
766
|
+
end
|
767
|
+
|
678
768
|
# Takes the commit message with the options and executes the commit command
|
679
769
|
#
|
680
770
|
# accepts options:
|
@@ -865,16 +955,17 @@ module Git
|
|
865
955
|
|
866
956
|
def conflicts # :yields: file, your, their
|
867
957
|
self.unmerged.each do |f|
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
958
|
+
Tempfile.create("YOUR-#{File.basename(f)}") do |your|
|
959
|
+
command('show', ":2:#{f}", out: your)
|
960
|
+
your.close
|
961
|
+
|
962
|
+
Tempfile.create("THEIR-#{File.basename(f)}") do |their|
|
963
|
+
command('show', ":3:#{f}", out: their)
|
964
|
+
their.close
|
965
|
+
|
966
|
+
yield(f, your.path, their.path)
|
967
|
+
end
|
968
|
+
end
|
878
969
|
end
|
879
970
|
end
|
880
971
|
|
@@ -915,7 +1006,7 @@ module Git
|
|
915
1006
|
opts = opts.last.instance_of?(Hash) ? opts.last : {}
|
916
1007
|
|
917
1008
|
if (opts[:a] || opts[:annotate]) && !(opts[:m] || opts[:message])
|
918
|
-
raise
|
1009
|
+
raise ArgumentError, 'Cannot create an annotated tag without a message.'
|
919
1010
|
end
|
920
1011
|
|
921
1012
|
arr_opts = []
|
@@ -948,7 +1039,7 @@ module Git
|
|
948
1039
|
arr_opts << remote if remote
|
949
1040
|
arr_opts << opts[:ref] if opts[:ref]
|
950
1041
|
|
951
|
-
command('fetch', *arr_opts)
|
1042
|
+
command('fetch', *arr_opts, merge: true)
|
952
1043
|
end
|
953
1044
|
|
954
1045
|
def push(remote = nil, branch = nil, opts = nil)
|
@@ -988,10 +1079,11 @@ module Git
|
|
988
1079
|
end
|
989
1080
|
end
|
990
1081
|
|
991
|
-
def pull(remote = nil, branch = nil)
|
1082
|
+
def pull(remote = nil, branch = nil, opts = {})
|
992
1083
|
raise ArgumentError, "You must specify a remote if a branch is specified" if remote.nil? && !branch.nil?
|
993
1084
|
|
994
1085
|
arr_opts = []
|
1086
|
+
arr_opts << '--allow-unrelated-histories' if opts[:allow_unrelated_histories]
|
995
1087
|
arr_opts << remote if remote
|
996
1088
|
arr_opts << branch if branch
|
997
1089
|
command('pull', *arr_opts)
|
@@ -1001,7 +1093,13 @@ module Git
|
|
1001
1093
|
head = File.join(@git_dir, 'refs', 'tags', tag_name)
|
1002
1094
|
return File.read(head).chomp if File.exist?(head)
|
1003
1095
|
|
1004
|
-
|
1096
|
+
begin
|
1097
|
+
command('show-ref', '--tags', '-s', tag_name)
|
1098
|
+
rescue Git::FailedError => e
|
1099
|
+
raise unless e.result.status.exitstatus == 1 && e.result.stderr == ''
|
1100
|
+
|
1101
|
+
''
|
1102
|
+
end
|
1005
1103
|
end
|
1006
1104
|
|
1007
1105
|
def repack
|
@@ -1026,15 +1124,12 @@ module Git
|
|
1026
1124
|
|
1027
1125
|
def commit_tree(tree, opts = {})
|
1028
1126
|
opts[:message] ||= "commit tree #{tree}"
|
1029
|
-
t = Tempfile.new('commit-message')
|
1030
|
-
t.write(opts[:message])
|
1031
|
-
t.close
|
1032
|
-
|
1033
1127
|
arr_opts = []
|
1034
1128
|
arr_opts << tree
|
1035
1129
|
arr_opts << '-p' << opts[:parent] if opts[:parent]
|
1036
|
-
|
1037
|
-
|
1130
|
+
Array(opts[:parents]).each { |p| arr_opts << '-p' << p } if opts[:parents]
|
1131
|
+
arr_opts << '-m' << opts[:message]
|
1132
|
+
command('commit-tree', *arr_opts)
|
1038
1133
|
end
|
1039
1134
|
|
1040
1135
|
def update_ref(ref, commit)
|
@@ -1080,7 +1175,11 @@ module Git
|
|
1080
1175
|
arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
|
1081
1176
|
arr_opts << sha
|
1082
1177
|
arr_opts << '--' << opts[:path] if opts[:path]
|
1083
|
-
|
1178
|
+
|
1179
|
+
f = File.open(file, 'wb')
|
1180
|
+
command('archive', *arr_opts, out: f)
|
1181
|
+
f.close
|
1182
|
+
|
1084
1183
|
if opts[:add_gzip]
|
1085
1184
|
file_content = File.read(file)
|
1086
1185
|
Zlib::GzipWriter.open(file) do |gz|
|
@@ -1115,7 +1214,7 @@ module Git
|
|
1115
1214
|
end
|
1116
1215
|
|
1117
1216
|
def required_command_version
|
1118
|
-
[
|
1217
|
+
[2, 28]
|
1119
1218
|
end
|
1120
1219
|
|
1121
1220
|
def meets_required_version?
|
@@ -1133,11 +1232,6 @@ module Git
|
|
1133
1232
|
|
1134
1233
|
private
|
1135
1234
|
|
1136
|
-
# Systen ENV variables involved in the git commands.
|
1137
|
-
#
|
1138
|
-
# @return [<String>] the names of the EVN variables involved in the git commands
|
1139
|
-
ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
|
1140
|
-
|
1141
1235
|
def command_lines(cmd, *opts, chdir: nil)
|
1142
1236
|
cmd_op = command(cmd, *opts, chdir: chdir)
|
1143
1237
|
if cmd_op.encoding.name != "UTF-8"
|
@@ -1148,84 +1242,90 @@ module Git
|
|
1148
1242
|
op.split("\n")
|
1149
1243
|
end
|
1150
1244
|
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1245
|
+
def env_overrides
|
1246
|
+
{
|
1247
|
+
'GIT_DIR' => @git_dir,
|
1248
|
+
'GIT_WORK_TREE' => @git_work_dir,
|
1249
|
+
'GIT_INDEX_FILE' => @git_index_file,
|
1250
|
+
'GIT_SSH' => Git::Base.config.git_ssh
|
1251
|
+
}
|
1157
1252
|
end
|
1158
1253
|
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1254
|
+
def global_opts
|
1255
|
+
Array.new.tap do |global_opts|
|
1256
|
+
global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
|
1257
|
+
global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
|
1258
|
+
global_opts << '-c' << 'core.quotePath=true'
|
1259
|
+
global_opts << '-c' << 'color.ui=false'
|
1260
|
+
global_opts << '-c' << 'color.advice=false'
|
1261
|
+
global_opts << '-c' << 'color.diff=false'
|
1262
|
+
global_opts << '-c' << 'color.grep=false'
|
1263
|
+
global_opts << '-c' << 'color.push=false'
|
1264
|
+
global_opts << '-c' << 'color.remote=false'
|
1265
|
+
global_opts << '-c' << 'color.showBranch=false'
|
1266
|
+
global_opts << '-c' << 'color.status=false'
|
1267
|
+
global_opts << '-c' << 'color.transport=false'
|
1163
1268
|
end
|
1164
1269
|
end
|
1165
1270
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
ENV['GIT_WORK_TREE'] = @git_work_dir
|
1170
|
-
ENV['GIT_INDEX_FILE'] = @git_index_file
|
1171
|
-
ENV['GIT_SSH'] = Git::Base.config.git_ssh
|
1271
|
+
def command_line
|
1272
|
+
@command_line ||=
|
1273
|
+
Git::CommandLine.new(env_overrides, Git::Base.config.binary_path, global_opts, @logger)
|
1172
1274
|
end
|
1173
1275
|
|
1174
|
-
# Runs a
|
1175
|
-
# It restores the ENV after execution.
|
1276
|
+
# Runs a git command and returns the output
|
1176
1277
|
#
|
1177
|
-
# @param [
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
output
|
1278
|
+
# @param args [Array] the git command to run and its arguments
|
1279
|
+
#
|
1280
|
+
# This should exclude the 'git' command itself and global options.
|
1281
|
+
#
|
1282
|
+
# For example, to run `git log --pretty=oneline`, you would pass `['log',
|
1283
|
+
# '--pretty=oneline']`
|
1284
|
+
#
|
1285
|
+
# @param out [String, nil] the path to a file or an IO to write the command's
|
1286
|
+
# stdout to
|
1287
|
+
#
|
1288
|
+
# @param err [String, nil] the path to a file or an IO to write the command's
|
1289
|
+
# stdout to
|
1290
|
+
#
|
1291
|
+
# @param normalize [Boolean] true to normalize the output encoding
|
1292
|
+
#
|
1293
|
+
# @param chomp [Boolean] true to remove trailing newlines from the output
|
1294
|
+
#
|
1295
|
+
# @param merge [Boolean] true to merge stdout and stderr
|
1296
|
+
#
|
1297
|
+
# @param chdir [String, nil] the directory to run the command in
|
1298
|
+
#
|
1299
|
+
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
|
1300
|
+
#
|
1301
|
+
# If timeout is nil, the global timeout from {Git::Config} is used.
|
1302
|
+
#
|
1303
|
+
# If timeout is zero, the timeout will not be enforced.
|
1304
|
+
#
|
1305
|
+
# If the command times out, it is killed via a `SIGKILL` signal and `Git::TimeoutError` is raised.
|
1306
|
+
#
|
1307
|
+
# If the command does not respond to SIGKILL, it will hang this method.
|
1308
|
+
#
|
1309
|
+
# @see Git::CommandLine#run
|
1310
|
+
#
|
1311
|
+
# @return [String] the command's stdout (or merged stdout and stderr if `merge`
|
1312
|
+
# is true)
|
1313
|
+
#
|
1314
|
+
# @raise [Git::FailedError] if the command failed
|
1315
|
+
# @raise [Git::SignaledError] if the command was signaled
|
1316
|
+
# @raise [Git::TimeoutError] if the command times out
|
1317
|
+
# @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
|
1318
|
+
#
|
1319
|
+
# The exception's `result` attribute is a {Git::CommandLineResult} which will
|
1320
|
+
# contain the result of the command including the exit status, stdout, and
|
1321
|
+
# stderr.
|
1322
|
+
#
|
1323
|
+
# @api private
|
1324
|
+
#
|
1325
|
+
def command(*args, out: nil, err: nil, normalize: true, chomp: true, merge: false, chdir: nil, timeout: nil)
|
1326
|
+
timeout = timeout || Git.config.timeout
|
1327
|
+
result = command_line.run(*args, out: out, err: err, normalize: normalize, chomp: chomp, merge: merge, chdir: chdir, timeout: timeout)
|
1328
|
+
result.stdout
|
1229
1329
|
end
|
1230
1330
|
|
1231
1331
|
# Takes the diff command line output (as Array) and parse it into a Hash
|
@@ -1291,38 +1391,5 @@ module Git
|
|
1291
1391
|
end
|
1292
1392
|
arr_opts
|
1293
1393
|
end
|
1294
|
-
|
1295
|
-
def run_command(git_cmd, chdir=nil, &block)
|
1296
|
-
block ||= Proc.new do |io|
|
1297
|
-
io.readlines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join
|
1298
|
-
end
|
1299
|
-
|
1300
|
-
opts = {}
|
1301
|
-
opts[:chdir] = File.expand_path(chdir) if chdir
|
1302
|
-
|
1303
|
-
Open3.popen2(git_cmd, opts) do |stdin, stdout, wait_thr|
|
1304
|
-
[block.call(stdout), wait_thr.value]
|
1305
|
-
end
|
1306
|
-
end
|
1307
|
-
|
1308
|
-
def escape(s)
|
1309
|
-
windows_platform? ? escape_for_windows(s) : escape_for_sh(s)
|
1310
|
-
end
|
1311
|
-
|
1312
|
-
def escape_for_sh(s)
|
1313
|
-
"'#{s && s.to_s.gsub('\'','\'"\'"\'')}'"
|
1314
|
-
end
|
1315
|
-
|
1316
|
-
def escape_for_windows(s)
|
1317
|
-
# Escape existing double quotes in s and then wrap the result with double quotes
|
1318
|
-
escaped_string = s.to_s.gsub('"','\\"')
|
1319
|
-
%Q{"#{escaped_string}"}
|
1320
|
-
end
|
1321
|
-
|
1322
|
-
def windows_platform?
|
1323
|
-
# Check if on Windows via RUBY_PLATFORM (CRuby) and RUBY_DESCRIPTION (JRuby)
|
1324
|
-
win_platform_regex = /mingw|mswin/
|
1325
|
-
RUBY_PLATFORM =~ win_platform_regex || RUBY_DESCRIPTION =~ win_platform_regex
|
1326
|
-
end
|
1327
1394
|
end
|
1328
1395
|
end
|