Skip to content

Commit 9a573a3

Browse files
committed
Remove calls to Dir.chdir
In multithreaded environment Dir.chdir changes current directory of all process' threads, so other threads might misbehave. Base#chdir, Base#with_working and Base#with_temp_working were left intact, cause they are not used internally, so it's an explicit user decision to use them. Signed-off-by: Pavel Forkert <fxposter@gmail.com>
1 parent 8481f8c commit 9a573a3

File tree

6 files changed

+40
-46
lines changed

6 files changed

+40
-46
lines changed

lib/git.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ def self.export(repository, name, options = {})
244244
options.delete(:remote)
245245
repo = clone(repository, name, {:depth => 1}.merge(options))
246246
repo.checkout("origin/#{options[:branch]}") if options[:branch]
247-
Dir.chdir(repo.dir.to_s) { FileUtils.rm_r '.git' }
247+
FileUtils.rm_r File.join(repo.dir.to_s, '.git')
248248
end
249249

250250
# Same as g.config, but forces it to be at the global level

lib/git/base.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'git/base/factory'
22
require 'logger'
3+
require 'open3'
34

45
module Git
56
# Git::Base is the main public interface for interacting with Git commands.
@@ -66,11 +67,11 @@ def self.init(directory = '.', options = {})
6667
def self.root_of_worktree(working_dir)
6768
result = working_dir
6869
status = nil
69-
Dir.chdir(working_dir) do
70-
git_cmd = "#{Git::Base.config.binary_path} -c core.quotePath=true -c color.ui=false rev-parse --show-toplevel 2>&1"
71-
result = `#{git_cmd}`.chomp
72-
status = $?
73-
end
70+
71+
git_cmd = "#{Git::Base.config.binary_path} -c core.quotePath=true -c color.ui=false rev-parse --show-toplevel 2>&1"
72+
result, status = Open3.capture2(git_cmd, chdir: File.expand_path(working_dir))
73+
result = result.chomp
74+
7475
raise ArgumentError, "'#{working_dir}' is not in a git working tree" unless status.success?
7576
result
7677
end

lib/git/lib.rb

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require 'logger'
33
require 'tempfile'
44
require 'zlib'
5+
require 'open3'
56

67
module Git
78
class Lib
@@ -441,7 +442,10 @@ def worktree_prune
441442
def list_files(ref_dir)
442443
dir = File.join(@git_dir, 'refs', ref_dir)
443444
files = []
444-
Dir.chdir(dir) { files = Dir.glob('**/*').select { |f| File.file?(f) } } rescue nil
445+
begin
446+
files = Dir.glob('**/*', base: dir).select { |f| File.file?(File.join(dir, f)) }
447+
rescue
448+
end
445449
files
446450
end
447451

@@ -579,31 +583,15 @@ def config_remote(name)
579583
end
580584

581585
def config_get(name)
582-
do_get = Proc.new do |path|
583-
command('config', '--get', name)
584-
end
585-
586-
if @git_dir
587-
Dir.chdir(@git_dir, &do_get)
588-
else
589-
do_get.call
590-
end
586+
command('config', '--get', name, chdir: @git_dir)
591587
end
592588

593589
def global_config_get(name)
594590
command('config', '--global', '--get', name)
595591
end
596592

597593
def config_list
598-
build_list = Proc.new do |path|
599-
parse_config_list command_lines('config', '--list')
600-
end
601-
602-
if @git_dir
603-
Dir.chdir(@git_dir, &build_list)
604-
else
605-
build_list.call
606-
end
594+
parse_config_list command_lines('config', '--list', chdir: @git_dir)
607595
end
608596

609597
def global_config_list
@@ -1148,8 +1136,8 @@ def self.warn_if_old_command(lib)
11481136
# @return [<String>] the names of the EVN variables involved in the git commands
11491137
ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
11501138

1151-
def command_lines(cmd, *opts)
1152-
cmd_op = command(cmd, *opts)
1139+
def command_lines(cmd, *opts, chdir: nil)
1140+
cmd_op = command(cmd, *opts, chdir: chdir)
11531141
if cmd_op.encoding.name != "UTF-8"
11541142
op = cmd_op.encode("UTF-8", "binary", :invalid => :replace, :undef => :replace)
11551143
else
@@ -1195,7 +1183,7 @@ def with_custom_env_variables(&block)
11951183
restore_git_system_env_variables()
11961184
end
11971185

1198-
def command(*cmd, redirect: '', chomp: true, &block)
1186+
def command(*cmd, redirect: '', chomp: true, chdir: nil, &block)
11991187
Git::Lib.warn_if_old_command(self)
12001188

12011189
raise 'cmd can not include a nested array' if cmd.any? { |o| o.is_a? Array }
@@ -1220,8 +1208,7 @@ def command(*cmd, redirect: '', chomp: true, &block)
12201208

12211209
with_custom_env_variables do
12221210
command_thread = Thread.new do
1223-
output = run_command(git_cmd, &block)
1224-
status = $?
1211+
output, status = run_command(git_cmd, chdir, &block)
12251212
end
12261213
command_thread.join
12271214
end
@@ -1303,10 +1290,17 @@ def log_path_options(opts)
13031290
arr_opts
13041291
end
13051292

1306-
def run_command(git_cmd, &block)
1307-
return IO.popen(git_cmd, &block) if block_given?
1293+
def run_command(git_cmd, chdir=nil, &block)
1294+
block ||= Proc.new do |io|
1295+
io.readlines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join
1296+
end
1297+
1298+
opts = {}
1299+
opts[:chdir] = File.expand_path(chdir) if chdir
13081300

1309-
`#{git_cmd}`.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join
1301+
Open3.popen2(git_cmd, opts) do |stdin, stdout, wait_thr|
1302+
[block.call(stdout), wait_thr.value]
1303+
end
13101304
end
13111305

13121306
def escape(s)

lib/git/status.rb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,12 @@ def construct_status
172172
def fetch_untracked
173173
ignore = @base.lib.ignored_files
174174

175-
Dir.chdir(@base.dir.path) do
176-
Dir.glob('**/*', File::FNM_DOTMATCH) do |file|
177-
next if @files[file] || File.directory?(file) ||
178-
ignore.include?(file) || file =~ %r{^.git\/.+}
175+
root_dir = @base.dir.path
176+
Dir.glob('**/*', File::FNM_DOTMATCH, base: root_dir) do |file|
177+
next if @files[file] || File.directory?(File.join(root_dir, file)) ||
178+
ignore.include?(file) || file =~ %r{^.git\/.+}
179179

180-
@files[file] = { path: file, untracked: true }
181-
end
180+
@files[file] = { path: file, untracked: true }
182181
end
183182
end
184183

tests/units/test_commit_with_gpg.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ def test_with_configured_gpg_keyid
1111
Dir.mktmpdir do |dir|
1212
git = Git.init(dir)
1313
actual_cmd = nil
14-
git.lib.define_singleton_method(:run_command) do |git_cmd, &block|
14+
git.lib.define_singleton_method(:run_command) do |git_cmd, chdir, &block|
1515
actual_cmd = git_cmd
16-
`true`
16+
[`true`, $?]
1717
end
1818
message = 'My commit message'
1919
git.commit(message, gpg_sign: true)
@@ -25,9 +25,9 @@ def test_with_specific_gpg_keyid
2525
Dir.mktmpdir do |dir|
2626
git = Git.init(dir)
2727
actual_cmd = nil
28-
git.lib.define_singleton_method(:run_command) do |git_cmd, &block|
28+
git.lib.define_singleton_method(:run_command) do |git_cmd, chdir, &block|
2929
actual_cmd = git_cmd
30-
`true`
30+
[`true`, $?]
3131
end
3232
message = 'My commit message'
3333
git.commit(message, gpg_sign: 'keykeykey')
@@ -39,9 +39,9 @@ def test_disabling_gpg_sign
3939
Dir.mktmpdir do |dir|
4040
git = Git.init(dir)
4141
actual_cmd = nil
42-
git.lib.define_singleton_method(:run_command) do |git_cmd, &block|
42+
git.lib.define_singleton_method(:run_command) do |git_cmd, chdir, &block|
4343
actual_cmd = git_cmd
44-
`true`
44+
[`true`, $?]
4545
end
4646
message = 'My commit message'
4747
git.commit(message, no_gpg_sign: true)

tests/units/test_lib.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def test_checkout_with_start_point
9191
assert(@lib.reset(nil, hard: true)) # to get around worktree status on windows
9292

9393
actual_cmd = nil
94-
@lib.define_singleton_method(:run_command) do |git_cmd, &block|
94+
@lib.define_singleton_method(:run_command) do |git_cmd, chdir, &block|
9595
actual_cmd = git_cmd
9696
super(git_cmd, &block)
9797
end

0 commit comments

Comments
 (0)