Hack 9. Automate Checkin Code Reviews
Let Perl::Tidy be your first code reviewon every Subversion checkin!
In a multideveloper project, relying on developers to follow the coding standards without fail and to run
perltidy
against all of their code ("Enforce Local Style" [Hack #7]) before every checkin is
The Hack
Within your Subversion repository, copy the hooks/post-commit.tmpl file to hooks/post-commit unless you already have the file. Remove all code that runs other commands (again, unless you're already using it). Add a single line: perl /usr/local/bin/check_tidy_file.pl "$REPOS" "$REV"
Adjust the file
Finally, save the check_tidy_file.pl program to the path you used in the file. The program is:
#!/usr/bin/perl
use strict;
use warnings;
use Perl::Tidy;
use File::Temp;
use File::Spec::Functions;
my $svnlook = '/usr/bin/svnlook';
my $diff = '/usr/bin/diff -u';
# eat the arguments so as not to confuse Perl::Tidy
my ($repo, $rev) = @ARGV;
@ARGV = ( );
my @diffs;
for my $changed_file (get_changed_perl_files( $repo, $rev ))
{
my $source = get_revision( $repo, $rev, $changed_file );
Perl::Tidy::perltidy( source => \$source, destination => \(my $dest) );
push @diffs, get_diff( $changed_file, $source, $dest );
}
report_diffs( @diffs );
sub get_changed_perl_files
{
my ($repo, $rev) = @_;
my @files;
for my $change (\Q$svnlook changed $repo -r $rev\Q)
{
my ($status, $file) = split( /\s+/, $change );
next unless $file =~ /\.p[lm]\z/;
push @files, $file;
}
return @files;
}
sub get_revision
{
my ($repo, $rev, $file) = @_;
return scalar \Q$svnlook cat $repo -r $rev $file\Q;
}
sub get_diff
{
my $filename = shift;
return if $_[0] eq $_[1];
my $dir = File::Temp::tempdir( );
my @files = map { catdir( $dir, $filename . $_ ) } qw( .orig .tidy );
for my $file (@files)
{
open( my $out, '>', $file ) or die "Couldn't write $file: $!\n";
print $out shift;
close $out;
}
return scalar \Q$diff @files\Q;
}
sub report_diffs
{
for my $diff (@_)
{
warn "Error:\n$diff\n";
}
}
When Subversion finishes committing a checkin to the repository, it calls the hooks/post-commit script, which itself launches other programs, passing the repository path and the number of the just-committed revision. This program uses the svnlook command to find the modified files, skipping everything that's not a Perl program or module (files ending in .pl or .pm ). For each of these files, it grabs the entire contents from the just-completed revision and runs it through Perl::Tidy (the actual engine of the perltidy utility). If the resulting file is the same as the revision, everything is fine. Otherwise, it runs a diff utility to see the changes necessary to make the file tidy. From there, report_diffs( ) receives a list of these differences. Hacking the HackAs it is now, the program is only useful when run directly with the path to the repository and a revision number. It could instead write the differences to a file, automatically check in the revised versions in a new checkin, or e-mail the diffs to a list of programmers. To use a .perltidyrc file with the tidier program, add the perltidy => $rcfile_path arguments to the perltidy( ) call, where $rcfile_path contains the path to the .perltidyrc file to use. |