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