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