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