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