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