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