OpenGrok

Cross Reference: webrev.sh
xref: /onnv/onnv-gate/usr/src/tools/scripts/webrev.sh
Home | History | Annotate | Line # | 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 (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
     25 #
     26 
     27 #
     28 # This script takes a file list and a workspace and builds a set of html files
     29 # suitable for doing a code review of source changes via a web page.
     30 # Documentation is available via the manual page, webrev.1, or just
     31 # type 'webrev -h'.
     32 #
     33 # Acknowledgements to contributors to webrev are listed in the webrev(1)
     34 # man page.
     35 #
     36 
     37 REMOVED_COLOR=brown
     38 CHANGED_COLOR=blue
     39 NEW_COLOR=blue
     40 
     41 HTML='<?xml version="1.0"?>
     42 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     43     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     44 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
     45 
     46 FRAMEHTML='<?xml version="1.0"?>
     47 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
     48     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
     49 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
     50 
     51 STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta>
     52 <meta http-equiv="Pragma" content="no-cache"></meta>
     53 <meta http-equiv="Expires" content="-1"></meta>
     54 <!--
     55    Note to customizers: the body of the webrev is IDed as SUNWwebrev
     56    to allow easy overriding by users of webrev via the userContent.css
     57    mechanism available in some browsers.
     58 
     59    For example, to have all "removed" information be red instead of
     60    brown, set a rule in your userContent.css file like:
     61 
     62        body#SUNWwebrev span.removed { color: red ! important; }
     63 -->
     64 <style type="text/css" media="screen">
     65 body {
     66     background-color: #eeeeee;
     67 }
     68 hr {
     69     border: none 0;
     70     border-top: 1px solid #aaa;
     71     height: 1px;
     72 }
     73 div.summary {
     74     font-size: .8em;
     75     border-bottom: 1px solid #aaa;
     76     padding-left: 1em;
     77     padding-right: 1em;
     78 }
     79 div.summary h2 {
     80     margin-bottom: 0.3em;
     81 }
     82 div.summary table th {
     83     text-align: right;
     84     vertical-align: top;
     85     white-space: nowrap;
     86 }
     87 span.lineschanged {
     88     font-size: 0.7em;
     89 }
     90 span.oldmarker {
     91     color: red;
     92     font-size: large;
     93     font-weight: bold;
     94 }
     95 span.newmarker {
     96     color: green;
     97     font-size: large;
     98     font-weight: bold;
     99 }
    100 span.removed {
    101     color: brown;
    102 }
    103 span.changed {
    104     color: blue;
    105 }
    106 span.new {
    107     color: blue;
    108     font-weight: bold;
    109 }
    110 span.chmod {
    111     font-size: 0.7em;
    112     color: #db7800;
    113 }
    114 a.print { font-size: x-small; }
    115 a:hover { background-color: #ffcc99; }
    116 </style>
    117 
    118 <style type="text/css" media="print">
    119 pre { font-size: 0.8em; font-family: courier, monospace; }
    120 span.removed { color: #444; font-style: italic }
    121 span.changed { font-weight: bold; }
    122 span.new { font-weight: bold; }
    123 span.newmarker { font-size: 1.2em; font-weight: bold; }
    124 span.oldmarker { font-size: 1.2em; font-weight: bold; }
    125 a.print {display: none}
    126 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
    127 </style>
    128 '
    129 
    130 #
    131 # UDiffs need a slightly different CSS rule for 'new' items (we don't
    132 # want them to be bolded as we do in cdiffs or sdiffs).
    133 #
    134 UDIFFCSS='
    135 <style type="text/css" media="screen">
    136 span.new {
    137     color: blue;
    138     font-weight: normal;
    139 }
    140 </style>
    141 '
    142 
    143 #
    144 # Display remote target with prefix and trailing slash.
    145 #
    146 function print_upload_header
    147 {
    148 	typeset -r prefix=$1
    149 	typeset display_target
    150 
    151 	if [[ -z $tflag ]]; then
    152 		display_target=${prefix}${remote_target}
    153 	else
    154 		display_target=${remote_target}
    155 	fi
    156 
    157 	if [[ ${display_target} != */ ]]; then
    158 		display_target=${display_target}/
    159 	fi
    160 
    161 	print "      Upload to: ${display_target}\n" \
    162 	    "     Uploading: \c"
    163 }
    164 
    165 #
    166 # Upload the webrev via rsync. Return 0 on success, 1 on error.
    167 #
    168 function rsync_upload
    169 {
    170 	if (( $# != 2 )); then
    171 		print "\nERROR: rsync_upload: wrong usage ($#)"
    172 		exit 1
    173 	fi
    174 
    175 	typeset -r dst=$1
    176 	integer -r print_err_msg=$2
    177 
    178 	print_upload_header ${rsync_prefix}
    179 	print "rsync ... \c"
    180 	typeset -r err_msg=$( $MKTEMP /tmp/rsync_err.XXXXXX )
    181 	if [[ -z $err_msg ]]; then
    182 		print "\nERROR: rsync_upload: cannot create temporary file"
    183 		return 1
    184 	fi
    185 	#
    186 	# The source directory must end with a slash in order to copy just
    187 	# directory contents, not the whole directory.
    188 	#
    189 	typeset src_dir=$WDIR
    190 	if [[ ${src_dir} != */ ]]; then
    191 		src_dir=${src_dir}/
    192 	fi
    193 	$RSYNC -r -q ${src_dir} $dst 2>$err_msg
    194 	if (( $? != 0 )); then
    195 		if (( ${print_err_msg} > 0 )); then
    196 			print "Failed.\nERROR: rsync failed"
    197 			print "src dir: '${src_dir}'\ndst dir: '$dst'"
    198 			print "error messages:"
    199 			$SED 's/^/> /' $err_msg
    200 			rm -f $err_msg
    201 		fi
    202 		return 1
    203 	fi
    204 
    205 	rm -f $err_msg
    206 	print "Done."
    207 	return 0
    208 }
    209 
    210 #
    211 # Create directories on remote host using SFTP. Return 0 on success,
    212 # 1 on failure.
    213 #
    214 function remote_mkdirs
    215 {
    216 	typeset -r dir_spec=$1
    217 	typeset -r host_spec=$2
    218 
    219 	#
    220 	# If the supplied path is absolute we assume all directories are
    221 	# created, otherwise try to create all directories in the path
    222 	# except the last one which will be created by scp.
    223 	#
    224 	if [[ "${dir_spec}" == */* && "${dir_spec}" != /* ]]; then
    225 		print "mkdirs \c"
    226 		#
    227 		# Remove the last directory from directory specification.
    228 		#
    229 		typeset -r dirs_mk=${dir_spec%/*}
    230 		typeset -r batch_file_mkdir=$( $MKTEMP \
    231 		    /tmp/webrev_mkdir.XXXXXX )
    232 		if [[ -z $batch_file_mkdir ]]; then
    233 			print "\nERROR: remote_mkdirs:" \
    234 			    "cannot create temporary file for batch file"
    235 			return 1
    236 		fi
    237                 OLDIFS=$IFS
    238                 IFS=/
    239 		typeset dir
    240                 for dir in ${dirs_mk}; do
    241 			#
    242 			# Use the '-' prefix to ignore mkdir errors in order
    243 			# to avoid an error in case the directory already
    244 			# exists. We check the directory with chdir to be sure
    245 			# there is one.
    246 			#
    247                         print -- "-mkdir ${dir}" >> ${batch_file_mkdir}
    248                         print "chdir ${dir}" >> ${batch_file_mkdir}
    249                 done
    250                 IFS=$OLDIFS
    251 		typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
    252 		if [[ -z ${sftp_err_msg} ]]; then
    253 			print "\nERROR: remote_mkdirs:" \
    254 			    "cannot create temporary file for error messages"
    255 			return 1
    256 		fi
    257 		$SFTP -b ${batch_file_mkdir} ${host_spec} 2>${sftp_err_msg} 1>&2
    258 		if (( $? != 0 )); then
    259 			print "\nERROR: failed to create remote directories"
    260 			print "error messages:"
    261 			$SED 's/^/> /' ${sftp_err_msg}
    262 			rm -f ${sftp_err_msg} ${batch_file_mkdir}
    263 			return 1
    264 		fi
    265 		rm -f ${sftp_err_msg} ${batch_file_mkdir}
    266 	fi
    267 
    268 	return 0
    269 }
    270 
    271 #
    272 # Upload the webrev via SSH. Return 0 on success, 1 on error.
    273 #
    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} ${host_spec}
    308 	if (( $? != 0 )); then
    309 		return 1
    310 	fi
    311 
    312 	print "upload ... \c"
    313 	typeset -r scp_err_msg=$( $MKTEMP /tmp/scp_err.XXXXXX )
    314 	if [[ -z ${scp_err_msg} ]]; then
    315 		print "\nERROR: ssh_upload:" \
    316 		    "cannot create temporary file for error messages"
    317 		return 1
    318 	fi
    319 	$SCP -q -C -B -o PreferredAuthentications=publickey -r \
    320 		$WDIR $dst 2>${scp_err_msg}
    321 	if (( $? != 0 )); then
    322 		print "Failed.\nERROR: scp failed"
    323 		print "src dir: '$WDIR'\ndst dir: '$dst'"
    324 		print "error messages:"
    325 		$SED 's/^/> /' ${scp_err_msg}
    326 		rm -f ${scp_err_msg}
    327 		return 1
    328 	fi
    329 
    330 	rm -f ${scp_err_msg}
    331 	print "Done."
    332 	return 0
    333 }
    334 
    335 #
    336 # Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp
    337 # on failure. If first argument is 1 then perform the check of sftp return
    338 # value otherwise ignore it. If second argument is present it means this run
    339 # only performs deletion.
    340 #
    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 	if [[ ! -x $HG_ACTIVE ]]; then
   1770 		print		# Blank line for the \c above
   1771 		print -u2 "Error: hg-active tool not found.  Exiting"
   1772 		exit 1
   1773 	fi
   1774 	hg_active_wxfile $child $parent
   1775 
   1776 	# flist_from_wx prints the Done, so we don't have to.
   1777 	flist_from_wx $TMPFLIST
   1778 }
   1779 
   1780 #
   1781 # flist_from_subversion
   1782 #
   1783 # Generate the file list by extracting file names from svn status.
   1784 #
   1785 function flist_from_subversion
   1786 {
   1787 	CWS=$1
   1788 	OLDPWD=$2
   1789 
   1790 	cd $CWS
   1791 	print -u2 " File list from: svn status ... \c"
   1792 	svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
   1793 	print -u2 " Done."
   1794 	cd $OLDPWD
   1795 }
   1796 
   1797 function env_from_flist
   1798 {
   1799 	[[ -r $FLIST ]] || return
   1800 
   1801 	#
   1802 	# Use "eval" to set env variables that are listed in the file
   1803 	# list.  Then copy those into our local versions of those
   1804 	# variables if they have not been set already.
   1805 	#
   1806 	eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
   1807 
   1808 	if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
   1809 		codemgr_ws=$CODEMGR_WS
   1810 		export CODEMGR_WS
   1811 	fi
   1812 
   1813 	#
   1814 	# Check to see if CODEMGR_PARENT is set in the flist file.
   1815 	#
   1816 	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
   1817 		codemgr_parent=$CODEMGR_PARENT
   1818 		export CODEMGR_PARENT
   1819 	fi
   1820 }
   1821 
   1822 function look_for_prog
   1823 {
   1824 	typeset path
   1825 	typeset ppath
   1826 	typeset progname=$1
   1827 
   1828 	ppath=$PATH
   1829 	ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
   1830 	ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin
   1831 	ppath=$ppath:/opt/onbld/bin/`uname -p`
   1832 
   1833 	PATH=$ppath prog=`whence $progname`
   1834 	if [[ -n $prog ]]; then
   1835 		print $prog
   1836 	fi
   1837 }
   1838 
   1839 function get_file_mode
   1840 {
   1841 	$PERL -e '
   1842 		if (@stat = stat($ARGV[0])) {
   1843 			$mode = $stat[2] & 0777;
   1844 			printf "%03o\n", $mode;
   1845 			exit 0;
   1846 		} else {
   1847 			exit 1;
   1848 		}
   1849 	    ' $1
   1850 }
   1851 
   1852 function build_old_new_teamware
   1853 {
   1854 	typeset olddir="$1"
   1855 	typeset newdir="$2"
   1856 
   1857 	# If the child's version doesn't exist then
   1858 	# get a readonly copy.
   1859 
   1860 	if [[ ! -f $CWS/$DIR/$F && -f $CWS/$DIR/SCCS/s.$F ]]; then
   1861 		$SCCS get -s -p $CWS/$DIR/$F > $CWS/$DIR/$F
   1862 	fi
   1863 
   1864 	# The following two sections propagate file permissions the
   1865 	# same way SCCS does.  If the file is already under version
   1866 	# control, always use permissions from the SCCS/s.file.  If
   1867 	# the file is not under SCCS control, use permissions from the
   1868 	# working copy.  In all cases, the file copied to the webrev
   1869 	# is set to read only, and group/other permissions are set to
   1870 	# match those of the file owner.  This way, even if the file
   1871 	# is currently checked out, the webrev will display the final
   1872 	# permissions that would result after check in.
   1873 
   1874 	#
   1875 	# Snag new version of file.
   1876 	#
   1877 	rm -f $newdir/$DIR/$F
   1878 	cp $CWS/$DIR/$F $newdir/$DIR/$F
   1879 	if [[ -f $CWS/$DIR/SCCS/s.$F ]]; then
   1880 		chmod `get_file_mode $CWS/$DIR/SCCS/s.$F` \
   1881 		    $newdir/$DIR/$F
   1882 	fi
   1883 	chmod u-w,go=u $newdir/$DIR/$F
   1884 
   1885 	#
   1886 	# Get the parent's version of the file. First see whether the
   1887 	# child's version is checked out and get the parent's version
   1888 	# with keywords expanded or unexpanded as appropriate.
   1889 	#
   1890 	if [[ -f $PWS/$PDIR/$PF && ! -f $PWS/$PDIR/SCCS/s.$PF && \
   1891 	    ! -f $PWS/$PDIR/SCCS/p.$PF ]]; then
   1892 		# Parent is not a real workspace, but just a raw
   1893 		# directory tree - use the file that's there as
   1894 		# the old file.
   1895 
   1896 		rm -f $olddir/$PDIR/$PF
   1897 		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
   1898 	else
   1899 		if [[ -f $PWS/$PDIR/SCCS/s.$PF ]]; then
   1900 			real_parent=$PWS
   1901 		else
   1902 			real_parent=$RWS
   1903 		fi
   1904 
   1905 		rm -f $olddir/$PDIR/$PF
   1906 
   1907 		if [[ -f $real_parent/$PDIR/$PF ]]; then
   1908 			if [ -f $CWS/$DIR/SCCS/p.$F ]; then
   1909 				$SCCS get -s -p -k $real_parent/$PDIR/$PF > \
   1910 				    $olddir/$PDIR/$PF
   1911 			else
   1912 				$SCCS get -s -p    $real_parent/$PDIR/$PF > \
   1913 				    $olddir/$PDIR/$PF
   1914 			fi
   1915 			chmod `get_file_mode $real_parent/$PDIR/SCCS/s.$PF` \
   1916 			    $olddir/$PDIR/$PF
   1917 		fi
   1918 	fi
   1919 	if [[ -f $olddir/$PDIR/$PF ]]; then
   1920 		chmod u-w,go=u $olddir/$PDIR/$PF
   1921 	fi
   1922 }
   1923 
   1924 function build_old_new_mercurial
   1925 {
   1926 	typeset olddir="$1"
   1927 	typeset newdir="$2"
   1928 	typeset old_mode=
   1929 	typeset new_mode=
   1930 	typeset file
   1931 
   1932 	#
   1933 	# Get old file mode, from the parent revision manifest entry.
   1934 	# Mercurial only stores a "file is executable" flag, but the
   1935 	# manifest will display an octal mode "644" or "755".
   1936 	#
   1937 	if [[ "$PDIR" == "." ]]; then
   1938 		file="$PF"
   1939 	else
   1940 		file="$PDIR/$PF"
   1941 	fi
   1942 	file=`echo $file | $SED 's#/#\\\/#g'`
   1943 	# match the exact filename, and return only the permission digits
   1944 	old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
   1945 	    < $HG_PARENT_MANIFEST`
   1946 
   1947 	#
   1948 	# Get new file mode, directly from the filesystem.
   1949 	# Normalize the mode to match Mercurial's behavior.
   1950 	#
   1951 	new_mode=`get_file_mode $CWS/$DIR/$F`
   1952 	if [[ -n "$new_mode" ]]; then
   1953 		if [[ "$new_mode" = *[1357]* ]]; then
   1954 			new_mode=755
   1955 		else
   1956 			new_mode=644
   1957 		fi
   1958 	fi
   1959 
   1960 	#
   1961 	# new version of the file.
   1962 	#
   1963 	rm -rf $newdir/$DIR/$F
   1964 	if [[ -e $CWS/$DIR/$F ]]; then
   1965 		cp $CWS/$DIR/$F $newdir/$DIR/$F
   1966 		if [[ -n $new_mode ]]; then
   1967 			chmod $new_mode $newdir/$DIR/$F
   1968 		else
   1969 			# should never happen
   1970 			print -u2 "ERROR: set mode of $newdir/$DIR/$F"
   1971 		fi
   1972 	fi
   1973 
   1974 	#
   1975 	# parent's version of the file
   1976 	#
   1977 	# Note that we get this from the last version common to both
   1978 	# ourselves and the parent.  References are via $CWS since we have no
   1979 	# guarantee that the parent workspace is reachable via the filesystem.
   1980 	#
   1981 	if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
   1982 		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
   1983 	elif [[ -n $HG_PARENT ]]; then
   1984 		hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
   1985 		    $olddir/$PDIR/$PF 2>/dev/null
   1986 
   1987 		if (( $? != 0 )); then
   1988 			rm -f $olddir/$PDIR/$PF
   1989 		else
   1990 			if [[ -n $old_mode ]]; then
   1991 				chmod $old_mode $olddir/$PDIR/$PF
   1992 			else
   1993 				# should never happen
   1994 				print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
   1995 			fi
   1996 		fi
   1997 	fi
   1998 }
   1999 
   2000 function build_old_new_subversion
   2001 {
   2002 	typeset olddir="$1"
   2003 	typeset newdir="$2"
   2004 
   2005 	# Snag new version of file.
   2006 	rm -f $newdir/$DIR/$F
   2007 	[[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
   2008 
   2009 	if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
   2010 		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
   2011 	else
   2012 		# Get the parent's version of the file.
   2013 		svn status $CWS/$DIR/$F | read stat file
   2014 		if [[ $stat != "A" ]]; then
   2015 			svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
   2016 		fi
   2017 	fi
   2018 }
   2019 
   2020 function build_old_new_unknown
   2021 {
   2022 	typeset olddir="$1"
   2023 	typeset newdir="$2"
   2024 
   2025 	#
   2026 	# Snag new version of file.
   2027 	#
   2028 	rm -f $newdir/$DIR/$F
   2029 	[[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
   2030 
   2031 	#
   2032 	# Snag the parent's version of the file.
   2033 	#
   2034 	if [[ -f $PWS/$PDIR/$PF ]]; then
   2035 		rm -f $olddir/$PDIR/$PF
   2036 		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
   2037 	fi
   2038 }
   2039 
   2040 function build_old_new
   2041 {
   2042 	typeset WDIR=$1
   2043 	typeset PWS=$2
   2044 	typeset PDIR=$3
   2045 	typeset PF=$4
   2046 	typeset CWS=$5
   2047 	typeset DIR=$6
   2048 	typeset F=$7
   2049 
   2050 	typeset olddir="$WDIR/raw_files/old"
   2051 	typeset newdir="$WDIR/raw_files/new"
   2052 
   2053 	mkdir -p $olddir/$PDIR
   2054 	mkdir -p $newdir/$DIR
   2055 
   2056 	if [[ $SCM_MODE == "teamware" ]]; then
   2057 		build_old_new_teamware "$olddir" "$newdir"
   2058 	elif [[ $SCM_MODE == "mercurial" ]]; then
   2059 		build_old_new_mercurial "$olddir" "$newdir"
   2060 	elif [[ $SCM_MODE == "subversion" ]]; then
   2061 		build_old_new_subversion "$olddir" "$newdir"
   2062 	elif [[ $SCM_MODE == "unknown" ]]; then
   2063 		build_old_new_unknown "$olddir" "$newdir"
   2064 	fi
   2065 
   2066 	if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
   2067 		print "*** Error: file not in parent or child"
   2068 		return 1
   2069 	fi
   2070 	return 0
   2071 }
   2072 
   2073 
   2074 #
   2075 # Usage message.
   2076 #
   2077 function usage
   2078 {
   2079 	print 'Usage:\twebrev [common-options]
   2080 	webrev [common-options] ( <file> | - )
   2081 	webrev [common-options] -w <wx file>
   2082 
   2083 Options:
   2084 	-C <filename>: Use <filename> for the information tracking configuration.
   2085 	-D: delete remote webrev
   2086 	-i <filename>: Include <filename> in the index.html file.
   2087 	-I <filename>: Use <filename> for the information tracking registry.
   2088 	-n: do not generate the webrev (useful with -U)
   2089 	-O: Print bugids/arc cases suitable for OpenSolaris.
   2090 	-o <outdir>: Output webrev to specified directory.
   2091 	-p <compare-against>: Use specified parent wkspc or basis for comparison
   2092 	-t <remote_target>: Specify remote destination for webrev upload
   2093 	-U: upload the webrev to remote destination
   2094 	-w <wxfile>: Use specified wx active file.
   2095 
   2096 Environment:
   2097 	WDIR: Control the output directory.
   2098 	WEBREV_TRASH_DIR: Set directory for webrev delete.
   2099 
   2100 SCM Specific Options:
   2101 	TeamWare: webrev [common-options] -l [arguments to 'putback']
   2102 
   2103 SCM Environment:
   2104 	CODEMGR_WS: Workspace location.
   2105 	CODEMGR_PARENT: Parent workspace location.
   2106 '
   2107 
   2108 	exit 2
   2109 }
   2110 
   2111 #
   2112 #
   2113 # Main program starts here
   2114 #
   2115 #
   2116 
   2117 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
   2118 
   2119 set +o noclobber
   2120 
   2121 PATH=$(dirname $(whence $0)):$PATH
   2122 
   2123 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
   2124 [[ -z $WX ]] && WX=`look_for_prog wx`
   2125 [[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
   2126 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
   2127 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
   2128 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
   2129 [[ -z $PERL ]] && PERL=`look_for_prog perl`
   2130 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
   2131 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
   2132 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
   2133 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
   2134 [[ -z $AWK ]] && AWK=`look_for_prog awk`
   2135 [[ -z $SCP ]] && SCP=`look_for_prog scp`
   2136 [[ -z $SED ]] && SED=`look_for_prog sed`
   2137 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
   2138 [[ -z $SORT ]] && SORT=`look_for_prog sort`
   2139 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
   2140 [[ -z $GREP ]] && GREP=`look_for_prog grep`
   2141 [[ -z $FIND ]] && FIND=`look_for_prog find`
   2142 
   2143 # set name of trash directory for remote webrev deletion
   2144 TRASH_DIR=".trash"
   2145 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
   2146 
   2147 if [[ ! -x $PERL ]]; then
   2148 	print -u2 "Error: No perl interpreter found.  Exiting."
   2149 	exit 1
   2150 fi
   2151 
   2152 if [[ ! -x $WHICH_SCM ]]; then
   2153 	print -u2 "Error: Could not find which_scm.  Exiting."
   2154 	exit 1
   2155 fi
   2156 
   2157 #
   2158 # These aren't fatal, but we want to note them to the user.
   2159 # We don't warn on the absence of 'wx' until later when we've
   2160 # determined that we actually need to try to invoke it.
   2161 #
   2162 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
   2163 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
   2164 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
   2165 
   2166 # Declare global total counters.
   2167 integer TOTL TINS TDEL TMOD TUNC
   2168 
   2169 # default remote host for upload/delete
   2170 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
   2171 # prefixes for upload targets
   2172 typeset -r rsync_prefix="rsync://"
   2173 typeset -r ssh_prefix="ssh://"
   2174 
   2175 Cflag=
   2176 Dflag=
   2177 flist_mode=
   2178 flist_file=
   2179 iflag=
   2180 Iflag=
   2181 lflag=
   2182 Nflag=
   2183 nflag=
   2184 Oflag=
   2185 oflag=
   2186 pflag=
   2187 tflag=
   2188 uflag=
   2189 Uflag=
   2190 wflag=
   2191 remote_target=
   2192 
   2193 #
   2194 # NOTE: when adding/removing options it is necessary to sync the list
   2195 #	with usr/src/tools/onbld/hgext/cdm.py
   2196 #
   2197 while getopts "C:Di:I:lnNo:Op:t:Uw" opt
   2198 do
   2199 	case $opt in
   2200 	C)	Cflag=1
   2201 		ITSCONF=$OPTARG;;
   2202 
   2203 	D)	Dflag=1;;
   2204 
   2205 	i)	iflag=1
   2206 		INCLUDE_FILE=$OPTARG;;
   2207 
   2208 	I)	Iflag=1
   2209 		ITSREG=$OPTARG;;
   2210 
   2211 	#
   2212 	# If -l has been specified, we need to abort further options
   2213 	# processing, because subsequent arguments are going to be
   2214 	# arguments to 'putback -n'.
   2215 	#
   2216 	l)	lflag=1
   2217 		break;;
   2218 
   2219 	N)	Nflag=1;;
   2220 
   2221 	n)	nflag=1;;
   2222 
   2223 	O)	Oflag=1;;
   2224 
   2225 	o)	oflag=1
   2226 		# Strip the trailing slash to correctly form remote target.
   2227 		WDIR=${OPTARG%/};;
   2228 
   2229 	p)	pflag=1
   2230 		codemgr_parent=$OPTARG;;
   2231 
   2232 	t)	tflag=1
   2233 		remote_target=$OPTARG;;
   2234 
   2235 	U)	Uflag=1;;
   2236 
   2237 	w)	wflag=1;;
   2238 
   2239 	?)	usage;;
   2240 	esac
   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 	# 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under
   2288 	#    usr/closed when we run webrev
   2289 	# 2. hg root from directory of invocation
   2290 	#
   2291 	if [[ ${PWD} =~ "usr/closed" ]]; then
   2292 		testparent=${CODEMGR_WS}/usr/closed
   2293 		# If we're in OpenSolaris mode, we enforce a minor policy:
   2294 		# help to make sure the reviewer doesn't accidentally publish
   2295 		# source which is under usr/closed
   2296 		if [[ -n "$Oflag" ]]; then
   2297 			print -u2 "OpenSolaris output not permitted with" \
   2298 			    "usr/closed changes"
   2299 			exit 1
   2300 		fi
   2301 	else
   2302 	        testparent=${CODEMGR_WS}
   2303 	fi
   2304 	[[ -z $codemgr_ws && -n $testparent ]] && \
   2305 	    codemgr_ws=$(hg root -R $testparent 2>/dev/null)
   2306 	[[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null)
   2307 	CWS=$codemgr_ws
   2308 elif [[ $SCM_MODE == "subversion" ]]; then
   2309 	#
   2310 	# Subversion priorities:
   2311 	# 1. CODEMGR_WS from environment
   2312 	# 2. Relative path from current directory to SVN repository root
   2313 	#
   2314 	if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
   2315 		CWS=$CODEMGR_WS
   2316 	else
   2317 		svn info | while read line; do
   2318 			if [[ $line == "URL: "* ]]; then
   2319 				url=${line#URL: }
   2320 			elif [[ $line == "Repository Root: "* ]]; then
   2321 				repo=${line#Repository Root: }
   2322 			fi
   2323 		done
   2324 
   2325 		rel=${url#$repo}
   2326 		CWS=${PWD%$rel}
   2327 	fi
   2328 fi
   2329 
   2330 #
   2331 # If no SCM has been determined, take either the environment setting
   2332 # setting for CODEMGR_WS, or the current directory if that wasn't set.
   2333 #
   2334 if [[ -z ${CWS} ]]; then
   2335 	CWS=${CODEMGR_WS:-.}
   2336 fi
   2337 
   2338 #
   2339 # If the command line options indicate no webrev generation, either
   2340 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
   2341 # ton of logic we can skip.
   2342 #
   2343 # Instead of increasing indentation, we intentionally leave this loop
   2344 # body open here, and exit via break from multiple points within.
   2345 # Search for DO_EVERYTHING below to find the break points and closure.
   2346 #
   2347 for do_everything in 1; do
   2348 
   2349 # DO_EVERYTHING: break point
   2350 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
   2351 	break
   2352 fi
   2353 
   2354 #
   2355 # If this manually set as the parent, and it appears to be an earlier webrev,
   2356 # then note that fact and set the parent to the raw_files/new subdirectory.
   2357 #
   2358 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
   2359 	parent_webrev="$codemgr_parent"
   2360 	codemgr_parent="$codemgr_parent/raw_files/new"
   2361 fi
   2362 
   2363 if [[ -z $wflag && -z $lflag ]]; then
   2364 	shift $(($OPTIND - 1))
   2365 
   2366 	if [[ $1 == "-" ]]; then
   2367 		cat > $FLIST
   2368 		flist_mode="stdin"
   2369 		flist_done=1
   2370 		shift
   2371 	elif [[ -n $1 ]]; then
   2372 		if [[ ! -r $1 ]]; then
   2373 			print -u2 "$1: no such file or not readable"
   2374 			usage
   2375 		fi
   2376 		cat $1 > $FLIST
   2377 		flist_mode="file"
   2378 		flist_file=$1
   2379 		flist_done=1
   2380 		shift
   2381 	else
   2382 		flist_mode="auto"
   2383 	fi
   2384 fi
   2385 
   2386 #
   2387 # Before we go on to further consider -l and -w, work out which SCM we think
   2388 # is in use.
   2389 #
   2390 case "$SCM_MODE" in
   2391 teamware|mercurial|subversion)
   2392 	;;
   2393 unknown)
   2394 	if [[ $flist_mode == "auto" ]]; then
   2395 		print -u2 "Unable to determine SCM in use and file list not specified"
   2396 		print -u2 "See which_scm(1) for SCM detection information."
   2397 		exit 1
   2398 	fi
   2399 	;;
   2400 *)
   2401 	if [[ $flist_mode == "auto" ]]; then
   2402 		print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
   2403 		exit 1
   2404 	fi
   2405 	;;
   2406 esac
   2407 
   2408 print -u2 "   SCM detected: $SCM_MODE"
   2409 
   2410 if [[ -n $lflag ]]; then
   2411 	#
   2412 	# If the -l flag is given instead of the name of a file list,
   2413 	# then generate the file list by extracting file names from a
   2414 	# putback -n.
   2415 	#
   2416 	shift $(($OPTIND - 1))
   2417 	if [[ $SCM_MODE == "teamware" ]]; then
   2418 		flist_from_teamware "$*"
   2419 	else
   2420 		print -u2 -- "Error: -l option only applies to TeamWare"
   2421 		exit 1
   2422 	fi
   2423 	flist_done=1
   2424 	shift $#
   2425 elif [[ -n $wflag ]]; then
   2426 	#
   2427 	# If the -w is given then assume the file list is in Bonwick's "wx"
   2428 	# command format, i.e.  pathname lines alternating with SCCS comment
   2429 	# lines with blank lines as separators.  Use the SCCS comments later
   2430 	# in building the index.html file.
   2431 	#
   2432 	shift $(($OPTIND - 1))
   2433 	wxfile=$1
   2434 	if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
   2435 		if [[ -r $CODEMGR_WS/wx/active ]]; then
   2436 			wxfile=$CODEMGR_WS/wx/active
   2437 		fi
   2438 	fi
   2439 
   2440 	[[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
   2441 	    "be auto-detected (check \$CODEMGR_WS)" && exit 1
   2442 
   2443 	if [[ ! -r $wxfile ]]; then
   2444 		print -u2 "$wxfile: no such file or not readable"
   2445 		usage
   2446 	fi
   2447 
   2448 	print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
   2449 	flist_from_wx $wxfile
   2450 	flist_done=1
   2451 	if [[ -n "$*" ]]; then
   2452 		shift
   2453 	fi
   2454 elif [[ $flist_mode == "stdin" ]]; then
   2455 	print -u2 " File list from: standard input"
   2456 elif [[ $flist_mode == "file" ]]; then
   2457 	print -u2 " File list from: $flist_file"
   2458 fi
   2459 
   2460 if [[ $# -gt 0 ]]; then
   2461 	print -u2 "WARNING: unused arguments: $*"
   2462 fi
   2463 
   2464 #
   2465 # Before we entered the DO_EVERYTHING loop, we should have already set CWS
   2466 # and CODEMGR_WS as needed.  Here, we set the parent workspace.
   2467 #
   2468 
   2469 if [[ $SCM_MODE == "teamware" ]]; then
   2470 
   2471 	#
   2472 	# Teamware priorities:
   2473 	#
   2474 	#      1) via -p command line option
   2475 	#      2) in the user environment
   2476 	#      3) in the flist
   2477 	#      4) automatically based on the workspace
   2478 	#
   2479 
   2480 	#
   2481 	# For 1, codemgr_parent will already be set.  Here's 2:
   2482 	#
   2483 	[[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
   2484 	    codemgr_parent=$CODEMGR_PARENT
   2485 	if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then
   2486 		print -u2 "$codemgr_parent: no such directory"
   2487 		exit 1
   2488 	fi
   2489 
   2490 	#
   2491 	# If we're in auto-detect mode and we haven't already gotten the file
   2492 	# list, then see if we can get it by probing for wx.
   2493 	#
   2494 	if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then
   2495 		if [[ ! -x $WX ]]; then
   2496 			print -u2 "WARNING: wx not found!"
   2497 		fi
   2498 
   2499 		#
   2500 		# We need to use wx list -w so that we get renamed files, etc.
   2501 		# but only if a wx active file exists-- otherwise wx will
   2502 		# hang asking us to initialize our wx information.
   2503 		#
   2504 		if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then
   2505 			print -u2 " File list from: 'wx list -w' ... \c"
   2506 			$WX list -w > $FLIST
   2507 			$WX comments > /tmp/$$.wx_comments
   2508 			wxfile=/tmp/$$.wx_comments
   2509 			print -u2 "done"
   2510 			flist_done=1
   2511 		fi
   2512 	fi
   2513 
   2514 	#
   2515 	# If by hook or by crook we've gotten a file list by now (perhaps
   2516 	# from the command line), eval it to extract environment variables from
   2517 	# it: This is method 3 for finding the parent.
   2518 	#
   2519 	if [[ -z $flist_done ]]; then
   2520 		flist_from_teamware
   2521 	fi
   2522 	env_from_flist
   2523 
   2524 	#
   2525 	# (4) If we still don't have a value for codemgr_parent, get it
   2526 	# from workspace.
   2527 	#
   2528 	[[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent`
   2529 	if [[ ! -d $codemgr_parent ]]; then
   2530 		print -u2 "$CODEMGR_PARENT: no such parent workspace"
   2531 		exit 1
   2532 	fi
   2533 
   2534 	PWS=$codemgr_parent
   2535 
   2536 	[[ -n $parent_webrev ]] && RWS=$(workspace parent $CWS)
   2537 
   2538 elif [[ $SCM_MODE == "mercurial" ]]; then
   2539 	#
   2540 	# Parent can either be specified with -p
   2541 	# Specified with CODEMGR_PARENT in the environment
   2542 	# or taken from hg's default path.
   2543 	#
   2544 
   2545 	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
   2546 		codemgr_parent=$CODEMGR_PARENT
   2547 	fi
   2548 
   2549 	if [[ -z $codemgr_parent ]]; then
   2550 		codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
   2551 	fi
   2552 
   2553 	CWS_REV=`hg parent -R $codemgr_ws --template '{node|short}' 2>/dev/null`
   2554 	PWS=$codemgr_parent
   2555 
   2556 	#
   2557 	# If the parent is a webrev, we want to do some things against
   2558 	# the natural workspace parent (file list, comments, etc)
   2559 	#
   2560 	if [[ -n $parent_webrev ]]; then
   2561 		real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
   2562 	else
   2563 		real_parent=$PWS
   2564 	fi
   2565 
   2566 	#
   2567 	# If hg-active exists, then we run it.  In the case of no explicit
   2568 	# flist given, we'll use it for our comments.  In the case of an
   2569 	# explicit flist given we'll try to use it for comments for any
   2570 	# files mentioned in the flist.
   2571 	#
   2572 	if [[ -z $flist_done ]]; then
   2573 		flist_from_mercurial $CWS $real_parent
   2574 		flist_done=1
   2575 	fi
   2576 
   2577 	#
   2578 	# If we have a file list now, pull out any variables set
   2579 	# therein.  We do this now (rather than when we possibly use
   2580 	# hg-active to find comments) to avoid stomping specifications
   2581 	# in the user-specified flist.
   2582 	#
   2583 	if [[ -n $flist_done ]]; then
   2584 		env_from_flist
   2585 	fi
   2586 
   2587 	#
   2588 	# Only call hg-active if we don't have a wx formatted file already
   2589 	#
   2590 	if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
   2591 		print "  Comments from: hg-active -p $real_parent ...\c"
   2592 		hg_active_wxfile $CWS $real_parent
   2593 		print " Done."
   2594 	fi
   2595 
   2596 	#
   2597 	# At this point we must have a wx flist either from hg-active,
   2598 	# or in general.  Use it to try and find our parent revision,
   2599 	# if we don't have one.
   2600 	#
   2601 	if [[ -z $HG_PARENT ]]; then
   2602 		eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
   2603 	fi
   2604 
   2605 	#
   2606 	# If we still don't have a parent, we must have been given a
   2607 	# wx-style active list with no HG_PARENT specification, run
   2608 	# hg-active and pull an HG_PARENT out of it, ignore the rest.
   2609 	#
   2610 	if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
   2611 		$HG_ACTIVE -w $codemgr_ws -p $real_parent | \
   2612 		    eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
   2613 	elif [[ -z $HG_PARENT ]]; then
   2614 		print -u2 "Error: Cannot discover parent revision"
   2615 		exit 1
   2616 	fi
   2617 elif [[ $SCM_MODE == "subversion" ]]; then
   2618 
   2619 	#
   2620 	# We only will have a real parent workspace in the case one
   2621 	# was specified (be it an older webrev, or another checkout).
   2622 	#
   2623 	[[ -n $codemgr_parent ]] && PWS=$codemgr_parent
   2624 
   2625 	if [[ -z $flist_done && $flist_mode == "auto" ]]; then
   2626 		flist_from_subversion $CWS $OLDPWD
   2627 	fi
   2628 else
   2629     if [[ $SCM_MODE == "unknown" ]]; then
   2630 	print -u2 "    Unknown type of SCM in use"
   2631     else
   2632 	print -u2 "    Unsupported SCM in use: $SCM_MODE"
   2633     fi
   2634 
   2635     env_from_flist
   2636 
   2637     if [[ -z $CODEMGR_WS ]]; then
   2638 	print -u2 "SCM not detected/supported and CODEMGR_WS not specified"
   2639 	exit 1
   2640     fi
   2641 
   2642     if [[ -z $CODEMGR_PARENT ]]; then
   2643 	print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified"
   2644 	exit 1
   2645     fi
   2646 
   2647     CWS=$CODEMGR_WS
   2648     PWS=$CODEMGR_PARENT
   2649 fi
   2650 
   2651 #
   2652 # If the user didn't specify a -i option, check to see if there is a
   2653 # webrev-info file in the workspace directory.
   2654 #
   2655 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
   2656 	iflag=1
   2657 	INCLUDE_FILE="$CWS/webrev-info"
   2658 fi
   2659 
   2660 if [[ -n $iflag ]]; then
   2661 	if [[ ! -r $INCLUDE_FILE ]]; then
   2662 		print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
   2663 		    "not readable."
   2664 		exit 1
   2665 	else
   2666 		#
   2667 		# $INCLUDE_FILE may be a relative path, and the script alters
   2668 		# PWD, so we just stash a copy in /tmp.
   2669 		#
   2670 		cp $INCLUDE_FILE /tmp/$$.include
   2671 	fi
   2672 fi
   2673 
   2674 # DO_EVERYTHING: break point
   2675 if [[ -n $Nflag ]]; then
   2676 	break
   2677 fi
   2678 
   2679 typeset -A itsinfo
   2680 typeset -r its_sed_script=/tmp/$$.its_sed
   2681 valid_prefixes=
   2682 if [[ -z $nflag ]]; then
   2683 	DEFREGFILE="$(dirname $(whence $0))/../etc/its.reg"
   2684 	if [[ -n $Iflag ]]; then
   2685 		REGFILE=$ITSREG
   2686 	elif [[ -r $HOME/.its.reg ]]; then
   2687 		REGFILE=$HOME/.its.reg
   2688 	else
   2689 		REGFILE=$DEFREGFILE
   2690 	fi
   2691 	if [[ ! -r $REGFILE ]]; then
   2692 		print "ERROR: Unable to read database registry file $REGFILE"
   2693 		exit 1
   2694 	elif [[ $REGFILE != $DEFREGFILE ]]; then
   2695 		print "   its.reg from: $REGFILE"
   2696 	fi
   2697 
   2698 	$SED -e '/^#/d' -e '/^[ 	]*$/d' $REGFILE | while read LINE; do
   2699 
   2700 		name=${LINE%%=*}
   2701 		value="${LINE#*=}"
   2702 
   2703 		if [[ $name == PREFIX ]]; then
   2704 			p=${value}
   2705 			valid_prefixes="${p} ${valid_prefixes}"
   2706 		else
   2707 			itsinfo["${p}_${name}"]="${value}"
   2708 		fi
   2709 	done
   2710 
   2711 
   2712 	DEFCONFFILE="$(dirname $(whence $0))/../etc/its.conf"
   2713 	CONFFILES=$DEFCONFFILE
   2714 	if [[ -r $HOME/.its.conf ]]; then
   2715 		CONFFILES="${CONFFILES} $HOME/.its.conf"
   2716 	fi
   2717 	if [[ -n $Cflag ]]; then
   2718 		CONFFILES="${CONFFILES} ${ITSCONF}"
   2719 	fi
   2720 	its_domain=
   2721 	its_priority=
   2722 	for cf in ${CONFFILES}; do
   2723 		if [[ ! -r $cf ]]; then
   2724 			print "ERROR: Unable to read database configuration file $cf"
   2725 			exit 1
   2726 		elif [[ $cf != $DEFCONFFILE ]]; then
   2727 			print "       its.conf: reading $cf"
   2728 		fi
   2729 		$SED -e '/^#/d' -e '/^[ 	]*$/d' $cf | while read LINE; do
   2730 		    eval "${LINE}"
   2731 		done
   2732 	done
   2733 
   2734 	#
   2735 	# If an information tracking system is explicitly identified by prefix,
   2736 	# we want to disregard the specified priorities and resolve it accordingly.
   2737 	#
   2738 	# To that end, we'll build a sed script to do each valid prefix in turn.
   2739 	#
   2740 	for p in ${valid_prefixes}; do
   2741 		#
   2742 		# When an informational URL was provided, translate it to a
   2743 		# hyperlink.  When omitted, simply use the prefix text.
   2744 		#
   2745 		if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
   2746 			itsinfo["${p}_INFO"]=${p}
   2747 		else
   2748 			itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
   2749 		fi
   2750 
   2751 		#
   2752 		# Assume that, for this invocation of webrev, all references
   2753 		# to this information tracking system should resolve through
   2754 		# the same URL.
   2755 		#
   2756 		# If the caller specified -O, then always use EXTERNAL_URL.
   2757 		#
   2758 		# Otherwise, look in the list of domains for a matching
   2759 		# INTERNAL_URL.
   2760 		#
   2761 		[[ -z $Oflag ]] && for d in ${its_domain}; do
   2762 			if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
   2763 				itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
   2764 				break
   2765 			fi
   2766 		done
   2767 		if [[ -z ${itsinfo["${p}_URL"]} ]]; then
   2768 			itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
   2769 		fi
   2770 
   2771 		#
   2772 		# Turn the destination URL into a hyperlink
   2773 		#
   2774 		itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
   2775 
   2776 		print "/^${p}[ 	]/ {
   2777 				s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
   2778 				s;^${p};${itsinfo[${p}_INFO]};
   2779 			}" >> ${its_sed_script}
   2780 	done
   2781 
   2782 	#
   2783 	# The previous loop took care of explicit specification.  Now use
   2784 	# the configured priorities to attempt implicit translations.
   2785 	#
   2786 	for p in ${its_priority}; do
   2787 		print "/^${itsinfo[${p}_REGEX]}[ 	]/ {
   2788 				s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
   2789 			}" >> ${its_sed_script}
   2790 	done
   2791 fi
   2792 
   2793 #
   2794 # Search for DO_EVERYTHING above for matching "for" statement
   2795 # and explanation of this terminator.
   2796 #
   2797 done
   2798 
   2799 #
   2800 # Output directory.
   2801 #
   2802 WDIR=${WDIR:-$CWS/webrev}
   2803 
   2804 #
   2805 # Name of the webrev, derived from the workspace name or output directory;
   2806 # in the future this could potentially be an option.
   2807 #
   2808 if [[ -n $oflag ]]; then
   2809 	WNAME=${WDIR##*/}
   2810 else
   2811 	WNAME=${CWS##*/}
   2812 fi
   2813 
   2814 # Make sure remote target is well formed for remote upload/delete.
   2815 if [[ -n $Dflag || -n $Uflag ]]; then
   2816 	#
   2817 	# If remote target is not specified, build it from scratch using
   2818 	# the default values.
   2819 	#
   2820 	if [[ -z $tflag ]]; then
   2821 		remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
   2822 	else
   2823 		#
   2824 		# Check upload target prefix first.
   2825 		#
   2826 		if [[ "${remote_target}" != ${rsync_prefix}* &&
   2827 		    "${remote_target}" != ${ssh_prefix}* ]]; then
   2828 			print "ERROR: invalid prefix of upload URI" \
   2829 			    "($remote_target)"
   2830 			exit 1
   2831 		fi
   2832 		#
   2833 		# If destination specification is not in the form of
   2834 		# host_spec:remote_dir then assume it is just remote hostname
   2835 		# and append a colon and destination directory formed from
   2836 		# local webrev directory name.
   2837 		#
   2838 		typeset target_no_prefix=${remote_target##*://}
   2839 		if [[ ${target_no_prefix} == *:* ]]; then
   2840 			if [[ "${remote_target}" == *: ]]; then
   2841 				remote_target=${remote_target}${WNAME}
   2842 			fi
   2843 		else
   2844 			if [[ ${target_no_prefix} == */* ]]; then
   2845 				print "ERROR: badly formed upload URI" \
   2846 					"($remote_target)"
   2847 				exit 1
   2848 			else
   2849 				remote_target=${remote_target}:${WNAME}
   2850 			fi
   2851 		fi
   2852 	fi
   2853 
   2854 	#
   2855 	# Strip trailing slash. Each upload method will deal with directory
   2856 	# specification separately.
   2857 	#
   2858 	remote_target=${remote_target%/}
   2859 fi
   2860 
   2861 #
   2862 # Option -D by itself (option -U not present) implies no webrev generation.
   2863 #
   2864 if [[ -z $Uflag && -n $Dflag ]]; then
   2865 	delete_webrev 1 1
   2866 	exit $?
   2867 fi
   2868 
   2869 #
   2870 # Do not generate the webrev, just upload it or delete it.
   2871 #
   2872 if [[ -n $nflag ]]; then
   2873 	if [[ -n $Dflag ]]; then
   2874 		delete_webrev 1 1
   2875 		(( $? == 0 )) || exit $?
   2876 	fi
   2877 	if [[ -n $Uflag ]]; then
   2878 		upload_webrev
   2879 		exit $?
   2880 	fi
   2881 fi
   2882 
   2883 if [ "${WDIR%%/*}" ]; then
   2884 	WDIR=$PWD/$WDIR
   2885 fi
   2886 
   2887 if [[ ! -d $WDIR ]]; then
   2888 	mkdir -p $WDIR
   2889 	(( $? != 0 )) && exit 1
   2890 fi
   2891 
   2892 #
   2893 # Summarize what we're going to do.
   2894 #
   2895 if [[ -n $CWS_REV ]]; then
   2896 	print "      Workspace: $CWS (at $CWS_REV)"
   2897 else
   2898 	print "      Workspace: $CWS"
   2899 fi
   2900 if [[ -n $parent_webrev ]]; then
   2901 	print "Compare against: webrev at $parent_webrev"
   2902 else
   2903 	if [[ -n $HG_PARENT ]]; then
   2904 		hg_parent_short=`echo $HG_PARENT \
   2905 			| $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'`
   2906 		print "Compare against: $PWS (at $hg_parent_short)"
   2907 	else
   2908 		print "Compare against: $PWS"
   2909 	fi
   2910 fi
   2911 
   2912 [[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
   2913 print "      Output to: $WDIR"
   2914 
   2915 #
   2916 # Save the file list in the webrev dir
   2917 #
   2918 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
   2919 
   2920 rm -f $WDIR/$WNAME.patch
   2921 rm -f $WDIR/$WNAME.ps
   2922 rm -f $WDIR/$WNAME.pdf
   2923 
   2924 touch $WDIR/$WNAME.patch
   2925 
   2926 print "   Output Files:"
   2927 
   2928 #
   2929 # Clean up the file list: Remove comments, blank lines and env variables.
   2930 #
   2931 $SED -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
   2932 FLIST=/tmp/$$.flist.clean
   2933 
   2934 #
   2935 # For Mercurial, create a cache of manifest entries.
   2936 #
   2937 if [[ $SCM_MODE == "mercurial" ]]; then
   2938 	#
   2939 	# Transform the FLIST into a temporary sed script that matches
   2940 	# relevant entries in the Mercurial manifest as follows:
   2941 	# 1) The script will be used against the parent revision manifest,
   2942 	#    so for FLIST lines that have two filenames (a renamed file)
   2943 	#    keep only the old name.
   2944 	# 2) Escape all forward slashes the filename.
   2945 	# 3) Change the filename into another sed command that matches
   2946 	#    that file in "hg manifest -v" output:  start of line, three
   2947 	#    octal digits for file permissions, space, a file type flag
   2948 	#    character, space, the filename, end of line.
   2949 	# 4) Eliminate any duplicate entries.  (This can occur if a
   2950 	#    file has been used as the source of an hg cp and it's
   2951 	#    also been modified in the same changeset.)
   2952 	#
   2953 	SEDFILE=/tmp/$$.manifest.sed
   2954 	$SED '
   2955 		s#^[^ ]* ##
   2956 		s#/#\\\/#g
   2957 		s#^.*$#/^... . &$/p#
   2958 	' < $FLIST | $SORT -u > $SEDFILE
   2959 
   2960 	#
   2961 	# Apply the generated script to the output of "hg manifest -v"
   2962 	# to get the relevant subset for this webrev.
   2963 	#
   2964 	HG_PARENT_MANIFEST=/tmp/$$.manifest
   2965 	hg -R $CWS manifest -v -r $HG_PARENT |
   2966 	    $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
   2967 fi
   2968 
   2969 #
   2970 # First pass through the files: generate the per-file webrev HTML-files.
   2971 #
   2972 cat $FLIST | while read LINE
   2973 do
   2974 	set - $LINE
   2975 	P=$1
   2976 
   2977 	#
   2978 	# Normally, each line in the file list is just a pathname of a
   2979 	# file that has been modified or created in the child.  A file
   2980 	# that is renamed in the child workspace has two names on the
   2981 	# line: new name followed by the old name.
   2982 	#
   2983 	oldname=""
   2984 	oldpath=""
   2985 	rename=
   2986 	if [[ $# -eq 2 ]]; then
   2987 		PP=$2			# old filename
   2988 		if [[ -f $PP ]]; then
   2989 			oldname=" (copied from $PP)"
   2990 		else
   2991 			oldname=" (renamed from $PP)"
   2992 		fi
   2993 		oldpath="$PP"
   2994 		rename=1
   2995 		PDIR=${PP%/*}
   2996 		if [[ $PDIR == $PP ]]; then
   2997 			PDIR="."   # File at root of workspace
   2998 		fi
   2999 
   3000 		PF=${PP##*/}
   3001 
   3002 		DIR=${P%/*}
   3003 		if [[ $DIR == $P ]]; then
   3004 			DIR="."   # File at root of workspace
   3005 		fi
   3006 
   3007 		F=${P##*/}
   3008 
   3009         else
   3010 		DIR=${P%/*}
   3011 		if [[ "$DIR" == "$P" ]]; then
   3012 			DIR="."   # File at root of workspace
   3013 		fi
   3014 
   3015 		F=${P##*/}
   3016 
   3017 		PP=$P
   3018 		PDIR=$DIR
   3019 		PF=$F
   3020 	fi
   3021 
   3022 	COMM=`getcomments html $P $PP`
   3023 
   3024 	print "\t$P$oldname\n\t\t\c"
   3025 
   3026 	# Make the webrev mirror directory if necessary
   3027 	mkdir -p $WDIR/$DIR
   3028 
   3029 	#
   3030 	# We stash old and new files into parallel directories in $WDIR
   3031 	# and do our diffs there.  This makes it possible to generate
   3032 	# clean looking diffs which don't have absolute paths present.
   3033 	#
   3034 
   3035 	build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
   3036 	    continue
   3037 
   3038 	#
   3039 	# Keep the old PWD around, so we can safely switch back after
   3040 	# diff generation, such that build_old_new runs in a
   3041 	# consistent environment.
   3042 	#
   3043 	OWD=$PWD
   3044 	cd $WDIR/raw_files
   3045 	ofile=old/$PDIR/$PF
   3046 	nfile=new/$DIR/$F
   3047 
   3048 	mv_but_nodiff=
   3049 	cmp $ofile $nfile > /dev/null 2>&1
   3050 	if [[ $? == 0 && $rename == 1 ]]; then
   3051 		mv_but_nodiff=1
   3052 	fi
   3053 
   3054 	#
   3055 	# If we have old and new versions of the file then run the appropriate
   3056 	# diffs.  This is complicated by a couple of factors:
   3057 	#
   3058 	#	- renames must be handled specially: we emit a 'remove'
   3059 	#	  diff and an 'add' diff
   3060 	#	- new files and deleted files must be handled specially
   3061 	#	- Solaris patch(1m) can't cope with file creation
   3062 	#	  (and hence renames) as of this writing.
   3063 	#       - To make matters worse, gnu patch doesn't interpret the
   3064 	#	  output of Solaris diff properly when it comes to
   3065 	#	  adds and deletes.  We need to do some "cleansing"
   3066 	#         transformations:
   3067 	#	    [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
   3068 	#	    [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
   3069 	#
   3070 	cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
   3071 	cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
   3072 
   3073 	rm -f $WDIR/$DIR/$F.patch
   3074 	if [[ -z $rename ]]; then
   3075 		if [ ! -f "$ofile" ]; then
   3076 			diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
   3077 			    > $WDIR/$DIR/$F.patch
   3078 		elif [ ! -f "$nfile" ]; then
   3079 			diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
   3080 			    > $WDIR/$DIR/$F.patch
   3081 		else
   3082 			diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
   3083 		fi
   3084 	else
   3085 		diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
   3086 		    > $WDIR/$DIR/$F.patch
   3087 
   3088 		diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
   3089 		    >> $WDIR/$DIR/$F.patch
   3090 	fi
   3091 
   3092 	#
   3093 	# Tack the patch we just made onto the accumulated patch for the
   3094 	# whole wad.
   3095 	#
   3096 	cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
   3097 
   3098 	print " patch\c"
   3099 
   3100 	if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
   3101 
   3102 		${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
   3103 		diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
   3104 		    > $WDIR/$DIR/$F.cdiff.html
   3105 		print " cdiffs\c"
   3106 
   3107 		${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
   3108 		diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
   3109 		    > $WDIR/$DIR/$F.udiff.html
   3110 
   3111 		print " udiffs\c"
   3112 
   3113 		if [[ -x $WDIFF ]]; then
   3114 			$WDIFF -c "$COMM" \
   3115 			    -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
   3116 			    $WDIR/$DIR/$F.wdiff.html 2>/dev/null
   3117 			if [[ $? -eq 0 ]]; then
   3118 				print " wdiffs\c"
   3119 			else
   3120 				print " wdiffs[fail]\c"
   3121 			fi
   3122 		fi
   3123 
   3124 		sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
   3125 		    > $WDIR/$DIR/$F.sdiff.html
   3126 		print " sdiffs\c"
   3127 
   3128 		print " frames\c"
   3129 
   3130 		rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
   3131 
   3132 		difflines $ofile $nfile > $WDIR/$DIR/$F.count
   3133 
   3134 	elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
   3135 		# renamed file: may also have differences
   3136 		difflines $ofile $nfile > $WDIR/$DIR/$F.count
   3137 	elif [[ -f $nfile ]]; then
   3138 		# new file: count added lines
   3139 		difflines /dev/null $nfile > $WDIR/$DIR/$F.count
   3140 	elif [[ -f $ofile ]]; then
   3141 		# old file: count deleted lines
   3142 		difflines $ofile /dev/null > $WDIR/$DIR/$F.count
   3143 	fi
   3144 
   3145 	#
   3146 	# Now we generate the postscript for this file.  We generate diffs
   3147 	# only in the event that there is delta, or the file is new (it seems
   3148 	# tree-killing to print out the contents of deleted files).
   3149 	#
   3150 	if [[ -f $nfile ]]; then
   3151 		ocr=$ofile
   3152 		[[ ! -f $ofile ]] && ocr=/dev/null
   3153 
   3154 		if [[ -z $mv_but_nodiff ]]; then
   3155 			textcomm=`getcomments text $P $PP`
   3156 			if [[ -x $CODEREVIEW ]]; then
   3157 				$CODEREVIEW -y "$textcomm" \
   3158 				    -e $ocr $nfile \
   3159 				    > /tmp/$$.psfile 2>/dev/null &&
   3160 				    cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
   3161 				if [[ $? -eq 0 ]]; then
   3162 					print " ps\c"
   3163 				else
   3164 					print " ps[fail]\c"
   3165 				fi
   3166 			fi
   3167 		fi
   3168 	fi
   3169 
   3170 	if [[ -f $ofile ]]; then
   3171 		source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
   3172 		print " old\c"
   3173 	fi
   3174 
   3175 	if [[ -f $nfile ]]; then
   3176 		source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
   3177 		print " new\c"
   3178 	fi
   3179 
   3180 	cd $OWD
   3181 
   3182 	print
   3183 done
   3184 
   3185 frame_nav_js > $WDIR/ancnav.js
   3186 frame_navigation > $WDIR/ancnav.html
   3187 
   3188 if [[ ! -f $WDIR/$WNAME.ps ]]; then
   3189 	print " Generating PDF: Skipped: no output available"
   3190 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
   3191 	print " Generating PDF: \c"
   3192 	fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
   3193 	print "Done."
   3194 else
   3195 	print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
   3196 fi
   3197 
   3198 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
   3199 # delete it - prevent accidental publishing of closed source
   3200 
   3201 if [[ -n "$Oflag" ]]; then
   3202 	$FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
   3203 fi
   3204 
   3205 # Now build the index.html file that contains
   3206 # links to the source files and their diffs.
   3207 
   3208 cd $CWS
   3209 
   3210 # Save total changed lines for Code Inspection.
   3211 print "$TOTL" > $WDIR/TotalChangedLines
   3212 
   3213 print "     index.html: \c"
   3214 INDEXFILE=$WDIR/index.html
   3215 exec 3<&1			# duplicate stdout to FD3.
   3216 exec 1<&-			# Close stdout.
   3217 exec > $INDEXFILE		# Open stdout to index file.
   3218 
   3219 print "$HTML<head>$STDHEAD"
   3220 print "<title>$WNAME</title>"
   3221 print "</head>"
   3222 print "<body id=\"SUNWwebrev\">"
   3223 print "<div class=\"summary\">"
   3224 print "<h2>Code Review for $WNAME</h2>"
   3225 
   3226 print "<table>"
   3227 
   3228 #
   3229 # Get the preparer's name:
   3230 #
   3231 # If the SCM detected is Mercurial, and the configuration property
   3232 # ui.username is available, use that, but be careful to properly escape
   3233 # angle brackets (HTML syntax characters) in the email address.
   3234 #
   3235 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
   3236 # to maintain compatibility with passwd(4), we must support '&' substitutions.
   3237 #
   3238 preparer=
   3239 if [[ "$SCM_MODE" == mercurial ]]; then
   3240 	preparer=`hg showconfig ui.username 2>/dev/null`
   3241 	if [[ -n "$preparer" ]]; then
   3242 		preparer="$(echo "$preparer" | html_quote)"
   3243 	fi
   3244 fi
   3245 if [[ -z "$preparer" ]]; then
   3246 	preparer=$(
   3247 	    $PERL -e '
   3248 	        ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
   3249 	        if ($login) {
   3250 	            $gcos =~ s/\&/ucfirst($login)/e;
   3251 	            printf "%s (%s)\n", $gcos, $login;
   3252 	        } else {
   3253 	            printf "(unknown)\n";
   3254 	        }
   3255 	')
   3256 fi
   3257 
   3258 PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
   3259 print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
   3260 print "<tr><th>Workspace:</th><td>$CWS"
   3261 if [[ -n $CWS_REV ]]; then
   3262 	print "(at $CWS_REV)"
   3263 fi
   3264 print "</td></tr>"
   3265 print "<tr><th>Compare against:</th><td>"
   3266 if [[ -n $parent_webrev ]]; then
   3267 	print "webrev at $parent_webrev"
   3268 else
   3269 	print "$PWS"
   3270 	if [[ -n $hg_parent_short ]]; then
   3271 		print "(at $hg_parent_short)"
   3272 	fi
   3273 fi
   3274 print "</td></tr>"
   3275 print "<tr><th>Summary of changes:</th><td>"
   3276 printCI $TOTL $TINS $TDEL $TMOD $TUNC
   3277 print "</td></tr>"
   3278 
   3279 if [[ -f $WDIR/$WNAME.patch ]]; then
   3280 	wpatch_url="$(print $WNAME.patch | url_encode)"
   3281 	print "<tr><th>Patch of changes:</th><td>"
   3282 	print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
   3283 fi
   3284 if [[ -f $WDIR/$WNAME.pdf ]]; then
   3285 	wpdf_url="$(print $WNAME.pdf | url_encode)"
   3286 	print "<tr><th>Printable review:</th><td>"
   3287 	print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
   3288 fi
   3289 
   3290 if [[ -n "$iflag" ]]; then
   3291 	print "<tr><th>Author comments:</th><td><div>"
   3292 	cat /tmp/$$.include
   3293 	print "</div></td></tr>"
   3294 fi
   3295 print "</table>"
   3296 print "</div>"
   3297 
   3298 #
   3299 # Second pass through the files: generate the rest of the index file
   3300 #
   3301 cat $FLIST | while read LINE
   3302 do
   3303 	set - $LINE
   3304 	P=$1
   3305 
   3306 	if [[ $# == 2 ]]; then
   3307 		PP=$2
   3308 		oldname="$PP"
   3309 	else
   3310 		PP=$P
   3311 		oldname=""
   3312 	fi
   3313 
   3314 	mv_but_nodiff=
   3315 	cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
   3316 	if [[ $? == 0 && -n "$oldname" ]]; then
   3317 		mv_but_nodiff=1
   3318 	fi
   3319 
   3320 	DIR=${P%/*}
   3321 	if [[ $DIR == $P ]]; then
   3322 		DIR="."   # File at root of workspace
   3323 	fi
   3324 
   3325 	# Avoid processing the same file twice.
   3326 	# It's possible for renamed files to
   3327 	# appear twice in the file list
   3328 
   3329 	F=$WDIR/$P
   3330 
   3331 	print "<p>"
   3332 
   3333 	# If there's a diffs file, make diffs links
   3334 
   3335 	if [[ -f $F.cdiff.html ]]; then
   3336 		cdiff_url="$(print $P.cdiff.html | url_encode)"
   3337 		udiff_url="$(print $P.udiff.html | url_encode)"
   3338 		print "<a href=\"$cdiff_url\">Cdiffs</a>"
   3339 		print "<a href=\"$udiff_url\">Udiffs</a>"
   3340 
   3341 		if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
   3342 			wdiff_url="$(print $P.wdiff.html | url_encode)"
   3343 			print "<a href=\"$wdiff_url\">Wdiffs</a>"
   3344 		fi
   3345 
   3346 		sdiff_url="$(print $P.sdiff.html | url_encode)"
   3347 		print "<a href=\"$sdiff_url\">Sdiffs</a>"
   3348 
   3349 		frames_url="$(print $P.frames.html | url_encode)"
   3350 		print "<a href=\"$frames_url\">Frames</a>"
   3351 	else
   3352 		print " ------ ------ ------"
   3353 
   3354 		if [[ -x $WDIFF ]]; then
   3355 			print " ------"
   3356 		fi
   3357 
   3358 		print " ------"
   3359 	fi
   3360 
   3361 	# If there's an old file, make the link
   3362 
   3363 	if [[ -f $F-.html ]]; then
   3364 		oldfile_url="$(print $P-.html | url_encode)"
   3365 		print "<a href=\"$oldfile_url\">Old</a>"
   3366 	else
   3367 		print " ---"
   3368 	fi
   3369 
   3370 	# If there's an new file, make the link
   3371 
   3372 	if [[ -f $F.html ]]; then
   3373 		newfile_url="$(print $P.html | url_encode)"
   3374 		print "<a href=\"$newfile_url\">New</a>"
   3375 	else
   3376 		print " ---"
   3377 	fi
   3378 
   3379 	if [[ -f $F.patch ]]; then
   3380 		patch_url="$(print $P.patch | url_encode)"
   3381 		print "<a href=\"$patch_url\">Patch</a>"
   3382 	else
   3383 		print " -----"
   3384 	fi
   3385 
   3386 	if [[ -f $WDIR/raw_files/new/$P ]]; then
   3387 		rawfiles_url="$(print raw_files/new/$P | url_encode)"
   3388 		print "<a href=\"$rawfiles_url\">Raw</a>"
   3389 	else
   3390 		print " ---"
   3391 	fi
   3392 
   3393 	print "<b>$P</b>"
   3394 
   3395 	# For renamed files, clearly state whether or not they are modified
   3396 	if [[ -f "$oldname" ]]; then
   3397 		if [[ -n "$mv_but_nodiff" ]]; then
   3398 			print "<i>(copied from $oldname)</i>"
   3399 		else
   3400 			print "<i>(copied and modified from $oldname)</i>"
   3401 		fi
   3402 	elif [[ -n "$oldname" ]]; then
   3403 		if [[ -n "$mv_but_nodiff" ]]; then
   3404 			print "<i>(renamed from $oldname)</i>"
   3405 		else
   3406 			print "<i>(renamed and modified from $oldname)</i>"
   3407 		fi
   3408 	fi
   3409 
   3410 	# If there's an old file, but no new file, the file was deleted
   3411 	if [[ -f $F-.html && ! -f $F.html ]]; then
   3412 		print " <i>(deleted)</i>"
   3413 	fi
   3414 
   3415 	#
   3416 	# Check for usr/closed and deleted_files/usr/closed
   3417 	#
   3418 	if [ ! -z "$Oflag" ]; then
   3419 		if [[ $P == usr/closed/* || \
   3420 		    $P == deleted_files/usr/closed/* ]]; then
   3421 			print "&nbsp;&nbsp;<i>Closed source: omitted from" \
   3422 			    "this review</i>"
   3423 		fi
   3424 	fi
   3425 
   3426 	print "</p>"
   3427 	# Insert delta comments
   3428 
   3429 	print "<blockquote><pre>"
   3430 	getcomments html $P $PP
   3431 	print "</pre>"
   3432 
   3433 	# Add additional comments comment
   3434 
   3435 	print "<!-- Add comments to explain changes in $P here -->"
   3436 
   3437 	# Add count of changes.
   3438 
   3439 	if [[ -f $F.count ]]; then
   3440 	    cat $F.count
   3441 	    rm $F.count
   3442 	fi
   3443 
   3444 	if [[ $SCM_MODE == "teamware" ||
   3445 	    $SCM_MODE == "mercurial" ||
   3446 	    $SCM_MODE == "unknown" ]]; then
   3447 
   3448 		# Include warnings for important file mode situations:
   3449 		# 1) New executable files
   3450 		# 2) Permission changes of any kind
   3451 		# 3) Existing executable files
   3452 
   3453 		old_mode=
   3454 		if [[ -f $WDIR/raw_files/old/$PP ]]; then
   3455 			old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
   3456 		fi
   3457 
   3458 		new_mode=
   3459 		if [[ -f $WDIR/raw_files/new/$P ]]; then
   3460 			new_mode=`get_file_mode $WDIR/raw_files/new/$P`
   3461 		fi
   3462 
   3463 		if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
   3464 			print "<span class=\"chmod\">"
   3465 			print "<p>new executable file: mode $new_mode</p>"
   3466 			print "</span>"
   3467 		elif [[ -n "$old_mode" && -n "$new_mode" &&
   3468 		    "$old_mode" != "$new_mode" ]]; then
   3469 			print "<span class=\"chmod\">"
   3470 			print "<p>mode change: $old_mode to $new_mode</p>"
   3471 			print "</span>"
   3472 		elif [[ "$new_mode" = *[1357]* ]]; then
   3473 			print "<span class=\"chmod\">"
   3474 			print "<p>executable file: mode $new_mode</p>"
   3475 			print "</span>"
   3476 		fi
   3477 	fi
   3478 
   3479 	print "</blockquote>"
   3480 done
   3481 
   3482 print
   3483 print
   3484 print "<hr></hr>"
   3485 print "<p style=\"font-size: small\">"
   3486 print "This code review page was prepared using <b>$0</b>."
   3487 print "Webrev is maintained by the <a href=\"http://www.opensolaris.org\">"
   3488 print "OpenSolaris</a> project.  The latest version may be obtained"
   3489 print "<a href=\"http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
   3490 print "</body>"
   3491 print "</html>"
   3492 
   3493 exec 1<&-			# Close FD 1.
   3494 exec 1<&3			# dup FD 3 to restore stdout.
   3495 exec 3<&-			# close FD 3.
   3496 
   3497 print "Done."
   3498 
   3499 #
   3500 # If remote deletion was specified and fails do not continue.
   3501 #
   3502 if [[ -n $Dflag ]]; then
   3503 	delete_webrev 1 1
   3504 	(( $? == 0 )) || exit $?
   3505 fi
   3506 
   3507 if [[ -n $Uflag ]]; then
   3508 	upload_webrev
   3509 	exit $?
   3510 fi
   3511