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 );
62
63 # default config
64 my %options = ();
65 my $linemax = 70;
66 my $today = "";
67 my $since = "";
68 my $indent = "    ";
69 my $pfx = "";
70
71 # some stuff we need
72 my $currev = 0;
73 my $lastrev = 0;
74 my @curlog = ();
75 my $curentry = "";
76 my $curauthor = "";
77 my $curcomm = "";
78 my $count = 0;
79 my %changes = ();
80
81 # nicely print log lines in itemized style with eye-candy indentation and
82 # somewhat smart line-breaking
83 sub niceline {
84   my ($text) = (@_);
85   my @lines = split (/\n/, $text);
86   for my $l (@lines) {
87     print "$indent$indent-";
88     my @words = split (/\ /, $l);
89     my $c = length ($indent);
90     for my $w (@words) {
91       if (length ($w) + $c > $linemax) {
92         print "\n$indent$indent  $w";
93         $c = length ($indent);
94       } else {
95         print " $w";
96       }
97       $c += length ($w) + 1;
98     }
99     print "\n";
100   }
101 }
102
103 sub usage {
104   print <<EOF
105 This is: svnlog2changelog.pl
106 written by Rocco Rutte <pdmef\@cs.tu-berlin.de>
107 for use with mutt-ng <http://mutt-ng.berlios.de/>
108
109 Usage:
110   svnlog2changelog.pl -h
111   svn log -v | svnlog2changelog.pl [-t] [-i YYYY-MM-DD] [-m number] [-p string]
112
113 Options:
114        -t              print only today's messages
115        -s YYYY-MM-DD   print only messages since (and including) date
116        -i string       use string for indentation
117        -m number       break lines at the latest at number columns
118        -p string       prefix for filenames in 'svn log -v' output;
119                        for things like 'A /mutt-ng/trunk/foo' set this
120                        to 'mutt-ng/', i.e. exclude the leading / from
121                        it but include everything up to the last char
122                        before (trunk|tags|branches)
123        -h              help
124
125 Examples:
126   - print Subversion's log for today:
127     svn log -v -r "{`date "+%Y-%m-%d"`}:HEAD" | svnlog2changelog.pl -t [-i string]
128
129   - print Subversion's log since (and including) YYYY-MM-DD
130     svn log -v | svnlog2changelog.pl -s YYYY-MM-DD [-i string]
131 EOF
132   ;
133 }
134
135 sub isknown {
136   my ($name) = (@_);
137   for my $k (keys %committers) {
138     if (substr ($committers{$k}, 0, length ($name)) eq $name) {
139       return (1);
140     }
141   }
142   return (0);
143 }
144
145 # get and process options
146 getopts ("tm:s:hi:p:", \%options);
147 if (defined $options{'t'}) {
148   $today = strftime ("%Y-%m-%d", localtime (time ()));
149 }
150 if (defined $options{'m'} and $options{'m'} =~ /^[0-9]{2,}$/) {
151   $linemax = $options{'m'};
152 }
153 if (defined $options{'s'} and $options{'s'} =~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) {
154   $since = $options{'s'};
155 }
156 if (defined $options{'h'}) {
157   &usage ();
158   exit (0);
159 }
160 if (defined $options{'i'}) {
161   $indent = $options{'i'};
162 }
163 if (defined $options{'p'}) {
164   $pfx = $options{'p'};
165   $pfx =~ s#/#\\/#g;
166 }
167
168 # parse log
169 while (<STDIN>) {
170   chomp;
171   if ($_ =~ /^r([0-9]+)/) {
172     $currev = $1;
173     @curlog = ();
174     $curauthor = "";
175     $count = 0;
176     my @items = split (/\ \|\ /, $_);
177     my @dateinfo = split (/\ /, $items[2]);
178     $curentry = $dateinfo[0];
179     $curauthor = $items[1];
180     $curcomm = $items[1];
181     # _keep_ latest rev. number for day
182     if (not defined ${$changes{$curentry}}{'rev'} or
183         ${$changes{$curentry}}{'rev'} lt substr ($items[0], 1)) {
184       ${$changes{$curentry}}{'rev'} = substr ($items[0], 1);
185     }
186     # _keep_ latest commit time for day
187     if (not defined ${$changes{$curentry}}{'time'}) {
188       ${$changes{$curentry}}{'time'} = "$dateinfo[1] $dateinfo[2]";
189     }
190     next;
191   }
192   $count++;
193   if ($count > 0) {
194     # check log line: contains author?
195     if ($_ =~ /^From: (.*)$/) {
196       $curauthor = "$1 ($curcomm)";
197       next;
198     }
199     elsif (defined $committers{$curauthor}) {
200       $curauthor = "$committers{$curcomm} ($curcomm)";
201     }
202     # check log line: contains noise?
203     if (length ($_) == 0 or $_ =~ /^[-]+$/ or 
204         $_ =~ /^([^:]+):?$/ and &isknown ($1) or
205         $_ eq "Changed paths:") {
206       next;
207     }
208     # check log line: contains list of changes/deleted/added files?
209     if ((length ($pfx) > 0 and $_ =~ /([AMD]) \/($pfx?.*)?$/) or
210         (length ($pfx) == 0 and $_ =~ /([AMD]) \/(.*)?$/)) {
211       my $what = $1;
212       my $target = "";
213       if (defined $2) {
214         $target = $2;
215       }
216       $target =~ s#$pfx##g;
217       ${${$changes{$curentry}}{"files$what"}}{$target} = 1;
218     } else {
219       # here the line really contains the log message
220       # try to be smart and remove itemizations people make
221       my $clean = $_;
222       $clean =~ s/^[- \t*]*//;
223       if (length ($clean) > 0) {
224         ${${$changes{$curentry}}{'log'}}{$curauthor} .= "$clean (r$currev)\n";
225         $lastrev = $currev;
226       }
227     }
228   }
229 }
230
231 my $first = "";
232 my $first2 = "";
233
234 for my $k (sort { $b cmp $a } (keys (%changes))) {
235   # ignore noise
236   if (not defined %{${$changes{$k}}{'log'}} or
237       (length ($since) > 0 && length ($today) == 0 && ($k lt $since)) or
238       (length ($today) > 0 && ($k ne $today))) {
239     next;
240   }
241   # print first line with date, time and latest revision for current day
242   print "$first$k  ${$changes{$k}}{'time'}  ";
243   $first = "\n";
244   $first2 = "";
245   print "Latest Revision: ${$changes{$k}}{'rev'}\n\n";
246   # per author: print his name and an itemized list of his log msgs.
247   # with smart line-breaking and indentation
248   for my $a (keys %{${$changes{$k}}{'log'}}) {
249     print "$first2$indent";
250     if (defined $committers{$a}) {
251       print $committers{$a};
252     } else {
253       print $a;
254     }
255     print ":\n";
256     &niceline (${${$changes{$k}}{'log'}}{$a});
257     $first2 = "\n";
258   }
259   for my $a (keys %fmap) {
260     if (defined %{${$changes{$k}}{$a}}) {
261       print "$first$indent$fmap{$a} Files:\n";
262       my $fixme = join (", ", keys %{${$changes{$k}}{$a}});
263       &niceline ($fixme);
264     }
265   }
266 }