lib/svn2git/migration.rb in svn2git-2.2.5 vs lib/svn2git/migration.rb in svn2git-2.3.0
- old
+ new
@@ -1,7 +1,9 @@
require 'optparse'
require 'pp'
+require 'open4'
+require 'timeout'
module Svn2Git
DEFAULT_AUTHORS_FILE = "~/.svn2git/authors"
class Migration
@@ -338,26 +340,82 @@
def run_command(cmd, exit_on_error=true, printout_output=false)
log "Running command: #{cmd}"
ret = ''
+ @mutex ||= Mutex.new
+ @stdin_queue ||= Queue.new
- cmd = "2>&1 #{cmd}"
- IO.popen(cmd) do |stdout|
- if printout_output
- stdout.each_char do |character|
- $stdout.print character
- ret << character
- end
- else
+ # 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.
+ status = (defined?(JRUBY_VERSION) ? IO : Open4).popen4(cmd) do |pid, stdin, stdout, stderr|
+ threads = []
+
+ threads << Thread.new(stdout) do |stdout|
stdout.each do |line|
- log line
- ret << line
+ @mutex.synchronize do
+ ret << line
+
+ if printout_output
+ $stdout.print line
+ else
+ log line
+ end
+ end
end
end
+
+ threads << Thread.new(stderr) do |stderr|
+ # 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.
+ stderr.each(' ') do |word|
+ @mutex.synchronize do
+ ret << word
+
+ if printout_output
+ $stdout.print word
+ else
+ log word
+ end
+ end
+ end
+ 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(stdin) do |stdin|
+ 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)
+ # JRuby's open4 doesn't return a Process::Status object when invoked with a block, but rather returns the
+ # block expression's value. The Process::Status is stored as $?, so we need to normalize the status
+ # object if on JRuby.
+ status = $? if defined?(JRUBY_VERSION)
+
+ if exit_on_error && (status.exitstatus != 0)
$stderr.puts "command failed:\n#{cmd}"
exit -1
end
ret