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