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