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