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