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