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