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