xref: /illumos-gate/usr/src/tools/scripts/webrev.sh (revision dd4eeefd)
1#!/usr/bin/ksh -p
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22#
23# ident	"%Z%%M%	%I%	%E% SMI"
24#
25# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
26# Use is subject to license terms.
27#
28# This script takes a file list and a workspace and builds a set of html files
29# suitable for doing a code review of source changes via a web page.
30# Documentation is available via the manual page, webrev.1, or just
31# type 'webrev -h'.
32#
33# Acknowledgements to contributors to webrev are listed in the webrev(1)
34# man page.
35#
36
37#
38# The following variable is set to SCCS delta date 20YY/MM/DD.
39# Note this will have to be changed in 2100 or when SCCS has support for
40# 4 digit years; whichever is the sooner!
41#
42WEBREV_UPDATED=20%E%
43
44REMOVED_COLOR=brown
45CHANGED_COLOR=blue
46NEW_COLOR=blue
47
48HTML='<?xml version="1.0"?>
49<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
50    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
51<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
52
53FRAMEHTML='<?xml version="1.0"?>
54<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
55    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
56<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
57
58STDHEAD='<meta http-equiv="cache-control" content="no-cache" />
59<meta http-equiv="Pragma" content="no-cache" />
60<meta http-equiv="Expires" content="-1" />
61<!--
62   Note to customizers: the body of the webrev is IDed as SUNWwebrev
63   to allow easy overriding by users of webrev via the userContent.css
64   mechanism available in some browsers.
65
66   For example, to have all "removed" information be red instead of
67   brown, set a rule in your userContent.css file like:
68
69       body#SUNWwebrev span.removed { color: red ! important; }
70-->
71<style type="text/css" media="screen">
72body {
73    background-color: #eeeeee;
74}
75hr {
76    border: none 0;
77    border-top: 1px solid #aaa;
78    height: 1px;
79}
80div.summary {
81    font-size: .8em;
82    border-bottom: 1px solid #aaa;
83    padding-left: 1em;
84    padding-right: 1em;
85}
86div.summary h2 {
87    margin-bottom: 0.3em;
88}
89div.summary table th {
90    text-align: right;
91    vertical-align: top;
92    white-space: nowrap;
93}
94span.lineschanged {
95    font-size: 0.7em;
96}
97span.oldmarker {
98    color: red;
99    font-size: large;
100    font-weight: bold;
101}
102span.newmarker {
103    color: green;
104    font-size: large;
105    font-weight: bold;
106}
107span.removed {
108    color: brown;
109}
110span.changed {
111    color: blue;
112}
113span.new {
114    color: blue;
115    font-weight: bold;
116}
117a.print { font-size: x-small; }
118a:hover { background-color: #ffcc99; }
119</style>
120
121<style type="text/css" media="print">
122pre { font-size: 0.8em; font-family: courier, monospace; }
123span.removed { color: #444; font-style: italic }
124span.changed { font-weight: bold; }
125span.new { font-weight: bold; }
126span.newmarker { font-size: 1.2em; font-weight: bold; }
127span.oldmarker { font-size: 1.2em; font-weight: bold; }
128a.print {display: none}
129hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
130</style>
131'
132
133#
134# UDiffs need a slightly different CSS rule for 'new' items (we don't
135# want them to be bolded as we do in cdiffs or sdiffs).
136#
137UDIFFCSS='
138<style type="text/css" media="screen">
139span.new {
140    color: blue;
141    font-weight: normal;
142}
143</style>
144'
145
146#
147# input_cmd | html_quote | output_cmd
148# or
149# html_quote filename | output_cmd
150#
151# Make a piece of source code safe for display in an HTML <pre> block.
152#
153html_quote()
154{
155	sed -e "s/&/\&amp;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g" "$@" | expand
156}
157
158#
159# input_cmd | bug2url | output_cmd
160#
161# Scan for bugids and insert <a> links to the relevent bug database.
162#
163bug2url()
164{
165	sed -e 's|[0-9]\{5,\}|<a href=\"'$BUGURL'&\">&</a>|g'
166}
167
168#
169# input_cmd | sac2url | output_cmd
170#
171# Scan for ARC cases and insert <a> links to the relevent SAC database.
172# This is slightly complicated because inside the SWAN, SAC cases are
173# grouped by ARC: PSARC/2006/123.  But on OpenSolaris.org, they are
174# referenced as 2006/123 (without labelling the ARC).
175#
176sac2url()
177{
178	if [[ -z $Oflag ]]; then
179	    sed -e 's|\([A-Z]\{1,2\}ARC\)[ /]\([0-9]\{4\}\)/\([0-9]\{3\}\)|<a href=\"'$SACURL'/\1/\2/\3\">\1 \2/\3</a>|g'
180	else
181	    sed -e 's|\([A-Z]\{1,2\}ARC\)[ /]\([0-9]\{4\}\)/\([0-9]\{3\}\)|<a href=\"'$SACURL'/\2/\3\">\1 \2/\3</a>|g'
182	fi
183}
184
185#
186# strip_unchanged <infile> | output_cmd
187#
188# Removes chunks of sdiff documents that have not changed. This makes it
189# easier for a code reviewer to find the bits that have changed.
190#
191# Deleted lines of text are replaced by a horizontal rule. Some
192# identical lines are retained before and after the changed lines to
193# provide some context.  The number of these lines is controlled by the
194# variable C in the nawk script below.
195#
196# The script detects changed lines as any line that has a "<span class="
197# string embedded (unchanged lines have no particular class and are not
198# part of a <span>).  Blank lines (without a sequence number) are also
199# detected since they flag lines that have been inserted or deleted.
200#
201strip_unchanged()
202{
203	nawk '
204	BEGIN	{ C = c = 20 }
205	NF == 0 || /span class=/ {
206		if (c > C) {
207			c -= C
208			inx = 0
209			if (c > C) {
210				print "\n</pre><hr /><pre>"
211				inx = c % C
212				c = C
213			}
214
215			for (i = 0; i < c; i++)
216				print ln[(inx + i) % C]
217		}
218		c = 0;
219		print
220		next
221	}
222	{	if (c >= C) {
223			ln[c % C] = $0
224			c++;
225			next;
226		}
227		c++;
228		print
229	}
230	END	{ if (c > (C * 2)) print "\n</pre><hr />" }
231
232	' $1
233}
234
235#
236# sdiff_to_html
237#
238# This function takes two files as arguments, obtains their diff, and
239# processes the diff output to present the files as an HTML document with
240# the files displayed side-by-side, differences shown in color.  It also
241# takes a delta comment, rendered as an HTML snippet, as the third
242# argument.  The function takes two files as arguments, then the name of
243# file, the path, and the comment.  The HTML will be delivered on stdout,
244# e.g.
245#
246#   $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
247#         new/usr/src/tools/scripts/webrev.sh \
248#         webrev.sh usr/src/tools/scripts \
249#         '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
250#          1234567</a> my bugid' > <file>.html
251#
252# framed_sdiff() is then called which creates $2.frames.html
253# in the webrev tree.
254#
255# FYI: This function is rather unusual in its use of awk.  The initial
256# diff run produces conventional diff output showing changed lines mixed
257# with editing codes.  The changed lines are ignored - we're interested in
258# the editing codes, e.g.
259#
260#      8c8
261#      57a61
262#      63c66,76
263#      68,93d80
264#      106d90
265#      108,110d91
266#
267#  These editing codes are parsed by the awk script and used to generate
268#  another awk script that generates HTML, e.g the above lines would turn
269#  into something like this:
270#
271#      BEGIN { printf "<pre>\n" }
272#      function sp(n) {for (i=0;i<n;i++)printf "\n"}
273#      function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
274#      NR==8           {wl("#7A7ADD");next}
275#      NR==54          {wl("#7A7ADD");sp(3);next}
276#      NR==56          {wl("#7A7ADD");next}
277#      NR==57          {wl("black");printf "\n"; next}
278#        :               :
279#
280#  This script is then run on the original source file to generate the
281#  HTML that corresponds to the source file.
282#
283#  The two HTML files are then combined into a single piece of HTML that
284#  uses an HTML table construct to present the files side by side.  You'll
285#  notice that the changes are color-coded:
286#
287#   black     - unchanged lines
288#   blue      - changed lines
289#   bold blue - new lines
290#   brown     - deleted lines
291#
292#  Blank lines are inserted in each file to keep unchanged lines in sync
293#  (side-by-side).  This format is familiar to users of sdiff(1) or
294#  Teamware's filemerge tool.
295#
296sdiff_to_html()
297{
298	diff -b $1 $2 > /tmp/$$.diffs
299
300	TNAME=$3
301	TPATH=$4
302	COMMENT=$5
303
304	#
305	#  Now we have the diffs, generate the HTML for the old file.
306	#
307	nawk '
308	BEGIN	{
309		printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
310		printf "function removed() "
311		printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
312		printf "function changed() "
313		printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
314		printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
315}
316	/^</	{next}
317	/^>/	{next}
318	/^---/	{next}
319
320	{
321	split($1, a, /[cad]/) ;
322	if (index($1, "a")) {
323		if (a[1] == 0) {
324			n = split(a[2], r, /,/);
325			if (n == 1)
326				printf "BEGIN\t\t{sp(1)}\n"
327			else
328				printf "BEGIN\t\t{sp(%d)}\n",\
329				(r[2] - r[1]) + 1
330			next
331		}
332
333		printf "NR==%s\t\t{", a[1]
334		n = split(a[2], r, /,/);
335		s = r[1];
336		if (n == 1)
337			printf "bl();printf \"\\n\"; next}\n"
338		else {
339			n = r[2] - r[1]
340			printf "bl();sp(%d);next}\n",\
341			(r[2] - r[1]) + 1
342		}
343		next
344	}
345	if (index($1, "d")) {
346		n = split(a[1], r, /,/);
347		n1 = r[1]
348		n2 = r[2]
349		if (n == 1)
350			printf "NR==%s\t\t{removed(); next}\n" , n1
351		else
352			printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
353		next
354	}
355	if (index($1, "c")) {
356		n = split(a[1], r, /,/);
357		n1 = r[1]
358		n2 = r[2]
359		final = n2
360		d1 = 0
361		if (n == 1)
362			printf "NR==%s\t\t{changed();" , n1
363		else {
364			d1 = n2 - n1
365			printf "NR==%s,NR==%s\t{changed();" , n1, n2
366		}
367		m = split(a[2], r, /,/);
368		n1 = r[1]
369		n2 = r[2]
370		if (m > 1) {
371			d2  = n2 - n1
372			if (d2 > d1) {
373				if (n > 1) printf "if (NR==%d)", final
374				printf "sp(%d);", d2 - d1
375			}
376		}
377		printf "next}\n" ;
378
379		next
380	}
381	}
382
383	END	{ printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
384	' /tmp/$$.diffs > /tmp/$$.file1
385
386	#
387	#  Now generate the HTML for the new file
388	#
389	nawk '
390	BEGIN	{
391		printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
392		printf "function new() "
393		printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
394		printf "function changed() "
395		printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
396		printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
397	}
398
399	/^</	{next}
400	/^>/	{next}
401	/^---/	{next}
402
403	{
404	split($1, a, /[cad]/) ;
405	if (index($1, "d")) {
406		if (a[2] == 0) {
407			n = split(a[1], r, /,/);
408			if (n == 1)
409				printf "BEGIN\t\t{sp(1)}\n"
410			else
411				printf "BEGIN\t\t{sp(%d)}\n",\
412				(r[2] - r[1]) + 1
413			next
414		}
415
416		printf "NR==%s\t\t{", a[2]
417		n = split(a[1], r, /,/);
418		s = r[1];
419		if (n == 1)
420			printf "bl();printf \"\\n\"; next}\n"
421		else {
422			n = r[2] - r[1]
423			printf "bl();sp(%d);next}\n",\
424			(r[2] - r[1]) + 1
425		}
426		next
427	}
428	if (index($1, "a")) {
429		n = split(a[2], r, /,/);
430		n1 = r[1]
431		n2 = r[2]
432		if (n == 1)
433			printf "NR==%s\t\t{new() ; next}\n" , n1
434		else
435			printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
436		next
437	}
438	if (index($1, "c")) {
439		n = split(a[2], r, /,/);
440		n1 = r[1]
441		n2 = r[2]
442		final = n2
443		d2 = 0;
444		if (n == 1) {
445			final = n1
446			printf "NR==%s\t\t{changed();" , n1
447		} else {
448			d2 = n2 - n1
449			printf "NR==%s,NR==%s\t{changed();" , n1, n2
450		}
451		m = split(a[1], r, /,/);
452		n1 = r[1]
453		n2 = r[2]
454		if (m > 1) {
455			d1  = n2 - n1
456			if (d1 > d2) {
457				if (n > 1) printf "if (NR==%d)", final
458				printf "sp(%d);", d1 - d2
459			}
460		}
461		printf "next}\n" ;
462		next
463	}
464	}
465	END	{ printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
466	' /tmp/$$.diffs > /tmp/$$.file2
467
468	#
469	# Post-process the HTML files by running them back through nawk
470	#
471	html_quote < $1 | nawk -f /tmp/$$.file1 > /tmp/$$.file1.html
472
473	html_quote < $2 | nawk -f /tmp/$$.file2 > /tmp/$$.file2.html
474
475	#
476	# Now combine into a valid HTML file and side-by-side into a table
477	#
478	print "$HTML<head>$STDHEAD"
479	print "<title>$WNAME Sdiff $TPATH </title>"
480	print "</head><body id=\"SUNWwebrev\">"
481        print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
482	print "<pre>$COMMENT</pre>\n"
483	print "<table><tr valign=\"top\">"
484	print "<td><pre>"
485
486	strip_unchanged /tmp/$$.file1.html
487
488	print "</pre></td><td><pre>"
489
490	strip_unchanged /tmp/$$.file2.html
491
492	print "</pre></td>"
493	print "</tr></table>"
494	print "</body></html>"
495
496	framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
497	    "$COMMENT"
498}
499
500
501#
502# framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
503#
504# Expects lefthand and righthand side html files created by sdiff_to_html.
505# We use insert_anchors() to augment those with HTML navigation anchors,
506# and then emit the main frame.  Content is placed into:
507#
508#    $WDIR/DIR/$TNAME.lhs.html
509#    $WDIR/DIR/$TNAME.rhs.html
510#    $WDIR/DIR/$TNAME.frames.html
511#
512# NOTE: We rely on standard usage of $WDIR and $DIR.
513#
514function framed_sdiff
515{
516	typeset TNAME=$1
517	typeset TPATH=$2
518	typeset lhsfile=$3
519	typeset rhsfile=$4
520	typeset comments=$5
521	typeset RTOP
522
523	# Enable html files to access WDIR via a relative path.
524	RTOP=$(relative_dir $TPATH $WDIR)
525
526	# Make the rhs/lhs files and output the frameset file.
527	print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
528
529	cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
530	    <script type="text/javascript" src="$RTOP/ancnav.js" />
531	    </head>
532	    <body id="SUNWwebrev" onkeypress="keypress(event);">
533	    <a name="0" />
534	    <pre>$comments</pre><hr />
535	EOF
536
537	cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
538
539	insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
540	insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
541
542	close='</body></html>'
543
544	print $close >> $WDIR/$DIR/$TNAME.lhs.html
545	print $close >> $WDIR/$DIR/$TNAME.rhs.html
546
547	print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
548	print "<title>$WNAME Framed-Sdiff " \
549	    "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
550	cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
551	  <frameset rows="*,60">
552	    <frameset cols="50%,50%">
553	      <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs" />
554	      <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs" />
555	    </frameset>
556	  <frame src="$RTOP/ancnav.html" scrolling="no" marginwidth="0"
557	   marginheight="0" name="nav" />
558	  <noframes>
559            <body id="SUNWwebrev">
560	      Alas 'frames' webrev requires that your browser supports frames
561	      and has the feature enabled.
562            </body>
563	  </noframes>
564	  </frameset>
565	</html>
566	EOF
567}
568
569
570#
571# fix_postscript
572#
573# Merge codereview output files to a single conforming postscript file, by:
574# 	- removing all extraneous headers/trailers
575#	- making the page numbers right
576#	- removing pages devoid of contents which confuse some
577#	  postscript readers.
578#
579# From Casper.
580#
581function fix_postscript
582{
583	infile=$1
584
585	cat > /tmp/$$.crmerge.pl << \EOF
586
587	print scalar(<>);		# %!PS-Adobe---
588	print "%%Orientation: Landscape\n";
589
590	$pno = 0;
591	$doprint = 1;
592
593	$page = "";
594
595	while (<>) {
596		next if (/^%%Pages:\s*\d+/);
597
598		if (/^%%Page:/) {
599			if ($pno == 0 || $page =~ /\)S/) {
600				# Header or single page containing text
601				print "%%Page: ? $pno\n" if ($pno > 0);
602				print $page;
603				$pno++;
604			} else {
605				# Empty page, skip it.
606			}
607			$page = "";
608			$doprint = 1;
609			next;
610		}
611
612		# Skip from %%Trailer of one document to Endprolog
613		# %%Page of the next
614		$doprint = 0 if (/^%%Trailer/);
615		$page .= $_ if ($doprint);
616	}
617
618	if ($page =~ /\)S/) {
619		print "%%Page: ? $pno\n";
620		print $page;
621	} else {
622		$pno--;
623	}
624	print "%%Trailer\n%%Pages: $pno\n";
625EOF
626
627	$PERL /tmp/$$.crmerge.pl < $infile
628}
629
630
631#
632# input_cmd | insert_anchors | output_cmd
633#
634# Flag blocks of difference with sequentially numbered invisible
635# anchors.  These are used to drive the frames version of the
636# sdiffs output.
637#
638# NOTE: Anchor zero flags the top of the file irrespective of changes,
639# an additional anchor is also appended to flag the bottom.
640#
641# The script detects changed lines as any line that has a "<span
642# class=" string embedded (unchanged lines have no class set and are
643# not part of a <span>.  Blank lines (without a sequence number)
644# are also detected since they flag lines that have been inserted or
645# deleted.
646#
647function insert_anchors
648{
649	nawk '
650	function ia() {
651		# This should be able to be a singleton <a /> but that
652		# seems to trigger a bug in firefox a:hover rule processing
653		printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
654	}
655
656	BEGIN {
657		anc=1;
658		inblock=1;
659		printf "<pre>\n";
660	}
661	NF == 0 || /^<span class=/ {
662		if (inblock == 0) {
663			ia();
664			inblock=1;
665		}
666		print;
667		next;
668	}
669	{
670		inblock=0;
671		print;
672	}
673	END {
674		ia();
675
676		printf "<b style=\"font-size: large; color: red\">";
677		printf "--- EOF ---</b>"
678        	for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
679		printf "</pre>"
680		printf "<form name=\"eof\">";
681		printf "<input name=\"value\" value=\"%d\" type=\"hidden\" />",
682		    anc - 1;
683		printf "</form>";
684	}
685	' $1
686}
687
688
689#
690# relative_dir
691#
692# Print a relative return path from $1 to $2.  For example if
693# $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
694# this function would print "../../../../".
695#
696# In the event that $1 is not in $2 a warning is printed to stderr,
697# and $2 is returned-- the result of this is that the resulting webrev
698# is not relocatable.
699#
700function relative_dir
701{
702	typeset cur="${1##$2?(/)}"
703	typeset ret=""
704	if [[ $2 == $cur ]]; then   # Should never happen.
705		# Should never happen.
706		print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
707		print -u2 "to \"$2\".  Check input paths.  Framed webrev "
708		print -u2 "will not be relocatable!"
709		print $2
710		return
711	fi
712
713	while [[ -n ${cur} ]];
714	do
715		cur=${cur%%*(/)*([!/])}
716		if [[ -z $ret ]]; then
717			ret=".."
718		else
719			ret="../$ret"
720		fi
721	done
722	print $ret
723}
724
725
726#
727# frame_nav_js
728#
729# Emit javascript for frame navigation
730#
731function frame_nav_js
732{
733cat << \EOF
734var myInt;
735var scrolling=0;
736var sfactor = 3;
737var scount=10;
738
739function scrollByPix() {
740	if (scount<=0) {
741		sfactor*=1.2;
742		scount=10;
743	}
744	parent.lhs.scrollBy(0,sfactor);
745	parent.rhs.scrollBy(0,sfactor);
746	scount--;
747}
748
749function scrollToAnc(num) {
750
751	// Update the value of the anchor in the form which we use as
752	// storage for this value.  setAncValue() will take care of
753	// correcting for overflow and underflow of the value and return
754	// us the new value.
755	num = setAncValue(num);
756
757	// Set location and scroll back a little to expose previous
758	// lines.
759	//
760	// Note that this could be improved: it is possible although
761	// complex to compute the x and y position of an anchor, and to
762	// scroll to that location directly.
763	//
764	parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
765	parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
766
767	parent.lhs.scrollBy(0,-30);
768	parent.rhs.scrollBy(0,-30);
769}
770
771function getAncValue()
772{
773	return (parseInt(parent.nav.document.diff.real.value));
774}
775
776function setAncValue(val)
777{
778	if (val <= 0) {
779		val = 0;
780		parent.nav.document.diff.real.value = val;
781		parent.nav.document.diff.display.value = "BOF";
782		return (val);
783	}
784
785	//
786	// The way we compute the max anchor value is to stash it
787	// inline in the left and right hand side pages-- it's the same
788	// on each side, so we pluck from the left.
789	//
790	maxval = parent.lhs.document.eof.value.value;
791	if (val < maxval) {
792		parent.nav.document.diff.real.value = val;
793		parent.nav.document.diff.display.value = val.toString();
794		return (val);
795	}
796
797	// this must be: val >= maxval
798	val = maxval;
799	parent.nav.document.diff.real.value = val;
800	parent.nav.document.diff.display.value = "EOF";
801	return (val);
802}
803
804function stopScroll() {
805	if (scrolling==1) {
806		clearInterval(myInt);
807		scrolling=0;
808	}
809}
810
811function startScroll() {
812	stopScroll();
813	scrolling=1;
814	myInt=setInterval("scrollByPix()",10);
815}
816
817function handlePress(b) {
818
819	switch (b) {
820	    case 1 :
821		scrollToAnc(-1);
822		break;
823	    case 2 :
824		scrollToAnc(getAncValue() - 1);
825		break;
826	    case 3 :
827		sfactor=-3;
828		startScroll();
829		break;
830	    case 4 :
831		sfactor=3;
832		startScroll();
833		break;
834	    case 5 :
835		scrollToAnc(getAncValue() + 1);
836		break;
837	    case 6 :
838		scrollToAnc(999999);
839		break;
840	}
841}
842
843function handleRelease(b) {
844	stopScroll();
845}
846
847function keypress(ev) {
848	var keynum;
849	var keychar;
850
851	if (window.event) { // IE
852		keynum = ev.keyCode;
853	} else if (ev.which) { // non-IE
854		keynum = ev.which;
855	}
856
857	keychar = String.fromCharCode(keynum);
858
859	if (keychar == "k") {
860		handlePress(2);
861		return (0);
862	} else if (keychar == "j" || keychar == " ") {
863		handlePress(5);
864		return (0);
865	}
866	return (1);
867}
868
869function ValidateDiffNum(){
870	val = parent.nav.document.diff.display.value;
871	if (val == "EOF") {
872		scrollToAnc(999999);
873		return;
874	}
875
876	if (val == "BOF") {
877		scrollToAnc(0);
878		return;
879	}
880
881        i=parseInt(val);
882        if (isNaN(i)) {
883                parent.nav.document.diff.display.value = getAncValue();
884        } else {
885                scrollToAnc(i);
886        }
887        return false;
888}
889
890EOF
891}
892
893#
894# frame_navigation
895#
896# Output anchor navigation file for framed sdiffs.
897#
898function frame_navigation
899{
900	print "$HTML<head>$STDHEAD"
901
902	cat << \EOF
903<title>Anchor Navigation</title>
904<meta http-equiv="Content-Script-Type" content="text/javascript">
905<meta http-equiv="Content-Type" content="text/html">
906
907<style type="text/css">
908    div.button td { padding-left: 5px; padding-right: 5px;
909		    background-color: #eee; text-align: center;
910		    border: 1px #444 outset; cursor: pointer; }
911    div.button a { font-weight: bold; color: black }
912    div.button td:hover { background: #ffcc99; }
913</style>
914EOF
915
916	print "<script type=\"text/javascript\" src=\"ancnav.js\" />"
917
918	cat << \EOF
919</head>
920<body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
921	onkeypress="keypress(event);">
922    <noscript lang="javascript">
923      <center>
924	<p><big>Framed Navigation controls require Javascript</big><br />
925	Either this browser is incompatable or javascript is not enabled</p>
926      </center>
927    </noscript>
928    <table width="100%" border="0" align="center">
929	<tr>
930          <td valign="middle" width="25%">Diff navigation:
931          Use 'j' and 'k' for next and previous diffs; or use buttons
932          at right</td>
933	  <td align="center" valign="top" width="50%">
934	    <div class="button">
935	      <table border="0" align="center">
936                  <tr>
937		    <td>
938		      <a onMouseDown="handlePress(1);return true;"
939			 onMouseUp="handleRelease(1);return true;"
940			 onMouseOut="handleRelease(1);return true;"
941			 onClick="return false;"
942			 title="Go to Beginning Of file">BOF</a></td>
943		    <td>
944		      <a onMouseDown="handlePress(3);return true;"
945			 onMouseUp="handleRelease(3);return true;"
946			 onMouseOut="handleRelease(3);return true;"
947			 title="Scroll Up: Press and Hold to accelerate"
948			 onClick="return false;">Scroll Up</a></td>
949		    <td>
950		      <a onMouseDown="handlePress(2);return true;"
951			 onMouseUp="handleRelease(2);return true;"
952			 onMouseOut="handleRelease(2);return true;"
953			 title="Go to previous Diff"
954			 onClick="return false;">Prev Diff</a>
955		    </td></tr>
956
957		  <tr>
958		    <td>
959		      <a onMouseDown="handlePress(6);return true;"
960			 onMouseUp="handleRelease(6);return true;"
961			 onMouseOut="handleRelease(6);return true;"
962			 onClick="return false;"
963			 title="Go to End Of File">EOF</a></td>
964		    <td>
965		      <a onMouseDown="handlePress(4);return true;"
966			 onMouseUp="handleRelease(4);return true;"
967			 onMouseOut="handleRelease(4);return true;"
968			 title="Scroll Down: Press and Hold to accelerate"
969			 onClick="return false;">Scroll Down</a></td>
970		    <td>
971		      <a onMouseDown="handlePress(5);return true;"
972			 onMouseUp="handleRelease(5);return true;"
973			 onMouseOut="handleRelease(5);return true;"
974			 title="Go to next Diff"
975			 onClick="return false;">Next Diff</a></td>
976		  </tr>
977              </table>
978	    </div>
979	  </td>
980	  <th valign="middle" width="25%">
981	    <form action="" name="diff" onsubmit="return ValidateDiffNum();">
982		<input name="display" value="BOF" size="8" type="text" />
983		<input name="real" value="0" size="8" type="hidden" />
984	    </form>
985	  </th>
986	</tr>
987    </table>
988  </body>
989</html>
990EOF
991}
992
993
994
995#
996# diff_to_html <filename> <filepath> { U | C } <comment>
997#
998# Processes the output of diff to produce an HTML file representing either
999# context or unified diffs.
1000#
1001diff_to_html()
1002{
1003	TNAME=$1
1004	TPATH=$2
1005	DIFFTYPE=$3
1006	COMMENT=$4
1007
1008	print "$HTML<head>$STDHEAD"
1009	print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1010
1011	if [[ $DIFFTYPE == "U" ]]; then
1012		print "$UDIFFCSS"
1013	fi
1014
1015	cat <<-EOF
1016	</head>
1017	<body id="SUNWwebrev">
1018        <a class="print" href="javascript:print()">Print this page</a>
1019	<pre>$COMMENT</pre>
1020        <pre>
1021	EOF
1022
1023	html_quote | nawk '
1024	/^--- new/	{ next }
1025	/^\+\+\+ new/	{ next }
1026	/^--- old/	{ next }
1027	/^\*\*\* old/	{ next }
1028	/^\*\*\*\*/	{ next }
1029	/^-------/	{ printf "<center><h1>%s</h1></center>\n", $0; next }
1030	/^\@\@.*\@\@$/	{ printf "</pre><hr /><pre>\n";
1031			  printf "<span class=\"newmarker\">%s</span>\n", $0;
1032			  next}
1033
1034	/^\*\*\*/	{ printf "<hr /><span class=\"oldmarker\">%s</span>\n", $0;
1035			  next}
1036	/^---/		{ printf "<span class=\"newmarker\">%s</span>\n", $0;
1037			  next}
1038	/^\+/		{printf "<span class=\"new\">%s</span>\n", $0; next}
1039	/^!/		{printf "<span class=\"changed\">%s</span>\n", $0; next}
1040	/^-/		{printf "<span class=\"removed\">%s</span>\n", $0; next}
1041			{printf "%s\n", $0; next}
1042	'
1043
1044	print "</pre></body></html>\n"
1045}
1046
1047
1048#
1049# source_to_html { new | old } <filename>
1050#
1051# Process a plain vanilla source file to transform it into an HTML file.
1052#
1053source_to_html()
1054{
1055	WHICH=$1
1056	TNAME=$2
1057
1058	print "$HTML<head>$STDHEAD"
1059	print "<title>$WHICH $TNAME</title>"
1060	print "<body id=\"SUNWwebrev\">"
1061	print "<pre>"
1062	html_quote | nawk '{line += 1 ; printf "%4d %s\n", line, $0 }'
1063	print "</pre></body></html>"
1064}
1065
1066#
1067# teamwarecomments {text|html} parent-file child-file
1068#
1069# Find the first delta in the child that's not in the parent.  Get the
1070# newest delta from the parent, get all deltas from the child starting
1071# with that delta, and then get all info starting with the second oldest
1072# delta in that list (the first delta unique to the child).
1073#
1074# This code adapted from Bill Shannon's "spc" script
1075#
1076comments_from_teamware()
1077{
1078	fmt=$1
1079	pfile=$PWS/$2
1080	cfile=$CWS/$3
1081
1082	if [[ -f $pfile ]]; then
1083		psid=$(sccs prs -d:I: $pfile 2>/dev/null)
1084	else
1085		psid=1.1
1086	fi
1087
1088	set -A sids $(sccs prs -l -r$psid -d:I: $cfile 2>/dev/null)
1089	N=${#sids[@]}
1090
1091	nawkprg='
1092		/^COMMENTS:/	{p=1; continue}
1093		/^D [0-9]+\.[0-9]+/ {printf "--- %s ---\n", $2; p=0; }
1094		NF == 0u	{ continue }
1095		{if (p==0) continue; print $0 }'
1096
1097	if [[ $N -ge 2 ]]; then
1098		sid1=${sids[$((N-2))]}	# Gets 2nd to last sid
1099
1100		if [[ $fmt == "text" ]]; then
1101			sccs prs -l -r$sid1 $cfile  2>/dev/null | \
1102			    nawk "$nawkprg"
1103			return
1104		fi
1105
1106		sccs prs -l -r$sid1 $cfile  2>/dev/null | \
1107		    html_quote | bug2url | sac2url | nawk "$nawkprg"
1108	fi
1109}
1110
1111#
1112# wxcomments {text|html} filepath
1113#
1114# Given the pathname of a file, find its location in a "wx" active file
1115# list and print the following sccs comment.  Output is either text or
1116# HTML; if the latter, embedded bugids (sequence of 5 or more digits) are
1117# turned into URLs.
1118#
1119comments_from_wx()
1120{
1121	typeset fmt=$1
1122	typeset p=$2
1123
1124	comm=`nawk '
1125	$1 == "'$p'" {
1126		do getline ; while (NF > 0)
1127		getline
1128		while (NF > 0) { print ; getline }
1129		exit
1130	}' < $wxfile`
1131
1132	if [[ $fmt == "text" ]]; then
1133		print "$comm"
1134		return
1135	fi
1136
1137	print "$comm" | html_quote | bug2url | sac2url
1138}
1139
1140#
1141# getcomments {text|html} filepath parentpath
1142#
1143# Fetch the comments depending on what SCM mode we're in.
1144#
1145getcomments()
1146{
1147	typeset fmt=$1
1148	typeset p=$2
1149	typeset pp=$3
1150
1151	if [[ -n $wxfile ]]; then
1152		comments_from_wx $fmt $p
1153	else
1154		if [[ $SCM_MODE == "teamware" ]]; then
1155			comments_from_teamware $fmt $pp $p
1156		fi
1157	fi
1158}
1159
1160#
1161# printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1162#
1163# Print out Code Inspection figures similar to sccs-prt(1) format.
1164#
1165function printCI
1166{
1167	integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1168	typeset str
1169	if (( tot == 1 )); then
1170		str="line"
1171	else
1172		str="lines"
1173	fi
1174	printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1175	    $tot $str $ins $del $mod $unc
1176}
1177
1178
1179#
1180# difflines <oldfile> <newfile>
1181#
1182# Calculate and emit number of added, removed, modified and unchanged lines,
1183# and total lines changed, the sum of added + removed + modified.
1184#
1185function difflines
1186{
1187	integer tot mod del ins unc err
1188	typeset filename
1189
1190	diff -e $1 $2 | eval $( nawk '
1191	# Change range of lines: N,Nc
1192	/^[0-9]*,[0-9]*c$/ {
1193		n=split(substr($1,1,length($1)-1), counts, ",");
1194		if (n != 2) {
1195		    error=2
1196		    exit;
1197		}
1198		#
1199		# 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1200		# following would be 5 - 3 = 2! Hence +1 for correction.
1201		#
1202		r=(counts[2]-counts[1])+1;
1203
1204		#
1205		# Now count replacement lines: each represents a change instead
1206		# of a delete, so increment c and decrement r.
1207		#
1208		while (getline != /^\.$/) {
1209			c++;
1210			r--;
1211		}
1212		#
1213		# If there were more replacement lines than original lines,
1214		# then r will be negative; in this case there are no deletions,
1215		# but there are r changes that should be counted as adds, and
1216		# since r is negative, subtract it from a and add it to c.
1217		#
1218		if (r < 0) {
1219			a-=r;
1220			c+=r;
1221		}
1222
1223		#
1224		# If there were more original lines than replacement lines, then
1225		# r will be positive; in this case, increment d by that much.
1226		#
1227		if (r > 0) {
1228			d+=r;
1229		}
1230		next;
1231	}
1232
1233	# Change lines: Nc
1234	/^[0-9].*c$/ {
1235		# The first line is a replacement; any more are additions.
1236		if (getline != /^\.$/) {
1237			c++;
1238			while (getline != /^\.$/) a++;
1239		}
1240		next;
1241	}
1242
1243	# Add lines: both Na and N,Na
1244	/^[0-9].*a$/ {
1245		while (getline != /^\.$/) a++;
1246		next;
1247	}
1248
1249	# Delete range of lines: N,Nd
1250	/^[0-9]*,[0-9]*d$/ {
1251		n=split(substr($1,1,length($1)-1), counts, ",");
1252		if (n != 2) {
1253			error=2
1254			exit;
1255		}
1256		#
1257		# 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1258		# following would be 5 - 3 = 2! Hence +1 for correction.
1259		#
1260		r=(counts[2]-counts[1])+1;
1261		d+=r;
1262		next;
1263	}
1264
1265	# Delete line: Nd.   For example 10d says line 10 is deleted.
1266	/^[0-9]*d$/ {d++; next}
1267
1268	# Should not get here!
1269	{
1270		error=1;
1271		exit;
1272	}
1273
1274	# Finish off - print results
1275	END {
1276		printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1277		    (c+d+a), c, d, a, error);
1278	}' )
1279
1280	# End of nawk, Check to see if any trouble occurred.
1281	if (( $? > 0 || err > 0 )); then
1282		print "Unexpected Error occurred reading" \
1283		    "\`diff -e $1 $2\`: \$?=$?, err=" $err
1284		return
1285	fi
1286
1287	# Accumulate totals
1288	(( TOTL += tot ))
1289	(( TMOD += mod ))
1290	(( TDEL += del ))
1291	(( TINS += ins ))
1292	# Calculate unchanged lines
1293	wc -l $1 | read unc filename
1294	if (( unc > 0 )); then
1295		(( unc -= del + mod ))
1296		(( TUNC += unc ))
1297	fi
1298	# print summary
1299	print "<span class=\"lineschanged\">"
1300	printCI $tot $ins $del $mod $unc
1301	print "</span>"
1302}
1303
1304
1305#
1306# flist_from_wx
1307#
1308# Sets up webrev to source its information from a wx-formatted file.
1309# Sets the global 'wxfile' variable.
1310#
1311function flist_from_wx
1312{
1313	typeset argfile=$1
1314	if [[ -n ${argfile%%/*} ]]; then
1315		#
1316		# If the wx file pathname is relative then make it absolute
1317		# because the webrev does a "cd" later on.
1318		#
1319		wxfile=$PWD/$argfile
1320	else
1321		wxfile=$argfile
1322	fi
1323
1324	nawk '{ c = 1; print;
1325	  while (getline) {
1326		if (NF == 0) { c = -c; continue }
1327		if (c > 0) print
1328	  }
1329	}' $wxfile > $FLIST
1330
1331	print " Done."
1332}
1333
1334#
1335# flist_from_teamware [ <args-to-putback-n> ]
1336#
1337# Generate the file list by extracting file names from a putback -n.  Some
1338# names may come from the "update/create" messages and others from the
1339# "currently checked out" warning.  Renames are detected here too.  Extract
1340# values for CODEMGR_WS and CODEMGR_PARENT from the output of the putback
1341# -n as well, but remove them if they are already defined.
1342#
1343function flist_from_teamware
1344{
1345	if [[ -n $codemgr_parent ]]; then
1346		if [[ ! -d $codemgr_parent/Codemgr_wsdata ]]; then
1347			print -u2 "parent $codemgr_parent doesn't look like a" \
1348			    "valid teamware workspace"
1349			exit 1
1350		fi
1351		parent_args="-p $codemgr_parent"
1352	fi
1353
1354	print " File list from: 'putback -n $parent_args $*' ... \c"
1355
1356	putback -n $parent_args $* 2>&1 |
1357	    nawk '
1358		/^update:|^create:/	{print $2}
1359		/^Parent workspace:/	{printf("CODEMGR_PARENT=%s\n",$3)}
1360		/^Child workspace:/	{printf("CODEMGR_WS=%s\n",$3)}
1361		/^The following files are currently checked out/ {p = 1; continue}
1362		NF == 0			{p=0 ; continue}
1363		/^rename/		{old=$3}
1364		$1 == "to:"		{print $2, old}
1365		/^"/			{continue}
1366		p == 1			{print $1}' |
1367	    sort -r -k 1,1 -u | sort > $FLIST
1368
1369	print " Done."
1370}
1371
1372function env_from_flist
1373{
1374	[[ -r $FLIST ]] || return
1375
1376	#
1377	# Use "eval" to set env variables that are listed in the file
1378	# list.  Then copy those into our local versions of those
1379	# variables if they have not been set already.
1380	#
1381	eval `sed -e "s/#.*$//" $FLIST | grep = `
1382
1383	[[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
1384
1385	#
1386	# Check to see if CODEMGR_PARENT is set in the flist file.
1387	#
1388	[[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
1389	    codemgr_parent=$CODEMGR_PARENT
1390}
1391
1392#
1393# detect_scm
1394#
1395# We dynamically test the SCM type; this allows future extensions to
1396# new SCM types
1397#
1398function detect_scm
1399{
1400	#
1401	# If CODEMGR_WS is specified in the flist file, we assume teamware.
1402	#
1403	if [[ -r $FLIST ]]; then
1404		egrep '^CODEMGR_WS=' $FLIST > /dev/null 2>&1
1405		if [[ $? -eq 0 ]]; then
1406			print "teamware"
1407			return
1408		fi
1409	fi
1410
1411	#
1412	# The presence of $CODEMGR_WS and a Codemgr_wsdata directory
1413	# is our clue that this is a teamware workspace.
1414	#
1415	if [[ -n $CODEMGR_WS && -d "$CODEMGR_WS/Codemgr_wsdata" ]]; then
1416		print "teamware"
1417	else
1418		print "unknown"
1419	fi
1420}
1421
1422function look_for_prog
1423{
1424	typeset path
1425	typeset ppath
1426	typeset progname=$1
1427
1428	ppath=$PATH
1429	ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1430	ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin
1431	ppath=$ppath:/opt/onbld/bin/`/usr/bin/uname -p`
1432
1433	PATH=$ppath prog=`whence $progname`
1434	if [[ -n $prog ]]; then
1435		print $prog
1436	fi
1437}
1438
1439#
1440# Usage message.
1441#
1442function usage
1443{
1444	print 'Usage:\twebrev [common-options]
1445	webrev [common-options] ( <file> | - )
1446	webrev [common-options] -w <wx file>
1447	webrev [common-options] -l [arguments to 'putback']
1448
1449Options:
1450	-O: Print bugids/arc cases suitable for OpenSolaris.
1451	-i <filename>: Include <filename> in the index.html file.
1452	-o <outdir>: Output webrev to specified directory.
1453	-p <compare-against>: Use specified parent wkspc or basis for comparison
1454	-w <wxfile>: Use specified wx active file.
1455
1456Environment:
1457	WDIR: Control the output directory.
1458	WEBREV_BUGURL: Control the URL prefix for bugids.
1459	WEBREV_SACURL: Control the URL prefix for ARC cases.
1460
1461SCM Environment:
1462	Teamware: CODEMGR_WS: Workspace location.
1463	Teamware: CODEMGR_PARENT: Parent workspace location.
1464'
1465
1466	exit 2
1467}
1468
1469#
1470#
1471# Main program starts here
1472#
1473#
1474
1475trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
1476
1477set +o noclobber
1478
1479[[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
1480[[ -z $WX ]] && WX=`look_for_prog wx`
1481[[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
1482[[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
1483[[ -z $PERL ]] && PERL=`look_for_prog perl`
1484
1485if [[ ! -x $PERL ]]; then
1486	print -u2 "Error: No perl interpreter found.  Exiting."
1487	exit 1
1488fi
1489
1490#
1491# These aren't fatal, but we want to note them to the user.
1492# We don't warn on the absence of 'wx' until later when we've
1493# determined that we actually need to try to invoke it.
1494#
1495[[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
1496[[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
1497[[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
1498
1499# Declare global total counters.
1500integer TOTL TINS TDEL TMOD TUNC
1501
1502flist_mode=
1503flist_file=
1504iflag=
1505oflag=
1506pflag=
1507lflag=
1508wflag=
1509Oflag=
1510while getopts "i:o:p:lwO" opt
1511do
1512	case $opt in
1513	i)	iflag=1
1514		INCLUDE_FILE=$OPTARG;;
1515
1516	o)	oflag=1
1517		WDIR=$OPTARG;;
1518
1519	p)	pflag=1
1520		codemgr_parent=$OPTARG;;
1521
1522	#
1523	# If -l has been specified, we need to abort further options
1524	# processing, because subsequent arguments are going to be
1525	# arguments to 'putback -n'.
1526	#
1527	l)	lflag=1
1528		break;;
1529
1530	w)	wflag=1;;
1531
1532	O)	Oflag=1;;
1533
1534	?)	usage;;
1535	esac
1536done
1537
1538FLIST=/tmp/$$.flist
1539
1540if [[ -n $wflag && -n $lflag ]]; then
1541	usage
1542fi
1543
1544#
1545# If this manually set as the parent, and it appears to be an earlier webrev,
1546# then note that fact and set the parent to the raw_files/new subdirectory.
1547#
1548if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
1549	parent_webrev="$codemgr_parent"
1550	codemgr_parent="$codemgr_parent/raw_files/new"
1551fi
1552
1553if [[ -z $wflag && -z $lflag ]]; then
1554	shift $(($OPTIND - 1))
1555
1556	if [[ $1 == "-" ]]; then
1557		cat > $FLIST
1558		flist_mode="stdin"
1559		flist_done=1
1560		shift
1561	elif [[ -n $1 ]]; then
1562		if [[ ! -r $1 ]]; then
1563			print -u2 "$1: no such file or not readable"
1564			usage
1565		fi
1566		cat $1 > $FLIST
1567		flist_mode="file"
1568		flist_file=$1
1569		flist_done=1
1570		shift
1571	else
1572		flist_mode="auto"
1573	fi
1574fi
1575
1576#
1577# Before we go on to further consider -l and -w, work out which SCM we think
1578# is in use.
1579#
1580SCM_MODE=`detect_scm $FLIST`
1581if [[ $SCM_MODE == "unknown" ]]; then
1582	print -u2 "Unable to determine SCM type currently in use."
1583	print -u2 "For teamware: webrev looks for \$CODEMGR_WS either in"
1584	print -u2 "              the environment or in the file list."
1585	exit 1
1586fi
1587
1588print -u2 "   SCM detected: $SCM_MODE"
1589
1590if [[ -n $lflag ]]; then
1591	#
1592	# If the -l flag is given instead of the name of a file list,
1593	# then generate the file list by extracting file names from a
1594	# putback -n.
1595	#
1596	shift $(($OPTIND - 1))
1597	flist_from_teamware "$*"
1598	flist_done=1
1599	shift $#
1600
1601elif [[ -n $wflag ]]; then
1602	#
1603	# If the -w is given then assume the file list is in Bonwick's "wx"
1604	# command format, i.e.  pathname lines alternating with SCCS comment
1605	# lines with blank lines as separators.  Use the SCCS comments later
1606	# in building the index.html file.
1607	#
1608	shift $(($OPTIND - 1))
1609	wxfile=$1
1610	if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
1611		if [[ -r $CODEMGR_WS/wx/active ]]; then
1612			wxfile=$CODEMGR_WS/wx/active
1613		fi
1614	fi
1615
1616	[[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
1617	    "be auto-detected (check \$CODEMGR_WS)" && exit 1
1618
1619	print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
1620	flist_from_wx $wxfile
1621	flist_done=1
1622	if [[ -n "$*" ]]; then
1623		shift
1624	fi
1625elif [[ $flist_mode == "stdin" ]]; then
1626	print -u2 " File list from: standard input"
1627elif [[ $flist_mode == "file" ]]; then
1628	print -u2 " File list from: $flist_file"
1629fi
1630
1631if [[ $# -gt 0 ]]; then
1632	print -u2 "WARNING: unused arguments: $*"
1633fi
1634
1635if [[ $SCM_MODE == "teamware" ]]; then
1636	#
1637	# Parent (internally $codemgr_parent) and workspace ($codemgr_ws) can
1638	# be set in a number of ways, in decreasing precedence:
1639	#
1640	#      1) on the command line (only for the parent)
1641	#      2) in the user environment
1642	#      3) in the flist
1643	#      4) automatically based on the workspace (only for the parent)
1644	#
1645
1646	#
1647	# Here is case (2): the user environment
1648	#
1649	[[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
1650	if [[ -n $codemgr_ws && ! -d $codemgr_ws ]]; then
1651		print -u2 "$codemgr_ws: no such workspace"
1652		exit 1
1653	fi
1654
1655	[[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
1656	    codemgr_parent=$CODEMGR_PARENT
1657	if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then
1658		print -u2 "$codemgr_parent: no such directory"
1659		exit 1
1660	fi
1661
1662	#
1663	# If we're in auto-detect mode and we haven't already gotten the file
1664	# list, then see if we can get it by probing for wx.
1665	#
1666	if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then
1667		if [[ ! -x $WX ]]; then
1668			print -u2 "WARNING: wx not found!"
1669		fi
1670
1671		#
1672		# We need to use wx list -w so that we get renamed files, etc.
1673		# but only if a wx active file exists-- otherwise wx will
1674		# hang asking us to initialize our wx information.
1675		#
1676		if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then
1677			print -u2 " File list from: 'wx list -w' ... \c"
1678			$WX list -w > $FLIST
1679			$WX comments > /tmp/$$.wx_comments
1680			wxfile=/tmp/$$.wx_comments
1681			print -u2 "done"
1682			flist_done=1
1683		fi
1684	fi
1685
1686	#
1687	# If by hook or by crook we've gotten a file list by now (perhaps
1688	# from the command line), eval it to extract environment variables from
1689	# it: This is step (3).
1690	#
1691	env_from_flist
1692
1693	#
1694	# Continuing step (3): If we still have no file list, we'll try to get
1695	# it from teamware.
1696	#
1697	if [[ -z $flist_done ]]; then
1698		flist_from_teamware
1699		env_from_flist
1700	fi
1701
1702	#
1703	# Observe true directory name of CODEMGR_WS, as used later in
1704	# webrev title.
1705	#
1706	codemgr_ws=$(cd $codemgr_ws;print $PWD)
1707
1708	#
1709	# (4) If we still don't have a value for codemgr_parent, get it
1710	# from workspace.
1711	#
1712	[[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent`
1713	if [[ ! -d $codemgr_parent ]]; then
1714		print -u2 "$CODEMGR_PARENT: no such parent workspace"
1715		exit 1
1716	fi
1717
1718	#
1719	# Reset CODEMGR_WS to make sure teamware commands are happy.
1720	#
1721	CODEMGR_WS=$codemgr_ws
1722	CWS=$codemgr_ws
1723	PWS=$codemgr_parent
1724fi
1725
1726#
1727# If the user didn't specify a -i option, check to see if there is a
1728# webrev-info file in the workspace directory.
1729#
1730if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
1731	iflag=1
1732	INCLUDE_FILE="$CWS/webrev-info"
1733fi
1734
1735if [[ -n $iflag ]]; then
1736	if [[ ! -r $INCLUDE_FILE ]]; then
1737		print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
1738		    "not readable."
1739		exit 1
1740	else
1741		#
1742		# $INCLUDE_FILE may be a relative path, and the script alters
1743		# PWD, so we just stash a copy in /tmp.
1744		#
1745		cp $INCLUDE_FILE /tmp/$$.include
1746	fi
1747fi
1748
1749#
1750# Output directory.
1751#
1752WDIR=${WDIR:-$CWS/webrev}
1753
1754#
1755# Name of the webrev, derived from the workspace name; in the
1756# future this could potentially be an option.
1757#
1758WNAME=${CWS##*/}
1759
1760if [ ${WDIR%%/*} ]; then
1761	WDIR=$PWD/$WDIR
1762fi
1763
1764if [[ ! -d $WDIR ]]; then
1765	mkdir -p $WDIR
1766	[[ $? != 0 ]] && exit 1
1767fi
1768
1769#
1770# Summarize what we're going to do.
1771#
1772print "      Workspace: $CWS"
1773if [[ -n $parent_webrev ]]; then
1774	print "Compare against: webrev at $parent_webrev"
1775else
1776	print "Compare against: $PWS"
1777fi
1778
1779[[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
1780print "      Output to: $WDIR"
1781
1782#
1783# Save the file list in the webrev dir
1784#
1785[[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
1786
1787#
1788#    Bug IDs will be replaced by a URL.  Order of precedence
1789#    is: default location, $WEBREV_BUGURL, the -O flag.
1790#
1791BUGURL='http://monaco.sfbay.sun.com/detail.jsp?cr='
1792[[ -n $WEBREV_BUGURL ]] && BUGURL="$WEBREV_BUGURL"
1793[[ -n "$Oflag" ]] && \
1794    BUGURL='http://bugs.opensolaris.org/bugdatabase/view_bug.do?bug_id='
1795
1796#
1797#    Likewise, ARC cases will be replaced by a URL.  Order of precedence
1798#    is: default, $WEBREV_SACURL, the -O flag.
1799#
1800#    Note that -O also triggers different substitution behavior for
1801#    SACURL.  See sac2url().
1802#
1803SACURL='http://sac.eng.sun.com'
1804[[ -n $WEBREV_SACURL ]] && SACURL="$WEBREV_SACURL"
1805[[ -n $Oflag ]] && \
1806    SACURL='http://www.opensolaris.org/os/community/arc/caselog'
1807
1808rm -f $WDIR/$WNAME.patch
1809rm -f $WDIR/$WNAME.ps
1810rm -f $WDIR/$WNAME.pdf
1811
1812touch $WDIR/$WNAME.patch
1813
1814print "   Output Files:"
1815
1816#
1817# Clean up the file list: Remove comments, blank lines and env variables.
1818#
1819sed -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
1820FLIST=/tmp/$$.flist.clean
1821
1822#
1823# First pass through the files: generate the per-file webrev HTML-files.
1824#
1825cat $FLIST | while read LINE
1826do
1827	set - $LINE
1828	P=$1
1829
1830	#
1831	# Normally, each line in the file list is just a pathname of a
1832	# file that has been modified or created in the child.  A file
1833	# that is renamed in the child workspace has two names on the
1834	# line: new name followed by the old name.
1835	#
1836	oldname=""
1837	oldpath=""
1838	rename=
1839	if [[ $# -eq 2 ]]; then
1840		PP=$2			# old filename
1841		oldname=" (was $PP)"
1842		oldpath="$PP"
1843		rename=1
1844        	PDIR=${PP%/*}
1845        	if [[ $PDIR == $PP ]]; then
1846			PDIR="."   # File at root of workspace
1847		fi
1848
1849		PF=${PP##*/}
1850
1851	        DIR=${P%/*}
1852	        if [[ $DIR == $P ]]; then
1853			DIR="."   # File at root of workspace
1854		fi
1855
1856		F=${P##*/}
1857
1858        else
1859	        DIR=${P%/*}
1860	        if [[ "$DIR" == "$P" ]]; then
1861			DIR="."   # File at root of workspace
1862		fi
1863
1864		F=${P##*/}
1865
1866		PP=$P
1867		PDIR=$DIR
1868		PF=$F
1869	fi
1870
1871	COMM=`getcomments html $P $PP`
1872
1873	if [[ ! -d $CWS/$DIR ]]; then
1874		print "  $CWS/$DIR: no such directory"
1875		continue
1876	fi
1877
1878	print "\t$P$oldname\n\t\t\c"
1879
1880	# Make the webrev mirror directory if necessary
1881	mkdir -p $WDIR/$DIR
1882
1883	# cd to the directory so the names are short
1884	cd $CWS/$DIR
1885
1886	#
1887	# If we're in OpenSolaris mode, we enforce a minor policy:
1888	# help to make sure the reviewer doesn't accidentally publish
1889	# source which is in usr/closed/*
1890	#
1891	if [[ -n $Oflag ]]; then
1892		pclosed=${P##usr/closed/}
1893		if [[ $pclosed != $P ]]; then
1894			print "*** Omitting closed source for OpenSolaris" \
1895			    "mode review"
1896			continue
1897		fi
1898	fi
1899
1900	#
1901	# We stash old and new files into parallel directories in /tmp
1902	# and do our diffs there.  This makes it possible to generate
1903	# clean looking diffs which don't have absolute paths present.
1904	#
1905	olddir=$WDIR/raw_files/old
1906	newdir=$WDIR/raw_files/new
1907	mkdir -p $olddir
1908	mkdir -p $newdir
1909	mkdir -p $olddir/$PDIR
1910	mkdir -p $newdir/$DIR
1911
1912	if [[ $SCM_MODE == "teamware" ]]; then
1913		# If the child's version doesn't exist then
1914		# get a readonly copy.
1915
1916		if [[ ! -f $F && -f SCCS/s.$F ]]; then
1917			sccs get -s $F
1918		fi
1919
1920		#
1921		# Snag new version of file.
1922		#
1923		rm -f $newdir/$DIR/$F
1924		cp $F $newdir/$DIR/$F
1925
1926		#
1927		# Get the parent's version of the file. First see whether the
1928		# child's version is checked out and get the parent's version
1929		# with keywords expanded or unexpanded as appropriate.
1930		#
1931		if [ -f $PWS/$PDIR/SCCS/s.$PF -o \
1932		    -f $PWS/$PDIR/SCCS/p.$PF ]; then
1933			rm -f $olddir/$PDIR/$PF
1934			if [ -f SCCS/p.$F ]; then
1935				sccs get -s -p -k $PWS/$PDIR/$PF \
1936				    > $olddir/$PDIR/$PF
1937			else
1938				sccs get -s -p    $PWS/$PDIR/$PF \
1939				    > $olddir/$PDIR/$PF
1940			fi
1941		else
1942			if [[ -f $PWS/$PDIR/$PF ]]; then
1943				# Parent is not a real workspace, but just a raw
1944				# directory tree - use the file that's there as
1945				# the old file.
1946
1947				rm -f $olddir/$DIR/$F
1948				cp $PWS/$PDIR/$PF $olddir/$DIR/$F
1949			fi
1950		fi
1951	fi
1952
1953	if [[ ! -f $F && ! -f $olddir/$DIR/$F ]]; then
1954		print "*** Error: file not in parent or child"
1955		continue
1956	fi
1957
1958	cd $WDIR/raw_files
1959	ofile=old/$PDIR/$PF
1960	nfile=new/$DIR/$F
1961
1962	mv_but_nodiff=
1963	cmp $ofile $nfile > /dev/null 2>&1
1964	if [[ $? == 0 && $rename == 1 ]]; then
1965		mv_but_nodiff=1
1966	fi
1967
1968	#
1969	# If we have old and new versions of the file then run the appropriate
1970	# diffs.  This is complicated by a couple of factors:
1971	#
1972	#	- renames must be handled specially: we emit a 'remove'
1973	#	  diff and an 'add' diff
1974	#	- new files and deleted files must be handled specially
1975	#	- Solaris patch(1m) can't cope with file creation
1976	#	  (and hence renames) as of this writing.
1977	#       - To make matters worse, gnu patch doesn't interpret the
1978	#	  output of Solaris diff properly when it comes to
1979	#	  adds and deletes.  We need to do some "cleansing"
1980	#         transformations:
1981	# 	    [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
1982	#	    [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
1983	#
1984	cleanse_rmfile="sed 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
1985	cleanse_newfile="sed 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
1986
1987	rm -f $WDIR/$DIR/$F.patch
1988	if [[ -z $rename ]]; then
1989		if [ ! -f $ofile ]; then
1990			diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
1991			    > $WDIR/$DIR/$F.patch
1992		elif [ ! -f $nfile ]; then
1993			diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
1994			    > $WDIR/$DIR/$F.patch
1995		else
1996			diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
1997		fi
1998	else
1999		diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
2000		    > $WDIR/$DIR/$F.patch
2001
2002		diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
2003		    >> $WDIR/$DIR/$F.patch
2004
2005	fi
2006
2007	#
2008	# Tack the patch we just made onto the accumulated patch for the
2009	# whole wad.
2010	#
2011	cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
2012
2013	print " patch\c"
2014
2015	if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
2016
2017		${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
2018		diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
2019		    > $WDIR/$DIR/$F.cdiff.html
2020		print " cdiffs\c"
2021
2022		${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
2023		diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
2024		    > $WDIR/$DIR/$F.udiff.html
2025
2026		print " udiffs\c"
2027
2028		if [[ -x $WDIFF ]]; then
2029			$WDIFF -c "$COMM" \
2030			    -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
2031			    $WDIR/$DIR/$F.wdiff.html 2>/dev/null
2032			if [[ $? -eq 0 ]]; then
2033				print " wdiffs\c"
2034			else
2035				print " wdiffs[fail]\c"
2036			fi
2037		fi
2038
2039		sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
2040		    > $WDIR/$DIR/$F.sdiff.html
2041		print " sdiffs\c"
2042
2043		print " frames\c"
2044
2045		rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
2046
2047		difflines $ofile $nfile > $WDIR/$DIR/$F.count
2048
2049	elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
2050		# renamed file: may also have differences
2051		difflines $ofile $nfile > $WDIR/$DIR/$F.count
2052	elif [[ -f $nfile ]]; then
2053		# new file: count added lines
2054		difflines /dev/null $nfile > $WDIR/$DIR/$F.count
2055	elif [[ -f $ofile ]]; then
2056		# old file: count deleted lines
2057		difflines $ofile /dev/null > $WDIR/$DIR/$F.count
2058	fi
2059
2060	#
2061	# Now we generate the postscript for this file.  We generate diffs
2062	# only in the event that there is delta, or the file is new (it seems
2063	# tree-killing to print out the contents of deleted files).
2064	#
2065	if [[ -f $nfile ]]; then
2066		ocr=$ofile
2067		[[ ! -f $ofile ]] && ocr=/dev/null
2068
2069		if [[ -z $mv_but_nodiff ]]; then
2070			textcomm=`getcomments text $P $PP`
2071			if [[ -x $CODEREVIEW ]]; then
2072				$CODEREVIEW -y "$textcomm" \
2073				    -e $ocr $nfile \
2074				    > /tmp/$$.psfile 2>/dev/null &&
2075				    cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
2076				if [[ $? -eq 0 ]]; then
2077					print " ps\c"
2078				else
2079					print " ps[fail]\c"
2080				fi
2081			fi
2082		fi
2083	fi
2084
2085	if [[ -f $ofile && -z $mv_but_nodiff ]]; then
2086		source_to_html Old $P < $ofile > $WDIR/$DIR/$F-.html
2087		print " old\c"
2088	fi
2089
2090	if [[ -f $nfile ]]; then
2091		source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
2092		print " new\c"
2093	fi
2094
2095	print
2096done
2097
2098frame_nav_js > $WDIR/ancnav.js
2099frame_navigation > $WDIR/ancnav.html
2100
2101if [[ ! -f $WDIR/$WNAME.ps ]]; then
2102	print " Generating PDF: Skipped: no output available"
2103elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
2104	print " Generating PDF: \c"
2105	fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
2106	rm -f $WDIR/$WNAME.ps
2107	print "Done."
2108else
2109	print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
2110fi
2111
2112# Now build the index.html file that contains
2113# links to the source files and their diffs.
2114
2115cd $CWS
2116
2117# Save total changed lines for Code Inspection.
2118print "$TOTL" > $WDIR/TotalChangedLines
2119
2120print "     index.html: \c"
2121INDEXFILE=$WDIR/index.html
2122exec 3<&1			# duplicate stdout to FD3.
2123exec 1<&-			# Close stdout.
2124exec > $INDEXFILE		# Open stdout to index file.
2125
2126print "$HTML<head>$STDHEAD"
2127print "<title>$WNAME</title>"
2128print "</head>"
2129print "<body id=\"SUNWwebrev\">"
2130print "<div class=\"summary\">"
2131print "<h2>Code Review for $WNAME</h2>"
2132
2133print "<table>"
2134
2135#
2136# Figure out the username and gcos name.  To maintain compatibility
2137# with passwd(4), we must support '&' substitutions.
2138#
2139username=`id | cut -d '(' -f 2 | cut -d ')' -f 1`
2140realname=`getent passwd $username | cut -d':' -f 5`
2141userupper=`$PERL -e "print ucfirst $username"`
2142realname=`print $realname | sed s/\&/$userupper/`
2143date="on `date`"
2144
2145if [[ -n "$username" && -n "$realname" ]]; then
2146	print "<tr><th>Prepared by:</th>"
2147	print "<td>$realname ($username) $date</td></tr>"
2148elif [[ -n "$username" ]]; then
2149	print "<tr><th>Prepared by:</th><td>$username $date</td></tr>"
2150fi
2151
2152print "<tr><th>Workspace:</th><td>$CWS</td></tr>"
2153print "<tr><th>Compare against:</th><td>"
2154if [[ -n $parent_webrev ]]; then
2155	print "webrev at $parent_webrev"
2156else
2157	print "$PWS"
2158fi
2159print "</td></tr>"
2160print "<tr><th>Summary of changes:</th><td>"
2161printCI $TOTL $TINS $TDEL $TMOD $TUNC
2162print "</td></tr>"
2163
2164if [[ -f $WDIR/$WNAME.patch ]]; then
2165	print "<tr><th>Patch of changes:</th><td>"
2166	print "<a href=\"$WNAME.patch\">$WNAME.patch</a></td></tr>"
2167fi
2168if [[ -f $WDIR/$WNAME.pdf ]]; then
2169	print "<tr><th>Printable review:</th><td>"
2170	print "<a href=\"$WNAME.pdf\">$WNAME.pdf</a></td></tr>"
2171fi
2172
2173if [[ -n "$iflag" ]]; then
2174	print "<tr><th>Author comments:</th><td><div>"
2175	cat /tmp/$$.include
2176	print "</div></td></tr>"
2177fi
2178print "</table>"
2179print "</div>"
2180
2181
2182#
2183# Second pass through the files: generate the rest of the index file
2184#
2185cat $FLIST | while read LINE
2186do
2187	set - $LINE
2188	P=$1
2189
2190	if [[ $# == 2 ]]; then
2191		PP=$2
2192		oldname=" <i>(was $PP)</i>"
2193
2194	else
2195		PP=$P
2196		oldname=""
2197	fi
2198
2199	DIR=${P%/*}
2200	if [[ $DIR == $P ]]; then
2201		DIR="."   # File at root of workspace
2202	fi
2203
2204	# Avoid processing the same file twice.
2205	# It's possible for renamed files to
2206	# appear twice in the file list
2207
2208	F=$WDIR/$P
2209
2210	print "<p>"
2211
2212	# If there's a diffs file, make diffs links
2213
2214	if [[ -f $F.cdiff.html ]]; then
2215		print "<a href=\"$P.cdiff.html\">Cdiffs</a>"
2216		print "<a href=\"$P.udiff.html\">Udiffs</a>"
2217
2218		if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
2219			print "<a href=\"$P.wdiff.html\">Wdiffs</a>"
2220		fi
2221
2222		print "<a href=\"$P.sdiff.html\">Sdiffs</a>"
2223
2224		print "<a href=\"$P.frames.html\">Frames</a>"
2225	else
2226		print " ------ ------ ------"
2227
2228		if [[ -x $WDIFF ]]; then
2229			print " ------"
2230		fi
2231
2232		print " ------"
2233	fi
2234
2235	# If there's an old file, make the link
2236
2237	if [[ -f $F-.html ]]; then
2238		print "<a href=\"$P-.html\">Old</a>"
2239	else
2240		print " ---"
2241	fi
2242
2243	# If there's an new file, make the link
2244
2245	if [[ -f $F.html ]]; then
2246		print "<a href=\"$P.html\">New</a>"
2247	else
2248		print " ---"
2249	fi
2250
2251	if [[ -f $F.patch ]]; then
2252		print "<a href=\"$P.patch\">Patch</a>"
2253	else
2254		print " -----"
2255	fi
2256
2257	if [[ -f $WDIR/raw_files/new/$P ]]; then
2258		print "<a href=\"raw_files/new/$P\">Raw</a>"
2259	else
2260		print " ---"
2261	fi
2262
2263	print "<b>$P</b> $oldname"
2264
2265	#
2266	# Check for usr/closed
2267	#
2268	if [ ! -z "$Oflag" ]; then
2269		if [[ $P == usr/closed/* ]]; then
2270			print "&nbsp;&nbsp;<i>Closed source: omitted from" \
2271			    "this review</i>"
2272		fi
2273	fi
2274
2275	print "</p>"
2276	# Insert delta comments
2277
2278	print "<blockquote><pre>"
2279	getcomments html $P $PP
2280	print "</pre>"
2281
2282	# Add additional comments comment
2283
2284	print "<!-- Add comments to explain changes in $P here -->"
2285
2286	# Add count of changes.
2287
2288	if [[ -f $F.count ]]; then
2289	    cat $F.count
2290	    rm $F.count
2291	fi
2292	print "</blockquote>"
2293done
2294
2295print
2296print
2297print "<hr />"
2298print "<p style=\"font-size: small\">"
2299print "This code review page was prepared using <b>$0</b>"
2300print "(vers $WEBREV_UPDATED)."
2301print "Webrev is maintained by the <a href=\"http://www.opensolaris.org\">"
2302print "OpenSolaris</a> project.  The latest version may be obtained"
2303print "<a href=\"http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
2304print "</body>"
2305print "</html>"
2306
2307exec 1<&-			# Close FD 1.
2308exec 1<&3			# dup FD 3 to restore stdout.
2309exec 3<&-			# close FD 3.
2310
2311print "Done."
2312