#!/usr/bin/perl -w # Purpose: # summarize and re-format subversion's log output to plain text # # Written by Rocco Rutte # for internal use with mutt-ng . # # 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 ", "nion" => "Nico Golde ", "pdmef" => "Rocco Rutte ", "dkg1" => "Daniel K. Gebhart " ); 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 < for use with mutt-ng 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 () { 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); } } }