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