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