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