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