#!/usr/bin/perl -w
# Purpose:
# summarize and re-format subversion's log output to plain text
#
# Written by Rocco Rutte <pdmef@cs.tu-berlin.de>
# for internal use with mutt-ng <http://mutt-ng.berlios.de/>.
#
# License: GPL
#
# Usage/Options:
# -t print only today's messages
# -s YYYY-MM-DD print only messages since (and including) date
# -i string use string for indentation
# -m number break lines at the latest at number columns
# -p string prefix for filenames in 'svn log -v' output;
# for things like 'A /mutt-ng/trunk/foo' set this
# to 'mutt-ng/', i.e. exclude the leading / from
# it but include everything up to the last char
# before (trunk|tags|branches)
# -h help
#
# Note: lines matching ``^([^:]+):?$'' will be ignored if the first
# submatch in brackets is a known author; i.e. the logs
#
# | Foo Bar
# | added l33t c0d3
#
# and
#
# | Foo Bar:
# | added l33t c0d3
#
# will be interpreted as only 'added l33t c0d3' if 'Foo Bar' is known as
# an author. This does not count when grouping messages for authors,
# those lines are just skipped. This is hard-coded, see below.
#
# Also, put
# | From: ...
# as the first line of log messages when change is only committed by
# someone with access but the original author is someone else. If
# there's no such author line, the author's name will be grabbed from
# the %committers table below.
use strict;
use POSIX;
use Getopt::Std;
# hard-coded configuration: this maps user to full names
my %committers = (
"ak1" => "Andreas Krennmair <ak\@synflood.at>",
"nion" => "Nico Golde <nion\@muttng.org>",
"pdmef" => "Rocco Rutte <pdmef\@cs.tu-berlin.de>",
"dkg1" => "Daniel K. Gebhart <dpkg1\@users.berlios.de>"
);
my %fmap = (
"filesA" => "Added",
"filesM" => "Modified",
"filesD" => "Deleted",
"filesR" => "Replaced"
);
# default config
my %options = ();
my $linemax = 70;
my $today = "";
my $since = "";
my $indent = " ";
my $pfx = "";
# some stuff we need
my $currev = 0;
my $lastrev = 0;
my @curlog = ();
my $curentry = "";
my $curauthor = "";
my $curcomm = "";
my $count = 0;
my %changes = ();
# nicely print log lines in itemized style with eye-candy indentation and
# somewhat smart line-breaking
sub niceline {
my ($text) = (@_);
my @lines = split (/\n/, $text);
for my $l (@lines) {
print "$indent$indent-";
my @words = split (/\ /, $l);
my $c = length ($indent);
for my $w (@words) {
if (length ($w) + $c > $linemax) {
print "\n$indent$indent $w";
$c = length ($indent);
} else {
print " $w";
}
$c += length ($w) + 1;
}
print "\n";
}
}
sub usage {
print <<EOF
This is: svnlog2changelog.pl
written by Rocco Rutte <pdmef\@cs.tu-berlin.de>
for use with mutt-ng <http://mutt-ng.berlios.de/>
Usage:
svnlog2changelog.pl -h
svn log -v | svnlog2changelog.pl [-t] [-i YYYY-MM-DD] [-m number] [-p string]
Options:
-t print only today's messages
-s YYYY-MM-DD print only messages since (and including) date
-i string use string for indentation
-m number break lines at the latest at number columns
-p string prefix for filenames in 'svn log -v' output;
for things like 'A /mutt-ng/trunk/foo' set this
to 'mutt-ng/', i.e. exclude the leading / from
it but include everything up to the last char
before (trunk|tags|branches)
-h help
Examples:
- print Subversion's log for today:
svn log -v -r "{`date "+%Y-%m-%d"`}:HEAD" | svnlog2changelog.pl -t [-i string]
- print Subversion's log since (and including) YYYY-MM-DD
svn log -v | svnlog2changelog.pl -s YYYY-MM-DD [-i string]
EOF
;
}
sub isknown {
my ($name) = (@_);
for my $k (keys %committers) {
if (substr ($committers{$k}, 0, length ($name)) eq $name) {
return (1);
}
}
return (0);
}
# get and process options
getopts ("tm:s:hi:p:", \%options);
if (defined $options{'t'}) {
$today = strftime ("%Y-%m-%d", localtime (time ()));
}
if (defined $options{'m'} and $options{'m'} =~ /^[0-9]{2,}$/) {
$linemax = $options{'m'};
}
if (defined $options{'s'} and $options{'s'} =~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) {
$since = $options{'s'};
}
if (defined $options{'h'}) {
&usage ();
exit (0);
}
if (defined $options{'i'}) {
$indent = $options{'i'};
}
if (defined $options{'p'}) {
$pfx = $options{'p'};
$pfx =~ s#/#\\/#g;
}
# parse log
while (<STDIN>) {
chomp;
if ($_ =~ /^r([0-9]+)/) {
$currev = $1;
@curlog = ();
$curauthor = "";
$count = 0;
my @items = split (/\ \|\ /, $_);
my @dateinfo = split (/\ /, $items[2]);
$curentry = $dateinfo[0];
$curauthor = $items[1];
$curcomm = $items[1];
# _keep_ latest rev. number for day
if (not defined ${$changes{$curentry}}{'rev'} or
${$changes{$curentry}}{'rev'} lt substr ($items[0], 1)) {
${$changes{$curentry}}{'rev'} = substr ($items[0], 1);
}
# _keep_ latest commit time for day
if (not defined ${$changes{$curentry}}{'time'}) {
${$changes{$curentry}}{'time'} = "$dateinfo[1] $dateinfo[2]";
}
next;
}
$count++;
if ($count > 0) {
# check log line: contains author?
if ($_ =~ /^From: (.*)$/) {
$curauthor = "$1 ($curcomm)";
next;
}
elsif (defined $committers{$curauthor}) {
$curauthor = "$committers{$curcomm} ($curcomm)";
}
# check log line: contains noise?
if (length ($_) == 0 or $_ =~ /^[-]+$/ or
$_ =~ /^([^:]+):?$/ and &isknown ($1) or
$_ eq "Changed paths:") {
next;
}
# check log line: contains list of changes/deleted/added files?
if ((length ($pfx) > 0 and $_ =~ /([AMD]) \/($pfx?.*)?$/) or
(length ($pfx) == 0 and $_ =~ /([AMD]) \/(.*)?$/)) {
my $what = $1;
my $target = "";
if (defined $2) {
$target = $2;
}
$target =~ s#$pfx##g;
${${$changes{$curentry}}{"files$what"}}{$target} = 1;
} else {
# here the line really contains the log message
# try to be smart and remove itemizations people make
my $clean = $_;
$clean =~ s/^[- \t*]*//;
if (length ($clean) > 0) {
${${$changes{$curentry}}{'log'}}{$curauthor} .= "$clean (r$currev)\n";
$lastrev = $currev;
}
}
}
}
my $first = "";
my $first2 = "";
for my $k (sort { $b cmp $a } (keys (%changes))) {
# ignore noise
if (not defined %{${$changes{$k}}{'log'}} or
(length ($since) > 0 && length ($today) == 0 && ($k lt $since)) or
(length ($today) > 0 && ($k ne $today))) {
next;
}
# print first line with date, time and latest revision for current day
print "$first$k ${$changes{$k}}{'time'} ";
$first = "\n";
$first2 = "";
print "Latest Revision: ${$changes{$k}}{'rev'}\n\n";
# per author: print his name and an itemized list of his log msgs.
# with smart line-breaking and indentation
for my $a (keys %{${$changes{$k}}{'log'}}) {
print "$first2$indent";
if (defined $committers{$a}) {
print $committers{$a};
} else {
print $a;
}
print ":\n";
&niceline (${${$changes{$k}}{'log'}}{$a});
$first2 = "\n";
}
for my $a (keys %fmap) {
if (defined %{${$changes{$k}}{$a}}) {
print "$first$indent$fmap{$a} Files:\n";
my $fixme = join (", ", keys %{${$changes{$k}}{$a}});
&niceline ($fixme);
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1