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