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