Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
nirvdrum#308

The proper way to do subprocess I/O is to do less.
  • Loading branch information
skull-squadron authored Jan 14, 2024
1 parent 6dac85a commit 6267250
Showing 1 changed file with 11 additions and 46 deletions.
57 changes: 11 additions & 46 deletions lib/svn2git/migration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -396,57 +396,22 @@ def run_command(cmd, exit_on_error=true, printout_output=false)
log "Running command: #{cmd}\n"

ret = ''
@stdin_queue ||= Queue.new

# We need to fetch input from the user to pass through to the underlying sub-process. We'll constantly listen
# for input and place any received values on a queue for consumption by a pass-through thread that will forward
# the contents to the underlying sub-process's stdin pipe.
@stdin_thread ||= Thread.new do
loop { @stdin_queue << $stdin.gets.chomp }
end

# Open4 forks, which JRuby doesn't support. But JRuby added a popen4-compatible method on the IO class,
# so we can use that instead.
IO.popen("2>&1 #{cmd}") do |output|
threads = []

threads << Thread.new(output) do |output|
# git-svn seems to do all of its prompting for user input via STDERR. When it prompts for input, it will
# not terminate the line with a newline character, so we can't split the input up by newline. It will,
# however, use a space to separate the user input from the prompt. So we split on word boundaries here
# while draining STDERR.
output.each(' ') do |word|
ret << word

if printout_output
$stdout.print word
else
log word
end
# so we can use that instead. Each command inherits the parent stdin so there's no need to manage it or close it before reading stdout.
IO.popen(cmd, :in => 0, :err => [:child, :out]) do |output|
if printout_output || @options[:verbose]
# Normally, `IO.copy_stream()` would be used, but we need compatibility with interactive supprocesses.
while c = output.getc do
ret << c
putc c
STDOUT.flush # `putc`'s `STDOUT` is buffered by default, and `STDOUT.sync = true` is the wrong solution because it's a global hack.
end
else # Save output only without logging or printing to stdout.
ret = output.read
end

# Simple pass-through thread to take anything the user types via STDIN and passes it through to the
# sub-process's stdin pipe.
Thread.new do
loop do
user_reply = @stdin_queue.pop

# nil is our cue to stop looping (pun intended).
break if user_reply.nil?

stdin.puts user_reply
stdin.close
end
end

threads.each(&:join)

# Push nil to the stdin_queue to gracefully exit the STDIN pass-through thread.
@stdin_queue << nil
end

if exit_on_error && $?.exitstatus != 0
if exit_on_error && !$?.success?
$stderr.puts "command failed:\n#{cmd}"
exit -1
end
Expand Down

0 comments on commit 6267250

Please sign in to comment.