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