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