Rocco Rutte:
[apps/madmutt.git] / svnlog2changelog.pl
1 #!/usr/bin/perl -w
2
3 # Purpose:
4 # summarize and re-format subversion's log output to plain text
5 #
6 # Written by Rocco Rutte <pdmef@cs.tu-berlin.de>
7 # for internal use with mutt-ng <http://mutt-ng.berlios.de/>.
8 #
9 # License: GPL
10 #
11 # Usage/Options:
12 #       -t              print only today's messages
13 #       -s YYYY-MM-DD   print only messages since (and including) date
14 #       -i string       use string for indentation
15 #       -m number       break lines at the latest at number columns
16 #       -p string       prefix for filenames in 'svn log -v' output;
17 #                       for things like 'A /mutt-ng/trunk/foo' set this
18 #                       to 'mutt-ng/', i.e. exclude the leading / from
19 #                       it but include everything up to the last char
20 #                       before (trunk|tags|branches)
21 #       -h              help
22 #
23 # Note: lines matching ``^([^:]+):?$'' will be ignored if the first
24 # submatch in brackets is a known author; i.e. the logs
25 #
26 # | Foo Bar
27 # | added l33t c0d3
28 #
29 # and
30 #
31 # | Foo Bar:
32 # | added l33t c0d3
33 #
34 # will be interpreted as only 'added l33t c0d3' if 'Foo Bar' is known as
35 # an author. This does not count when grouping messages for authors,
36 # those lines are just skipped. This is hard-coded, see below.
37 #
38 # Also, put
39 # | From: ...
40 # as the first line of log messages when change is only committed by
41 # someone with access but the original author is someone else. If
42 # there's no such author line, the author's name will be grabbed from
43 # the %committers table below.
44
45 use strict;
46 use POSIX;
47 use Getopt::Std;
48
49 # hard-coded configuration: this maps user to full names
50 my %committers = (
51   "ak1"         => "Andreas Krennmair <ak\@synflood.at>",
52   "nion"        => "Nico Golde <nion\@muttng.org>",
53   "pdmef"       => "Rocco Rutte <pdmef\@cs.tu-berlin.de>",
54   "dkg1"        => "Daniel K. Gebhart <dpkg1\@users.berlios.de>"
55 );
56
57 my %fmap = (
58   "filesA"      => "Added",
59   "filesM"      => "Modified",
60   "filesD"      => "Deleted",
61   "filesR"      => "Replaced"
62 );
63
64 # default config
65 my %options = ();
66 my $linemax = 70;
67 my $today = "";
68 my $since = "";
69 my $indent = "    ";
70 my $pfx = "";
71
72 # some stuff we need
73 my $currev = 0;
74 my $lastrev = 0;
75 my @curlog = ();
76 my $curentry = "";
77 my $curauthor = "";
78 my $curcomm = "";
79 my $count = 0;
80 my %changes = ();
81
82 # nicely print log lines in itemized style with eye-candy indentation and
83 # somewhat smart line-breaking
84 sub niceline {
85   my ($text) = (@_);
86   my @lines = split (/\n/, $text);
87   for my $l (@lines) {
88     print "$indent$indent-";
89     my @words = split (/\ /, $l);
90     my $c = length ($indent);
91     for my $w (@words) {
92       if (length ($w) + $c > $linemax) {
93         print "\n$indent$indent  $w";
94         $c = length ($indent);
95       } else {
96         print " $w";
97       }
98       $c += length ($w) + 1;
99     }
100     print "\n";
101   }
102 }
103
104 sub usage {
105   print <<EOF
106 This is: svnlog2changelog.pl
107 written by Rocco Rutte <pdmef\@cs.tu-berlin.de>
108 for use with mutt-ng <http://mutt-ng.berlios.de/>
109
110 Usage:
111   svnlog2changelog.pl -h
112   svn log -v | svnlog2changelog.pl [-t] [-i YYYY-MM-DD] [-m number] [-p string]
113
114 Options:
115        -t              print only today's messages
116        -s YYYY-MM-DD   print only messages since (and including) date
117        -i string       use string for indentation
118        -m number       break lines at the latest at number columns
119        -p string       prefix for filenames in 'svn log -v' output;
120                        for things like 'A /mutt-ng/trunk/foo' set this
121                        to 'mutt-ng/', i.e. exclude the leading / from
122                        it but include everything up to the last char
123                        before (trunk|tags|branches)
124        -h              help
125
126 Examples:
127   - print Subversion's log for today:
128     svn log -v -r "{`date "+%Y-%m-%d"`}:HEAD" | svnlog2changelog.pl -t [-i string]
129
130   - print Subversion's log since (and including) YYYY-MM-DD
131     svn log -v | svnlog2changelog.pl -s YYYY-MM-DD [-i string]
132 EOF
133   ;
134 }
135
136 sub isknown {
137   my ($name) = (@_);
138   for my $k (keys %committers) {
139     if (substr ($committers{$k}, 0, length ($name)) eq $name) {
140       return (1);
141     }
142   }
143   return (0);
144 }
145
146 # get and process options
147 getopts ("tm:s:hi:p:", \%options);
148 if (defined $options{'t'}) {
149   $today = strftime ("%Y-%m-%d", localtime (time ()));
150 }
151 if (defined $options{'m'} and $options{'m'} =~ /^[0-9]{2,}$/) {
152   $linemax = $options{'m'};
153 }
154 if (defined $options{'s'} and $options{'s'} =~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) {
155   $since = $options{'s'};
156 }
157 if (defined $options{'h'}) {
158   &usage ();
159   exit (0);
160 }
161 if (defined $options{'i'}) {
162   $indent = $options{'i'};
163 }
164 if (defined $options{'p'}) {
165   $pfx = $options{'p'};
166   $pfx =~ s#/#\\/#g;
167 }
168
169 # parse log
170 while (<STDIN>) {
171   chomp;
172   if ($_ =~ /^r([0-9]+)/) {
173     $currev = $1;
174     @curlog = ();
175     $curauthor = "";
176     $count = 0;
177     my @items = split (/\ \|\ /, $_);
178     my @dateinfo = split (/\ /, $items[2]);
179     $curentry = $dateinfo[0];
180     $curauthor = $items[1];
181     $curcomm = $items[1];
182     # _keep_ latest rev. number for day
183     if (not defined ${$changes{$curentry}}{'rev'} or
184         ${$changes{$curentry}}{'rev'} lt substr ($items[0], 1)) {
185       ${$changes{$curentry}}{'rev'} = substr ($items[0], 1);
186     }
187     # _keep_ latest commit time for day
188     if (not defined ${$changes{$curentry}}{'time'}) {
189       ${$changes{$curentry}}{'time'} = "$dateinfo[1] $dateinfo[2]";
190     }
191     next;
192   }
193   $count++;
194   if ($count > 0) {
195     # check log line: contains author?
196     if ($_ =~ /^From: (.*)$/) {
197       $curauthor = "$1 ($curcomm)";
198       next;
199     }
200     elsif (defined $committers{$curauthor}) {
201       $curauthor = "$committers{$curcomm} ($curcomm)";
202     }
203     # check log line: contains noise?
204     if (length ($_) == 0 or $_ =~ /^[-]+$/ or 
205         $_ =~ /^([^:]+):?$/ and &isknown ($1) or
206         $_ eq "Changed paths:") {
207       next;
208     }
209     # check log line: contains list of changes/deleted/added files?
210     if ((length ($pfx) > 0 and $_ =~ /([AMD]) \/($pfx?.*)?$/) or
211         (length ($pfx) == 0 and $_ =~ /([AMD]) \/(.*)?$/)) {
212       my $what = $1;
213       my $target = "";
214       if (defined $2) {
215         $target = $2;
216       }
217       $target =~ s#$pfx##g;
218       ${${$changes{$curentry}}{"files$what"}}{$target} = 1;
219     } else {
220       # here the line really contains the log message
221       # try to be smart and remove itemizations people make
222       my $clean = $_;
223       $clean =~ s/^[- \t*]*//;
224       if (length ($clean) > 0) {
225         ${${$changes{$curentry}}{'log'}}{$curauthor} .= "$clean (r$currev)\n";
226         $lastrev = $currev;
227       }
228     }
229   }
230 }
231
232 my $first = "";
233 my $first2 = "";
234
235 for my $k (sort { $b cmp $a } (keys (%changes))) {
236   # ignore noise
237   if (not defined %{${$changes{$k}}{'log'}} or
238       (length ($since) > 0 && length ($today) == 0 && ($k lt $since)) or
239       (length ($today) > 0 && ($k ne $today))) {
240     next;
241   }
242   # print first line with date, time and latest revision for current day
243   print "$first$k  ${$changes{$k}}{'time'}  ";
244   $first = "\n";
245   $first2 = "";
246   print "Latest Revision: ${$changes{$k}}{'rev'}\n\n";
247   # per author: print his name and an itemized list of his log msgs.
248   # with smart line-breaking and indentation
249   for my $a (keys %{${$changes{$k}}{'log'}}) {
250     print "$first2$indent";
251     if (defined $committers{$a}) {
252       print $committers{$a};
253     } else {
254       print $a;
255     }
256     print ":\n";
257     &niceline (${${$changes{$k}}{'log'}}{$a});
258     $first2 = "\n";
259   }
260   for my $a (keys %fmap) {
261     if (defined %{${$changes{$k}}{$a}}) {
262       print "$first$indent$fmap{$a} Files:\n";
263       my $fixme = join (", ", keys %{${$changes{$k}}{$a}});
264       &niceline ($fixme);
265     }
266   }
267 }