#!/usr/perl5/bin/perl -w
#
# Script for generating code review pages similar to those generated by
# ON's webrev tool
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License, Version 1.0 only
# (the "License"). You may not use this file except in compliance
# with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2006 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
# FIXMEs:
# - require the target dir to be empty
# - breaks if you have a new subdir that is not under svn control
# - should have a way to exclude some or all not-svn-controlled files
use strict;
use Fcntl;
use File::Basename;
#
# Usage: webrev /path/to/output/dir
#
# creates the html report of changed in the current svn workspace (current dir)
#
# FIXME: would be nice to turn these into command line options
# max number of chars in each line, above which lines are wrapped in
# side-by-side diffs
my $SDIFF_MAX_LINE=80;
# number of lines of context in sdiffs
my $SDIFF_CONTEXT=20;
# Valid @FOO@ tags in the HTML templates:
#
# @TITLE@ - page title
# @AUTHOR@ - real name of the current user according to the passwd entry
# @COPYRIGHT@ - copyright statement (not implemented)
# @UNAME@ - current user name
# @HOSTNAME@ - hostname of the current host as printed by /bin/hostname
# @DATE@ - current date string as printed by /bin/date
#
# HTML page header template for index.html
my $index_page_header =
'<HTML>\n' .
' <HEAD>\n' .
' <TITLE>@TITLE@</TITLE>\n' .
' <META NAME="author" CONTENT="@AUTHOR@">\n' .
' <META NAME="generator" CONTENT="webrev for svn">\n' .
# ' <META NAME="copyright" CONTENT="@COPYRIGHT@">\n' .
' </HEAD>\n' .
' <BODY BGCOLOR="#FFFFFF">\n' .
' <FONT FACE="arial,sans">\n' .
' <CENTER><FONT SIZE=+1><B>@TITLE@</B></FONT></CENTER><P>\n';
# HTML page footer template for index.html
my $index_page_footer =
' </FONT>\n' .
' <HR SIZE=1 NOSHADE>\n' .
' <FONT FACE="arial,sans" SIZE="-2">\n' .
' Webrev report generated by @UNAME@@@HOSTNAME@ on @DATE@.\n' .
' </FONT>\n' .
' </BODY>\n' .
'</HTML>\n';
# HTML page header template for the diff pages
my $file_page_header = $index_page_header;
# HTML page footer template for the diff pages
my $file_page_footer = $index_page_footer;
# Map status to file name
my %file_status;
# Descriptions of file status flags
my %status_desc = (' ', 'No change',
'A', 'New',
'C', '<FONT COLOR="#FF4444">Conflicted</FONT>',
'D', 'Deleted',
'G', 'Merged',
'I', 'Ignored',
'M', 'Modified',
'R', 'Replaced',
'?', '<FONT COLOR="#FF4444">Not under version control</FONT>',
'!', 'Missing');
# Map property change status to file name
# FIXME: currently these are not used, but should be.
my %file_prop;
# Descriptions of file status flags
my %prop_desc = (' ', 'No change',
'C', 'Conflicted',
'M', 'Modified');
my $scm;
my %file_hist;
my $overwrite = O_EXCL;
sub msg_fatal ($) {
my $msg = shift;
print STDERR "ERROR: $msg\n";
exit (1);
}
sub msg_error ($) {
my $msg = shift;
print STDERR "ERROR: $msg\n";
}
sub msg_warning ($) {
my $msg = shift;
print STDERR "WARNING: $msg\n";
}
# fill the %file_status map based on svn status / cvs status output
sub get_changed_files () {
if ($scm eq "svn") {
my @lines = `LC_ALL=C svn --non-interactive status` or msg_fatal ('"svn status" failed');
foreach my $line (@lines) {
chomp ($line);
if ($line =~ /^(.)(.)(.)(.)(.)(.) (.*)/) {
$file_status{$7} = $1;
$file_prop{$7} = $2;
$file_hist{$7} = $4;
} else {
msg_warning ("Cannot process svn status output: $line");
}
}
} elsif ($scm eq "cvs") {
# map CVS status names to svn status flags
my %status_map = ('Locally Added', 'A',
'Locally Modified', 'M',
'Needs Merge', 'M',
'Needs Checkout', '!',
'File had conflicts on merge', 'C');
my @lines = `LC_ALL=C cvs -z3 status 2>&1 | egrep '(^\\? |^cvs status: Examining |Status:)' | grep -v Up-to-date`
or msg_fatal ('"cvs status" failed');
my $dir = "";
foreach my $line (@lines) {
chomp ($line);
if ($line =~ /^cvs status: Examining (.*)/) {
$dir = "$1/";
} elsif ($line =~ /^File: no file (.+)\s+Status: (.*)/) {
if (defined ($status_map{$2})) {
$file_status{"$dir$1"} = $status_map{$2};
}
} elsif ($line =~ /^File: (.*\S)\s+Status: (.*)/) {
if (defined ($status_map{$2})) {
$file_status{"$dir$1"} = $status_map{$2};
}
} elsif ($line =~ /^? (.*)/) {
my $f0 = $1;
if (-d $f0) {
my @files = `find $f0 -type f -print | sort`;
foreach my $f (@files) {
chomp ($f);
$file_status{"$f"} = '?';
}
} else {
$file_status{"$f0"} = '?';
}
} else {
msg_warning ("Cannot process cvs status output: $line");
}
}
}
}
# fill in values in the HTML templates
my $uname;
my $author;
my $hostname;
sub eval_template ($;$) {
my $str = shift;
my $title = shift;
$title = "" unless defined $title;
if (not defined ($uname)) {
$uname = `logname`;
chomp ($uname);
}
if (not defined ($author)) {
$author = (getpwnam $uname)[6];
}
if (not defined ($hostname)) {
$hostname = `/bin/hostname`;
chomp ($hostname);
}
my $date = `/bin/date`;
chomp ($date);
$str =~ s/\@TITLE\@/$title/g;
$str =~ s/\@AUTHOR\@/$author/g;
$str =~ s/\@UNAME\@/$uname/g;
$str =~ s/\@HOSTNAME\@/$hostname/g;
$str =~ s/\@DATE\@/$date/g;
$str =~ s/\\n/\n/g;
return $str;
}
# replace html special chars with corresponding entities
sub html_encode ($) {
my $str = shift;
$str =~ s/&/&amp;/g;
$str =~ s/</&lt;/g;
$str =~ s/>/&gt;/g;
$str =~ s/\t/ /g;
return $str;
}
sub make_base_dir ($$) {
my $webrev_dir = shift;
my $file = shift;
system ("mkdir -p $webrev_dir/$file");
if ($? != 0) {
msg_error ("Failed to create directory $webrev_dir/$file");
return 0;
}
return 1;
}
sub gen_diff_new ($$) {
my $webrev_dir = shift;
my $file = shift;
my $basename = basename ($file);
system ("rm -f $webrev_dir/$file/new.$basename; cp $file $webrev_dir/$file/new.$basename");
if ($? != 0) {
msg_fatal ("failed to copy file $file to $webrev_dir/$file");
}
return "[<A HREF=\"$file/new.$basename\">new</A>] ";
}
sub gen_diff_old ($$) {
my $webrev_dir = shift;
my $file = shift;
my $basename = basename ($file);
system ("rm -f $webrev_dir/$file/old.$basename");
if ($? != 0) {
return undef;
}
if ($scm eq 'svn') {
system ("svn --non-interactive cat -r BASE $file > $webrev_dir/$file/old.$basename");
if ($? != 0) {
return undef;
}
} elsif ($scm eq 'cvs') {
my $rev=`LC_ALL=C cvs -z3 status $file | grep 'Working revision' | cut -f2 -d:`;
chomp ($rev);
my $CVSDIR = dirname ($file) . "/CVS";
my $CVSROOT = `cat $CVSDIR/Root`;
chomp ($CVSROOT);
my $REPO = `cat $CVSDIR/Repository`;
chomp ($REPO);
system ("mkdir -p $webrev_dir/tmp");
if ($? != 0) {
return undef;
}
system ("cd $webrev_dir/tmp && LC_ALL=C cvs -q -z3 -d $CVSROOT co -r$rev $REPO/$basename >/dev/null 2>&1 && mv $REPO/$basename $webrev_dir/$file/old.$basename && cd / && rm -rf $webrev_dir/tmp");
if ($? != 0) {
return undef;
}
}
return "[<A HREF=\"$file/old.$basename\">old</A>] ";
}
# create the unified diff page and return the [udiff] link
sub gen_diff_udiff ($$) {
my $webrev_dir = shift;
my $file = shift;
my $basename = basename ($file);
my @diff;
if ($scm eq 'svn') {
@diff = `svn --non-interactive diff $file`;
} elsif ($scm eq 'cvs') {
@diff = `cd $webrev_dir/$file; /usr/bin/diff -u old.$basename new.$basename`;
}
system ("rm -f $webrev_dir/$file/udiff.html");
if ($? != 0) {
return undef;
}
sysopen (DIFF, "$webrev_dir/$file/udiff.html", O_WRONLY | $overwrite | O_CREAT) or
msg_error ("failed to create file $webrev_dir/$file/udiff.html");
print DIFF eval_template ($file_page_header, "Unified diff of $file");
print DIFF "<TT><PRE>\n";
foreach my $line (@diff) {
chomp ($line);
$line = html_encode ($line);
if ($line =~ /^---/) {
print DIFF "<FONT COLOR=\"green\" SIZE=\"+1\"><b>$line</b></FONT>\n";
} elsif ($line =~ /^@@/) {
print DIFF "<FONT COLOR=\"red\" SIZE=\"+1\"><b>$line</b></FONT>\n";
} elsif ($line =~ /^\+\+\+/) {
print DIFF "<FONT COLOR=\"red\" SIZE=\"+1\"><b>$line</b></FONT>\n";
} elsif ($line =~ /^\+/) {
print DIFF "<FONT COLOR=\"blue\"><b>$line</b></FONT>\n";
} elsif ($line =~ /^\*\*\*/) {
print DIFF "<FONT COLOR=\"red\" SIZE=\"+1\"><b>$line</b></FONT>\n";
} elsif ($line =~ /^-/) {
print DIFF "<FONT COLOR=\"brown\">$line</FONT>\n";
} else {
print DIFF "$line\n";
}
}
print DIFF "</PRE></TT>\n";
print DIFF eval_template ($file_page_footer);
close DIFF;
return "[<A HREF=\"$file/udiff.html\">udiff</A>] ";
}
# create the context diff page and return the [cdiff] link
sub gen_diff_cdiff ($$) {
my $webrev_dir = shift;
my $file = shift;
my $basename = basename ($file);
if (! -f "$webrev_dir/$file/new.$basename") {
gen_diff_new ($webrev_dir, $file);
}
if (! -f "$webrev_dir/$file/old.$basename") {
gen_diff_old ($webrev_dir, $file);
}
my @diff = `cd $webrev_dir/$file; /usr/bin/diff -c old.$basename new.$basename`;
system ("rm -f $webrev_dir/$file/cdiff.html");
if ($? != 0) {
return undef;
}
sysopen (DIFF, "$webrev_dir/$file/cdiff.html", O_WRONLY | $overwrite | O_CREAT) or
msg_error ("failed to create file $webrev_dir/$file/cdiff.html");
print DIFF eval_template ($file_page_header, "Context diff of $file");
print DIFF "<TT><PRE>\n";
foreach my $line (@diff) {
chomp ($line);
$line = html_encode ($line);
if ($line =~ /^\+/) {
print DIFF "<FONT COLOR=\"blue\"><b>$line</b></FONT>\n";
} elsif ($line =~ /^---/) {
print DIFF "<FONT COLOR=\"green\" SIZE=\"+1\"><b>$line</b></FONT>\n";
} elsif ($line =~ /^\*\*\*/) {
print DIFF "<FONT COLOR=\"red\" SIZE=\"+1\"><b>$line</b></FONT>\n";
} elsif ($line =~ /^-/) {
print DIFF "<FONT COLOR=\"brown\">$line</FONT>\n";
} elsif ($line =~ /^!/) {
print DIFF "<FONT COLOR=\"blue\">$line</FONT>\n";
} else {
print DIFF "$line\n";
}
}
print DIFF "</PRE></TT>\n";
print DIFF eval_template ($file_page_footer);
close DIFF;
return "[<A HREF=\"$file/cdiff.html\">cdiff</A>] ";
}
# add a line to the array representing either the left of the right side
# of an sdiff. Lines are wrapped if longer than $SDIFF_MAX_LINE
# returns the number of lines actually added to the array
#
# $ref is a reference to the array
# $start is printed before the line
# $line is the line itself
# $end is printed to the end of the line
# $indent_len is the number of chars to indent wrapped lines (because of the
# line numbers
sub push_line ($$$$$) {
my $ref = shift;
my $start = shift;
my $line = shift;
my $end = shift;
my $indent_len = shift;
my $indent_str = sprintf ("%${indent_len}s ", "");
if (length ($line) <= $SDIFF_MAX_LINE) {
$line = html_encode ($line);
push (@$ref, "$start$line$end");
return 1;
}
my $l = 0;
my $lstart = substr ($line, 0, $SDIFF_MAX_LINE);
$line = substr ($line, $SDIFF_MAX_LINE);
$lstart = html_encode ($lstart);
my $the_line = "$start$lstart";
$l++;
while (length($line) > $SDIFF_MAX_LINE) {
$lstart = substr ($line, 0, $SDIFF_MAX_LINE);
$line = substr ($line, $SDIFF_MAX_LINE);
$lstart = html_encode ($lstart);
$the_line = "$the_line\n$indent_str$lstart";
$l++;
}
$line = html_encode ($line);
$the_line = "$the_line\n$indent_str$line$end";
push (@$ref, $the_line);
$l++;
return $l;
}
sub add_empty_line ($$) {
my $ref = shift;
my $len = shift;
my $line = "";
while ($len) {
$line = "$line\n";
$len--;
}
# push (@$ref, "<PRE STYLE=\"margin: 1pt\">$line</PRE>");
push (@$ref, $line);
}
sub extend_last_line ($$) {
my $ref = shift;
my $len = shift;
my $line = pop (@$ref);
while ($len) {
$line = "$line\n";
$len--;
}
push (@$ref, $line);
}
# generate the sdiff page and return the [sdiff] link
sub gen_diff_sdiff ($$) {
my $webrev_dir = shift;
my $file = shift;
my $basename = basename ($file);
# we're going to work from a unified diff between the old and the new files
# make sure they exist
if (! -f "$webrev_dir/$file/new.$basename") {
gen_diff_new ($webrev_dir, $file);
}
if (! -f "$webrev_dir/$file/old.$basename") {
gen_diff_old ($webrev_dir, $file);
}
my $total_lines = `cat $webrev_dir/$file/old.$basename | wc -l`;
chomp ($total_lines);
$total_lines++;
my $line_nr_len = length ("$total_lines");
my @diff = `cd $webrev_dir/$file; /usr/bin/diff -U $SDIFF_CONTEXT old.$basename new.$basename`;
# the 1st 2 lines are the file names
my $l = shift (@diff); chomp ($l);
my @left = ("<FONT COLOR=\"red\" SIZE=\"+1\"><b>$l</b></FONT>\n");
$l = shift (@diff); chomp ($l);
my @right = ("<FONT COLOR=\"green\" SIZE=\"+1\"><b>$l</b></FONT>\n");
# line numbers on the left and right side
my $left_line;
my $right_line;
my $line = shift (@diff);
chomp ($line);
while (@diff) {
# start of a block
if ($line =~ /^\@\@ -([0-9]+),[0-9]+ \+([0-9]+),[0-9]+ \@\@/) {
$left_line = sprintf ("%${line_nr_len}s",$1);
$right_line = sprintf ("%${line_nr_len}s",$2);
push (@left, "<HR SIZE=1 NOSHADE>");
push (@right, "<HR SIZE=1 NOSHADE>");
$line = shift (@diff);
chomp ($line);
next;
}
# new lines added to the file: print them on the right side in blue
if ($line =~ /^\+(.*)/) {
my $n = push_line (\@right, "<FONT COLOR=\"blue\">$right_line ", $1, "</FONT>", $line_nr_len);
# print an equal number of blank lines on the left side
add_empty_line (\@left, $n);
$right_line = sprintf ("%${line_nr_len}s", ++$right_line);
$line = shift (@diff);
chomp ($line);
next;
}
# lines deleted
my @dellines;
while ($line =~ /^\-(.*)/) {
push (@dellines, $1);
$line = shift (@diff);
chomp ($line);
}
# if deleted lines are immediately followed by added lines,
# then some of the deleted lines are actually changed lines.
# print them in blue on both sides
while ($line =~ /^\+(.*)/) {
my $line1 = $1;
if (@dellines) {
my $line2 = shift (@dellines);
my $n1 = push_line (\@left, "<FONT COLOR=\"blue\">$left_line ", $line2, "</FONT>", $line_nr_len);
$n1--;
$left_line = sprintf ("%${line_nr_len}s", ++$left_line);
my $n2 = push_line (\@right, "<FONT COLOR=\"blue\">$right_line ", $line1, "</FONT>", $line_nr_len);
$n2--;
$right_line = sprintf ("%${line_nr_len}s", ++$right_line);
if ($n2 > $n1) {
$n2 -= $n1;
$n1 = 0;
} else {
$n1 -= $n2;
$n2 = 0;
}
extend_last_line (\@left, $n2) if $n2;
extend_last_line (\@right, $n1) if $n1;
} else {
# no deleted lines: print the new lines on the right side
my $n = push_line (\@right, "<FONT COLOR=\"blue\">$right_line ", $line1, "</FONT>", $line_nr_len);
add_empty_line (\@left, $n);
$right_line = sprintf ("%${line_nr_len}s", ++$right_line);
}
$line = shift (@diff);
chomp ($line);
}
# deleted lines remain, print them in brown on the left side
while (@dellines) {
my $line2 = shift (@dellines);
my $n = push_line (\@left, "<FONT COLOR=\"brown\">$left_line ", $line2, "</FONT>", $line_nr_len);
$left_line = sprintf ("%${line_nr_len}s", ++$left_line);
add_empty_line (\@right, $n);
}
# unchanged (context) lines
if ($line =~ /^[^+-]/) {
push_line (\@right, "$right_line ", $line, "", $line_nr_len);
push_line (\@left, "$left_line ", $line, "", $line_nr_len);
$left_line = sprintf ("%${line_nr_len}s", ++$left_line);
$right_line = sprintf ("%${line_nr_len}s", ++$right_line);
} else {
next;
}
# fetch the next line if exists
if (@diff) {
$line = shift (@diff);
chomp ($line);
}
}
# write out the report
system ("rm -f $webrev_dir/$file/sdiff.html");
if ($? != 0) {
return undef;
}
sysopen (DIFF, "$webrev_dir/$file/sdiff.html", O_WRONLY | $overwrite | O_CREAT) or
msg_error ("failed to create file $webrev_dir/$file/sdiff.html");
print DIFF eval_template ($file_page_header, "Side by side diff of $file");
print DIFF "<TABLE COLS=2 BORDER=1 CELLSPACING=0>\n";
print DIFF "<TR><TD VALIGN=top>\n";
print DIFF "<TABLE WIDTH=100% COLS=1 BORDER=0 CELLSPACING=0 CELLPADDING=0>\n";
my $col = 1;
foreach my $line (@left) {
if ($col) {
print DIFF "<TR><TD BGCOLOR=#DDDDDD><PRE STYLE=\"margin: 1pt;\">$line\n</PRE></TD></TR>\n";
} else {
print DIFF "<TR><TD><PRE STYLE=\"margin: 1pt;\">$line\n</PRE></TD></TR>\n";
}
$col = 1 - $col;
}
print DIFF "</TABLE></TD>\n";
print DIFF "<TD VALIGN=top>\n";
print DIFF "<TABLE WIDTH=100% COLS=1 BORDER=0 CELLSPACING=0 CELLPADDING=0>\n";
$col = 1;
foreach my $line (@right) {
if ($col) {
print DIFF "<TR><TD BGCOLOR=#DDDDDD><PRE STYLE=\"margin: 1pt;\">$line\n</PRE></TD></TR>\n";
} else {
print DIFF "<TR><TD><PRE STYLE=\"margin: 1pt;\">$line\n</PRE></TD></TR>\n";
}
$col = 1 - $col;
}
print DIFF "</TABLE></TD></TR>\n";
print DIFF "</TABLE>\n";
print DIFF eval_template ($file_page_footer);
close DIFF;
return "[<A HREF=\"$file/sdiff.html\">sdiff</A>] ";
}
sub gen_diff_patch ($$) {
my $webrev_dir = shift;
my $file = shift;
my $basename = basename ($file);
if ($scm eq 'svn') {
system ("rm -f $webrev_dir/$file/$basename.diff; svn --non-interactive diff $file > $webrev_dir/$file/$basename.diff");
} elsif ($scm eq 'cvs') {
system ("rm -f $webrev_dir/$file/$basename.diff; cvs -q diff -up $file > $webrev_dir/$file/$basename.diff");
}
if ($? != 0) {
return undef;
}
return "[<A HREF=\"$file/$basename.diff\">patch</A>] ";
}
# map ChangeLog entries to files
my %changelog_entry;
# find updated ChangeLog files and extract the entries for each file
sub read_changelog_entries () {
foreach my $file (sort keys %file_status) {
if ($file eq "ChangeLog" or $file =~ /\/ChangeLog$/) {
my @chlog_lines;
if ($scm eq 'svn') {
@chlog_lines = `svn --non-interactive diff $file | grep "^\+"`;
} elsif ($scm eq 'cvs') {
@chlog_lines = `cvs -q diff -u $file | grep "^\+"`;
}
my $dirname = dirname ($file);
if ($dirname eq ".") {
$dirname = "";
} else {
$dirname = "$dirname/";
}
while (@chlog_lines) {
my $line = shift (@chlog_lines);
chomp ($line);
# * file: foo bar
if ($line =~ /^\+(\s+\* \S+.*)/) {
my $entry = $1;
my $ecat = $1;
$line = shift (@chlog_lines);
chomp ($line);
# read all lines until the next
# * file: foo bar
# entry
while (defined ($line) and $line =~ /^\+(\s+[^*].*)/) {
$entry = "$entry\n$1";
$ecat = "$ecat$1";
$line = shift (@chlog_lines);
chomp ($line);
}
$ecat =~ s/^\s*\*\s*//;
# assign the same entry to each file listed with
# commas before the first :
while ($ecat =~ /^([^:,]+)[:,]\s*(.*)/) {
$changelog_entry{"$dirname$1"} = "$entry\n";
$ecat = $2;
}
unshift (@chlog_lines, $line);
}
}
}
}
}
################ MAIN ###################################################
sub main ($) {
my $webrev_dir = shift;
system ("mkdir -p $webrev_dir");
if ($? != 0) {
msg_fatal ("Webrev directory could not be created");
}
sysopen (INDEX, "$webrev_dir/index.html", O_WRONLY | $overwrite | O_CREAT) or
msg_fatal ("failed to create file $webrev_dir/index.html");
print "Finding changed files...\n";
get_changed_files ();
print "Reading ChangeLogs...\n";
read_changelog_entries ();
my $title = `pwd`;
chomp ($title);
$title = basename ($title . " Webrev");
print INDEX eval_template ($index_page_header, $title);
my $total_new = 0;
my $total_deleted = 0;
my $total_changed = 0;
my $total_unchanged = 0;
my $total_non_svn = 0;
print "Processing files...\n";
foreach my $file (sort keys %file_status) {
print " $file\n";
print INDEX "<P><B>$file</B> ($status_desc{$file_status{$file}})<BR>\n";
print INDEX "&nbsp; &nbsp; &nbsp; &nbsp;";
make_base_dir ($webrev_dir, $file) or
print INDEX "<P>\n", next;
if ($file_status{$file} eq 'A') {
print INDEX gen_diff_new ($webrev_dir, $file);
my $lines = `cat $file | wc -l`;
chomp ($lines);
print INDEX "<BR>&nbsp; &nbsp; &nbsp; &nbsp;";
print INDEX "$lines new line(s)\n";
$total_new += $lines;
} elsif ($file_status{$file} eq 'D') {
print INDEX gen_diff_old ($webrev_dir, $file);
print INDEX "<BR>&nbsp; &nbsp; &nbsp; &nbsp;";
my $basename = basename ($file);
my $lines = `cat $webrev_dir/$file/old.$basename | wc -l`;
chomp ($lines);
print INDEX "$lines deleted line(s)\n";
$total_deleted += $lines;
} elsif ($file_status{$file} eq '?') {
next if -d $file;
print INDEX gen_diff_new ($webrev_dir, $file);
print INDEX "<BR>&nbsp; &nbsp; &nbsp; &nbsp;";
my $lines = `cat $file | wc -l`;
chomp ($lines);
print INDEX "$lines new line(s) not under svn control\n";
$total_non_svn += $lines;
} elsif ($file_status{$file} eq 'M') {
my $label;
$label = gen_diff_old ($webrev_dir, $file);
next if not defined $label;
print INDEX $label;
$label = gen_diff_new ($webrev_dir, $file);
next if not defined $label;
print INDEX $label;
$label = gen_diff_udiff ($webrev_dir, $file);
next if not defined $label;
print INDEX $label;
$label = gen_diff_cdiff ($webrev_dir, $file);
next if not defined $label;
print INDEX $label;
$label = gen_diff_sdiff ($webrev_dir, $file);
next if not defined $label;
print INDEX $label;
$label = gen_diff_patch ($webrev_dir, $file);
my $basename = basename ($file);
my $changed_lines = `diff -c $webrev_dir/$file/old.$basename $webrev_dir/$file/new.$basename | grep '^! ' | wc -l`;
chomp ($changed_lines);
my $deleted_lines = `diff -c $webrev_dir/$file/old.$basename $webrev_dir/$file/new.$basename | grep '^- ' | wc -l`;
chomp ($deleted_lines);
my $new_lines = `diff -c $webrev_dir/$file/old.$basename $webrev_dir/$file/new.$basename | grep '^+ ' | wc -l`;
chomp ($new_lines);
my $total_lines = `cat $webrev_dir/$file/old.$basename | wc -l`;
chomp ($total_lines);
my $unchanged_lines = $total_lines - $deleted_lines - $changed_lines;
print INDEX "<BR>&nbsp; &nbsp; &nbsp; &nbsp;";
print INDEX "$new_lines line(s) new / $deleted_lines line(s) deleted / $changed_lines line(s) updated / $unchanged_lines line(s) unchanged\n";
$total_new += $new_lines;
$total_deleted += $deleted_lines;
$total_changed += $changed_lines;
$total_unchanged += $unchanged_lines;
}
if (defined ($changelog_entry{$file})) {
print INDEX "<PRE>\n";
print INDEX $changelog_entry{$file};
print INDEX "</PRE>\n";
} elsif ($file ne "ChangeLog" and not $file =~ /\/ChangeLog$/) {
print INDEX "<BR>&nbsp; &nbsp; &nbsp; &nbsp;";
print INDEX "<FONT COLOR=red>No ChangeLog entry found</FONT><BR>\n";
}
}
print INDEX "<P><B>Total</B>: $total_new line(s) new / $total_deleted line(s) deleted / $total_changed line(s) updated / $total_unchanged line(s) unchanged<BR>\n";
if ($total_non_svn) {
print INDEX "An additional $total_non_svn line(s) not under source control<P>\n";
}
print INDEX eval_template ($index_page_footer);
close INDEX;
print "Done.\n"
}
if ((@ARGV != 1) or ($ARGV[0] eq "-h") or ($ARGV[0] eq "--help")) {
print "Usage: webrev /path/to/webrev/dir\n\n";
print "Run this script inside a Subversion or CVS controlled directory\n";
print "to create an html code review document.\n";
print "The argument is a directory where the output is written.\n";
print "The svn or cvs command must be in your PATH and should not\n";
print "require interaction (e.g. use ssh-add first)\n";
exit(1);
}
if (-d '.svn') {
$scm = 'svn';
} elsif (-d 'CVS') {
$scm = 'cvs';
} else {
msg_fatal ("No CVS or Subversion control files found in this directory");
}
if (-d "$ARGV[0]/.svn" or -d "$ARGV[0]/CVS") {
msg_fatal ("The target directory should not be svn or CVS controlled");
}
if (-f "$ARGV[0]/index.html") {
print "Overwrite files in $ARGV[0] (y/n)? ";
my $ans = lc(<STDIN>);
chomp ($ans);
if ($ans eq 'y' or $ans eq 'yes') {
$overwrite = O_CREAT;
}
}
main ($ARGV[0]);