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