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