#!/usr/bin/env ruby -w # $FreeBSD$ # Please note, that this utility must be kept in sync with # CVSROOT/log_accum.pl. If someone has a different output from their # mail client when saving e-mails as text files, feel free to hack it # in as an option. # # If someone would like to hack in the ability to generate diffs based # off of this script, by all means, be my guest. require 'getoptlong' $basedir = '/usr' $backout_script = "backout-#{Time.now.strftime("%Y-%m-%d-%H-%M")}.sh" $commit_authors = [] $commit_dates = [] $commit_file = nil $commit_message = nil $cvsbin = nil $cvs_path = '/usr/bin/cvs' $cvsrc_ignore = true $debug = 0 $echo_path = '/usr/bin/echo' $echo_warnings = true $force_script_edit = false $force_remove = false $output = $stdout $quiet_script = false $shell_path = '/bin/sh' $shell_args = '--' def debug(level, *msgs) if level <= $debug if $debug > 1 $output.puts "DEBUG(#{level}): #{msgs.shift}" else $output.puts msgs.shift end for msg in msgs $output.puts "\t #{msg}" end end end # def debug() def usage(msg, info = nil) out = (msg.nil? ? $stdout : $stderr) out.puts "#{File.basename($0)} usage:" << (msg.nil? ? '' : " #{msg}") out.puts "#{info}" unless info.nil? out.puts "" out.puts " -s, --backout-script= Specifies the filename of the script" out.puts " -D, --basedir= Specifies the base directory [/usr]" out.puts " -a, --commit-author= Forces a commit author" out.puts " -d, --commit-date= Forces a commit date" out.puts " -m, --commit-file= Specifies a commit message file" out.puts " -M, --commit-message= Specifies a commit message" out.puts " -c, --cvs-path= Specifies the CVS binary to be used [cvs]" out.puts " -C, --cvsrc-ignore= If true, will ignore options in ~/.cvsrc" out.puts " -e, --echo-path= Specifies the path to echo" out.puts " -f, --force-remove= If true, removes new files [false]" out.puts " -F, --force-edit= If true, add -C to the shell arguments in" out.puts " the backout script if the shell is sh," out.puts " which forces an edit of the script" out.puts " -O, --output= Specifies what fd to direct the output to" out.puts " -A, --shell-args= Specifies the shell arguments to be used" out.puts " -S, --shell-path= Specifies the shell to be used [/bin/sh]" out.puts " -W, --warnings= Turns on or off warnings [true]" exit(msg.nil? ? 0 : 1) end OPTION_LIST = [ ['--backout-script','-s', GetoptLong::REQUIRED_ARGUMENT], ['--basedir','-D', GetoptLong::REQUIRED_ARGUMENT], ['--commit-author','-a', GetoptLong::REQUIRED_ARGUMENT], ['--commit-date','-d', GetoptLong::REQUIRED_ARGUMENT], ['--commit-file','-m', GetoptLong::REQUIRED_ARGUMENT], ['--commit-message','-M', GetoptLong::REQUIRED_ARGUMENT], ['--cvs-path','-c',GetoptLong::REQUIRED_ARGUMENT], ['--cvsrc-ignore','-C',GetoptLong::REQUIRED_ARGUMENT], ['--echo-path','-e',GetoptLong::REQUIRED_ARGUMENT], ['--force-edit','-F',GetoptLong::REQUIRED_ARGUMENT], ['--force-remove','-f',GetoptLong::REQUIRED_ARGUMENT], ['--output', '-O', GetoptLong::REQUIRED_ARGUMENT], ['--quiet-script','-q',GetoptLong::REQUIRED_ARGUMENT], ['--shell-args','-A',GetoptLong::REQUIRED_ARGUMENT], ['--shell-path','-S',GetoptLong::REQUIRED_ARGUMENT], ['--warnings','-w', GetoptLong::REQUIRED_ARGUMENT], ] opt_parser = GetoptLong.new(*OPTION_LIST) opt_parser.quiet = true begin opt_parser.each do |opt,arg| case opt when '--backout-script' debug(3, "backout script was #{$backout_script.inspect} : is #{arg.inspect}") $backout_script = arg when '--basedir' debug(3, "base directory was #{$basedir.inspect} : is #{arg.inspect}") $basedir = arg when '--commit-author' debug(3, "commit author #{arg.inspect} added to list") $commit_authors.push(arg.dup) when '--commit-date' debug(3, "commit date #{arg.inspect} added to list") $commit_date.push(arg.dup) when '--commit-file' debug(3, "commit file was #{$commit_file.inspect} : is #{arg.inspect}") $commit_file = arg when '--commit-message' debug(3, "commit message was #{$commit_message.inspect} : is #{arg.inspect}") $commit_message = arg when '--cvs-path' debug(3, "cvs path was #{$cvs_path.inspect} : is #{arg.inspect}") $cvs_path = arg when '--cvsrc-ignore' if arg =~ /true|yes/i $cvsrc_ignore = true elsif arg =~ /false|no/i $cvsrc_ignore = false else usage("#{opt}: unknown bool format \"#{arg}\"", "Valid options are \"true\", \"false\", \"yes\", or \"no\"") end debug(3, "ignoring of ~/.cvsrc is set to #{$cvsrc_ignore.inspect}") when '--echo-path' debug(3, "echo path was #{$echo_path.inspect} : is #{arg.inspect}") $echo_path = arg when '--force-edit' if arg =~ /true|yes/i $force_script_edit = true elsif arg =~ /false|no/i $force_script_edit = false else usage("#{opt}: unknown bool format \"#{arg}\"", "Valid options are \"true\", \"false\", \"yes\", or \"no\"") end debug(3, "force edit of backout script is set to #{$force_script_edit.inspect}") when '--force-remove' if arg =~ /true|yes/i $force_remove = true elsif arg =~ /false|no/i $force_remove = false else usage("#{opt}: unknown bool format \"#{arg}\"", "Valid options are \"true\", \"false\", \"yes\", or \"no\"") end debug(3, "force removal of files is set to #{$force_remove.inspect}") when '--output' case arg when 'stdout' $output = $stdout when 'stderr' $output = $stderr else usage("#{opt}: unknown output format","Valid outputs are \"stdout\" and \"stderr\"") end debug(3, "output set to #{arg}") when '--quiet-script' if arg =~ /true|yes/i $quiet_script = true elsif arg =~ /false|no/i $quiet_script = false else usage("#{opt}: unknown bool format \"#{arg}\"", "Valid options are \"true\", \"false\", \"yes\", or \"no\"") end debug(3, "quiet script is set to #{$quiet_script.inspect}") when '--shell-args' debug(3, "shell args were #{$shell_args.inspect} : is #{arg.inspect}") $shell_args = arg when '--shell-path' debug(3, "shell path was #{$shell_path.inspect} : is #{arg.inspect}") $shell_path = arg when '--warnings' if arg =~ /true|yes/i $echo_warnings = true elsif arg =~ /false|no/i $echo_warnings = false else usage("#{opt}: unknown bool format \"#{arg}\"", "Valid options are \"true\", \"false\", \"yes\", or \"no\"") end debug(3, "warnings are set to #{$echo_warnings.inspect}") end end rescue GetoptLong::InvalidOption usage("invalid argument") rescue GetoptLong::MissingArgument usage("missing argument") rescue GetoptLong::NeedlessArgument => msg usage("passed an extra argument: #{msg}") end debug(3, "Verbosity set to: #{$debug}") $cvsbin = $cvs_path $cvsbin << " -f" if $cvsrc_ignore if ARGV.length < 1 usage("require a commit message to parse") end $output.puts("Backout directory:\t#{$basedir}") $output.puts("Backout script:\t\t#{$backout_script}") $output.puts("") # Backout script - to be run by hand File.open($backout_script, "w+") do |f| removals = [] updates = [] files = [] f.puts("#!#{$shell_path}#{($force_script_edit && $shell_path == '/bin/sh') ? ' -C' : ''} #{$shell_args}") f.puts() f.puts("# Generated at: #{Time.now()}") f.puts("# Generated by: #{ENV['USER']}\@#{ENV['HOST']}") f.puts() f.puts("BASEDIR=#{$basedir}") f.puts('if [ $BASEDIR != $PWD ]; then') f.puts(' echo "Please change to $BASEDIR before running this shell script"') f.puts(' exit 1') f.puts('fi') f.puts() author_regexp = Regexp.new(/^([^\ ]+)\s+([\d]{4})\/([\d]{2})\/([\d]{2}) ([\d]{2}):([\d]{2}):([\d]{2}) ([A-Z]{3})$/) file_regexp = Regexp.new(/^ ([\d\.]+)\s+\+([\d]+) \-([\d]+)\s+(.*?)$/) newdead_regexp = Regexp.new(/^(.*?) \((new|dead)\)$/) rev_regexp = Regexp.new(/^ Revision Changes Path$/) for email_file in ARGV File.open(email_file) do |e| $output.print("Scanning through #{email_file}...") found_files = false for line in e line.chomp! if found_files == false amd = author_regexp.match(line) if !amd.nil? $commit_authors.push(amd[1].dup) $commit_dates.push(Time.local(*amd[2..7]).dup) elsif rev_regexp.match(line) found_files = true end else # if found_files md = file_regexp.match(line) next if md.nil? filename = md[4] ndmd = newdead_regexp.match(filename) if !ndmd.nil? filename = ndmd[1] if ndmd[2] == 'new' removals.push(filename) f.puts("#{$force_remove ? '' : '# '}#{$echo_path} -n \"Removing #{filename}...\"") if !$quiet_script f.puts("#{$force_remove ? '' : '# '}#{$cvsbin} rm -f #{filename}") f.puts("#{$force_remove ? '' : '# '}#{$echo_path} \"done.\"") if !$quiet_script f.puts() files.push(filename) next end end f.puts("#{$echo_path} -n \"Updating #{filename} to #{md[1]}...\"") if !$quiet_script f.puts("#{$cvsbin} up -p -r #{md[1]} #{filename} > #{filename}") f.puts("#{$echo_path} \"done.\"") if !$quiet_script f.puts() files.push(filename) end # if found_files end # for line in.. $output.puts("done.") end # File.open() end # for email_file in ARGV... if removals.length > 0 && $force_remove == false f.puts("#{$echo_warnings ? '' : '# '}#{$echo_path} \"You may want to remove the following file#{removals.length > 1 ? 's' : ''}:\"") for filename in removals f.puts("#{$echo_warnings ? '' : '# '}#{$echo_path} \"\t#{filename}\"") end f.puts() f.puts("#{$echo_warnings ? '' : '# '}#{$echo_path} \"There is code in #{$backout_script} to remove #{removals.length > 1 ? 'these files' : 'this file'} for you,\"") f.puts("#{$echo_warnings ? '' : '# '}#{$echo_path} \"just uncomment them or pass the option --force-remove=true to #{$0}.\"") end f.puts() f.puts("# # # Uncomment the following line to commit the backout.") f.puts("# # #{$echo_path} -n \"Committing backout...\"") if !$quiet_script if !$commit_message.nil? if $commit_message.empty? or $commit_message =~ /^default|no|yes|true|false/i $commit_message = "Backout of commit by #{$commit_authors.join(', ')} done on #{$commit_dates.join(', ')} because\n[___FILL_IN_THE_BLANK___]\n" end f.puts() f.puts("# # # EDIT COMMIT MESSAGE HERE") f.puts("CVSCOMMITMSG=<