diff --git a/lib/overcommit/hook_context/base.rb b/lib/overcommit/hook_context/base.rb index ec318a3c..839f93b5 100644 --- a/lib/overcommit/hook_context/base.rb +++ b/lib/overcommit/hook_context/base.rb @@ -99,7 +99,10 @@ def input_string return @input_string if defined?(@input_string) @input_mutex.synchronize do - @input_string = @input.read unless defined?(@input_string) + unless defined?(@input_string) + @input.binmode if @input.respond_to?(:binmode) + @input_string = @input.read + end end @input_string diff --git a/lib/overcommit/subprocess.rb b/lib/overcommit/subprocess.rb index 8493efab..504fecad 100644 --- a/lib/overcommit/subprocess.rb +++ b/lib/overcommit/subprocess.rb @@ -35,7 +35,8 @@ def spawn(args, options = {}) process.start if options[:input] begin - process.io.stdin.puts(options[:input]) + process.io.stdin.binmode + process.io.stdin.write(options[:input]) rescue StandardError # Silently ignore if the standard input stream of the spawned # process is closed before we get a chance to write to it. This diff --git a/spec/integration/custom_script_spec.rb b/spec/integration/custom_script_spec.rb new file mode 100644 index 00000000..2d4bca91 --- /dev/null +++ b/spec/integration/custom_script_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'json' + +describe 'post-rewrite CustomScript input' do + around do |example| + repo do + echo({ 'PostRewrite' => { + 'ALL' => { 'enabled' => false }, + 'CustomScript' => { + 'enabled' => true, + 'command' => [Gem.ruby, 'record-rewrite.rb'] + } + } }.to_yaml, '.overcommit.yml') + echo(<<~RUBY, 'record-rewrite.rb') + require 'json' + STDIN.binmode + input = STDIN.read + # Like git-branchless, accept an empty stream but reject a blank record. + input.each_line do |line| + abort 'Invalid rewrite line' unless line.strip.match?(/\\A[0-9a-f]{40} [0-9a-f]{40}\\z/) + end + File.write('received.json', JSON.generate(args: ARGV, bytes: input.bytes)) + RUBY + install_overcommit_hooks + example.run + end + end + + pair = "#{'a' * 40} #{'b' * 40}" + ['', pair, "#{pair}\n", "#{pair}\n#{pair}\n", "#{pair}\r\n"].each do |input| + it "forwards the rewrite type and exact input bytes for #{input.inspect}" do + result = Overcommit::Subprocess.spawn( + [Gem.ruby, '.git/hooks/post-rewrite', 'rebase'], input: input, + ) + + expect(result.status).to eq(0), result.stdout + result.stderr + JSON.parse(File.read('received.json')).should == { 'args' => ['rebase'], + 'bytes' => input.bytes } + end + end +end diff --git a/spec/overcommit/subprocess_spec.rb b/spec/overcommit/subprocess_spec.rb index 4778baf0..e2e1c698 100644 --- a/spec/overcommit/subprocess_spec.rb +++ b/spec/overcommit/subprocess_spec.rb @@ -51,7 +51,18 @@ def received_argv(result) it 'passes the input to the standard input stream of the process' do subject.should be_success - subject.stdout.chomp.should == 'from-stdin' + subject.stdout.should == 'from-stdin' + end + end + + ['', 'no newline', "one line\n", "two\nlines\n", "binary\0\r\n\xFF".b].each do |input| + it "preserves input bytes for #{input.inspect}" do + result = described_class.spawn( + [ruby, '-e', 'STDIN.binmode; STDOUT.print STDIN.read.bytes.join(",")'], + input: input, + ) + result.should be_success + result.stdout.should == input.bytes.join(',') end end diff --git a/spec/overcommit/utils_spec.rb b/spec/overcommit/utils_spec.rb index 10d8c751..13682f94 100644 --- a/spec/overcommit/utils_spec.rb +++ b/spec/overcommit/utils_spec.rb @@ -321,7 +321,7 @@ subject { described_class.execute(arguments, input: input) } it 'passes the input to the standard input stream' do - subject.stdout.should == "Hello world\n" + subject.stdout.should == 'Hello world' end end