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