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