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