xref: /illumos-gate/usr/src/tools/scripts/webrev.sh (revision 499fd601)
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/* or deleted_files/usr/closed/*
1890	#
1891	if [[ -n "$Oflag" ]]; then
1892		pclosed=${P##usr/closed/}
1893		pdeleted=${P##deleted_files/usr/closed/}
1894		if [[ "$pclosed" != "$P" || "$pdeleted" != "$P" ]]; then
1895			print "*** Omitting closed source for OpenSolaris" \
1896			    "mode review"
1897			continue
1898		fi
1899	fi
1900
1901	#
1902	# We stash old and new files into parallel directories in /tmp
1903	# and do our diffs there.  This makes it possible to generate
1904	# clean looking diffs which don't have absolute paths present.
1905	#
1906	olddir=$WDIR/raw_files/old
1907	newdir=$WDIR/raw_files/new
1908	mkdir -p $olddir
1909	mkdir -p $newdir
1910	mkdir -p $olddir/$PDIR
1911	mkdir -p $newdir/$DIR
1912
1913	if [[ $SCM_MODE == "teamware" ]]; then
1914		# If the child's version doesn't exist then
1915		# get a readonly copy.
1916
1917		if [[ ! -f $F && -f SCCS/s.$F ]]; then
1918			sccs get -s $F
1919		fi
1920
1921		#
1922		# Snag new version of file.
1923		#
1924		rm -f $newdir/$DIR/$F
1925		cp $F $newdir/$DIR/$F
1926
1927		#
1928		# Get the parent's version of the file. First see whether the
1929		# child's version is checked out and get the parent's version
1930		# with keywords expanded or unexpanded as appropriate.
1931		#
1932		if [ -f "$PWS/$PDIR/SCCS/s.$PF" -o \
1933		    -f "$PWS/$PDIR/SCCS/p.$PF" ]; then
1934			rm -f $olddir/$PDIR/$PF
1935			if [ -f "SCCS/p.$F" ]; then
1936				sccs get -s -p -k $PWS/$PDIR/$PF \
1937				    > $olddir/$PDIR/$PF
1938			else
1939				sccs get -s -p    $PWS/$PDIR/$PF \
1940				    > $olddir/$PDIR/$PF
1941			fi
1942		else
1943			if [[ -f $PWS/$PDIR/$PF ]]; then
1944				# Parent is not a real workspace, but just a raw
1945				# directory tree - use the file that's there as
1946				# the old file.
1947
1948				rm -f $olddir/$DIR/$F
1949				cp $PWS/$PDIR/$PF $olddir/$DIR/$F
1950			fi
1951		fi
1952	fi
1953
1954	if [[ ! -f $F && ! -f $olddir/$DIR/$F ]]; then
1955		print "*** Error: file not in parent or child"
1956		continue
1957	fi
1958
1959	cd $WDIR/raw_files
1960	ofile=old/$PDIR/$PF
1961	nfile=new/$DIR/$F
1962
1963	mv_but_nodiff=
1964	cmp $ofile $nfile > /dev/null 2>&1
1965	if [[ $? == 0 && $rename == 1 ]]; then
1966		mv_but_nodiff=1
1967	fi
1968
1969	#
1970	# If we have old and new versions of the file then run the appropriate
1971	# diffs.  This is complicated by a couple of factors:
1972	#
1973	#	- renames must be handled specially: we emit a 'remove'
1974	#	  diff and an 'add' diff
1975	#	- new files and deleted files must be handled specially
1976	#	- Solaris patch(1m) can't cope with file creation
1977	#	  (and hence renames) as of this writing.
1978	#       - To make matters worse, gnu patch doesn't interpret the
1979	#	  output of Solaris diff properly when it comes to
1980	#	  adds and deletes.  We need to do some "cleansing"
1981	#         transformations:
1982	# 	    [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
1983	#	    [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
1984	#
1985	cleanse_rmfile="sed 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
1986	cleanse_newfile="sed 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
1987
1988	rm -f $WDIR/$DIR/$F.patch
1989	if [[ -z $rename ]]; then
1990		if [ ! -f "$ofile" ]; then
1991			diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
1992			    > $WDIR/$DIR/$F.patch
1993		elif [ ! -f "$nfile" ]; then
1994			diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
1995			    > $WDIR/$DIR/$F.patch
1996		else
1997			diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
1998		fi
1999	else
2000		diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
2001		    > $WDIR/$DIR/$F.patch
2002
2003		diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
2004		    >> $WDIR/$DIR/$F.patch
2005
2006	fi
2007
2008	#
2009	# Tack the patch we just made onto the accumulated patch for the
2010	# whole wad.
2011	#
2012	cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
2013
2014	print " patch\c"
2015
2016	if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
2017
2018		${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
2019		diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
2020		    > $WDIR/$DIR/$F.cdiff.html
2021		print " cdiffs\c"
2022
2023		${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
2024		diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
2025		    > $WDIR/$DIR/$F.udiff.html
2026
2027		print " udiffs\c"
2028
2029		if [[ -x $WDIFF ]]; then
2030			$WDIFF -c "$COMM" \
2031			    -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
2032			    $WDIR/$DIR/$F.wdiff.html 2>/dev/null
2033			if [[ $? -eq 0 ]]; then
2034				print " wdiffs\c"
2035			else
2036				print " wdiffs[fail]\c"
2037			fi
2038		fi
2039
2040		sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
2041		    > $WDIR/$DIR/$F.sdiff.html
2042		print " sdiffs\c"
2043
2044		print " frames\c"
2045
2046		rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
2047
2048		difflines $ofile $nfile > $WDIR/$DIR/$F.count
2049
2050	elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
2051		# renamed file: may also have differences
2052		difflines $ofile $nfile > $WDIR/$DIR/$F.count
2053	elif [[ -f $nfile ]]; then
2054		# new file: count added lines
2055		difflines /dev/null $nfile > $WDIR/$DIR/$F.count
2056	elif [[ -f $ofile ]]; then
2057		# old file: count deleted lines
2058		difflines $ofile /dev/null > $WDIR/$DIR/$F.count
2059	fi
2060
2061	#
2062	# Now we generate the postscript for this file.  We generate diffs
2063	# only in the event that there is delta, or the file is new (it seems
2064	# tree-killing to print out the contents of deleted files).
2065	#
2066	if [[ -f $nfile ]]; then
2067		ocr=$ofile
2068		[[ ! -f $ofile ]] && ocr=/dev/null
2069
2070		if [[ -z $mv_but_nodiff ]]; then
2071			textcomm=`getcomments text $P $PP`
2072			if [[ -x $CODEREVIEW ]]; then
2073				$CODEREVIEW -y "$textcomm" \
2074				    -e $ocr $nfile \
2075				    > /tmp/$$.psfile 2>/dev/null &&
2076				    cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
2077				if [[ $? -eq 0 ]]; then
2078					print " ps\c"
2079				else
2080					print " ps[fail]\c"
2081				fi
2082			fi
2083		fi
2084	fi
2085
2086	if [[ -f $ofile && -z $mv_but_nodiff ]]; then
2087		source_to_html Old $P < $ofile > $WDIR/$DIR/$F-.html
2088		print " old\c"
2089	fi
2090
2091	if [[ -f $nfile ]]; then
2092		source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
2093		print " new\c"
2094	fi
2095
2096	print
2097done
2098
2099frame_nav_js > $WDIR/ancnav.js
2100frame_navigation > $WDIR/ancnav.html
2101
2102if [[ ! -f $WDIR/$WNAME.ps ]]; then
2103	print " Generating PDF: Skipped: no output available"
2104elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
2105	print " Generating PDF: \c"
2106	fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
2107	rm -f $WDIR/$WNAME.ps
2108	print "Done."
2109else
2110	print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
2111fi
2112
2113# If we're in OpenSolaris mode and there's a closed dir under $WDIR,
2114# delete it - prevent accidental publishing of closed source
2115
2116if [[ -n "$Oflag" ]]; then
2117	/usr/bin/find $WDIR -type d -name closed -exec /bin/rm -rf {} \;
2118fi
2119
2120# Now build the index.html file that contains
2121# links to the source files and their diffs.
2122
2123cd $CWS
2124
2125# Save total changed lines for Code Inspection.
2126print "$TOTL" > $WDIR/TotalChangedLines
2127
2128print "     index.html: \c"
2129INDEXFILE=$WDIR/index.html
2130exec 3<&1			# duplicate stdout to FD3.
2131exec 1<&-			# Close stdout.
2132exec > $INDEXFILE		# Open stdout to index file.
2133
2134print "$HTML<head>$STDHEAD"
2135print "<title>$WNAME</title>"
2136print "</head>"
2137print "<body id=\"SUNWwebrev\">"
2138print "<div class=\"summary\">"
2139print "<h2>Code Review for $WNAME</h2>"
2140
2141print "<table>"
2142
2143#
2144# Figure out the username and gcos name.  To maintain compatibility
2145# with passwd(4), we must support '&' substitutions.
2146#
2147username=`id | cut -d '(' -f 2 | cut -d ')' -f 1`
2148realname=`getent passwd $username | cut -d':' -f 5`
2149userupper=`$PERL -e "print ucfirst $username"`
2150realname=`print $realname | sed s/\&/$userupper/`
2151date="on `date`"
2152
2153if [[ -n "$username" && -n "$realname" ]]; then
2154	print "<tr><th>Prepared by:</th>"
2155	print "<td>$realname ($username) $date</td></tr>"
2156elif [[ -n "$username" ]]; then
2157	print "<tr><th>Prepared by:</th><td>$username $date</td></tr>"
2158fi
2159
2160print "<tr><th>Workspace:</th><td>$CWS</td></tr>"
2161print "<tr><th>Compare against:</th><td>"
2162if [[ -n $parent_webrev ]]; then
2163	print "webrev at $parent_webrev"
2164else
2165	print "$PWS"
2166fi
2167print "</td></tr>"
2168print "<tr><th>Summary of changes:</th><td>"
2169printCI $TOTL $TINS $TDEL $TMOD $TUNC
2170print "</td></tr>"
2171
2172if [[ -f $WDIR/$WNAME.patch ]]; then
2173	print "<tr><th>Patch of changes:</th><td>"
2174	print "<a href=\"$WNAME.patch\">$WNAME.patch</a></td></tr>"
2175fi
2176if [[ -f $WDIR/$WNAME.pdf ]]; then
2177	print "<tr><th>Printable review:</th><td>"
2178	print "<a href=\"$WNAME.pdf\">$WNAME.pdf</a></td></tr>"
2179fi
2180
2181if [[ -n "$iflag" ]]; then
2182	print "<tr><th>Author comments:</th><td><div>"
2183	cat /tmp/$$.include
2184	print "</div></td></tr>"
2185fi
2186print "</table>"
2187print "</div>"
2188
2189
2190#
2191# Second pass through the files: generate the rest of the index file
2192#
2193cat $FLIST | while read LINE
2194do
2195	set - $LINE
2196	P=$1
2197
2198	if [[ $# == 2 ]]; then
2199		PP=$2
2200		oldname=" <i>(was $PP)</i>"
2201
2202	else
2203		PP=$P
2204		oldname=""
2205	fi
2206
2207	DIR=${P%/*}
2208	if [[ $DIR == $P ]]; then
2209		DIR="."   # File at root of workspace
2210	fi
2211
2212	# Avoid processing the same file twice.
2213	# It's possible for renamed files to
2214	# appear twice in the file list
2215
2216	F=$WDIR/$P
2217
2218	print "<p>"
2219
2220	# If there's a diffs file, make diffs links
2221
2222	if [[ -f $F.cdiff.html ]]; then
2223		print "<a href=\"$P.cdiff.html\">Cdiffs</a>"
2224		print "<a href=\"$P.udiff.html\">Udiffs</a>"
2225
2226		if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
2227			print "<a href=\"$P.wdiff.html\">Wdiffs</a>"
2228		fi
2229
2230		print "<a href=\"$P.sdiff.html\">Sdiffs</a>"
2231
2232		print "<a href=\"$P.frames.html\">Frames</a>"
2233	else
2234		print " ------ ------ ------"
2235
2236		if [[ -x $WDIFF ]]; then
2237			print " ------"
2238		fi
2239
2240		print " ------"
2241	fi
2242
2243	# If there's an old file, make the link
2244
2245	if [[ -f $F-.html ]]; then
2246		print "<a href=\"$P-.html\">Old</a>"
2247	else
2248		print " ---"
2249	fi
2250
2251	# If there's an new file, make the link
2252
2253	if [[ -f $F.html ]]; then
2254		print "<a href=\"$P.html\">New</a>"
2255	else
2256		print " ---"
2257	fi
2258
2259	if [[ -f $F.patch ]]; then
2260		print "<a href=\"$P.patch\">Patch</a>"
2261	else
2262		print " -----"
2263	fi
2264
2265	if [[ -f $WDIR/raw_files/new/$P ]]; then
2266		print "<a href=\"raw_files/new/$P\">Raw</a>"
2267	else
2268		print " ---"
2269	fi
2270
2271	print "<b>$P</b> $oldname"
2272
2273	#
2274	# Check for usr/closed and deleted_files/usr/closed
2275	#
2276	if [ ! -z "$Oflag" ]; then
2277		if [[ $P == usr/closed/* || \
2278		    $P == deleted_files/usr/closed/* ]]; then
2279			print "&nbsp;&nbsp;<i>Closed source: omitted from" \
2280			    "this review</i>"
2281		fi
2282	fi
2283
2284	print "</p>"
2285	# Insert delta comments
2286
2287	print "<blockquote><pre>"
2288	getcomments html $P $PP
2289	print "</pre>"
2290
2291	# Add additional comments comment
2292
2293	print "<!-- Add comments to explain changes in $P here -->"
2294
2295	# Add count of changes.
2296
2297	if [[ -f $F.count ]]; then
2298	    cat $F.count
2299	    rm $F.count
2300	fi
2301	print "</blockquote>"
2302done
2303
2304print
2305print
2306print "<hr />"
2307print "<p style=\"font-size: small\">"
2308print "This code review page was prepared using <b>$0</b>"
2309print "(vers $WEBREV_UPDATED)."
2310print "Webrev is maintained by the <a href=\"http://www.opensolaris.org\">"
2311print "OpenSolaris</a> project.  The latest version may be obtained"
2312print "<a href=\"http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
2313print "</body>"
2314print "</html>"
2315
2316exec 1<&-			# Close FD 1.
2317exec 1<&3			# dup FD 3 to restore stdout.
2318exec 3<&-			# close FD 3.
2319
2320print "Done."
2321