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