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) or
 650 #  Teamware's filemerge tool.
 651 #
 652 sdiff_to_html()
 653 {
 654         diff -b $1 $2 > /tmp/$$.diffs
 655 
 656         TNAME=$3
 657         TPATH=$4
 658         COMMENT=$5
 659 
 660         #
 661         #  Now we have the diffs, generate the HTML for the old file.
 662         #
 663         $AWK '
 664         BEGIN   {
 665                 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
 666                 printf "function removed() "
 667                 printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
 668                 printf "function changed() "
 669                 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
 670                 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
 671 }
 672         /^</ {next}
 673         /^>/ {next}
 674         /^---/  {next}
 675 
 676         {
 677         split($1, a, /[cad]/) ;
 678         if (index($1, "a")) {
 679                 if (a[1] == 0) {
 680                         n = split(a[2], r, /,/);
 681                         if (n == 1)
 682                                 printf "BEGIN\t\t{sp(1)}\n"
 683                         else
 684                                 printf "BEGIN\t\t{sp(%d)}\n",\
 685                                 (r[2] - r[1]) + 1
 686                         next
 687                 }
 688 
 689                 printf "NR==%s\t\t{", a[1]
 690                 n = split(a[2], r, /,/);
 691                 s = r[1];
 692                 if (n == 1)
 693                         printf "bl();printf \"\\n\"; next}\n"
 694                 else {
 695                         n = r[2] - r[1]
 696                         printf "bl();sp(%d);next}\n",\
 697                         (r[2] - r[1]) + 1
 698                 }
 699                 next
 700         }
 701         if (index($1, "d")) {
 702                 n = split(a[1], r, /,/);
 703                 n1 = r[1]
 704                 n2 = r[2]
 705                 if (n == 1)
 706                         printf "NR==%s\t\t{removed(); next}\n" , n1
 707                 else
 708                         printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
 709                 next
 710         }
 711         if (index($1, "c")) {
 712                 n = split(a[1], r, /,/);
 713                 n1 = r[1]
 714                 n2 = r[2]
 715                 final = n2
 716                 d1 = 0
 717                 if (n == 1)
 718                         printf "NR==%s\t\t{changed();" , n1
 719                 else {
 720                         d1 = n2 - n1
 721                         printf "NR==%s,NR==%s\t{changed();" , n1, n2
 722                 }
 723                 m = split(a[2], r, /,/);
 724                 n1 = r[1]
 725                 n2 = r[2]
 726                 if (m > 1) {
 727                         d2  = n2 - n1
 728                         if (d2 > d1) {
 729                                 if (n > 1) printf "if (NR==%d)", final
 730                                 printf "sp(%d);", d2 - d1
 731                         }
 732                 }
 733                 printf "next}\n" ;
 734 
 735                 next
 736         }
 737         }
 738 
 739         END     { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
 740         ' /tmp/$$.diffs > /tmp/$$.file1
 741 
 742         #
 743         #  Now generate the HTML for the new file
 744         #
 745         $AWK '
 746         BEGIN   {
 747                 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
 748                 printf "function new() "
 749                 printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
 750                 printf "function changed() "
 751                 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
 752                 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
 753         }
 754 
 755         /^</ {next}
 756         /^>/ {next}
 757         /^---/  {next}
 758 
 759         {
 760         split($1, a, /[cad]/) ;
 761         if (index($1, "d")) {
 762                 if (a[2] == 0) {
 763                         n = split(a[1], r, /,/);
 764                         if (n == 1)
 765                                 printf "BEGIN\t\t{sp(1)}\n"
 766                         else
 767                                 printf "BEGIN\t\t{sp(%d)}\n",\
 768                                 (r[2] - r[1]) + 1
 769                         next
 770                 }
 771 
 772                 printf "NR==%s\t\t{", a[2]
 773                 n = split(a[1], r, /,/);
 774                 s = r[1];
 775                 if (n == 1)
 776                         printf "bl();printf \"\\n\"; next}\n"
 777                 else {
 778                         n = r[2] - r[1]
 779                         printf "bl();sp(%d);next}\n",\
 780                         (r[2] - r[1]) + 1
 781                 }
 782                 next
 783         }
 784         if (index($1, "a")) {
 785                 n = split(a[2], r, /,/);
 786                 n1 = r[1]
 787                 n2 = r[2]
 788                 if (n == 1)
 789                         printf "NR==%s\t\t{new() ; next}\n" , n1
 790                 else
 791                         printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
 792                 next
 793         }
 794         if (index($1, "c")) {
 795                 n = split(a[2], r, /,/);
 796                 n1 = r[1]
 797                 n2 = r[2]
 798                 final = n2
 799                 d2 = 0;
 800                 if (n == 1) {
 801                         final = n1
 802                         printf "NR==%s\t\t{changed();" , n1
 803                 } else {
 804                         d2 = n2 - n1
 805                         printf "NR==%s,NR==%s\t{changed();" , n1, n2
 806                 }
 807                 m = split(a[1], r, /,/);
 808                 n1 = r[1]
 809                 n2 = r[2]
 810                 if (m > 1) {
 811                         d1  = n2 - n1
 812                         if (d1 > d2) {
 813                                 if (n > 1) printf "if (NR==%d)", final
 814                                 printf "sp(%d);", d1 - d2
 815                         }
 816                 }
 817                 printf "next}\n" ;
 818                 next
 819         }
 820         }
 821         END     { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
 822         ' /tmp/$$.diffs > /tmp/$$.file2
 823 
 824         #
 825         # Post-process the HTML files by running them back through $AWK
 826         #
 827         html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html
 828 
 829         html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html
 830 
 831         #
 832         # Now combine into a valid HTML file and side-by-side into a table
 833         #
 834         print "$HTML<head>$STDHEAD"
 835         print "<title>$WNAME Sdiff $TPATH/$TNAME</title>"
 836         print "</head><body id=\"SUNWwebrev\">"
 837         print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
 838         print "<pre>$COMMENT</pre>\n"
 839         print "<table><tr valign=\"top\">"
 840         print "<td><pre>"
 841 
 842         strip_unchanged /tmp/$$.file1.html
 843 
 844         print "</pre></td><td><pre>"
 845 
 846         strip_unchanged /tmp/$$.file2.html
 847 
 848         print "</pre></td>"
 849         print "</tr></table>"
 850         print "</body></html>"
 851 
 852         framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
 853             "$COMMENT"
 854 }
 855 
 856 
 857 #
 858 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
 859 #
 860 # Expects lefthand and righthand side html files created by sdiff_to_html.
 861 # We use insert_anchors() to augment those with HTML navigation anchors,
 862 # and then emit the main frame.  Content is placed into:
 863 #
 864 #    $WDIR/DIR/$TNAME.lhs.html
 865 #    $WDIR/DIR/$TNAME.rhs.html
 866 #    $WDIR/DIR/$TNAME.frames.html
 867 #
 868 # NOTE: We rely on standard usage of $WDIR and $DIR.
 869 #
 870 function framed_sdiff
 871 {
 872         typeset TNAME=$1
 873         typeset TPATH=$2
 874         typeset lhsfile=$3
 875         typeset rhsfile=$4
 876         typeset comments=$5
 877         typeset RTOP
 878 
 879         # Enable html files to access WDIR via a relative path.
 880         RTOP=$(relative_dir $TPATH $WDIR)
 881 
 882         # Make the rhs/lhs files and output the frameset file.
 883         print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
 884 
 885         cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
 886             <script type="text/javascript" src="${RTOP}ancnav.js"></script>
 887             </head>
 888             <body id="SUNWwebrev" onkeypress="keypress(event);">
 889             <a name="0"></a>
 890             <pre>$comments</pre><hr></hr>
 891         EOF
 892 
 893         cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
 894 
 895         insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
 896         insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
 897 
 898         close='</body></html>'
 899 
 900         print $close >> $WDIR/$DIR/$TNAME.lhs.html
 901         print $close >> $WDIR/$DIR/$TNAME.rhs.html
 902 
 903         print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
 904         print "<title>$WNAME Framed-Sdiff " \
 905             "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
 906         cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
 907           <frameset rows="*,60">
 908             <frameset cols="50%,50%">
 909               <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
 910               <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
 911             </frameset>
 912           <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0"
 913            marginheight="0" name="nav"></frame>
 914           <noframes>
 915             <body id="SUNWwebrev">
 916               Alas 'frames' webrev requires that your browser supports frames
 917               and has the feature enabled.
 918             </body>
 919           </noframes>
 920           </frameset>
 921         </html>
 922         EOF
 923 }
 924 
 925 
 926 #
 927 # fix_postscript
 928 #
 929 # Merge codereview output files to a single conforming postscript file, by:
 930 #       - removing all extraneous headers/trailers
 931 #       - making the page numbers right
 932 #       - removing pages devoid of contents which confuse some
 933 #         postscript readers.
 934 #
 935 # From Casper.
 936 #
 937 function fix_postscript
 938 {
 939         infile=$1
 940 
 941         cat > /tmp/$$.crmerge.pl << \EOF
 942 
 943         print scalar(<>);         # %!PS-Adobe---
 944         print "%%Orientation: Landscape\n";
 945 
 946         $pno = 0;
 947         $doprint = 1;
 948 
 949         $page = "";
 950 
 951         while (<>) {
 952                 next if (/^%%Pages:\s*\d+/);
 953 
 954                 if (/^%%Page:/) {
 955                         if ($pno == 0 || $page =~ /\)S/) {
 956                                 # Header or single page containing text
 957                                 print "%%Page: ? $pno\n" if ($pno > 0);
 958                                 print $page;
 959                                 $pno++;
 960                         } else {
 961                                 # Empty page, skip it.
 962                         }
 963                         $page = "";
 964                         $doprint = 1;
 965                         next;
 966                 }
 967 
 968                 # Skip from %%Trailer of one document to Endprolog
 969                 # %%Page of the next
 970                 $doprint = 0 if (/^%%Trailer/);
 971                 $page .= $_ if ($doprint);
 972         }
 973 
 974         if ($page =~ /\)S/) {
 975                 print "%%Page: ? $pno\n";
 976                 print $page;
 977         } else {
 978                 $pno--;
 979         }
 980         print "%%Trailer\n%%Pages: $pno\n";
 981 EOF
 982 
 983         $PERL /tmp/$$.crmerge.pl < $infile
 984 }
 985 
 986 
 987 #
 988 # input_cmd | insert_anchors | output_cmd
 989 #
 990 # Flag blocks of difference with sequentially numbered invisible
 991 # anchors.  These are used to drive the frames version of the
 992 # sdiffs output.
 993 #
 994 # NOTE: Anchor zero flags the top of the file irrespective of changes,
 995 # an additional anchor is also appended to flag the bottom.
 996 #
 997 # The script detects changed lines as any line that has a "<span
 998 # class=" string embedded (unchanged lines have no class set and are
 999 # not part of a <span>.  Blank lines (without a sequence number)
1000 # are also detected since they flag lines that have been inserted or
1001 # deleted.
1002 #
1003 function insert_anchors
1004 {
1005         $AWK '
1006         function ia() {
1007                 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1008         }
1009 
1010         BEGIN {
1011                 anc=1;
1012                 inblock=1;
1013                 printf "<pre>\n";
1014         }
1015         NF == 0 || /^<span class=/ {
1016                 if (inblock == 0) {
1017                         ia();
1018                         inblock=1;
1019                 }
1020                 print;
1021                 next;
1022         }
1023         {
1024                 inblock=0;
1025                 print;
1026         }
1027         END {
1028                 ia();
1029 
1030                 printf "<b style=\"font-size: large; color: red\">";
1031                 printf "--- EOF ---</b>"
1032                 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1033                 printf "</pre>"
1034                 printf "<form name=\"eof\">";
1035                 printf "<input name=\"value\" value=\"%d\" " \
1036                     "type=\"hidden\"></input>", anc - 1;
1037                 printf "</form>";
1038         }
1039         ' $1
1040 }
1041 
1042 
1043 #
1044 # relative_dir
1045 #
1046 # Print a relative return path from $1 to $2.  For example if
1047 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1048 # this function would print "../../../../".
1049 #
1050 # In the event that $1 is not in $2 a warning is printed to stderr,
1051 # and $2 is returned-- the result of this is that the resulting webrev
1052 # is not relocatable.
1053 #
1054 function relative_dir
1055 {
1056         typeset cur="${1##$2?(/)}"
1057 
1058         #
1059         # If the first path was specified absolutely, and it does
1060         # not start with the second path, it's an error.
1061         #
1062         if [[ "$cur" = "/${1#/}" ]]; then
1063                 # Should never happen.
1064                 print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1065                 print -u2 "to \"$2\".  Check input paths.  Framed webrev "
1066                 print -u2 "will not be relocatable!"
1067                 print $2
1068                 return
1069         fi
1070 
1071         #
1072         # This is kind of ugly.  The sed script will do the following:
1073         #
1074         # 1. Strip off a leading "." or "./": this is important to get
1075         #    the correct arcnav links for files in $WDIR.
1076         # 2. Strip off a trailing "/": this is not strictly necessary,
1077         #    but is kind of nice, since it doesn't end up in "//" at
1078         #    the end of a relative path.
1079         # 3. Replace all remaining sequences of non-"/" with "..": the
1080         #    assumption here is that each dirname represents another
1081         #    level of relative separation.
1082         # 4. Append a trailing "/" only for non-empty paths: this way
1083         #    the caller doesn't need to duplicate this logic, and does
1084         #    not end up using $RTOP/file for files in $WDIR.
1085         #
1086         print $cur | $SED -e '{
1087                 s:^\./*::
1088                 s:/$::
1089                 s:[^/][^/]*:..:g
1090                 s:^\(..*\)$:\1/:
1091         }'
1092 }
1093 
1094 #
1095 # frame_nav_js
1096 #
1097 # Emit javascript for frame navigation
1098 #
1099 function frame_nav_js
1100 {
1101 cat << \EOF
1102 var myInt;
1103 var scrolling=0;
1104 var sfactor = 3;
1105 var scount=10;
1106 
1107 function scrollByPix() {
1108         if (scount<=0) {
1109                 sfactor*=1.2;
1110                 scount=10;
1111         }
1112         parent.lhs.scrollBy(0,sfactor);
1113         parent.rhs.scrollBy(0,sfactor);
1114         scount--;
1115 }
1116 
1117 function scrollToAnc(num) {
1118 
1119         // Update the value of the anchor in the form which we use as
1120         // storage for this value.  setAncValue() will take care of
1121         // correcting for overflow and underflow of the value and return
1122         // us the new value.
1123         num = setAncValue(num);
1124 
1125         // Set location and scroll back a little to expose previous
1126         // lines.
1127         //
1128         // Note that this could be improved: it is possible although
1129         // complex to compute the x and y position of an anchor, and to
1130         // scroll to that location directly.
1131         //
1132         parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1133         parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1134 
1135         parent.lhs.scrollBy(0,-30);
1136         parent.rhs.scrollBy(0,-30);
1137 }
1138 
1139 function getAncValue()
1140 {
1141         return (parseInt(parent.nav.document.diff.real.value));
1142 }
1143 
1144 function setAncValue(val)
1145 {
1146         if (val <= 0) {
1147                 val = 0;
1148                 parent.nav.document.diff.real.value = val;
1149                 parent.nav.document.diff.display.value = "BOF";
1150                 return (val);
1151         }
1152 
1153         //
1154         // The way we compute the max anchor value is to stash it
1155         // inline in the left and right hand side pages-- it's the same
1156         // on each side, so we pluck from the left.
1157         //
1158         maxval = parent.lhs.document.eof.value.value;
1159         if (val < maxval) {
1160                 parent.nav.document.diff.real.value = val;
1161                 parent.nav.document.diff.display.value = val.toString();
1162                 return (val);
1163         }
1164 
1165         // this must be: val >= maxval
1166         val = maxval;
1167         parent.nav.document.diff.real.value = val;
1168         parent.nav.document.diff.display.value = "EOF";
1169         return (val);
1170 }
1171 
1172 function stopScroll() {
1173         if (scrolling==1) {
1174                 clearInterval(myInt);
1175                 scrolling=0;
1176         }
1177 }
1178 
1179 function startScroll() {
1180         stopScroll();
1181         scrolling=1;
1182         myInt=setInterval("scrollByPix()",10);
1183 }
1184 
1185 function handlePress(b) {
1186 
1187         switch (b) {
1188             case 1 :
1189                 scrollToAnc(-1);
1190                 break;
1191             case 2 :
1192                 scrollToAnc(getAncValue() - 1);
1193                 break;
1194             case 3 :
1195                 sfactor=-3;
1196                 startScroll();
1197                 break;
1198             case 4 :
1199                 sfactor=3;
1200                 startScroll();
1201                 break;
1202             case 5 :
1203                 scrollToAnc(getAncValue() + 1);
1204                 break;
1205             case 6 :
1206                 scrollToAnc(999999);
1207                 break;
1208         }
1209 }
1210 
1211 function handleRelease(b) {
1212         stopScroll();
1213 }
1214 
1215 function keypress(ev) {
1216         var keynum;
1217         var keychar;
1218 
1219         if (window.event) { // IE
1220                 keynum = ev.keyCode;
1221         } else if (ev.which) { // non-IE
1222                 keynum = ev.which;
1223         }
1224 
1225         keychar = String.fromCharCode(keynum);
1226 
1227         if (keychar == "k") {
1228                 handlePress(2);
1229                 return (0);
1230         } else if (keychar == "j" || keychar == " ") {
1231                 handlePress(5);
1232                 return (0);
1233         }
1234         return (1);
1235 }
1236 
1237 function ValidateDiffNum(){
1238         val = parent.nav.document.diff.display.value;
1239         if (val == "EOF") {
1240                 scrollToAnc(999999);
1241                 return;
1242         }
1243 
1244         if (val == "BOF") {
1245                 scrollToAnc(0);
1246                 return;
1247         }
1248 
1249         i=parseInt(val);
1250         if (isNaN(i)) {
1251                 parent.nav.document.diff.display.value = getAncValue();
1252         } else {
1253                 scrollToAnc(i);
1254         }
1255         return false;
1256 }
1257 
1258 EOF
1259 }
1260 
1261 #
1262 # frame_navigation
1263 #
1264 # Output anchor navigation file for framed sdiffs.
1265 #
1266 function frame_navigation
1267 {
1268         print "$HTML<head>$STDHEAD"
1269 
1270         cat << \EOF
1271 <title>Anchor Navigation</title>
1272 <meta http-equiv="Content-Script-Type" content="text/javascript">
1273 <meta http-equiv="Content-Type" content="text/html">
1274 
1275 <style type="text/css">
1276     div.button td { padding-left: 5px; padding-right: 5px;
1277                     background-color: #eee; text-align: center;
1278                     border: 1px #444 outset; cursor: pointer; }
1279     div.button a { font-weight: bold; color: black }
1280     div.button td:hover { background: #ffcc99; }
1281 </style>
1282 EOF
1283 
1284         print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1285 
1286         cat << \EOF
1287 </head>
1288 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1289         onkeypress="keypress(event);">
1290     <noscript lang="javascript">
1291       <center>
1292         <p><big>Framed Navigation controls require Javascript</big><br></br>
1293         Either this browser is incompatable or javascript is not enabled</p>
1294       </center>
1295     </noscript>
1296     <table width="100%" border="0" align="center">
1297         <tr>
1298           <td valign="middle" width="25%">Diff navigation:
1299           Use 'j' and 'k' for next and previous diffs; or use buttons
1300           at right</td>
1301           <td align="center" valign="top" width="50%">
1302             <div class="button">
1303               <table border="0" align="center">
1304                   <tr>
1305                     <td>
1306                       <a onMouseDown="handlePress(1);return true;"
1307                          onMouseUp="handleRelease(1);return true;"
1308                          onMouseOut="handleRelease(1);return true;"
1309                          onClick="return false;"
1310                          title="Go to Beginning Of file">BOF</a></td>
1311                     <td>
1312                       <a onMouseDown="handlePress(3);return true;"
1313                          onMouseUp="handleRelease(3);return true;"
1314                          onMouseOut="handleRelease(3);return true;"
1315                          title="Scroll Up: Press and Hold to accelerate"
1316                          onClick="return false;">Scroll Up</a></td>
1317                     <td>
1318                       <a onMouseDown="handlePress(2);return true;"
1319                          onMouseUp="handleRelease(2);return true;"
1320                          onMouseOut="handleRelease(2);return true;"
1321                          title="Go to previous Diff"
1322                          onClick="return false;">Prev Diff</a>
1323                     </td></tr>
1324 
1325                   <tr>
1326                     <td>
1327                       <a onMouseDown="handlePress(6);return true;"
1328                          onMouseUp="handleRelease(6);return true;"
1329                          onMouseOut="handleRelease(6);return true;"
1330                          onClick="return false;"
1331                          title="Go to End Of File">EOF</a></td>
1332                     <td>
1333                       <a onMouseDown="handlePress(4);return true;"
1334                          onMouseUp="handleRelease(4);return true;"
1335                          onMouseOut="handleRelease(4);return true;"
1336                          title="Scroll Down: Press and Hold to accelerate"
1337                          onClick="return false;">Scroll Down</a></td>
1338                     <td>
1339                       <a onMouseDown="handlePress(5);return true;"
1340                          onMouseUp="handleRelease(5);return true;"
1341                          onMouseOut="handleRelease(5);return true;"
1342                          title="Go to next Diff"
1343                          onClick="return false;">Next Diff</a></td>
1344                   </tr>
1345               </table>
1346             </div>
1347           </td>
1348           <th valign="middle" width="25%">
1349             <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1350                 <input name="display" value="BOF" size="8" type="text"></input>
1351                 <input name="real" value="0" size="8" type="hidden"></input>
1352             </form>
1353           </th>
1354         </tr>
1355     </table>
1356   </body>
1357 </html>
1358 EOF
1359 }
1360 
1361 
1362 
1363 #
1364 # diff_to_html <filename> <filepath> { U | C } <comment>
1365 #
1366 # Processes the output of diff to produce an HTML file representing either
1367 # context or unified diffs.
1368 #
1369 diff_to_html()
1370 {
1371         TNAME=$1
1372         TPATH=$2
1373         DIFFTYPE=$3
1374         COMMENT=$4
1375 
1376         print "$HTML<head>$STDHEAD"
1377         print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1378 
1379         if [[ $DIFFTYPE == "U" ]]; then
1380                 print "$UDIFFCSS"
1381         fi
1382 
1383         cat <<-EOF
1384         </head>
1385         <body id="SUNWwebrev">
1386         <a class="print" href="javascript:print()">Print this page</a>
1387         <pre>$COMMENT</pre>
1388         <pre>
1389         EOF
1390 
1391         html_quote | $AWK '
1392         /^--- new/      { next }
1393         /^\+\+\+ new/   { next }
1394         /^--- old/      { next }
1395         /^\*\*\* old/   { next }
1396         /^\*\*\*\*/     { next }
1397         /^-------/      { printf "<center><h1>%s</h1></center>\n", $0; next }
1398         /^\@\@.*\@\@$/  { printf "</pre><hr></hr><pre>\n";
1399                           printf "<span class=\"newmarker\">%s</span>\n", $0;
1400                           next}
1401 
1402         /^\*\*\*/       { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1403                           next}
1404         /^---/          { printf "<span class=\"newmarker\">%s</span>\n", $0;
1405                           next}
1406         /^\+/           {printf "<span class=\"new\">%s</span>\n", $0; next}
1407         /^!/            {printf "<span class=\"changed\">%s</span>\n", $0; next}
1408         /^-/            {printf "<span class=\"removed\">%s</span>\n", $0; next}
1409                         {printf "%s\n", $0; next}
1410         '
1411 
1412         print "</pre></body></html>\n"
1413 }
1414 
1415 
1416 #
1417 # source_to_html { new | old } <filename>
1418 #
1419 # Process a plain vanilla source file to transform it into an HTML file.
1420 #
1421 source_to_html()
1422 {
1423         WHICH=$1
1424         TNAME=$2
1425 
1426         print "$HTML<head>$STDHEAD"
1427         print "<title>$WNAME $WHICH $TNAME</title>"
1428         print "<body id=\"SUNWwebrev\">"
1429         print "<pre>"
1430         html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1431         print "</pre></body></html>"
1432 }
1433 
1434 #
1435 # comments_from_teamware {text|html} parent-file child-file
1436 #
1437 # Find the first delta in the child that's not in the parent.  Get the
1438 # newest delta from the parent, get all deltas from the child starting
1439 # with that delta, and then get all info starting with the second oldest
1440 # delta in that list (the first delta unique to the child).
1441 #
1442 # This code adapted from Bill Shannon's "spc" script
1443 #
1444 comments_from_teamware()
1445 {
1446         fmt=$1
1447         pfile=$PWS/$2
1448         cfile=$CWS/$3
1449 
1450         if [[ ! -f $PWS/${2%/*}/SCCS/s.${2##*/} && -n $RWS ]]; then
1451                 pfile=$RWS/$2
1452         fi
1453 
1454         if [[ -f $pfile ]]; then
1455                 psid=$($SCCS prs -d:I: $pfile 2>/dev/null)
1456         else
1457                 psid=1.1
1458         fi
1459 
1460         set -A sids $($SCCS prs -l -r$psid -d:I: $cfile 2>/dev/null)
1461         N=${#sids[@]}
1462 
1463         nawkprg='
1464                 /^COMMENTS:/    {p=1; continue}
1465                 /^D [0-9]+\.[0-9]+/ {printf "--- %s ---\n", $2; p=0; }
1466                 NF == 0u        { continue }
1467                 {if (p==0) continue; print $0 }'
1468 
1469         if [[ $N -ge 2 ]]; then
1470                 sid1=${sids[$((N-2))]}  # Gets 2nd to last sid
1471 
1472                 if [[ $fmt == "text" ]]; then
1473                         $SCCS prs -l -r$sid1 $cfile  2>/dev/null | \
1474                             $AWK "$nawkprg"
1475                         return
1476                 fi
1477 
1478                 $SCCS prs -l -r$sid1 $cfile  2>/dev/null | \
1479                     html_quote | its2url | $AWK "$nawkprg"
1480         fi
1481 }
1482 
1483 #
1484 # comments_from_wx {text|html} filepath
1485 #
1486 # Given the pathname of a file, find its location in a "wx" active
1487 # file list and print the following comment.  Output is either text or
1488 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1489 # are turned into URLs.
1490 #
1491 # This is also used with Mercurial and the file list provided by hg-active.
1492 #
1493 comments_from_wx()
1494 {
1495         typeset fmt=$1
1496         typeset p=$2
1497 
1498         comm=`$AWK '
1499         $1 == "'$p'" {
1500                 do getline ; while (NF > 0)
1501                 getline
1502                 while (NF > 0) { print ; getline }
1503                 exit
1504         }' < $wxfile`
1505 
1506         if [[ -z $comm ]]; then
1507                 comm="*** NO COMMENTS ***"
1508         fi
1509 
1510         if [[ $fmt == "text" ]]; then
1511                 print -- "$comm"
1512                 return
1513         fi
1514 
1515         print -- "$comm" | html_quote | its2url
1516 
1517 }
1518 
1519 #
1520 # getcomments {text|html} filepath parentpath
1521 #
1522 # Fetch the comments depending on what SCM mode we're in.
1523 #
1524 getcomments()
1525 {
1526         typeset fmt=$1
1527         typeset p=$2
1528         typeset pp=$3
1529 
1530         if [[ -n $Nflag ]]; then
1531                 return
1532         fi
1533         #
1534         # Mercurial support uses a file list in wx format, so this
1535         # will be used there, too
1536         #
1537         if [[ -n $wxfile ]]; then
1538                 comments_from_wx $fmt $p
1539         else
1540                 if [[ $SCM_MODE == "teamware" ]]; then
1541                         comments_from_teamware $fmt $pp $p
1542                 fi
1543         fi
1544 }
1545 
1546 #
1547 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1548 #
1549 # Print out Code Inspection figures similar to sccs-prt(1) format.
1550 #
1551 function printCI
1552 {
1553         integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1554         typeset str
1555         if (( tot == 1 )); then
1556                 str="line"
1557         else
1558                 str="lines"
1559         fi
1560         printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1561             $tot $str $ins $del $mod $unc
1562 }
1563 
1564 
1565 #
1566 # difflines <oldfile> <newfile>
1567 #
1568 # Calculate and emit number of added, removed, modified and unchanged lines,
1569 # and total lines changed, the sum of added + removed + modified.
1570 #
1571 function difflines
1572 {
1573         integer tot mod del ins unc err
1574         typeset filename
1575 
1576         eval $( diff -e $1 $2 | $AWK '
1577         # Change range of lines: N,Nc
1578         /^[0-9]*,[0-9]*c$/ {
1579                 n=split(substr($1,1,length($1)-1), counts, ",");
1580                 if (n != 2) {
1581                     error=2
1582                     exit;
1583                 }
1584                 #
1585                 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1586                 # following would be 5 - 3 = 2! Hence +1 for correction.
1587                 #
1588                 r=(counts[2]-counts[1])+1;
1589 
1590                 #
1591                 # Now count replacement lines: each represents a change instead
1592                 # of a delete, so increment c and decrement r.
1593                 #
1594                 while (getline != /^\.$/) {
1595                         c++;
1596                         r--;
1597                 }
1598                 #
1599                 # If there were more replacement lines than original lines,
1600                 # then r will be negative; in this case there are no deletions,
1601                 # but there are r changes that should be counted as adds, and
1602                 # since r is negative, subtract it from a and add it to c.
1603                 #
1604                 if (r < 0) {
1605                         a-=r;
1606                         c+=r;
1607                 }
1608 
1609                 #
1610                 # If there were more original lines than replacement lines, then
1611                 # r will be positive; in this case, increment d by that much.
1612                 #
1613                 if (r > 0) {
1614                         d+=r;
1615                 }
1616                 next;
1617         }
1618 
1619         # Change lines: Nc
1620         /^[0-9].*c$/ {
1621                 # The first line is a replacement; any more are additions.
1622                 if (getline != /^\.$/) {
1623                         c++;
1624                         while (getline != /^\.$/) a++;
1625                 }
1626                 next;
1627         }
1628 
1629         # Add lines: both Na and N,Na
1630         /^[0-9].*a$/ {
1631                 while (getline != /^\.$/) a++;
1632                 next;
1633         }
1634 
1635         # Delete range of lines: N,Nd
1636         /^[0-9]*,[0-9]*d$/ {
1637                 n=split(substr($1,1,length($1)-1), counts, ",");
1638                 if (n != 2) {
1639                         error=2
1640                         exit;
1641                 }
1642                 #
1643                 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1644                 # following would be 5 - 3 = 2! Hence +1 for correction.
1645                 #
1646                 r=(counts[2]-counts[1])+1;
1647                 d+=r;
1648                 next;
1649         }
1650 
1651         # Delete line: Nd.   For example 10d says line 10 is deleted.
1652         /^[0-9]*d$/ {d++; next}
1653 
1654         # Should not get here!
1655         {
1656                 error=1;
1657                 exit;
1658         }
1659 
1660         # Finish off - print results
1661         END {
1662                 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1663                     (c+d+a), c, d, a, error);
1664         }' )
1665 
1666         # End of $AWK, Check to see if any trouble occurred.
1667         if (( $? > 0 || err > 0 )); then
1668                 print "Unexpected Error occurred reading" \
1669                     "\`diff -e $1 $2\`: \$?=$?, err=" $err
1670                 return
1671         fi
1672 
1673         # Accumulate totals
1674         (( TOTL += tot ))
1675         (( TMOD += mod ))
1676         (( TDEL += del ))
1677         (( TINS += ins ))
1678         # Calculate unchanged lines
1679         unc=`wc -l < $1`
1680         if (( unc > 0 )); then
1681                 (( unc -= del + mod ))
1682                 (( TUNC += unc ))
1683         fi
1684         # print summary
1685         print "<span class=\"lineschanged\">"
1686         printCI $tot $ins $del $mod $unc
1687         print "</span>"
1688 }
1689 
1690 
1691 #
1692 # flist_from_wx
1693 #
1694 # Sets up webrev to source its information from a wx-formatted file.
1695 # Sets the global 'wxfile' variable.
1696 #
1697 function flist_from_wx
1698 {
1699         typeset argfile=$1
1700         if [[ -n ${argfile%%/*} ]]; then
1701                 #
1702                 # If the wx file pathname is relative then make it absolute
1703                 # because the webrev does a "cd" later on.
1704                 #
1705                 wxfile=$PWD/$argfile
1706         else
1707                 wxfile=$argfile
1708         fi
1709 
1710         $AWK '{ c = 1; print;
1711           while (getline) {
1712                 if (NF == 0) { c = -c; continue }
1713                 if (c > 0) print
1714           }
1715         }' $wxfile > $FLIST
1716 
1717         print " Done."
1718 }
1719 
1720 #
1721 # flist_from_teamware [ <args-to-putback-n> ]
1722 #
1723 # Generate the file list by extracting file names from a putback -n.  Some
1724 # names may come from the "update/create" messages and others from the
1725 # "currently checked out" warning.  Renames are detected here too.  Extract
1726 # values for CODEMGR_WS and CODEMGR_PARENT from the output of the putback
1727 # -n as well, but remove them if they are already defined.
1728 #
1729 function flist_from_teamware
1730 {
1731         if [[ -n $codemgr_parent && -z $parent_webrev ]]; then
1732                 if [[ ! -d $codemgr_parent/Codemgr_wsdata ]]; then
1733                         print -u2 "parent $codemgr_parent doesn't look like a" \
1734                             "valid teamware workspace"
1735                         exit 1
1736                 fi
1737                 parent_args="-p $codemgr_parent"
1738         fi
1739 
1740         print " File list from: 'putback -n $parent_args $*' ... \c"
1741 
1742         putback -n $parent_args $* 2>&1 |
1743             $AWK '
1744                 /^update:|^create:/     {print $2}
1745                 /^Parent workspace:/    {printf("CODEMGR_PARENT=%s\n",$3)}
1746                 /^Child workspace:/     {printf("CODEMGR_WS=%s\n",$3)}
1747                 /^The following files are currently checked out/ {p = 1; continue}
1748                 NF == 0                 {p=0 ; continue}
1749                 /^rename/               {old=$3}
1750                 $1 == "to:"             {print $2, old}
1751                 /^"/                    {continue}
1752                 p == 1                  {print $1}' |
1753             sort -r -k 1,1 -u | sort > $FLIST
1754 
1755         print " Done."
1756 }
1757 
1758 #
1759 # Call hg-active to get the active list output in the wx active list format
1760 #
1761 function hg_active_wxfile
1762 {
1763         typeset child=$1
1764         typeset parent=$2
1765 
1766         TMPFLIST=/tmp/$$.active
1767         $HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1768         wxfile=$TMPFLIST
1769 }
1770 
1771 #
1772 # flist_from_mercurial
1773 # Call hg-active to get a wx-style active list, and hand it off to
1774 # flist_from_wx
1775 #
1776 function flist_from_mercurial
1777 {
1778         typeset child=$1
1779         typeset parent=$2
1780 
1781         print " File list from: hg-active -p $parent ...\c"
1782         if [[ ! -x $HG_ACTIVE ]]; then
1783                 print           # Blank line for the \c above
1784                 print -u2 "Error: hg-active tool not found.  Exiting"
1785                 exit 1
1786         fi
1787         hg_active_wxfile $child $parent
1788 
1789         # flist_from_wx prints the Done, so we don't have to.
1790         flist_from_wx $TMPFLIST
1791 }
1792 
1793 #
1794 # Transform a specified 'git log' output format into a wx-like active list.
1795 #
1796 function git_wxfile
1797 {
1798         typeset child="$1"
1799         typeset parent="$2"
1800 
1801         TMPFLIST=/tmp/$$.active
1802         $PERL -e 'my (%files, %realfiles, $msg);
1803         my $branch = $ARGV[0];
1804          
1805         open(F, "git diff -M --name-status $branch |");
1806         while (<F>) {
1807             chomp;
1808             if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
1809                 if ($1 >= 75) {                       # Probably worth treating as a rename
1810                     $realfiles{$3} = $2;
1811                 } else {
1812                     $realfiles{$3} = $3;
1813                     $realfiles{$2} = $2;
1814                 }
1815             } else {
1816                 my $f = (split /\s+/, $_)[1];
1817                 $realfiles{$f} = $f;
1818             }
1819         }
1820         close(F);
1821          
1822         my $state = 1;              # 0|comments, 1|files
1823         open(F, "git whatchanged --pretty=format:%B $branch.. |");
1824         while (<F>) {
1825             chomp;
1826             if (/^:[0-9]{6}/) {
1827                 my $fname = (split /\t/, $_)[1];
1828                 next if !defined($realfiles{$fname}); # No real change
1829                 $state = 1;
1830                 chomp $msg;
1831                 $files{$fname} .= $msg;
1832             } else {
1833                 if ($state == 1) {
1834                     $state = 0;
1835                     $msg = /^\n/ ? "" : "\n";
1836                 }
1837                 $msg .= "$_\n" if ($_);
1838             }
1839         }
1840         close(F);
1841          
1842         for (sort keys %files) {
1843             if ($realfiles{$_} ne $_) {
1844                 print "$_ $realfiles{$_}\n$files{$_}\n\n";
1845             } else {
1846                 print "$_\n$files{$_}\n\n"
1847             }
1848         }' ${parent} > $TMPFLIST
1849 
1850         wxfile=$TMPFLIST
1851 }
1852 
1853 #
1854 # flist_from_git
1855 # Build a wx-style active list, and hand it off to flist_from_wx
1856 #
1857 function flist_from_git
1858 {
1859         typeset child=$1
1860         typeset parent=$2
1861 
1862         print " File list from: git ...\c"
1863         git_wxfile "$child" "$parent";
1864 
1865         # flist_from_wx prints the Done, so we don't have to.
1866         flist_from_wx $TMPFLIST
1867 }
1868 
1869 #
1870 # flist_from_subversion
1871 #
1872 # Generate the file list by extracting file names from svn status.
1873 #
1874 function flist_from_subversion
1875 {
1876         CWS=$1
1877         OLDPWD=$2
1878 
1879         cd $CWS
1880         print -u2 " File list from: svn status ... \c"
1881         svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1882         print -u2 " Done."
1883         cd $OLDPWD
1884 }
1885 
1886 function env_from_flist
1887 {
1888         [[ -r $FLIST ]] || return
1889 
1890         #
1891         # Use "eval" to set env variables that are listed in the file
1892         # list.  Then copy those into our local versions of those
1893         # variables if they have not been set already.
1894         #
1895         eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1896 
1897         if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1898                 codemgr_ws=$CODEMGR_WS
1899                 export CODEMGR_WS
1900         fi
1901 
1902         #
1903         # Check to see if CODEMGR_PARENT is set in the flist file.
1904         #
1905         if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1906                 codemgr_parent=$CODEMGR_PARENT
1907                 export CODEMGR_PARENT
1908         fi
1909 }
1910 
1911 function look_for_prog
1912 {
1913         typeset path
1914         typeset ppath
1915         typeset progname=$1
1916 
1917         ppath=$PATH
1918         ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1919         ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin
1920         ppath=$ppath:/opt/onbld/bin/`uname -p`
1921 
1922         PATH=$ppath prog=`whence $progname`
1923         if [[ -n $prog ]]; then
1924                 print $prog
1925         fi
1926 }
1927 
1928 function get_file_mode
1929 {
1930         $PERL -e '
1931                 if (@stat = stat($ARGV[0])) {
1932                         $mode = $stat[2] & 0777;
1933                         printf "%03o\n", $mode;
1934                         exit 0;
1935                 } else {
1936                         exit 1;
1937                 }
1938             ' $1
1939 }
1940 
1941 function build_old_new_teamware
1942 {
1943         typeset olddir="$1"
1944         typeset newdir="$2"
1945 
1946         # If the child's version doesn't exist then
1947         # get a readonly copy.
1948 
1949         if [[ ! -f $CWS/$DIR/$F && -f $CWS/$DIR/SCCS/s.$F ]]; then
1950                 $SCCS get -s -p $CWS/$DIR/$F > $CWS/$DIR/$F
1951         fi
1952 
1953         # The following two sections propagate file permissions the
1954         # same way SCCS does.  If the file is already under version
1955         # control, always use permissions from the SCCS/s.file.  If
1956         # the file is not under SCCS control, use permissions from the
1957         # working copy.  In all cases, the file copied to the webrev
1958         # is set to read only, and group/other permissions are set to
1959         # match those of the file owner.  This way, even if the file
1960         # is currently checked out, the webrev will display the final
1961         # permissions that would result after check in.
1962 
1963         #
1964         # Snag new version of file.
1965         #
1966         rm -f $newdir/$DIR/$F
1967         cp $CWS/$DIR/$F $newdir/$DIR/$F
1968         if [[ -f $CWS/$DIR/SCCS/s.$F ]]; then
1969                 chmod `get_file_mode $CWS/$DIR/SCCS/s.$F` \
1970                     $newdir/$DIR/$F
1971         fi
1972         chmod u-w,go=u $newdir/$DIR/$F
1973 
1974         #
1975         # Get the parent's version of the file. First see whether the
1976         # child's version is checked out and get the parent's version
1977         # with keywords expanded or unexpanded as appropriate.
1978         #
1979         if [[ -f $PWS/$PDIR/$PF && ! -f $PWS/$PDIR/SCCS/s.$PF && \
1980             ! -f $PWS/$PDIR/SCCS/p.$PF ]]; then
1981                 # Parent is not a real workspace, but just a raw
1982                 # directory tree - use the file that's there as
1983                 # the old file.
1984 
1985                 rm -f $olddir/$PDIR/$PF
1986                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1987         else
1988                 if [[ -f $PWS/$PDIR/SCCS/s.$PF ]]; then
1989                         real_parent=$PWS
1990                 else
1991                         real_parent=$RWS
1992                 fi
1993 
1994                 rm -f $olddir/$PDIR/$PF
1995 
1996                 if [[ -f $real_parent/$PDIR/$PF ]]; then
1997                         if [ -f $CWS/$DIR/SCCS/p.$F ]; then
1998                                 $SCCS get -s -p -k $real_parent/$PDIR/$PF > \
1999                                     $olddir/$PDIR/$PF
2000                         else
2001                                 $SCCS get -s -p    $real_parent/$PDIR/$PF > \
2002                                     $olddir/$PDIR/$PF
2003                         fi
2004                         chmod `get_file_mode $real_parent/$PDIR/SCCS/s.$PF` \
2005                             $olddir/$PDIR/$PF
2006                 fi
2007         fi
2008         if [[ -f $olddir/$PDIR/$PF ]]; then
2009                 chmod u-w,go=u $olddir/$PDIR/$PF
2010         fi
2011 }
2012 
2013 function build_old_new_mercurial
2014 {
2015         typeset olddir="$1"
2016         typeset newdir="$2"
2017         typeset old_mode=
2018         typeset new_mode=
2019         typeset file
2020 
2021         #
2022         # Get old file mode, from the parent revision manifest entry.
2023         # Mercurial only stores a "file is executable" flag, but the
2024         # manifest will display an octal mode "644" or "755".
2025         #
2026         if [[ "$PDIR" == "." ]]; then
2027                 file="$PF"
2028         else
2029                 file="$PDIR/$PF"
2030         fi
2031         file=`echo $file | $SED 's#/#\\\/#g'`
2032         # match the exact filename, and return only the permission digits
2033         old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
2034             < $HG_PARENT_MANIFEST`
2035 
2036         #
2037         # Get new file mode, directly from the filesystem.
2038         # Normalize the mode to match Mercurial's behavior.
2039         #
2040         new_mode=`get_file_mode $CWS/$DIR/$F`
2041         if [[ -n "$new_mode" ]]; then
2042                 if [[ "$new_mode" = *[1357]* ]]; then
2043                         new_mode=755
2044                 else
2045                         new_mode=644
2046                 fi
2047         fi
2048 
2049         #
2050         # new version of the file.
2051         #
2052         rm -rf $newdir/$DIR/$F
2053         if [[ -e $CWS/$DIR/$F ]]; then
2054                 cp $CWS/$DIR/$F $newdir/$DIR/$F
2055                 if [[ -n $new_mode ]]; then
2056                         chmod $new_mode $newdir/$DIR/$F
2057                 else
2058                         # should never happen
2059                         print -u2 "ERROR: set mode of $newdir/$DIR/$F"
2060                 fi
2061         fi
2062 
2063         #
2064         # parent's version of the file
2065         #
2066         # Note that we get this from the last version common to both
2067         # ourselves and the parent.  References are via $CWS since we have no
2068         # guarantee that the parent workspace is reachable via the filesystem.
2069         #
2070         if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2071                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2072         elif [[ -n $HG_PARENT ]]; then
2073                 hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
2074                     $olddir/$PDIR/$PF 2>/dev/null
2075 
2076                 if (( $? != 0 )); then
2077                         rm -f $olddir/$PDIR/$PF
2078                 else
2079                         if [[ -n $old_mode ]]; then
2080                                 chmod $old_mode $olddir/$PDIR/$PF
2081                         else
2082                                 # should never happen
2083                                 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
2084                         fi
2085                 fi
2086         fi
2087 }
2088 
2089 function build_old_new_git
2090 {
2091         typeset olddir="$1"
2092         typeset newdir="$2"
2093         typeset o_mode=
2094         typeset n_mode=
2095         typeset o_object=
2096         typeset n_object=
2097         typeset OWD=$PWD
2098         typeset file
2099         typeset type
2100 
2101         cd $CWS
2102 
2103         #
2104         # Get old file and its mode from the git object tree
2105         #
2106         if [[ "$PDIR" == "." ]]; then
2107                 file="$PF"
2108         else
2109                file="$PDIR/$PF"
2110         fi
2111 
2112         if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2113                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2114         else
2115                 $GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk
2116                 $GIT cat-file $type $o_object > $olddir/$file 2>/dev/null
2117                  
2118                 if (( $? != 0 )); then
2119                         rm -f $olddir/$file
2120                 elif [[ -n $o_mode ]]; then
2121                         # Strip the first 3 digits, to get a regular octal mode
2122                         o_mode=${o_mode/???/}
2123                         chmod $o_mode $olddir/$file
2124                 else
2125                         # should never happen
2126                         print -u2 "ERROR: set mode of $olddir/$file"
2127                 fi
2128         fi
2129 
2130         #
2131         # new version of the file.
2132         #
2133         if [[ "$DIR" == "." ]]; then
2134                 file="$F"
2135         else
2136                 file="$DIR/$F"
2137         fi
2138         rm -rf $newdir/$file
2139 
2140         if [[ -e $CWS/$DIR/$F ]]; then
2141             cp $CWS/$DIR/$F $newdir/$DIR/$F
2142             chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F
2143         fi
2144         cd $OWD
2145 }
2146 
2147 function build_old_new_subversion
2148 {
2149         typeset olddir="$1"
2150         typeset newdir="$2"
2151 
2152         # Snag new version of file.
2153         rm -f $newdir/$DIR/$F
2154         [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2155 
2156         if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2157                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2158         else
2159                 # Get the parent's version of the file.
2160                 svn status $CWS/$DIR/$F | read stat file
2161                 if [[ $stat != "A" ]]; then
2162                         svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2163                 fi
2164         fi
2165 }
2166 
2167 function build_old_new_unknown
2168 {
2169         typeset olddir="$1"
2170         typeset newdir="$2"
2171 
2172         #
2173         # Snag new version of file.
2174         #
2175         rm -f $newdir/$DIR/$F
2176         [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2177 
2178         #
2179         # Snag the parent's version of the file.
2180         #
2181         if [[ -f $PWS/$PDIR/$PF ]]; then
2182                 rm -f $olddir/$PDIR/$PF
2183                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2184         fi
2185 }
2186 
2187 function build_old_new
2188 {
2189         typeset WDIR=$1
2190         typeset PWS=$2
2191         typeset PDIR=$3
2192         typeset PF=$4
2193         typeset CWS=$5
2194         typeset DIR=$6
2195         typeset F=$7
2196 
2197         typeset olddir="$WDIR/raw_files/old"
2198         typeset newdir="$WDIR/raw_files/new"
2199 
2200         mkdir -p $olddir/$PDIR
2201         mkdir -p $newdir/$DIR
2202 
2203         if [[ $SCM_MODE == "teamware" ]]; then
2204                 build_old_new_teamware "$olddir" "$newdir"
2205         elif [[ $SCM_MODE == "mercurial" ]]; then
2206                 build_old_new_mercurial "$olddir" "$newdir"
2207         elif [[ $SCM_MODE == "git" ]]; then
2208                 build_old_new_git "$olddir" "$newdir"
2209         elif [[ $SCM_MODE == "subversion" ]]; then
2210                 build_old_new_subversion "$olddir" "$newdir"
2211         elif [[ $SCM_MODE == "unknown" ]]; then
2212                 build_old_new_unknown "$olddir" "$newdir"
2213         fi
2214 
2215         if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2216                 print "*** Error: file not in parent or child"
2217                 return 1
2218         fi
2219         return 0
2220 }
2221 
2222 
2223 #
2224 # Usage message.
2225 #
2226 function usage
2227 {
2228         print 'Usage:\twebrev [common-options]
2229         webrev [common-options] ( <file> | - )
2230         webrev [common-options] -w <wx file>
2231 
2232 Options:
2233         -C <filename>: Use <filename> for the information tracking configuration.
2234         -D: delete remote webrev
2235         -i <filename>: Include <filename> in the index.html file.
2236         -I <filename>: Use <filename> for the information tracking registry.
2237         -n: do not generate the webrev (useful with -U)
2238         -O: Print bugids/arc cases suitable for OpenSolaris.
2239         -o <outdir>: Output webrev to specified directory.
2240         -p <compare-against>: Use specified parent wkspc or basis for comparison
2241         -t <remote_target>: Specify remote destination for webrev upload
2242         -U: upload the webrev to remote destination
2243         -w <wxfile>: Use specified wx active file.
2244 
2245 Environment:
2246         WDIR: Control the output directory.
2247         WEBREV_TRASH_DIR: Set directory for webrev delete.
2248 
2249 SCM Specific Options:
2250         TeamWare: webrev [common-options] -l [arguments to 'putback']
2251 
2252 SCM Environment:
2253         CODEMGR_WS: Workspace location.
2254         CODEMGR_PARENT: Parent workspace location.
2255 '
2256 
2257         exit 2
2258 }
2259 
2260 #
2261 #
2262 # Main program starts here
2263 #
2264 #
2265 
2266 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2267 
2268 set +o noclobber
2269 
2270 PATH=$(/bin/dirname "$(whence $0)"):$PATH
2271 
2272 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2273 [[ -z $WX ]] && WX=`look_for_prog wx`
2274 [[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
2275 [[ -z $GIT ]] && GIT=`look_for_prog git`
2276 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2277 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2278 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2279 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2280 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2281 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2282 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2283 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2284 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2285 [[ -z $SCP ]] && SCP=`look_for_prog scp`
2286 [[ -z $SED ]] && SED=`look_for_prog sed`
2287 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2288 [[ -z $SORT ]] && SORT=`look_for_prog sort`
2289 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2290 [[ -z $GREP ]] && GREP=`look_for_prog grep`
2291 [[ -z $FIND ]] && FIND=`look_for_prog find`
2292 
2293 # set name of trash directory for remote webrev deletion
2294 TRASH_DIR=".trash"
2295 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2296 
2297 if [[ ! -x $PERL ]]; then
2298         print -u2 "Error: No perl interpreter found.  Exiting."
2299         exit 1
2300 fi
2301 
2302 if [[ ! -x $WHICH_SCM ]]; then
2303         print -u2 "Error: Could not find which_scm.  Exiting."
2304         exit 1
2305 fi
2306 
2307 #
2308 # These aren't fatal, but we want to note them to the user.
2309 # We don't warn on the absence of 'wx' until later when we've
2310 # determined that we actually need to try to invoke it.
2311 #
2312 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2313 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2314 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2315 
2316 # Declare global total counters.
2317 integer TOTL TINS TDEL TMOD TUNC
2318 
2319 # default remote host for upload/delete
2320 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2321 # prefixes for upload targets
2322 typeset -r rsync_prefix="rsync://"
2323 typeset -r ssh_prefix="ssh://"
2324 
2325 Cflag=
2326 Dflag=
2327 flist_mode=
2328 flist_file=
2329 iflag=
2330 Iflag=
2331 lflag=
2332 Nflag=
2333 nflag=
2334 Oflag=
2335 oflag=
2336 pflag=
2337 tflag=
2338 uflag=
2339 Uflag=
2340 wflag=
2341 remote_target=
2342 
2343 #
2344 # NOTE: when adding/removing options it is necessary to sync the list
2345 #       with usr/src/tools/onbld/hgext/cdm.py
2346 #
2347 while getopts "C:Di:I:lnNo:Op:t:Uw" opt
2348 do
2349         case $opt in
2350         C)      Cflag=1
2351                 ITSCONF=$OPTARG;;
2352 
2353         D)      Dflag=1;;
2354 
2355         i)      iflag=1
2356                 INCLUDE_FILE=$OPTARG;;
2357 
2358         I)      Iflag=1
2359                 ITSREG=$OPTARG;;
2360 
2361         #
2362         # If -l has been specified, we need to abort further options
2363         # processing, because subsequent arguments are going to be
2364         # arguments to 'putback -n'.
2365         #
2366         l)      lflag=1
2367                 break;;
2368 
2369         N)      Nflag=1;;
2370 
2371         n)      nflag=1;;
2372 
2373         O)      Oflag=1;;
2374 
2375         o)      oflag=1
2376                 # Strip the trailing slash to correctly form remote target.
2377                 WDIR=${OPTARG%/};;
2378 
2379         p)      pflag=1
2380                 codemgr_parent=$OPTARG;;
2381 
2382         t)      tflag=1
2383                 remote_target=$OPTARG;;
2384 
2385         U)      Uflag=1;;
2386 
2387         w)      wflag=1;;
2388 
2389         ?)      usage;;
2390         esac
2391 done
2392 
2393 FLIST=/tmp/$$.flist
2394 
2395 if [[ -n $wflag && -n $lflag ]]; then
2396         usage
2397 fi
2398 
2399 # more sanity checking
2400 if [[ -n $nflag && -z $Uflag ]]; then
2401         print "it does not make sense to skip webrev generation" \
2402             "without -U"
2403         exit 1
2404 fi
2405 
2406 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2407         echo "remote target has to be used only for upload or delete"
2408         exit 1
2409 fi
2410 
2411 #
2412 # For the invocation "webrev -n -U" with no other options, webrev will assume
2413 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2414 # $(basename ${CWS}).  So we need to get CWS set before we skip any remaining
2415 # logic.
2416 #
2417 $WHICH_SCM | read SCM_MODE junk || exit 1
2418 if [[ $SCM_MODE == "teamware" ]]; then
2419         #
2420         # Teamware priorities:
2421         # 1. CODEMGR_WS from the environment
2422         # 2. workspace name
2423         #
2424         [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
2425         if [[ -n $codemgr_ws && ! -d $codemgr_ws ]]; then
2426                 print -u2 "$codemgr_ws: no such workspace"
2427                 exit 1
2428         fi
2429         [[ -z $codemgr_ws ]] && codemgr_ws=$(workspace name)
2430         codemgr_ws=$(cd $codemgr_ws;print $PWD)
2431         CODEMGR_WS=$codemgr_ws
2432         CWS=$codemgr_ws
2433 elif [[ $SCM_MODE == "mercurial" ]]; then
2434         #
2435         # Mercurial priorities:
2436         # 1. hg root from CODEMGR_WS environment variable
2437         # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under
2438         #    usr/closed when we run webrev
2439         # 2. hg root from directory of invocation
2440         #
2441         if [[ ${PWD} =~ "usr/closed" ]]; then
2442                 testparent=${CODEMGR_WS}/usr/closed
2443                 # If we're in OpenSolaris mode, we enforce a minor policy:
2444                 # help to make sure the reviewer doesn't accidentally publish
2445                 # source which is under usr/closed
2446                 if [[ -n "$Oflag" ]]; then
2447                         print -u2 "OpenSolaris output not permitted with" \
2448                             "usr/closed changes"
2449                         exit 1
2450                 fi
2451         else
2452                 testparent=${CODEMGR_WS}
2453         fi
2454         [[ -z $codemgr_ws && -n $testparent ]] && \
2455             codemgr_ws=$(hg root -R $testparent 2>/dev/null)
2456         [[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null)
2457         CWS=$codemgr_ws
2458 elif [[ $SCM_MODE == "git" ]]; then
2459         #
2460         # Git priorities:
2461         # 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2462         # 2. git rev-parse --git-dir from directory of invocation
2463         #
2464         [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2465             codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \
2466                 2>/dev/null)
2467         [[ -z $codemgr_ws ]] && \
2468             codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null)
2469 
2470         if [[ "$codemgr_ws" == ".git" ]]; then
2471                 codemgr_ws="${PWD}/${codemgr_ws}"
2472         fi
2473 
2474         codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git'
2475         CWS="$codemgr_ws"
2476 elif [[ $SCM_MODE == "subversion" ]]; then
2477         #
2478         # Subversion priorities:
2479         # 1. CODEMGR_WS from environment
2480         # 2. Relative path from current directory to SVN repository root
2481         #
2482         if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2483                 CWS=$CODEMGR_WS
2484         else
2485                 svn info | while read line; do
2486                         if [[ $line == "URL: "* ]]; then
2487                                 url=${line#URL: }
2488                         elif [[ $line == "Repository Root: "* ]]; then
2489                                 repo=${line#Repository Root: }
2490                         fi
2491                 done
2492 
2493                 rel=${url#$repo}
2494                 CWS=${PWD%$rel}
2495         fi
2496 fi
2497 
2498 #
2499 # If no SCM has been determined, take either the environment setting
2500 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2501 #
2502 if [[ -z ${CWS} ]]; then
2503         CWS=${CODEMGR_WS:-.}
2504 fi
2505 
2506 #
2507 # If the command line options indicate no webrev generation, either
2508 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2509 # ton of logic we can skip.
2510 #
2511 # Instead of increasing indentation, we intentionally leave this loop
2512 # body open here, and exit via break from multiple points within.
2513 # Search for DO_EVERYTHING below to find the break points and closure.
2514 #
2515 for do_everything in 1; do
2516 
2517 # DO_EVERYTHING: break point
2518 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2519         break
2520 fi
2521 
2522 #
2523 # If this manually set as the parent, and it appears to be an earlier webrev,
2524 # then note that fact and set the parent to the raw_files/new subdirectory.
2525 #
2526 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2527         parent_webrev=$(readlink -f "$codemgr_parent")
2528         codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new")
2529 fi
2530 
2531 if [[ -z $wflag && -z $lflag ]]; then
2532         shift $(($OPTIND - 1))
2533 
2534         if [[ $1 == "-" ]]; then
2535                 cat > $FLIST
2536                 flist_mode="stdin"
2537                 flist_done=1
2538                 shift
2539         elif [[ -n $1 ]]; then
2540                 if [[ ! -r $1 ]]; then
2541                         print -u2 "$1: no such file or not readable"
2542                         usage
2543                 fi
2544                 cat $1 > $FLIST
2545                 flist_mode="file"
2546                 flist_file=$1
2547                 flist_done=1
2548                 shift
2549         else
2550                 flist_mode="auto"
2551         fi
2552 fi
2553 
2554 #
2555 # Before we go on to further consider -l and -w, work out which SCM we think
2556 # is in use.
2557 #
2558 case "$SCM_MODE" in
2559 teamware|mercurial|git|subversion)
2560         ;;
2561 unknown)
2562         if [[ $flist_mode == "auto" ]]; then
2563                 print -u2 "Unable to determine SCM in use and file list not specified"
2564                 print -u2 "See which_scm(1) for SCM detection information."
2565                 exit 1
2566         fi
2567         ;;
2568 *)
2569         if [[ $flist_mode == "auto" ]]; then
2570                 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2571                 exit 1
2572         fi
2573         ;;
2574 esac
2575 
2576 print -u2 "   SCM detected: $SCM_MODE"
2577 
2578 if [[ -n $lflag ]]; then
2579         #
2580         # If the -l flag is given instead of the name of a file list,
2581         # then generate the file list by extracting file names from a
2582         # putback -n.
2583         #
2584         shift $(($OPTIND - 1))
2585         if [[ $SCM_MODE == "teamware" ]]; then
2586                 flist_from_teamware "$*"
2587         else
2588                 print -u2 -- "Error: -l option only applies to TeamWare"
2589                 exit 1
2590         fi
2591         flist_done=1
2592         shift $#
2593 elif [[ -n $wflag ]]; then
2594         #
2595         # If the -w is given then assume the file list is in Bonwick's "wx"
2596         # command format, i.e.  pathname lines alternating with SCCS comment
2597         # lines with blank lines as separators.  Use the SCCS comments later
2598         # in building the index.html file.
2599         #
2600         shift $(($OPTIND - 1))
2601         wxfile=$1
2602         if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2603                 if [[ -r $CODEMGR_WS/wx/active ]]; then
2604                         wxfile=$CODEMGR_WS/wx/active
2605                 fi
2606         fi
2607 
2608         [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2609             "be auto-detected (check \$CODEMGR_WS)" && exit 1
2610 
2611         if [[ ! -r $wxfile ]]; then
2612                 print -u2 "$wxfile: no such file or not readable"
2613                 usage
2614         fi
2615 
2616         print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2617         flist_from_wx $wxfile
2618         flist_done=1
2619         if [[ -n "$*" ]]; then
2620                 shift
2621         fi
2622 elif [[ $flist_mode == "stdin" ]]; then
2623         print -u2 " File list from: standard input"
2624 elif [[ $flist_mode == "file" ]]; then
2625         print -u2 " File list from: $flist_file"
2626 fi
2627 
2628 if [[ $# -gt 0 ]]; then
2629         print -u2 "WARNING: unused arguments: $*"
2630 fi
2631 
2632 #
2633 # Before we entered the DO_EVERYTHING loop, we should have already set CWS
2634 # and CODEMGR_WS as needed.  Here, we set the parent workspace.
2635 #
2636 
2637 if [[ $SCM_MODE == "teamware" ]]; then
2638 
2639         #
2640         # Teamware priorities:
2641         #
2642         #      1) via -p command line option
2643         #      2) in the user environment
2644         #      3) in the flist
2645         #      4) automatically based on the workspace
2646         #
2647 
2648         #
2649         # For 1, codemgr_parent will already be set.  Here's 2:
2650         #
2651         [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
2652             codemgr_parent=$CODEMGR_PARENT
2653         if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then
2654                 print -u2 "$codemgr_parent: no such directory"
2655                 exit 1
2656         fi
2657 
2658         #
2659         # If we're in auto-detect mode and we haven't already gotten the file
2660         # list, then see if we can get it by probing for wx.
2661         #
2662         if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then
2663                 if [[ ! -x $WX ]]; then
2664                         print -u2 "WARNING: wx not found!"
2665                 fi
2666 
2667                 #
2668                 # We need to use wx list -w so that we get renamed files, etc.
2669                 # but only if a wx active file exists-- otherwise wx will
2670                 # hang asking us to initialize our wx information.
2671                 #
2672                 if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then
2673                         print -u2 " File list from: 'wx list -w' ... \c"
2674                         $WX list -w > $FLIST
2675                         $WX comments > /tmp/$$.wx_comments
2676                         wxfile=/tmp/$$.wx_comments
2677                         print -u2 "done"
2678                         flist_done=1
2679                 fi
2680         fi
2681 
2682         #
2683         # If by hook or by crook we've gotten a file list by now (perhaps
2684         # from the command line), eval it to extract environment variables from
2685         # it: This is method 3 for finding the parent.
2686         #
2687         if [[ -z $flist_done ]]; then
2688                 flist_from_teamware
2689         fi
2690         env_from_flist
2691 
2692         #
2693         # (4) If we still don't have a value for codemgr_parent, get it
2694         # from workspace.
2695         #
2696         [[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent`
2697         if [[ ! -d $codemgr_parent ]]; then
2698                 print -u2 "$CODEMGR_PARENT: no such parent workspace"
2699                 exit 1
2700         fi
2701 
2702         PWS=$codemgr_parent
2703 
2704         [[ -n $parent_webrev ]] && RWS=$(workspace parent $CWS)
2705 
2706 elif [[ $SCM_MODE == "mercurial" ]]; then
2707         #
2708         # Parent can either be specified with -p
2709         # Specified with CODEMGR_PARENT in the environment
2710         # or taken from hg's default path.
2711         #
2712 
2713         if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2714                 codemgr_parent=$CODEMGR_PARENT
2715         fi
2716 
2717         if [[ -z $codemgr_parent ]]; then
2718                 codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
2719         fi
2720 
2721         PWS=$codemgr_parent
2722 
2723         #
2724         # If the parent is a webrev, we want to do some things against
2725         # the natural workspace parent (file list, comments, etc)
2726         #
2727         if [[ -n $parent_webrev ]]; then
2728                 real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
2729         else
2730                 real_parent=$PWS
2731         fi
2732 
2733         #
2734         # If hg-active exists, then we run it.  In the case of no explicit
2735         # flist given, we'll use it for our comments.  In the case of an
2736         # explicit flist given we'll try to use it for comments for any
2737         # files mentioned in the flist.
2738         #
2739         if [[ -z $flist_done ]]; then
2740                 flist_from_mercurial $CWS $real_parent
2741                 flist_done=1
2742         fi
2743 
2744         #
2745         # If we have a file list now, pull out any variables set
2746         # therein.  We do this now (rather than when we possibly use
2747         # hg-active to find comments) to avoid stomping specifications
2748         # in the user-specified flist.
2749         #
2750         if [[ -n $flist_done ]]; then
2751                 env_from_flist
2752         fi
2753 
2754         #
2755         # Only call hg-active if we don't have a wx formatted file already
2756         #
2757         if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2758                 print "  Comments from: hg-active -p $real_parent ...\c"
2759                 hg_active_wxfile $CWS $real_parent
2760                 print " Done."
2761         fi
2762 
2763         #
2764         # At this point we must have a wx flist either from hg-active,
2765         # or in general.  Use it to try and find our parent revision,
2766         # if we don't have one.
2767         #
2768         if [[ -z $HG_PARENT ]]; then
2769                 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2770         fi
2771 
2772         #
2773         # If we still don't have a parent, we must have been given a
2774         # wx-style active list with no HG_PARENT specification, run
2775         # hg-active and pull an HG_PARENT out of it, ignore the rest.
2776         #
2777         if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2778                 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2779                     eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2780         elif [[ -z $HG_PARENT ]]; then
2781                 print -u2 "Error: Cannot discover parent revision"
2782                 exit 1
2783         fi
2784 
2785         pnode=$(trim_digest $HG_PARENT)
2786         PRETTY_PWS="${PWS} (at ${pnode})"
2787         cnode=$(hg parent -R $codemgr_ws --template '{node|short}' \
2788             2>/dev/null)
2789         PRETTY_CWS="${CWS} (at ${cnode})"}
2790 elif [[ $SCM_MODE == "git" ]]; then
2791         #
2792         # Parent can either be specified with -p, or specified with
2793         # CODEMGR_PARENT in the environment.
2794         #
2795 
2796         if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2797                 codemgr_parent=$CODEMGR_PARENT
2798         fi
2799 
2800         # Try to figure out the parent based on the branch the current
2801         # branch is tracking, if we fail, use origin/master
2802         this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2803         par_branch="origin/master"
2804 
2805         # If we're not on a branch there's nothing we can do
2806         if [[ $this_branch != "(no branch)" ]]; then
2807                 $GIT for-each-ref                                                 \
2808                     --format='%(refname:short) %(upstream:short)' refs/heads/ |   \
2809                     while read local remote; do                                   \
2810                         [[ "$local" == "$this_branch" ]] && par_branch="$remote"; \
2811                     done
2812         fi
2813 
2814         if [[ -z $codemgr_parent ]]; then
2815                 codemgr_parent=$par_branch
2816         fi
2817         PWS=$codemgr_parent
2818 
2819         #
2820         # If the parent is a webrev, we want to do some things against
2821         # the natural workspace parent (file list, comments, etc)
2822         #
2823         if [[ -n $parent_webrev ]]; then
2824                 real_parent=$par_branch
2825         else
2826                 real_parent=$PWS
2827         fi
2828 
2829         if [[ -z $flist_done ]]; then
2830                 flist_from_git "$CWS" "$real_parent"
2831                 flist_done=1
2832         fi
2833 
2834         #
2835         # If we have a file list now, pull out any variables set
2836         # therein.
2837         #
2838         if [[ -n $flist_done ]]; then
2839                 env_from_flist
2840         fi
2841 
2842         #
2843         # If we don't have a wx-format file list, build one we can pull change
2844         # comments from.
2845         #
2846         if [[ -z $wxfile ]]; then
2847                 print "  Comments from: git...\c"
2848                 git_wxfile "$CWS" "$real_parent"
2849                 print " Done."
2850         fi
2851 
2852         if [[ -z $GIT_PARENT ]]; then
2853                 GIT_PARENT=$($GIT merge-base "$real_parent" HEAD)
2854         fi
2855         if [[ -z $GIT_PARENT ]]; then
2856                 print -u2 "Error: Cannot discover parent revision"
2857                 exit 1
2858         fi
2859 
2860         pnode=$(trim_digest $GIT_PARENT)
2861 
2862         if [[ $real_parent == */* ]]; then
2863                 origin=$(echo $real_parent | cut -d/ -f1)
2864                 origin=$($GIT remote -v | \
2865                     $AWK '$1 == "'$origin'" { print $2; exit }')
2866                 PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2867         else
2868                 PRETTY_PWS="${PWS} (at ${pnode})"
2869         fi
2870 
2871         cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 HEAD \
2872             2>/dev/null)
2873         PRETTY_CWS="${CWS} (at ${cnode})"
2874 elif [[ $SCM_MODE == "subversion" ]]; then
2875 
2876         #
2877         # We only will have a real parent workspace in the case one
2878         # was specified (be it an older webrev, or another checkout).
2879         #
2880         [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2881 
2882         if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2883                 flist_from_subversion $CWS $OLDPWD
2884         fi
2885 else
2886     if [[ $SCM_MODE == "unknown" ]]; then
2887         print -u2 "    Unknown type of SCM in use"
2888     else
2889         print -u2 "    Unsupported SCM in use: $SCM_MODE"
2890     fi
2891 
2892     env_from_flist
2893 
2894     if [[ -z $CODEMGR_WS ]]; then
2895         print -u2 "SCM not detected/supported and CODEMGR_WS not specified"
2896         exit 1
2897     fi
2898 
2899     if [[ -z $CODEMGR_PARENT ]]; then
2900         print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified"
2901         exit 1
2902     fi
2903 
2904     CWS=$CODEMGR_WS
2905     PWS=$CODEMGR_PARENT
2906 fi
2907 
2908 #
2909 # If the user didn't specify a -i option, check to see if there is a
2910 # webrev-info file in the workspace directory.
2911 #
2912 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2913         iflag=1
2914         INCLUDE_FILE="$CWS/webrev-info"
2915 fi
2916 
2917 if [[ -n $iflag ]]; then
2918         if [[ ! -r $INCLUDE_FILE ]]; then
2919                 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2920                     "not readable."
2921                 exit 1
2922         else
2923                 #
2924                 # $INCLUDE_FILE may be a relative path, and the script alters
2925                 # PWD, so we just stash a copy in /tmp.
2926                 #
2927                 cp $INCLUDE_FILE /tmp/$$.include
2928         fi
2929 fi
2930 
2931 # DO_EVERYTHING: break point
2932 if [[ -n $Nflag ]]; then
2933         break
2934 fi
2935 
2936 typeset -A itsinfo
2937 typeset -r its_sed_script=/tmp/$$.its_sed
2938 valid_prefixes=
2939 if [[ -z $nflag ]]; then
2940         DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg"
2941         if [[ -n $Iflag ]]; then
2942                 REGFILE=$ITSREG
2943         elif [[ -r $HOME/.its.reg ]]; then
2944                 REGFILE=$HOME/.its.reg
2945         else
2946                 REGFILE=$DEFREGFILE
2947         fi
2948         if [[ ! -r $REGFILE ]]; then
2949                 print "ERROR: Unable to read database registry file $REGFILE"
2950                 exit 1
2951         elif [[ $REGFILE != $DEFREGFILE ]]; then
2952                 print "   its.reg from: $REGFILE"
2953         fi
2954 
2955         $SED -e '/^#/d' -e '/^[         ]*$/d' $REGFILE | while read LINE; do
2956 
2957                 name=${LINE%%=*}
2958                 value="${LINE#*=}"
2959 
2960                 if [[ $name == PREFIX ]]; then
2961                         p=${value}
2962                         valid_prefixes="${p} ${valid_prefixes}"
2963                 else
2964                         itsinfo["${p}_${name}"]="${value}"
2965                 fi
2966         done
2967 
2968 
2969         DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf"
2970         CONFFILES=$DEFCONFFILE
2971         if [[ -r $HOME/.its.conf ]]; then
2972                 CONFFILES="${CONFFILES} $HOME/.its.conf"
2973         fi
2974         if [[ -n $Cflag ]]; then
2975                 CONFFILES="${CONFFILES} ${ITSCONF}"
2976         fi
2977         its_domain=
2978         its_priority=
2979         for cf in ${CONFFILES}; do
2980                 if [[ ! -r $cf ]]; then
2981                         print "ERROR: Unable to read database configuration file $cf"
2982                         exit 1
2983                 elif [[ $cf != $DEFCONFFILE ]]; then
2984                         print "       its.conf: reading $cf"
2985                 fi
2986                 $SED -e '/^#/d' -e '/^[         ]*$/d' $cf | while read LINE; do
2987                     eval "${LINE}"
2988                 done
2989         done
2990 
2991         #
2992         # If an information tracking system is explicitly identified by prefix,
2993         # we want to disregard the specified priorities and resolve it accordingly.
2994         #
2995         # To that end, we'll build a sed script to do each valid prefix in turn.
2996         #
2997         for p in ${valid_prefixes}; do
2998                 #
2999                 # When an informational URL was provided, translate it to a
3000                 # hyperlink.  When omitted, simply use the prefix text.
3001                 #
3002                 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
3003                         itsinfo["${p}_INFO"]=${p}
3004                 else
3005                         itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
3006                 fi
3007 
3008                 #
3009                 # Assume that, for this invocation of webrev, all references
3010                 # to this information tracking system should resolve through
3011                 # the same URL.
3012                 #
3013                 # If the caller specified -O, then always use EXTERNAL_URL.
3014                 #
3015                 # Otherwise, look in the list of domains for a matching
3016                 # INTERNAL_URL.
3017                 #
3018                 [[ -z $Oflag ]] && for d in ${its_domain}; do
3019                         if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
3020                                 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
3021                                 break
3022                         fi
3023                 done
3024                 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
3025                         itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
3026                 fi
3027 
3028                 #
3029                 # Turn the destination URL into a hyperlink
3030                 #
3031                 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
3032 
3033                 # The character class below contains a literal tab
3034                 print "/^${p}[:         ]/ {
3035                                 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
3036                                 s;^${p};${itsinfo[${p}_INFO]};
3037                         }" >> ${its_sed_script}
3038         done
3039 
3040         #
3041         # The previous loop took care of explicit specification.  Now use
3042         # the configured priorities to attempt implicit translations.
3043         #
3044         for p in ${its_priority}; do
3045                 print "/^${itsinfo[${p}_REGEX]}[        ]/ {
3046                                 s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
3047                         }" >> ${its_sed_script}
3048         done
3049 fi
3050 
3051 #
3052 # Search for DO_EVERYTHING above for matching "for" statement
3053 # and explanation of this terminator.
3054 #
3055 done
3056 
3057 #
3058 # Output directory.
3059 #
3060 WDIR=${WDIR:-$CWS/webrev}
3061 
3062 #
3063 # Name of the webrev, derived from the workspace name or output directory;
3064 # in the future this could potentially be an option.
3065 #
3066 if [[ -n $oflag ]]; then
3067         WNAME=${WDIR##*/}
3068 else
3069         WNAME=${CWS##*/}
3070 fi
3071 
3072 # Make sure remote target is well formed for remote upload/delete.
3073 if [[ -n $Dflag || -n $Uflag ]]; then
3074         #
3075         # If remote target is not specified, build it from scratch using
3076         # the default values.
3077         #
3078         if [[ -z $tflag ]]; then
3079                 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
3080         else
3081                 #
3082                 # Check upload target prefix first.
3083                 #
3084                 if [[ "${remote_target}" != ${rsync_prefix}* &&
3085                     "${remote_target}" != ${ssh_prefix}* ]]; then
3086                         print "ERROR: invalid prefix of upload URI" \
3087                             "($remote_target)"
3088                         exit 1
3089                 fi
3090                 #
3091                 # If destination specification is not in the form of
3092                 # host_spec:remote_dir then assume it is just remote hostname
3093                 # and append a colon and destination directory formed from
3094                 # local webrev directory name.
3095                 #
3096                 typeset target_no_prefix=${remote_target##*://}
3097                 if [[ ${target_no_prefix} == *:* ]]; then
3098                         if [[ "${remote_target}" == *: ]]; then
3099                                 remote_target=${remote_target}${WNAME}
3100                         fi
3101                 else
3102                         if [[ ${target_no_prefix} == */* ]]; then
3103                                 print "ERROR: badly formed upload URI" \
3104                                         "($remote_target)"
3105                                 exit 1
3106                         else
3107                                 remote_target=${remote_target}:${WNAME}
3108                         fi
3109                 fi
3110         fi
3111 
3112         #
3113         # Strip trailing slash. Each upload method will deal with directory
3114         # specification separately.
3115         #
3116         remote_target=${remote_target%/}
3117 fi
3118 
3119 #
3120 # Option -D by itself (option -U not present) implies no webrev generation.
3121 #
3122 if [[ -z $Uflag && -n $Dflag ]]; then
3123         delete_webrev 1 1
3124         exit $?
3125 fi
3126 
3127 #
3128 # Do not generate the webrev, just upload it or delete it.
3129 #
3130 if [[ -n $nflag ]]; then
3131         if [[ -n $Dflag ]]; then
3132                 delete_webrev 1 1
3133                 (( $? == 0 )) || exit $?
3134         fi
3135         if [[ -n $Uflag ]]; then
3136                 upload_webrev
3137                 exit $?
3138         fi
3139 fi
3140 
3141 if [ "${WDIR%%/*}" ]; then
3142         WDIR=$PWD/$WDIR
3143 fi
3144 
3145 if [[ ! -d $WDIR ]]; then
3146         mkdir -p $WDIR
3147         (( $? != 0 )) && exit 1
3148 fi
3149 
3150 #
3151 # Summarize what we're going to do.
3152 #
3153 print "      Workspace: ${PRETTY_CWS:-$CWS}"
3154 if [[ -n $parent_webrev ]]; then
3155         print "Compare against: webrev at $parent_webrev"
3156 else
3157         print "Compare against: ${PRETTY_PWS:-$PWS}"
3158 fi
3159 
3160 [[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
3161 print "      Output to: $WDIR"
3162 
3163 #
3164 # Save the file list in the webrev dir
3165 #
3166 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
3167 
3168 rm -f $WDIR/$WNAME.patch
3169 rm -f $WDIR/$WNAME.ps
3170 rm -f $WDIR/$WNAME.pdf
3171 
3172 touch $WDIR/$WNAME.patch
3173 
3174 print "   Output Files:"
3175 
3176 #
3177 # Clean up the file list: Remove comments, blank lines and env variables.
3178 #
3179 $SED -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
3180 FLIST=/tmp/$$.flist.clean
3181 
3182 #
3183 # For Mercurial, create a cache of manifest entries.
3184 #
3185 if [[ $SCM_MODE == "mercurial" ]]; then
3186         #
3187         # Transform the FLIST into a temporary sed script that matches
3188         # relevant entries in the Mercurial manifest as follows:
3189         # 1) The script will be used against the parent revision manifest,
3190         #    so for FLIST lines that have two filenames (a renamed file)
3191         #    keep only the old name.
3192         # 2) Escape all forward slashes the filename.
3193         # 3) Change the filename into another sed command that matches
3194         #    that file in "hg manifest -v" output:  start of line, three
3195         #    octal digits for file permissions, space, a file type flag
3196         #    character, space, the filename, end of line.
3197         # 4) Eliminate any duplicate entries.  (This can occur if a
3198         #    file has been used as the source of an hg cp and it's
3199         #    also been modified in the same changeset.)
3200         #
3201         SEDFILE=/tmp/$$.manifest.sed
3202         $SED '
3203                 s#^[^ ]* ##
3204                 s#/#\\\/#g
3205                 s#^.*$#/^... . &$/p#
3206         ' < $FLIST | $SORT -u > $SEDFILE
3207 
3208         #
3209         # Apply the generated script to the output of "hg manifest -v"
3210         # to get the relevant subset for this webrev.
3211         #
3212         HG_PARENT_MANIFEST=/tmp/$$.manifest
3213         hg -R $CWS manifest -v -r $HG_PARENT |
3214             $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
3215 fi
3216 
3217 #
3218 # First pass through the files: generate the per-file webrev HTML-files.
3219 #
3220 cat $FLIST | while read LINE
3221 do
3222         set - $LINE
3223         P=$1
3224 
3225         #
3226         # Normally, each line in the file list is just a pathname of a
3227         # file that has been modified or created in the child.  A file
3228         # that is renamed in the child workspace has two names on the
3229         # line: new name followed by the old name.
3230         #
3231         oldname=""
3232         oldpath=""
3233         rename=
3234         if [[ $# -eq 2 ]]; then
3235                 PP=$2                   # old filename
3236                 if [[ -f $PP ]]; then
3237                         oldname=" (copied from $PP)"
3238                 else
3239                         oldname=" (renamed from $PP)"
3240                 fi
3241                 oldpath="$PP"
3242                 rename=1
3243                 PDIR=${PP%/*}
3244                 if [[ $PDIR == $PP ]]; then
3245                         PDIR="."   # File at root of workspace
3246                 fi
3247 
3248                 PF=${PP##*/}
3249 
3250                 DIR=${P%/*}
3251                 if [[ $DIR == $P ]]; then
3252                         DIR="."   # File at root of workspace
3253                 fi
3254 
3255                 F=${P##*/}
3256 
3257         else
3258                 DIR=${P%/*}
3259                 if [[ "$DIR" == "$P" ]]; then
3260                         DIR="."   # File at root of workspace
3261                 fi
3262 
3263                 F=${P##*/}
3264 
3265                 PP=$P
3266                 PDIR=$DIR
3267                 PF=$F
3268         fi
3269 
3270         COMM=`getcomments html $P $PP`
3271 
3272         print "\t$P$oldname\n\t\t\c"
3273 
3274         # Make the webrev mirror directory if necessary
3275         mkdir -p $WDIR/$DIR
3276 
3277         #
3278         # We stash old and new files into parallel directories in $WDIR
3279         # and do our diffs there.  This makes it possible to generate
3280         # clean looking diffs which don't have absolute paths present.
3281         #
3282 
3283         build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3284             continue
3285 
3286         #
3287         # Keep the old PWD around, so we can safely switch back after
3288         # diff generation, such that build_old_new runs in a
3289         # consistent environment.
3290         #
3291         OWD=$PWD
3292         cd $WDIR/raw_files
3293         ofile=old/$PDIR/$PF
3294         nfile=new/$DIR/$F
3295 
3296         mv_but_nodiff=
3297         cmp $ofile $nfile > /dev/null 2>&1
3298         if [[ $? == 0 && $rename == 1 ]]; then
3299                 mv_but_nodiff=1
3300         fi
3301 
3302         #
3303         # If we have old and new versions of the file then run the appropriate
3304         # diffs.  This is complicated by a couple of factors:
3305         #
3306         #       - renames must be handled specially: we emit a 'remove'
3307         #         diff and an 'add' diff
3308         #       - new files and deleted files must be handled specially
3309         #       - Solaris patch(1m) can't cope with file creation
3310         #         (and hence renames) as of this writing.
3311         #       - To make matters worse, gnu patch doesn't interpret the
3312         #         output of Solaris diff properly when it comes to
3313         #         adds and deletes.  We need to do some "cleansing"
3314         #         transformations:
3315         #           [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
3316         #           [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
3317         #
3318         cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3319         cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3320 
3321         rm -f $WDIR/$DIR/$F.patch
3322         if [[ -z $rename ]]; then
3323                 if [ ! -f "$ofile" ]; then
3324                         diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3325                             > $WDIR/$DIR/$F.patch
3326                 elif [ ! -f "$nfile" ]; then
3327                         diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3328                             > $WDIR/$DIR/$F.patch
3329                 else
3330                         diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3331                 fi
3332         else
3333                 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3334                     > $WDIR/$DIR/$F.patch
3335 
3336                 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3337                     >> $WDIR/$DIR/$F.patch
3338         fi
3339 
3340         #
3341         # Tack the patch we just made onto the accumulated patch for the
3342         # whole wad.
3343         #
3344         cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3345 
3346         print " patch\c"
3347 
3348         if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3349 
3350                 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3351                 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3352                     > $WDIR/$DIR/$F.cdiff.html
3353                 print " cdiffs\c"
3354 
3355                 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3356                 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3357                     > $WDIR/$DIR/$F.udiff.html
3358 
3359                 print " udiffs\c"
3360 
3361                 if [[ -x $WDIFF ]]; then
3362                         $WDIFF -c "$COMM" \
3363                             -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3364                             $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3365                         if [[ $? -eq 0 ]]; then
3366                                 print " wdiffs\c"
3367                         else
3368                                 print " wdiffs[fail]\c"
3369                         fi
3370                 fi
3371 
3372                 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3373                     > $WDIR/$DIR/$F.sdiff.html
3374                 print " sdiffs\c"
3375 
3376                 print " frames\c"
3377 
3378                 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3379 
3380                 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3381 
3382         elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3383                 # renamed file: may also have differences
3384                 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3385         elif [[ -f $nfile ]]; then
3386                 # new file: count added lines
3387                 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3388         elif [[ -f $ofile ]]; then
3389                 # old file: count deleted lines
3390                 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3391         fi
3392 
3393         #
3394         # Now we generate the postscript for this file.  We generate diffs
3395         # only in the event that there is delta, or the file is new (it seems
3396         # tree-killing to print out the contents of deleted files).
3397         #
3398         if [[ -f $nfile ]]; then
3399                 ocr=$ofile
3400                 [[ ! -f $ofile ]] && ocr=/dev/null
3401 
3402                 if [[ -z $mv_but_nodiff ]]; then
3403                         textcomm=`getcomments text $P $PP`
3404                         if [[ -x $CODEREVIEW ]]; then
3405                                 $CODEREVIEW -y "$textcomm" \
3406                                     -e $ocr $nfile \
3407                                     > /tmp/$$.psfile 2>/dev/null &&
3408                                     cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3409                                 if [[ $? -eq 0 ]]; then
3410                                         print " ps\c"
3411                                 else
3412                                         print " ps[fail]\c"
3413                                 fi
3414                         fi
3415                 fi
3416         fi
3417 
3418         if [[ -f $ofile ]]; then
3419                 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3420                 print " old\c"
3421         fi
3422 
3423         if [[ -f $nfile ]]; then
3424                 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3425                 print " new\c"
3426         fi
3427 
3428         cd $OWD
3429 
3430         print
3431 done
3432 
3433 frame_nav_js > $WDIR/ancnav.js
3434 frame_navigation > $WDIR/ancnav.html
3435 
3436 if [[ ! -f $WDIR/$WNAME.ps ]]; then
3437         print " Generating PDF: Skipped: no output available"
3438 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3439         print " Generating PDF: \c"
3440         fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3441         print "Done."
3442 else
3443         print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3444 fi
3445 
3446 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3447 # delete it - prevent accidental publishing of closed source
3448 
3449 if [[ -n "$Oflag" ]]; then
3450         $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3451 fi
3452 
3453 # Now build the index.html file that contains
3454 # links to the source files and their diffs.
3455 
3456 cd $CWS
3457 
3458 # Save total changed lines for Code Inspection.
3459 print "$TOTL" > $WDIR/TotalChangedLines
3460 
3461 print "     index.html: \c"
3462 INDEXFILE=$WDIR/index.html
3463 exec 3<&1                        # duplicate stdout to FD3.
3464 exec 1<&-                        # Close stdout.
3465 exec > $INDEXFILE            # Open stdout to index file.
3466 
3467 print "$HTML<head>$STDHEAD"
3468 print "<title>$WNAME</title>"
3469 print "</head>"
3470 print "<body id=\"SUNWwebrev\">"
3471 print "<div class=\"summary\">"
3472 print "<h2>Code Review for $WNAME</h2>"
3473 
3474 print "<table>"
3475 
3476 #
3477 # Get the preparer's name:
3478 #
3479 # If the SCM detected is Mercurial, and the configuration property
3480 # ui.username is available, use that, but be careful to properly escape
3481 # angle brackets (HTML syntax characters) in the email address.
3482 #
3483 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3484 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3485 #
3486 preparer=
3487 if [[ "$SCM_MODE" == mercurial ]]; then
3488         preparer=`hg showconfig ui.username 2>/dev/null`
3489         if [[ -n "$preparer" ]]; then
3490                 preparer="$(echo "$preparer" | html_quote)"
3491         fi
3492 fi
3493 if [[ -z "$preparer" ]]; then
3494         preparer=$(
3495             $PERL -e '
3496                 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3497                 if ($login) {
3498                     $gcos =~ s/\&/ucfirst($login)/e;
3499                     printf "%s (%s)\n", $gcos, $login;
3500                 } else {
3501                     printf "(unknown)\n";
3502                 }
3503         ')
3504 fi
3505 
3506 PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3507 print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3508 print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3509 print "</td></tr>"
3510 print "<tr><th>Compare against:</th><td>"
3511 if [[ -n $parent_webrev ]]; then
3512         print "webrev at $parent_webrev"
3513 else
3514         print "${PRETTY_PWS:-$PWS}"
3515 fi
3516 print "</td></tr>"
3517 print "<tr><th>Summary of changes:</th><td>"
3518 printCI $TOTL $TINS $TDEL $TMOD $TUNC
3519 print "</td></tr>"
3520 
3521 if [[ -f $WDIR/$WNAME.patch ]]; then
3522         wpatch_url="$(print $WNAME.patch | url_encode)"
3523         print "<tr><th>Patch of changes:</th><td>"
3524         print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3525 fi
3526 if [[ -f $WDIR/$WNAME.pdf ]]; then
3527         wpdf_url="$(print $WNAME.pdf | url_encode)"
3528         print "<tr><th>Printable review:</th><td>"
3529         print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3530 fi
3531 
3532 if [[ -n "$iflag" ]]; then
3533         print "<tr><th>Author comments:</th><td><div>"
3534         cat /tmp/$$.include
3535         print "</div></td></tr>"
3536 fi
3537 print "</table>"
3538 print "</div>"
3539 
3540 #
3541 # Second pass through the files: generate the rest of the index file
3542 #
3543 cat $FLIST | while read LINE
3544 do
3545         set - $LINE
3546         P=$1
3547 
3548         if [[ $# == 2 ]]; then
3549                 PP=$2
3550                 oldname="$PP"
3551         else
3552                 PP=$P
3553                 oldname=""
3554         fi
3555 
3556         mv_but_nodiff=
3557         cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3558         if [[ $? == 0 && -n "$oldname" ]]; then
3559                 mv_but_nodiff=1
3560         fi
3561 
3562         DIR=${P%/*}
3563         if [[ $DIR == $P ]]; then
3564                 DIR="."   # File at root of workspace
3565         fi
3566 
3567         # Avoid processing the same file twice.
3568         # It's possible for renamed files to
3569         # appear twice in the file list
3570 
3571         F=$WDIR/$P
3572 
3573         print "<p>"
3574 
3575         # If there's a diffs file, make diffs links
3576 
3577         if [[ -f $F.cdiff.html ]]; then
3578                 cdiff_url="$(print $P.cdiff.html | url_encode)"
3579                 udiff_url="$(print $P.udiff.html | url_encode)"
3580                 print "<a href=\"$cdiff_url\">Cdiffs</a>"
3581                 print "<a href=\"$udiff_url\">Udiffs</a>"
3582 
3583                 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3584                         wdiff_url="$(print $P.wdiff.html | url_encode)"
3585                         print "<a href=\"$wdiff_url\">Wdiffs</a>"
3586                 fi
3587 
3588                 sdiff_url="$(print $P.sdiff.html | url_encode)"
3589                 print "<a href=\"$sdiff_url\">Sdiffs</a>"
3590 
3591                 frames_url="$(print $P.frames.html | url_encode)"
3592                 print "<a href=\"$frames_url\">Frames</a>"
3593         else
3594                 print " ------ ------ ------"
3595 
3596                 if [[ -x $WDIFF ]]; then
3597                         print " ------"
3598                 fi
3599 
3600                 print " ------"
3601         fi
3602 
3603         # If there's an old file, make the link
3604 
3605         if [[ -f $F-.html ]]; then
3606                 oldfile_url="$(print $P-.html | url_encode)"
3607                 print "<a href=\"$oldfile_url\">Old</a>"
3608         else
3609                 print " ---"
3610         fi
3611 
3612         # If there's an new file, make the link
3613 
3614         if [[ -f $F.html ]]; then
3615                 newfile_url="$(print $P.html | url_encode)"
3616                 print "<a href=\"$newfile_url\">New</a>"
3617         else
3618                 print " ---"
3619         fi
3620 
3621         if [[ -f $F.patch ]]; then
3622                 patch_url="$(print $P.patch | url_encode)"
3623                 print "<a href=\"$patch_url\">Patch</a>"
3624         else
3625                 print " -----"
3626         fi
3627 
3628         if [[ -f $WDIR/raw_files/new/$P ]]; then
3629                 rawfiles_url="$(print raw_files/new/$P | url_encode)"
3630                 print "<a href=\"$rawfiles_url\">Raw</a>"
3631         else
3632                 print " ---"
3633         fi
3634 
3635         print "<b>$P</b>"
3636 
3637         # For renamed files, clearly state whether or not they are modified
3638         if [[ -f "$oldname" ]]; then
3639                 if [[ -n "$mv_but_nodiff" ]]; then
3640                         print "<i>(copied from $oldname)</i>"
3641                 else
3642                         print "<i>(copied and modified from $oldname)</i>"
3643                 fi
3644         elif [[ -n "$oldname" ]]; then
3645                 if [[ -n "$mv_but_nodiff" ]]; then
3646                         print "<i>(renamed from $oldname)</i>"
3647                 else
3648                         print "<i>(renamed and modified from $oldname)</i>"
3649                 fi
3650         fi
3651 
3652         # If there's an old file, but no new file, the file was deleted
3653         if [[ -f $F-.html && ! -f $F.html ]]; then
3654                 print " <i>(deleted)</i>"
3655         fi
3656 
3657         #
3658         # Check for usr/closed and deleted_files/usr/closed
3659         #
3660         if [ ! -z "$Oflag" ]; then
3661                 if [[ $P == usr/closed/* || \
3662                     $P == deleted_files/usr/closed/* ]]; then
3663                         print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3664                             "this review</i>"
3665                 fi
3666         fi
3667 
3668         print "</p>"
3669         # Insert delta comments
3670 
3671         print "<blockquote><pre>"
3672         getcomments html $P $PP
3673         print "</pre>"
3674 
3675         # Add additional comments comment
3676 
3677         print "<!-- Add comments to explain changes in $P here -->"
3678 
3679         # Add count of changes.
3680 
3681         if [[ -f $F.count ]]; then
3682             cat $F.count
3683             rm $F.count
3684         fi
3685 
3686         if [[ $SCM_MODE == "teamware" ||
3687             $SCM_MODE == "mercurial" ||
3688             $SCM_MODE == "unknown" ]]; then
3689 
3690                 # Include warnings for important file mode situations:
3691                 # 1) New executable files
3692                 # 2) Permission changes of any kind
3693                 # 3) Existing executable files
3694 
3695                 old_mode=
3696                 if [[ -f $WDIR/raw_files/old/$PP ]]; then
3697                         old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3698                 fi
3699 
3700                 new_mode=
3701                 if [[ -f $WDIR/raw_files/new/$P ]]; then
3702                         new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3703                 fi
3704 
3705                 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3706                         print "<span class=\"chmod\">"
3707                         print "<p>new executable file: mode $new_mode</p>"
3708                         print "</span>"
3709                 elif [[ -n "$old_mode" && -n "$new_mode" &&
3710                     "$old_mode" != "$new_mode" ]]; then
3711                         print "<span class=\"chmod\">"
3712                         print "<p>mode change: $old_mode to $new_mode</p>"
3713                         print "</span>"
3714                 elif [[ "$new_mode" = *[1357]* ]]; then
3715                         print "<span class=\"chmod\">"
3716                         print "<p>executable file: mode $new_mode</p>"
3717                         print "</span>"
3718                 fi
3719         fi
3720 
3721         print "</blockquote>"
3722 done
3723 
3724 print
3725 print
3726 print "<hr></hr>"
3727 print "<p style=\"font-size: small\">"
3728 print "This code review page was prepared using <b>$0</b>."
3729 print "Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3730 print "illumos</a> project.  The latest version may be obtained"
3731 print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3732 print "</body>"
3733 print "</html>"
3734 
3735 exec 1<&-                        # Close FD 1.
3736 exec 1<&3                        # dup FD 3 to restore stdout.
3737 exec 3<&-                        # close FD 3.
3738 
3739 print "Done."
3740 
3741 #
3742 # If remote deletion was specified and fails do not continue.
3743 #
3744 if [[ -n $Dflag ]]; then
3745         delete_webrev 1 1
3746         (( $? == 0 )) || exit $?
3747 fi
3748 
3749 if [[ -n $Uflag ]]; then
3750         upload_webrev
3751         exit $?
3752 fi