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