]> CyberLeo.Net >> Repos - FreeBSD/releng/8.1.git/blob - tools/tools/backout_commit/backout_commit.rb
Copy stable/8 to releng/8.1 in preparation for 8.1-RC1.
[FreeBSD/releng/8.1.git] / tools / tools / backout_commit / backout_commit.rb
1 #!/usr/bin/env ruby -w
2
3 # $FreeBSD$
4
5 # Please note, that this utility must be kept in sync with
6 # CVSROOT/log_accum.pl.  If someone has a different output from their
7 # mail client when saving e-mails as text files, feel free to hack it
8 # in as an option.
9 #
10 # If someone would like to hack in the ability to generate diffs based
11 # off of this script, by all means, be my guest.
12
13 require 'getoptlong'
14
15 $basedir        = '/usr'
16 $backout_script = "backout-#{Time.now.strftime("%Y-%m-%d-%H-%M")}.sh"
17 $commit_authors = []
18 $commit_dates   = []
19 $commit_file    = nil
20 $commit_message = nil
21 $cvsbin         = nil
22 $cvs_path       = '/usr/bin/cvs'
23 $cvsrc_ignore   = true
24 $debug          = 0
25 $echo_path      = '/usr/bin/echo'
26 $echo_warnings  = true
27 $force_script_edit = false
28 $force_remove   = false
29 $output         = $stdout
30 $quiet_script   = false
31 $shell_path     = '/bin/sh'
32 $shell_args     = '--'
33
34 def debug(level, *msgs)
35   if level <= $debug
36     if $debug > 1
37       $output.puts "DEBUG(#{level}): #{msgs.shift}"
38     else
39       $output.puts msgs.shift
40     end
41
42     for msg in msgs
43       $output.puts "\t  #{msg}"
44     end
45   end
46 end # def debug()
47
48
49 def usage(msg, info = nil)
50   out = (msg.nil? ? $stdout : $stderr)
51   out.puts "#{File.basename($0)} usage:" << (msg.nil? ? '' : " #{msg}")
52   out.puts "#{info}" unless info.nil?
53   out.puts ""
54   out.puts "  -s, --backout-script=<file>  Specifies the filename of the script"
55   out.puts "  -D, --basedir=<dir>          Specifies the base directory [/usr]"
56   out.puts "  -a, --commit-author=<uid>    Forces a commit author"
57   out.puts "  -d, --commit-date=<date>     Forces a commit date"
58   out.puts "  -m, --commit-file=<path>     Specifies a commit message file"
59   out.puts "  -M, --commit-message=<msg>   Specifies a commit message"
60   out.puts "  -c, --cvs-path=<path>        Specifies the CVS binary to be used [cvs]"
61   out.puts "  -C, --cvsrc-ignore=<bool>    If true, will ignore options in ~/.cvsrc"
62   out.puts "  -e, --echo-path=<path>       Specifies the path to echo"
63   out.puts "  -f, --force-remove=<bool>    If true, removes new files [false]"
64   out.puts "  -F, --force-edit=<bool>      If true, add -C to the shell arguments in"
65   out.puts "                               the backout script if the shell is sh,"
66   out.puts "                               which forces an edit of the script"
67   out.puts "  -O, --output=<stdio>         Specifies what fd to direct the output to"
68   out.puts "  -A, --shell-args=<string>    Specifies the shell arguments to be used"
69   out.puts "  -S, --shell-path=<path>      Specifies the shell to be used [/bin/sh]"
70   out.puts "  -W, --warnings=<bool>        Turns on or off warnings [true]"
71   exit(msg.nil? ? 0 : 1)
72 end
73
74
75 OPTION_LIST = [
76   ['--backout-script','-s', GetoptLong::REQUIRED_ARGUMENT],
77   ['--basedir','-D', GetoptLong::REQUIRED_ARGUMENT],
78   ['--commit-author','-a', GetoptLong::REQUIRED_ARGUMENT],
79   ['--commit-date','-d', GetoptLong::REQUIRED_ARGUMENT],
80   ['--commit-file','-m', GetoptLong::REQUIRED_ARGUMENT],
81   ['--commit-message','-M', GetoptLong::REQUIRED_ARGUMENT],
82   ['--cvs-path','-c',GetoptLong::REQUIRED_ARGUMENT],
83   ['--cvsrc-ignore','-C',GetoptLong::REQUIRED_ARGUMENT],
84   ['--echo-path','-e',GetoptLong::REQUIRED_ARGUMENT],
85   ['--force-edit','-F',GetoptLong::REQUIRED_ARGUMENT],
86   ['--force-remove','-f',GetoptLong::REQUIRED_ARGUMENT],
87   ['--output', '-O', GetoptLong::REQUIRED_ARGUMENT],
88   ['--quiet-script','-q',GetoptLong::REQUIRED_ARGUMENT],
89   ['--shell-args','-A',GetoptLong::REQUIRED_ARGUMENT],
90   ['--shell-path','-S',GetoptLong::REQUIRED_ARGUMENT],
91   ['--warnings','-w', GetoptLong::REQUIRED_ARGUMENT],
92 ]
93
94 opt_parser = GetoptLong.new(*OPTION_LIST)
95 opt_parser.quiet = true
96
97 begin
98   opt_parser.each do |opt,arg|
99     case opt
100     when '--backout-script'
101       debug(3, "backout script was #{$backout_script.inspect} : is #{arg.inspect}")
102       $backout_script = arg
103     when '--basedir'
104       debug(3, "base directory was #{$basedir.inspect} : is #{arg.inspect}")
105       $basedir = arg
106     when '--commit-author'
107       debug(3, "commit author #{arg.inspect} added to list")
108       $commit_authors.push(arg.dup)
109     when '--commit-date'
110       debug(3, "commit date #{arg.inspect} added to list")
111       $commit_date.push(arg.dup)
112     when '--commit-file'
113       debug(3, "commit file was #{$commit_file.inspect} : is #{arg.inspect}")
114       $commit_file = arg
115     when '--commit-message'
116       debug(3, "commit message was #{$commit_message.inspect} : is #{arg.inspect}")
117       $commit_message = arg
118     when '--cvs-path'
119       debug(3, "cvs path was #{$cvs_path.inspect} : is #{arg.inspect}")
120       $cvs_path = arg
121     when '--cvsrc-ignore'
122       if arg =~ /true|yes/i
123         $cvsrc_ignore = true
124       elsif arg =~ /false|no/i
125         $cvsrc_ignore = false
126       else
127         usage("#{opt}: unknown bool format \"#{arg}\"", "Valid options are \"true\", \"false\", \"yes\", or \"no\"")
128       end
129       debug(3, "ignoring of ~/.cvsrc is set to #{$cvsrc_ignore.inspect}")
130     when '--echo-path'
131       debug(3, "echo path was #{$echo_path.inspect} : is #{arg.inspect}")
132       $echo_path = arg
133     when '--force-edit'
134       if arg =~ /true|yes/i
135         $force_script_edit = true
136       elsif arg =~ /false|no/i
137         $force_script_edit = false
138       else
139         usage("#{opt}: unknown bool format \"#{arg}\"", "Valid options are \"true\", \"false\", \"yes\", or \"no\"")
140       end
141       debug(3, "force edit of backout script is set to #{$force_script_edit.inspect}")
142     when '--force-remove'
143       if arg =~ /true|yes/i
144         $force_remove = true
145       elsif arg =~ /false|no/i
146         $force_remove = false
147       else
148         usage("#{opt}: unknown bool format \"#{arg}\"", "Valid options are \"true\", \"false\", \"yes\", or \"no\"")
149       end
150       debug(3, "force removal of files is set to #{$force_remove.inspect}")
151     when '--output'
152       case arg
153       when 'stdout'
154         $output = $stdout
155       when 'stderr'
156         $output = $stderr
157       else
158         usage("#{opt}: unknown output format","Valid outputs are \"stdout\" and \"stderr\"")
159       end
160       debug(3, "output set to #{arg}")
161     when '--quiet-script'
162       if arg =~ /true|yes/i
163         $quiet_script = true
164       elsif arg =~ /false|no/i
165         $quiet_script = false
166       else
167         usage("#{opt}: unknown bool format \"#{arg}\"", "Valid options are \"true\", \"false\", \"yes\", or \"no\"")
168       end
169       debug(3, "quiet script is set to #{$quiet_script.inspect}")
170     when '--shell-args'
171       debug(3, "shell args were #{$shell_args.inspect} : is #{arg.inspect}")
172       $shell_args = arg
173     when '--shell-path'
174       debug(3, "shell path was #{$shell_path.inspect} : is #{arg.inspect}")
175       $shell_path = arg
176     when '--warnings'
177       if arg =~ /true|yes/i
178         $echo_warnings = true
179       elsif arg =~ /false|no/i
180         $echo_warnings = false
181       else
182         usage("#{opt}: unknown bool format \"#{arg}\"", "Valid options are \"true\", \"false\", \"yes\", or \"no\"")
183       end
184       debug(3, "warnings are set to #{$echo_warnings.inspect}")
185     end
186   end
187 rescue GetoptLong::InvalidOption
188   usage("invalid argument")
189 rescue GetoptLong::MissingArgument
190   usage("missing argument")
191 rescue GetoptLong::NeedlessArgument => msg
192   usage("passed an extra argument: #{msg}")
193 end
194
195 debug(3, "Verbosity set to: #{$debug}")
196
197 $cvsbin = $cvs_path
198 $cvsbin << " -f" if $cvsrc_ignore  
199
200 if ARGV.length < 1
201   usage("require a commit message to parse")
202 end
203
204 $output.puts("Backout directory:\t#{$basedir}")
205 $output.puts("Backout script:\t\t#{$backout_script}")
206 $output.puts("")
207
208 # Backout script - to be run by hand
209 File.open($backout_script, "w+") do |f|
210   removals = []
211   updates = []
212   files = []
213
214   f.puts("#!#{$shell_path}#{($force_script_edit && $shell_path == '/bin/sh') ? ' -C' : ''} #{$shell_args}")
215   f.puts()
216   f.puts("# Generated at: #{Time.now()}")
217   f.puts("# Generated by: #{ENV['USER']}\@#{ENV['HOST']}")
218   f.puts()
219   f.puts("BASEDIR=#{$basedir}")
220   f.puts('if [ $BASEDIR != $PWD ]; then')
221   f.puts('  echo "Please change to $BASEDIR before running this shell script"')
222   f.puts('  exit 1')
223   f.puts('fi')
224   f.puts()
225
226   author_regexp  = Regexp.new(/^([^\ ]+)\s+([\d]{4})\/([\d]{2})\/([\d]{2}) ([\d]{2}):([\d]{2}):([\d]{2}) ([A-Z]{3})$/)
227   file_regexp    = Regexp.new(/^  ([\d\.]+)\s+\+([\d]+) \-([\d]+)\s+(.*?)$/)
228   newdead_regexp = Regexp.new(/^(.*?) \((new|dead)\)$/)
229   rev_regexp     = Regexp.new(/^  Revision  Changes    Path$/)
230
231   for email_file in ARGV
232     File.open(email_file) do |e|
233       $output.print("Scanning through #{email_file}...")
234       found_files = false
235       for line in e
236         line.chomp!
237         if found_files == false
238           amd = author_regexp.match(line)
239           if !amd.nil?
240             $commit_authors.push(amd[1].dup)
241             $commit_dates.push(Time.local(*amd[2..7]).dup)
242           elsif rev_regexp.match(line)
243             found_files = true
244           end
245         else # if found_files
246           md = file_regexp.match(line)
247           next if md.nil?
248
249           filename = md[4]
250           ndmd = newdead_regexp.match(filename)
251           if !ndmd.nil?
252             filename = ndmd[1]
253             if ndmd[2] == 'new'
254               removals.push(filename)
255               f.puts("#{$force_remove ? '' : '# '}#{$echo_path} -n \"Removing #{filename}...\"") if !$quiet_script
256               f.puts("#{$force_remove ? '' : '# '}#{$cvsbin} rm -f #{filename}")
257               f.puts("#{$force_remove ? '' : '# '}#{$echo_path} \"done.\"") if !$quiet_script
258               f.puts()
259               files.push(filename)
260               next
261             end
262           end
263           f.puts("#{$echo_path} -n \"Updating #{filename} to #{md[1]}...\"") if !$quiet_script
264           f.puts("#{$cvsbin} up -p -r #{md[1]} #{filename} > #{filename}")
265           f.puts("#{$echo_path} \"done.\"") if !$quiet_script
266           f.puts()
267           files.push(filename)
268         end # if found_files
269       end # for line in..
270       $output.puts("done.")
271     end # File.open()
272   end # for email_file in ARGV...
273
274   if removals.length > 0 && $force_remove == false
275     f.puts("#{$echo_warnings ? '' : '# '}#{$echo_path} \"You may want to remove the following file#{removals.length > 1 ? 's' : ''}:\"")
276     for filename in removals
277       f.puts("#{$echo_warnings ? '' : '# '}#{$echo_path} \"\t#{filename}\"")
278     end
279     f.puts()
280     f.puts("#{$echo_warnings ? '' : '# '}#{$echo_path} \"There is code in #{$backout_script} to remove #{removals.length > 1 ? 'these files' : 'this file'} for you,\"")
281     f.puts("#{$echo_warnings ? '' : '# '}#{$echo_path} \"just uncomment them or pass the option --force-remove=true to #{$0}.\"")
282   end
283
284   f.puts()
285   f.puts("# # # Uncomment the following line to commit the backout.")
286   f.puts("# # #{$echo_path} -n \"Committing backout...\"") if !$quiet_script
287   if !$commit_message.nil?
288     if $commit_message.empty? or $commit_message =~ /^default|no|yes|true|false/i
289       $commit_message = "Backout of commit by #{$commit_authors.join(', ')} done on #{$commit_dates.join(', ')} because\n[___FILL_IN_THE_BLANK___]\n"
290     end
291
292     f.puts()
293     f.puts("# # # EDIT COMMIT MESSAGE HERE")
294     f.puts("CVSCOMMITMSG=<<DONTUSECVSMSG")
295     f.puts($commit_message)
296     f.puts('DONTUSECVSMSG')
297     f.puts()
298   elsif !$commit_file.nil?
299     f.puts("if [ ! -r #{$commit_file} ]; then")
300     f.puts("  #{$echo_path} \"The commit message file #{$commit_file} is not readable,\"")
301     f.puts("  #{$echo_path} \"please fix this and re-run the script.\"")
302     f.puts("  exit 1")
303     f.puts("fi")
304     f.puts()
305   end
306
307   f.print("# # #{$cvsbin} ci")
308   if !$commit_message.nil?
309     f.print(" -m \"$CVSCOMMITMSG\"")
310   elsif !$commit_file.nil?
311     f.print(" -F \"#{$commit_file}\"")
312   end
313   f.puts(" #{files.join(' ')}")
314
315   if !$quiet_script
316     if $commit_message.nil? and $commit_file.nil?
317       f.print("# # #{$echo_path} \"Commit complete.  Backout should be complete.  Please check to verify.\"")
318     else
319       f.puts("# # #{$echo_path} \"done.\"")
320     end
321   end
322 end # File.open()
323
324 $output.puts()
325 $output.puts("Change to #{$basedir} and run this script.  Please look through this script and")
326 $output.puts("make changes as necessary.  There are commented out commands available")
327 $output.puts("in the script.")
328 $output.puts()
329 if !$commit_message.nil?
330   $output.puts("If you scroll to the bottom of #{$backout_script} you should be able to")
331   $output.puts("find a HERE document with your commit message, if you would like to make")
332   $output.puts("any further changes to your message.")
333   $output.puts()
334 end
335 if !$commit_file.nil?
336   begin
337     stat = File.stat($commit_file)
338   rescue Errno::ENOENT
339     $output.puts("The output file specified, \"#{$commit_file}\" DOES NOT EXIST!!!  Please be sure to")
340     $output.puts("create/edit the file \"#{$commit_file}\" before you run this script")
341     $output.puts()
342   end
343 end
344 $output.puts("Example script usage:")
345 $output.puts("\tmv #{$backout_script} #{$basedir}")
346 $output.puts("\tcd #{$basedir}")
347 $output.puts("\tless #{$backout_script}")
348 $output.puts("\t#{$shell_path} #{$backout_script}")
349 $output.puts("\trm -f #{$backout_script}")
350 $output.puts()