xref: /illumos-gate/usr/src/tools/scripts/webrev.sh (revision 250d0a0c32d883b96c71a7bb324ab7f77f3a863a)
1#!/usr/bin/ksh93 -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 (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
25# Copyright 2008, 2010, Richard Lowe
26# Copyright 2012 Marcel Telka <marcel@telka.sk>
27# Copyright 2014 Bart Coddens <bart.coddens@gmail.com>
28# Copyright 2016 Nexenta Systems, Inc.
29# Copyright 2016 Joyent, Inc.
30# Copyright 2016 RackTop Systems.
31#
32
33#
34# This script takes a file list and a workspace and builds a set of html files
35# suitable for doing a code review of source changes via a web page.
36# Documentation is available via the manual page, webrev.1, or just
37# type 'webrev -h'.
38#
39# Acknowledgements to contributors to webrev are listed in the webrev(1)
40# man page.
41#
42
43REMOVED_COLOR=brown
44CHANGED_COLOR=blue
45NEW_COLOR=blue
46
47HTML='<?xml version="1.0"?>
48<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
49    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
50<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
51
52FRAMEHTML='<?xml version="1.0"?>
53<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
54    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
55<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
56
57STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta>
58<meta http-equiv="Content-Type" content="text/xhtml;charset=utf-8"></meta>
59<meta http-equiv="Pragma" content="no-cache"></meta>
60<meta http-equiv="Expires" content="-1"></meta>
61<!--
62   Note to customizers: the body of the webrev is IDed as SUNWwebrev
63   to allow easy overriding by users of webrev via the userContent.css
64   mechanism available in some browsers.
65
66   For example, to have all "removed" information be red instead of
67   brown, set a rule in your userContent.css file like:
68
69       body#SUNWwebrev span.removed { color: red ! important; }
70-->
71<style type="text/css" media="screen">
72body {
73    background-color: #eeeeee;
74}
75hr {
76    border: none 0;
77    border-top: 1px solid #aaa;
78    height: 1px;
79}
80div.summary {
81    font-size: .8em;
82    border-bottom: 1px solid #aaa;
83    padding-left: 1em;
84    padding-right: 1em;
85}
86div.summary h2 {
87    margin-bottom: 0.3em;
88}
89div.summary table th {
90    text-align: right;
91    vertical-align: top;
92    white-space: nowrap;
93}
94span.lineschanged {
95    font-size: 0.7em;
96}
97span.oldmarker {
98    color: red;
99    font-size: large;
100    font-weight: bold;
101}
102span.newmarker {
103    color: green;
104    font-size: large;
105    font-weight: bold;
106}
107span.removed {
108    color: brown;
109}
110span.changed {
111    color: blue;
112}
113span.new {
114    color: blue;
115    font-weight: bold;
116}
117span.chmod {
118    font-size: 0.7em;
119    color: #db7800;
120}
121a.print { font-size: x-small; }
122a:hover { background-color: #ffcc99; }
123</style>
124
125<style type="text/css" media="print">
126pre { font-size: 0.8em; font-family: courier, monospace; }
127span.removed { color: #444; font-style: italic }
128span.changed { font-weight: bold; }
129span.new { font-weight: bold; }
130span.newmarker { font-size: 1.2em; font-weight: bold; }
131span.oldmarker { font-size: 1.2em; font-weight: bold; }
132a.print {display: none}
133hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
134</style>
135'
136
137#
138# UDiffs need a slightly different CSS rule for 'new' items (we don't
139# want them to be bolded as we do in cdiffs or sdiffs).
140#
141UDIFFCSS='
142<style type="text/css" media="screen">
143span.new {
144    color: blue;
145    font-weight: normal;
146}
147</style>
148'
149
150#
151# CSS for the HTML version of the man pages.
152#
153MANCSS='
154html { max-width: 880px; margin-left: 1em; }
155body { font-size: smaller; font-family: Helvetica,Arial,sans-serif; }
156h1 { margin-bottom: 1ex; font-size: 110%; margin-left: -4ex; }
157h2 { margin-bottom: 1ex; font-size: 105%; margin-left: -2ex; }
158table { width: 100%; margin-top: 0ex; margin-bottom: 0ex; }
159td { vertical-align: top; }
160blockquote { margin-left: 5ex; margin-top: 0ex; margin-bottom: 0ex; }
161div.section { margin-bottom: 2ex; margin-left: 5ex; }
162table.foot { font-size: smaller; margin-top: 1em;
163    border-top: 1px dotted #dddddd; }
164td.foot-date { width: 50%; }
165td.foot-os { width: 50%; text-align: right; }
166table.head { font-size: smaller; margin-bottom: 1em;
167    border-bottom: 1px dotted #dddddd; }
168td.head-ltitle { width: 10%; }
169td.head-vol { width: 80%; text-align: center; }
170td.head-rtitle { width: 10%; text-align: right; }
171.emph { font-style: italic; font-weight: normal; }
172.symb { font-style: normal; font-weight: bold; }
173.lit { font-style: normal; font-weight: normal; font-family: monospace; }
174i.addr { font-weight: normal; }
175i.arg { font-weight: normal; }
176b.cmd { font-style: normal; }
177b.config { font-style: normal; }
178b.diag { font-style: normal; }
179i.farg { font-weight: normal; }
180i.file { font-weight: normal; }
181b.flag { font-style: normal; }
182b.fname { font-style: normal; }
183i.ftype { font-weight: normal; }
184b.includes { font-style: normal; }
185i.link-sec { font-weight: normal; }
186b.macro { font-style: normal; }
187b.name { font-style: normal; }
188i.ref-book { font-weight: normal; }
189i.ref-issue { font-weight: normal; }
190i.ref-jrnl { font-weight: normal; }
191span.ref-title { text-decoration: underline; }
192span.type { font-style: italic; font-weight: normal; }
193b.utility { font-style: normal; }
194b.var { font-style: normal; }
195dd.list-ohang { margin-left: 0ex; }
196ul.list-bul { list-style-type: disc; padding-left: 1em; }
197ul.list-dash { list-style-type: none; padding-left: 0em; }
198li.list-dash:before { content: "\2014  "; }
199ul.list-hyph { list-style-type: none; padding-left: 0em; }
200li.list-hyph:before { content: "\2013  "; }
201ul.list-item { list-style-type: none; padding-left: 0em; }
202ol.list-enum { padding-left: 2em; }
203'
204
205#
206# Display remote target with prefix and trailing slash.
207#
208function print_upload_header
209{
210	typeset -r prefix=$1
211	typeset display_target
212
213	if [[ -z $tflag ]]; then
214		display_target=${prefix}${remote_target}
215	else
216		display_target=${remote_target}
217	fi
218
219	if [[ ${display_target} != */ ]]; then
220		display_target=${display_target}/
221	fi
222
223	print "      Upload to: ${display_target}\n" \
224	    "     Uploading: \c"
225}
226
227#
228# Upload the webrev via rsync. Return 0 on success, 1 on error.
229#
230function rsync_upload
231{
232	if (( $# != 2 )); then
233		print "\nERROR: rsync_upload: wrong usage ($#)"
234		exit 1
235	fi
236
237	typeset -r dst=$1
238	integer -r print_err_msg=$2
239
240	print_upload_header ${rsync_prefix}
241	print "rsync ... \c"
242	typeset -r err_msg=$( $MKTEMP /tmp/rsync_err.XXXXXX )
243	if [[ -z $err_msg ]]; then
244		print "\nERROR: rsync_upload: cannot create temporary file"
245		return 1
246	fi
247	#
248	# The source directory must end with a slash in order to copy just
249	# directory contents, not the whole directory.
250	#
251	typeset src_dir=$WDIR
252	if [[ ${src_dir} != */ ]]; then
253		src_dir=${src_dir}/
254	fi
255	$RSYNC -r -q ${src_dir} $dst 2>$err_msg
256	if (( $? != 0 )); then
257		if (( ${print_err_msg} > 0 )); then
258			print "Failed.\nERROR: rsync failed"
259			print "src dir: '${src_dir}'\ndst dir: '$dst'"
260			print "error messages:"
261			$SED 's/^/> /' $err_msg
262			rm -f $err_msg
263		fi
264		return 1
265	fi
266
267	rm -f $err_msg
268	print "Done."
269	return 0
270}
271
272#
273# Create directories on remote host using SFTP. Return 0 on success,
274# 1 on failure.
275#
276function remote_mkdirs
277{
278	typeset -r dir_spec=$1
279	typeset -r host_spec=$2
280
281	#
282	# If the supplied path is absolute we assume all directories are
283	# created, otherwise try to create all directories in the path
284	# except the last one which will be created by scp.
285	#
286	if [[ "${dir_spec}" == */* && "${dir_spec}" != /* ]]; then
287		print "mkdirs \c"
288		#
289		# Remove the last directory from directory specification.
290		#
291		typeset -r dirs_mk=${dir_spec%/*}
292		typeset -r batch_file_mkdir=$( $MKTEMP \
293		    /tmp/webrev_mkdir.XXXXXX )
294		if [[ -z $batch_file_mkdir ]]; then
295			print "\nERROR: remote_mkdirs:" \
296			    "cannot create temporary file for batch file"
297			return 1
298		fi
299		OLDIFS=$IFS
300		IFS=/
301		typeset dir
302		for dir in ${dirs_mk}; do
303			#
304			# Use the '-' prefix to ignore mkdir errors in order
305			# to avoid an error in case the directory already
306			# exists. We check the directory with chdir to be sure
307			# there is one.
308			#
309			print -- "-mkdir ${dir}" >> ${batch_file_mkdir}
310			print "chdir ${dir}" >> ${batch_file_mkdir}
311		done
312		IFS=$OLDIFS
313		typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
314		if [[ -z ${sftp_err_msg} ]]; then
315			print "\nERROR: remote_mkdirs:" \
316			    "cannot create temporary file for error messages"
317			return 1
318		fi
319		$SFTP -b ${batch_file_mkdir} ${host_spec} 2>${sftp_err_msg} 1>&2
320		if (( $? != 0 )); then
321			print "\nERROR: failed to create remote directories"
322			print "error messages:"
323			$SED 's/^/> /' ${sftp_err_msg}
324			rm -f ${sftp_err_msg} ${batch_file_mkdir}
325			return 1
326		fi
327		rm -f ${sftp_err_msg} ${batch_file_mkdir}
328	fi
329
330	return 0
331}
332
333#
334# Upload the webrev via SSH. Return 0 on success, 1 on error.
335#
336function ssh_upload
337{
338	if (( $# != 1 )); then
339		print "\nERROR: ssh_upload: wrong number of arguments"
340		exit 1
341	fi
342
343	typeset dst=$1
344	typeset -r host_spec=${dst%%:*}
345	typeset -r dir_spec=${dst#*:}
346
347	#
348	# Display the upload information before calling delete_webrev
349	# because it will also print its progress.
350	#
351	print_upload_header ${ssh_prefix}
352
353	#
354	# If the deletion was explicitly requested there is no need
355	# to perform it again.
356	#
357	if [[ -z $Dflag ]]; then
358		#
359		# We do not care about return value because this might be
360		# the first time this directory is uploaded.
361		#
362		delete_webrev 0
363	fi
364
365	#
366	# Create remote directories. Any error reporting will be done
367	# in remote_mkdirs function.
368	#
369	remote_mkdirs ${dir_spec} ${host_spec}
370	if (( $? != 0 )); then
371		return 1
372	fi
373
374	print "upload ... \c"
375	typeset -r scp_err_msg=$( $MKTEMP /tmp/scp_err.XXXXXX )
376	if [[ -z ${scp_err_msg} ]]; then
377		print "\nERROR: ssh_upload:" \
378		    "cannot create temporary file for error messages"
379		return 1
380	fi
381	$SCP -q -C -B -o PreferredAuthentications=publickey -r \
382		$WDIR $dst 2>${scp_err_msg}
383	if (( $? != 0 )); then
384		print "Failed.\nERROR: scp failed"
385		print "src dir: '$WDIR'\ndst dir: '$dst'"
386		print "error messages:"
387		$SED 's/^/> /' ${scp_err_msg}
388		rm -f ${scp_err_msg}
389		return 1
390	fi
391
392	rm -f ${scp_err_msg}
393	print "Done."
394	return 0
395}
396
397#
398# Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp
399# on failure. If first argument is 1 then perform the check of sftp return
400# value otherwise ignore it. If second argument is present it means this run
401# only performs deletion.
402#
403function delete_webrev
404{
405	if (( $# < 1 )); then
406		print "delete_webrev: wrong number of arguments"
407		exit 1
408	fi
409
410	integer -r check=$1
411	integer delete_only=0
412	if (( $# == 2 )); then
413		delete_only=1
414	fi
415
416	#
417	# Strip the transport specification part of remote target first.
418	#
419	typeset -r stripped_target=${remote_target##*://}
420	typeset -r host_spec=${stripped_target%%:*}
421	typeset -r dir_spec=${stripped_target#*:}
422	typeset dir_rm
423
424	#
425	# Do not accept an absolute path.
426	#
427	if [[ ${dir_spec} == /* ]]; then
428		return 1
429	fi
430
431	#
432	# Strip the ending slash.
433	#
434	if [[ ${dir_spec} == */ ]]; then
435		dir_rm=${dir_spec%%/}
436	else
437		dir_rm=${dir_spec}
438	fi
439
440	if (( ${delete_only} > 0 )); then
441		print "       Removing: \c"
442	else
443		print "rmdir \c"
444	fi
445	if [[ -z "$dir_rm" ]]; then
446		print "\nERROR: empty directory for removal"
447		return 1
448	fi
449
450	#
451	# Prepare batch file.
452	#
453	typeset -r batch_file_rm=$( $MKTEMP /tmp/webrev_remove.XXXXXX )
454	if [[ -z $batch_file_rm ]]; then
455		print "\nERROR: delete_webrev: cannot create temporary file"
456		return 1
457	fi
458	print "rename $dir_rm $TRASH_DIR/removed.$$" > $batch_file_rm
459
460	#
461	# Perform remote deletion and remove the batch file.
462	#
463	typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
464	if [[ -z ${sftp_err_msg} ]]; then
465		print "\nERROR: delete_webrev:" \
466		    "cannot create temporary file for error messages"
467		return 1
468	fi
469	$SFTP -b $batch_file_rm $host_spec 2>${sftp_err_msg} 1>&2
470	integer -r ret=$?
471	rm -f $batch_file_rm
472	if (( $ret != 0 && $check > 0 )); then
473		print "Failed.\nERROR: failed to remove remote directories"
474		print "error messages:"
475		$SED 's/^/> /' ${sftp_err_msg}
476		rm -f ${sftp_err_msg}
477		return $ret
478	fi
479	rm -f ${sftp_err_msg}
480	if (( ${delete_only} > 0 )); then
481		print "Done."
482	fi
483
484	return 0
485}
486
487#
488# Upload webrev to remote site
489#
490function upload_webrev
491{
492	integer ret
493
494	if [[ ! -d "$WDIR" ]]; then
495		print "\nERROR: webrev directory '$WDIR' does not exist"
496		return 1
497	fi
498
499	#
500	# Perform a late check to make sure we do not upload closed source
501	# to remote target when -n is used. If the user used custom remote
502	# target he probably knows what he is doing.
503	#
504	if [[ -n $nflag && -z $tflag ]]; then
505		$FIND $WDIR -type d -name closed \
506			| $GREP closed >/dev/null
507		if (( $? == 0 )); then
508			print "\nERROR: directory '$WDIR' contains" \
509			    "\"closed\" directory"
510			return 1
511		fi
512	fi
513
514
515	#
516	# We have the URI for remote destination now so let's start the upload.
517	#
518	if [[ -n $tflag ]]; then
519		if [[ "${remote_target}" == ${rsync_prefix}?* ]]; then
520			rsync_upload ${remote_target##$rsync_prefix} 1
521			ret=$?
522			return $ret
523		elif [[ "${remote_target}" == ${ssh_prefix}?* ]]; then
524			ssh_upload ${remote_target##$ssh_prefix}
525			ret=$?
526			return $ret
527		fi
528	else
529		#
530		# Try rsync first and fallback to SSH in case it fails.
531		#
532		rsync_upload ${remote_target} 0
533		ret=$?
534		if (( $ret != 0 )); then
535			print "Failed. (falling back to SSH)"
536			ssh_upload ${remote_target}
537			ret=$?
538		fi
539		return $ret
540	fi
541}
542
543#
544# input_cmd | url_encode | output_cmd
545#
546# URL-encode (percent-encode) reserved characters as defined in RFC 3986.
547#
548# Reserved characters are: :/?#[]@!$&'()*+,;=
549#
550# While not a reserved character itself, percent '%' is reserved by definition
551# so encode it first to avoid recursive transformation, and skip '/' which is
552# a path delimiter.
553#
554# The quotation character is deliberately not escaped in order to make
555# the substitution work with GNU sed.
556#
557function url_encode
558{
559	$SED -e "s|%|%25|g" -e "s|:|%3A|g" -e "s|\&|%26|g" \
560	    -e "s|?|%3F|g" -e "s|#|%23|g" -e "s|\[|%5B|g" \
561	    -e "s|*|%2A|g" -e "s|@|%40|g" -e "s|\!|%21|g" \
562	    -e "s|=|%3D|g" -e "s|;|%3B|g" -e "s|\]|%5D|g" \
563	    -e "s|(|%28|g" -e "s|)|%29|g" -e "s|'|%27|g" \
564	    -e "s|+|%2B|g" -e "s|\,|%2C|g" -e "s|\\\$|%24|g"
565}
566
567#
568# input_cmd | html_quote | output_cmd
569# or
570# html_quote filename | output_cmd
571#
572# Make a piece of source code safe for display in an HTML <pre> block.
573#
574html_quote()
575{
576	$SED -e "s/&/\&amp;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g" "$@" | expand
577}
578
579#
580# Trim a digest-style revision to a conventionally readable yet useful length
581#
582trim_digest()
583{
584	typeset digest=$1
585
586	echo $digest | $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'
587}
588
589#
590# input_cmd | its2url | output_cmd
591#
592# Scan for information tracking system references and insert <a> links to the
593# relevant databases.
594#
595its2url()
596{
597	$SED -f ${its_sed_script}
598}
599
600#
601# strip_unchanged <infile> | output_cmd
602#
603# Removes chunks of sdiff documents that have not changed. This makes it
604# easier for a code reviewer to find the bits that have changed.
605#
606# Deleted lines of text are replaced by a horizontal rule. Some
607# identical lines are retained before and after the changed lines to
608# provide some context.  The number of these lines is controlled by the
609# variable C in the $AWK script below.
610#
611# The script detects changed lines as any line that has a "<span class="
612# string embedded (unchanged lines have no particular class and are not
613# part of a <span>).  Blank lines (without a sequence number) are also
614# detected since they flag lines that have been inserted or deleted.
615#
616strip_unchanged()
617{
618	$AWK '
619	BEGIN	{ C = c = 20 }
620	NF == 0 || /<span class="/ {
621		if (c > C) {
622			c -= C
623			inx = 0
624			if (c > C) {
625				print "\n</pre><hr></hr><pre>"
626				inx = c % C
627				c = C
628			}
629
630			for (i = 0; i < c; i++)
631				print ln[(inx + i) % C]
632		}
633		c = 0;
634		print
635		next
636	}
637	{	if (c >= C) {
638			ln[c % C] = $0
639			c++;
640			next;
641		}
642		c++;
643		print
644	}
645	END	{ if (c > (C * 2)) print "\n</pre><hr></hr>" }
646
647	' $1
648}
649
650#
651# sdiff_to_html
652#
653# This function takes two files as arguments, obtains their diff, and
654# processes the diff output to present the files as an HTML document with
655# the files displayed side-by-side, differences shown in color.  It also
656# takes a delta comment, rendered as an HTML snippet, as the third
657# argument.  The function takes two files as arguments, then the name of
658# file, the path, and the comment.  The HTML will be delivered on stdout,
659# e.g.
660#
661#   $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
662#         new/usr/src/tools/scripts/webrev.sh \
663#         webrev.sh usr/src/tools/scripts \
664#         '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
665#          1234567</a> my bugid' > <file>.html
666#
667# framed_sdiff() is then called which creates $2.frames.html
668# in the webrev tree.
669#
670# FYI: This function is rather unusual in its use of awk.  The initial
671# diff run produces conventional diff output showing changed lines mixed
672# with editing codes.  The changed lines are ignored - we're interested in
673# the editing codes, e.g.
674#
675#      8c8
676#      57a61
677#      63c66,76
678#      68,93d80
679#      106d90
680#      108,110d91
681#
682#  These editing codes are parsed by the awk script and used to generate
683#  another awk script that generates HTML, e.g the above lines would turn
684#  into something like this:
685#
686#      BEGIN { printf "<pre>\n" }
687#      function sp(n) {for (i=0;i<n;i++)printf "\n"}
688#      function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
689#      NR==8           {wl("#7A7ADD");next}
690#      NR==54          {wl("#7A7ADD");sp(3);next}
691#      NR==56          {wl("#7A7ADD");next}
692#      NR==57          {wl("black");printf "\n"; next}
693#        :               :
694#
695#  This script is then run on the original source file to generate the
696#  HTML that corresponds to the source file.
697#
698#  The two HTML files are then combined into a single piece of HTML that
699#  uses an HTML table construct to present the files side by side.  You'll
700#  notice that the changes are color-coded:
701#
702#   black     - unchanged lines
703#   blue      - changed lines
704#   bold blue - new lines
705#   brown     - deleted lines
706#
707#  Blank lines are inserted in each file to keep unchanged lines in sync
708#  (side-by-side).  This format is familiar to users of sdiff(1) or
709#  Teamware's filemerge tool.
710#
711sdiff_to_html()
712{
713	diff -b $1 $2 > /tmp/$$.diffs
714
715	TNAME=$3
716	TPATH=$4
717	COMMENT=$5
718
719	#
720	#  Now we have the diffs, generate the HTML for the old file.
721	#
722	$AWK '
723	BEGIN	{
724		printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
725		printf "function removed() "
726		printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
727		printf "function changed() "
728		printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
729		printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
730}
731	/^</	{next}
732	/^>/	{next}
733	/^---/	{next}
734
735	{
736	split($1, a, /[cad]/) ;
737	if (index($1, "a")) {
738		if (a[1] == 0) {
739			n = split(a[2], r, /,/);
740			if (n == 1)
741				printf "BEGIN\t\t{sp(1)}\n"
742			else
743				printf "BEGIN\t\t{sp(%d)}\n",\
744				(r[2] - r[1]) + 1
745			next
746		}
747
748		printf "NR==%s\t\t{", a[1]
749		n = split(a[2], r, /,/);
750		s = r[1];
751		if (n == 1)
752			printf "bl();printf \"\\n\"; next}\n"
753		else {
754			n = r[2] - r[1]
755			printf "bl();sp(%d);next}\n",\
756			(r[2] - r[1]) + 1
757		}
758		next
759	}
760	if (index($1, "d")) {
761		n = split(a[1], r, /,/);
762		n1 = r[1]
763		n2 = r[2]
764		if (n == 1)
765			printf "NR==%s\t\t{removed(); next}\n" , n1
766		else
767			printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
768		next
769	}
770	if (index($1, "c")) {
771		n = split(a[1], r, /,/);
772		n1 = r[1]
773		n2 = r[2]
774		final = n2
775		d1 = 0
776		if (n == 1)
777			printf "NR==%s\t\t{changed();" , n1
778		else {
779			d1 = n2 - n1
780			printf "NR==%s,NR==%s\t{changed();" , n1, n2
781		}
782		m = split(a[2], r, /,/);
783		n1 = r[1]
784		n2 = r[2]
785		if (m > 1) {
786			d2  = n2 - n1
787			if (d2 > d1) {
788				if (n > 1) printf "if (NR==%d)", final
789				printf "sp(%d);", d2 - d1
790			}
791		}
792		printf "next}\n" ;
793
794		next
795	}
796	}
797
798	END	{ printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
799	' /tmp/$$.diffs > /tmp/$$.file1
800
801	#
802	#  Now generate the HTML for the new file
803	#
804	$AWK '
805	BEGIN	{
806		printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
807		printf "function new() "
808		printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
809		printf "function changed() "
810		printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
811		printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
812	}
813
814	/^</	{next}
815	/^>/	{next}
816	/^---/	{next}
817
818	{
819	split($1, a, /[cad]/) ;
820	if (index($1, "d")) {
821		if (a[2] == 0) {
822			n = split(a[1], r, /,/);
823			if (n == 1)
824				printf "BEGIN\t\t{sp(1)}\n"
825			else
826				printf "BEGIN\t\t{sp(%d)}\n",\
827				(r[2] - r[1]) + 1
828			next
829		}
830
831		printf "NR==%s\t\t{", a[2]
832		n = split(a[1], r, /,/);
833		s = r[1];
834		if (n == 1)
835			printf "bl();printf \"\\n\"; next}\n"
836		else {
837			n = r[2] - r[1]
838			printf "bl();sp(%d);next}\n",\
839			(r[2] - r[1]) + 1
840		}
841		next
842	}
843	if (index($1, "a")) {
844		n = split(a[2], r, /,/);
845		n1 = r[1]
846		n2 = r[2]
847		if (n == 1)
848			printf "NR==%s\t\t{new() ; next}\n" , n1
849		else
850			printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
851		next
852	}
853	if (index($1, "c")) {
854		n = split(a[2], r, /,/);
855		n1 = r[1]
856		n2 = r[2]
857		final = n2
858		d2 = 0;
859		if (n == 1) {
860			final = n1
861			printf "NR==%s\t\t{changed();" , n1
862		} else {
863			d2 = n2 - n1
864			printf "NR==%s,NR==%s\t{changed();" , n1, n2
865		}
866		m = split(a[1], r, /,/);
867		n1 = r[1]
868		n2 = r[2]
869		if (m > 1) {
870			d1  = n2 - n1
871			if (d1 > d2) {
872				if (n > 1) printf "if (NR==%d)", final
873				printf "sp(%d);", d1 - d2
874			}
875		}
876		printf "next}\n" ;
877		next
878	}
879	}
880	END	{ printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
881	' /tmp/$$.diffs > /tmp/$$.file2
882
883	#
884	# Post-process the HTML files by running them back through $AWK
885	#
886	html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html
887
888	html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html
889
890	#
891	# Now combine into a valid HTML file and side-by-side into a table
892	#
893	print "$HTML<head>$STDHEAD"
894	print "<title>$WNAME Sdiff $TPATH/$TNAME</title>"
895	print "</head><body id=\"SUNWwebrev\">"
896	print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
897	print "<pre>$COMMENT</pre>\n"
898	print "<table><tr valign=\"top\">"
899	print "<td><pre>"
900
901	strip_unchanged /tmp/$$.file1.html
902
903	print "</pre></td><td><pre>"
904
905	strip_unchanged /tmp/$$.file2.html
906
907	print "</pre></td>"
908	print "</tr></table>"
909	print "</body></html>"
910
911	framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
912	    "$COMMENT"
913}
914
915
916#
917# framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
918#
919# Expects lefthand and righthand side html files created by sdiff_to_html.
920# We use insert_anchors() to augment those with HTML navigation anchors,
921# and then emit the main frame.  Content is placed into:
922#
923#    $WDIR/DIR/$TNAME.lhs.html
924#    $WDIR/DIR/$TNAME.rhs.html
925#    $WDIR/DIR/$TNAME.frames.html
926#
927# NOTE: We rely on standard usage of $WDIR and $DIR.
928#
929function framed_sdiff
930{
931	typeset TNAME=$1
932	typeset TPATH=$2
933	typeset lhsfile=$3
934	typeset rhsfile=$4
935	typeset comments=$5
936	typeset RTOP
937
938	# Enable html files to access WDIR via a relative path.
939	RTOP=$(relative_dir $TPATH $WDIR)
940
941	# Make the rhs/lhs files and output the frameset file.
942	print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
943
944	cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
945	    <script type="text/javascript" src="${RTOP}ancnav.js"></script>
946	    </head>
947	    <body id="SUNWwebrev" onkeypress="keypress(event);">
948	    <a name="0"></a>
949	    <pre>$comments</pre><hr></hr>
950	EOF
951
952	cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
953
954	insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
955	insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
956
957	close='</body></html>'
958
959	print $close >> $WDIR/$DIR/$TNAME.lhs.html
960	print $close >> $WDIR/$DIR/$TNAME.rhs.html
961
962	print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
963	print "<title>$WNAME Framed-Sdiff " \
964	    "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
965	cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
966	  <frameset rows="*,60">
967	    <frameset cols="50%,50%">
968	      <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
969	      <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
970	    </frameset>
971	  <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0"
972	   marginheight="0" name="nav"></frame>
973	  <noframes>
974	    <body id="SUNWwebrev">
975	      Alas 'frames' webrev requires that your browser supports frames
976	      and has the feature enabled.
977	    </body>
978	  </noframes>
979	  </frameset>
980	</html>
981	EOF
982}
983
984
985#
986# fix_postscript
987#
988# Merge codereview output files to a single conforming postscript file, by:
989#	- removing all extraneous headers/trailers
990#	- making the page numbers right
991#	- removing pages devoid of contents which confuse some
992#	  postscript readers.
993#
994# From Casper.
995#
996function fix_postscript
997{
998	infile=$1
999
1000	cat > /tmp/$$.crmerge.pl << \EOF
1001
1002	print scalar(<>);		# %!PS-Adobe---
1003	print "%%Orientation: Landscape\n";
1004
1005	$pno = 0;
1006	$doprint = 1;
1007
1008	$page = "";
1009
1010	while (<>) {
1011		next if (/^%%Pages:\s*\d+/);
1012
1013		if (/^%%Page:/) {
1014			if ($pno == 0 || $page =~ /\)S/) {
1015				# Header or single page containing text
1016				print "%%Page: ? $pno\n" if ($pno > 0);
1017				print $page;
1018				$pno++;
1019			} else {
1020				# Empty page, skip it.
1021			}
1022			$page = "";
1023			$doprint = 1;
1024			next;
1025		}
1026
1027		# Skip from %%Trailer of one document to Endprolog
1028		# %%Page of the next
1029		$doprint = 0 if (/^%%Trailer/);
1030		$page .= $_ if ($doprint);
1031	}
1032
1033	if ($page =~ /\)S/) {
1034		print "%%Page: ? $pno\n";
1035		print $page;
1036	} else {
1037		$pno--;
1038	}
1039	print "%%Trailer\n%%Pages: $pno\n";
1040EOF
1041
1042	$PERL /tmp/$$.crmerge.pl < $infile
1043}
1044
1045
1046#
1047# input_cmd | insert_anchors | output_cmd
1048#
1049# Flag blocks of difference with sequentially numbered invisible
1050# anchors.  These are used to drive the frames version of the
1051# sdiffs output.
1052#
1053# NOTE: Anchor zero flags the top of the file irrespective of changes,
1054# an additional anchor is also appended to flag the bottom.
1055#
1056# The script detects changed lines as any line that has a "<span
1057# class=" string embedded (unchanged lines have no class set and are
1058# not part of a <span>.  Blank lines (without a sequence number)
1059# are also detected since they flag lines that have been inserted or
1060# deleted.
1061#
1062function insert_anchors
1063{
1064	$AWK '
1065	function ia() {
1066		printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1067	}
1068
1069	BEGIN {
1070		anc=1;
1071		inblock=1;
1072		printf "<pre>\n";
1073	}
1074	NF == 0 || /^<span class=/ {
1075		if (inblock == 0) {
1076			ia();
1077			inblock=1;
1078		}
1079		print;
1080		next;
1081	}
1082	{
1083		inblock=0;
1084		print;
1085	}
1086	END {
1087		ia();
1088
1089		printf "<b style=\"font-size: large; color: red\">";
1090		printf "--- EOF ---</b>"
1091		for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1092		printf "</pre>"
1093		printf "<form name=\"eof\">";
1094		printf "<input name=\"value\" value=\"%d\" " \
1095		    "type=\"hidden\"></input>", anc - 1;
1096		printf "</form>";
1097	}
1098	' $1
1099}
1100
1101
1102#
1103# relative_dir
1104#
1105# Print a relative return path from $1 to $2.  For example if
1106# $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1107# this function would print "../../../../".
1108#
1109# In the event that $1 is not in $2 a warning is printed to stderr,
1110# and $2 is returned-- the result of this is that the resulting webrev
1111# is not relocatable.
1112#
1113function relative_dir
1114{
1115	typeset cur="${1##$2?(/)}"
1116
1117	#
1118	# If the first path was specified absolutely, and it does
1119	# not start with the second path, it's an error.
1120	#
1121	if [[ "$cur" = "/${1#/}" ]]; then
1122		# Should never happen.
1123		print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1124		print -u2 "to \"$2\".  Check input paths.  Framed webrev "
1125		print -u2 "will not be relocatable!"
1126		print $2
1127		return
1128	fi
1129
1130	#
1131	# This is kind of ugly.  The sed script will do the following:
1132	#
1133	# 1. Strip off a leading "." or "./": this is important to get
1134	#    the correct arcnav links for files in $WDIR.
1135	# 2. Strip off a trailing "/": this is not strictly necessary,
1136	#    but is kind of nice, since it doesn't end up in "//" at
1137	#    the end of a relative path.
1138	# 3. Replace all remaining sequences of non-"/" with "..": the
1139	#    assumption here is that each dirname represents another
1140	#    level of relative separation.
1141	# 4. Append a trailing "/" only for non-empty paths: this way
1142	#    the caller doesn't need to duplicate this logic, and does
1143	#    not end up using $RTOP/file for files in $WDIR.
1144	#
1145	print $cur | $SED -e '{
1146		s:^\./*::
1147		s:/$::
1148		s:[^/][^/]*:..:g
1149		s:^\(..*\)$:\1/:
1150	}'
1151}
1152
1153#
1154# frame_nav_js
1155#
1156# Emit javascript for frame navigation
1157#
1158function frame_nav_js
1159{
1160cat << \EOF
1161var myInt;
1162var scrolling = 0;
1163var sfactor = 3;
1164var scount = 10;
1165
1166function scrollByPix()
1167{
1168	if (scount <= 0) {
1169		sfactor *= 1.2;
1170		scount = 10;
1171	}
1172	parent.lhs.scrollBy(0, sfactor);
1173	parent.rhs.scrollBy(0, sfactor);
1174	scount--;
1175}
1176
1177function scrollToAnc(num)
1178{
1179	// Update the value of the anchor in the form which we use as
1180	// storage for this value.  setAncValue() will take care of
1181	// correcting for overflow and underflow of the value and return
1182	// us the new value.
1183	num = setAncValue(num);
1184
1185	// Set location and scroll back a little to expose previous
1186	// lines.
1187	//
1188	// Note that this could be improved: it is possible although
1189	// complex to compute the x and y position of an anchor, and to
1190	// scroll to that location directly.
1191	//
1192	parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1193	parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1194
1195	parent.lhs.scrollBy(0, -30);
1196	parent.rhs.scrollBy(0, -30);
1197}
1198
1199function getAncValue()
1200{
1201	return (parseInt(parent.nav.document.diff.real.value));
1202}
1203
1204function setAncValue(val)
1205{
1206	if (val <= 0) {
1207		val = 0;
1208		parent.nav.document.diff.real.value = val;
1209		parent.nav.document.diff.display.value = "BOF";
1210		return (val);
1211	}
1212
1213	//
1214	// The way we compute the max anchor value is to stash it
1215	// inline in the left and right hand side pages-- it's the same
1216	// on each side, so we pluck from the left.
1217	//
1218	maxval = parent.lhs.document.eof.value.value;
1219	if (val < maxval) {
1220		parent.nav.document.diff.real.value = val;
1221		parent.nav.document.diff.display.value = val.toString();
1222		return (val);
1223	}
1224
1225	// this must be: val >= maxval
1226	val = maxval;
1227	parent.nav.document.diff.real.value = val;
1228	parent.nav.document.diff.display.value = "EOF";
1229	return (val);
1230}
1231
1232function stopScroll()
1233{
1234	if (scrolling == 1) {
1235		clearInterval(myInt);
1236		scrolling = 0;
1237	}
1238}
1239
1240function startScroll()
1241{
1242	stopScroll();
1243	scrolling = 1;
1244	myInt = setInterval("scrollByPix()", 10);
1245}
1246
1247function handlePress(b)
1248{
1249	switch (b) {
1250	case 1:
1251		scrollToAnc(-1);
1252		break;
1253	case 2:
1254		scrollToAnc(getAncValue() - 1);
1255		break;
1256	case 3:
1257		sfactor = -3;
1258		startScroll();
1259		break;
1260	case 4:
1261		sfactor = 3;
1262		startScroll();
1263		break;
1264	case 5:
1265		scrollToAnc(getAncValue() + 1);
1266		break;
1267	case 6:
1268		scrollToAnc(999999);
1269		break;
1270	}
1271}
1272
1273function handleRelease(b)
1274{
1275	stopScroll();
1276}
1277
1278function keypress(ev)
1279{
1280	var keynum;
1281	var keychar;
1282
1283	if (window.event) { // IE
1284		keynum = ev.keyCode;
1285	} else if (ev.which) { // non-IE
1286		keynum = ev.which;
1287	}
1288
1289	keychar = String.fromCharCode(keynum);
1290
1291	if (keychar == "k") {
1292		handlePress(2);
1293		return (0);
1294	} else if (keychar == "j" || keychar == " ") {
1295		handlePress(5);
1296		return (0);
1297	}
1298
1299	return (1);
1300}
1301
1302function ValidateDiffNum()
1303{
1304	var val;
1305	var i;
1306
1307	val = parent.nav.document.diff.display.value;
1308	if (val == "EOF") {
1309		scrollToAnc(999999);
1310		return;
1311	}
1312
1313	if (val == "BOF") {
1314		scrollToAnc(0);
1315		return;
1316	}
1317
1318	i = parseInt(val);
1319	if (isNaN(i)) {
1320		parent.nav.document.diff.display.value = getAncValue();
1321	} else {
1322		scrollToAnc(i);
1323	}
1324
1325	return (false);
1326}
1327EOF
1328}
1329
1330#
1331# frame_navigation
1332#
1333# Output anchor navigation file for framed sdiffs.
1334#
1335function frame_navigation
1336{
1337	print "$HTML<head>$STDHEAD"
1338
1339	cat << \EOF
1340<title>Anchor Navigation</title>
1341<meta http-equiv="Content-Script-Type" content="text/javascript">
1342<meta http-equiv="Content-Type" content="text/html">
1343
1344<style type="text/css">
1345    div.button td { padding-left: 5px; padding-right: 5px;
1346		    background-color: #eee; text-align: center;
1347		    border: 1px #444 outset; cursor: pointer; }
1348    div.button a { font-weight: bold; color: black }
1349    div.button td:hover { background: #ffcc99; }
1350</style>
1351EOF
1352
1353	print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1354
1355	cat << \EOF
1356</head>
1357<body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1358	onkeypress="keypress(event);">
1359    <noscript lang="javascript">
1360      <center>
1361	<p><big>Framed Navigation controls require Javascript</big><br></br>
1362	Either this browser is incompatable or javascript is not enabled</p>
1363      </center>
1364    </noscript>
1365    <table width="100%" border="0" align="center">
1366	<tr>
1367          <td valign="middle" width="25%">Diff navigation:
1368          Use 'j' and 'k' for next and previous diffs; or use buttons
1369          at right</td>
1370	  <td align="center" valign="top" width="50%">
1371	    <div class="button">
1372	      <table border="0" align="center">
1373                  <tr>
1374		    <td>
1375		      <a onMouseDown="handlePress(1);return true;"
1376			 onMouseUp="handleRelease(1);return true;"
1377			 onMouseOut="handleRelease(1);return true;"
1378			 onClick="return false;"
1379			 title="Go to Beginning Of file">BOF</a></td>
1380		    <td>
1381		      <a onMouseDown="handlePress(3);return true;"
1382			 onMouseUp="handleRelease(3);return true;"
1383			 onMouseOut="handleRelease(3);return true;"
1384			 title="Scroll Up: Press and Hold to accelerate"
1385			 onClick="return false;">Scroll Up</a></td>
1386		    <td>
1387		      <a onMouseDown="handlePress(2);return true;"
1388			 onMouseUp="handleRelease(2);return true;"
1389			 onMouseOut="handleRelease(2);return true;"
1390			 title="Go to previous Diff"
1391			 onClick="return false;">Prev Diff</a>
1392		    </td></tr>
1393
1394		  <tr>
1395		    <td>
1396		      <a onMouseDown="handlePress(6);return true;"
1397			 onMouseUp="handleRelease(6);return true;"
1398			 onMouseOut="handleRelease(6);return true;"
1399			 onClick="return false;"
1400			 title="Go to End Of File">EOF</a></td>
1401		    <td>
1402		      <a onMouseDown="handlePress(4);return true;"
1403			 onMouseUp="handleRelease(4);return true;"
1404			 onMouseOut="handleRelease(4);return true;"
1405			 title="Scroll Down: Press and Hold to accelerate"
1406			 onClick="return false;">Scroll Down</a></td>
1407		    <td>
1408		      <a onMouseDown="handlePress(5);return true;"
1409			 onMouseUp="handleRelease(5);return true;"
1410			 onMouseOut="handleRelease(5);return true;"
1411			 title="Go to next Diff"
1412			 onClick="return false;">Next Diff</a></td>
1413		  </tr>
1414              </table>
1415	    </div>
1416	  </td>
1417	  <th valign="middle" width="25%">
1418	    <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1419		<input name="display" value="BOF" size="8" type="text"></input>
1420		<input name="real" value="0" size="8" type="hidden"></input>
1421	    </form>
1422	  </th>
1423	</tr>
1424    </table>
1425  </body>
1426</html>
1427EOF
1428}
1429
1430
1431
1432#
1433# diff_to_html <filename> <filepath> { U | C } <comment>
1434#
1435# Processes the output of diff to produce an HTML file representing either
1436# context or unified diffs.
1437#
1438diff_to_html()
1439{
1440	TNAME=$1
1441	TPATH=$2
1442	DIFFTYPE=$3
1443	COMMENT=$4
1444
1445	print "$HTML<head>$STDHEAD"
1446	print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1447
1448	if [[ $DIFFTYPE == "U" ]]; then
1449		print "$UDIFFCSS"
1450	fi
1451
1452	cat <<-EOF
1453	</head>
1454	<body id="SUNWwebrev">
1455        <a class="print" href="javascript:print()">Print this page</a>
1456	<pre>$COMMENT</pre>
1457        <pre>
1458	EOF
1459
1460	html_quote | $AWK '
1461	/^--- new/	{ next }
1462	/^\+\+\+ new/	{ next }
1463	/^--- old/	{ next }
1464	/^\*\*\* old/	{ next }
1465	/^\*\*\*\*/	{ next }
1466	/^-------/	{ printf "<center><h1>%s</h1></center>\n", $0; next }
1467	/^\@\@.*\@\@$/	{ printf "</pre><hr></hr><pre>\n";
1468			  printf "<span class=\"newmarker\">%s</span>\n", $0;
1469			  next}
1470
1471	/^\*\*\*/	{ printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1472			  next}
1473	/^---/		{ printf "<span class=\"newmarker\">%s</span>\n", $0;
1474			  next}
1475	/^\+/		{printf "<span class=\"new\">%s</span>\n", $0; next}
1476	/^!/		{printf "<span class=\"changed\">%s</span>\n", $0; next}
1477	/^-/		{printf "<span class=\"removed\">%s</span>\n", $0; next}
1478			{printf "%s\n", $0; next}
1479	'
1480
1481	print "</pre></body></html>\n"
1482}
1483
1484
1485#
1486# source_to_html { new | old } <filename>
1487#
1488# Process a plain vanilla source file to transform it into an HTML file.
1489#
1490source_to_html()
1491{
1492	WHICH=$1
1493	TNAME=$2
1494
1495	print "$HTML<head>$STDHEAD"
1496	print "<title>$WNAME $WHICH $TNAME</title>"
1497	print "<body id=\"SUNWwebrev\">"
1498	print "<pre>"
1499	html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1500	print "</pre></body></html>"
1501}
1502
1503#
1504# comments_from_wx {text|html} filepath
1505#
1506# Given the pathname of a file, find its location in a "wx" active
1507# file list and print the following comment.  Output is either text or
1508# HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1509# are turned into URLs.
1510#
1511# This is also used with Mercurial and the file list provided by hg-active.
1512#
1513comments_from_wx()
1514{
1515	typeset fmt=$1
1516	typeset p=$2
1517
1518	comm=`$AWK '
1519	$1 == "'$p'" {
1520		do getline ; while (NF > 0)
1521		getline
1522		while (NF > 0) { print ; getline }
1523		exit
1524	}' < $wxfile`
1525
1526	if [[ -z $comm ]]; then
1527		comm="*** NO COMMENTS ***"
1528	fi
1529
1530	if [[ $fmt == "text" ]]; then
1531		print -- "$comm"
1532		return
1533	fi
1534
1535	print -- "$comm" | html_quote | its2url
1536
1537}
1538
1539#
1540# getcomments {text|html} filepath parentpath
1541#
1542# Fetch the comments depending on what SCM mode we're in.
1543#
1544getcomments()
1545{
1546	typeset fmt=$1
1547	typeset p=$2
1548	typeset pp=$3
1549
1550	if [[ -n $Nflag ]]; then
1551		return
1552	fi
1553	#
1554	# Mercurial support uses a file list in wx format, so this
1555	# will be used there, too
1556	#
1557	if [[ -n $wxfile ]]; then
1558		comments_from_wx $fmt $p
1559	fi
1560}
1561
1562#
1563# printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1564#
1565# Print out Code Inspection figures similar to sccs-prt(1) format.
1566#
1567function printCI
1568{
1569	integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1570	typeset str
1571	if (( tot == 1 )); then
1572		str="line"
1573	else
1574		str="lines"
1575	fi
1576	printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1577	    $tot $str $ins $del $mod $unc
1578}
1579
1580
1581#
1582# difflines <oldfile> <newfile>
1583#
1584# Calculate and emit number of added, removed, modified and unchanged lines,
1585# and total lines changed, the sum of added + removed + modified.
1586#
1587function difflines
1588{
1589	integer tot mod del ins unc err
1590	typeset filename
1591
1592	eval $( diff -e $1 $2 | $AWK '
1593	# Change range of lines: N,Nc
1594	/^[0-9]*,[0-9]*c$/ {
1595		n=split(substr($1,1,length($1)-1), counts, ",");
1596		if (n != 2) {
1597			error=2
1598			exit;
1599		}
1600		#
1601		# 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1602		# following would be 5 - 3 = 2! Hence +1 for correction.
1603		#
1604		r=(counts[2]-counts[1])+1;
1605
1606		#
1607		# Now count replacement lines: each represents a change instead
1608		# of a delete, so increment c and decrement r.
1609		#
1610		while (getline != /^\.$/) {
1611			c++;
1612			r--;
1613		}
1614		#
1615		# If there were more replacement lines than original lines,
1616		# then r will be negative; in this case there are no deletions,
1617		# but there are r changes that should be counted as adds, and
1618		# since r is negative, subtract it from a and add it to c.
1619		#
1620		if (r < 0) {
1621			a-=r;
1622			c+=r;
1623		}
1624
1625		#
1626		# If there were more original lines than replacement lines, then
1627		# r will be positive; in this case, increment d by that much.
1628		#
1629		if (r > 0) {
1630			d+=r;
1631		}
1632		next;
1633	}
1634
1635	# Change lines: Nc
1636	/^[0-9].*c$/ {
1637		# The first line is a replacement; any more are additions.
1638		if (getline != /^\.$/) {
1639			c++;
1640			while (getline != /^\.$/) a++;
1641		}
1642		next;
1643	}
1644
1645	# Add lines: both Na and N,Na
1646	/^[0-9].*a$/ {
1647		while (getline != /^\.$/) a++;
1648		next;
1649	}
1650
1651	# Delete range of lines: N,Nd
1652	/^[0-9]*,[0-9]*d$/ {
1653		n=split(substr($1,1,length($1)-1), counts, ",");
1654		if (n != 2) {
1655			error=2
1656			exit;
1657		}
1658		#
1659		# 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1660		# following would be 5 - 3 = 2! Hence +1 for correction.
1661		#
1662		r=(counts[2]-counts[1])+1;
1663		d+=r;
1664		next;
1665	}
1666
1667	# Delete line: Nd.   For example 10d says line 10 is deleted.
1668	/^[0-9]*d$/ {d++; next}
1669
1670	# Should not get here!
1671	{
1672		error=1;
1673		exit;
1674	}
1675
1676	# Finish off - print results
1677	END {
1678		printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1679		    (c+d+a), c, d, a, error);
1680	}' )
1681
1682	# End of $AWK, Check to see if any trouble occurred.
1683	if (( $? > 0 || err > 0 )); then
1684		print "Unexpected Error occurred reading" \
1685		    "\`diff -e $1 $2\`: \$?=$?, err=" $err
1686		return
1687	fi
1688
1689	# Accumulate totals
1690	(( TOTL += tot ))
1691	(( TMOD += mod ))
1692	(( TDEL += del ))
1693	(( TINS += ins ))
1694	# Calculate unchanged lines
1695	unc=`wc -l < $1`
1696	if (( unc > 0 )); then
1697		(( unc -= del + mod ))
1698		(( TUNC += unc ))
1699	fi
1700	# print summary
1701	print "<span class=\"lineschanged\">"
1702	printCI $tot $ins $del $mod $unc
1703	print "</span>"
1704}
1705
1706
1707#
1708# flist_from_wx
1709#
1710# Sets up webrev to source its information from a wx-formatted file.
1711# Sets the global 'wxfile' variable.
1712#
1713function flist_from_wx
1714{
1715	typeset argfile=$1
1716	if [[ -n ${argfile%%/*} ]]; then
1717		#
1718		# If the wx file pathname is relative then make it absolute
1719		# because the webrev does a "cd" later on.
1720		#
1721		wxfile=$PWD/$argfile
1722	else
1723		wxfile=$argfile
1724	fi
1725
1726	$AWK '{ c = 1; print;
1727	  while (getline) {
1728		if (NF == 0) { c = -c; continue }
1729		if (c > 0) print
1730	  }
1731	}' $wxfile > $FLIST
1732
1733	print " Done."
1734}
1735
1736#
1737# Call hg-active to get the active list output in the wx active list format
1738#
1739function hg_active_wxfile
1740{
1741	typeset child=$1
1742	typeset parent=$2
1743
1744	TMPFLIST=/tmp/$$.active
1745	$HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1746	wxfile=$TMPFLIST
1747}
1748
1749#
1750# flist_from_mercurial
1751# Call hg-active to get a wx-style active list, and hand it off to
1752# flist_from_wx
1753#
1754function flist_from_mercurial
1755{
1756	typeset child=$1
1757	typeset parent=$2
1758
1759	print " File list from: hg-active -p $parent ...\c"
1760	if [[ ! -x $HG_ACTIVE ]]; then
1761		print		# Blank line for the \c above
1762		print -u2 "Error: hg-active tool not found.  Exiting"
1763		exit 1
1764	fi
1765	hg_active_wxfile $child $parent
1766
1767	# flist_from_wx prints the Done, so we don't have to.
1768	flist_from_wx $TMPFLIST
1769}
1770
1771#
1772# Transform a specified 'git log' output format into a wx-like active list.
1773#
1774function git_wxfile
1775{
1776	typeset child="$1"
1777	typeset parent="$2"
1778
1779	TMPFLIST=/tmp/$$.active
1780	$PERL -e 'my (%files, %realfiles, $msg);
1781	my $parent = $ARGV[0];
1782	my $child = $ARGV[1];
1783
1784	open(F, "git diff -M --name-status $parent..$child |");
1785	while (<F>) {
1786	    chomp;
1787	    if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
1788		if ($1 >= 75) {			 # Probably worth treating as a rename
1789		    $realfiles{$3} = $2;
1790		} else {
1791		    $realfiles{$3} = $3;
1792		    $realfiles{$2} = $2;
1793	        }
1794	    } else {
1795		my $f = (split /\s+/, $_)[1];
1796		$realfiles{$f} = $f;
1797	    }
1798	}
1799	close(F);
1800
1801	my $state = 1;		    # 0|comments, 1|files
1802	open(F, "git whatchanged --pretty=format:%B $parent..$child |");
1803	while (<F>) {
1804	    chomp;
1805	    if (/^:[0-9]{6}/) {
1806		my $fname = (split /\t/, $_)[1];
1807		next if !defined($realfiles{$fname}); # No real change
1808		$state = 1;
1809		chomp $msg;
1810		$files{$fname} .= $msg;
1811	    } else {
1812		if ($state == 1) {
1813		    $state = 0;
1814		    $msg = /^\n/ ? "" : "\n";
1815		}
1816		$msg .= "$_\n" if ($_);
1817	    }
1818	}
1819	close(F);
1820
1821	for (sort keys %files) {
1822	    if ($realfiles{$_} ne $_) {
1823		print "$_ $realfiles{$_}\n$files{$_}\n\n";
1824	    } else {
1825		print "$_\n$files{$_}\n\n"
1826	    }
1827	}' ${parent} ${child} > $TMPFLIST
1828
1829	wxfile=$TMPFLIST
1830}
1831
1832#
1833# flist_from_git
1834# Build a wx-style active list, and hand it off to flist_from_wx
1835#
1836function flist_from_git
1837{
1838	typeset child=$1
1839	typeset parent=$2
1840
1841	print " File list from: git ...\c"
1842	git_wxfile "$child" "$parent";
1843
1844	# flist_from_wx prints the Done, so we don't have to.
1845	flist_from_wx $TMPFLIST
1846}
1847
1848#
1849# flist_from_subversion
1850#
1851# Generate the file list by extracting file names from svn status.
1852#
1853function flist_from_subversion
1854{
1855	CWS=$1
1856	OLDPWD=$2
1857
1858	cd $CWS
1859	print -u2 " File list from: svn status ... \c"
1860	svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1861	print -u2 " Done."
1862	cd $OLDPWD
1863}
1864
1865function env_from_flist
1866{
1867	[[ -r $FLIST ]] || return
1868
1869	#
1870	# Use "eval" to set env variables that are listed in the file
1871	# list.  Then copy those into our local versions of those
1872	# variables if they have not been set already.
1873	#
1874	eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1875
1876	if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1877		codemgr_ws=$CODEMGR_WS
1878		export CODEMGR_WS
1879	fi
1880
1881	#
1882	# Check to see if CODEMGR_PARENT is set in the flist file.
1883	#
1884	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1885		codemgr_parent=$CODEMGR_PARENT
1886		export CODEMGR_PARENT
1887	fi
1888}
1889
1890function look_for_prog
1891{
1892	typeset path
1893	typeset ppath
1894	typeset progname=$1
1895
1896	ppath=$PATH
1897	ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1898	ppath=$ppath:/opt/onbld/bin
1899	ppath=$ppath:/opt/onbld/bin/`uname -p`
1900
1901	PATH=$ppath prog=`whence $progname`
1902	if [[ -n $prog ]]; then
1903		print $prog
1904	fi
1905}
1906
1907function get_file_mode
1908{
1909	$PERL -e '
1910		if (@stat = stat($ARGV[0])) {
1911			$mode = $stat[2] & 0777;
1912			printf "%03o\n", $mode;
1913			exit 0;
1914		} else {
1915			exit 1;
1916		}
1917	    ' $1
1918}
1919
1920function build_old_new_mercurial
1921{
1922	typeset olddir="$1"
1923	typeset newdir="$2"
1924	typeset old_mode=
1925	typeset new_mode=
1926	typeset file
1927
1928	#
1929	# Get old file mode, from the parent revision manifest entry.
1930	# Mercurial only stores a "file is executable" flag, but the
1931	# manifest will display an octal mode "644" or "755".
1932	#
1933	if [[ "$PDIR" == "." ]]; then
1934		file="$PF"
1935	else
1936		file="$PDIR/$PF"
1937	fi
1938	file=`echo $file | $SED 's#/#\\\/#g'`
1939	# match the exact filename, and return only the permission digits
1940	old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
1941	    < $HG_PARENT_MANIFEST`
1942
1943	#
1944	# Get new file mode, directly from the filesystem.
1945	# Normalize the mode to match Mercurial's behavior.
1946	#
1947	new_mode=`get_file_mode $CWS/$DIR/$F`
1948	if [[ -n "$new_mode" ]]; then
1949		if [[ "$new_mode" = *[1357]* ]]; then
1950			new_mode=755
1951		else
1952			new_mode=644
1953		fi
1954	fi
1955
1956	#
1957	# new version of the file.
1958	#
1959	rm -rf $newdir/$DIR/$F
1960	if [[ -e $CWS/$DIR/$F ]]; then
1961		cp $CWS/$DIR/$F $newdir/$DIR/$F
1962		if [[ -n $new_mode ]]; then
1963			chmod $new_mode $newdir/$DIR/$F
1964		else
1965			# should never happen
1966			print -u2 "ERROR: set mode of $newdir/$DIR/$F"
1967		fi
1968	fi
1969
1970	#
1971	# parent's version of the file
1972	#
1973	# Note that we get this from the last version common to both
1974	# ourselves and the parent.  References are via $CWS since we have no
1975	# guarantee that the parent workspace is reachable via the filesystem.
1976	#
1977	if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
1978		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1979	elif [[ -n $HG_PARENT ]]; then
1980		hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
1981		    $olddir/$PDIR/$PF 2>/dev/null
1982
1983		if (( $? != 0 )); then
1984			rm -f $olddir/$PDIR/$PF
1985		else
1986			if [[ -n $old_mode ]]; then
1987				chmod $old_mode $olddir/$PDIR/$PF
1988			else
1989				# should never happen
1990				print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
1991			fi
1992		fi
1993	fi
1994}
1995
1996function build_old_new_git
1997{
1998	typeset olddir="$1"
1999	typeset newdir="$2"
2000	typeset o_mode=
2001	typeset n_mode=
2002	typeset o_object=
2003	typeset n_object=
2004	typeset OWD=$PWD
2005	typeset file
2006	typeset type
2007
2008	cd $CWS
2009
2010	#
2011	# Get old file and its mode from the git object tree
2012	#
2013	if [[ "$PDIR" == "." ]]; then
2014		file="$PF"
2015	else
2016		file="$PDIR/$PF"
2017	fi
2018
2019	if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2020		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2021	else
2022		$GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk
2023		$GIT cat-file $type $o_object > $olddir/$file 2>/dev/null
2024
2025		if (( $? != 0 )); then
2026			rm -f $olddir/$file
2027		elif [[ -n $o_mode ]]; then
2028			# Strip the first 3 digits, to get a regular octal mode
2029			o_mode=${o_mode/???/}
2030			chmod $o_mode $olddir/$file
2031		else
2032			# should never happen
2033			print -u2 "ERROR: set mode of $olddir/$file"
2034		fi
2035	fi
2036
2037	#
2038	# new version of the file.
2039	#
2040	if [[ "$DIR" == "." ]]; then
2041		file="$F"
2042	else
2043		file="$DIR/$F"
2044	fi
2045	rm -rf $newdir/$file
2046
2047        if [[ -e $CWS/$DIR/$F ]]; then
2048		cp $CWS/$DIR/$F $newdir/$DIR/$F
2049		chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F
2050        fi
2051	cd $OWD
2052}
2053
2054function build_old_new_subversion
2055{
2056	typeset olddir="$1"
2057	typeset newdir="$2"
2058
2059	# Snag new version of file.
2060	rm -f $newdir/$DIR/$F
2061	[[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2062
2063	if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2064		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2065	else
2066		# Get the parent's version of the file.
2067		svn status $CWS/$DIR/$F | read stat file
2068		if [[ $stat != "A" ]]; then
2069			svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2070		fi
2071	fi
2072}
2073
2074function build_old_new_unknown
2075{
2076	typeset olddir="$1"
2077	typeset newdir="$2"
2078
2079	#
2080	# Snag new version of file.
2081	#
2082	rm -f $newdir/$DIR/$F
2083	[[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2084
2085	#
2086	# Snag the parent's version of the file.
2087	#
2088	if [[ -f $PWS/$PDIR/$PF ]]; then
2089		rm -f $olddir/$PDIR/$PF
2090		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2091	fi
2092}
2093
2094function build_old_new
2095{
2096	typeset WDIR=$1
2097	typeset PWS=$2
2098	typeset PDIR=$3
2099	typeset PF=$4
2100	typeset CWS=$5
2101	typeset DIR=$6
2102	typeset F=$7
2103
2104	typeset olddir="$WDIR/raw_files/old"
2105	typeset newdir="$WDIR/raw_files/new"
2106
2107	mkdir -p $olddir/$PDIR
2108	mkdir -p $newdir/$DIR
2109
2110	if [[ $SCM_MODE == "mercurial" ]]; then
2111		build_old_new_mercurial "$olddir" "$newdir"
2112	elif [[ $SCM_MODE == "git" ]]; then
2113		build_old_new_git "$olddir" "$newdir"
2114	elif [[ $SCM_MODE == "subversion" ]]; then
2115		build_old_new_subversion "$olddir" "$newdir"
2116	elif [[ $SCM_MODE == "unknown" ]]; then
2117		build_old_new_unknown "$olddir" "$newdir"
2118	fi
2119
2120	if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2121		print "*** Error: file not in parent or child"
2122		return 1
2123	fi
2124	return 0
2125}
2126
2127
2128#
2129# Usage message.
2130#
2131function usage
2132{
2133	print 'Usage:\twebrev [common-options]
2134	webrev [common-options] ( <file> | - )
2135	webrev [common-options] -w <wx file>
2136
2137Options:
2138	-c <revision>: generate webrev for single revision (git only)
2139	-C <filename>: Use <filename> for the information tracking configuration.
2140	-D: delete remote webrev
2141	-h <revision>: specify "head" revision for comparison (git only)
2142	-i <filename>: Include <filename> in the index.html file.
2143	-I <filename>: Use <filename> for the information tracking registry.
2144	-n: do not generate the webrev (useful with -U)
2145	-O: Print bugids/arc cases suitable for OpenSolaris.
2146	-o <outdir>: Output webrev to specified directory.
2147	-p <compare-against>: Use specified parent wkspc or basis for comparison
2148	-t <remote_target>: Specify remote destination for webrev upload
2149	-U: upload the webrev to remote destination
2150	-w <wxfile>: Use specified wx active file.
2151
2152Environment:
2153	WDIR: Control the output directory.
2154	WEBREV_TRASH_DIR: Set directory for webrev delete.
2155
2156SCM Environment:
2157	CODEMGR_WS: Workspace location.
2158	CODEMGR_PARENT: Parent workspace location.
2159'
2160
2161	exit 2
2162}
2163
2164#
2165#
2166# Main program starts here
2167#
2168#
2169
2170trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2171
2172set +o noclobber
2173
2174PATH=$(/bin/dirname "$(whence $0)"):$PATH
2175
2176[[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2177[[ -z $WX ]] && WX=`look_for_prog wx`
2178[[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
2179[[ -z $GIT ]] && GIT=`look_for_prog git`
2180[[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2181[[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2182[[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2183[[ -z $PERL ]] && PERL=`look_for_prog perl`
2184[[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2185[[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2186[[ -z $AWK ]] && AWK=`look_for_prog nawk`
2187[[ -z $AWK ]] && AWK=`look_for_prog gawk`
2188[[ -z $AWK ]] && AWK=`look_for_prog awk`
2189[[ -z $SCP ]] && SCP=`look_for_prog scp`
2190[[ -z $SED ]] && SED=`look_for_prog sed`
2191[[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2192[[ -z $SORT ]] && SORT=`look_for_prog sort`
2193[[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2194[[ -z $GREP ]] && GREP=`look_for_prog grep`
2195[[ -z $FIND ]] && FIND=`look_for_prog find`
2196[[ -z $MANDOC ]] && MANDOC=`look_for_prog mandoc`
2197[[ -z $COL ]] && COL=`look_for_prog col`
2198
2199# set name of trash directory for remote webrev deletion
2200TRASH_DIR=".trash"
2201[[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2202
2203if [[ ! -x $PERL ]]; then
2204	print -u2 "Error: No perl interpreter found.  Exiting."
2205	exit 1
2206fi
2207
2208if [[ ! -x $WHICH_SCM ]]; then
2209	print -u2 "Error: Could not find which_scm.  Exiting."
2210	exit 1
2211fi
2212
2213#
2214# These aren't fatal, but we want to note them to the user.
2215# We don't warn on the absence of 'wx' until later when we've
2216# determined that we actually need to try to invoke it.
2217#
2218[[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2219[[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2220[[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2221
2222# Declare global total counters.
2223integer TOTL TINS TDEL TMOD TUNC
2224
2225# default remote host for upload/delete
2226typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2227# prefixes for upload targets
2228typeset -r rsync_prefix="rsync://"
2229typeset -r ssh_prefix="ssh://"
2230
2231cflag=
2232Cflag=
2233Dflag=
2234flist_mode=
2235flist_file=
2236hflag=
2237iflag=
2238Iflag=
2239lflag=
2240Nflag=
2241nflag=
2242Oflag=
2243oflag=
2244pflag=
2245tflag=
2246uflag=
2247Uflag=
2248wflag=
2249remote_target=
2250
2251#
2252# NOTE: when adding/removing options it is necessary to sync the list
2253#	with usr/src/tools/onbld/hgext/cdm.py
2254#
2255while getopts "c:C:Dh:i:I:lnNo:Op:t:Uw" opt
2256do
2257	case $opt in
2258	c)	cflag=1
2259		codemgr_head=$OPTARG
2260		codemgr_parent=$OPTARG~1;;
2261
2262	C)	Cflag=1
2263		ITSCONF=$OPTARG;;
2264
2265	D)	Dflag=1;;
2266
2267	h)	hflag=1
2268		codemgr_head=$OPTARG;;
2269
2270	i)	iflag=1
2271		INCLUDE_FILE=$OPTARG;;
2272
2273	I)	Iflag=1
2274		ITSREG=$OPTARG;;
2275
2276	N)	Nflag=1;;
2277
2278	n)	nflag=1;;
2279
2280	O)	Oflag=1;;
2281
2282	o)	oflag=1
2283		# Strip the trailing slash to correctly form remote target.
2284		WDIR=${OPTARG%/};;
2285
2286	p)	pflag=1
2287		codemgr_parent=$OPTARG;;
2288
2289	t)	tflag=1
2290		remote_target=$OPTARG;;
2291
2292	U)	Uflag=1;;
2293
2294	w)	wflag=1;;
2295
2296	?)	usage;;
2297	esac
2298done
2299
2300FLIST=/tmp/$$.flist
2301
2302if [[ -n $wflag && -n $lflag ]]; then
2303	usage
2304fi
2305
2306# more sanity checking
2307if [[ -n $nflag && -z $Uflag ]]; then
2308	print "it does not make sense to skip webrev generation" \
2309	    "without -U"
2310	exit 1
2311fi
2312
2313if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2314	echo "remote target has to be used only for upload or delete"
2315	exit 1
2316fi
2317
2318#
2319# For the invocation "webrev -n -U" with no other options, webrev will assume
2320# that the webrev exists in ${CWS}/webrev, but will upload it using the name
2321# $(basename ${CWS}).  So we need to get CWS set before we skip any remaining
2322# logic.
2323#
2324$WHICH_SCM | read SCM_MODE junk || exit 1
2325if [[ $SCM_MODE == "mercurial" ]]; then
2326	#
2327	# Mercurial priorities:
2328	# 1. hg root from CODEMGR_WS environment variable
2329	# 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under
2330	#    usr/closed when we run webrev
2331	# 2. hg root from directory of invocation
2332	#
2333	if [[ ${PWD} =~ "usr/closed" ]]; then
2334		testparent=${CODEMGR_WS}/usr/closed
2335		# If we're in OpenSolaris mode, we enforce a minor policy:
2336		# help to make sure the reviewer doesn't accidentally publish
2337		# source which is under usr/closed
2338		if [[ -n "$Oflag" ]]; then
2339			print -u2 "OpenSolaris output not permitted with" \
2340			    "usr/closed changes"
2341			exit 1
2342		fi
2343	else
2344		testparent=${CODEMGR_WS}
2345	fi
2346	[[ -z $codemgr_ws && -n $testparent ]] && \
2347	    codemgr_ws=$(hg root -R $testparent 2>/dev/null)
2348	[[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null)
2349	CWS=$codemgr_ws
2350elif [[ $SCM_MODE == "git" ]]; then
2351	#
2352	# Git priorities:
2353	# 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2354	# 2. git rev-parse --git-dir from directory of invocation
2355	#
2356	[[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2357	    codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \
2358		2>/dev/null)
2359	[[ -z $codemgr_ws ]] && \
2360	    codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null)
2361
2362	if [[ "$codemgr_ws" == ".git" ]]; then
2363		codemgr_ws="${PWD}/${codemgr_ws}"
2364	fi
2365
2366	if [[ "$codemgr_ws" = *"/.git" ]]; then
2367		codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git'
2368	fi
2369	CWS="$codemgr_ws"
2370elif [[ $SCM_MODE == "subversion" ]]; then
2371	#
2372	# Subversion priorities:
2373	# 1. CODEMGR_WS from environment
2374	# 2. Relative path from current directory to SVN repository root
2375	#
2376	if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2377		CWS=$CODEMGR_WS
2378	else
2379		svn info | while read line; do
2380			if [[ $line == "URL: "* ]]; then
2381				url=${line#URL: }
2382			elif [[ $line == "Repository Root: "* ]]; then
2383				repo=${line#Repository Root: }
2384			fi
2385		done
2386
2387		rel=${url#$repo}
2388		CWS=${PWD%$rel}
2389	fi
2390fi
2391
2392#
2393# If no SCM has been determined, take either the environment setting
2394# setting for CODEMGR_WS, or the current directory if that wasn't set.
2395#
2396if [[ -z ${CWS} ]]; then
2397	CWS=${CODEMGR_WS:-.}
2398fi
2399
2400#
2401# If the command line options indicate no webrev generation, either
2402# explicitly (-n) or implicitly (-D but not -U), then there's a whole
2403# ton of logic we can skip.
2404#
2405# Instead of increasing indentation, we intentionally leave this loop
2406# body open here, and exit via break from multiple points within.
2407# Search for DO_EVERYTHING below to find the break points and closure.
2408#
2409for do_everything in 1; do
2410
2411# DO_EVERYTHING: break point
2412if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2413	break
2414fi
2415
2416#
2417# If this manually set as the parent, and it appears to be an earlier webrev,
2418# then note that fact and set the parent to the raw_files/new subdirectory.
2419#
2420if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2421	parent_webrev=$(readlink -f "$codemgr_parent")
2422	codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new")
2423fi
2424
2425if [[ -z $wflag && -z $lflag ]]; then
2426	shift $(($OPTIND - 1))
2427
2428	if [[ $1 == "-" ]]; then
2429		cat > $FLIST
2430		flist_mode="stdin"
2431		flist_done=1
2432		shift
2433	elif [[ -n $1 ]]; then
2434		if [[ ! -r $1 ]]; then
2435			print -u2 "$1: no such file or not readable"
2436			usage
2437		fi
2438		cat $1 > $FLIST
2439		flist_mode="file"
2440		flist_file=$1
2441		flist_done=1
2442		shift
2443	else
2444		flist_mode="auto"
2445	fi
2446fi
2447
2448#
2449# Before we go on to further consider -l and -w, work out which SCM we think
2450# is in use.
2451#
2452case "$SCM_MODE" in
2453mercurial|git|subversion)
2454	;;
2455unknown)
2456	if [[ $flist_mode == "auto" ]]; then
2457		print -u2 "Unable to determine SCM in use and file list not specified"
2458		print -u2 "See which_scm(1) for SCM detection information."
2459		exit 1
2460	fi
2461	;;
2462*)
2463	if [[ $flist_mode == "auto" ]]; then
2464		print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2465		exit 1
2466	fi
2467	;;
2468esac
2469
2470print -u2 "   SCM detected: $SCM_MODE"
2471
2472if [[ -n $wflag ]]; then
2473	#
2474	# If the -w is given then assume the file list is in Bonwick's "wx"
2475	# command format, i.e.  pathname lines alternating with SCCS comment
2476	# lines with blank lines as separators.  Use the SCCS comments later
2477	# in building the index.html file.
2478	#
2479	shift $(($OPTIND - 1))
2480	wxfile=$1
2481	if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2482		if [[ -r $CODEMGR_WS/wx/active ]]; then
2483			wxfile=$CODEMGR_WS/wx/active
2484		fi
2485	fi
2486
2487	[[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2488	    "be auto-detected (check \$CODEMGR_WS)" && exit 1
2489
2490	if [[ ! -r $wxfile ]]; then
2491		print -u2 "$wxfile: no such file or not readable"
2492		usage
2493	fi
2494
2495	print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2496	flist_from_wx $wxfile
2497	flist_done=1
2498	if [[ -n "$*" ]]; then
2499		shift
2500	fi
2501elif [[ $flist_mode == "stdin" ]]; then
2502	print -u2 " File list from: standard input"
2503elif [[ $flist_mode == "file" ]]; then
2504	print -u2 " File list from: $flist_file"
2505fi
2506
2507if [[ $# -gt 0 ]]; then
2508	print -u2 "WARNING: unused arguments: $*"
2509fi
2510
2511#
2512# Before we entered the DO_EVERYTHING loop, we should have already set CWS
2513# and CODEMGR_WS as needed.  Here, we set the parent workspace.
2514#
2515if [[ $SCM_MODE == "mercurial" ]]; then
2516	#
2517	# Parent can either be specified with -p
2518	# Specified with CODEMGR_PARENT in the environment
2519	# or taken from hg's default path.
2520	#
2521
2522	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2523		codemgr_parent=$CODEMGR_PARENT
2524	fi
2525
2526	if [[ -z $codemgr_parent ]]; then
2527		codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
2528	fi
2529
2530	PWS=$codemgr_parent
2531
2532	#
2533	# If the parent is a webrev, we want to do some things against
2534	# the natural workspace parent (file list, comments, etc)
2535	#
2536	if [[ -n $parent_webrev ]]; then
2537		real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
2538	else
2539		real_parent=$PWS
2540	fi
2541
2542	#
2543	# If hg-active exists, then we run it.  In the case of no explicit
2544	# flist given, we'll use it for our comments.  In the case of an
2545	# explicit flist given we'll try to use it for comments for any
2546	# files mentioned in the flist.
2547	#
2548	if [[ -z $flist_done ]]; then
2549		flist_from_mercurial $CWS $real_parent
2550		flist_done=1
2551	fi
2552
2553	#
2554	# If we have a file list now, pull out any variables set
2555	# therein.  We do this now (rather than when we possibly use
2556	# hg-active to find comments) to avoid stomping specifications
2557	# in the user-specified flist.
2558	#
2559	if [[ -n $flist_done ]]; then
2560		env_from_flist
2561	fi
2562
2563	#
2564	# Only call hg-active if we don't have a wx formatted file already
2565	#
2566	if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2567		print "  Comments from: hg-active -p $real_parent ...\c"
2568		hg_active_wxfile $CWS $real_parent
2569		print " Done."
2570	fi
2571
2572	#
2573	# At this point we must have a wx flist either from hg-active,
2574	# or in general.  Use it to try and find our parent revision,
2575	# if we don't have one.
2576	#
2577	if [[ -z $HG_PARENT ]]; then
2578		eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2579	fi
2580
2581	#
2582	# If we still don't have a parent, we must have been given a
2583	# wx-style active list with no HG_PARENT specification, run
2584	# hg-active and pull an HG_PARENT out of it, ignore the rest.
2585	#
2586	if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2587		$HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2588		    eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2589	elif [[ -z $HG_PARENT ]]; then
2590		print -u2 "Error: Cannot discover parent revision"
2591		exit 1
2592	fi
2593
2594	pnode=$(trim_digest $HG_PARENT)
2595	PRETTY_PWS="${PWS} (at ${pnode})"
2596	cnode=$(hg parent -R $codemgr_ws --template '{node|short}' \
2597	    2>/dev/null)
2598	PRETTY_CWS="${CWS} (at ${cnode})"}
2599elif [[ $SCM_MODE == "git" ]]; then
2600	# Check that "head" revision specified with -c or -h is sane
2601	if [[ -n $cflag || -n $hflag ]]; then
2602		head_rev=$($GIT rev-parse --verify --quiet "$codemgr_head")
2603		if [[ -z $head_rev ]]; then
2604			print -u2 "Error: bad revision ${codemgr_head}"
2605			exit 1
2606		fi
2607	fi
2608
2609	if [[ -z $codemgr_head ]]; then
2610		codemgr_head="HEAD";
2611	fi
2612
2613	# Parent can either be specified with -p, or specified with
2614	# CODEMGR_PARENT in the environment.
2615	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2616		codemgr_parent=$CODEMGR_PARENT
2617	fi
2618
2619	# Try to figure out the parent based on the branch the current
2620	# branch is tracking, if we fail, use origin/master
2621	this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2622	par_branch="origin/master"
2623
2624	# If we're not on a branch there's nothing we can do
2625	if [[ $this_branch != "(no branch)" ]]; then
2626		$GIT for-each-ref					\
2627		    --format='%(refname:short) %(upstream:short)'	\
2628		    refs/heads/ |					\
2629		    while read local remote; do
2630			if [[ "$local" == "$this_branch" ]]; then
2631				par_branch="$remote"
2632			fi
2633		done
2634	fi
2635
2636	if [[ -z $codemgr_parent ]]; then
2637		codemgr_parent=$par_branch
2638	fi
2639	PWS=$codemgr_parent
2640
2641	#
2642	# If the parent is a webrev, we want to do some things against
2643	# the natural workspace parent (file list, comments, etc)
2644	#
2645	if [[ -n $parent_webrev ]]; then
2646		real_parent=$par_branch
2647	else
2648		real_parent=$PWS
2649	fi
2650
2651	if [[ -z $flist_done ]]; then
2652		flist_from_git "$codemgr_head" "$real_parent"
2653		flist_done=1
2654	fi
2655
2656	#
2657	# If we have a file list now, pull out any variables set
2658	# therein.
2659	#
2660	if [[ -n $flist_done ]]; then
2661		env_from_flist
2662	fi
2663
2664	#
2665	# If we don't have a wx-format file list, build one we can pull change
2666	# comments from.
2667	#
2668	if [[ -z $wxfile ]]; then
2669		print "  Comments from: git...\c"
2670		git_wxfile "$codemgr_head" "$real_parent"
2671		print " Done."
2672	fi
2673
2674	if [[ -z $GIT_PARENT ]]; then
2675		GIT_PARENT=$($GIT merge-base "$real_parent" "$codemgr_head")
2676	fi
2677	if [[ -z $GIT_PARENT ]]; then
2678		print -u2 "Error: Cannot discover parent revision"
2679		exit 1
2680	fi
2681
2682	pnode=$(trim_digest $GIT_PARENT)
2683
2684	if [[ -n $cflag ]]; then
2685		PRETTY_PWS="previous revision (at ${pnode})"
2686	elif [[ $real_parent == */* ]]; then
2687		origin=$(echo $real_parent | cut -d/ -f1)
2688		origin=$($GIT remote -v | \
2689		    $AWK '$1 == "'$origin'" { print $2; exit }')
2690		PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2691	elif [[ -n $pflag && -z $parent_webrev ]]; then
2692		PRETTY_PWS="${CWS} (explicit revision ${pnode})"
2693	else
2694		PRETTY_PWS="${PWS} (at ${pnode})"
2695	fi
2696
2697	cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 \
2698	    ${codemgr_head} 2>/dev/null)
2699
2700	if [[ -n $cflag || -n $hflag ]]; then
2701		PRETTY_CWS="${CWS} (explicit head at ${cnode})"
2702	else
2703		PRETTY_CWS="${CWS} (at ${cnode})"
2704	fi
2705elif [[ $SCM_MODE == "subversion" ]]; then
2706
2707	#
2708	# We only will have a real parent workspace in the case one
2709	# was specified (be it an older webrev, or another checkout).
2710	#
2711	[[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2712
2713	if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2714		flist_from_subversion $CWS $OLDPWD
2715	fi
2716else
2717	if [[ $SCM_MODE == "unknown" ]]; then
2718		print -u2 "    Unknown type of SCM in use"
2719	else
2720		print -u2 "    Unsupported SCM in use: $SCM_MODE"
2721	fi
2722
2723	env_from_flist
2724
2725	if [[ -z $CODEMGR_WS ]]; then
2726		print -u2 "SCM not detected/supported and " \
2727		    "CODEMGR_WS not specified"
2728		exit 1
2729		fi
2730
2731	if [[ -z $CODEMGR_PARENT ]]; then
2732		print -u2 "SCM not detected/supported and " \
2733		    "CODEMGR_PARENT not specified"
2734		exit 1
2735	fi
2736
2737	CWS=$CODEMGR_WS
2738	PWS=$CODEMGR_PARENT
2739fi
2740
2741#
2742# If the user didn't specify a -i option, check to see if there is a
2743# webrev-info file in the workspace directory.
2744#
2745if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2746	iflag=1
2747	INCLUDE_FILE="$CWS/webrev-info"
2748fi
2749
2750if [[ -n $iflag ]]; then
2751	if [[ ! -r $INCLUDE_FILE ]]; then
2752		print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2753		    "not readable."
2754		exit 1
2755	else
2756		#
2757		# $INCLUDE_FILE may be a relative path, and the script alters
2758		# PWD, so we just stash a copy in /tmp.
2759		#
2760		cp $INCLUDE_FILE /tmp/$$.include
2761	fi
2762fi
2763
2764# DO_EVERYTHING: break point
2765if [[ -n $Nflag ]]; then
2766	break
2767fi
2768
2769typeset -A itsinfo
2770typeset -r its_sed_script=/tmp/$$.its_sed
2771valid_prefixes=
2772if [[ -z $nflag ]]; then
2773	DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg"
2774	if [[ -n $Iflag ]]; then
2775		REGFILE=$ITSREG
2776	elif [[ -r $HOME/.its.reg ]]; then
2777		REGFILE=$HOME/.its.reg
2778	else
2779		REGFILE=$DEFREGFILE
2780	fi
2781	if [[ ! -r $REGFILE ]]; then
2782		print "ERROR: Unable to read database registry file $REGFILE"
2783		exit 1
2784	elif [[ $REGFILE != $DEFREGFILE ]]; then
2785		print "   its.reg from: $REGFILE"
2786	fi
2787
2788	$SED -e '/^#/d' -e '/^[ 	]*$/d' $REGFILE | while read LINE; do
2789
2790		name=${LINE%%=*}
2791		value="${LINE#*=}"
2792
2793		if [[ $name == PREFIX ]]; then
2794			p=${value}
2795			valid_prefixes="${p} ${valid_prefixes}"
2796		else
2797			itsinfo["${p}_${name}"]="${value}"
2798		fi
2799	done
2800
2801
2802	DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf"
2803	CONFFILES=$DEFCONFFILE
2804	if [[ -r $HOME/.its.conf ]]; then
2805		CONFFILES="${CONFFILES} $HOME/.its.conf"
2806	fi
2807	if [[ -n $Cflag ]]; then
2808		CONFFILES="${CONFFILES} ${ITSCONF}"
2809	fi
2810	its_domain=
2811	its_priority=
2812	for cf in ${CONFFILES}; do
2813		if [[ ! -r $cf ]]; then
2814			print "ERROR: Unable to read database configuration file $cf"
2815			exit 1
2816		elif [[ $cf != $DEFCONFFILE ]]; then
2817			print "       its.conf: reading $cf"
2818		fi
2819		$SED -e '/^#/d' -e '/^[ 	]*$/d' $cf | while read LINE; do
2820		    eval "${LINE}"
2821		done
2822	done
2823
2824	#
2825	# If an information tracking system is explicitly identified by prefix,
2826	# we want to disregard the specified priorities and resolve it accordingly.
2827	#
2828	# To that end, we'll build a sed script to do each valid prefix in turn.
2829	#
2830	for p in ${valid_prefixes}; do
2831		#
2832		# When an informational URL was provided, translate it to a
2833		# hyperlink.  When omitted, simply use the prefix text.
2834		#
2835		if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2836			itsinfo["${p}_INFO"]=${p}
2837		else
2838			itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2839		fi
2840
2841		#
2842		# Assume that, for this invocation of webrev, all references
2843		# to this information tracking system should resolve through
2844		# the same URL.
2845		#
2846		# If the caller specified -O, then always use EXTERNAL_URL.
2847		#
2848		# Otherwise, look in the list of domains for a matching
2849		# INTERNAL_URL.
2850		#
2851		[[ -z $Oflag ]] && for d in ${its_domain}; do
2852			if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2853				itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2854				break
2855			fi
2856		done
2857		if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2858			itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2859		fi
2860
2861		#
2862		# Turn the destination URL into a hyperlink
2863		#
2864		itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2865
2866		# The character class below contains a literal tab
2867		print "/^${p}[: 	]/ {
2868				s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2869				s;^${p};${itsinfo[${p}_INFO]};
2870			}" >> ${its_sed_script}
2871	done
2872
2873	#
2874	# The previous loop took care of explicit specification.  Now use
2875	# the configured priorities to attempt implicit translations.
2876	#
2877	for p in ${its_priority}; do
2878		print "/^${itsinfo[${p}_REGEX]}[ 	]/ {
2879				s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2880			}" >> ${its_sed_script}
2881	done
2882fi
2883
2884#
2885# Search for DO_EVERYTHING above for matching "for" statement
2886# and explanation of this terminator.
2887#
2888done
2889
2890#
2891# Output directory.
2892#
2893WDIR=${WDIR:-$CWS/webrev}
2894
2895#
2896# Name of the webrev, derived from the workspace name or output directory;
2897# in the future this could potentially be an option.
2898#
2899if [[ -n $oflag ]]; then
2900	WNAME=${WDIR##*/}
2901else
2902	WNAME=${CWS##*/}
2903fi
2904
2905# Make sure remote target is well formed for remote upload/delete.
2906if [[ -n $Dflag || -n $Uflag ]]; then
2907	#
2908	# If remote target is not specified, build it from scratch using
2909	# the default values.
2910	#
2911	if [[ -z $tflag ]]; then
2912		remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2913	else
2914		#
2915		# Check upload target prefix first.
2916		#
2917		if [[ "${remote_target}" != ${rsync_prefix}* &&
2918		    "${remote_target}" != ${ssh_prefix}* ]]; then
2919			print "ERROR: invalid prefix of upload URI" \
2920			    "($remote_target)"
2921			exit 1
2922		fi
2923		#
2924		# If destination specification is not in the form of
2925		# host_spec:remote_dir then assume it is just remote hostname
2926		# and append a colon and destination directory formed from
2927		# local webrev directory name.
2928		#
2929		typeset target_no_prefix=${remote_target##*://}
2930		if [[ ${target_no_prefix} == *:* ]]; then
2931			if [[ "${remote_target}" == *: ]]; then
2932				remote_target=${remote_target}${WNAME}
2933			fi
2934		else
2935			if [[ ${target_no_prefix} == */* ]]; then
2936				print "ERROR: badly formed upload URI" \
2937					"($remote_target)"
2938				exit 1
2939			else
2940				remote_target=${remote_target}:${WNAME}
2941			fi
2942		fi
2943	fi
2944
2945	#
2946	# Strip trailing slash. Each upload method will deal with directory
2947	# specification separately.
2948	#
2949	remote_target=${remote_target%/}
2950fi
2951
2952#
2953# Option -D by itself (option -U not present) implies no webrev generation.
2954#
2955if [[ -z $Uflag && -n $Dflag ]]; then
2956	delete_webrev 1 1
2957	exit $?
2958fi
2959
2960#
2961# Do not generate the webrev, just upload it or delete it.
2962#
2963if [[ -n $nflag ]]; then
2964	if [[ -n $Dflag ]]; then
2965		delete_webrev 1 1
2966		(( $? == 0 )) || exit $?
2967	fi
2968	if [[ -n $Uflag ]]; then
2969		upload_webrev
2970		exit $?
2971	fi
2972fi
2973
2974if [ "${WDIR%%/*}" ]; then
2975	WDIR=$PWD/$WDIR
2976fi
2977
2978if [[ ! -d $WDIR ]]; then
2979	mkdir -p $WDIR
2980	(( $? != 0 )) && exit 1
2981fi
2982
2983#
2984# Summarize what we're going to do.
2985#
2986print "      Workspace: ${PRETTY_CWS:-$CWS}"
2987if [[ -n $parent_webrev ]]; then
2988	print "Compare against: webrev at $parent_webrev"
2989else
2990	print "Compare against: ${PRETTY_PWS:-$PWS}"
2991fi
2992
2993[[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
2994print "      Output to: $WDIR"
2995
2996#
2997# Save the file list in the webrev dir
2998#
2999[[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
3000
3001rm -f $WDIR/$WNAME.patch
3002rm -f $WDIR/$WNAME.ps
3003rm -f $WDIR/$WNAME.pdf
3004
3005touch $WDIR/$WNAME.patch
3006
3007print "   Output Files:"
3008
3009#
3010# Clean up the file list: Remove comments, blank lines and env variables.
3011#
3012$SED -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
3013FLIST=/tmp/$$.flist.clean
3014
3015#
3016# For Mercurial, create a cache of manifest entries.
3017#
3018if [[ $SCM_MODE == "mercurial" ]]; then
3019	#
3020	# Transform the FLIST into a temporary sed script that matches
3021	# relevant entries in the Mercurial manifest as follows:
3022	# 1) The script will be used against the parent revision manifest,
3023	#    so for FLIST lines that have two filenames (a renamed file)
3024	#    keep only the old name.
3025	# 2) Escape all forward slashes the filename.
3026	# 3) Change the filename into another sed command that matches
3027	#    that file in "hg manifest -v" output:  start of line, three
3028	#    octal digits for file permissions, space, a file type flag
3029	#    character, space, the filename, end of line.
3030	# 4) Eliminate any duplicate entries.  (This can occur if a
3031	#    file has been used as the source of an hg cp and it's
3032	#    also been modified in the same changeset.)
3033	#
3034	SEDFILE=/tmp/$$.manifest.sed
3035	$SED '
3036		s#^[^ ]* ##
3037		s#/#\\\/#g
3038		s#^.*$#/^... . &$/p#
3039	' < $FLIST | $SORT -u > $SEDFILE
3040
3041	#
3042	# Apply the generated script to the output of "hg manifest -v"
3043	# to get the relevant subset for this webrev.
3044	#
3045	HG_PARENT_MANIFEST=/tmp/$$.manifest
3046	hg -R $CWS manifest -v -r $HG_PARENT |
3047	    $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
3048fi
3049
3050#
3051# First pass through the files: generate the per-file webrev HTML-files.
3052#
3053cat $FLIST | while read LINE
3054do
3055	set - $LINE
3056	P=$1
3057
3058	#
3059	# Normally, each line in the file list is just a pathname of a
3060	# file that has been modified or created in the child.  A file
3061	# that is renamed in the child workspace has two names on the
3062	# line: new name followed by the old name.
3063	#
3064	oldname=""
3065	oldpath=""
3066	rename=
3067	if [[ $# -eq 2 ]]; then
3068		PP=$2			# old filename
3069		if [[ -f $PP ]]; then
3070			oldname=" (copied from $PP)"
3071		else
3072			oldname=" (renamed from $PP)"
3073		fi
3074		oldpath="$PP"
3075		rename=1
3076		PDIR=${PP%/*}
3077		if [[ $PDIR == $PP ]]; then
3078			PDIR="."   # File at root of workspace
3079		fi
3080
3081		PF=${PP##*/}
3082
3083		DIR=${P%/*}
3084		if [[ $DIR == $P ]]; then
3085			DIR="."   # File at root of workspace
3086		fi
3087
3088		F=${P##*/}
3089
3090	else
3091		DIR=${P%/*}
3092		if [[ "$DIR" == "$P" ]]; then
3093			DIR="."   # File at root of workspace
3094		fi
3095
3096		F=${P##*/}
3097
3098		PP=$P
3099		PDIR=$DIR
3100		PF=$F
3101	fi
3102
3103	COMM=`getcomments html $P $PP`
3104
3105	print "\t$P$oldname\n\t\t\c"
3106
3107	# Make the webrev mirror directory if necessary
3108	mkdir -p $WDIR/$DIR
3109
3110	#
3111	# We stash old and new files into parallel directories in $WDIR
3112	# and do our diffs there.  This makes it possible to generate
3113	# clean looking diffs which don't have absolute paths present.
3114	#
3115
3116	build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3117	    continue
3118
3119	#
3120	# Keep the old PWD around, so we can safely switch back after
3121	# diff generation, such that build_old_new runs in a
3122	# consistent environment.
3123	#
3124	OWD=$PWD
3125	cd $WDIR/raw_files
3126
3127	#
3128	# The "git apply" command does not tolerate the spurious
3129	# "./" that we otherwise insert; be careful not to include
3130	# it in the paths that we pass to diff(1).
3131	#
3132	if [[ $PDIR == "." ]]; then
3133		ofile=old/$PF
3134	else
3135		ofile=old/$PDIR/$PF
3136	fi
3137	if [[ $DIR == "." ]]; then
3138		nfile=new/$F
3139	else
3140		nfile=new/$DIR/$F
3141	fi
3142
3143	mv_but_nodiff=
3144	cmp $ofile $nfile > /dev/null 2>&1
3145	if [[ $? == 0 && $rename == 1 ]]; then
3146		mv_but_nodiff=1
3147	fi
3148
3149	#
3150	# If we have old and new versions of the file then run the appropriate
3151	# diffs.  This is complicated by a couple of factors:
3152	#
3153	#	- renames must be handled specially: we emit a 'remove'
3154	#	  diff and an 'add' diff
3155	#	- new files and deleted files must be handled specially
3156	#	- GNU patch doesn't interpret the output of illumos diff
3157	#	  properly when it comes to adds and deletes.  We need to
3158	#	  do some "cleansing" transformations:
3159	#	    [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
3160	#	    [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
3161	#
3162	cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3163	cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3164
3165	rm -f $WDIR/$DIR/$F.patch
3166	if [[ -z $rename ]]; then
3167		if [ ! -f "$ofile" ]; then
3168			diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3169			    > $WDIR/$DIR/$F.patch
3170		elif [ ! -f "$nfile" ]; then
3171			diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3172			    > $WDIR/$DIR/$F.patch
3173		else
3174			diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3175		fi
3176	else
3177		diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3178		    > $WDIR/$DIR/$F.patch
3179
3180		diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3181		    >> $WDIR/$DIR/$F.patch
3182	fi
3183
3184	#
3185	# Tack the patch we just made onto the accumulated patch for the
3186	# whole wad.
3187	#
3188	cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3189	print " patch\c"
3190
3191	if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3192		${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3193		diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3194		    > $WDIR/$DIR/$F.cdiff.html
3195		print " cdiffs\c"
3196
3197		${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3198		diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3199		    > $WDIR/$DIR/$F.udiff.html
3200		print " udiffs\c"
3201
3202		if [[ -x $WDIFF ]]; then
3203			$WDIFF -c "$COMM" \
3204			    -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3205			    $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3206			if [[ $? -eq 0 ]]; then
3207				print " wdiffs\c"
3208			else
3209				print " wdiffs[fail]\c"
3210			fi
3211		fi
3212
3213		sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3214		    > $WDIR/$DIR/$F.sdiff.html
3215		print " sdiffs\c"
3216		print " frames\c"
3217
3218		rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3219		difflines $ofile $nfile > $WDIR/$DIR/$F.count
3220	elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3221		# renamed file: may also have differences
3222		difflines $ofile $nfile > $WDIR/$DIR/$F.count
3223	elif [[ -f $nfile ]]; then
3224		# new file: count added lines
3225		difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3226	elif [[ -f $ofile ]]; then
3227		# old file: count deleted lines
3228		difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3229	fi
3230
3231	#
3232	# Check if it's man page, and create plain text, html and raw (ascii)
3233	# output for the new version, as well as diffs against old version.
3234	#
3235	if [[ -f "$nfile" && "$nfile" = *.+([0-9])*([a-zA-Z]) && \
3236	    -x $MANDOC && -x $COL ]]; then
3237		$MANDOC -Tascii $nfile | $COL -b > $nfile.man.txt
3238		source_to_html txt < $nfile.man.txt > $nfile.man.txt.html
3239		print " man-txt\c"
3240		print "$MANCSS" > $WDIR/raw_files/new/$DIR/man.css
3241		$MANDOC -Thtml -Ostyle=man.css $nfile > $nfile.man.html
3242		print " man-html\c"
3243		$MANDOC -Tascii $nfile > $nfile.man.raw
3244		print " man-raw\c"
3245		if [[ -f "$ofile" && -z $mv_but_nodiff ]]; then
3246			$MANDOC -Tascii $ofile | $COL -b > $ofile.man.txt
3247			${CDIFFCMD:-diff -bt -C 5} $ofile.man.txt \
3248			    $nfile.man.txt > $WDIR/$DIR/$F.man.cdiff
3249			diff_to_html $F $DIR/$F "C" "$COMM" < \
3250			    $WDIR/$DIR/$F.man.cdiff > \
3251			    $WDIR/$DIR/$F.man.cdiff.html
3252			print " man-cdiffs\c"
3253			${UDIFFCMD:-diff -bt -U 5} $ofile.man.txt \
3254			    $nfile.man.txt > $WDIR/$DIR/$F.man.udiff
3255			diff_to_html $F $DIR/$F "U" "$COMM" < \
3256			    $WDIR/$DIR/$F.man.udiff > \
3257			    $WDIR/$DIR/$F.man.udiff.html
3258			print " man-udiffs\c"
3259			if [[ -x $WDIFF ]]; then
3260				$WDIFF -c "$COMM" -t "$WNAME Wdiff $DIR/$F" \
3261				    $ofile.man.txt $nfile.man.txt > \
3262				    $WDIR/$DIR/$F.man.wdiff.html 2>/dev/null
3263				if [[ $? -eq 0 ]]; then
3264					print " man-wdiffs\c"
3265				else
3266					print " man-wdiffs[fail]\c"
3267				fi
3268			fi
3269			sdiff_to_html $ofile.man.txt $nfile.man.txt $F.man $DIR \
3270			    "$COMM" > $WDIR/$DIR/$F.man.sdiff.html
3271			print " man-sdiffs\c"
3272			print " man-frames\c"
3273		fi
3274		rm -f $ofile.man.txt $nfile.man.txt
3275		rm -f $WDIR/$DIR/$F.man.cdiff $WDIR/$DIR/$F.man.udiff
3276	fi
3277
3278	#
3279	# Now we generate the postscript for this file.  We generate diffs
3280	# only in the event that there is delta, or the file is new (it seems
3281	# tree-killing to print out the contents of deleted files).
3282	#
3283	if [[ -f $nfile ]]; then
3284		ocr=$ofile
3285		[[ ! -f $ofile ]] && ocr=/dev/null
3286
3287		if [[ -z $mv_but_nodiff ]]; then
3288			textcomm=`getcomments text $P $PP`
3289			if [[ -x $CODEREVIEW ]]; then
3290				$CODEREVIEW -y "$textcomm" \
3291				    -e $ocr $nfile \
3292				    > /tmp/$$.psfile 2>/dev/null &&
3293				    cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3294				if [[ $? -eq 0 ]]; then
3295					print " ps\c"
3296				else
3297					print " ps[fail]\c"
3298				fi
3299			fi
3300		fi
3301	fi
3302
3303	if [[ -f $ofile ]]; then
3304		source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3305		print " old\c"
3306	fi
3307
3308	if [[ -f $nfile ]]; then
3309		source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3310		print " new\c"
3311	fi
3312
3313	cd $OWD
3314
3315	print
3316done
3317
3318frame_nav_js > $WDIR/ancnav.js
3319frame_navigation > $WDIR/ancnav.html
3320
3321if [[ ! -f $WDIR/$WNAME.ps ]]; then
3322	print " Generating PDF: Skipped: no output available"
3323elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3324	print " Generating PDF: \c"
3325	fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3326	print "Done."
3327else
3328	print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3329fi
3330
3331# If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3332# delete it - prevent accidental publishing of closed source
3333
3334if [[ -n "$Oflag" ]]; then
3335	$FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3336fi
3337
3338# Now build the index.html file that contains
3339# links to the source files and their diffs.
3340
3341cd $CWS
3342
3343# Save total changed lines for Code Inspection.
3344print "$TOTL" > $WDIR/TotalChangedLines
3345
3346print "     index.html: \c"
3347INDEXFILE=$WDIR/index.html
3348exec 3<&1			# duplicate stdout to FD3.
3349exec 1<&-			# Close stdout.
3350exec > $INDEXFILE		# Open stdout to index file.
3351
3352print "$HTML<head>$STDHEAD"
3353print "<title>$WNAME</title>"
3354print "</head>"
3355print "<body id=\"SUNWwebrev\">"
3356print "<div class=\"summary\">"
3357print "<h2>Code Review for $WNAME</h2>"
3358
3359print "<table>"
3360
3361#
3362# Get the preparer's name:
3363#
3364# If the SCM detected is Mercurial, and the configuration property
3365# ui.username is available, use that, but be careful to properly escape
3366# angle brackets (HTML syntax characters) in the email address.
3367#
3368# Otherwise, use the current userid in the form "John Doe (jdoe)", but
3369# to maintain compatibility with passwd(4), we must support '&' substitutions.
3370#
3371preparer=
3372if [[ "$SCM_MODE" == mercurial ]]; then
3373	preparer=`hg showconfig ui.username 2>/dev/null`
3374	if [[ -n "$preparer" ]]; then
3375		preparer="$(echo "$preparer" | html_quote)"
3376	fi
3377fi
3378if [[ -z "$preparer" ]]; then
3379	preparer=$(
3380	    $PERL -e '
3381	        ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3382	        if ($login) {
3383	            $gcos =~ s/\&/ucfirst($login)/e;
3384	            printf "%s (%s)\n", $gcos, $login;
3385	        } else {
3386	            printf "(unknown)\n";
3387	        }
3388	')
3389fi
3390
3391PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3392print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3393print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3394print "</td></tr>"
3395print "<tr><th>Compare against:</th><td>"
3396if [[ -n $parent_webrev ]]; then
3397	print "webrev at $parent_webrev"
3398else
3399	print "${PRETTY_PWS:-$PWS}"
3400fi
3401print "</td></tr>"
3402print "<tr><th>Summary of changes:</th><td>"
3403printCI $TOTL $TINS $TDEL $TMOD $TUNC
3404print "</td></tr>"
3405
3406if [[ -f $WDIR/$WNAME.patch ]]; then
3407	wpatch_url="$(print $WNAME.patch | url_encode)"
3408	print "<tr><th>Patch of changes:</th><td>"
3409	print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3410fi
3411if [[ -f $WDIR/$WNAME.pdf ]]; then
3412	wpdf_url="$(print $WNAME.pdf | url_encode)"
3413	print "<tr><th>Printable review:</th><td>"
3414	print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3415fi
3416
3417if [[ -n "$iflag" ]]; then
3418	print "<tr><th>Author comments:</th><td><div>"
3419	cat /tmp/$$.include
3420	print "</div></td></tr>"
3421fi
3422print "</table>"
3423print "</div>"
3424
3425#
3426# Second pass through the files: generate the rest of the index file
3427#
3428cat $FLIST | while read LINE
3429do
3430	set - $LINE
3431	P=$1
3432
3433	if [[ $# == 2 ]]; then
3434		PP=$2
3435		oldname="$PP"
3436	else
3437		PP=$P
3438		oldname=""
3439	fi
3440
3441	mv_but_nodiff=
3442	cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3443	if [[ $? == 0 && -n "$oldname" ]]; then
3444		mv_but_nodiff=1
3445	fi
3446
3447	DIR=${P%/*}
3448	if [[ $DIR == $P ]]; then
3449		DIR="."   # File at root of workspace
3450	fi
3451
3452	# Avoid processing the same file twice.
3453	# It's possible for renamed files to
3454	# appear twice in the file list
3455
3456	F=$WDIR/$P
3457
3458	print "<p>"
3459
3460	# If there's a diffs file, make diffs links
3461
3462	if [[ -f $F.cdiff.html ]]; then
3463		cdiff_url="$(print $P.cdiff.html | url_encode)"
3464		udiff_url="$(print $P.udiff.html | url_encode)"
3465		sdiff_url="$(print $P.sdiff.html | url_encode)"
3466		frames_url="$(print $P.frames.html | url_encode)"
3467		print "<a href=\"$cdiff_url\">Cdiffs</a>"
3468		print "<a href=\"$udiff_url\">Udiffs</a>"
3469		if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3470			wdiff_url="$(print $P.wdiff.html | url_encode)"
3471			print "<a href=\"$wdiff_url\">Wdiffs</a>"
3472		fi
3473		print "<a href=\"$sdiff_url\">Sdiffs</a>"
3474		print "<a href=\"$frames_url\">Frames</a>"
3475	else
3476		print " ------ ------"
3477		if [[ -x $WDIFF ]]; then
3478			print " ------"
3479		fi
3480		print " ------ ------"
3481	fi
3482
3483	# If there's an old file, make the link
3484
3485	if [[ -f $F-.html ]]; then
3486		oldfile_url="$(print $P-.html | url_encode)"
3487		print "<a href=\"$oldfile_url\">Old</a>"
3488	else
3489		print " ---"
3490	fi
3491
3492	# If there's an new file, make the link
3493
3494	if [[ -f $F.html ]]; then
3495		newfile_url="$(print $P.html | url_encode)"
3496		print "<a href=\"$newfile_url\">New</a>"
3497	else
3498		print " ---"
3499	fi
3500
3501	if [[ -f $F.patch ]]; then
3502		patch_url="$(print $P.patch | url_encode)"
3503		print "<a href=\"$patch_url\">Patch</a>"
3504	else
3505		print " -----"
3506	fi
3507
3508	if [[ -f $WDIR/raw_files/new/$P ]]; then
3509		rawfiles_url="$(print raw_files/new/$P | url_encode)"
3510		print "<a href=\"$rawfiles_url\">Raw</a>"
3511	else
3512		print " ---"
3513	fi
3514
3515	print "<b>$P</b>"
3516
3517	# For renamed files, clearly state whether or not they are modified
3518	if [[ -f "$oldname" ]]; then
3519		if [[ -n "$mv_but_nodiff" ]]; then
3520			print "<i>(copied from $oldname)</i>"
3521		else
3522			print "<i>(copied and modified from $oldname)</i>"
3523		fi
3524	elif [[ -n "$oldname" ]]; then
3525		if [[ -n "$mv_but_nodiff" ]]; then
3526			print "<i>(renamed from $oldname)</i>"
3527		else
3528			print "<i>(renamed and modified from $oldname)</i>"
3529		fi
3530	fi
3531
3532	# If there's an old file, but no new file, the file was deleted
3533	if [[ -f $F-.html && ! -f $F.html ]]; then
3534		print " <i>(deleted)</i>"
3535	fi
3536
3537	# Check for usr/closed and deleted_files/usr/closed
3538	if [ ! -z "$Oflag" ]; then
3539		if [[ $P == usr/closed/* || \
3540		    $P == deleted_files/usr/closed/* ]]; then
3541			print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3542			    "this review</i>"
3543		fi
3544	fi
3545
3546	manpage=
3547	if [[ -f $F.man.cdiff.html || \
3548	    -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3549		manpage=1
3550		print "<br/>man:"
3551	fi
3552
3553	if [[ -f $F.man.cdiff.html ]]; then
3554		mancdiff_url="$(print $P.man.cdiff.html | url_encode)"
3555		manudiff_url="$(print $P.man.udiff.html | url_encode)"
3556		mansdiff_url="$(print $P.man.sdiff.html | url_encode)"
3557		manframes_url="$(print $P.man.frames.html | url_encode)"
3558		print "<a href=\"$mancdiff_url\">Cdiffs</a>"
3559		print "<a href=\"$manudiff_url\">Udiffs</a>"
3560		if [[ -f $F.man.wdiff.html && -x $WDIFF ]]; then
3561			manwdiff_url="$(print $P.man.wdiff.html | url_encode)"
3562			print "<a href=\"$manwdiff_url\">Wdiffs</a>"
3563		fi
3564		print "<a href=\"$mansdiff_url\">Sdiffs</a>"
3565		print "<a href=\"$manframes_url\">Frames</a>"
3566	elif [[ -n $manpage ]]; then
3567		print " ------ ------"
3568		if [[ -x $WDIFF ]]; then
3569			print " ------"
3570		fi
3571		print " ------ ------"
3572	fi
3573
3574	if [[ -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3575		mantxt_url="$(print raw_files/new/$P.man.txt.html | url_encode)"
3576		print "<a href=\"$mantxt_url\">TXT</a>"
3577		manhtml_url="$(print raw_files/new/$P.man.html | url_encode)"
3578		print "<a href=\"$manhtml_url\">HTML</a>"
3579		manraw_url="$(print raw_files/new/$P.man.raw | url_encode)"
3580		print "<a href=\"$manraw_url\">Raw</a>"
3581	elif [[ -n $manpage ]]; then
3582		print " --- ---- ---"
3583	fi
3584
3585	print "</p>"
3586
3587	# Insert delta comments
3588	print "<blockquote><pre>"
3589	getcomments html $P $PP
3590	print "</pre>"
3591
3592	# Add additional comments comment
3593	print "<!-- Add comments to explain changes in $P here -->"
3594
3595	# Add count of changes.
3596	if [[ -f $F.count ]]; then
3597	    cat $F.count
3598	    rm $F.count
3599	fi
3600
3601	if [[ $SCM_MODE == "mercurial" ||
3602	    $SCM_MODE == "unknown" ]]; then
3603		# Include warnings for important file mode situations:
3604		# 1) New executable files
3605		# 2) Permission changes of any kind
3606		# 3) Existing executable files
3607		old_mode=
3608		if [[ -f $WDIR/raw_files/old/$PP ]]; then
3609			old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3610		fi
3611
3612		new_mode=
3613		if [[ -f $WDIR/raw_files/new/$P ]]; then
3614			new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3615		fi
3616
3617		if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3618			print "<span class=\"chmod\">"
3619			print "<p>new executable file: mode $new_mode</p>"
3620			print "</span>"
3621		elif [[ -n "$old_mode" && -n "$new_mode" &&
3622		    "$old_mode" != "$new_mode" ]]; then
3623			print "<span class=\"chmod\">"
3624			print "<p>mode change: $old_mode to $new_mode</p>"
3625			print "</span>"
3626		elif [[ "$new_mode" = *[1357]* ]]; then
3627			print "<span class=\"chmod\">"
3628			print "<p>executable file: mode $new_mode</p>"
3629			print "</span>"
3630		fi
3631	fi
3632
3633	print "</blockquote>"
3634done
3635
3636print
3637print
3638print "<hr></hr>"
3639print "<p style=\"font-size: small\">"
3640print "This code review page was prepared using <b>$0</b>."
3641print "Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3642print "illumos</a> project.  The latest version may be obtained"
3643print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3644print "</body>"
3645print "</html>"
3646
3647exec 1<&-			# Close FD 1.
3648exec 1<&3			# dup FD 3 to restore stdout.
3649exec 3<&-			# close FD 3.
3650
3651print "Done."
3652
3653#
3654# If remote deletion was specified and fails do not continue.
3655#
3656if [[ -n $Dflag ]]; then
3657	delete_webrev 1 1
3658	(( $? == 0 )) || exit $?
3659fi
3660
3661if [[ -n $Uflag ]]; then
3662	upload_webrev
3663	exit $?
3664fi
3665