xref: /illumos-gate/usr/src/tools/scripts/webrev.sh (revision fe3e2633be44d2f5361a7bba26abeb80fcc04fbc)
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	#
1159	# Mercurial support uses a file list in wx format, so this
1160	# will be used there, too
1161	#
1162	if [[ -n $wxfile ]]; then
1163		comments_from_wx $fmt $p
1164	else
1165		if [[ $SCM_MODE == "teamware" ]]; then
1166			comments_from_teamware $fmt $pp $p
1167		fi
1168	fi
1169}
1170
1171#
1172# printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1173#
1174# Print out Code Inspection figures similar to sccs-prt(1) format.
1175#
1176function printCI
1177{
1178	integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1179	typeset str
1180	if (( tot == 1 )); then
1181		str="line"
1182	else
1183		str="lines"
1184	fi
1185	printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1186	    $tot $str $ins $del $mod $unc
1187}
1188
1189
1190#
1191# difflines <oldfile> <newfile>
1192#
1193# Calculate and emit number of added, removed, modified and unchanged lines,
1194# and total lines changed, the sum of added + removed + modified.
1195#
1196function difflines
1197{
1198	integer tot mod del ins unc err
1199	typeset filename
1200
1201	eval $( diff -e $1 $2 | $AWK '
1202	# Change range of lines: N,Nc
1203	/^[0-9]*,[0-9]*c$/ {
1204		n=split(substr($1,1,length($1)-1), counts, ",");
1205		if (n != 2) {
1206		    error=2
1207		    exit;
1208		}
1209		#
1210		# 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1211		# following would be 5 - 3 = 2! Hence +1 for correction.
1212		#
1213		r=(counts[2]-counts[1])+1;
1214
1215		#
1216		# Now count replacement lines: each represents a change instead
1217		# of a delete, so increment c and decrement r.
1218		#
1219		while (getline != /^\.$/) {
1220			c++;
1221			r--;
1222		}
1223		#
1224		# If there were more replacement lines than original lines,
1225		# then r will be negative; in this case there are no deletions,
1226		# but there are r changes that should be counted as adds, and
1227		# since r is negative, subtract it from a and add it to c.
1228		#
1229		if (r < 0) {
1230			a-=r;
1231			c+=r;
1232		}
1233
1234		#
1235		# If there were more original lines than replacement lines, then
1236		# r will be positive; in this case, increment d by that much.
1237		#
1238		if (r > 0) {
1239			d+=r;
1240		}
1241		next;
1242	}
1243
1244	# Change lines: Nc
1245	/^[0-9].*c$/ {
1246		# The first line is a replacement; any more are additions.
1247		if (getline != /^\.$/) {
1248			c++;
1249			while (getline != /^\.$/) a++;
1250		}
1251		next;
1252	}
1253
1254	# Add lines: both Na and N,Na
1255	/^[0-9].*a$/ {
1256		while (getline != /^\.$/) a++;
1257		next;
1258	}
1259
1260	# Delete range of lines: N,Nd
1261	/^[0-9]*,[0-9]*d$/ {
1262		n=split(substr($1,1,length($1)-1), counts, ",");
1263		if (n != 2) {
1264			error=2
1265			exit;
1266		}
1267		#
1268		# 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1269		# following would be 5 - 3 = 2! Hence +1 for correction.
1270		#
1271		r=(counts[2]-counts[1])+1;
1272		d+=r;
1273		next;
1274	}
1275
1276	# Delete line: Nd.   For example 10d says line 10 is deleted.
1277	/^[0-9]*d$/ {d++; next}
1278
1279	# Should not get here!
1280	{
1281		error=1;
1282		exit;
1283	}
1284
1285	# Finish off - print results
1286	END {
1287		printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1288		    (c+d+a), c, d, a, error);
1289	}' )
1290
1291	# End of $AWK, Check to see if any trouble occurred.
1292	if (( $? > 0 || err > 0 )); then
1293		print "Unexpected Error occurred reading" \
1294		    "\`diff -e $1 $2\`: \$?=$?, err=" $err
1295		return
1296	fi
1297
1298	# Accumulate totals
1299	(( TOTL += tot ))
1300	(( TMOD += mod ))
1301	(( TDEL += del ))
1302	(( TINS += ins ))
1303	# Calculate unchanged lines
1304	unc=`wc -l < $1`
1305	if (( unc > 0 )); then
1306		(( unc -= del + mod ))
1307		(( TUNC += unc ))
1308	fi
1309	# print summary
1310	print "<span class=\"lineschanged\">"
1311	printCI $tot $ins $del $mod $unc
1312	print "</span>"
1313}
1314
1315
1316#
1317# flist_from_wx
1318#
1319# Sets up webrev to source its information from a wx-formatted file.
1320# Sets the global 'wxfile' variable.
1321#
1322function flist_from_wx
1323{
1324	typeset argfile=$1
1325	if [[ -n ${argfile%%/*} ]]; then
1326		#
1327		# If the wx file pathname is relative then make it absolute
1328		# because the webrev does a "cd" later on.
1329		#
1330		wxfile=$PWD/$argfile
1331	else
1332		wxfile=$argfile
1333	fi
1334
1335	$AWK '{ c = 1; print;
1336	  while (getline) {
1337		if (NF == 0) { c = -c; continue }
1338		if (c > 0) print
1339	  }
1340	}' $wxfile > $FLIST
1341
1342	print " Done."
1343}
1344
1345#
1346# flist_from_teamware [ <args-to-putback-n> ]
1347#
1348# Generate the file list by extracting file names from a putback -n.  Some
1349# names may come from the "update/create" messages and others from the
1350# "currently checked out" warning.  Renames are detected here too.  Extract
1351# values for CODEMGR_WS and CODEMGR_PARENT from the output of the putback
1352# -n as well, but remove them if they are already defined.
1353#
1354function flist_from_teamware
1355{
1356	if [[ -n $codemgr_parent && -z $parent_webrev ]]; then
1357		if [[ ! -d $codemgr_parent/Codemgr_wsdata ]]; then
1358			print -u2 "parent $codemgr_parent doesn't look like a" \
1359			    "valid teamware workspace"
1360			exit 1
1361		fi
1362		parent_args="-p $codemgr_parent"
1363	fi
1364
1365	print " File list from: 'putback -n $parent_args $*' ... \c"
1366
1367	putback -n $parent_args $* 2>&1 |
1368	    $AWK '
1369		/^update:|^create:/	{print $2}
1370		/^Parent workspace:/	{printf("CODEMGR_PARENT=%s\n",$3)}
1371		/^Child workspace:/	{printf("CODEMGR_WS=%s\n",$3)}
1372		/^The following files are currently checked out/ {p = 1; continue}
1373		NF == 0			{p=0 ; continue}
1374		/^rename/		{old=$3}
1375		$1 == "to:"		{print $2, old}
1376		/^"/			{continue}
1377		p == 1			{print $1}' |
1378	    sort -r -k 1,1 -u | sort > $FLIST
1379
1380	print " Done."
1381}
1382
1383#
1384# Call hg-active to get the active list output in the wx active list format
1385#
1386function hg_active_wxfile
1387{
1388	typeset child=$1
1389	typeset parent=$2
1390
1391	TMPFLIST=/tmp/$$.active
1392	$HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1393	wxfile=$TMPFLIST
1394}
1395
1396#
1397# flist_from_mercurial
1398# Call hg-active to get a wx-style active list, and hand it off to
1399# flist_from_wx
1400#
1401function flist_from_mercurial
1402{
1403	typeset child=$1
1404	typeset parent=$2
1405
1406	print " File list from: hg-active -p $parent ...\c"
1407
1408	if [[ ! -x $HG_ACTIVE ]]; then
1409		print		# Blank line for the \c above
1410		print -u2 "Error: hg-active tool not found.  Exiting"
1411		exit 1
1412	fi
1413	hg_active_wxfile $child $parent
1414
1415	# flist_from_wx prints the Done, so we don't have to.
1416	flist_from_wx $TMPFLIST
1417}
1418
1419#
1420# flist_from_subversion
1421#
1422# Generate the file list by extracting file names from svn status.
1423#
1424function flist_from_subversion
1425{
1426	CWS=$1
1427	OLDPWD=$2
1428
1429	cd $CWS
1430	print -u2 " File list from: svn status ... \c"
1431	svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1432	print -u2 " Done."
1433	cd $OLDPWD
1434}
1435
1436function env_from_flist
1437{
1438	[[ -r $FLIST ]] || return
1439
1440	#
1441	# Use "eval" to set env variables that are listed in the file
1442	# list.  Then copy those into our local versions of those
1443	# variables if they have not been set already.
1444	#
1445	eval `sed -e "s/#.*$//" $FLIST | grep = `
1446
1447	if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1448		codemgr_ws=$CODEMGR_WS
1449		export CODEMGR_WS
1450	fi
1451
1452	#
1453	# Check to see if CODEMGR_PARENT is set in the flist file.
1454	#
1455	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1456		codemgr_parent=$CODEMGR_PARENT
1457		export CODEMGR_PARENT
1458	fi
1459}
1460
1461function look_for_prog
1462{
1463	typeset path
1464	typeset ppath
1465	typeset progname=$1
1466
1467	ppath=$PATH
1468	ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1469	ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin
1470	ppath=$ppath:/opt/onbld/bin/`uname -p`
1471
1472	PATH=$ppath prog=`whence $progname`
1473	if [[ -n $prog ]]; then
1474		print $prog
1475	fi
1476}
1477
1478function get_file_mode
1479{
1480	$PERL -e '
1481		if (@stat = stat($ARGV[0])) {
1482			$mode = $stat[2] & 0777;
1483			printf "%03o\n", $mode;
1484			exit 0;
1485		} else {
1486			exit 1;
1487		}
1488	    ' $1
1489}
1490
1491function build_old_new_teamware
1492{
1493	typeset olddir="$1"
1494	typeset newdir="$2"
1495
1496	# If the child's version doesn't exist then
1497	# get a readonly copy.
1498
1499	if [[ ! -f $CWS/$DIR/$F && -f $CWS/$DIR/SCCS/s.$F ]]; then
1500		$SCCS get -s -p $CWS/$DIR/$F > $CWS/$DIR/$F
1501	fi
1502
1503	# The following two sections propagate file permissions the
1504	# same way SCCS does.  If the file is already under version
1505	# control, always use permissions from the SCCS/s.file.  If
1506	# the file is not under SCCS control, use permissions from the
1507	# working copy.  In all cases, the file copied to the webrev
1508	# is set to read only, and group/other permissions are set to
1509	# match those of the file owner.  This way, even if the file
1510	# is currently checked out, the webrev will display the final
1511	# permissions that would result after check in.
1512
1513	#
1514	# Snag new version of file.
1515	#
1516	rm -f $newdir/$DIR/$F
1517	cp $CWS/$DIR/$F $newdir/$DIR/$F
1518	if [[ -f $CWS/$DIR/SCCS/s.$F ]]; then
1519		chmod `get_file_mode $CWS/$DIR/SCCS/s.$F` \
1520		    $newdir/$DIR/$F
1521	fi
1522	chmod u-w,go=u $newdir/$DIR/$F
1523
1524	#
1525	# Get the parent's version of the file. First see whether the
1526	# child's version is checked out and get the parent's version
1527	# with keywords expanded or unexpanded as appropriate.
1528	#
1529	if [[ -f $PWS/$PDIR/$PF && ! -f $PWS/$PDIR/SCCS/s.$PF && \
1530	    ! -f $PWS/$PDIR/SCCS/p.$PF ]]; then
1531		# Parent is not a real workspace, but just a raw
1532		# directory tree - use the file that's there as
1533		# the old file.
1534
1535		rm -f $olddir/$PDIR/$PF
1536		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1537	else
1538		if [[ -f $PWS/$PDIR/SCCS/s.$PF ]]; then
1539			real_parent=$PWS
1540		else
1541			real_parent=$RWS
1542		fi
1543
1544		rm -f $olddir/$PDIR/$PF
1545
1546		if [[ -f $real_parent/$PDIR/$PF ]]; then
1547			if [ -f $CWS/$DIR/SCCS/p.$F ]; then
1548				$SCCS get -s -p -k $real_parent/$PDIR/$PF > \
1549				    $olddir/$PDIR/$PF
1550			else
1551				$SCCS get -s -p    $real_parent/$PDIR/$PF > \
1552				    $olddir/$PDIR/$PF
1553			fi
1554			chmod `get_file_mode $real_parent/$PDIR/SCCS/s.$PF` \
1555			    $olddir/$PDIR/$PF
1556		fi
1557	fi
1558	if [[ -f $olddir/$PDIR/$PF ]]; then
1559		chmod u-w,go=u $olddir/$PDIR/$PF
1560	fi
1561}
1562
1563function build_old_new_mercurial
1564{
1565	typeset olddir="$1"
1566	typeset newdir="$2"
1567	typeset old_mode=
1568	typeset new_mode=
1569	typeset file
1570
1571	#
1572	# Get old file mode, from the parent revision manifest entry.
1573	# Mercurial only stores a "file is executable" flag, but the
1574	# manifest will display an octal mode "644" or "755".
1575	#
1576	if [[ "$PDIR" == "." ]]; then
1577		file="$PF"
1578	else
1579		file="$PDIR/$PF"
1580	fi
1581	file=`echo $file | sed 's#/#\\\/#g'`
1582	# match the exact filename, and return only the permission digits
1583	old_mode=`sed -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
1584	    < $HG_PARENT_MANIFEST`
1585
1586	#
1587	# Get new file mode, directly from the filesystem.
1588	# Normalize the mode to match Mercurial's behavior.
1589	#
1590	new_mode=`get_file_mode $CWS/$DIR/$F`
1591	if [[ -n "$new_mode" ]]; then
1592		if [[ "$new_mode" = *[1357]* ]]; then
1593			new_mode=755
1594		else
1595			new_mode=644
1596		fi
1597	fi
1598
1599	#
1600	# new version of the file.
1601	#
1602	rm -rf $newdir/$DIR/$F
1603	if [[ -e $CWS/$DIR/$F ]]; then
1604		cp $CWS/$DIR/$F $newdir/$DIR/$F
1605		if [[ -n $new_mode ]]; then
1606			chmod $new_mode $newdir/$DIR/$F
1607		else
1608			# should never happen
1609			print -u2 "ERROR: set mode of $newdir/$DIR/$F"
1610		fi
1611	fi
1612
1613	#
1614	# parent's version of the file
1615	#
1616	# Note that we get this from the last version common to both
1617	# ourselves and the parent.  References are via $CWS since we have no
1618	# guarantee that the parent workspace is reachable via the filesystem.
1619	#
1620	if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
1621		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1622	elif [[ -n $HG_PARENT ]]; then
1623		hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
1624		    $olddir/$PDIR/$PF 2>/dev/null
1625
1626		if [ $? -ne 0 ]; then
1627			rm -f $olddir/$PDIR/$PF
1628		else
1629			if [[ -n $old_mode ]]; then
1630				chmod $old_mode $olddir/$PDIR/$PF
1631			else
1632				# should never happen
1633				print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
1634			fi
1635		fi
1636	fi
1637}
1638
1639function build_old_new_subversion
1640{
1641	typeset olddir="$1"
1642	typeset newdir="$2"
1643
1644	# Snag new version of file.
1645	rm -f $newdir/$DIR/$F
1646	[[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
1647
1648	if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
1649		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1650	else
1651		# Get the parent's version of the file.
1652		svn status $CWS/$DIR/$F | read stat file
1653		if [[ $stat != "A" ]]; then
1654			svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
1655		fi
1656	fi
1657}
1658
1659function build_old_new_unknown
1660{
1661	typeset olddir="$1"
1662	typeset newdir="$2"
1663
1664	#
1665	# Snag new version of file.
1666	#
1667	rm -f $newdir/$DIR/$F
1668	[[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
1669
1670	#
1671	# Snag the parent's version of the file.
1672	#
1673	if [[ -f $PWS/$PDIR/$PF ]]; then
1674		rm -f $olddir/$PDIR/$PF
1675		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1676	fi
1677}
1678
1679function build_old_new
1680{
1681	typeset WDIR=$1
1682	typeset PWS=$2
1683	typeset PDIR=$3
1684	typeset PF=$4
1685	typeset CWS=$5
1686	typeset DIR=$6
1687	typeset F=$7
1688
1689	typeset olddir="$WDIR/raw_files/old"
1690	typeset newdir="$WDIR/raw_files/new"
1691
1692	mkdir -p $olddir/$PDIR
1693	mkdir -p $newdir/$DIR
1694
1695	if [[ $SCM_MODE == "teamware" ]]; then
1696		build_old_new_teamware "$olddir" "$newdir"
1697	elif [[ $SCM_MODE == "mercurial" ]]; then
1698		build_old_new_mercurial "$olddir" "$newdir"
1699	elif [[ $SCM_MODE == "subversion" ]]; then
1700		build_old_new_subversion "$olddir" "$newdir"
1701	elif [[ $SCM_MODE == "unknown" ]]; then
1702		build_old_new_unknown "$olddir" "$newdir"
1703	fi
1704
1705	if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
1706		print "*** Error: file not in parent or child"
1707		return 1
1708	fi
1709	return 0
1710}
1711
1712
1713#
1714# Usage message.
1715#
1716function usage
1717{
1718	print 'Usage:\twebrev [common-options]
1719	webrev [common-options] ( <file> | - )
1720	webrev [common-options] -w <wx file>
1721
1722Options:
1723	-O: Print bugids/arc cases suitable for OpenSolaris.
1724	-i <filename>: Include <filename> in the index.html file.
1725	-o <outdir>: Output webrev to specified directory.
1726	-p <compare-against>: Use specified parent wkspc or basis for comparison
1727	-w <wxfile>: Use specified wx active file.
1728
1729Environment:
1730	WDIR: Control the output directory.
1731	WEBREV_BUGURL: Control the URL prefix for bugids.
1732	WEBREV_SACURL: Control the URL prefix for ARC cases.
1733
1734SCM Specific Options:
1735	TeamWare: webrev [common-options] -l [arguments to 'putback']
1736
1737SCM Environment:
1738	CODEMGR_WS: Workspace location.
1739	CODEMGR_PARENT: Parent workspace location.
1740'
1741
1742	exit 2
1743}
1744
1745#
1746#
1747# Main program starts here
1748#
1749#
1750
1751trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
1752
1753set +o noclobber
1754
1755PATH=$(dirname $(whence $0)):$PATH
1756
1757[[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
1758[[ -z $WX ]] && WX=`look_for_prog wx`
1759[[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
1760[[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
1761[[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
1762[[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
1763[[ -z $PERL ]] && PERL=`look_for_prog perl`
1764[[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
1765[[ -z $AWK ]] && AWK=`look_for_prog nawk`
1766[[ -z $AWK ]] && AWK=`look_for_prog gawk`
1767[[ -z $AWK ]] && AWK=`look_for_prog awk`
1768
1769
1770if [[ ! -x $PERL ]]; then
1771	print -u2 "Error: No perl interpreter found.  Exiting."
1772	exit 1
1773fi
1774
1775if [[ ! -x $WHICH_SCM ]]; then
1776	print -u2 "Error: Could not find which_scm.  Exiting."
1777	exit 1
1778fi
1779
1780#
1781# These aren't fatal, but we want to note them to the user.
1782# We don't warn on the absence of 'wx' until later when we've
1783# determined that we actually need to try to invoke it.
1784#
1785[[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
1786[[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
1787[[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
1788
1789# Declare global total counters.
1790integer TOTL TINS TDEL TMOD TUNC
1791
1792flist_mode=
1793flist_file=
1794iflag=
1795oflag=
1796pflag=
1797lflag=
1798wflag=
1799Oflag=
1800while getopts "i:o:p:lwO" opt
1801do
1802	case $opt in
1803	i)	iflag=1
1804		INCLUDE_FILE=$OPTARG;;
1805
1806	o)	oflag=1
1807		WDIR=$OPTARG;;
1808
1809	p)	pflag=1
1810		codemgr_parent=$OPTARG;;
1811
1812	#
1813	# If -l has been specified, we need to abort further options
1814	# processing, because subsequent arguments are going to be
1815	# arguments to 'putback -n'.
1816	#
1817	l)	lflag=1
1818		break;;
1819
1820	w)	wflag=1;;
1821
1822	O)	Oflag=1;;
1823
1824	?)	usage;;
1825	esac
1826done
1827
1828FLIST=/tmp/$$.flist
1829
1830if [[ -n $wflag && -n $lflag ]]; then
1831	usage
1832fi
1833
1834#
1835# If this manually set as the parent, and it appears to be an earlier webrev,
1836# then note that fact and set the parent to the raw_files/new subdirectory.
1837#
1838if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
1839	parent_webrev="$codemgr_parent"
1840	codemgr_parent="$codemgr_parent/raw_files/new"
1841fi
1842
1843if [[ -z $wflag && -z $lflag ]]; then
1844	shift $(($OPTIND - 1))
1845
1846	if [[ $1 == "-" ]]; then
1847		cat > $FLIST
1848		flist_mode="stdin"
1849		flist_done=1
1850		shift
1851	elif [[ -n $1 ]]; then
1852		if [[ ! -r $1 ]]; then
1853			print -u2 "$1: no such file or not readable"
1854			usage
1855		fi
1856		cat $1 > $FLIST
1857		flist_mode="file"
1858		flist_file=$1
1859		flist_done=1
1860		shift
1861	else
1862		flist_mode="auto"
1863	fi
1864fi
1865
1866#
1867# Before we go on to further consider -l and -w, work out which SCM we think
1868# is in use.
1869#
1870$WHICH_SCM | read SCM_MODE junk || exit 1
1871case "$SCM_MODE" in
1872teamware|mercurial|subversion)
1873	;;
1874unknown)
1875	if [[ $flist_mode == "auto" ]]; then
1876		print -u2 "Unable to determine SCM in use and file list not specified"
1877		print -u2 "See which_scm(1) for SCM detection information."
1878		exit 1
1879	fi
1880	;;
1881*)
1882	if [[ $flist_mode == "auto" ]]; then
1883		print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
1884		exit 1
1885	fi
1886	;;
1887esac
1888
1889print -u2 "   SCM detected: $SCM_MODE"
1890
1891if [[ -n $lflag ]]; then
1892	#
1893	# If the -l flag is given instead of the name of a file list,
1894	# then generate the file list by extracting file names from a
1895	# putback -n.
1896	#
1897	shift $(($OPTIND - 1))
1898	if [[ $SCM_MODE == "teamware" ]]; then
1899		flist_from_teamware "$*"
1900	else
1901		print -u2 -- "Error: -l option only applies to TeamWare"
1902		exit 1
1903	fi
1904	flist_done=1
1905	shift $#
1906elif [[ -n $wflag ]]; then
1907	#
1908	# If the -w is given then assume the file list is in Bonwick's "wx"
1909	# command format, i.e.  pathname lines alternating with SCCS comment
1910	# lines with blank lines as separators.  Use the SCCS comments later
1911	# in building the index.html file.
1912	#
1913	shift $(($OPTIND - 1))
1914	wxfile=$1
1915	if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
1916		if [[ -r $CODEMGR_WS/wx/active ]]; then
1917			wxfile=$CODEMGR_WS/wx/active
1918		fi
1919	fi
1920
1921	[[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
1922	    "be auto-detected (check \$CODEMGR_WS)" && exit 1
1923
1924	if [[ ! -r $wxfile ]]; then
1925		print -u2 "$wxfile: no such file or not readable"
1926		usage
1927	fi
1928
1929	print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
1930	flist_from_wx $wxfile
1931	flist_done=1
1932	if [[ -n "$*" ]]; then
1933		shift
1934	fi
1935elif [[ $flist_mode == "stdin" ]]; then
1936	print -u2 " File list from: standard input"
1937elif [[ $flist_mode == "file" ]]; then
1938	print -u2 " File list from: $flist_file"
1939fi
1940
1941if [[ $# -gt 0 ]]; then
1942	print -u2 "WARNING: unused arguments: $*"
1943fi
1944
1945if [[ $SCM_MODE == "teamware" ]]; then
1946	#
1947	# Parent (internally $codemgr_parent) and workspace ($codemgr_ws) can
1948	# be set in a number of ways, in decreasing precedence:
1949	#
1950	#      1) on the command line (only for the parent)
1951	#      2) in the user environment
1952	#      3) in the flist
1953	#      4) automatically based on the workspace (only for the parent)
1954	#
1955
1956	#
1957	# Here is case (2): the user environment
1958	#
1959	[[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
1960	if [[ -n $codemgr_ws && ! -d $codemgr_ws ]]; then
1961		print -u2 "$codemgr_ws: no such workspace"
1962		exit 1
1963	fi
1964
1965	[[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
1966	    codemgr_parent=$CODEMGR_PARENT
1967	if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then
1968		print -u2 "$codemgr_parent: no such directory"
1969		exit 1
1970	fi
1971
1972	#
1973	# If we're in auto-detect mode and we haven't already gotten the file
1974	# list, then see if we can get it by probing for wx.
1975	#
1976	if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then
1977		if [[ ! -x $WX ]]; then
1978			print -u2 "WARNING: wx not found!"
1979		fi
1980
1981		#
1982		# We need to use wx list -w so that we get renamed files, etc.
1983		# but only if a wx active file exists-- otherwise wx will
1984		# hang asking us to initialize our wx information.
1985		#
1986		if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then
1987			print -u2 " File list from: 'wx list -w' ... \c"
1988			$WX list -w > $FLIST
1989			$WX comments > /tmp/$$.wx_comments
1990			wxfile=/tmp/$$.wx_comments
1991			print -u2 "done"
1992			flist_done=1
1993		fi
1994	fi
1995
1996	#
1997	# If by hook or by crook we've gotten a file list by now (perhaps
1998	# from the command line), eval it to extract environment variables from
1999	# it: This is step (3).
2000	#
2001	env_from_flist
2002
2003	#
2004	# Continuing step (3): If we still have no file list, we'll try to get
2005	# it from teamware.
2006	#
2007	if [[ -z $flist_done ]]; then
2008		flist_from_teamware
2009		env_from_flist
2010	fi
2011
2012	#
2013	# (4) If we still don't have a value for codemgr_parent, get it
2014	# from workspace.
2015	#
2016	[[ -z $codemgr_ws ]] && codemgr_ws=`workspace name`
2017	[[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent`
2018	if [[ ! -d $codemgr_parent ]]; then
2019		print -u2 "$CODEMGR_PARENT: no such parent workspace"
2020		exit 1
2021	fi
2022
2023	#
2024	# Observe true directory name of CODEMGR_WS, as used later in
2025	# webrev title.
2026	#
2027	codemgr_ws=$(cd $codemgr_ws;print $PWD)
2028
2029	#
2030	# Reset CODEMGR_WS to make sure teamware commands are happy.
2031	#
2032	CODEMGR_WS=$codemgr_ws
2033	CWS=$codemgr_ws
2034	PWS=$codemgr_parent
2035
2036	[[ -n $parent_webrev ]] && RWS=$(workspace parent $CWS)
2037
2038elif [[ $SCM_MODE == "mercurial" ]]; then
2039	[[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2040	    codemgr_ws=`hg root -R $CODEMGR_WS 2>/dev/null`
2041
2042	[[ -z $codemgr_ws ]] && codemgr_ws=`hg root 2>/dev/null`
2043
2044	#
2045	# Parent can either be specified with -p
2046	# Specified with CODEMGR_PARENT in the environment
2047	# or taken from hg's default path.
2048	#
2049
2050	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2051		codemgr_parent=$CODEMGR_PARENT
2052	fi
2053
2054	if [[ -z $codemgr_parent ]]; then
2055		codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
2056	fi
2057
2058	CWS_REV=`hg parent -R $codemgr_ws --template '{node|short}' 2>/dev/null`
2059	CWS=$codemgr_ws
2060	PWS=$codemgr_parent
2061
2062	#
2063	# If the parent is a webrev, we want to do some things against
2064	# the natural workspace parent (file list, comments, etc)
2065	#
2066	if [[ -n $parent_webrev ]]; then
2067		real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
2068	else
2069		real_parent=$PWS
2070	fi
2071
2072	#
2073	# If hg-active exists, then we run it.  In the case of no explicit
2074	# flist given, we'll use it for our comments.  In the case of an
2075	# explicit flist given we'll try to use it for comments for any
2076	# files mentioned in the flist.
2077	#
2078	if [[ -z $flist_done ]]; then
2079		flist_from_mercurial $CWS $real_parent
2080		flist_done=1
2081	fi
2082
2083	#
2084	# If we have a file list now, pull out any variables set
2085	# therein.  We do this now (rather than when we possibly use
2086	# hg-active to find comments) to avoid stomping specifications
2087	# in the user-specified flist.
2088	#
2089	if [[ -n $flist_done ]]; then
2090		env_from_flist
2091	fi
2092
2093	#
2094	# Only call hg-active if we don't have a wx formatted file already
2095	#
2096	if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2097		print "  Comments from: hg-active -p $real_parent ...\c"
2098		hg_active_wxfile $CWS $real_parent
2099		print " Done."
2100	fi
2101
2102	#
2103	# At this point we must have a wx flist either from hg-active,
2104	# or in general.  Use it to try and find our parent revision,
2105	# if we don't have one.
2106	#
2107	if [[ -z $HG_PARENT ]]; then
2108		eval `sed -e "s/#.*$//" $wxfile | grep HG_PARENT=`
2109	fi
2110
2111	#
2112	# If we still don't have a parent, we must have been given a
2113	# wx-style active list with no HG_PARENT specification, run
2114	# hg-active and pull an HG_PARENT out of it, ignore the rest.
2115	#
2116	if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2117		$HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2118		    eval `sed -e "s/#.*$//" | grep HG_PARENT=`
2119	elif [[ -z $HG_PARENT ]]; then
2120		print -u2 "Error: Cannot discover parent revision"
2121		exit 1
2122	fi
2123elif [[ $SCM_MODE == "subversion" ]]; then
2124	if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2125		CWS=$CODEMGR_WS
2126	else
2127		svn info | while read line; do
2128			if [[ $line == "URL: "* ]]; then
2129				url=${line#URL: }
2130			elif [[ $line == "Repository Root: "* ]]; then
2131				repo=${line#Repository Root: }
2132			fi
2133		done
2134
2135		rel=${url#$repo}
2136		CWS=${PWD%$rel}
2137	fi
2138
2139	#
2140	# We only will have a real parent workspace in the case one
2141	# was specified (be it an older webrev, or another checkout).
2142	#
2143	[[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2144
2145	if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2146		flist_from_subversion $CWS $OLDPWD
2147	fi
2148else
2149    if [[ $SCM_MODE == "unknown" ]]; then
2150	print -u2 "    Unknown type of SCM in use"
2151    else
2152	print -u2 "    Unsupported SCM in use: $SCM_MODE"
2153    fi
2154
2155    env_from_flist
2156
2157    if [[ -z $CODEMGR_WS ]]; then
2158	print -u2 "SCM not detected/supported and CODEMGR_WS not specified"
2159	exit 1
2160    fi
2161
2162    if [[ -z $CODEMGR_PARENT ]]; then
2163	print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified"
2164	exit 1
2165    fi
2166
2167    CWS=$CODEMGR_WS
2168    PWS=$CODEMGR_PARENT
2169fi
2170
2171#
2172# If the user didn't specify a -i option, check to see if there is a
2173# webrev-info file in the workspace directory.
2174#
2175if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2176	iflag=1
2177	INCLUDE_FILE="$CWS/webrev-info"
2178fi
2179
2180if [[ -n $iflag ]]; then
2181	if [[ ! -r $INCLUDE_FILE ]]; then
2182		print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2183		    "not readable."
2184		exit 1
2185	else
2186		#
2187		# $INCLUDE_FILE may be a relative path, and the script alters
2188		# PWD, so we just stash a copy in /tmp.
2189		#
2190		cp $INCLUDE_FILE /tmp/$$.include
2191	fi
2192fi
2193
2194#
2195# Output directory.
2196#
2197WDIR=${WDIR:-$CWS/webrev}
2198
2199#
2200# Name of the webrev, derived from the workspace name; in the
2201# future this could potentially be an option.
2202#
2203WNAME=${CWS##*/}
2204
2205if [ "${WDIR%%/*}" ]; then
2206	WDIR=$PWD/$WDIR
2207fi
2208
2209if [[ ! -d $WDIR ]]; then
2210	mkdir -p $WDIR
2211	[[ $? != 0 ]] && exit 1
2212fi
2213
2214#
2215# Summarize what we're going to do.
2216#
2217if [[ -n $CWS_REV ]]; then
2218	print "      Workspace: $CWS (at $CWS_REV)"
2219else
2220	print "      Workspace: $CWS"
2221fi
2222if [[ -n $parent_webrev ]]; then
2223	print "Compare against: webrev at $parent_webrev"
2224else
2225	if [[ -n $HG_PARENT ]]; then
2226		hg_parent_short=`echo $HG_PARENT \
2227			| sed -e 's/\([0-9a-f]\{12\}\).*/\1/'`
2228		print "Compare against: $PWS (at $hg_parent_short)"
2229	else
2230		print "Compare against: $PWS"
2231	fi
2232fi
2233
2234[[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
2235print "      Output to: $WDIR"
2236
2237#
2238# Save the file list in the webrev dir
2239#
2240[[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
2241
2242#
2243#    Bug IDs will be replaced by a URL.  Order of precedence
2244#    is: default location, $WEBREV_BUGURL, the -O flag.
2245#
2246BUGURL='http://monaco.sfbay.sun.com/detail.jsp?cr='
2247[[ -n $WEBREV_BUGURL ]] && BUGURL="$WEBREV_BUGURL"
2248[[ -n "$Oflag" ]] && \
2249    BUGURL='http://bugs.opensolaris.org/bugdatabase/view_bug.do?bug_id='
2250
2251#
2252#    Likewise, ARC cases will be replaced by a URL.  Order of precedence
2253#    is: default, $WEBREV_SACURL, the -O flag.
2254#
2255#    Note that -O also triggers different substitution behavior for
2256#    SACURL.  See sac2url().
2257#
2258SACURL='http://sac.eng.sun.com'
2259[[ -n $WEBREV_SACURL ]] && SACURL="$WEBREV_SACURL"
2260[[ -n "$Oflag" ]] && \
2261    SACURL='http://www.opensolaris.org/os/community/arc/caselog'
2262
2263rm -f $WDIR/$WNAME.patch
2264rm -f $WDIR/$WNAME.ps
2265rm -f $WDIR/$WNAME.pdf
2266
2267touch $WDIR/$WNAME.patch
2268
2269print "   Output Files:"
2270
2271#
2272# Clean up the file list: Remove comments, blank lines and env variables.
2273#
2274sed -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
2275FLIST=/tmp/$$.flist.clean
2276
2277#
2278# For Mercurial, create a cache of manifest entries.
2279#
2280if [[ $SCM_MODE == "mercurial" ]]; then
2281	#
2282	# Transform the FLIST into a temporary sed script that matches
2283	# relevant entries in the Mercurial manifest as follows:
2284	# 1) The script will be used against the parent revision manifest,
2285	#    so for FLIST lines that have two filenames (a renamed file)
2286	#    keep only the old name.
2287	# 2) Escape all forward slashes the filename.
2288	# 3) Change the filename into another sed command that matches
2289	#    that file in "hg manifest -v" output:  start of line, three
2290	#    octal digits for file permissions, space, a file type flag
2291	#    character, space, the filename, end of line.
2292	#
2293	SEDFILE=/tmp/$$.manifest.sed
2294	sed '
2295		s#^[^ ]* ##
2296		s#/#\\\/#g
2297		s#^.*$#/^... . &$/p#
2298	' < $FLIST > $SEDFILE
2299
2300	#
2301	# Apply the generated script to the output of "hg manifest -v"
2302	# to get the relevant subset for this webrev.
2303	#
2304	HG_PARENT_MANIFEST=/tmp/$$.manifest
2305	hg -R $CWS manifest -v -r $HG_PARENT |
2306	    sed -n -f $SEDFILE > $HG_PARENT_MANIFEST
2307fi
2308
2309#
2310# First pass through the files: generate the per-file webrev HTML-files.
2311#
2312cat $FLIST | while read LINE
2313do
2314	set - $LINE
2315	P=$1
2316
2317	#
2318	# Normally, each line in the file list is just a pathname of a
2319	# file that has been modified or created in the child.  A file
2320	# that is renamed in the child workspace has two names on the
2321	# line: new name followed by the old name.
2322	#
2323	oldname=""
2324	oldpath=""
2325	rename=
2326	if [[ $# -eq 2 ]]; then
2327		PP=$2			# old filename
2328		oldname=" (was $PP)"
2329		oldpath="$PP"
2330		rename=1
2331        	PDIR=${PP%/*}
2332        	if [[ $PDIR == $PP ]]; then
2333			PDIR="."   # File at root of workspace
2334		fi
2335
2336		PF=${PP##*/}
2337
2338	        DIR=${P%/*}
2339	        if [[ $DIR == $P ]]; then
2340			DIR="."   # File at root of workspace
2341		fi
2342
2343		F=${P##*/}
2344
2345        else
2346	        DIR=${P%/*}
2347	        if [[ "$DIR" == "$P" ]]; then
2348			DIR="."   # File at root of workspace
2349		fi
2350
2351		F=${P##*/}
2352
2353		PP=$P
2354		PDIR=$DIR
2355		PF=$F
2356	fi
2357
2358	COMM=`getcomments html $P $PP`
2359
2360	print "\t$P$oldname\n\t\t\c"
2361
2362	# Make the webrev mirror directory if necessary
2363	mkdir -p $WDIR/$DIR
2364
2365	#
2366	# If we're in OpenSolaris mode, we enforce a minor policy:
2367	# help to make sure the reviewer doesn't accidentally publish
2368	# source which is in usr/closed/* or deleted_files/usr/closed/*
2369	#
2370	if [[ -n "$Oflag" ]]; then
2371		pclosed=${P##usr/closed/}
2372		pdeleted=${P##deleted_files/usr/closed/}
2373		if [[ "$pclosed" != "$P" || "$pdeleted" != "$P" ]]; then
2374			print "*** Omitting closed source for OpenSolaris" \
2375			    "mode review"
2376			continue
2377		fi
2378	fi
2379
2380	#
2381	# We stash old and new files into parallel directories in $WDIR
2382	# and do our diffs there.  This makes it possible to generate
2383	# clean looking diffs which don't have absolute paths present.
2384	#
2385
2386	build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
2387	    continue
2388
2389	#
2390	# Keep the old PWD around, so we can safely switch back after
2391	# diff generation, such that build_old_new runs in a
2392	# consistent environment.
2393	#
2394	OWD=$PWD
2395	cd $WDIR/raw_files
2396	ofile=old/$PDIR/$PF
2397	nfile=new/$DIR/$F
2398
2399	mv_but_nodiff=
2400	cmp $ofile $nfile > /dev/null 2>&1
2401	if [[ $? == 0 && $rename == 1 ]]; then
2402		mv_but_nodiff=1
2403	fi
2404
2405	#
2406	# If we have old and new versions of the file then run the appropriate
2407	# diffs.  This is complicated by a couple of factors:
2408	#
2409	#	- renames must be handled specially: we emit a 'remove'
2410	#	  diff and an 'add' diff
2411	#	- new files and deleted files must be handled specially
2412	#	- Solaris patch(1m) can't cope with file creation
2413	#	  (and hence renames) as of this writing.
2414	#       - To make matters worse, gnu patch doesn't interpret the
2415	#	  output of Solaris diff properly when it comes to
2416	#	  adds and deletes.  We need to do some "cleansing"
2417	#         transformations:
2418	# 	    [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
2419	#	    [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
2420	#
2421	cleanse_rmfile="sed 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
2422	cleanse_newfile="sed 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
2423
2424	rm -f $WDIR/$DIR/$F.patch
2425	if [[ -z $rename ]]; then
2426		if [ ! -f "$ofile" ]; then
2427			diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
2428			    > $WDIR/$DIR/$F.patch
2429		elif [ ! -f "$nfile" ]; then
2430			diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
2431			    > $WDIR/$DIR/$F.patch
2432		else
2433			diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
2434		fi
2435	else
2436		diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
2437		    > $WDIR/$DIR/$F.patch
2438
2439		diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
2440		    >> $WDIR/$DIR/$F.patch
2441
2442	fi
2443
2444	#
2445	# Tack the patch we just made onto the accumulated patch for the
2446	# whole wad.
2447	#
2448	cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
2449
2450	print " patch\c"
2451
2452	if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
2453
2454		${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
2455		diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
2456		    > $WDIR/$DIR/$F.cdiff.html
2457		print " cdiffs\c"
2458
2459		${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
2460		diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
2461		    > $WDIR/$DIR/$F.udiff.html
2462
2463		print " udiffs\c"
2464
2465		if [[ -x $WDIFF ]]; then
2466			$WDIFF -c "$COMM" \
2467			    -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
2468			    $WDIR/$DIR/$F.wdiff.html 2>/dev/null
2469			if [[ $? -eq 0 ]]; then
2470				print " wdiffs\c"
2471			else
2472				print " wdiffs[fail]\c"
2473			fi
2474		fi
2475
2476		sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
2477		    > $WDIR/$DIR/$F.sdiff.html
2478		print " sdiffs\c"
2479
2480		print " frames\c"
2481
2482		rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
2483
2484		difflines $ofile $nfile > $WDIR/$DIR/$F.count
2485
2486	elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
2487		# renamed file: may also have differences
2488		difflines $ofile $nfile > $WDIR/$DIR/$F.count
2489	elif [[ -f $nfile ]]; then
2490		# new file: count added lines
2491		difflines /dev/null $nfile > $WDIR/$DIR/$F.count
2492	elif [[ -f $ofile ]]; then
2493		# old file: count deleted lines
2494		difflines $ofile /dev/null > $WDIR/$DIR/$F.count
2495	fi
2496
2497	#
2498	# Now we generate the postscript for this file.  We generate diffs
2499	# only in the event that there is delta, or the file is new (it seems
2500	# tree-killing to print out the contents of deleted files).
2501	#
2502	if [[ -f $nfile ]]; then
2503		ocr=$ofile
2504		[[ ! -f $ofile ]] && ocr=/dev/null
2505
2506		if [[ -z $mv_but_nodiff ]]; then
2507			textcomm=`getcomments text $P $PP`
2508			if [[ -x $CODEREVIEW ]]; then
2509				$CODEREVIEW -y "$textcomm" \
2510				    -e $ocr $nfile \
2511				    > /tmp/$$.psfile 2>/dev/null &&
2512				    cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
2513				if [[ $? -eq 0 ]]; then
2514					print " ps\c"
2515				else
2516					print " ps[fail]\c"
2517				fi
2518			fi
2519		fi
2520	fi
2521
2522	if [[ -f $ofile ]]; then
2523		source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
2524		print " old\c"
2525	fi
2526
2527	if [[ -f $nfile ]]; then
2528		source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
2529		print " new\c"
2530	fi
2531
2532	cd $OWD
2533
2534	print
2535done
2536
2537frame_nav_js > $WDIR/ancnav.js
2538frame_navigation > $WDIR/ancnav.html
2539
2540if [[ ! -f $WDIR/$WNAME.ps ]]; then
2541	print " Generating PDF: Skipped: no output available"
2542elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
2543	print " Generating PDF: \c"
2544	fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
2545	print "Done."
2546else
2547	print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
2548fi
2549
2550# If we're in OpenSolaris mode and there's a closed dir under $WDIR,
2551# delete it - prevent accidental publishing of closed source
2552
2553if [[ -n "$Oflag" ]]; then
2554	/usr/bin/find $WDIR -type d -name closed -exec /bin/rm -rf {} \;
2555fi
2556
2557# Now build the index.html file that contains
2558# links to the source files and their diffs.
2559
2560cd $CWS
2561
2562# Save total changed lines for Code Inspection.
2563print "$TOTL" > $WDIR/TotalChangedLines
2564
2565print "     index.html: \c"
2566INDEXFILE=$WDIR/index.html
2567exec 3<&1			# duplicate stdout to FD3.
2568exec 1<&-			# Close stdout.
2569exec > $INDEXFILE		# Open stdout to index file.
2570
2571print "$HTML<head>$STDHEAD"
2572print "<title>$WNAME</title>"
2573print "</head>"
2574print "<body id=\"SUNWwebrev\">"
2575print "<div class=\"summary\">"
2576print "<h2>Code Review for $WNAME</h2>"
2577
2578print "<table>"
2579
2580#
2581# Get the preparer's name:
2582#
2583# If the SCM detected is Mercurial, and the configuration property
2584# ui.username is available, use that, but be careful to properly escape
2585# angle brackets (HTML syntax characters) in the email address.
2586#
2587# Otherwise, use the current userid in the form "John Doe (jdoe)", but
2588# to maintain compatibility with passwd(4), we must support '&' substitutions.
2589#
2590preparer=
2591if [[ "$SCM_MODE" == mercurial ]]; then
2592	preparer=`hg showconfig ui.username 2>/dev/null`
2593	if [[ -n "$preparer" ]]; then
2594		preparer="$(echo "$preparer" | html_quote)"
2595	fi
2596fi
2597if [[ -z "$preparer" ]]; then
2598	preparer=$(
2599	    $PERL -e '
2600	        ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
2601	        if ($login) {
2602	            $gcos =~ s/\&/ucfirst($login)/e;
2603	            printf "%s (%s)\n", $gcos, $login;
2604	        } else {
2605	            printf "(unknown)\n";
2606	        }
2607	')
2608fi
2609
2610print "<tr><th>Prepared by:</th><td>$preparer on `date`</td></tr>"
2611print "<tr><th>Workspace:</th><td>$CWS"
2612if [[ -n $CWS_REV ]]; then
2613	print "(at $CWS_REV)"
2614fi
2615print "</td></tr>"
2616print "<tr><th>Compare against:</th><td>"
2617if [[ -n $parent_webrev ]]; then
2618	print "webrev at $parent_webrev"
2619else
2620	print "$PWS"
2621	if [[ -n $hg_parent_short ]]; then
2622		print "(at $hg_parent_short)"
2623	fi
2624fi
2625print "</td></tr>"
2626print "<tr><th>Summary of changes:</th><td>"
2627printCI $TOTL $TINS $TDEL $TMOD $TUNC
2628print "</td></tr>"
2629
2630if [[ -f $WDIR/$WNAME.patch ]]; then
2631	print "<tr><th>Patch of changes:</th><td>"
2632	print "<a href=\"$WNAME.patch\">$WNAME.patch</a></td></tr>"
2633fi
2634if [[ -f $WDIR/$WNAME.pdf ]]; then
2635	print "<tr><th>Printable review:</th><td>"
2636	print "<a href=\"$WNAME.pdf\">$WNAME.pdf</a></td></tr>"
2637fi
2638
2639if [[ -n "$iflag" ]]; then
2640	print "<tr><th>Author comments:</th><td><div>"
2641	cat /tmp/$$.include
2642	print "</div></td></tr>"
2643fi
2644print "</table>"
2645print "</div>"
2646
2647#
2648# Second pass through the files: generate the rest of the index file
2649#
2650cat $FLIST | while read LINE
2651do
2652	set - $LINE
2653	P=$1
2654
2655	if [[ $# == 2 ]]; then
2656		PP=$2
2657		oldname="$PP"
2658	else
2659		PP=$P
2660		oldname=""
2661	fi
2662
2663	mv_but_nodiff=
2664	cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
2665	if [[ $? == 0 && -n "$oldname" ]]; then
2666		mv_but_nodiff=1
2667	fi
2668
2669	DIR=${P%/*}
2670	if [[ $DIR == $P ]]; then
2671		DIR="."   # File at root of workspace
2672	fi
2673
2674	# Avoid processing the same file twice.
2675	# It's possible for renamed files to
2676	# appear twice in the file list
2677
2678	F=$WDIR/$P
2679
2680	print "<p>"
2681
2682	# If there's a diffs file, make diffs links
2683
2684	if [[ -f $F.cdiff.html ]]; then
2685		print "<a href=\"$P.cdiff.html\">Cdiffs</a>"
2686		print "<a href=\"$P.udiff.html\">Udiffs</a>"
2687
2688		if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
2689			print "<a href=\"$P.wdiff.html\">Wdiffs</a>"
2690		fi
2691
2692		print "<a href=\"$P.sdiff.html\">Sdiffs</a>"
2693
2694		print "<a href=\"$P.frames.html\">Frames</a>"
2695	else
2696		print " ------ ------ ------"
2697
2698		if [[ -x $WDIFF ]]; then
2699			print " ------"
2700		fi
2701
2702		print " ------"
2703	fi
2704
2705	# If there's an old file, make the link
2706
2707	if [[ -f $F-.html ]]; then
2708		print "<a href=\"$P-.html\">Old</a>"
2709	else
2710		print " ---"
2711	fi
2712
2713	# If there's an new file, make the link
2714
2715	if [[ -f $F.html ]]; then
2716		print "<a href=\"$P.html\">New</a>"
2717	else
2718		print " ---"
2719	fi
2720
2721	if [[ -f $F.patch ]]; then
2722		print "<a href=\"$P.patch\">Patch</a>"
2723	else
2724		print " -----"
2725	fi
2726
2727	if [[ -f $WDIR/raw_files/new/$P ]]; then
2728		print "<a href=\"raw_files/new/$P\">Raw</a>"
2729	else
2730		print " ---"
2731	fi
2732
2733	print "<b>$P</b>"
2734
2735	# For renamed files, clearly state whether or not they are modified
2736	if [[ -n "$oldname" ]]; then
2737		if [[ -n "$mv_but_nodiff" ]]; then
2738			print "<i>(renamed only, was $oldname)</i>"
2739		else
2740			print "<i>(modified and renamed, was $oldname)</i>"
2741		fi
2742	fi
2743
2744	# If there's an old file, but no new file, the file was deleted
2745	if [[ -f $F-.html && ! -f $F.html ]]; then
2746		print " <i>(deleted)</i>"
2747	fi
2748
2749	#
2750	# Check for usr/closed and deleted_files/usr/closed
2751	#
2752	if [ ! -z "$Oflag" ]; then
2753		if [[ $P == usr/closed/* || \
2754		    $P == deleted_files/usr/closed/* ]]; then
2755			print "&nbsp;&nbsp;<i>Closed source: omitted from" \
2756			    "this review</i>"
2757		fi
2758	fi
2759
2760	print "</p>"
2761	# Insert delta comments
2762
2763	print "<blockquote><pre>"
2764	getcomments html $P $PP
2765	print "</pre>"
2766
2767	# Add additional comments comment
2768
2769	print "<!-- Add comments to explain changes in $P here -->"
2770
2771	# Add count of changes.
2772
2773	if [[ -f $F.count ]]; then
2774	    cat $F.count
2775	    rm $F.count
2776	fi
2777
2778	if [[ $SCM_MODE == "teamware" ||
2779	    $SCM_MODE == "mercurial" ||
2780	    $SCM_MODE == "unknown" ]]; then
2781
2782		# Include warnings for important file mode situations:
2783		# 1) New executable files
2784		# 2) Permission changes of any kind
2785		# 3) Existing executable files
2786
2787		old_mode=
2788		if [[ -f $WDIR/raw_files/old/$PP ]]; then
2789			old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
2790		fi
2791
2792		new_mode=
2793		if [[ -f $WDIR/raw_files/new/$P ]]; then
2794			new_mode=`get_file_mode $WDIR/raw_files/new/$P`
2795		fi
2796
2797		if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
2798			print "<span class=\"chmod\">"
2799			print "<p>new executable file: mode $new_mode</p>"
2800			print "</span>"
2801		elif [[ -n "$old_mode" && -n "$new_mode" &&
2802		    "$old_mode" != "$new_mode" ]]; then
2803			print "<span class=\"chmod\">"
2804			print "<p>mode change: $old_mode to $new_mode</p>"
2805			print "</span>"
2806		elif [[ "$new_mode" = *[1357]* ]]; then
2807			print "<span class=\"chmod\">"
2808			print "<p>executable file: mode $new_mode</p>"
2809			print "</span>"
2810		fi
2811	fi
2812
2813	print "</blockquote>"
2814done
2815
2816print
2817print
2818print "<hr></hr>"
2819print "<p style=\"font-size: small\">"
2820print "This code review page was prepared using <b>$0</b>."
2821print "Webrev is maintained by the <a href=\"http://www.opensolaris.org\">"
2822print "OpenSolaris</a> project.  The latest version may be obtained"
2823print "<a href=\"http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
2824print "</body>"
2825print "</html>"
2826
2827exec 1<&-			# Close FD 1.
2828exec 1<&3			# dup FD 3 to restore stdout.
2829exec 3<&-			# close FD 3.
2830
2831print "Done."
2832