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