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