1#!ruby
2require "time"
3
4def usage!
5  STDERR.puts <<-EOS
6Usage: #$0 [--trunk=<dir>] [--target=<dir>] <revision(s)>
7
8Generate ChangeLog entries for backporting.
9The entries are output to STDOUT, and the messages of this tool are output to
10STDERR. So you can simply redirect STDOUT to get the entries.
11
12You should specify the path of trunk by `--trunk`. If not, assumed cwd.
13You also should specify the path of the target branch by `--target`. If not,
14assumed cwd.
15This means that you have to specify at least one of `--trunk` or `--target`.
16
17revision(s) can be below or their combinations:
18  12345        # means r12345
19  12345,54321  # means r12345 and r54321
20  12345-12347  # means r12345, r12346 and r12347 (of course, if available)
21
22Note that the revisions is backported branch's ones, not trunk's.
23
24The target of this tool is *not* to generate ChangeLog automatically, but to
25generate the draft of ChangeLog.
26You have to check and modify the output.
27  EOS
28  exit
29end
30
31Majors = {
32  "eregon" => "Benoit Daloze  <eregontp@gmail.com>",
33  "kazu" => "Kazuhiro NISHIYAMA  <zn@mbf.nifty.com>",
34  "ko1" => "Koichi Sasada  <ko1@atdot.net>",
35  "marcandre" => "Marc-Andre Lafortune  <ruby-core@marc-andre.ca>",
36  "naruse" => "NARUSE, Yui  <naruse@ruby-lang.org>",
37  "nobu" => "Nobuyoshi Nakada  <nobu@ruby-lang.org>",
38  "normal" => "Eric Wong  <normalperson@yhbt.net>",
39  "rhe" => "Kazuki Yamaguchi  <k@rhe.jp>",
40  "shugo" => "Shugo Maeda  <shugo@ruby-lang.org>",
41  "stomar" => "Marcus Stollsteimer <sto.mar@web.de>",
42  "usa" => "NAKAMURA Usaku  <usa@ruby-lang.org>",
43  "zzak" => "Zachary Scott  <e@zzak.io>",
44}
45
46trunk = "."
47target = "."
48ARGV.delete_if{|e| /^--trunk=(.*)/ =~ e && trunk = $1}
49ARGV.delete_if{|e| /^--target=(.*)/ =~ e && target = $1}
50usage! if ARGV.size == 0 || trunk == target
51
52revisions = []
53ARGV.each do |a|
54  a.split(/,/).each do |b|
55    if /-/ =~ b
56      revisions += Range.new(*b.split(/-/, 2).map{|e| Integer(e)}).to_a
57    else
58      revisions << Integer(b)
59    end
60  end
61end
62revisions.sort!
63revisions.reverse!
64
65revisions.each do |rev|
66  if /^Index: ChangeLog$/ =~ `svn diff -c #{rev} #{target}`
67    STDERR.puts "#{rev} already has ChangeLog. Skip."
68  else
69    lines = `svn log -r #{rev} #{target}`.lines[1..-2]
70    if lines.empty?
71      STDERR.puts "#{rev} does not exist. Skip."
72      next
73    end
74    unless /^merge revision\(s\) (\d+)/ =~ lines[2]
75      STDERR.puts "#{rev} is not seems to be a merge commit. Skip."
76      next
77    end
78    original = $1
79    committer = `svn log -r #{original} #{trunk}`.lines[1].split(/\|/)[1].strip
80    if Majors[committer]
81      committer = Majors[committer]
82    else
83      committer = "#{committer}  <#{committer}@ruby-lang.org>"
84    end
85    time = Time.parse(lines.shift.split(/\|/)[2]).getlocal("+09:00")
86    puts "#{time.asctime}  #{committer}"
87    puts
88    lines.shift(2) # skip "merge" line
89    lines.shift while lines.first == "\n"
90    lines.pop while lines.last == "\n"
91    lines.each do |line|
92      line.chomp!
93      line = "\t#{line}" if line[0] != "\t" && line != ""
94      puts line
95    end
96    puts
97    STDERR.puts "#{rev} is processed."
98  end
99end
100