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