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