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