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