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