xref: /illumos-gate/usr/src/tools/scripts/webrev.sh (revision 411fa6a8116a35ace2d92381e3d827b6b9938df5)
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 2006 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
1422#
1423# Usage message.
1424#
1425function usage
1426{
1427	print 'Usage:\twebrev [common-options]
1428	webrev [common-options] ( <file> | - )
1429	webrev [common-options] -w <wx file>
1430	webrev [common-options] -l [arguments to 'putback']
1431
1432Options:
1433	-O: Print bugids/arc cases suitable for OpenSolaris.
1434	-i <filename>: Include <filename> in the index.html file.
1435	-o <outdir>: Output webrev to specified directory.
1436	-p <compare-against>: Use specified parent wkspc or basis for comparison
1437	-w <wxfile>: Use specified wx active file.
1438
1439Environment:
1440	WDIR: Control the output directory.
1441	WEBREV_BUGURL: Control the URL prefix for bugids.
1442	WEBREV_SACURL: Control the URL prefix for ARC cases.
1443
1444SCM Environment:
1445	Teamware: CODEMGR_WS: Workspace location.
1446	Teamware: CODEMGR_PARENT: Parent workspace location.
1447'
1448
1449	exit 2
1450}
1451
1452#
1453#
1454# Main program starts here
1455#
1456#
1457
1458trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
1459
1460set +o noclobber
1461
1462if [[ -z $WDIFF ]]; then
1463	WDIFF=`whence wdiff`
1464	if [[ -z $WDIFF ]]; then
1465		if [[ -x /opt/onbld/bin/wdiff ]]; then
1466			WDIFF=/opt/onbld/bin/wdiff
1467		elif [[ -x /ws/onnv-gate/public/bin/wdiff ]]; then
1468			WDIFF=/ws/onnv-gate/public/bin/wdiff
1469		else
1470			print -u2 "Warning: wdiff not found!"
1471		fi
1472	fi
1473fi
1474
1475# Declare global total counters.
1476integer TOTL TINS TDEL TMOD TUNC
1477
1478flist_autodetect=
1479iflag=
1480oflag=
1481pflag=
1482lflag=
1483wflag=
1484Oflag=
1485while getopts "i:o:p:lwO" opt
1486do
1487	case $opt in
1488	i)	iflag=1
1489		INCLUDE_FILE=$OPTARG;;
1490
1491	o)	oflag=1
1492		WDIR=$OPTARG;;
1493
1494	p)	pflag=1
1495		codemgr_parent=$OPTARG;;
1496
1497	#
1498	# If -l has been specified, we need to abort further options
1499	# processing, because subsequent arguments are going to be
1500	# arguments to 'putback -n'.
1501	#
1502	l)	lflag=1
1503		break;;
1504
1505	w)	wflag=1;;
1506
1507	O)	Oflag=1;;
1508
1509	?)	usage;;
1510	esac
1511done
1512
1513FLIST=/tmp/$$.flist
1514
1515if [[ -n $wflag && -n $lflag ]]; then
1516	usage
1517fi
1518
1519#
1520# If this manually set as the parent, and it appears to be an earlier webrev,
1521# then note that fact and set the parent to the raw_files/new subdirectory.
1522#
1523if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
1524	parent_webrev="$codemgr_parent"
1525	codemgr_parent="$codemgr_parent/raw_files/new"
1526fi
1527
1528if [[ -z $wflag && -z $lflag ]]; then
1529	shift $(($OPTIND - 1))
1530
1531	if [[ $1 == "-" ]]; then
1532		cat > $FLIST
1533	elif [[ -n $1 ]]; then
1534		if [[ -r $1 ]]; then
1535			print -u2 "$1: no such file or not readable"
1536			usage
1537		fi
1538		cat $1 > $FLIST
1539	else
1540		flist_autodetect=1
1541	fi
1542fi
1543
1544#
1545# Before we go on to further consider -l and -w, work out which SCM we think
1546# is in use.
1547#
1548SCM_MODE=`detect_scm $FLIST`
1549if [[ $SCM_MODE == "unknown" ]]; then
1550	print -u2 "Unable to determine SCM type currently in use."
1551	print -u2 "For teamware: webrev looks for \$CODEMGR_WS either in"
1552	print -u2 "              the environment or in the file list."
1553	exit 1
1554fi
1555
1556print -u2 "   SCM detected: $SCM_MODE"
1557
1558if [[ -n $lflag ]]; then
1559	#
1560	# If the -l flag is given instead of the name of a file list,
1561	# then generate the file list by extracting file names from a
1562	# putback -n.
1563	#
1564	shift $(($OPTIND - 1))
1565	flist_from_teamware "$*"
1566	flist_done=1
1567	shift $#
1568
1569elif [[ -n $wflag ]]; then
1570	#
1571	# If the -w is given then assume the file list is in Bonwick's "wx"
1572	# command format, i.e.  pathname lines alternating with SCCS comment
1573	# lines with blank lines as separators.  Use the SCCS comments later
1574	# in building the index.html file.
1575	#
1576	shift $(($OPTIND - 1))
1577	wxfile=$1
1578	if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
1579		if [[ -r $CODEMGR_WS/wx/active ]]; then
1580			wxfile=$CODEMGR_WS/wx/active
1581		fi
1582	fi
1583
1584	[[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
1585	    "be auto-detected (check \$CODEMGR_WS)" && exit 1
1586
1587	print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
1588	flist_from_wx $wxfile
1589	flist_done=1
1590	if [[ -n "$*" ]]; then
1591		shift
1592	fi
1593fi
1594
1595if [[ $# -gt 0 ]]; then
1596	print -u2 "Warning: unused arguments: $*"
1597fi
1598
1599if [[ $SCM_MODE == "teamware" ]]; then
1600	#
1601	# Parent (internally $codemgr_parent) and workspace ($codemgr_ws) can
1602	# be set in a number of ways, in decreasing precedence:
1603	#
1604	#      1) on the command line (only for the parent)
1605	#      2) in the user environment
1606	#      3) in the flist
1607	#      4) automatically based on the workspace (only for the parent)
1608	#
1609
1610	#
1611	# Here is case (2): the user environment
1612	#
1613	[[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
1614	if [[ -n $codemgr_ws && ! -d $codemgr_ws ]]; then
1615		print -u2 "$codemgr_ws: no such workspace"
1616		exit 1
1617	fi
1618
1619	[[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
1620	    codemgr_parent=$CODEMGR_PARENT
1621	if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then
1622		print -u2 "$codemgr_parent: no such directory"
1623		exit 1
1624	fi
1625
1626	#
1627	# If we're in auto-detect mode and we haven't already gotten the file
1628	# list, then see if we can get it by probing for wx.
1629	#
1630	if [[ -z $flist_done && -n $flist_autodetect && -n $codemgr_ws ]]; then
1631		if [[ -z $WX ]]; then
1632			WX=`whence wx`
1633			if [[ -z $WX ]]; then
1634				if [[ -x /opt/onbld/bin/wx ]]; then
1635					WDIFF=/opt/onbld/bin/wx
1636				elif [[ -x /ws/onnv-gate/public/bin/wx ]]; then
1637					WDIFF=/ws/onnv-gate/public/bin/wx
1638				else
1639					print -u2 "Warning: wx not found!"
1640				fi
1641			fi
1642		fi
1643
1644		#
1645		# We need to use wx list -w so that we get renamed files, etc.
1646		# but only if a wx active file exists-- otherwise wx will
1647		# hang asking us to initialize our wx information.
1648		#
1649		if [[ -n $WX && -f $codemgr_ws/wx/active ]]; then
1650			print -u2 " File list from: 'wx list -w' ... \c"
1651			$WX list -w > $FLIST
1652			$WX comments > /tmp/$$.wx_comments
1653			wxfile=/tmp/$$.wx_comments
1654			print -u2 "done"
1655			flist_done=1
1656		fi
1657	fi
1658
1659	#
1660	# If by hook or by crook we've gotten a file list by now (perhaps
1661	# from the command line), eval it to extract environment variables from
1662	# it: This is step (3).
1663	#
1664	env_from_flist
1665
1666	#
1667	# Continuing step (3): If we still have no file list, we'll try to get
1668	# it from teamware.
1669	#
1670	if [[ -z $flist_done ]]; then
1671		flist_from_teamware
1672		env_from_flist
1673	fi
1674
1675	#
1676	# Observe true directory name of CODEMGR_WS, as used later in
1677	# webrev title.
1678	#
1679	codemgr_ws=$(cd $codemgr_ws;print $PWD)
1680
1681	#
1682	# (4) If we still don't have a value for codemgr_parent, get it
1683	# from workspace.
1684	#
1685	[[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent`
1686	if [[ ! -d $codemgr_parent ]]; then
1687		print -u2 "$CODEMGR_PARENT: no such parent workspace"
1688		exit 1
1689	fi
1690
1691	#
1692	# Reset CODEMGR_WS to make sure teamware commands are happy.
1693	#
1694	CODEMGR_WS=$codemgr_ws
1695	CWS=$codemgr_ws
1696	PWS=$codemgr_parent
1697fi
1698
1699#
1700# If the user didn't specify a -i option, check to see if there is a
1701# webrev-info file in the workspace directory.
1702#
1703if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
1704	iflag=1
1705	INCLUDE_FILE="$CWS/webrev-info"
1706fi
1707
1708if [[ -n $iflag ]]; then
1709	if [[ ! -r $INCLUDE_FILE ]]; then
1710		print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
1711		    "not readable."
1712		exit 1
1713	else
1714		#
1715		# $INCLUDE_FILE may be a relative path, and the script alters
1716		# PWD, so we just stash a copy in /tmp.
1717		#
1718		cp $INCLUDE_FILE /tmp/$$.include
1719	fi
1720fi
1721
1722#
1723# Output directory.
1724#
1725WDIR=${WDIR:-$CWS/webrev}
1726
1727#
1728# Name of the webrev, derived from the workspace name; in the
1729# future this could potentially be an option.
1730#
1731WNAME=${CWS##*/}
1732
1733if [ ${WDIR%%/*} ]; then
1734	WDIR=$PWD/$WDIR
1735fi
1736
1737if [[ ! -d $WDIR ]]; then
1738	mkdir -p $WDIR
1739	[[ $? != 0 ]] && exit 1
1740fi
1741
1742#
1743# Summarize what we're going to do.
1744#
1745print "      Workspace: $CWS"
1746if [[ -n $parent_webrev ]]; then
1747	print "Compare against: webrev at $parent_webrev"
1748else
1749	print "Compare against: $PWS"
1750fi
1751
1752[[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
1753print "      Output to: $WDIR"
1754
1755#
1756# Save the file list in the webrev dir
1757#
1758[[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
1759
1760#
1761#    Bug IDs will be replaced by a URL.  Order of precedence
1762#    is: default location, $WEBREV_BUGURL, the -O flag.
1763#
1764BUGURL='http://monaco.sfbay.sun.com/detail.jsp?cr='
1765[[ -n $WEBREV_BUGURL ]] && BUGURL="$WEBREV_BUGURL"
1766[[ -n "$Oflag" ]] && \
1767    BUGURL='http://bugs.opensolaris.org/bugdatabase/view_bug.do?bug_id='
1768
1769#
1770#    Likewise, ARC cases will be replaced by a URL.  Order of precedence
1771#    is: default, $WEBREV_SACURL, the -O flag.
1772#
1773#    Note that -O also triggers different substitution behavior for
1774#    SACURL.  See sac2url().
1775#
1776SACURL='http://sac.eng.sun.com'
1777[[ -n $WEBREV_SACURL ]] && SACURL="$WEBREV_SACURL"
1778[[ -n $Oflag ]] && \
1779    SACURL='http://www.opensolaris.org/os/community/arc/caselog'
1780
1781rm -f $WDIR/$WNAME.patch
1782rm -f $WDIR/$WNAME.ps
1783rm -f $WDIR/$WNAME.pdf
1784
1785touch $WDIR/$WNAME.patch
1786touch $WDIR/$WNAME.ps
1787
1788print "   Output Files:"
1789
1790#
1791# Clean up the file list: Remove comments, blank lines and env variables.
1792#
1793sed -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
1794FLIST=/tmp/$$.flist.clean
1795
1796#
1797# First pass through the files: generate the per-file webrev HTML-files.
1798#
1799cat $FLIST | while read LINE
1800do
1801	set - $LINE
1802	P=$1
1803
1804	#
1805	# Normally, each line in the file list is just a pathname of a
1806	# file that has been modified or created in the child.  A file
1807	# that is renamed in the child workspace has two names on the
1808	# line: new name followed by the old name.
1809	#
1810	oldname=""
1811	oldpath=""
1812	rename=
1813	if [[ $# -eq 2 ]]; then
1814		PP=$2			# old filename
1815		oldname=" (was $PP)"
1816		oldpath="$PP"
1817		rename=1
1818        	PDIR=${PP%/*}
1819        	if [[ $PDIR == $PP ]]; then
1820			PDIR="."   # File at root of workspace
1821		fi
1822
1823		PF=${PP##*/}
1824
1825	        DIR=${P%/*}
1826	        if [[ $DIR == $P ]]; then
1827			DIR="."   # File at root of workspace
1828		fi
1829
1830		F=${P##*/}
1831
1832        else
1833	        DIR=${P%/*}
1834	        if [[ "$DIR" == "$P" ]]; then
1835			DIR="."   # File at root of workspace
1836		fi
1837
1838		F=${P##*/}
1839
1840		PP=$P
1841		PDIR=$DIR
1842		PF=$F
1843	fi
1844
1845	COMM=`getcomments html $P $PP`
1846
1847	if [[ ! -d $CWS/$DIR ]]; then
1848		print "  $CWS/$DIR: no such directory"
1849		continue
1850	fi
1851
1852	print "\t$P$oldname\n\t\t\c"
1853
1854	# Make the webrev mirror directory if necessary
1855	mkdir -p $WDIR/$DIR
1856
1857	# cd to the directory so the names are short
1858	cd $CWS/$DIR
1859
1860	#
1861	# If we're in OpenSolaris mode, we enforce a minor policy:
1862	# help to make sure the reviewer doesn't accidentally publish
1863	# source which is in usr/closed/*
1864	#
1865	if [[ -n $Oflag ]]; then
1866		pclosed=${P##usr/closed/}
1867		if [[ $pclosed != $P ]]; then
1868			print "*** Omitting closed source for OpenSolaris" \
1869			    "mode review"
1870			continue
1871		fi
1872	fi
1873
1874	#
1875	# We stash old and new files into parallel directories in /tmp
1876	# and do our diffs there.  This makes it possible to generate
1877	# clean looking diffs which don't have absolute paths present.
1878	#
1879	olddir=$WDIR/raw_files/old
1880	newdir=$WDIR/raw_files/new
1881	mkdir -p $olddir
1882	mkdir -p $newdir
1883	mkdir -p $olddir/$PDIR
1884	mkdir -p $newdir/$DIR
1885
1886	if [[ $SCM_MODE == "teamware" ]]; then
1887		# If the child's version doesn't exist then
1888		# get a readonly copy.
1889
1890		if [[ ! -f $F && -f SCCS/s.$F ]]; then
1891			sccs get -s $F
1892		fi
1893
1894		#
1895		# Snag new version of file.
1896		#
1897		rm -f $newdir/$DIR/$F
1898		cp $F $newdir/$DIR/$F
1899
1900		#
1901		# Get the parent's version of the file. First see whether the
1902		# child's version is checked out and get the parent's version
1903		# with keywords expanded or unexpanded as appropriate.
1904		#
1905		if [ -f $PWS/$PDIR/SCCS/s.$PF -o \
1906		    -f $PWS/$PDIR/SCCS/p.$PF ]; then
1907			rm -f $olddir/$PDIR/$PF
1908			if [ -f SCCS/p.$F ]; then
1909				sccs get -s -p -k $PWS/$PDIR/$PF \
1910				    > $olddir/$PDIR/$PF
1911			else
1912				sccs get -s -p    $PWS/$PDIR/$PF \
1913				    > $olddir/$PDIR/$PF
1914			fi
1915		else
1916			if [[ -f $PWS/$PDIR/$PF ]]; then
1917				# Parent is not a real workspace, but just a raw
1918				# directory tree - use the file that's there as
1919				# the old file.
1920
1921				rm -f $olddir/$DIR/$F
1922				cp $PWS/$PDIR/$PF $olddir/$DIR/$F
1923			fi
1924		fi
1925	fi
1926
1927	if [[ ! -f $F && ! -f $olddir/$DIR/$F ]]; then
1928		print "*** Error: file not in parent or child"
1929		continue
1930	fi
1931
1932	cd $WDIR/raw_files
1933	ofile=old/$PDIR/$PF
1934	nfile=new/$DIR/$F
1935
1936	mv_but_nodiff=
1937	cmp $ofile $nfile > /dev/null 2>&1
1938	if [[ $? == 0 && $rename == 1 ]]; then
1939		mv_but_nodiff=1
1940	fi
1941
1942	#
1943	# If we have old and new versions of the file then run the appropriate
1944	# diffs.  This is complicated by a couple of factors:
1945	#
1946	#	- renames must be handled specially: we emit a 'remove'
1947	#	  diff and an 'add' diff
1948	#	- new files and deleted files must be handled specially
1949	#	- Solaris patch(1m) can't cope with file creation
1950	#	  (and hence renames) as of this writing.
1951	#       - To make matters worse, gnu patch doesn't interpret the
1952	#	  output of Solaris diff properly when it comes to
1953	#	  adds and deletes.  We need to do some "cleansing"
1954	#         transformations:
1955	# 	    [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
1956	#	    [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
1957	#
1958	cleanse_rmfile="sed 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
1959	cleanse_newfile="sed 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
1960
1961	rm -f $WDIR/$DIR/$F.patch
1962	if [[ -z $rename ]]; then
1963		if [ ! -f $ofile ]; then
1964			diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
1965			    > $WDIR/$DIR/$F.patch
1966		elif [ ! -f $nfile ]; then
1967			diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
1968			    > $WDIR/$DIR/$F.patch
1969		else
1970			diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
1971		fi
1972	else
1973		diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
1974		    > $WDIR/$DIR/$F.patch
1975
1976		diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
1977		    >> $WDIR/$DIR/$F.patch
1978
1979	fi
1980
1981	#
1982	# Tack the patch we just made onto the accumulated patch for the
1983	# whole wad.
1984	#
1985	cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
1986
1987	print " patch\c"
1988
1989	if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
1990
1991		${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
1992		diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
1993		    > $WDIR/$DIR/$F.cdiff.html
1994		print " cdiffs\c"
1995
1996		${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
1997		diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
1998		    > $WDIR/$DIR/$F.udiff.html
1999
2000		print " udiffs\c"
2001
2002		if [[ -x $WDIFF ]]; then
2003			$WDIFF -c "$COMM" \
2004			    -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
2005			    $WDIR/$DIR/$F.wdiff.html 2>/dev/null
2006			if [[ $? -eq 0 ]]; then
2007				print " wdiffs\c"
2008			else
2009				print " wdiffs[fail]\c"
2010			fi
2011		fi
2012
2013		sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
2014		    > $WDIR/$DIR/$F.sdiff.html
2015		print " sdiffs\c"
2016
2017		print " frames\c"
2018
2019		rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
2020
2021		difflines $ofile $nfile > $WDIR/$DIR/$F.count
2022
2023	elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
2024		# renamed file: may also have differences
2025		difflines $ofile $nfile > $WDIR/$DIR/$F.count
2026	elif [[ -f $nfile ]]; then
2027		# new file: count added lines
2028		difflines /dev/null $nfile > $WDIR/$DIR/$F.count
2029	elif [[ -f $ofile ]]; then
2030		# old file: count deleted lines
2031		difflines $ofile /dev/null > $WDIR/$DIR/$F.count
2032	fi
2033
2034	#
2035	# Now we generate the postscript for this file.  We generate diffs
2036	# only in the event that there is delta, or the file is new (it seems
2037	# tree-killing to print out the contents of deleted files).
2038	#
2039	if [[ -f $nfile ]]; then
2040		ocr=$ofile
2041		[[ ! -f $ofile ]] && ocr=/dev/null
2042
2043		if [[ -z $mv_but_nodiff ]]; then
2044			textcomm=`getcomments text $P $PP`
2045			codereview -y "$textcomm" \
2046			    -e $ocr $nfile >> $WDIR/$WNAME.ps  # 2>/dev/null
2047			if [[ $? -eq 0 ]]; then
2048				print " ps\c"
2049			else
2050				print " ps[fail]\c"
2051			fi
2052		fi
2053	fi
2054
2055	if [[ -f $ofile && -z $mv_but_nodiff ]]; then
2056		source_to_html Old $P < $ofile > $WDIR/$DIR/$F-.html
2057		print " old\c"
2058	fi
2059
2060	if [[ -f $nfile ]]; then
2061		source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
2062		print " new\c"
2063	fi
2064
2065	print
2066done
2067
2068frame_nav_js > $WDIR/ancnav.js
2069frame_navigation > $WDIR/ancnav.html
2070
2071print " ${B}Generating PDF: ${NB}\c"
2072fix_postscript $WDIR/$WNAME.ps | ps2pdf - > $WDIR/$WNAME.pdf
2073rm -f $WDIR/$WNAME.ps
2074print "Done."
2075
2076# Now build the index.html file that contains
2077# links to the source files and their diffs.
2078
2079cd $CWS
2080
2081# Save total changed lines for Code Inspection.
2082print "$TOTL" > $WDIR/TotalChangedLines
2083
2084print "     index.html: \c"
2085INDEXFILE=$WDIR/index.html
2086exec 3<&1			# duplicate stdout to FD3.
2087exec 1<&-			# Close stdout.
2088exec > $INDEXFILE		# Open stdout to index file.
2089
2090print "$HTML<head>$STDHEAD"
2091print "<title>$WNAME</title>"
2092print "</head>"
2093print "<body id=\"SUNWwebrev\">"
2094print "<div class=\"summary\">"
2095print "<h2>Code Review for $WNAME</h2>"
2096
2097print "<table>"
2098
2099#
2100# Figure out the username and gcos name.  To maintain compatibility
2101# with passwd(4), we must support '&' substitutions.
2102#
2103username=`id | cut -d '(' -f 2 | cut -d ')' -f 1`
2104realname=`getent passwd $username | cut -d':' -f 5`
2105userupper=`perl -e "print ucfirst $username"`
2106realname=`print $realname | sed s/\&/$userupper/`
2107date="on `date`"
2108
2109if [[ -n "$username" && -n "$realname" ]]; then
2110	print "<tr><th>Prepared by:</th>"
2111	print "<td>$realname ($username) $date</td></tr>"
2112elif [[ -n "$username" ]]; then
2113	print "<tr><th>Prepared by:</th><td>$username $date</td></tr>"
2114fi
2115
2116print "<tr><th>Workspace:</th><td>$CWS</td></tr>"
2117print "<tr><th>Compare against:</th><td>"
2118if [[ -n $parent_webrev ]]; then
2119	print "webrev at $parent_webrev"
2120else
2121	print "$PWS"
2122fi
2123print "</td></tr>"
2124print "<tr><th>Summary of changes:</th><td>"
2125printCI $TOTL $TINS $TDEL $TMOD $TUNC
2126print "</td></tr>"
2127
2128if [[ -f $WDIR/$WNAME.patch ]]; then
2129	print "<tr><th>Patch of changes:</th><td>"
2130	print "<a href=\"$WNAME.patch\">$WNAME.patch</a></td></tr>"
2131fi
2132if [[ -f $WDIR/$WNAME.pdf ]]; then
2133	print "<tr><th>Printable review:</th><td>"
2134	print "<a href=\"$WNAME.pdf\">$WNAME.pdf</a></td></tr>"
2135fi
2136
2137if [[ -n "$iflag" ]]; then
2138	print "<tr><th>Author comments:</th><td><div>"
2139	cat /tmp/$$.include
2140	print "</div></td></tr>"
2141fi
2142print "</table>"
2143print "</div>"
2144
2145
2146#
2147# Second pass through the files: generate the rest of the index file
2148#
2149cat $FLIST | while read LINE
2150do
2151	set - $LINE
2152	P=$1
2153
2154	if [[ $# == 2 ]]; then
2155		PP=$2
2156		oldname=" <i>(was $PP)</i>"
2157
2158	else
2159		PP=$P
2160		oldname=""
2161	fi
2162
2163	DIR=${P%/*}
2164	if [[ $DIR == $P ]]; then
2165		DIR="."   # File at root of workspace
2166	fi
2167
2168	# Avoid processing the same file twice.
2169	# It's possible for renamed files to
2170	# appear twice in the file list
2171
2172	F=$WDIR/$P
2173
2174	print "<p>"
2175
2176	# If there's a diffs file, make diffs links
2177
2178	if [[ -f $F.cdiff.html ]]; then
2179		print "<a href=\"$P.cdiff.html\">Cdiffs</a>"
2180		print "<a href=\"$P.udiff.html\">Udiffs</a>"
2181
2182		if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
2183			print "<a href=\"$P.wdiff.html\">Wdiffs</a>"
2184		fi
2185
2186		print "<a href=\"$P.sdiff.html\">Sdiffs</a>"
2187
2188		print "<a href=\"$P.frames.html\">Frames</a>"
2189	else
2190		print " ------ ------ ------"
2191
2192		if [[ -x $WDIFF ]]; then
2193			print " ------"
2194		fi
2195
2196		print " ------"
2197	fi
2198
2199	# If there's an old file, make the link
2200
2201	if [[ -f $F-.html ]]; then
2202		print "<a href=\"$P-.html\">Old</a>"
2203	else
2204		print " ---"
2205	fi
2206
2207	# If there's an new file, make the link
2208
2209	if [[ -f $F.html ]]; then
2210		print "<a href=\"$P.html\">New</a>"
2211	else
2212		print " ---"
2213	fi
2214
2215	if [[ -f $F.patch ]]; then
2216		print "<a href=\"$P.patch\">Patch</a>"
2217	else
2218		print " -----"
2219	fi
2220
2221	if [[ -f $WDIR/raw_files/new/$P ]]; then
2222		print "<a href=\"raw_files/new/$P\">Raw</a>"
2223	else
2224		print " ---"
2225	fi
2226
2227	print "<b>$P</b> $oldname"
2228
2229	#
2230	# Check for usr/closed
2231	#
2232	if [ ! -z "$Oflag" ]; then
2233		if [[ $P == usr/closed/* ]]; then
2234			print "&nbsp;&nbsp;<i>Closed source: omitted from" \
2235			    "this review</i>"
2236		fi
2237	fi
2238
2239	print "</p>"
2240	# Insert delta comments
2241
2242	print "<blockquote><pre>"
2243	getcomments html $P $PP
2244	print "</pre>"
2245
2246	# Add additional comments comment
2247
2248	print "<!-- Add comments to explain changes in $P here -->"
2249
2250	# Add count of changes.
2251
2252	if [[ -f $F.count ]]; then
2253	    cat $F.count
2254	    rm $F.count
2255	fi
2256	print "</blockquote>"
2257done
2258
2259print
2260print
2261print "<hr />"
2262print "<p style=\"font-size: small\">"
2263print "This code review page was prepared using <b>$0</b>"
2264print "(vers $WEBREV_UPDATED)."
2265print "Webrev is maintained by the <a href=\"http://www.opensolaris.org\">"
2266print "OpenSolaris</a> project.  The latest version may be obtained"
2267print "<a href=\"http://cvs.opensolaris.org/source/xref/on/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
2268print "</body>"
2269print "</html>"
2270
2271exec 1<&-			# Close FD 1.
2272exec 1<&3			# dup FD 3 to restore stdout.
2273exec 3<&-			# close FD 3.
2274
2275print "Done."
2276