xref: /illumos-gate/usr/src/tools/scripts/webrev.sh (revision 7a088f03b431bdffa96c3b2175964d4d38420caa)
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	typeset ret=""
1057	if [[ $2 == $cur ]]; then   # Should never happen.
1058		# Should never happen.
1059		print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1060		print -u2 "to \"$2\".  Check input paths.  Framed webrev "
1061		print -u2 "will not be relocatable!"
1062		print $2
1063		return
1064	fi
1065
1066	while [[ -n ${cur} ]];
1067	do
1068		cur=${cur%%*(/)*([!/])}
1069		if [[ -z $ret ]]; then
1070			ret=".."
1071		else
1072			ret="../$ret"
1073		fi
1074	done
1075	print $ret
1076}
1077
1078
1079#
1080# frame_nav_js
1081#
1082# Emit javascript for frame navigation
1083#
1084function frame_nav_js
1085{
1086cat << \EOF
1087var myInt;
1088var scrolling=0;
1089var sfactor = 3;
1090var scount=10;
1091
1092function scrollByPix() {
1093	if (scount<=0) {
1094		sfactor*=1.2;
1095		scount=10;
1096	}
1097	parent.lhs.scrollBy(0,sfactor);
1098	parent.rhs.scrollBy(0,sfactor);
1099	scount--;
1100}
1101
1102function scrollToAnc(num) {
1103
1104	// Update the value of the anchor in the form which we use as
1105	// storage for this value.  setAncValue() will take care of
1106	// correcting for overflow and underflow of the value and return
1107	// us the new value.
1108	num = setAncValue(num);
1109
1110	// Set location and scroll back a little to expose previous
1111	// lines.
1112	//
1113	// Note that this could be improved: it is possible although
1114	// complex to compute the x and y position of an anchor, and to
1115	// scroll to that location directly.
1116	//
1117	parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1118	parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1119
1120	parent.lhs.scrollBy(0,-30);
1121	parent.rhs.scrollBy(0,-30);
1122}
1123
1124function getAncValue()
1125{
1126	return (parseInt(parent.nav.document.diff.real.value));
1127}
1128
1129function setAncValue(val)
1130{
1131	if (val <= 0) {
1132		val = 0;
1133		parent.nav.document.diff.real.value = val;
1134		parent.nav.document.diff.display.value = "BOF";
1135		return (val);
1136	}
1137
1138	//
1139	// The way we compute the max anchor value is to stash it
1140	// inline in the left and right hand side pages-- it's the same
1141	// on each side, so we pluck from the left.
1142	//
1143	maxval = parent.lhs.document.eof.value.value;
1144	if (val < maxval) {
1145		parent.nav.document.diff.real.value = val;
1146		parent.nav.document.diff.display.value = val.toString();
1147		return (val);
1148	}
1149
1150	// this must be: val >= maxval
1151	val = maxval;
1152	parent.nav.document.diff.real.value = val;
1153	parent.nav.document.diff.display.value = "EOF";
1154	return (val);
1155}
1156
1157function stopScroll() {
1158	if (scrolling==1) {
1159		clearInterval(myInt);
1160		scrolling=0;
1161	}
1162}
1163
1164function startScroll() {
1165	stopScroll();
1166	scrolling=1;
1167	myInt=setInterval("scrollByPix()",10);
1168}
1169
1170function handlePress(b) {
1171
1172	switch (b) {
1173	    case 1 :
1174		scrollToAnc(-1);
1175		break;
1176	    case 2 :
1177		scrollToAnc(getAncValue() - 1);
1178		break;
1179	    case 3 :
1180		sfactor=-3;
1181		startScroll();
1182		break;
1183	    case 4 :
1184		sfactor=3;
1185		startScroll();
1186		break;
1187	    case 5 :
1188		scrollToAnc(getAncValue() + 1);
1189		break;
1190	    case 6 :
1191		scrollToAnc(999999);
1192		break;
1193	}
1194}
1195
1196function handleRelease(b) {
1197	stopScroll();
1198}
1199
1200function keypress(ev) {
1201	var keynum;
1202	var keychar;
1203
1204	if (window.event) { // IE
1205		keynum = ev.keyCode;
1206	} else if (ev.which) { // non-IE
1207		keynum = ev.which;
1208	}
1209
1210	keychar = String.fromCharCode(keynum);
1211
1212	if (keychar == "k") {
1213		handlePress(2);
1214		return (0);
1215	} else if (keychar == "j" || keychar == " ") {
1216		handlePress(5);
1217		return (0);
1218	}
1219	return (1);
1220}
1221
1222function ValidateDiffNum(){
1223	val = parent.nav.document.diff.display.value;
1224	if (val == "EOF") {
1225		scrollToAnc(999999);
1226		return;
1227	}
1228
1229	if (val == "BOF") {
1230		scrollToAnc(0);
1231		return;
1232	}
1233
1234        i=parseInt(val);
1235        if (isNaN(i)) {
1236                parent.nav.document.diff.display.value = getAncValue();
1237        } else {
1238                scrollToAnc(i);
1239        }
1240        return false;
1241}
1242
1243EOF
1244}
1245
1246#
1247# frame_navigation
1248#
1249# Output anchor navigation file for framed sdiffs.
1250#
1251function frame_navigation
1252{
1253	print "$HTML<head>$STDHEAD"
1254
1255	cat << \EOF
1256<title>Anchor Navigation</title>
1257<meta http-equiv="Content-Script-Type" content="text/javascript">
1258<meta http-equiv="Content-Type" content="text/html">
1259
1260<style type="text/css">
1261    div.button td { padding-left: 5px; padding-right: 5px;
1262		    background-color: #eee; text-align: center;
1263		    border: 1px #444 outset; cursor: pointer; }
1264    div.button a { font-weight: bold; color: black }
1265    div.button td:hover { background: #ffcc99; }
1266</style>
1267EOF
1268
1269	print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1270
1271	cat << \EOF
1272</head>
1273<body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1274	onkeypress="keypress(event);">
1275    <noscript lang="javascript">
1276      <center>
1277	<p><big>Framed Navigation controls require Javascript</big><br></br>
1278	Either this browser is incompatable or javascript is not enabled</p>
1279      </center>
1280    </noscript>
1281    <table width="100%" border="0" align="center">
1282	<tr>
1283          <td valign="middle" width="25%">Diff navigation:
1284          Use 'j' and 'k' for next and previous diffs; or use buttons
1285          at right</td>
1286	  <td align="center" valign="top" width="50%">
1287	    <div class="button">
1288	      <table border="0" align="center">
1289                  <tr>
1290		    <td>
1291		      <a onMouseDown="handlePress(1);return true;"
1292			 onMouseUp="handleRelease(1);return true;"
1293			 onMouseOut="handleRelease(1);return true;"
1294			 onClick="return false;"
1295			 title="Go to Beginning Of file">BOF</a></td>
1296		    <td>
1297		      <a onMouseDown="handlePress(3);return true;"
1298			 onMouseUp="handleRelease(3);return true;"
1299			 onMouseOut="handleRelease(3);return true;"
1300			 title="Scroll Up: Press and Hold to accelerate"
1301			 onClick="return false;">Scroll Up</a></td>
1302		    <td>
1303		      <a onMouseDown="handlePress(2);return true;"
1304			 onMouseUp="handleRelease(2);return true;"
1305			 onMouseOut="handleRelease(2);return true;"
1306			 title="Go to previous Diff"
1307			 onClick="return false;">Prev Diff</a>
1308		    </td></tr>
1309
1310		  <tr>
1311		    <td>
1312		      <a onMouseDown="handlePress(6);return true;"
1313			 onMouseUp="handleRelease(6);return true;"
1314			 onMouseOut="handleRelease(6);return true;"
1315			 onClick="return false;"
1316			 title="Go to End Of File">EOF</a></td>
1317		    <td>
1318		      <a onMouseDown="handlePress(4);return true;"
1319			 onMouseUp="handleRelease(4);return true;"
1320			 onMouseOut="handleRelease(4);return true;"
1321			 title="Scroll Down: Press and Hold to accelerate"
1322			 onClick="return false;">Scroll Down</a></td>
1323		    <td>
1324		      <a onMouseDown="handlePress(5);return true;"
1325			 onMouseUp="handleRelease(5);return true;"
1326			 onMouseOut="handleRelease(5);return true;"
1327			 title="Go to next Diff"
1328			 onClick="return false;">Next Diff</a></td>
1329		  </tr>
1330              </table>
1331	    </div>
1332	  </td>
1333	  <th valign="middle" width="25%">
1334	    <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1335		<input name="display" value="BOF" size="8" type="text"></input>
1336		<input name="real" value="0" size="8" type="hidden"></input>
1337	    </form>
1338	  </th>
1339	</tr>
1340    </table>
1341  </body>
1342</html>
1343EOF
1344}
1345
1346
1347
1348#
1349# diff_to_html <filename> <filepath> { U | C } <comment>
1350#
1351# Processes the output of diff to produce an HTML file representing either
1352# context or unified diffs.
1353#
1354diff_to_html()
1355{
1356	TNAME=$1
1357	TPATH=$2
1358	DIFFTYPE=$3
1359	COMMENT=$4
1360
1361	print "$HTML<head>$STDHEAD"
1362	print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1363
1364	if [[ $DIFFTYPE == "U" ]]; then
1365		print "$UDIFFCSS"
1366	fi
1367
1368	cat <<-EOF
1369	</head>
1370	<body id="SUNWwebrev">
1371        <a class="print" href="javascript:print()">Print this page</a>
1372	<pre>$COMMENT</pre>
1373        <pre>
1374	EOF
1375
1376	html_quote | $AWK '
1377	/^--- new/	{ next }
1378	/^\+\+\+ new/	{ next }
1379	/^--- old/	{ next }
1380	/^\*\*\* old/	{ next }
1381	/^\*\*\*\*/	{ next }
1382	/^-------/	{ printf "<center><h1>%s</h1></center>\n", $0; next }
1383	/^\@\@.*\@\@$/	{ printf "</pre><hr></hr><pre>\n";
1384			  printf "<span class=\"newmarker\">%s</span>\n", $0;
1385			  next}
1386
1387	/^\*\*\*/	{ printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1388			  next}
1389	/^---/		{ printf "<span class=\"newmarker\">%s</span>\n", $0;
1390			  next}
1391	/^\+/		{printf "<span class=\"new\">%s</span>\n", $0; next}
1392	/^!/		{printf "<span class=\"changed\">%s</span>\n", $0; next}
1393	/^-/		{printf "<span class=\"removed\">%s</span>\n", $0; next}
1394			{printf "%s\n", $0; next}
1395	'
1396
1397	print "</pre></body></html>\n"
1398}
1399
1400
1401#
1402# source_to_html { new | old } <filename>
1403#
1404# Process a plain vanilla source file to transform it into an HTML file.
1405#
1406source_to_html()
1407{
1408	WHICH=$1
1409	TNAME=$2
1410
1411	print "$HTML<head>$STDHEAD"
1412	print "<title>$WNAME $WHICH $TNAME</title>"
1413	print "<body id=\"SUNWwebrev\">"
1414	print "<pre>"
1415	html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1416	print "</pre></body></html>"
1417}
1418
1419#
1420# comments_from_teamware {text|html} parent-file child-file
1421#
1422# Find the first delta in the child that's not in the parent.  Get the
1423# newest delta from the parent, get all deltas from the child starting
1424# with that delta, and then get all info starting with the second oldest
1425# delta in that list (the first delta unique to the child).
1426#
1427# This code adapted from Bill Shannon's "spc" script
1428#
1429comments_from_teamware()
1430{
1431	fmt=$1
1432	pfile=$PWS/$2
1433	cfile=$CWS/$3
1434
1435	if [[ ! -f $PWS/${2%/*}/SCCS/s.${2##*/} && -n $RWS ]]; then
1436		pfile=$RWS/$2
1437	fi
1438
1439	if [[ -f $pfile ]]; then
1440		psid=$($SCCS prs -d:I: $pfile 2>/dev/null)
1441	else
1442		psid=1.1
1443	fi
1444
1445	set -A sids $($SCCS prs -l -r$psid -d:I: $cfile 2>/dev/null)
1446	N=${#sids[@]}
1447
1448	nawkprg='
1449		/^COMMENTS:/	{p=1; continue}
1450		/^D [0-9]+\.[0-9]+/ {printf "--- %s ---\n", $2; p=0; }
1451		NF == 0u	{ continue }
1452		{if (p==0) continue; print $0 }'
1453
1454	if [[ $N -ge 2 ]]; then
1455		sid1=${sids[$((N-2))]}	# Gets 2nd to last sid
1456
1457		if [[ $fmt == "text" ]]; then
1458			$SCCS prs -l -r$sid1 $cfile  2>/dev/null | \
1459			    $AWK "$nawkprg"
1460			return
1461		fi
1462
1463		$SCCS prs -l -r$sid1 $cfile  2>/dev/null | \
1464		    html_quote | bug2url | sac2url | $AWK "$nawkprg"
1465	fi
1466}
1467
1468#
1469# comments_from_wx {text|html} filepath
1470#
1471# Given the pathname of a file, find its location in a "wx" active
1472# file list and print the following comment.  Output is either text or
1473# HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1474# are turned into URLs.
1475#
1476# This is also used with Mercurial and the file list provided by hg-active.
1477#
1478comments_from_wx()
1479{
1480	typeset fmt=$1
1481	typeset p=$2
1482
1483	comm=`$AWK '
1484	$1 == "'$p'" {
1485		do getline ; while (NF > 0)
1486		getline
1487		while (NF > 0) { print ; getline }
1488		exit
1489	}' < $wxfile`
1490
1491	if [[ -z $comm ]]; then
1492		comm="*** NO COMMENTS ***"
1493	fi
1494
1495	if [[ $fmt == "text" ]]; then
1496		print -- "$comm"
1497		return
1498	fi
1499
1500	print -- "$comm" | html_quote | bug2url | sac2url
1501
1502}
1503
1504#
1505# getcomments {text|html} filepath parentpath
1506#
1507# Fetch the comments depending on what SCM mode we're in.
1508#
1509getcomments()
1510{
1511	typeset fmt=$1
1512	typeset p=$2
1513	typeset pp=$3
1514
1515	if [[ -n $Nflag ]]; then
1516		return
1517	fi
1518	#
1519	# Mercurial support uses a file list in wx format, so this
1520	# will be used there, too
1521	#
1522	if [[ -n $wxfile ]]; then
1523		comments_from_wx $fmt $p
1524	else
1525		if [[ $SCM_MODE == "teamware" ]]; then
1526			comments_from_teamware $fmt $pp $p
1527		fi
1528	fi
1529}
1530
1531#
1532# printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1533#
1534# Print out Code Inspection figures similar to sccs-prt(1) format.
1535#
1536function printCI
1537{
1538	integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1539	typeset str
1540	if (( tot == 1 )); then
1541		str="line"
1542	else
1543		str="lines"
1544	fi
1545	printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1546	    $tot $str $ins $del $mod $unc
1547}
1548
1549
1550#
1551# difflines <oldfile> <newfile>
1552#
1553# Calculate and emit number of added, removed, modified and unchanged lines,
1554# and total lines changed, the sum of added + removed + modified.
1555#
1556function difflines
1557{
1558	integer tot mod del ins unc err
1559	typeset filename
1560
1561	eval $( diff -e $1 $2 | $AWK '
1562	# Change range of lines: N,Nc
1563	/^[0-9]*,[0-9]*c$/ {
1564		n=split(substr($1,1,length($1)-1), counts, ",");
1565		if (n != 2) {
1566		    error=2
1567		    exit;
1568		}
1569		#
1570		# 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1571		# following would be 5 - 3 = 2! Hence +1 for correction.
1572		#
1573		r=(counts[2]-counts[1])+1;
1574
1575		#
1576		# Now count replacement lines: each represents a change instead
1577		# of a delete, so increment c and decrement r.
1578		#
1579		while (getline != /^\.$/) {
1580			c++;
1581			r--;
1582		}
1583		#
1584		# If there were more replacement lines than original lines,
1585		# then r will be negative; in this case there are no deletions,
1586		# but there are r changes that should be counted as adds, and
1587		# since r is negative, subtract it from a and add it to c.
1588		#
1589		if (r < 0) {
1590			a-=r;
1591			c+=r;
1592		}
1593
1594		#
1595		# If there were more original lines than replacement lines, then
1596		# r will be positive; in this case, increment d by that much.
1597		#
1598		if (r > 0) {
1599			d+=r;
1600		}
1601		next;
1602	}
1603
1604	# Change lines: Nc
1605	/^[0-9].*c$/ {
1606		# The first line is a replacement; any more are additions.
1607		if (getline != /^\.$/) {
1608			c++;
1609			while (getline != /^\.$/) a++;
1610		}
1611		next;
1612	}
1613
1614	# Add lines: both Na and N,Na
1615	/^[0-9].*a$/ {
1616		while (getline != /^\.$/) a++;
1617		next;
1618	}
1619
1620	# Delete range of lines: N,Nd
1621	/^[0-9]*,[0-9]*d$/ {
1622		n=split(substr($1,1,length($1)-1), counts, ",");
1623		if (n != 2) {
1624			error=2
1625			exit;
1626		}
1627		#
1628		# 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1629		# following would be 5 - 3 = 2! Hence +1 for correction.
1630		#
1631		r=(counts[2]-counts[1])+1;
1632		d+=r;
1633		next;
1634	}
1635
1636	# Delete line: Nd.   For example 10d says line 10 is deleted.
1637	/^[0-9]*d$/ {d++; next}
1638
1639	# Should not get here!
1640	{
1641		error=1;
1642		exit;
1643	}
1644
1645	# Finish off - print results
1646	END {
1647		printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1648		    (c+d+a), c, d, a, error);
1649	}' )
1650
1651	# End of $AWK, Check to see if any trouble occurred.
1652	if (( $? > 0 || err > 0 )); then
1653		print "Unexpected Error occurred reading" \
1654		    "\`diff -e $1 $2\`: \$?=$?, err=" $err
1655		return
1656	fi
1657
1658	# Accumulate totals
1659	(( TOTL += tot ))
1660	(( TMOD += mod ))
1661	(( TDEL += del ))
1662	(( TINS += ins ))
1663	# Calculate unchanged lines
1664	unc=`wc -l < $1`
1665	if (( unc > 0 )); then
1666		(( unc -= del + mod ))
1667		(( TUNC += unc ))
1668	fi
1669	# print summary
1670	print "<span class=\"lineschanged\">"
1671	printCI $tot $ins $del $mod $unc
1672	print "</span>"
1673}
1674
1675
1676#
1677# flist_from_wx
1678#
1679# Sets up webrev to source its information from a wx-formatted file.
1680# Sets the global 'wxfile' variable.
1681#
1682function flist_from_wx
1683{
1684	typeset argfile=$1
1685	if [[ -n ${argfile%%/*} ]]; then
1686		#
1687		# If the wx file pathname is relative then make it absolute
1688		# because the webrev does a "cd" later on.
1689		#
1690		wxfile=$PWD/$argfile
1691	else
1692		wxfile=$argfile
1693	fi
1694
1695	$AWK '{ c = 1; print;
1696	  while (getline) {
1697		if (NF == 0) { c = -c; continue }
1698		if (c > 0) print
1699	  }
1700	}' $wxfile > $FLIST
1701
1702	print " Done."
1703}
1704
1705#
1706# flist_from_teamware [ <args-to-putback-n> ]
1707#
1708# Generate the file list by extracting file names from a putback -n.  Some
1709# names may come from the "update/create" messages and others from the
1710# "currently checked out" warning.  Renames are detected here too.  Extract
1711# values for CODEMGR_WS and CODEMGR_PARENT from the output of the putback
1712# -n as well, but remove them if they are already defined.
1713#
1714function flist_from_teamware
1715{
1716	if [[ -n $codemgr_parent && -z $parent_webrev ]]; then
1717		if [[ ! -d $codemgr_parent/Codemgr_wsdata ]]; then
1718			print -u2 "parent $codemgr_parent doesn't look like a" \
1719			    "valid teamware workspace"
1720			exit 1
1721		fi
1722		parent_args="-p $codemgr_parent"
1723	fi
1724
1725	print " File list from: 'putback -n $parent_args $*' ... \c"
1726
1727	putback -n $parent_args $* 2>&1 |
1728	    $AWK '
1729		/^update:|^create:/	{print $2}
1730		/^Parent workspace:/	{printf("CODEMGR_PARENT=%s\n",$3)}
1731		/^Child workspace:/	{printf("CODEMGR_WS=%s\n",$3)}
1732		/^The following files are currently checked out/ {p = 1; continue}
1733		NF == 0			{p=0 ; continue}
1734		/^rename/		{old=$3}
1735		$1 == "to:"		{print $2, old}
1736		/^"/			{continue}
1737		p == 1			{print $1}' |
1738	    sort -r -k 1,1 -u | sort > $FLIST
1739
1740	print " Done."
1741}
1742
1743#
1744# Call hg-active to get the active list output in the wx active list format
1745#
1746function hg_active_wxfile
1747{
1748	typeset child=$1
1749	typeset parent=$2
1750
1751	TMPFLIST=/tmp/$$.active
1752	$HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1753	wxfile=$TMPFLIST
1754}
1755
1756#
1757# flist_from_mercurial
1758# Call hg-active to get a wx-style active list, and hand it off to
1759# flist_from_wx
1760#
1761function flist_from_mercurial
1762{
1763	typeset child=$1
1764	typeset parent=$2
1765
1766	print " File list from: hg-active -p $parent ...\c"
1767
1768	if [[ ! -x $HG_ACTIVE ]]; then
1769		print		# Blank line for the \c above
1770		print -u2 "Error: hg-active tool not found.  Exiting"
1771		exit 1
1772	fi
1773	hg_active_wxfile $child $parent
1774
1775	# flist_from_wx prints the Done, so we don't have to.
1776	flist_from_wx $TMPFLIST
1777}
1778
1779#
1780# flist_from_subversion
1781#
1782# Generate the file list by extracting file names from svn status.
1783#
1784function flist_from_subversion
1785{
1786	CWS=$1
1787	OLDPWD=$2
1788
1789	cd $CWS
1790	print -u2 " File list from: svn status ... \c"
1791	svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1792	print -u2 " Done."
1793	cd $OLDPWD
1794}
1795
1796function env_from_flist
1797{
1798	[[ -r $FLIST ]] || return
1799
1800	#
1801	# Use "eval" to set env variables that are listed in the file
1802	# list.  Then copy those into our local versions of those
1803	# variables if they have not been set already.
1804	#
1805	eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1806
1807	if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1808		codemgr_ws=$CODEMGR_WS
1809		export CODEMGR_WS
1810	fi
1811
1812	#
1813	# Check to see if CODEMGR_PARENT is set in the flist file.
1814	#
1815	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1816		codemgr_parent=$CODEMGR_PARENT
1817		export CODEMGR_PARENT
1818	fi
1819}
1820
1821function look_for_prog
1822{
1823	typeset path
1824	typeset ppath
1825	typeset progname=$1
1826
1827	ppath=$PATH
1828	ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1829	ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin
1830	ppath=$ppath:/opt/onbld/bin/`uname -p`
1831
1832	PATH=$ppath prog=`whence $progname`
1833	if [[ -n $prog ]]; then
1834		print $prog
1835	fi
1836}
1837
1838function get_file_mode
1839{
1840	$PERL -e '
1841		if (@stat = stat($ARGV[0])) {
1842			$mode = $stat[2] & 0777;
1843			printf "%03o\n", $mode;
1844			exit 0;
1845		} else {
1846			exit 1;
1847		}
1848	    ' $1
1849}
1850
1851function build_old_new_teamware
1852{
1853	typeset olddir="$1"
1854	typeset newdir="$2"
1855
1856	# If the child's version doesn't exist then
1857	# get a readonly copy.
1858
1859	if [[ ! -f $CWS/$DIR/$F && -f $CWS/$DIR/SCCS/s.$F ]]; then
1860		$SCCS get -s -p $CWS/$DIR/$F > $CWS/$DIR/$F
1861	fi
1862
1863	# The following two sections propagate file permissions the
1864	# same way SCCS does.  If the file is already under version
1865	# control, always use permissions from the SCCS/s.file.  If
1866	# the file is not under SCCS control, use permissions from the
1867	# working copy.  In all cases, the file copied to the webrev
1868	# is set to read only, and group/other permissions are set to
1869	# match those of the file owner.  This way, even if the file
1870	# is currently checked out, the webrev will display the final
1871	# permissions that would result after check in.
1872
1873	#
1874	# Snag new version of file.
1875	#
1876	rm -f $newdir/$DIR/$F
1877	cp $CWS/$DIR/$F $newdir/$DIR/$F
1878	if [[ -f $CWS/$DIR/SCCS/s.$F ]]; then
1879		chmod `get_file_mode $CWS/$DIR/SCCS/s.$F` \
1880		    $newdir/$DIR/$F
1881	fi
1882	chmod u-w,go=u $newdir/$DIR/$F
1883
1884	#
1885	# Get the parent's version of the file. First see whether the
1886	# child's version is checked out and get the parent's version
1887	# with keywords expanded or unexpanded as appropriate.
1888	#
1889	if [[ -f $PWS/$PDIR/$PF && ! -f $PWS/$PDIR/SCCS/s.$PF && \
1890	    ! -f $PWS/$PDIR/SCCS/p.$PF ]]; then
1891		# Parent is not a real workspace, but just a raw
1892		# directory tree - use the file that's there as
1893		# the old file.
1894
1895		rm -f $olddir/$PDIR/$PF
1896		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1897	else
1898		if [[ -f $PWS/$PDIR/SCCS/s.$PF ]]; then
1899			real_parent=$PWS
1900		else
1901			real_parent=$RWS
1902		fi
1903
1904		rm -f $olddir/$PDIR/$PF
1905
1906		if [[ -f $real_parent/$PDIR/$PF ]]; then
1907			if [ -f $CWS/$DIR/SCCS/p.$F ]; then
1908				$SCCS get -s -p -k $real_parent/$PDIR/$PF > \
1909				    $olddir/$PDIR/$PF
1910			else
1911				$SCCS get -s -p    $real_parent/$PDIR/$PF > \
1912				    $olddir/$PDIR/$PF
1913			fi
1914			chmod `get_file_mode $real_parent/$PDIR/SCCS/s.$PF` \
1915			    $olddir/$PDIR/$PF
1916		fi
1917	fi
1918	if [[ -f $olddir/$PDIR/$PF ]]; then
1919		chmod u-w,go=u $olddir/$PDIR/$PF
1920	fi
1921}
1922
1923function build_old_new_mercurial
1924{
1925	typeset olddir="$1"
1926	typeset newdir="$2"
1927	typeset old_mode=
1928	typeset new_mode=
1929	typeset file
1930
1931	#
1932	# Get old file mode, from the parent revision manifest entry.
1933	# Mercurial only stores a "file is executable" flag, but the
1934	# manifest will display an octal mode "644" or "755".
1935	#
1936	if [[ "$PDIR" == "." ]]; then
1937		file="$PF"
1938	else
1939		file="$PDIR/$PF"
1940	fi
1941	file=`echo $file | $SED 's#/#\\\/#g'`
1942	# match the exact filename, and return only the permission digits
1943	old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
1944	    < $HG_PARENT_MANIFEST`
1945
1946	#
1947	# Get new file mode, directly from the filesystem.
1948	# Normalize the mode to match Mercurial's behavior.
1949	#
1950	new_mode=`get_file_mode $CWS/$DIR/$F`
1951	if [[ -n "$new_mode" ]]; then
1952		if [[ "$new_mode" = *[1357]* ]]; then
1953			new_mode=755
1954		else
1955			new_mode=644
1956		fi
1957	fi
1958
1959	#
1960	# new version of the file.
1961	#
1962	rm -rf $newdir/$DIR/$F
1963	if [[ -e $CWS/$DIR/$F ]]; then
1964		cp $CWS/$DIR/$F $newdir/$DIR/$F
1965		if [[ -n $new_mode ]]; then
1966			chmod $new_mode $newdir/$DIR/$F
1967		else
1968			# should never happen
1969			print -u2 "ERROR: set mode of $newdir/$DIR/$F"
1970		fi
1971	fi
1972
1973	#
1974	# parent's version of the file
1975	#
1976	# Note that we get this from the last version common to both
1977	# ourselves and the parent.  References are via $CWS since we have no
1978	# guarantee that the parent workspace is reachable via the filesystem.
1979	#
1980	if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
1981		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1982	elif [[ -n $HG_PARENT ]]; then
1983		hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
1984		    $olddir/$PDIR/$PF 2>/dev/null
1985
1986		if (( $? != 0 )); then
1987			rm -f $olddir/$PDIR/$PF
1988		else
1989			if [[ -n $old_mode ]]; then
1990				chmod $old_mode $olddir/$PDIR/$PF
1991			else
1992				# should never happen
1993				print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
1994			fi
1995		fi
1996	fi
1997}
1998
1999function build_old_new_subversion
2000{
2001	typeset olddir="$1"
2002	typeset newdir="$2"
2003
2004	# Snag new version of file.
2005	rm -f $newdir/$DIR/$F
2006	[[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2007
2008	if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2009		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2010	else
2011		# Get the parent's version of the file.
2012		svn status $CWS/$DIR/$F | read stat file
2013		if [[ $stat != "A" ]]; then
2014			svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2015		fi
2016	fi
2017}
2018
2019function build_old_new_unknown
2020{
2021	typeset olddir="$1"
2022	typeset newdir="$2"
2023
2024	#
2025	# Snag new version of file.
2026	#
2027	rm -f $newdir/$DIR/$F
2028	[[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2029
2030	#
2031	# Snag the parent's version of the file.
2032	#
2033	if [[ -f $PWS/$PDIR/$PF ]]; then
2034		rm -f $olddir/$PDIR/$PF
2035		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2036	fi
2037}
2038
2039function build_old_new
2040{
2041	typeset WDIR=$1
2042	typeset PWS=$2
2043	typeset PDIR=$3
2044	typeset PF=$4
2045	typeset CWS=$5
2046	typeset DIR=$6
2047	typeset F=$7
2048
2049	typeset olddir="$WDIR/raw_files/old"
2050	typeset newdir="$WDIR/raw_files/new"
2051
2052	mkdir -p $olddir/$PDIR
2053	mkdir -p $newdir/$DIR
2054
2055	if [[ $SCM_MODE == "teamware" ]]; then
2056		build_old_new_teamware "$olddir" "$newdir"
2057	elif [[ $SCM_MODE == "mercurial" ]]; then
2058		build_old_new_mercurial "$olddir" "$newdir"
2059	elif [[ $SCM_MODE == "subversion" ]]; then
2060		build_old_new_subversion "$olddir" "$newdir"
2061	elif [[ $SCM_MODE == "unknown" ]]; then
2062		build_old_new_unknown "$olddir" "$newdir"
2063	fi
2064
2065	if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2066		print "*** Error: file not in parent or child"
2067		return 1
2068	fi
2069	return 0
2070}
2071
2072
2073#
2074# Usage message.
2075#
2076function usage
2077{
2078	print 'Usage:\twebrev [common-options]
2079	webrev [common-options] ( <file> | - )
2080	webrev [common-options] -w <wx file>
2081
2082Options:
2083	-D: delete remote webrev
2084	-i <filename>: Include <filename> in the index.html file.
2085	-n: do not generate the webrev (useful with -U)
2086	-O: Print bugids/arc cases suitable for OpenSolaris.
2087	-o <outdir>: Output webrev to specified directory.
2088	-p <compare-against>: Use specified parent wkspc or basis for comparison
2089	-t <remote_target>: Specify remote destination for webrev upload
2090	-U: upload the webrev to remote destination
2091	-w <wxfile>: Use specified wx active file.
2092
2093Environment:
2094	WDIR: Control the output directory.
2095	WEBREV_BUGURL: Control the URL prefix for bugids.
2096	WEBREV_SACURL: Control the URL prefix for ARC cases.
2097	WEBREV_TRASH_DIR: Set directory for webrev delete.
2098
2099SCM Specific Options:
2100	TeamWare: webrev [common-options] -l [arguments to 'putback']
2101
2102SCM Environment:
2103	CODEMGR_WS: Workspace location.
2104	CODEMGR_PARENT: Parent workspace location.
2105'
2106
2107	exit 2
2108}
2109
2110#
2111#
2112# Main program starts here
2113#
2114#
2115
2116trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2117
2118set +o noclobber
2119
2120PATH=$(dirname $(whence $0)):$PATH
2121
2122[[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2123[[ -z $WX ]] && WX=`look_for_prog wx`
2124[[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
2125[[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2126[[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2127[[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2128[[ -z $PERL ]] && PERL=`look_for_prog perl`
2129[[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2130[[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2131[[ -z $AWK ]] && AWK=`look_for_prog nawk`
2132[[ -z $AWK ]] && AWK=`look_for_prog gawk`
2133[[ -z $AWK ]] && AWK=`look_for_prog awk`
2134[[ -z $SCP ]] && SCP=`look_for_prog scp`
2135[[ -z $SED ]] && SED=`look_for_prog sed`
2136[[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2137[[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2138[[ -z $GREP ]] && GREP=`look_for_prog grep`
2139[[ -z $FIND ]] && FIND=`look_for_prog find`
2140
2141# set name of trash directory for remote webrev deletion
2142TRASH_DIR=".trash"
2143[[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2144
2145if [[ ! -x $PERL ]]; then
2146	print -u2 "Error: No perl interpreter found.  Exiting."
2147	exit 1
2148fi
2149
2150if [[ ! -x $WHICH_SCM ]]; then
2151	print -u2 "Error: Could not find which_scm.  Exiting."
2152	exit 1
2153fi
2154
2155#
2156# These aren't fatal, but we want to note them to the user.
2157# We don't warn on the absence of 'wx' until later when we've
2158# determined that we actually need to try to invoke it.
2159#
2160[[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2161[[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2162[[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2163
2164# Declare global total counters.
2165integer TOTL TINS TDEL TMOD TUNC
2166
2167# default remote host for upload/delete
2168typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2169# prefixes for upload targets
2170typeset -r rsync_prefix="rsync://"
2171typeset -r ssh_prefix="ssh://"
2172
2173Dflag=
2174flist_mode=
2175flist_file=
2176iflag=
2177lflag=
2178Nflag=
2179nflag=
2180Oflag=
2181oflag=
2182pflag=
2183tflag=
2184uflag=
2185Uflag=
2186wflag=
2187remote_target=
2188
2189#
2190# NOTE: when adding/removing options it is necessary to sync the list
2191# 	with usr/src/tools/onbld/hgext/cdm.py
2192#
2193while getopts "i:o:p:lwONnt:UD" opt
2194do
2195	case $opt in
2196	D)	Dflag=1;;
2197
2198	i)	iflag=1
2199		INCLUDE_FILE=$OPTARG;;
2200
2201	#
2202	# If -l has been specified, we need to abort further options
2203	# processing, because subsequent arguments are going to be
2204	# arguments to 'putback -n'.
2205	#
2206	l)	lflag=1
2207		break;;
2208
2209	N)	Nflag=1;;
2210
2211	n)	nflag=1;;
2212
2213	O)	Oflag=1;;
2214
2215	o)	oflag=1
2216		WDIR=$OPTARG;;
2217
2218	p)	pflag=1
2219		codemgr_parent=$OPTARG;;
2220
2221	t)	tflag=1
2222		remote_target=$OPTARG;;
2223
2224	U)	Uflag=1;;
2225
2226	w)	wflag=1;;
2227
2228	?)	usage;;
2229	esac
2230done
2231
2232FLIST=/tmp/$$.flist
2233
2234if [[ -n $wflag && -n $lflag ]]; then
2235	usage
2236fi
2237
2238# more sanity checking
2239if [[ -n $nflag && -z $Uflag ]]; then
2240	print "it does not make sense to skip webrev generation" \
2241	    "without -U"
2242	exit 1
2243fi
2244
2245if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2246	echo "remote target has to be used only for upload or delete"
2247	exit 1
2248fi
2249
2250#
2251# If this manually set as the parent, and it appears to be an earlier webrev,
2252# then note that fact and set the parent to the raw_files/new subdirectory.
2253#
2254if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2255	parent_webrev="$codemgr_parent"
2256	codemgr_parent="$codemgr_parent/raw_files/new"
2257fi
2258
2259if [[ -z $wflag && -z $lflag ]]; then
2260	shift $(($OPTIND - 1))
2261
2262	if [[ $1 == "-" ]]; then
2263		cat > $FLIST
2264		flist_mode="stdin"
2265		flist_done=1
2266		shift
2267	elif [[ -n $1 ]]; then
2268		if [[ ! -r $1 ]]; then
2269			print -u2 "$1: no such file or not readable"
2270			usage
2271		fi
2272		cat $1 > $FLIST
2273		flist_mode="file"
2274		flist_file=$1
2275		flist_done=1
2276		shift
2277	else
2278		flist_mode="auto"
2279	fi
2280fi
2281
2282#
2283# Before we go on to further consider -l and -w, work out which SCM we think
2284# is in use.
2285#
2286$WHICH_SCM | read SCM_MODE junk || exit 1
2287case "$SCM_MODE" in
2288teamware|mercurial|subversion)
2289	;;
2290unknown)
2291	if [[ $flist_mode == "auto" ]]; then
2292		print -u2 "Unable to determine SCM in use and file list not specified"
2293		print -u2 "See which_scm(1) for SCM detection information."
2294		exit 1
2295	fi
2296	;;
2297*)
2298	if [[ $flist_mode == "auto" ]]; then
2299		print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2300		exit 1
2301	fi
2302	;;
2303esac
2304
2305print -u2 "   SCM detected: $SCM_MODE"
2306
2307if [[ -n $lflag ]]; then
2308	#
2309	# If the -l flag is given instead of the name of a file list,
2310	# then generate the file list by extracting file names from a
2311	# putback -n.
2312	#
2313	shift $(($OPTIND - 1))
2314	if [[ $SCM_MODE == "teamware" ]]; then
2315		flist_from_teamware "$*"
2316	else
2317		print -u2 -- "Error: -l option only applies to TeamWare"
2318		exit 1
2319	fi
2320	flist_done=1
2321	shift $#
2322elif [[ -n $wflag ]]; then
2323	#
2324	# If the -w is given then assume the file list is in Bonwick's "wx"
2325	# command format, i.e.  pathname lines alternating with SCCS comment
2326	# lines with blank lines as separators.  Use the SCCS comments later
2327	# in building the index.html file.
2328	#
2329	shift $(($OPTIND - 1))
2330	wxfile=$1
2331	if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2332		if [[ -r $CODEMGR_WS/wx/active ]]; then
2333			wxfile=$CODEMGR_WS/wx/active
2334		fi
2335	fi
2336
2337	[[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2338	    "be auto-detected (check \$CODEMGR_WS)" && exit 1
2339
2340	if [[ ! -r $wxfile ]]; then
2341		print -u2 "$wxfile: no such file or not readable"
2342		usage
2343	fi
2344
2345	print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2346	flist_from_wx $wxfile
2347	flist_done=1
2348	if [[ -n "$*" ]]; then
2349		shift
2350	fi
2351elif [[ $flist_mode == "stdin" ]]; then
2352	print -u2 " File list from: standard input"
2353elif [[ $flist_mode == "file" ]]; then
2354	print -u2 " File list from: $flist_file"
2355fi
2356
2357if [[ $# -gt 0 ]]; then
2358	print -u2 "WARNING: unused arguments: $*"
2359fi
2360
2361if [[ $SCM_MODE == "teamware" ]]; then
2362	#
2363	# Parent (internally $codemgr_parent) and workspace ($codemgr_ws) can
2364	# be set in a number of ways, in decreasing precedence:
2365	#
2366	#      1) on the command line (only for the parent)
2367	#      2) in the user environment
2368	#      3) in the flist
2369	#      4) automatically based on the workspace (only for the parent)
2370	#
2371
2372	#
2373	# Here is case (2): the user environment
2374	#
2375	[[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
2376	if [[ -n $codemgr_ws && ! -d $codemgr_ws ]]; then
2377		print -u2 "$codemgr_ws: no such workspace"
2378		exit 1
2379	fi
2380
2381	[[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
2382	    codemgr_parent=$CODEMGR_PARENT
2383	if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then
2384		print -u2 "$codemgr_parent: no such directory"
2385		exit 1
2386	fi
2387
2388	#
2389	# If we're in auto-detect mode and we haven't already gotten the file
2390	# list, then see if we can get it by probing for wx.
2391	#
2392	if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then
2393		if [[ ! -x $WX ]]; then
2394			print -u2 "WARNING: wx not found!"
2395		fi
2396
2397		#
2398		# We need to use wx list -w so that we get renamed files, etc.
2399		# but only if a wx active file exists-- otherwise wx will
2400		# hang asking us to initialize our wx information.
2401		#
2402		if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then
2403			print -u2 " File list from: 'wx list -w' ... \c"
2404			$WX list -w > $FLIST
2405			$WX comments > /tmp/$$.wx_comments
2406			wxfile=/tmp/$$.wx_comments
2407			print -u2 "done"
2408			flist_done=1
2409		fi
2410	fi
2411
2412	#
2413	# If by hook or by crook we've gotten a file list by now (perhaps
2414	# from the command line), eval it to extract environment variables from
2415	# it: This is step (3).
2416	#
2417	env_from_flist
2418
2419	#
2420	# Continuing step (3): If we still have no file list, we'll try to get
2421	# it from teamware.
2422	#
2423	if [[ -z $flist_done ]]; then
2424		flist_from_teamware
2425		env_from_flist
2426	fi
2427
2428	#
2429	# (4) If we still don't have a value for codemgr_parent, get it
2430	# from workspace.
2431	#
2432	[[ -z $codemgr_ws ]] && codemgr_ws=`workspace name`
2433	[[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent`
2434	if [[ ! -d $codemgr_parent ]]; then
2435		print -u2 "$CODEMGR_PARENT: no such parent workspace"
2436		exit 1
2437	fi
2438
2439	#
2440	# Observe true directory name of CODEMGR_WS, as used later in
2441	# webrev title.
2442	#
2443	codemgr_ws=$(cd $codemgr_ws;print $PWD)
2444
2445	#
2446	# Reset CODEMGR_WS to make sure teamware commands are happy.
2447	#
2448	CODEMGR_WS=$codemgr_ws
2449	CWS=$codemgr_ws
2450	PWS=$codemgr_parent
2451
2452	[[ -n $parent_webrev ]] && RWS=$(workspace parent $CWS)
2453
2454elif [[ $SCM_MODE == "mercurial" ]]; then
2455	[[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2456	    codemgr_ws=`hg root -R $CODEMGR_WS 2>/dev/null`
2457
2458	[[ -z $codemgr_ws ]] && codemgr_ws=`hg root 2>/dev/null`
2459
2460	#
2461	# Parent can either be specified with -p
2462	# Specified with CODEMGR_PARENT in the environment
2463	# or taken from hg's default path.
2464	#
2465
2466	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2467		codemgr_parent=$CODEMGR_PARENT
2468	fi
2469
2470	if [[ -z $codemgr_parent ]]; then
2471		codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
2472	fi
2473
2474	CWS_REV=`hg parent -R $codemgr_ws --template '{node|short}' 2>/dev/null`
2475	CWS=$codemgr_ws
2476	PWS=$codemgr_parent
2477
2478	#
2479	# If the parent is a webrev, we want to do some things against
2480	# the natural workspace parent (file list, comments, etc)
2481	#
2482	if [[ -n $parent_webrev ]]; then
2483		real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
2484	else
2485		real_parent=$PWS
2486	fi
2487
2488	#
2489	# If hg-active exists, then we run it.  In the case of no explicit
2490	# flist given, we'll use it for our comments.  In the case of an
2491	# explicit flist given we'll try to use it for comments for any
2492	# files mentioned in the flist.
2493	#
2494	if [[ -z $flist_done ]]; then
2495		flist_from_mercurial $CWS $real_parent
2496		flist_done=1
2497	fi
2498
2499	#
2500	# If we have a file list now, pull out any variables set
2501	# therein.  We do this now (rather than when we possibly use
2502	# hg-active to find comments) to avoid stomping specifications
2503	# in the user-specified flist.
2504	#
2505	if [[ -n $flist_done ]]; then
2506		env_from_flist
2507	fi
2508
2509	#
2510	# Only call hg-active if we don't have a wx formatted file already
2511	#
2512	if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2513		print "  Comments from: hg-active -p $real_parent ...\c"
2514		hg_active_wxfile $CWS $real_parent
2515		print " Done."
2516	fi
2517
2518	#
2519	# At this point we must have a wx flist either from hg-active,
2520	# or in general.  Use it to try and find our parent revision,
2521	# if we don't have one.
2522	#
2523	if [[ -z $HG_PARENT ]]; then
2524		eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2525	fi
2526
2527	#
2528	# If we still don't have a parent, we must have been given a
2529	# wx-style active list with no HG_PARENT specification, run
2530	# hg-active and pull an HG_PARENT out of it, ignore the rest.
2531	#
2532	if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2533		$HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2534		    eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2535	elif [[ -z $HG_PARENT ]]; then
2536		print -u2 "Error: Cannot discover parent revision"
2537		exit 1
2538	fi
2539elif [[ $SCM_MODE == "subversion" ]]; then
2540	if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2541		CWS=$CODEMGR_WS
2542	else
2543		svn info | while read line; do
2544			if [[ $line == "URL: "* ]]; then
2545				url=${line#URL: }
2546			elif [[ $line == "Repository Root: "* ]]; then
2547				repo=${line#Repository Root: }
2548			fi
2549		done
2550
2551		rel=${url#$repo}
2552		CWS=${PWD%$rel}
2553	fi
2554
2555	#
2556	# We only will have a real parent workspace in the case one
2557	# was specified (be it an older webrev, or another checkout).
2558	#
2559	[[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2560
2561	if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2562		flist_from_subversion $CWS $OLDPWD
2563	fi
2564else
2565    if [[ $SCM_MODE == "unknown" ]]; then
2566	print -u2 "    Unknown type of SCM in use"
2567    else
2568	print -u2 "    Unsupported SCM in use: $SCM_MODE"
2569    fi
2570
2571    env_from_flist
2572
2573    if [[ -z $CODEMGR_WS ]]; then
2574	print -u2 "SCM not detected/supported and CODEMGR_WS not specified"
2575	exit 1
2576    fi
2577
2578    if [[ -z $CODEMGR_PARENT ]]; then
2579	print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified"
2580	exit 1
2581    fi
2582
2583    CWS=$CODEMGR_WS
2584    PWS=$CODEMGR_PARENT
2585fi
2586
2587#
2588# If the user didn't specify a -i option, check to see if there is a
2589# webrev-info file in the workspace directory.
2590#
2591if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2592	iflag=1
2593	INCLUDE_FILE="$CWS/webrev-info"
2594fi
2595
2596if [[ -n $iflag ]]; then
2597	if [[ ! -r $INCLUDE_FILE ]]; then
2598		print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2599		    "not readable."
2600		exit 1
2601	else
2602		#
2603		# $INCLUDE_FILE may be a relative path, and the script alters
2604		# PWD, so we just stash a copy in /tmp.
2605		#
2606		cp $INCLUDE_FILE /tmp/$$.include
2607	fi
2608fi
2609
2610#
2611# Output directory.
2612#
2613WDIR=${WDIR:-$CWS/webrev}
2614
2615#
2616# Name of the webrev, derived from the workspace name or output directory;
2617# in the future this could potentially be an option.
2618#
2619if [[ -n $oflag ]]; then
2620	WNAME=${WDIR##*/}
2621else
2622	WNAME=${CWS##*/}
2623fi
2624
2625# Make sure remote target is well formed for remote upload/delete.
2626if [[ -n $Dflag || -n $Uflag ]]; then
2627	#
2628	# If remote target is not specified, build it from scratch using
2629	# the default values.
2630	#
2631	if [[ -z $tflag ]]; then
2632		remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2633	else
2634		#
2635		# Check upload target prefix first.
2636		#
2637		if [[ "${remote_target}" != ${rsync_prefix}* &&
2638		    "${remote_target}" != ${ssh_prefix}* ]]; then
2639			print "ERROR: invalid prefix of upload URI" \
2640			    "($remote_target)"
2641			exit 1
2642		fi
2643		#
2644		# If destination specification is not in the form of
2645		# host_spec:remote_dir then assume it is just remote hostname
2646		# and append a colon and destination directory formed from
2647		# local webrev directory name.
2648		#
2649		typeset target_no_prefix=${remote_target##*://}
2650		if [[ ${target_no_prefix} == *:* ]]; then
2651			if [[ "${remote_target}" == *: ]]; then
2652				remote_target=${remote_target}${WNAME}
2653			fi
2654		else
2655			if [[ ${target_no_prefix} == */* ]]; then
2656				print "ERROR: badly formed upload URI" \
2657					"($remote_target)"
2658				exit 1
2659			else
2660				remote_target=${remote_target}:${WNAME}
2661			fi
2662		fi
2663	fi
2664
2665	#
2666	# Strip trailing slash. Each upload method will deal with directory
2667	# specification separately.
2668	#
2669	remote_target=${remote_target%/}
2670fi
2671
2672#
2673# Option -D by itself (option -U not present) implies no webrev generation.
2674#
2675if [[ -z $Uflag && -n $Dflag ]]; then
2676	delete_webrev 1 1
2677	exit $?
2678fi
2679
2680#
2681# Do not generate the webrev, just upload it or delete it.
2682#
2683if [[ -n $nflag ]]; then
2684	if [[ -n $Dflag ]]; then
2685		delete_webrev 1 1
2686		(( $? == 0 )) || exit $?
2687	fi
2688	if [[ -n $Uflag ]]; then
2689		upload_webrev
2690		exit $?
2691	fi
2692fi
2693
2694if [ "${WDIR%%/*}" ]; then
2695	WDIR=$PWD/$WDIR
2696fi
2697
2698if [[ ! -d $WDIR ]]; then
2699	mkdir -p $WDIR
2700	(( $? != 0 )) && exit 1
2701fi
2702
2703#
2704# Summarize what we're going to do.
2705#
2706if [[ -n $CWS_REV ]]; then
2707	print "      Workspace: $CWS (at $CWS_REV)"
2708else
2709	print "      Workspace: $CWS"
2710fi
2711if [[ -n $parent_webrev ]]; then
2712	print "Compare against: webrev at $parent_webrev"
2713else
2714	if [[ -n $HG_PARENT ]]; then
2715		hg_parent_short=`echo $HG_PARENT \
2716			| $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'`
2717		print "Compare against: $PWS (at $hg_parent_short)"
2718	else
2719		print "Compare against: $PWS"
2720	fi
2721fi
2722
2723[[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
2724print "      Output to: $WDIR"
2725
2726#
2727# Save the file list in the webrev dir
2728#
2729[[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
2730
2731#
2732#    Bug IDs will be replaced by a URL.  Order of precedence
2733#    is: default location, $WEBREV_BUGURL, the -O flag.
2734#
2735BUGURL='http://monaco.sfbay.sun.com/detail.jsp?cr='
2736[[ -n $WEBREV_BUGURL ]] && BUGURL="$WEBREV_BUGURL"
2737[[ -n "$Oflag" ]] && \
2738    BUGURL='http://bugs.opensolaris.org/bugdatabase/view_bug.do?bug_id='
2739
2740#
2741#    Likewise, ARC cases will be replaced by a URL.  Order of precedence
2742#    is: default, $WEBREV_SACURL, the -O flag.
2743#
2744#    Note that -O also triggers different substitution behavior for
2745#    SACURL.  See sac2url().
2746#
2747SACURL='http://sac.eng.sun.com'
2748[[ -n $WEBREV_SACURL ]] && SACURL="$WEBREV_SACURL"
2749[[ -n "$Oflag" ]] && \
2750    SACURL='http://www.opensolaris.org/os/community/arc/caselog'
2751
2752rm -f $WDIR/$WNAME.patch
2753rm -f $WDIR/$WNAME.ps
2754rm -f $WDIR/$WNAME.pdf
2755
2756touch $WDIR/$WNAME.patch
2757
2758print "   Output Files:"
2759
2760#
2761# Clean up the file list: Remove comments, blank lines and env variables.
2762#
2763$SED -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
2764FLIST=/tmp/$$.flist.clean
2765
2766#
2767# For Mercurial, create a cache of manifest entries.
2768#
2769if [[ $SCM_MODE == "mercurial" ]]; then
2770	#
2771	# Transform the FLIST into a temporary sed script that matches
2772	# relevant entries in the Mercurial manifest as follows:
2773	# 1) The script will be used against the parent revision manifest,
2774	#    so for FLIST lines that have two filenames (a renamed file)
2775	#    keep only the old name.
2776	# 2) Escape all forward slashes the filename.
2777	# 3) Change the filename into another sed command that matches
2778	#    that file in "hg manifest -v" output:  start of line, three
2779	#    octal digits for file permissions, space, a file type flag
2780	#    character, space, the filename, end of line.
2781	#
2782	SEDFILE=/tmp/$$.manifest.sed
2783	$SED '
2784		s#^[^ ]* ##
2785		s#/#\\\/#g
2786		s#^.*$#/^... . &$/p#
2787	' < $FLIST > $SEDFILE
2788
2789	#
2790	# Apply the generated script to the output of "hg manifest -v"
2791	# to get the relevant subset for this webrev.
2792	#
2793	HG_PARENT_MANIFEST=/tmp/$$.manifest
2794	hg -R $CWS manifest -v -r $HG_PARENT |
2795	    $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
2796fi
2797
2798#
2799# First pass through the files: generate the per-file webrev HTML-files.
2800#
2801cat $FLIST | while read LINE
2802do
2803	set - $LINE
2804	P=$1
2805
2806	#
2807	# Normally, each line in the file list is just a pathname of a
2808	# file that has been modified or created in the child.  A file
2809	# that is renamed in the child workspace has two names on the
2810	# line: new name followed by the old name.
2811	#
2812	oldname=""
2813	oldpath=""
2814	rename=
2815	if [[ $# -eq 2 ]]; then
2816		PP=$2			# old filename
2817		oldname=" (was $PP)"
2818		oldpath="$PP"
2819		rename=1
2820        	PDIR=${PP%/*}
2821        	if [[ $PDIR == $PP ]]; then
2822			PDIR="."   # File at root of workspace
2823		fi
2824
2825		PF=${PP##*/}
2826
2827	        DIR=${P%/*}
2828	        if [[ $DIR == $P ]]; then
2829			DIR="."   # File at root of workspace
2830		fi
2831
2832		F=${P##*/}
2833
2834        else
2835	        DIR=${P%/*}
2836	        if [[ "$DIR" == "$P" ]]; then
2837			DIR="."   # File at root of workspace
2838		fi
2839
2840		F=${P##*/}
2841
2842		PP=$P
2843		PDIR=$DIR
2844		PF=$F
2845	fi
2846
2847	COMM=`getcomments html $P $PP`
2848
2849	print "\t$P$oldname\n\t\t\c"
2850
2851	# Make the webrev mirror directory if necessary
2852	mkdir -p $WDIR/$DIR
2853
2854	#
2855	# If we're in OpenSolaris mode, we enforce a minor policy:
2856	# help to make sure the reviewer doesn't accidentally publish
2857	# source which is in usr/closed/* or deleted_files/usr/closed/*
2858	#
2859	if [[ -n "$Oflag" ]]; then
2860		pclosed=${P##usr/closed/}
2861		pdeleted=${P##deleted_files/usr/closed/}
2862		if [[ "$pclosed" != "$P" || "$pdeleted" != "$P" ]]; then
2863			print "*** Omitting closed source for OpenSolaris" \
2864			    "mode review"
2865			continue
2866		fi
2867	fi
2868
2869	#
2870	# We stash old and new files into parallel directories in $WDIR
2871	# and do our diffs there.  This makes it possible to generate
2872	# clean looking diffs which don't have absolute paths present.
2873	#
2874
2875	build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
2876	    continue
2877
2878	#
2879	# Keep the old PWD around, so we can safely switch back after
2880	# diff generation, such that build_old_new runs in a
2881	# consistent environment.
2882	#
2883	OWD=$PWD
2884	cd $WDIR/raw_files
2885	ofile=old/$PDIR/$PF
2886	nfile=new/$DIR/$F
2887
2888	mv_but_nodiff=
2889	cmp $ofile $nfile > /dev/null 2>&1
2890	if [[ $? == 0 && $rename == 1 ]]; then
2891		mv_but_nodiff=1
2892	fi
2893
2894	#
2895	# If we have old and new versions of the file then run the appropriate
2896	# diffs.  This is complicated by a couple of factors:
2897	#
2898	#	- renames must be handled specially: we emit a 'remove'
2899	#	  diff and an 'add' diff
2900	#	- new files and deleted files must be handled specially
2901	#	- Solaris patch(1m) can't cope with file creation
2902	#	  (and hence renames) as of this writing.
2903	#       - To make matters worse, gnu patch doesn't interpret the
2904	#	  output of Solaris diff properly when it comes to
2905	#	  adds and deletes.  We need to do some "cleansing"
2906	#         transformations:
2907	# 	    [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
2908	#	    [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
2909	#
2910	cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
2911	cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
2912
2913	rm -f $WDIR/$DIR/$F.patch
2914	if [[ -z $rename ]]; then
2915		if [ ! -f "$ofile" ]; then
2916			diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
2917			    > $WDIR/$DIR/$F.patch
2918		elif [ ! -f "$nfile" ]; then
2919			diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
2920			    > $WDIR/$DIR/$F.patch
2921		else
2922			diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
2923		fi
2924	else
2925		diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
2926		    > $WDIR/$DIR/$F.patch
2927
2928		diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
2929		    >> $WDIR/$DIR/$F.patch
2930
2931	fi
2932
2933	#
2934	# Tack the patch we just made onto the accumulated patch for the
2935	# whole wad.
2936	#
2937	cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
2938
2939	print " patch\c"
2940
2941	if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
2942
2943		${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
2944		diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
2945		    > $WDIR/$DIR/$F.cdiff.html
2946		print " cdiffs\c"
2947
2948		${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
2949		diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
2950		    > $WDIR/$DIR/$F.udiff.html
2951
2952		print " udiffs\c"
2953
2954		if [[ -x $WDIFF ]]; then
2955			$WDIFF -c "$COMM" \
2956			    -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
2957			    $WDIR/$DIR/$F.wdiff.html 2>/dev/null
2958			if [[ $? -eq 0 ]]; then
2959				print " wdiffs\c"
2960			else
2961				print " wdiffs[fail]\c"
2962			fi
2963		fi
2964
2965		sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
2966		    > $WDIR/$DIR/$F.sdiff.html
2967		print " sdiffs\c"
2968
2969		print " frames\c"
2970
2971		rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
2972
2973		difflines $ofile $nfile > $WDIR/$DIR/$F.count
2974
2975	elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
2976		# renamed file: may also have differences
2977		difflines $ofile $nfile > $WDIR/$DIR/$F.count
2978	elif [[ -f $nfile ]]; then
2979		# new file: count added lines
2980		difflines /dev/null $nfile > $WDIR/$DIR/$F.count
2981	elif [[ -f $ofile ]]; then
2982		# old file: count deleted lines
2983		difflines $ofile /dev/null > $WDIR/$DIR/$F.count
2984	fi
2985
2986	#
2987	# Now we generate the postscript for this file.  We generate diffs
2988	# only in the event that there is delta, or the file is new (it seems
2989	# tree-killing to print out the contents of deleted files).
2990	#
2991	if [[ -f $nfile ]]; then
2992		ocr=$ofile
2993		[[ ! -f $ofile ]] && ocr=/dev/null
2994
2995		if [[ -z $mv_but_nodiff ]]; then
2996			textcomm=`getcomments text $P $PP`
2997			if [[ -x $CODEREVIEW ]]; then
2998				$CODEREVIEW -y "$textcomm" \
2999				    -e $ocr $nfile \
3000				    > /tmp/$$.psfile 2>/dev/null &&
3001				    cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3002				if [[ $? -eq 0 ]]; then
3003					print " ps\c"
3004				else
3005					print " ps[fail]\c"
3006				fi
3007			fi
3008		fi
3009	fi
3010
3011	if [[ -f $ofile ]]; then
3012		source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3013		print " old\c"
3014	fi
3015
3016	if [[ -f $nfile ]]; then
3017		source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3018		print " new\c"
3019	fi
3020
3021	cd $OWD
3022
3023	print
3024done
3025
3026frame_nav_js > $WDIR/ancnav.js
3027frame_navigation > $WDIR/ancnav.html
3028
3029if [[ ! -f $WDIR/$WNAME.ps ]]; then
3030	print " Generating PDF: Skipped: no output available"
3031elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3032	print " Generating PDF: \c"
3033	fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3034	print "Done."
3035else
3036	print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3037fi
3038
3039# If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3040# delete it - prevent accidental publishing of closed source
3041
3042if [[ -n "$Oflag" ]]; then
3043	$FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3044fi
3045
3046# Now build the index.html file that contains
3047# links to the source files and their diffs.
3048
3049cd $CWS
3050
3051# Save total changed lines for Code Inspection.
3052print "$TOTL" > $WDIR/TotalChangedLines
3053
3054print "     index.html: \c"
3055INDEXFILE=$WDIR/index.html
3056exec 3<&1			# duplicate stdout to FD3.
3057exec 1<&-			# Close stdout.
3058exec > $INDEXFILE		# Open stdout to index file.
3059
3060print "$HTML<head>$STDHEAD"
3061print "<title>$WNAME</title>"
3062print "</head>"
3063print "<body id=\"SUNWwebrev\">"
3064print "<div class=\"summary\">"
3065print "<h2>Code Review for $WNAME</h2>"
3066
3067print "<table>"
3068
3069#
3070# Get the preparer's name:
3071#
3072# If the SCM detected is Mercurial, and the configuration property
3073# ui.username is available, use that, but be careful to properly escape
3074# angle brackets (HTML syntax characters) in the email address.
3075#
3076# Otherwise, use the current userid in the form "John Doe (jdoe)", but
3077# to maintain compatibility with passwd(4), we must support '&' substitutions.
3078#
3079preparer=
3080if [[ "$SCM_MODE" == mercurial ]]; then
3081	preparer=`hg showconfig ui.username 2>/dev/null`
3082	if [[ -n "$preparer" ]]; then
3083		preparer="$(echo "$preparer" | html_quote)"
3084	fi
3085fi
3086if [[ -z "$preparer" ]]; then
3087	preparer=$(
3088	    $PERL -e '
3089	        ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3090	        if ($login) {
3091	            $gcos =~ s/\&/ucfirst($login)/e;
3092	            printf "%s (%s)\n", $gcos, $login;
3093	        } else {
3094	            printf "(unknown)\n";
3095	        }
3096	')
3097fi
3098
3099print "<tr><th>Prepared by:</th><td>$preparer on `date`</td></tr>"
3100print "<tr><th>Workspace:</th><td>$CWS"
3101if [[ -n $CWS_REV ]]; then
3102	print "(at $CWS_REV)"
3103fi
3104print "</td></tr>"
3105print "<tr><th>Compare against:</th><td>"
3106if [[ -n $parent_webrev ]]; then
3107	print "webrev at $parent_webrev"
3108else
3109	print "$PWS"
3110	if [[ -n $hg_parent_short ]]; then
3111		print "(at $hg_parent_short)"
3112	fi
3113fi
3114print "</td></tr>"
3115print "<tr><th>Summary of changes:</th><td>"
3116printCI $TOTL $TINS $TDEL $TMOD $TUNC
3117print "</td></tr>"
3118
3119if [[ -f $WDIR/$WNAME.patch ]]; then
3120	wpatch_url="$(print $WNAME.patch | url_encode)"
3121	print "<tr><th>Patch of changes:</th><td>"
3122	print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3123fi
3124if [[ -f $WDIR/$WNAME.pdf ]]; then
3125	wpdf_url="$(print $WNAME.pdf | url_encode)"
3126	print "<tr><th>Printable review:</th><td>"
3127	print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3128fi
3129
3130if [[ -n "$iflag" ]]; then
3131	print "<tr><th>Author comments:</th><td><div>"
3132	cat /tmp/$$.include
3133	print "</div></td></tr>"
3134fi
3135print "</table>"
3136print "</div>"
3137
3138#
3139# Second pass through the files: generate the rest of the index file
3140#
3141cat $FLIST | while read LINE
3142do
3143	set - $LINE
3144	P=$1
3145
3146	if [[ $# == 2 ]]; then
3147		PP=$2
3148		oldname="$PP"
3149	else
3150		PP=$P
3151		oldname=""
3152	fi
3153
3154	mv_but_nodiff=
3155	cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3156	if [[ $? == 0 && -n "$oldname" ]]; then
3157		mv_but_nodiff=1
3158	fi
3159
3160	DIR=${P%/*}
3161	if [[ $DIR == $P ]]; then
3162		DIR="."   # File at root of workspace
3163	fi
3164
3165	# Avoid processing the same file twice.
3166	# It's possible for renamed files to
3167	# appear twice in the file list
3168
3169	F=$WDIR/$P
3170
3171	print "<p>"
3172
3173	# If there's a diffs file, make diffs links
3174
3175	if [[ -f $F.cdiff.html ]]; then
3176		cdiff_url="$(print $P.cdiff.html | url_encode)"
3177		udiff_url="$(print $P.udiff.html | url_encode)"
3178		print "<a href=\"$cdiff_url\">Cdiffs</a>"
3179		print "<a href=\"$udiff_url\">Udiffs</a>"
3180
3181		if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3182			wdiff_url="$(print $P.wdiff.html | url_encode)"
3183			print "<a href=\"$wdiff_url\">Wdiffs</a>"
3184		fi
3185
3186		sdiff_url="$(print $P.sdiff.html | url_encode)"
3187		print "<a href=\"$sdiff_url\">Sdiffs</a>"
3188
3189		frames_url="$(print $P.frames.html | url_encode)"
3190		print "<a href=\"$frames_url\">Frames</a>"
3191	else
3192		print " ------ ------ ------"
3193
3194		if [[ -x $WDIFF ]]; then
3195			print " ------"
3196		fi
3197
3198		print " ------"
3199	fi
3200
3201	# If there's an old file, make the link
3202
3203	if [[ -f $F-.html ]]; then
3204		oldfile_url="$(print $P-.html | url_encode)"
3205		print "<a href=\"$oldfile_url\">Old</a>"
3206	else
3207		print " ---"
3208	fi
3209
3210	# If there's an new file, make the link
3211
3212	if [[ -f $F.html ]]; then
3213		newfile_url="$(print $P.html | url_encode)"
3214		print "<a href=\"$newfile_url\">New</a>"
3215	else
3216		print " ---"
3217	fi
3218
3219	if [[ -f $F.patch ]]; then
3220		patch_url="$(print $P.patch | url_encode)"
3221		print "<a href=\"$patch_url\">Patch</a>"
3222	else
3223		print " -----"
3224	fi
3225
3226	if [[ -f $WDIR/raw_files/new/$P ]]; then
3227		rawfiles_url="$(print raw_files/new/$P | url_encode)"
3228		print "<a href=\"$rawfiles_url\">Raw</a>"
3229	else
3230		print " ---"
3231	fi
3232
3233	print "<b>$P</b>"
3234
3235	# For renamed files, clearly state whether or not they are modified
3236	if [[ -n "$oldname" ]]; then
3237		if [[ -n "$mv_but_nodiff" ]]; then
3238			print "<i>(renamed only, was $oldname)</i>"
3239		else
3240			print "<i>(modified and renamed, was $oldname)</i>"
3241		fi
3242	fi
3243
3244	# If there's an old file, but no new file, the file was deleted
3245	if [[ -f $F-.html && ! -f $F.html ]]; then
3246		print " <i>(deleted)</i>"
3247	fi
3248
3249	#
3250	# Check for usr/closed and deleted_files/usr/closed
3251	#
3252	if [ ! -z "$Oflag" ]; then
3253		if [[ $P == usr/closed/* || \
3254		    $P == deleted_files/usr/closed/* ]]; then
3255			print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3256			    "this review</i>"
3257		fi
3258	fi
3259
3260	print "</p>"
3261	# Insert delta comments
3262
3263	print "<blockquote><pre>"
3264	getcomments html $P $PP
3265	print "</pre>"
3266
3267	# Add additional comments comment
3268
3269	print "<!-- Add comments to explain changes in $P here -->"
3270
3271	# Add count of changes.
3272
3273	if [[ -f $F.count ]]; then
3274	    cat $F.count
3275	    rm $F.count
3276	fi
3277
3278	if [[ $SCM_MODE == "teamware" ||
3279	    $SCM_MODE == "mercurial" ||
3280	    $SCM_MODE == "unknown" ]]; then
3281
3282		# Include warnings for important file mode situations:
3283		# 1) New executable files
3284		# 2) Permission changes of any kind
3285		# 3) Existing executable files
3286
3287		old_mode=
3288		if [[ -f $WDIR/raw_files/old/$PP ]]; then
3289			old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3290		fi
3291
3292		new_mode=
3293		if [[ -f $WDIR/raw_files/new/$P ]]; then
3294			new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3295		fi
3296
3297		if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3298			print "<span class=\"chmod\">"
3299			print "<p>new executable file: mode $new_mode</p>"
3300			print "</span>"
3301		elif [[ -n "$old_mode" && -n "$new_mode" &&
3302		    "$old_mode" != "$new_mode" ]]; then
3303			print "<span class=\"chmod\">"
3304			print "<p>mode change: $old_mode to $new_mode</p>"
3305			print "</span>"
3306		elif [[ "$new_mode" = *[1357]* ]]; then
3307			print "<span class=\"chmod\">"
3308			print "<p>executable file: mode $new_mode</p>"
3309			print "</span>"
3310		fi
3311	fi
3312
3313	print "</blockquote>"
3314done
3315
3316print
3317print
3318print "<hr></hr>"
3319print "<p style=\"font-size: small\">"
3320print "This code review page was prepared using <b>$0</b>."
3321print "Webrev is maintained by the <a href=\"http://www.opensolaris.org\">"
3322print "OpenSolaris</a> project.  The latest version may be obtained"
3323print "<a href=\"http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3324print "</body>"
3325print "</html>"
3326
3327exec 1<&-			# Close FD 1.
3328exec 1<&3			# dup FD 3 to restore stdout.
3329exec 3<&-			# close FD 3.
3330
3331print "Done."
3332
3333#
3334# If remote deletion was specified and fails do not continue.
3335#
3336if [[ -n $Dflag ]]; then
3337	delete_webrev 1 1
3338	(( $? == 0 )) || exit $?
3339fi
3340
3341if [[ -n $Uflag ]]; then
3342	upload_webrev
3343	exit $?
3344fi
3345