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