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