diff --git a/share/svi/lib/svi/svn_runner.rb b/share/svi/lib/svi/svn_runner.rb index 992b94d..156ea99 100644 --- a/share/svi/lib/svi/svn_runner.rb +++ b/share/svi/lib/svi/svn_runner.rb @@ -1,4 +1,91 @@ module Svi module SvnRunner + + # Exception class to indicate an error with executing svn. + class SvnExecError < RuntimeError; end + + # Run Subversion and yield results linewise. + # + # @param subcommand [String, nil] + # The svn subcommand to execute (e.g. "ls"). + # @param args [Array] + # The svn subcommand arguments. + # @param options [Hash] + # Optional arguments. + # @option options [Array] :global_args + # Global svn arguments to place before the subcommand. + # + # @yield [line] + # If a block is given, each line that the Subversion command writes to + # standard output will be yielded to the calling block. + # @yieldparam line [String] + # Line of standard output. + # @return [String] + # The standard output from svn. + # @raise [SvnExecError] + # If the svn command errors out this exception is raised. + def run_svn(subcommand, args, options = {}, &block) + command = [ + "svn", + *(options[:global_args] || []), + subcommand, + *args, + ].compact + + # Create pipes for standard output and standard error. + stdout_rd, stdout_wr = IO.pipe + stderr_rd, stderr_wr = IO.pipe + + # Launch the svn subprocess using the pipes. + spawn_options = { + out: stdout_wr, + err: stderr_wr, + close_others: true, + } + spawn_options[:in] = :close unless options[:allow_interactive] + svn_pid = Process.spawn(*command, spawn_options) + + # Close write side of the pipes in the parent process. + stdout_wr.close + stderr_wr.close + + stdout = "" + stderr = "" + stdout_yield = "" + + loop do + so = stdout_rd.read_nonblock(exception: false) + if so.is_a?(String) + stdout += so + stdout_yield += so + end + se = stderr_rd.read_nonblock(exception: false) + if se.is_a?(String) + stderr += se + end + if so.nil? and se.nil? + break + end + if block + loop do + index = stdout_yield.index("\n") + break unless index + line = stdout_yield[0, index] + block[line] + stdout_yield = stdout_yield[index + 1, stdout_yield.size] + end + end + IO.select([stdout_rd, stderr_rd]) + end + + Process.waitpid(svn_pid) + + if stderr != "" + raise SvnExecError.new(stderr) + end + + stdout + end + end end