From 597e697e9d26a6711d78923f5225d1a34b7a5be5 Mon Sep 17 00:00:00 2001 From: James Couball Date: Sun, 12 Feb 2023 13:29:59 -0800 Subject: [PATCH] Integrate process_executor gem for controlling the git subprocess Signed-off-by: James Couball --- .github/workflows/continuous_integration.yml | 14 +- git.gemspec | 1 + lib/git/lib.rb | 325 ++++++++++++------- tests/test_helper.rb | 22 +- tests/units/test_archive.rb | 44 ++- tests/units/test_commit_with_gpg.rb | 30 +- tests/units/test_config.rb | 42 ++- tests/units/test_lib.rb | 61 ++-- tests/units/test_logger.rb | 6 +- tests/units/test_remotes.rb | 8 +- 10 files changed, 322 insertions(+), 231 deletions(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 302c5eed..dd27bbd2 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -13,17 +13,17 @@ jobs: strategy: fail-fast: false matrix: - ruby: [2.7, 3.0, 3.1, 3.2] + ruby: [2.7, 3.0, 3.1, 3.2, head] operating-system: [ubuntu-latest] include: - - ruby: head - operating-system: ubuntu-latest - - ruby: truffleruby-head - operating-system: ubuntu-latest - - ruby: 2.7 - operating-system: windows-latest - ruby: jruby-head + operating-system: ubuntu-latest + - ruby: 3.0 operating-system: windows-latest + # - ruby: jruby-head + # operating-system: windows-latest + # - ruby: truffleruby-head + # operating-system: ubuntu-latest name: Ruby ${{ matrix.ruby }} on ${{ matrix.operating-system }} diff --git a/git.gemspec b/git.gemspec index 50b9c140..b67a16a0 100644 --- a/git.gemspec +++ b/git.gemspec @@ -27,6 +27,7 @@ Gem::Specification.new do |s| s.requirements = ['git 1.6.0.0, or greater'] s.add_runtime_dependency 'addressable', '~> 2.8' + s.add_runtime_dependency 'process_executer', '~> 0.6' s.add_runtime_dependency 'rchardet', '~> 1.8' s.add_development_dependency 'bump', '~> 0.10' diff --git a/lib/git/lib.rb b/lib/git/lib.rb index 35791c02..10628671 100644 --- a/lib/git/lib.rb +++ b/lib/git/lib.rb @@ -1,15 +1,13 @@ +require 'process_executer' +require 'stringio' require 'tempfile' require 'zlib' module Git - class GitExecuteError < StandardError end class Lib - - @@semaphore = Mutex.new - # The path to the Git working copy. The default is '"./.git"'. # # @return [Pathname] the path to the Git working copy. @@ -113,7 +111,7 @@ def clone(repository_url, directory, opts = {}) arr_opts << repository_url arr_opts << clone_dir - command('clone', arr_opts) + command_merged_output('clone', arr_opts) return_base_opts_from_clone(clone_dir, opts) end @@ -318,7 +316,19 @@ def process_commit_log_data(data) end def object_contents(sha, &block) - command('cat-file', '-p', sha, &block) + if block_given? + Tempfile.create do |file| + # If a block is given, write the output from the process to a temporary + # file and then yield the file to the block + # + command_with_redirect('cat-file', "-p", sha, stdout_writer: file, stderr_writer: file) + file.rewind + yield file + end + else + # If a block is not given, return stdout + command('cat-file', '-p', sha) + end end def ls_tree(sha) @@ -739,24 +749,24 @@ def stashes_all end def stash_save(message) - output = command('stash save', message) + output = command('stash', 'save', message) output =~ /HEAD is now at/ end def stash_apply(id = nil) if id - command('stash apply', id) + command('stash', 'apply', id) else - command('stash apply') + command('stash', 'apply') end end def stash_clear - command('stash clear') + command('stash', 'clear') end def stash_list - command('stash list') + command('stash', 'list') end def branch_new(branch) @@ -798,7 +808,7 @@ def merge(branch, message = nil, opts = {}) arr_opts << '--no-commit' if opts[:no_commit] arr_opts << '--no-ff' if opts[:no_ff] arr_opts << '-m' << message if message - arr_opts += [branch] + arr_opts += Array(branch) command('merge', arr_opts) end @@ -827,16 +837,17 @@ def unmerged def conflicts # :yields: file, your, their self.unmerged.each do |f| - your_tempfile = Tempfile.new("YOUR-#{File.basename(f)}") - your = your_tempfile.path - your_tempfile.close # free up file for git command process - command('show', ":2:#{f}", redirect: "> #{escape your}") - - their_tempfile = Tempfile.new("THEIR-#{File.basename(f)}") - their = their_tempfile.path - their_tempfile.close # free up file for git command process - command('show', ":3:#{f}", redirect: "> #{escape their}") - yield(f, your, their) + Tempfile.create("YOUR-#{File.basename(f)}") do |your| + command_with_redirect('show', ":2:#{f}", stdout_writer: your) + your.close + + Tempfile.create("THEIR-#{File.basename(f)}") do |their| + command_with_redirect('show', ":3:#{f}", stdout_writer: their) + their.close + + yield(f, your.path, their.path) + end + end end end @@ -909,7 +920,7 @@ def fetch(remote, opts) arr_opts << remote if remote arr_opts << opts[:ref] if opts[:ref] - command('fetch', arr_opts) + command_merged_output('fetch', arr_opts) end def push(remote, branch = 'master', opts = {}) @@ -963,15 +974,12 @@ def write_tree def commit_tree(tree, opts = {}) opts[:message] ||= "commit tree #{tree}" - t = Tempfile.new('commit-message') - t.write(opts[:message]) - t.close - arr_opts = [] arr_opts << tree arr_opts << '-p' << opts[:parent] if opts[:parent] arr_opts += [opts[:parents]].map { |p| ['-p', p] }.flatten if opts[:parents] - command('commit-tree', arr_opts, redirect: "< #{escape t.path}") + arr_opts << '-m' << opts[:message] + command('commit-tree', arr_opts) end def update_ref(branch, commit) @@ -1017,7 +1025,11 @@ def archive(sha, file = nil, opts = {}) arr_opts << "--remote=#{opts[:remote]}" if opts[:remote] arr_opts << sha arr_opts << '--' << opts[:path] if opts[:path] - command('archive', arr_opts, redirect: " > #{escape file}") + + f = File.open(file, 'wb') + command_with_redirect('archive', arr_opts, stdout_writer: f) + f.close + if opts[:add_gzip] file_content = File.read(file) Zlib::GzipWriter.open(file) do |gz| @@ -1054,11 +1066,6 @@ def self.warn_if_old_command(lib) private - # Systen ENV variables involved in the git commands. - # - # @return [] the names of the EVN variables involved in the git commands - ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH'] - def command_lines(cmd, *opts) cmd_op = command(cmd, *opts) if cmd_op.encoding.name != "UTF-8" @@ -1069,91 +1076,201 @@ def command_lines(cmd, *opts) op.split("\n") end - # Takes the current git's system ENV variables and store them. - def store_git_system_env_variables - @git_system_env_variables = {} - ENV_VARIABLE_NAMES.each do |env_variable_name| - @git_system_env_variables[env_variable_name] = ENV[env_variable_name] - end + def env_overrides + { + 'GIT_DIR' => @git_dir, + 'GIT_WORK_TREE' => @git_work_dir, + 'GIT_INDEX_FILE' => @git_index_file, + 'GIT_SSH' => Git::Base.config.git_ssh + } end - # Takes the previously stored git's ENV variables and set them again on ENV. - def restore_git_system_env_variables - ENV_VARIABLE_NAMES.each do |env_variable_name| - ENV[env_variable_name] = @git_system_env_variables[env_variable_name] + DEFAULT_COMMAND_OPTS = { chomp: true } + + def command_opts(given_command_opts) + DEFAULT_COMMAND_OPTS.merge(given_command_opts).tap do |command_options| + command_options.keys.each do |k| + raise ArgumentError.new("Unsupported command option: #{k}") unless DEFAULT_COMMAND_OPTS.keys.include?(k) + end end end - # Sets git's ENV variables to the custom values for the current instance. - def set_custom_git_env_variables - ENV['GIT_DIR'] = @git_dir - ENV['GIT_WORK_TREE'] = @git_work_dir - ENV['GIT_INDEX_FILE'] = @git_index_file - ENV['GIT_SSH'] = Git::Base.config.git_ssh + def global_opts + Array.new.tap do |global_opts| + global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil? + global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil? + global_opts << %w[-c core.quotePath=true] + global_opts << %w[-c color.ui=false] + end.flatten end - # Runs a block inside an environment with customized ENV variables. - # It restores the ENV after execution. - # - # @param [Proc] block block to be executed within the customized environment - def with_custom_env_variables(&block) - @@semaphore.synchronize do - store_git_system_env_variables() - set_custom_git_env_variables() - return block.call() - end - ensure - restore_git_system_env_variables() + def build_command(global_opts, cmd, opts) + [ + Git::Base.config.binary_path, + *Array(global_opts).flatten, + cmd, + *Array(opts).flatten + ].map { |e| e.to_s } end - def command(cmd, *opts, &block) - Git::Lib.warn_if_old_command(self) + def command_with_redirect(cmd, *opts, stdout_writer: nil, stderr_writer: nil) + given_command_opts = opts.last.is_a?(Hash) ? opts.pop : {} + command_opts = command_opts(given_command_opts) + + git_cmd = build_command(global_opts, cmd, opts).flatten + + stdout_writers = [] + stdout_writers << stdout_writer if stdout_writer + + stderr_writers = [] + stderr_writers << stderr_writer if stderr_writer - command_opts = { chomp: true, redirect: '' } - if opts.last.is_a?(Hash) - command_opts.merge!(opts.pop) + begin + stdout_pipe = ProcessExecuter::MonitoredPipe.new(*stdout_writers, chunk_size: 10_000) + stderr_pipe = ProcessExecuter::MonitoredPipe.new(*stderr_writers, chunk_size: 10_000) + + spawn_opts = { out: stdout_pipe, err: stderr_pipe } + + status = ProcessExecuter.spawn(env_overrides, *git_cmd, **spawn_opts) + ensure + stdout_pipe&.close + stderr_pipe&.close end - command_opts.keys.each do |k| - raise ArgumentError.new("Unsupported option: #{k}") unless [:chomp, :redirect].include?(k) + + warn "status is a #{status.class}" unless status.is_a?(Process::Status) + warn "stdout_pipe exception #{stdout_pipe.exception.inspect}, status is #{status.inspect}, exitstatus is #{status.exitstatus}" if stdout_pipe.exception + warn "stderr_pipe exception #{stderr_pipe.exception.inspect}, status is #{status.inspect}, exitstatus is #{status.exitstatus}" if stderr_pipe.exception + + if @logger + @logger.info("#{git_cmd} exited with status #{status.inspect}, output redirected") end - global_opts = [] - global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil? - global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil? - global_opts << %w[-c core.quotePath=true] - global_opts << %w[-c color.ui=false] + if stdout_pipe.exception || stderr_pipe.exception + message = if stdout_pipe.exception + message = "#{git_cmd} stdout_pipe.exception=#{stdout_pipe.exception.inspect}, status=#{status.inspect}" + elsif stderr_pipe.exception + message = "#{git_cmd} stderr_pipe.exception=#{stderr_pipe.exception.inspect}, status=#{status.inspect}" + end + raise Git::GitExecuteError, message if message + end + + raise Git::GitExecuteError, "#{git_cmd} exited with signal #{status.inspect}" if status.signaled? - opts = [opts].flatten.map {|s| escape(s) }.join(' ') + exitstatus = status.exitstatus + + raise Git::GitExecuteError, "#{git_cmd}\n#{status.inspect}\nstderr: #{stderr}" if + exitstatus > 1 || (exitstatus == 1 && stderr != '') + end - global_opts = global_opts.flatten.map {|s| escape(s) }.join(' ') + def command_merged_output(cmd, *opts) + given_command_opts = opts.last.is_a?(Hash) ? opts.pop : {} + command_opts = command_opts(given_command_opts) - git_cmd = "#{Git::Base.config.binary_path} #{global_opts} #{cmd} #{opts} #{command_opts[:redirect]} 2>&1" + git_cmd = build_command(global_opts, cmd, opts).flatten - output = nil + stdout_writer = StringIO.new + stdout_writers = [stdout_writer] - command_thread = nil; + begin + stdout_pipe = ProcessExecuter::MonitoredPipe.new(*stdout_writers, chunk_size: 10_000) + stderr_pipe = stdout_pipe - exitstatus = nil + spawn_opts = { out: stdout_pipe, err: stderr_pipe } - with_custom_env_variables do - command_thread = Thread.new do - output = run_command(git_cmd, &block) - exitstatus = $?.exitstatus + status = ProcessExecuter.spawn(env_overrides, *git_cmd, **spawn_opts) + ensure + stdout_pipe&.close + end + + warn "status is a #{status.class}" unless status.is_a?(Process::Status) + warn "stdout_pipe exception #{stdout_pipe.exception.inspect}, status is #{status.inspect}, exitstatus is #{status.exitstatus}" if stdout_pipe.exception + warn "stderr_pipe exception #{stderr_pipe.exception.inspect}, status is #{status.inspect}, exitstatus is #{status.exitstatus}" if stderr_pipe.exception + + if @logger + @logger.info("#{git_cmd} exited with status #{status.inspect}, stdout and stderr merged") + end + + if stdout_pipe.exception || stderr_pipe.exception + message = if stdout_pipe.exception + message = "#{git_cmd} stdout_pipe.exception=#{stdout_pipe.exception.inspect}, status=#{status.inspect}" + elsif stderr_pipe.exception + message = "#{git_cmd} stderr_pipe.exception=#{stderr_pipe.exception.inspect}, status=#{status.inspect}" end - command_thread.join + raise Git::GitExecuteError, message if message end + raise Git::GitExecuteError, "#{git_cmd} exited with signal #{status.inspect}" if status.signaled? + + stdout = stdout_writer.string.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join + stderr = '' + exitstatus = status.exitstatus + if @logger - @logger.info(git_cmd) - @logger.debug(output) + @logger.debug("Merged stdout and stderr: #{stdout}") end - raise Git::GitExecuteError, "#{git_cmd}:#{output}" if - exitstatus > 1 || (exitstatus == 1 && output != '') + raise Git::GitExecuteError, "#{git_cmd}\n#{status.inspect}\nstderr: #{stderr}" if + exitstatus > 1 || (exitstatus == 1 && stderr != '') + + command_opts[:chomp] ? stdout.chomp : stdout + end + + def command(cmd, *opts) + given_command_opts = opts.last.is_a?(Hash) ? opts.pop : {} + command_opts = command_opts(given_command_opts) + + git_cmd = build_command(global_opts, cmd, opts).flatten + + stdout_writer = StringIO.new + stdout_writers = [stdout_writer] + + stderr_writer = StringIO.new + stderr_writers = [stderr_writer] - output.chomp! if output && command_opts[:chomp] && !block_given? + begin + stdout_pipe = ProcessExecuter::MonitoredPipe.new(*stdout_writers, chunk_size: 10_000) + stderr_pipe = ProcessExecuter::MonitoredPipe.new(*stderr_writers, chunk_size: 10_000) - output + spawn_opts = { out: stdout_pipe, err: stderr_pipe } + + status = ProcessExecuter.spawn(env_overrides, *git_cmd, **spawn_opts) + ensure + stdout_pipe&.close + stderr_pipe&.close + end + + warn "status is a #{status.class}" unless status.is_a?(Process::Status) + warn "stdout_pipe exception #{stdout_pipe.exception.inspect}, status is #{status.inspect}, exitstatus is #{status.exitstatus}" if stdout_pipe.exception + warn "stderr_pipe exception #{stderr_pipe.exception.inspect}, status is #{status.inspect}, exitstatus is #{status.exitstatus}" if stderr_pipe.exception + + if @logger + @logger.info("#{git_cmd} exited with status #{status.inspect}") + end + + if stdout_pipe.exception || stderr_pipe.exception + message = if stdout_pipe.exception + message = "#{git_cmd} stdout_pipe.exception=#{stdout_pipe.exception.inspect}, status=#{status.inspect}" + elsif stderr_pipe.exception + message = "#{git_cmd} stderr_pipe.exception=#{stderr_pipe.exception.inspect}, status=#{status.inspect}" + end + raise Git::GitExecuteError, message if message + end + + raise Git::GitExecuteError, "#{git_cmd} exited with signal #{status.inspect}" if status.signaled? + + stdout = stdout_writer.string.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join + stderr = stderr_writer.string.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join + exitstatus = status.exitstatus + + if @logger + @logger.debug("stdout: #{stdout}") + @logger.debug("stderr: #{stderr}") + end + + raise Git::GitExecuteError, "#{git_cmd}\n#{status.inspect}\nstderr: #{stderr}" if + exitstatus > 1 || (exitstatus == 1 && stderr != '') + + command_opts[:chomp] ? stdout.chomp : stdout end # Takes the diff command line output (as Array) and parse it into a Hash @@ -1211,31 +1328,5 @@ def log_path_options(opts) arr_opts << '--' << opts[:path_limiter] if opts[:path_limiter] arr_opts end - - def run_command(git_cmd, &block) - return IO.popen(git_cmd, &block) if block_given? - - `#{git_cmd}`.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join - end - - def escape(s) - windows_platform? ? escape_for_windows(s) : escape_for_sh(s) - end - - def escape_for_sh(s) - "'#{s && s.to_s.gsub('\'','\'"\'"\'')}'" - end - - def escape_for_windows(s) - # Escape existing double quotes in s and then wrap the result with double quotes - escaped_string = s.to_s.gsub('"','\\"') - %Q{"#{escaped_string}"} - end - - def windows_platform? - # Check if on Windows via RUBY_PLATFORM (CRuby) and RUBY_DESCRIPTION (JRuby) - win_platform_regex = /mingw|mswin/ - RUBY_PLATFORM =~ win_platform_regex || RUBY_DESCRIPTION =~ win_platform_regex - end end end diff --git a/tests/test_helper.rb b/tests/test_helper.rb index c92f1892..d384fc58 100644 --- a/tests/test_helper.rb +++ b/tests/test_helper.rb @@ -6,6 +6,9 @@ require "git" +$stdout.sync = true +$stderr.sync = true + class Test::Unit::TestCase TEST_ROOT = File.expand_path(__dir__) @@ -92,21 +95,6 @@ def append_file(name, contents) end end - # Runs a block inside an environment with customized ENV variables. - # It restores the ENV after execution. - # - # @param [Proc] block block to be executed within the customized environment - # - def with_custom_env_variables(&block) - saved_env = {} - begin - Git::Lib::ENV_VARIABLE_NAMES.each { |k| saved_env[k] = ENV[k] } - return block.call - ensure - Git::Lib::ENV_VARIABLE_NAMES.each { |k| ENV[k] = saved_env[k] } - end - end - # Assert that the expected command line args are generated for a given Git::Lib method # # This assertion generates an empty git repository and then runs calls @@ -149,7 +137,7 @@ def with_custom_env_variables(&block) # # @return [void] # - def assert_command_line(expected_command_line, git_cmd, git_cmd_args) + def assert_command_line(expected_command_line, git_cmd, git_cmd_args, method: :command) actual_command_line = nil in_temp_dir do |path| @@ -159,7 +147,7 @@ def assert_command_line(expected_command_line, git_cmd, git_cmd_args) yield(git) if block_given? # Mock the Git::Lib#command method to capture the actual command line args - git.lib.define_singleton_method(:command) do |cmd, *opts, &block| + git.lib.define_singleton_method(method) do |cmd, *opts, &block| actual_command_line = [cmd, *opts.flatten] end diff --git a/tests/units/test_archive.rb b/tests/units/test_archive.rb index 68ef3a65..7f0e1eed 100644 --- a/tests/units/test_archive.rb +++ b/tests/units/test_archive.rb @@ -11,53 +11,79 @@ def setup end def teardown - @tempfiles.clear + @tempfiles.each { |f| File.delete(f) } end def tempfile - tempfile_object = Tempfile.new('archive-test') - @tempfiles << tempfile_object # prevent deletion until teardown - tempfile_object.close # close to avoid locking from git processes - tempfile_object.path + Dir::Tmpname.create('test-archive') { }.tap do |f| + @tempfiles << f + end end def test_archive f = @git.archive('v2.6', tempfile) assert(File.exist?(f)) + end + def test_archive_object f = @git.object('v2.6').archive(tempfile) # writes to given file assert(File.exist?(f)) + end + def test_archive_object_with_no_filename f = @git.object('v2.6').archive # returns path to temp file assert(File.exist?(f)) + end + def test_archive_to_tar f = @git.object('v2.6').archive(nil, :format => 'tar') # returns path to temp file assert(File.exist?(f)) - lines = Minitar::Input.open(f).each.to_a.map(&:full_name) + lines = [] + Minitar::Input.open(f) do |tar_reader| + lines = tar_reader.to_a.map(&:full_name) + end + assert_match(%r{ex_dir/}, lines[1]) assert_match(/ex_dir\/ex\.txt/, lines[2]) assert_match(/example\.txt/, lines[3]) + end + def test_archive_to_zip f = @git.object('v2.6').archive(tempfile, :format => 'zip') assert(File.file?(f)) + end + def test_archive_to_tgz f = @git.object('v2.6').archive(tempfile, :format => 'tgz', :prefix => 'test/') assert(File.exist?(f)) - lines = Minitar::Input.open(Zlib::GzipReader.new(File.open(f, 'rb'))).each.to_a.map(&:full_name) + lines = [] + File.open(f, 'rb') do |file_reader| + Zlib::GzipReader.open(file_reader) do |gz_reader| + Minitar::Input.open(gz_reader) do |tar_reader| + lines = tar_reader.to_a.map(&:full_name) + end + end + end + assert_match(%r{test/}, lines[1]) assert_match(%r{test/ex_dir/ex\.txt}, lines[3]) + end + def test_archive_with_prefix_and_path f = @git.object('v2.6').archive(tempfile, :format => 'tar', :prefix => 'test/', :path => 'ex_dir/') assert(File.exist?(f)) - lines = Minitar::Input.open(f).each.to_a.map(&:full_name) + tar_file = Minitar::Input.open(f) + lines = tar_file.each.to_a.map(&:full_name) + tar_file.close assert_match(%r{test/}, lines[1]) assert_match(%r{test/ex_dir/ex\.txt}, lines[3]) + end + def test_archive_branch f = @git.remote('working').branch('master').archive(tempfile, :format => 'tgz') assert(File.exist?(f)) end - end diff --git a/tests/units/test_commit_with_gpg.rb b/tests/units/test_commit_with_gpg.rb index f9e8bb28..c8b2b2a4 100644 --- a/tests/units/test_commit_with_gpg.rb +++ b/tests/units/test_commit_with_gpg.rb @@ -10,42 +10,42 @@ def setup def test_with_configured_gpg_keyid Dir.mktmpdir do |dir| git = Git.init(dir) - actual_cmd = nil - git.lib.define_singleton_method(:run_command) do |git_cmd, &block| - actual_cmd = git_cmd - `true` + actual_opts = nil + git.lib.define_singleton_method(:command) do |cmd, *opts, &block| + actual_opts = opts.flatten + '' end message = 'My commit message' git.commit(message, gpg_sign: true) - assert_match(/commit.*--gpg-sign['"]/, actual_cmd) + assert(actual_opts.include?('--gpg-sign')) end end def test_with_specific_gpg_keyid Dir.mktmpdir do |dir| git = Git.init(dir) - actual_cmd = nil - git.lib.define_singleton_method(:run_command) do |git_cmd, &block| - actual_cmd = git_cmd - `true` + actual_opts = nil + git.lib.define_singleton_method(:command) do |cmd, *opts, &block| + actual_opts = opts.flatten + '' end message = 'My commit message' git.commit(message, gpg_sign: 'keykeykey') - assert_match(/commit.*--gpg-sign=keykeykey['"]/, actual_cmd) + assert(actual_opts.include?('--gpg-sign=keykeykey')) end end def test_disabling_gpg_sign Dir.mktmpdir do |dir| git = Git.init(dir) - actual_cmd = nil - git.lib.define_singleton_method(:run_command) do |git_cmd, &block| - actual_cmd = git_cmd - `true` + actual_opts = nil + git.lib.define_singleton_method(:command) do |cmd, *opts, &block| + actual_opts = opts.flatten + '' end message = 'My commit message' git.commit(message, no_gpg_sign: true) - assert_match(/commit.*--no-gpg-sign['"]/, actual_cmd) + assert(actual_opts.include?('--no-gpg-sign')) end end diff --git a/tests/units/test_config.rb b/tests/units/test_config.rb index 35208d24..b60e6c83 100644 --- a/tests/units/test_config.rb +++ b/tests/units/test_config.rb @@ -38,34 +38,32 @@ def test_set_config_with_custom_file end def test_env_config - with_custom_env_variables do - begin - assert_equal(Git::Base.config.binary_path, 'git') - assert_equal(Git::Base.config.git_ssh, nil) + begin + assert_equal(Git::Base.config.binary_path, 'git') + assert_equal(Git::Base.config.git_ssh, nil) - ENV['GIT_PATH'] = '/env/bin' - ENV['GIT_SSH'] = '/env/git/ssh' + ENV['GIT_PATH'] = '/env/bin' + ENV['GIT_SSH'] = '/env/git/ssh' - assert_equal(Git::Base.config.binary_path, '/env/bin/git') - assert_equal(Git::Base.config.git_ssh, '/env/git/ssh') + assert_equal(Git::Base.config.binary_path, '/env/bin/git') + assert_equal(Git::Base.config.git_ssh, '/env/git/ssh') - Git.configure do |config| - config.binary_path = '/usr/bin/git' - config.git_ssh = '/path/to/ssh/script' - end + Git.configure do |config| + config.binary_path = '/usr/bin/git' + config.git_ssh = '/path/to/ssh/script' + end - assert_equal(Git::Base.config.binary_path, '/usr/bin/git') - assert_equal(Git::Base.config.git_ssh, '/path/to/ssh/script') + assert_equal(Git::Base.config.binary_path, '/usr/bin/git') + assert_equal(Git::Base.config.git_ssh, '/path/to/ssh/script') - @git.log - ensure - ENV['GIT_SSH'] = nil - ENV['GIT_PATH'] = nil + @git.log + ensure + ENV['GIT_SSH'] = nil + ENV['GIT_PATH'] = nil - Git.configure do |config| - config.binary_path = nil - config.git_ssh = nil - end + Git.configure do |config| + config.binary_path = nil + config.git_ssh = nil end end end diff --git a/tests/units/test_lib.rb b/tests/units/test_lib.rb index 5c1409e6..4f470ba2 100644 --- a/tests/units/test_lib.rb +++ b/tests/units/test_lib.rb @@ -91,13 +91,14 @@ def test_checkout_with_start_point assert(@lib.reset(nil, hard: true)) # to get around worktree status on windows actual_cmd = nil - @lib.define_singleton_method(:run_command) do |git_cmd, &block| - actual_cmd = git_cmd - super(git_cmd, &block) + @lib.define_singleton_method(:command) do |cmd, *options| + actual_cmd = [cmd, *options].flatten + super(cmd, *options) end assert(@lib.checkout('test_checkout_b2', {new_branch: true, start_point: 'master'})) - assert_match(%r/checkout ['"]-b['"] ['"]test_checkout_b2['"] ['"]master['"]/, actual_cmd) + + assert_equal(['checkout', '-b', 'test_checkout_b2', 'master'], actual_cmd) end # takes parameters, returns array of appropriate commit objects @@ -127,41 +128,27 @@ def test_log_commits assert_equal(20, a.size) end - def test_environment_reset - with_custom_env_variables do - ENV['GIT_DIR'] = '/my/git/dir' - ENV['GIT_WORK_TREE'] = '/my/work/tree' - ENV['GIT_INDEX_FILE'] = 'my_index' - - @lib.log_commits :count => 10 - - assert_equal(ENV['GIT_DIR'], '/my/git/dir') - assert_equal(ENV['GIT_WORK_TREE'], '/my/work/tree') - assert_equal(ENV['GIT_INDEX_FILE'],'my_index') - end - end - def test_git_ssh_from_environment_is_passed_to_binary - with_custom_env_variables do - begin - Dir.mktmpdir do |dir| - output_path = File.join(dir, 'git_ssh_value') - binary_path = File.join(dir, 'git.bat') # .bat so it works in Windows too - Git::Base.config.binary_path = binary_path - File.open(binary_path, 'w') { |f| - f << "echo \"my/git-ssh-wrapper\" > #{output_path}" - } - FileUtils.chmod(0700, binary_path) - @lib.checkout('something') - assert(File.read(output_path).include?("my/git-ssh-wrapper")) - end - ensure - Git.configure do |config| - config.binary_path = nil - config.git_ssh = nil - end - end + saved_binary_path = Git::Base.config.binary_path + saved_git_ssh = Git::Base.config.git_ssh + + Dir.mktmpdir do |dir| + output_path = File.join(dir, 'git_ssh_value') + binary_path = File.join(dir, 'my_own_git.bat') # .bat so it works in Windows too + Git::Base.config.binary_path = binary_path + Git::Base.config.git_ssh = 'GIT_SSH_VALUE' + File.write(binary_path, <<~SCRIPT) + #!/bin/sh + set > "#{output_path}" + SCRIPT + FileUtils.chmod(0700, binary_path) + @lib.checkout('something') + env = File.read(output_path) + assert_match(/^GIT_SSH=(["']?)GIT_SSH_VALUE\1$/, env, 'GIT_SSH should be set in the environment') end + ensure + Git::Base.config.binary_path = saved_binary_path + Git::Base.config.git_ssh = saved_git_ssh end def test_revparse diff --git a/tests/units/test_logger.rb b/tests/units/test_logger.rb index 931728ab..4c4e6c86 100644 --- a/tests/units/test_logger.rb +++ b/tests/units/test_logger.rb @@ -28,10 +28,10 @@ def test_logger logc = File.read(log.path) - expected_log_entry = /INFO -- : git (?.*?) branch ['"]-a['"]/ + expected_log_entry = /INFO -- : .*git.*branch.*-a/ assert_match(expected_log_entry, logc, missing_log_entry) - expected_log_entry = /DEBUG -- : cherry/ + expected_log_entry = /DEBUG -- : stdout: cherry/ assert_match(expected_log_entry, logc, missing_log_entry) end @@ -46,7 +46,7 @@ def test_logging_at_info_level_should_not_show_debug_messages logc = File.read(log.path) - expected_log_entry = /INFO -- : git (?.*?) branch ['"]-a['"]/ + expected_log_entry = /INFO -- : .*git.*branch.*-a/ assert_match(expected_log_entry, logc, missing_log_entry) expected_log_entry = /DEBUG -- : cherry/ diff --git a/tests/units/test_remotes.rb b/tests/units/test_remotes.rb index d51451e3..f60042ae 100644 --- a/tests/units/test_remotes.rb +++ b/tests/units/test_remotes.rb @@ -123,28 +123,28 @@ def test_fetch_cmd_with_no_args expected_command_line = ['fetch', '--', 'origin'] git_cmd = :fetch git_cmd_args = [] - assert_command_line(expected_command_line, git_cmd, git_cmd_args) + assert_command_line(expected_command_line, git_cmd, git_cmd_args, method: :command_merged_output) end def test_fetch_cmd_with_origin_and_branch expected_command_line = ['fetch', '--depth', '2', '--', 'origin', 'master'] git_cmd = :fetch git_cmd_args = ['origin', ref: 'master', depth: '2'] - assert_command_line(expected_command_line, git_cmd, git_cmd_args) + assert_command_line(expected_command_line, git_cmd, git_cmd_args, method: :command_merged_output) end def test_fetch_cmd_with_all expected_command_line = ['fetch', '--all'] git_cmd = :fetch git_cmd_args = [all: true] - assert_command_line(expected_command_line, git_cmd, git_cmd_args) + assert_command_line(expected_command_line, git_cmd, git_cmd_args, method: :command_merged_output) end def test_fetch_cmd_with_all_with_other_args expected_command_line = ['fetch', '--all', '--force', '--depth', '2'] git_cmd = :fetch git_cmd_args = [all: true, force: true, depth: '2'] - assert_command_line(expected_command_line, git_cmd, git_cmd_args) + assert_command_line(expected_command_line, git_cmd, git_cmd_args, method: :command_merged_output) end def test_fetch_command_injection