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 # This is also used with Mercurial and the file list provided by hg-active.
1573 #
1574 comments_from_wx()
1575 {
1576         typeset fmt=$1
1577         typeset p=$2
1578 
1579         comm=`$AWK '
1580         $1 == "'$p'" {
1581                 do getline ; while (NF > 0)
1582                 getline
1583                 while (NF > 0) { print ; getline }
1584                 exit
1585         }' < $wxfile`
1586 
1587         if [[ -z $comm ]]; then
1588                 comm="*** NO COMMENTS ***"
1589         fi
1590 
1591         if [[ $fmt == "text" ]]; then
1592                 print -- "$comm"
1593                 return
1594         fi
1595 
1596         print -- "$comm" | html_quote | its2url
1597 
1598 }
1599 
1600 #
1601 # getcomments {text|html} filepath parentpath
1602 #
1603 # Fetch the comments depending on what SCM mode we're in.
1604 #
1605 getcomments()
1606 {
1607         typeset fmt=$1
1608         typeset p=$2
1609         typeset pp=$3
1610 
1611         if [[ -n $Nflag ]]; then
1612                 return
1613         fi
1614         #
1615         # Mercurial support uses a file list in wx format, so this
1616         # will be used there, too
1617         #
1618         if [[ -n $wxfile ]]; then
1619                 comments_from_wx $fmt $p
1620         fi
1621 }
1622 
1623 #
1624 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1625 #
1626 # Print out Code Inspection figures similar to sccs-prt(1) format.
1627 #
1628 function printCI
1629 {
1630         integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1631         typeset str
1632         if (( tot == 1 )); then
1633                 str="line"
1634         else
1635                 str="lines"
1636         fi
1637         printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1638             $tot $str $ins $del $mod $unc
1639 }
1640 
1641 
1642 #
1643 # difflines <oldfile> <newfile>
1644 #
1645 # Calculate and emit number of added, removed, modified and unchanged lines,
1646 # and total lines changed, the sum of added + removed + modified.
1647 #
1648 function difflines
1649 {
1650         integer tot mod del ins unc err
1651         typeset filename
1652 
1653         eval $( diff -e $1 $2 | $AWK '
1654         # Change range of lines: N,Nc
1655         /^[0-9]*,[0-9]*c$/ {
1656                 n=split(substr($1,1,length($1)-1), counts, ",");
1657                 if (n != 2) {
1658                         error=2
1659                         exit;
1660                 }
1661                 #
1662                 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1663                 # following would be 5 - 3 = 2! Hence +1 for correction.
1664                 #
1665                 r=(counts[2]-counts[1])+1;
1666 
1667                 #
1668                 # Now count replacement lines: each represents a change instead
1669                 # of a delete, so increment c and decrement r.
1670                 #
1671                 while (getline != /^\.$/) {
1672                         c++;
1673                         r--;
1674                 }
1675                 #
1676                 # If there were more replacement lines than original lines,
1677                 # then r will be negative; in this case there are no deletions,
1678                 # but there are r changes that should be counted as adds, and
1679                 # since r is negative, subtract it from a and add it to c.
1680                 #
1681                 if (r < 0) {
1682                         a-=r;
1683                         c+=r;
1684                 }
1685 
1686                 #
1687                 # If there were more original lines than replacement lines, then
1688                 # r will be positive; in this case, increment d by that much.
1689                 #
1690                 if (r > 0) {
1691                         d+=r;
1692                 }
1693                 next;
1694         }
1695 
1696         # Change lines: Nc
1697         /^[0-9].*c$/ {
1698                 # The first line is a replacement; any more are additions.
1699                 if (getline != /^\.$/) {
1700                         c++;
1701                         while (getline != /^\.$/) a++;
1702                 }
1703                 next;
1704         }
1705 
1706         # Add lines: both Na and N,Na
1707         /^[0-9].*a$/ {
1708                 while (getline != /^\.$/) a++;
1709                 next;
1710         }
1711 
1712         # Delete range of lines: N,Nd
1713         /^[0-9]*,[0-9]*d$/ {
1714                 n=split(substr($1,1,length($1)-1), counts, ",");
1715                 if (n != 2) {
1716                         error=2
1717                         exit;
1718                 }
1719                 #
1720                 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1721                 # following would be 5 - 3 = 2! Hence +1 for correction.
1722                 #
1723                 r=(counts[2]-counts[1])+1;
1724                 d+=r;
1725                 next;
1726         }
1727 
1728         # Delete line: Nd.   For example 10d says line 10 is deleted.
1729         /^[0-9]*d$/ {d++; next}
1730 
1731         # Should not get here!
1732         {
1733                 error=1;
1734                 exit;
1735         }
1736 
1737         # Finish off - print results
1738         END {
1739                 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1740                     (c+d+a), c, d, a, error);
1741         }' )
1742 
1743         # End of $AWK, Check to see if any trouble occurred.
1744         if (( $? > 0 || err > 0 )); then
1745                 print "Unexpected Error occurred reading" \
1746                     "\`diff -e $1 $2\`: \$?=$?, err=" $err
1747                 return
1748         fi
1749 
1750         # Accumulate totals
1751         (( TOTL += tot ))
1752         (( TMOD += mod ))
1753         (( TDEL += del ))
1754         (( TINS += ins ))
1755         # Calculate unchanged lines
1756         unc=`wc -l < $1`
1757         if (( unc > 0 )); then
1758                 (( unc -= del + mod ))
1759                 (( TUNC += unc ))
1760         fi
1761         # print summary
1762         print "<span class=\"lineschanged\">"
1763         printCI $tot $ins $del $mod $unc
1764         print "</span>"
1765 }
1766 
1767 
1768 #
1769 # flist_from_wx
1770 #
1771 # Sets up webrev to source its information from a wx-formatted file.
1772 # Sets the global 'wxfile' variable.
1773 #
1774 function flist_from_wx
1775 {
1776         typeset argfile=$1
1777         if [[ -n ${argfile%%/*} ]]; then
1778                 #
1779                 # If the wx file pathname is relative then make it absolute
1780                 # because the webrev does a "cd" later on.
1781                 #
1782                 wxfile=$PWD/$argfile
1783         else
1784                 wxfile=$argfile
1785         fi
1786 
1787         $AWK '{ c = 1; print;
1788           while (getline) {
1789                 if (NF == 0) { c = -c; continue }
1790                 if (c > 0) print
1791           }
1792         }' $wxfile > $FLIST
1793 
1794         print " Done."
1795 }
1796 
1797 #
1798 # Call hg-active to get the active list output in the wx active list format
1799 #
1800 function hg_active_wxfile
1801 {
1802         typeset child=$1
1803         typeset parent=$2
1804 
1805         TMPFLIST=/tmp/$$.active
1806         $HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1807         wxfile=$TMPFLIST
1808 }
1809 
1810 #
1811 # flist_from_mercurial
1812 # Call hg-active to get a wx-style active list, and hand it off to
1813 # flist_from_wx
1814 #
1815 function flist_from_mercurial
1816 {
1817         typeset child=$1
1818         typeset parent=$2
1819 
1820         print " File list from: hg-active -p $parent ...\c"
1821         if [[ ! -x $HG_ACTIVE ]]; then
1822                 print           # Blank line for the \c above
1823                 print -u2 "Error: hg-active tool not found.  Exiting"
1824                 exit 1
1825         fi
1826         hg_active_wxfile $child $parent
1827 
1828         # flist_from_wx prints the Done, so we don't have to.
1829         flist_from_wx $TMPFLIST
1830 }
1831 
1832 #
1833 # Transform a specified 'git log' output format into a wx-like active list.
1834 #
1835 function git_wxfile
1836 {
1837         typeset child="$1"
1838         typeset parent="$2"
1839 
1840         TMPFLIST=/tmp/$$.active
1841         $PERL -e 'my (%files, %realfiles, $msg);
1842         my $parent = $ARGV[0];
1843         my $child = $ARGV[1];
1844 
1845         open(F, "git diff -M --name-status $parent..$child |");
1846         while (<F>) {
1847             chomp;
1848             if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
1849                 if ($1 >= 75) {               # Probably worth treating as a rename
1850                     $realfiles{$3} = $2;
1851                 } else {
1852                     $realfiles{$3} = $3;
1853                     $realfiles{$2} = $2;
1854                 }
1855             } else {
1856                 my $f = (split /\s+/, $_)[1];
1857                 $realfiles{$f} = $f;
1858             }
1859         }
1860         close(F);
1861 
1862         my $state = 1;              # 0|comments, 1|files
1863         open(F, "git whatchanged --pretty=format:%B $parent..$child |");
1864         while (<F>) {
1865             chomp;
1866             if (/^:[0-9]{6}/) {
1867                 my ($unused, $fname, $fname2) = split(/\t/, $_);
1868                 $fname = $fname2 if defined($fname2);
1869                 next if !defined($realfiles{$fname}); # No real change
1870                 $state = 1;
1871                 chomp $msg;
1872                 $files{$fname} .= $msg;
1873             } else {
1874                 if ($state == 1) {
1875                     $state = 0;
1876                     $msg = /^\n/ ? "" : "\n";
1877                 }
1878                 $msg .= "$_\n" if ($_);
1879             }
1880         }
1881         close(F);
1882          
1883         for (sort keys %files) {
1884             if ($realfiles{$_} ne $_) {
1885                 print "$_ $realfiles{$_}\n$files{$_}\n\n";
1886             } else {
1887                 print "$_\n$files{$_}\n\n"
1888             }
1889         }' ${parent} ${child} > $TMPFLIST
1890 
1891         wxfile=$TMPFLIST
1892 }
1893 
1894 #
1895 # flist_from_git
1896 # Build a wx-style active list, and hand it off to flist_from_wx
1897 #
1898 function flist_from_git
1899 {
1900         typeset child=$1
1901         typeset parent=$2
1902 
1903         print " File list from: git ...\c"
1904         git_wxfile "$child" "$parent";
1905 
1906         # flist_from_wx prints the Done, so we don't have to.
1907         flist_from_wx $TMPFLIST
1908 }
1909 
1910 #
1911 # flist_from_subversion
1912 #
1913 # Generate the file list by extracting file names from svn status.
1914 #
1915 function flist_from_subversion
1916 {
1917         CWS=$1
1918         OLDPWD=$2
1919 
1920         cd $CWS
1921         print -u2 " File list from: svn status ... \c"
1922         svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1923         print -u2 " Done."
1924         cd $OLDPWD
1925 }
1926 
1927 function env_from_flist
1928 {
1929         [[ -r $FLIST ]] || return
1930 
1931         #
1932         # Use "eval" to set env variables that are listed in the file
1933         # list.  Then copy those into our local versions of those
1934         # variables if they have not been set already.
1935         #
1936         eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1937 
1938         if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1939                 codemgr_ws=$CODEMGR_WS
1940                 export CODEMGR_WS
1941         fi
1942 
1943         #
1944         # Check to see if CODEMGR_PARENT is set in the flist file.
1945         #
1946         if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1947                 codemgr_parent=$CODEMGR_PARENT
1948                 export CODEMGR_PARENT
1949         fi
1950 }
1951 
1952 function look_for_prog
1953 {
1954         typeset path
1955         typeset ppath
1956         typeset progname=$1
1957 
1958         ppath=$PATH
1959         ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1960         ppath=$ppath:/opt/onbld/bin
1961         ppath=$ppath:/opt/onbld/bin/`uname -p`
1962 
1963         PATH=$ppath prog=`whence $progname`
1964         if [[ -n $prog ]]; then
1965                 print $prog
1966         fi
1967 }
1968 
1969 function get_file_mode
1970 {
1971         $PERL -e '
1972                 if (@stat = stat($ARGV[0])) {
1973                         $mode = $stat[2] & 0777;
1974                         printf "%03o\n", $mode;
1975                         exit 0;
1976                 } else {
1977                         exit 1;
1978                 }
1979             ' $1
1980 }
1981 
1982 function build_old_new_mercurial
1983 {
1984         typeset olddir="$1"
1985         typeset newdir="$2"
1986         typeset old_mode=
1987         typeset new_mode=
1988         typeset file
1989 
1990         #
1991         # Get old file mode, from the parent revision manifest entry.
1992         # Mercurial only stores a "file is executable" flag, but the
1993         # manifest will display an octal mode "644" or "755".
1994         #
1995         if [[ "$PDIR" == "." ]]; then
1996                 file="$PF"
1997         else
1998                 file="$PDIR/$PF"
1999         fi
2000         file=`echo $file | $SED 's#/#\\\/#g'`
2001         # match the exact filename, and return only the permission digits
2002         old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
2003             < $HG_PARENT_MANIFEST`
2004 
2005         #
2006         # Get new file mode, directly from the filesystem.
2007         # Normalize the mode to match Mercurial's behavior.
2008         #
2009         new_mode=`get_file_mode $CWS/$DIR/$F`
2010         if [[ -n "$new_mode" ]]; then
2011                 if [[ "$new_mode" = *[1357]* ]]; then
2012                         new_mode=755
2013                 else
2014                         new_mode=644
2015                 fi
2016         fi
2017 
2018         #
2019         # new version of the file.
2020         #
2021         rm -rf $newdir/$DIR/$F
2022         if [[ -e $CWS/$DIR/$F ]]; then
2023                 cp $CWS/$DIR/$F $newdir/$DIR/$F
2024                 if [[ -n $new_mode ]]; then
2025                         chmod $new_mode $newdir/$DIR/$F
2026                 else
2027                         # should never happen
2028                         print -u2 "ERROR: set mode of $newdir/$DIR/$F"
2029                 fi
2030         fi
2031 
2032         #
2033         # parent's version of the file
2034         #
2035         # Note that we get this from the last version common to both
2036         # ourselves and the parent.  References are via $CWS since we have no
2037         # guarantee that the parent workspace is reachable via the filesystem.
2038         #
2039         if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2040                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2041         elif [[ -n $HG_PARENT ]]; then
2042                 hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
2043                     $olddir/$PDIR/$PF 2>/dev/null
2044 
2045                 if (( $? != 0 )); then
2046                         rm -f $olddir/$PDIR/$PF
2047                 else
2048                         if [[ -n $old_mode ]]; then
2049                                 chmod $old_mode $olddir/$PDIR/$PF
2050                         else
2051                                 # should never happen
2052                                 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
2053                         fi
2054                 fi
2055         fi
2056 }
2057 
2058 function build_old_new_git
2059 {
2060         typeset olddir="$1"
2061         typeset newdir="$2"
2062         typeset o_mode=
2063         typeset n_mode=
2064         typeset o_object=
2065         typeset n_object=
2066         typeset OWD=$PWD
2067         typeset file
2068         typeset type
2069 
2070         cd $CWS
2071 
2072         #
2073         # Get old file and its mode from the git object tree
2074         #
2075         if [[ "$PDIR" == "." ]]; then
2076                 file="$PF"
2077         else
2078                 file="$PDIR/$PF"
2079         fi
2080 
2081         if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2082                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2083         else
2084                 $GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk
2085                 $GIT cat-file $type $o_object > $olddir/$file 2>/dev/null
2086 
2087                 if (( $? != 0 )); then
2088                         rm -f $olddir/$file
2089                 elif [[ -n $o_mode ]]; then
2090                         # Strip the first 3 digits, to get a regular octal mode
2091                         o_mode=${o_mode/???/}
2092                         chmod $o_mode $olddir/$file
2093                 else
2094                         # should never happen
2095                         print -u2 "ERROR: set mode of $olddir/$file"
2096                 fi
2097         fi
2098 
2099         #
2100         # new version of the file.
2101         #
2102         if [[ "$DIR" == "." ]]; then
2103                 file="$F"
2104         else
2105                 file="$DIR/$F"
2106         fi
2107         rm -rf $newdir/$file
2108 
2109         if [[ -e $CWS/$DIR/$F ]]; then
2110                 cp $CWS/$DIR/$F $newdir/$DIR/$F
2111                 chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F
2112         fi
2113         cd $OWD
2114 }
2115 
2116 function build_old_new_subversion
2117 {
2118         typeset olddir="$1"
2119         typeset newdir="$2"
2120 
2121         # Snag new version of file.
2122         rm -f $newdir/$DIR/$F
2123         [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2124 
2125         if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2126                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2127         else
2128                 # Get the parent's version of the file.
2129                 svn status $CWS/$DIR/$F | read stat file
2130                 if [[ $stat != "A" ]]; then
2131                         svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2132                 fi
2133         fi
2134 }
2135 
2136 function build_old_new_unknown
2137 {
2138         typeset olddir="$1"
2139         typeset newdir="$2"
2140 
2141         #
2142         # Snag new version of file.
2143         #
2144         rm -f $newdir/$DIR/$F
2145         [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2146 
2147         #
2148         # Snag the parent's version of the file.
2149         #
2150         if [[ -f $PWS/$PDIR/$PF ]]; then
2151                 rm -f $olddir/$PDIR/$PF
2152                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2153         fi
2154 }
2155 
2156 function build_old_new
2157 {
2158         typeset WDIR=$1
2159         typeset PWS=$2
2160         typeset PDIR=$3
2161         typeset PF=$4
2162         typeset CWS=$5
2163         typeset DIR=$6
2164         typeset F=$7
2165 
2166         typeset olddir="$WDIR/raw_files/old"
2167         typeset newdir="$WDIR/raw_files/new"
2168 
2169         mkdir -p $olddir/$PDIR
2170         mkdir -p $newdir/$DIR
2171 
2172         if [[ $SCM_MODE == "mercurial" ]]; then
2173                 build_old_new_mercurial "$olddir" "$newdir"
2174         elif [[ $SCM_MODE == "git" ]]; then
2175                 build_old_new_git "$olddir" "$newdir"
2176         elif [[ $SCM_MODE == "subversion" ]]; then
2177                 build_old_new_subversion "$olddir" "$newdir"
2178         elif [[ $SCM_MODE == "unknown" ]]; then
2179                 build_old_new_unknown "$olddir" "$newdir"
2180         fi
2181 
2182         if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2183                 print "*** Error: file not in parent or child"
2184                 return 1
2185         fi
2186         return 0
2187 }
2188 
2189 
2190 #
2191 # Usage message.
2192 #
2193 function usage
2194 {
2195         print 'Usage:\twebrev [common-options]
2196         webrev [common-options] ( <file> | - )
2197         webrev [common-options] -w <wx file>
2198 
2199 Options:
2200         -c <revision>: generate webrev for single revision (git only)
2201         -C <filename>: Use <filename> for the information tracking configuration.
2202         -D: delete remote webrev
2203         -h <revision>: specify "head" revision for comparison (git only)
2204         -i <filename>: Include <filename> in the index.html file.
2205         -I <filename>: Use <filename> for the information tracking registry.
2206         -n: do not generate the webrev (useful with -U)
2207         -O: Print bugids/arc cases suitable for OpenSolaris.
2208         -o <outdir>: Output webrev to specified directory.
2209         -p <compare-against>: Use specified parent wkspc or basis for comparison
2210         -t <remote_target>: Specify remote destination for webrev upload
2211         -U: upload the webrev to remote destination
2212         -w <wxfile>: Use specified wx active file.
2213 
2214 Environment:
2215         WDIR: Control the output directory.
2216         WEBREV_TRASH_DIR: Set directory for webrev delete.
2217 
2218 SCM Environment:
2219         CODEMGR_WS: Workspace location.
2220         CODEMGR_PARENT: Parent workspace location.
2221 '
2222 
2223         exit 2
2224 }
2225 
2226 #
2227 #
2228 # Main program starts here
2229 #
2230 #
2231 
2232 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2233 
2234 set +o noclobber
2235 
2236 PATH=$(/bin/dirname "$(whence $0)"):$PATH
2237 
2238 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2239 [[ -z $WX ]] && WX=`look_for_prog wx`
2240 [[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
2241 [[ -z $GIT ]] && GIT=`look_for_prog git`
2242 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2243 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2244 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2245 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2246 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2247 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2248 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2249 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2250 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2251 [[ -z $SCP ]] && SCP=`look_for_prog scp`
2252 [[ -z $SED ]] && SED=`look_for_prog sed`
2253 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2254 [[ -z $SORT ]] && SORT=`look_for_prog sort`
2255 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2256 [[ -z $GREP ]] && GREP=`look_for_prog grep`
2257 [[ -z $FIND ]] && FIND=`look_for_prog find`
2258 [[ -z $MANDOC ]] && MANDOC=`look_for_prog mandoc`
2259 [[ -z $COL ]] && COL=`look_for_prog col`
2260 
2261 # set name of trash directory for remote webrev deletion
2262 TRASH_DIR=".trash"
2263 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2264 
2265 if [[ ! -x $PERL ]]; then
2266         print -u2 "Error: No perl interpreter found.  Exiting."
2267         exit 1
2268 fi
2269 
2270 if [[ ! -x $WHICH_SCM ]]; then
2271         print -u2 "Error: Could not find which_scm.  Exiting."
2272         exit 1
2273 fi
2274 
2275 #
2276 # These aren't fatal, but we want to note them to the user.
2277 # We don't warn on the absence of 'wx' until later when we've
2278 # determined that we actually need to try to invoke it.
2279 #
2280 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2281 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2282 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2283 
2284 # Declare global total counters.
2285 integer TOTL TINS TDEL TMOD TUNC
2286 
2287 # default remote host for upload/delete
2288 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2289 # prefixes for upload targets
2290 typeset -r rsync_prefix="rsync://"
2291 typeset -r ssh_prefix="ssh://"
2292 
2293 cflag=
2294 Cflag=
2295 Dflag=
2296 flist_mode=
2297 flist_file=
2298 hflag=
2299 iflag=
2300 Iflag=
2301 lflag=
2302 Nflag=
2303 nflag=
2304 Oflag=
2305 oflag=
2306 pflag=
2307 tflag=
2308 uflag=
2309 Uflag=
2310 wflag=
2311 remote_target=
2312 
2313 #
2314 # NOTE: when adding/removing options it is necessary to sync the list
2315 #       with usr/src/tools/onbld/hgext/cdm.py
2316 #
2317 while getopts "c:C:Dh:i:I:lnNo:Op:t:Uw" opt
2318 do
2319         case $opt in
2320         c)      cflag=1
2321                 codemgr_head=$OPTARG
2322                 codemgr_parent=$OPTARG~1;;
2323 
2324         C)      Cflag=1
2325                 ITSCONF=$OPTARG;;
2326 
2327         D)      Dflag=1;;
2328 
2329         h)      hflag=1
2330                 codemgr_head=$OPTARG;;
2331 
2332         i)      iflag=1
2333                 INCLUDE_FILE=$OPTARG;;
2334 
2335         I)      Iflag=1
2336                 ITSREG=$OPTARG;;
2337 
2338         N)      Nflag=1;;
2339 
2340         n)      nflag=1;;
2341 
2342         O)      Oflag=1;;
2343 
2344         o)      oflag=1
2345                 # Strip the trailing slash to correctly form remote target.
2346                 WDIR=${OPTARG%/};;
2347 
2348         p)      pflag=1
2349                 codemgr_parent=$OPTARG;;
2350 
2351         t)      tflag=1
2352                 remote_target=$OPTARG;;
2353 
2354         U)      Uflag=1;;
2355 
2356         w)      wflag=1;;
2357 
2358         ?)      usage;;
2359         esac
2360 done
2361 
2362 FLIST=/tmp/$$.flist
2363 
2364 if [[ -n $wflag && -n $lflag ]]; then
2365         usage
2366 fi
2367 
2368 # more sanity checking
2369 if [[ -n $nflag && -z $Uflag ]]; then
2370         print "it does not make sense to skip webrev generation" \
2371             "without -U"
2372         exit 1
2373 fi
2374 
2375 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2376         echo "remote target has to be used only for upload or delete"
2377         exit 1
2378 fi
2379 
2380 #
2381 # For the invocation "webrev -n -U" with no other options, webrev will assume
2382 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2383 # $(basename ${CWS}).  So we need to get CWS set before we skip any remaining
2384 # logic.
2385 #
2386 $WHICH_SCM | read SCM_MODE junk || exit 1
2387 if [[ $SCM_MODE == "mercurial" ]]; then
2388         #
2389         # Mercurial priorities:
2390         # 1. hg root from CODEMGR_WS environment variable
2391         # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under
2392         #    usr/closed when we run webrev
2393         # 2. hg root from directory of invocation
2394         #
2395         if [[ ${PWD} =~ "usr/closed" ]]; then
2396                 testparent=${CODEMGR_WS}/usr/closed
2397                 # If we're in OpenSolaris mode, we enforce a minor policy:
2398                 # help to make sure the reviewer doesn't accidentally publish
2399                 # source which is under usr/closed
2400                 if [[ -n "$Oflag" ]]; then
2401                         print -u2 "OpenSolaris output not permitted with" \
2402                             "usr/closed changes"
2403                         exit 1
2404                 fi
2405         else
2406                 testparent=${CODEMGR_WS}
2407         fi
2408         [[ -z $codemgr_ws && -n $testparent ]] && \
2409             codemgr_ws=$(hg root -R $testparent 2>/dev/null)
2410         [[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null)
2411         CWS=$codemgr_ws
2412 elif [[ $SCM_MODE == "git" ]]; then
2413         #
2414         # Git priorities:
2415         # 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2416         # 2. git rev-parse --git-dir from directory of invocation
2417         #
2418         [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2419             codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \
2420                 2>/dev/null)
2421         [[ -z $codemgr_ws ]] && \
2422             codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null)
2423 
2424         if [[ "$codemgr_ws" == ".git" ]]; then
2425                 codemgr_ws="${PWD}/${codemgr_ws}"
2426         fi
2427 
2428         if [[ "$codemgr_ws" = *"/.git" ]]; then
2429                 codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git'
2430         fi
2431         CWS="$codemgr_ws"
2432 elif [[ $SCM_MODE == "subversion" ]]; then
2433         #
2434         # Subversion priorities:
2435         # 1. CODEMGR_WS from environment
2436         # 2. Relative path from current directory to SVN repository root
2437         #
2438         if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2439                 CWS=$CODEMGR_WS
2440         else
2441                 svn info | while read line; do
2442                         if [[ $line == "URL: "* ]]; then
2443                                 url=${line#URL: }
2444                         elif [[ $line == "Repository Root: "* ]]; then
2445                                 repo=${line#Repository Root: }
2446                         fi
2447                 done
2448 
2449                 rel=${url#$repo}
2450                 CWS=${PWD%$rel}
2451         fi
2452 fi
2453 
2454 #
2455 # If no SCM has been determined, take either the environment setting
2456 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2457 #
2458 if [[ -z ${CWS} ]]; then
2459         CWS=${CODEMGR_WS:-.}
2460 fi
2461 
2462 #
2463 # If the command line options indicate no webrev generation, either
2464 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2465 # ton of logic we can skip.
2466 #
2467 # Instead of increasing indentation, we intentionally leave this loop
2468 # body open here, and exit via break from multiple points within.
2469 # Search for DO_EVERYTHING below to find the break points and closure.
2470 #
2471 for do_everything in 1; do
2472 
2473 # DO_EVERYTHING: break point
2474 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2475         break
2476 fi
2477 
2478 #
2479 # If this manually set as the parent, and it appears to be an earlier webrev,
2480 # then note that fact and set the parent to the raw_files/new subdirectory.
2481 #
2482 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2483         parent_webrev=$(readlink -f "$codemgr_parent")
2484         codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new")
2485 fi
2486 
2487 if [[ -z $wflag && -z $lflag ]]; then
2488         shift $(($OPTIND - 1))
2489 
2490         if [[ $1 == "-" ]]; then
2491                 cat > $FLIST
2492                 flist_mode="stdin"
2493                 flist_done=1
2494                 shift
2495         elif [[ -n $1 ]]; then
2496                 if [[ ! -r $1 ]]; then
2497                         print -u2 "$1: no such file or not readable"
2498                         usage
2499                 fi
2500                 cat $1 > $FLIST
2501                 flist_mode="file"
2502                 flist_file=$1
2503                 flist_done=1
2504                 shift
2505         else
2506                 flist_mode="auto"
2507         fi
2508 fi
2509 
2510 #
2511 # Before we go on to further consider -l and -w, work out which SCM we think
2512 # is in use.
2513 #
2514 case "$SCM_MODE" in
2515 mercurial|git|subversion)
2516         ;;
2517 unknown)
2518         if [[ $flist_mode == "auto" ]]; then
2519                 print -u2 "Unable to determine SCM in use and file list not specified"
2520                 print -u2 "See which_scm(1) for SCM detection information."
2521                 exit 1
2522         fi
2523         ;;
2524 *)
2525         if [[ $flist_mode == "auto" ]]; then
2526                 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2527                 exit 1
2528         fi
2529         ;;
2530 esac
2531 
2532 print -u2 "   SCM detected: $SCM_MODE"
2533 
2534 if [[ -n $wflag ]]; then
2535         #
2536         # If the -w is given then assume the file list is in Bonwick's "wx"
2537         # command format, i.e.  pathname lines alternating with SCCS comment
2538         # lines with blank lines as separators.  Use the SCCS comments later
2539         # in building the index.html file.
2540         #
2541         shift $(($OPTIND - 1))
2542         wxfile=$1
2543         if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2544                 if [[ -r $CODEMGR_WS/wx/active ]]; then
2545                         wxfile=$CODEMGR_WS/wx/active
2546                 fi
2547         fi
2548 
2549         [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2550             "be auto-detected (check \$CODEMGR_WS)" && exit 1
2551 
2552         if [[ ! -r $wxfile ]]; then
2553                 print -u2 "$wxfile: no such file or not readable"
2554                 usage
2555         fi
2556 
2557         print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2558         flist_from_wx $wxfile
2559         flist_done=1
2560         if [[ -n "$*" ]]; then
2561                 shift
2562         fi
2563 elif [[ $flist_mode == "stdin" ]]; then
2564         print -u2 " File list from: standard input"
2565 elif [[ $flist_mode == "file" ]]; then
2566         print -u2 " File list from: $flist_file"
2567 fi
2568 
2569 if [[ $# -gt 0 ]]; then
2570         print -u2 "WARNING: unused arguments: $*"
2571 fi
2572 
2573 #
2574 # Before we entered the DO_EVERYTHING loop, we should have already set CWS
2575 # and CODEMGR_WS as needed.  Here, we set the parent workspace.
2576 #
2577 if [[ $SCM_MODE == "mercurial" ]]; then
2578         #
2579         # Parent can either be specified with -p
2580         # Specified with CODEMGR_PARENT in the environment
2581         # or taken from hg's default path.
2582         #
2583 
2584         if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2585                 codemgr_parent=$CODEMGR_PARENT
2586         fi
2587 
2588         if [[ -z $codemgr_parent ]]; then
2589                 codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
2590         fi
2591 
2592         PWS=$codemgr_parent
2593 
2594         #
2595         # If the parent is a webrev, we want to do some things against
2596         # the natural workspace parent (file list, comments, etc)
2597         #
2598         if [[ -n $parent_webrev ]]; then
2599                 real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
2600         else
2601                 real_parent=$PWS
2602         fi
2603 
2604         #
2605         # If hg-active exists, then we run it.  In the case of no explicit
2606         # flist given, we'll use it for our comments.  In the case of an
2607         # explicit flist given we'll try to use it for comments for any
2608         # files mentioned in the flist.
2609         #
2610         if [[ -z $flist_done ]]; then
2611                 flist_from_mercurial $CWS $real_parent
2612                 flist_done=1
2613         fi
2614 
2615         #
2616         # If we have a file list now, pull out any variables set
2617         # therein.  We do this now (rather than when we possibly use
2618         # hg-active to find comments) to avoid stomping specifications
2619         # in the user-specified flist.
2620         #
2621         if [[ -n $flist_done ]]; then
2622                 env_from_flist
2623         fi
2624 
2625         #
2626         # Only call hg-active if we don't have a wx formatted file already
2627         #
2628         if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2629                 print "  Comments from: hg-active -p $real_parent ...\c"
2630                 hg_active_wxfile $CWS $real_parent
2631                 print " Done."
2632         fi
2633 
2634         #
2635         # At this point we must have a wx flist either from hg-active,
2636         # or in general.  Use it to try and find our parent revision,
2637         # if we don't have one.
2638         #
2639         if [[ -z $HG_PARENT ]]; then
2640                 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2641         fi
2642 
2643         #
2644         # If we still don't have a parent, we must have been given a
2645         # wx-style active list with no HG_PARENT specification, run
2646         # hg-active and pull an HG_PARENT out of it, ignore the rest.
2647         #
2648         if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2649                 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2650                     eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2651         elif [[ -z $HG_PARENT ]]; then
2652                 print -u2 "Error: Cannot discover parent revision"
2653                 exit 1
2654         fi
2655 
2656         pnode=$(trim_digest $HG_PARENT)
2657         PRETTY_PWS="${PWS} (at ${pnode})"
2658         cnode=$(hg parent -R $codemgr_ws --template '{node|short}' \
2659             2>/dev/null)
2660         PRETTY_CWS="${CWS} (at ${cnode})"}
2661 elif [[ $SCM_MODE == "git" ]]; then
2662         # Check that "head" revision specified with -c or -h is sane
2663         if [[ -n $cflag || -n $hflag ]]; then
2664                 head_rev=$($GIT rev-parse --verify --quiet "$codemgr_head")
2665                 if [[ -z $head_rev ]]; then
2666                         print -u2 "Error: bad revision ${codemgr_head}"
2667                         exit 1
2668                 fi
2669         fi
2670 
2671         if [[ -z $codemgr_head ]]; then
2672                 codemgr_head="HEAD";
2673         fi
2674 
2675         # Parent can either be specified with -p, or specified with
2676         # CODEMGR_PARENT in the environment.
2677         if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2678                 codemgr_parent=$CODEMGR_PARENT
2679         fi
2680 
2681         # Try to figure out the parent based on the branch the current
2682         # branch is tracking, if we fail, use origin/master
2683         this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2684         par_branch="origin/master"
2685 
2686         # If we're not on a branch there's nothing we can do
2687         if [[ $this_branch != "(no branch)" ]]; then
2688                 $GIT for-each-ref                                       \
2689                     --format='%(refname:short) %(upstream:short)'       \
2690                     refs/heads/ |                                       \
2691                     while read local remote; do
2692                         if [[ "$local" == "$this_branch" ]]; then
2693                                 par_branch="$remote"
2694                         fi
2695                 done
2696         fi
2697 
2698         if [[ -z $codemgr_parent ]]; then
2699                 codemgr_parent=$par_branch
2700         fi
2701         PWS=$codemgr_parent
2702 
2703         #
2704         # If the parent is a webrev, we want to do some things against
2705         # the natural workspace parent (file list, comments, etc)
2706         #
2707         if [[ -n $parent_webrev ]]; then
2708                 real_parent=$par_branch
2709         else
2710                 real_parent=$PWS
2711         fi
2712 
2713         if [[ -z $flist_done ]]; then
2714                 flist_from_git "$codemgr_head" "$real_parent"
2715                 flist_done=1
2716         fi
2717 
2718         #
2719         # If we have a file list now, pull out any variables set
2720         # therein.
2721         #
2722         if [[ -n $flist_done ]]; then
2723                 env_from_flist
2724         fi
2725 
2726         #
2727         # If we don't have a wx-format file list, build one we can pull change
2728         # comments from.
2729         #
2730         if [[ -z $wxfile ]]; then
2731                 print "  Comments from: git...\c"
2732                 git_wxfile "$codemgr_head" "$real_parent"
2733                 print " Done."
2734         fi
2735 
2736         if [[ -z $GIT_PARENT ]]; then
2737                 GIT_PARENT=$($GIT merge-base "$real_parent" "$codemgr_head")
2738         fi
2739         if [[ -z $GIT_PARENT ]]; then
2740                 print -u2 "Error: Cannot discover parent revision"
2741                 exit 1
2742         fi
2743 
2744         pnode=$(trim_digest $GIT_PARENT)
2745 
2746         if [[ -n $cflag ]]; then
2747                 PRETTY_PWS="previous revision (at ${pnode})"
2748         elif [[ $real_parent == */* ]]; then
2749                 origin=$(echo $real_parent | cut -d/ -f1)
2750                 origin=$($GIT remote -v | \
2751                     $AWK '$1 == "'$origin'" { print $2; exit }')
2752                 PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2753         elif [[ -n $pflag && -z $parent_webrev ]]; then
2754                 PRETTY_PWS="${CWS} (explicit revision ${pnode})"
2755         else
2756                 PRETTY_PWS="${PWS} (at ${pnode})"
2757         fi
2758 
2759         cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 \
2760             ${codemgr_head} 2>/dev/null)
2761 
2762         if [[ -n $cflag || -n $hflag ]]; then
2763                 PRETTY_CWS="${CWS} (explicit head at ${cnode})"
2764         else
2765                 PRETTY_CWS="${CWS} (at ${cnode})"
2766         fi
2767 elif [[ $SCM_MODE == "subversion" ]]; then
2768 
2769         #
2770         # We only will have a real parent workspace in the case one
2771         # was specified (be it an older webrev, or another checkout).
2772         #
2773         [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2774 
2775         if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2776                 flist_from_subversion $CWS $OLDPWD
2777         fi
2778 else
2779         if [[ $SCM_MODE == "unknown" ]]; then
2780                 print -u2 "    Unknown type of SCM in use"
2781         else
2782                 print -u2 "    Unsupported SCM in use: $SCM_MODE"
2783         fi
2784 
2785         env_from_flist
2786 
2787         if [[ -z $CODEMGR_WS ]]; then
2788                 print -u2 "SCM not detected/supported and " \
2789                     "CODEMGR_WS not specified"
2790                 exit 1
2791                 fi
2792 
2793         if [[ -z $CODEMGR_PARENT ]]; then
2794                 print -u2 "SCM not detected/supported and " \
2795                     "CODEMGR_PARENT not specified"
2796                 exit 1
2797         fi
2798 
2799         CWS=$CODEMGR_WS
2800         PWS=$CODEMGR_PARENT
2801 fi
2802 
2803 #
2804 # If the user didn't specify a -i option, check to see if there is a
2805 # webrev-info file in the workspace directory.
2806 #
2807 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2808         iflag=1
2809         INCLUDE_FILE="$CWS/webrev-info"
2810 fi
2811 
2812 if [[ -n $iflag ]]; then
2813         if [[ ! -r $INCLUDE_FILE ]]; then
2814                 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2815                     "not readable."
2816                 exit 1
2817         else
2818                 #
2819                 # $INCLUDE_FILE may be a relative path, and the script alters
2820                 # PWD, so we just stash a copy in /tmp.
2821                 #
2822                 cp $INCLUDE_FILE /tmp/$$.include
2823         fi
2824 fi
2825 
2826 # DO_EVERYTHING: break point
2827 if [[ -n $Nflag ]]; then
2828         break
2829 fi
2830 
2831 typeset -A itsinfo
2832 typeset -r its_sed_script=/tmp/$$.its_sed
2833 valid_prefixes=
2834 if [[ -z $nflag ]]; then
2835         DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg"
2836         if [[ -n $Iflag ]]; then
2837                 REGFILE=$ITSREG
2838         elif [[ -r $HOME/.its.reg ]]; then
2839                 REGFILE=$HOME/.its.reg
2840         else
2841                 REGFILE=$DEFREGFILE
2842         fi
2843         if [[ ! -r $REGFILE ]]; then
2844                 print "ERROR: Unable to read database registry file $REGFILE"
2845                 exit 1
2846         elif [[ $REGFILE != $DEFREGFILE ]]; then
2847                 print "   its.reg from: $REGFILE"
2848         fi
2849 
2850         $SED -e '/^#/d' -e '/^[         ]*$/d' $REGFILE | while read LINE; do
2851 
2852                 name=${LINE%%=*}
2853                 value="${LINE#*=}"
2854 
2855                 if [[ $name == PREFIX ]]; then
2856                         p=${value}
2857                         valid_prefixes="${p} ${valid_prefixes}"
2858                 else
2859                         itsinfo["${p}_${name}"]="${value}"
2860                 fi
2861         done
2862 
2863 
2864         DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf"
2865         CONFFILES=$DEFCONFFILE
2866         if [[ -r $HOME/.its.conf ]]; then
2867                 CONFFILES="${CONFFILES} $HOME/.its.conf"
2868         fi
2869         if [[ -n $Cflag ]]; then
2870                 CONFFILES="${CONFFILES} ${ITSCONF}"
2871         fi
2872         its_domain=
2873         its_priority=
2874         for cf in ${CONFFILES}; do
2875                 if [[ ! -r $cf ]]; then
2876                         print "ERROR: Unable to read database configuration file $cf"
2877                         exit 1
2878                 elif [[ $cf != $DEFCONFFILE ]]; then
2879                         print "       its.conf: reading $cf"
2880                 fi
2881                 $SED -e '/^#/d' -e '/^[         ]*$/d' $cf | while read LINE; do
2882                     eval "${LINE}"
2883                 done
2884         done
2885 
2886         #
2887         # If an information tracking system is explicitly identified by prefix,
2888         # we want to disregard the specified priorities and resolve it accordingly.
2889         #
2890         # To that end, we'll build a sed script to do each valid prefix in turn.
2891         #
2892         for p in ${valid_prefixes}; do
2893                 #
2894                 # When an informational URL was provided, translate it to a
2895                 # hyperlink.  When omitted, simply use the prefix text.
2896                 #
2897                 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2898                         itsinfo["${p}_INFO"]=${p}
2899                 else
2900                         itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2901                 fi
2902 
2903                 #
2904                 # Assume that, for this invocation of webrev, all references
2905                 # to this information tracking system should resolve through
2906                 # the same URL.
2907                 #
2908                 # If the caller specified -O, then always use EXTERNAL_URL.
2909                 #
2910                 # Otherwise, look in the list of domains for a matching
2911                 # INTERNAL_URL.
2912                 #
2913                 [[ -z $Oflag ]] && for d in ${its_domain}; do
2914                         if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2915                                 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2916                                 break
2917                         fi
2918                 done
2919                 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2920                         itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2921                 fi
2922 
2923                 #
2924                 # Turn the destination URL into a hyperlink
2925                 #
2926                 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2927 
2928                 # The character class below contains a literal tab
2929                 print "/^${p}[:         ]/ {
2930                                 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2931                                 s;^${p};${itsinfo[${p}_INFO]};
2932                         }" >> ${its_sed_script}
2933         done
2934 
2935         #
2936         # The previous loop took care of explicit specification.  Now use
2937         # the configured priorities to attempt implicit translations.
2938         #
2939         for p in ${its_priority}; do
2940                 print "/^${itsinfo[${p}_REGEX]}[        ]/ {
2941                                 s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2942                         }" >> ${its_sed_script}
2943         done
2944 fi
2945 
2946 #
2947 # Search for DO_EVERYTHING above for matching "for" statement
2948 # and explanation of this terminator.
2949 #
2950 done
2951 
2952 #
2953 # Output directory.
2954 #
2955 WDIR=${WDIR:-$CWS/webrev}
2956 
2957 #
2958 # Name of the webrev, derived from the workspace name or output directory;
2959 # in the future this could potentially be an option.
2960 #
2961 if [[ -n $oflag ]]; then
2962         WNAME=${WDIR##*/}
2963 else
2964         WNAME=${CWS##*/}
2965 fi
2966 
2967 # Make sure remote target is well formed for remote upload/delete.
2968 if [[ -n $Dflag || -n $Uflag ]]; then
2969         #
2970         # If remote target is not specified, build it from scratch using
2971         # the default values.
2972         #
2973         if [[ -z $tflag ]]; then
2974                 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2975         else
2976                 #
2977                 # Check upload target prefix first.
2978                 #
2979                 if [[ "${remote_target}" != ${rsync_prefix}* &&
2980                     "${remote_target}" != ${ssh_prefix}* ]]; then
2981                         print "ERROR: invalid prefix of upload URI" \
2982                             "($remote_target)"
2983                         exit 1
2984                 fi
2985                 #
2986                 # If destination specification is not in the form of
2987                 # host_spec:remote_dir then assume it is just remote hostname
2988                 # and append a colon and destination directory formed from
2989                 # local webrev directory name.
2990                 #
2991                 typeset target_no_prefix=${remote_target##*://}
2992                 if [[ ${target_no_prefix} == *:* ]]; then
2993                         if [[ "${remote_target}" == *: ]]; then
2994                                 remote_target=${remote_target}${WNAME}
2995                         fi
2996                 else
2997                         if [[ ${target_no_prefix} == */* ]]; then
2998                                 print "ERROR: badly formed upload URI" \
2999                                         "($remote_target)"
3000                                 exit 1
3001                         else
3002                                 remote_target=${remote_target}:${WNAME}
3003                         fi
3004                 fi
3005         fi
3006 
3007         #
3008         # Strip trailing slash. Each upload method will deal with directory
3009         # specification separately.
3010         #
3011         remote_target=${remote_target%/}
3012 fi
3013 
3014 #
3015 # Option -D by itself (option -U not present) implies no webrev generation.
3016 #
3017 if [[ -z $Uflag && -n $Dflag ]]; then
3018         delete_webrev 1 1
3019         exit $?
3020 fi
3021 
3022 #
3023 # Do not generate the webrev, just upload it or delete it.
3024 #
3025 if [[ -n $nflag ]]; then
3026         if [[ -n $Dflag ]]; then
3027                 delete_webrev 1 1
3028                 (( $? == 0 )) || exit $?
3029         fi
3030         if [[ -n $Uflag ]]; then
3031                 upload_webrev
3032                 exit $?
3033         fi
3034 fi
3035 
3036 if [ "${WDIR%%/*}" ]; then
3037         WDIR=$PWD/$WDIR
3038 fi
3039 
3040 if [[ ! -d $WDIR ]]; then
3041         mkdir -p $WDIR
3042         (( $? != 0 )) && exit 1
3043 fi
3044 
3045 #
3046 # Summarize what we're going to do.
3047 #
3048 print "      Workspace: ${PRETTY_CWS:-$CWS}"
3049 if [[ -n $parent_webrev ]]; then
3050         print "Compare against: webrev at $parent_webrev"
3051 else
3052         print "Compare against: ${PRETTY_PWS:-$PWS}"
3053 fi
3054 
3055 [[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
3056 print "      Output to: $WDIR"
3057 
3058 #
3059 # Save the file list in the webrev dir
3060 #
3061 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
3062 
3063 rm -f $WDIR/$WNAME.patch
3064 rm -f $WDIR/$WNAME.ps
3065 rm -f $WDIR/$WNAME.pdf
3066 
3067 touch $WDIR/$WNAME.patch
3068 
3069 print "   Output Files:"
3070 
3071 #
3072 # Clean up the file list: Remove comments, blank lines and env variables.
3073 #
3074 $SED -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
3075 FLIST=/tmp/$$.flist.clean
3076 
3077 #
3078 # For Mercurial, create a cache of manifest entries.
3079 #
3080 if [[ $SCM_MODE == "mercurial" ]]; then
3081         #
3082         # Transform the FLIST into a temporary sed script that matches
3083         # relevant entries in the Mercurial manifest as follows:
3084         # 1) The script will be used against the parent revision manifest,
3085         #    so for FLIST lines that have two filenames (a renamed file)
3086         #    keep only the old name.
3087         # 2) Escape all forward slashes the filename.
3088         # 3) Change the filename into another sed command that matches
3089         #    that file in "hg manifest -v" output:  start of line, three
3090         #    octal digits for file permissions, space, a file type flag
3091         #    character, space, the filename, end of line.
3092         # 4) Eliminate any duplicate entries.  (This can occur if a
3093         #    file has been used as the source of an hg cp and it's
3094         #    also been modified in the same changeset.)
3095         #
3096         SEDFILE=/tmp/$$.manifest.sed
3097         $SED '
3098                 s#^[^ ]* ##
3099                 s#/#\\\/#g
3100                 s#^.*$#/^... . &$/p#
3101         ' < $FLIST | $SORT -u > $SEDFILE
3102 
3103         #
3104         # Apply the generated script to the output of "hg manifest -v"
3105         # to get the relevant subset for this webrev.
3106         #
3107         HG_PARENT_MANIFEST=/tmp/$$.manifest
3108         hg -R $CWS manifest -v -r $HG_PARENT |
3109             $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
3110 fi
3111 
3112 #
3113 # First pass through the files: generate the per-file webrev HTML-files.
3114 #
3115 cat $FLIST | while read LINE
3116 do
3117         set - $LINE
3118         P=$1
3119 
3120         #
3121         # Normally, each line in the file list is just a pathname of a
3122         # file that has been modified or created in the child.  A file
3123         # that is renamed in the child workspace has two names on the
3124         # line: new name followed by the old name.
3125         #
3126         oldname=""
3127         oldpath=""
3128         rename=
3129         if [[ $# -eq 2 ]]; then
3130                 PP=$2                   # old filename
3131                 if [[ -f $PP ]]; then
3132                         oldname=" (copied from $PP)"
3133                 else
3134                         oldname=" (renamed from $PP)"
3135                 fi
3136                 oldpath="$PP"
3137                 rename=1
3138                 PDIR=${PP%/*}
3139                 if [[ $PDIR == $PP ]]; then
3140                         PDIR="."   # File at root of workspace
3141                 fi
3142 
3143                 PF=${PP##*/}
3144 
3145                 DIR=${P%/*}
3146                 if [[ $DIR == $P ]]; then
3147                         DIR="."   # File at root of workspace
3148                 fi
3149 
3150                 F=${P##*/}
3151 
3152         else
3153                 DIR=${P%/*}
3154                 if [[ "$DIR" == "$P" ]]; then
3155                         DIR="."   # File at root of workspace
3156                 fi
3157 
3158                 F=${P##*/}
3159 
3160                 PP=$P
3161                 PDIR=$DIR
3162                 PF=$F
3163         fi
3164 
3165         COMM=`getcomments html $P $PP`
3166 
3167         print "\t$P$oldname\n\t\t\c"
3168 
3169         # Make the webrev mirror directory if necessary
3170         mkdir -p $WDIR/$DIR
3171 
3172         #
3173         # We stash old and new files into parallel directories in $WDIR
3174         # and do our diffs there.  This makes it possible to generate
3175         # clean looking diffs which don't have absolute paths present.
3176         #
3177 
3178         build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3179             continue
3180 
3181         #
3182         # Keep the old PWD around, so we can safely switch back after
3183         # diff generation, such that build_old_new runs in a
3184         # consistent environment.
3185         #
3186         OWD=$PWD
3187         cd $WDIR/raw_files
3188 
3189         #
3190         # The "git apply" command does not tolerate the spurious
3191         # "./" that we otherwise insert; be careful not to include
3192         # it in the paths that we pass to diff(1).
3193         #
3194         if [[ $PDIR == "." ]]; then
3195                 ofile=old/$PF
3196         else
3197                 ofile=old/$PDIR/$PF
3198         fi
3199         if [[ $DIR == "." ]]; then
3200                 nfile=new/$F
3201         else
3202                 nfile=new/$DIR/$F
3203         fi
3204 
3205         mv_but_nodiff=
3206         cmp $ofile $nfile > /dev/null 2>&1
3207         if [[ $? == 0 && $rename == 1 ]]; then
3208                 mv_but_nodiff=1
3209         fi
3210 
3211         #
3212         # If we have old and new versions of the file then run the appropriate
3213         # diffs.  This is complicated by a couple of factors:
3214         #
3215         #       - renames must be handled specially: we emit a 'remove'
3216         #         diff and an 'add' diff
3217         #       - new files and deleted files must be handled specially
3218         #       - GNU patch doesn't interpret the output of illumos diff
3219         #         properly when it comes to adds and deletes.  We need to
3220         #         do some "cleansing" transformations:
3221         #           [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
3222         #           [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
3223         #
3224         cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3225         cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3226 
3227         rm -f $WDIR/$DIR/$F.patch
3228         if [[ -z $rename ]]; then
3229                 if [ ! -f "$ofile" ]; then
3230                         diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3231                             > $WDIR/$DIR/$F.patch
3232                 elif [ ! -f "$nfile" ]; then
3233                         diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3234                             > $WDIR/$DIR/$F.patch
3235                 else
3236                         diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3237                 fi
3238         else
3239                 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3240                     > $WDIR/$DIR/$F.patch
3241 
3242                 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3243                     >> $WDIR/$DIR/$F.patch
3244         fi
3245 
3246         #
3247         # Tack the patch we just made onto the accumulated patch for the
3248         # whole wad.
3249         #
3250         cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3251         print " patch\c"
3252 
3253         if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3254                 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3255                 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3256                     > $WDIR/$DIR/$F.cdiff.html
3257                 print " cdiffs\c"
3258 
3259                 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3260                 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3261                     > $WDIR/$DIR/$F.udiff.html
3262                 print " udiffs\c"
3263 
3264                 if [[ -x $WDIFF ]]; then
3265                         $WDIFF -c "$COMM" \
3266                             -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3267                             $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3268                         if [[ $? -eq 0 ]]; then
3269                                 print " wdiffs\c"
3270                         else
3271                                 print " wdiffs[fail]\c"
3272                         fi
3273                 fi
3274 
3275                 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3276                     > $WDIR/$DIR/$F.sdiff.html
3277                 print " sdiffs\c"
3278                 print " frames\c"
3279 
3280                 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3281                 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3282         elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3283                 # renamed file: may also have differences
3284                 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3285         elif [[ -f $nfile ]]; then
3286                 # new file: count added lines
3287                 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3288         elif [[ -f $ofile ]]; then
3289                 # old file: count deleted lines
3290                 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3291         fi
3292 
3293         #
3294         # Check if it's man page, and create plain text, html and raw (ascii)
3295         # output for the new version, as well as diffs against old version.
3296         #
3297         if [[ -f "$nfile" && "$nfile" = *.+([0-9])*([a-zA-Z]) && \
3298             -x $MANDOC && -x $COL ]]; then
3299                 $MANDOC -Tascii $nfile | $COL -b > $nfile.man.txt
3300                 source_to_html txt < $nfile.man.txt > $nfile.man.txt.html
3301                 print " man-txt\c"
3302                 print "$MANCSS" > $WDIR/raw_files/new/$DIR/man.css
3303                 $MANDOC -Thtml -Ostyle=man.css $nfile > $nfile.man.html
3304                 print " man-html\c"
3305                 $MANDOC -Tascii $nfile > $nfile.man.raw
3306                 print " man-raw\c"
3307                 if [[ -f "$ofile" && -z $mv_but_nodiff ]]; then
3308                         $MANDOC -Tascii $ofile | $COL -b > $ofile.man.txt
3309                         ${CDIFFCMD:-diff -bt -C 5} $ofile.man.txt \
3310                             $nfile.man.txt > $WDIR/$DIR/$F.man.cdiff
3311                         diff_to_html $F $DIR/$F "C" "$COMM" < \
3312                             $WDIR/$DIR/$F.man.cdiff > \
3313                             $WDIR/$DIR/$F.man.cdiff.html
3314                         print " man-cdiffs\c"
3315                         ${UDIFFCMD:-diff -bt -U 5} $ofile.man.txt \
3316                             $nfile.man.txt > $WDIR/$DIR/$F.man.udiff
3317                         diff_to_html $F $DIR/$F "U" "$COMM" < \
3318                             $WDIR/$DIR/$F.man.udiff > \
3319                             $WDIR/$DIR/$F.man.udiff.html
3320                         print " man-udiffs\c"
3321                         if [[ -x $WDIFF ]]; then
3322                                 $WDIFF -c "$COMM" -t "$WNAME Wdiff $DIR/$F" \
3323                                     $ofile.man.txt $nfile.man.txt > \
3324                                     $WDIR/$DIR/$F.man.wdiff.html 2>/dev/null
3325                                 if [[ $? -eq 0 ]]; then
3326                                         print " man-wdiffs\c"
3327                                 else
3328                                         print " man-wdiffs[fail]\c"
3329                                 fi
3330                         fi
3331                         sdiff_to_html $ofile.man.txt $nfile.man.txt $F.man $DIR \
3332                             "$COMM" > $WDIR/$DIR/$F.man.sdiff.html
3333                         print " man-sdiffs\c"
3334                         print " man-frames\c"
3335                 fi
3336                 rm -f $ofile.man.txt $nfile.man.txt
3337                 rm -f $WDIR/$DIR/$F.man.cdiff $WDIR/$DIR/$F.man.udiff
3338         fi
3339 
3340         #
3341         # Now we generate the postscript for this file.  We generate diffs
3342         # only in the event that there is delta, or the file is new (it seems
3343         # tree-killing to print out the contents of deleted files).
3344         #
3345         if [[ -f $nfile ]]; then
3346                 ocr=$ofile
3347                 [[ ! -f $ofile ]] && ocr=/dev/null
3348 
3349                 if [[ -z $mv_but_nodiff ]]; then
3350                         textcomm=`getcomments text $P $PP`
3351                         if [[ -x $CODEREVIEW ]]; then
3352                                 $CODEREVIEW -y "$textcomm" \
3353                                     -e $ocr $nfile \
3354                                     > /tmp/$$.psfile 2>/dev/null &&
3355                                     cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3356                                 if [[ $? -eq 0 ]]; then
3357                                         print " ps\c"
3358                                 else
3359                                         print " ps[fail]\c"
3360                                 fi
3361                         fi
3362                 fi
3363         fi
3364 
3365         if [[ -f $ofile ]]; then
3366                 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3367                 print " old\c"
3368         fi
3369 
3370         if [[ -f $nfile ]]; then
3371                 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3372                 print " new\c"
3373         fi
3374 
3375         cd $OWD
3376 
3377         print
3378 done
3379 
3380 frame_nav_js > $WDIR/ancnav.js
3381 frame_navigation > $WDIR/ancnav.html
3382 
3383 if [[ ! -f $WDIR/$WNAME.ps ]]; then
3384         print " Generating PDF: Skipped: no output available"
3385 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3386         print " Generating PDF: \c"
3387         fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3388         print "Done."
3389 else
3390         print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3391 fi
3392 
3393 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3394 # delete it - prevent accidental publishing of closed source
3395 
3396 if [[ -n "$Oflag" ]]; then
3397         $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3398 fi
3399 
3400 # Now build the index.html file that contains
3401 # links to the source files and their diffs.
3402 
3403 cd $CWS
3404 
3405 # Save total changed lines for Code Inspection.
3406 print "$TOTL" > $WDIR/TotalChangedLines
3407 
3408 print "     index.html: \c"
3409 INDEXFILE=$WDIR/index.html
3410 exec 3<&1                        # duplicate stdout to FD3.
3411 exec 1<&-                        # Close stdout.
3412 exec > $INDEXFILE            # Open stdout to index file.
3413 
3414 print "$HTML<head>$STDHEAD"
3415 print "<title>$WNAME</title>"
3416 print "</head>"
3417 print "<body id=\"SUNWwebrev\">"
3418 print "<div class=\"summary\">"
3419 print "<h2>Code Review for $WNAME</h2>"
3420 
3421 print "<table>"
3422 
3423 #
3424 # Get the preparer's name:
3425 #
3426 # If the SCM detected is Mercurial, and the configuration property
3427 # ui.username is available, use that, but be careful to properly escape
3428 # angle brackets (HTML syntax characters) in the email address.
3429 #
3430 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3431 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3432 #
3433 preparer=
3434 if [[ "$SCM_MODE" == mercurial ]]; then
3435         preparer=`hg showconfig ui.username 2>/dev/null`
3436         if [[ -n "$preparer" ]]; then
3437                 preparer="$(echo "$preparer" | html_quote)"
3438         fi
3439 fi
3440 if [[ -z "$preparer" ]]; then
3441         preparer=$(
3442             $PERL -e '
3443                 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3444                 if ($login) {
3445                     $gcos =~ s/\&/ucfirst($login)/e;
3446                     printf "%s (%s)\n", $gcos, $login;
3447                 } else {
3448                     printf "(unknown)\n";
3449                 }
3450         ')
3451 fi
3452 
3453 PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3454 print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3455 print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3456 print "</td></tr>"
3457 print "<tr><th>Compare against:</th><td>"
3458 if [[ -n $parent_webrev ]]; then
3459         print "webrev at $parent_webrev"
3460 else
3461         print "${PRETTY_PWS:-$PWS}"
3462 fi
3463 print "</td></tr>"
3464 print "<tr><th>Summary of changes:</th><td>"
3465 printCI $TOTL $TINS $TDEL $TMOD $TUNC
3466 print "</td></tr>"
3467 
3468 if [[ -f $WDIR/$WNAME.patch ]]; then
3469         wpatch_url="$(print $WNAME.patch | url_encode)"
3470         print "<tr><th>Patch of changes:</th><td>"
3471         print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3472 fi
3473 if [[ -f $WDIR/$WNAME.pdf ]]; then
3474         wpdf_url="$(print $WNAME.pdf | url_encode)"
3475         print "<tr><th>Printable review:</th><td>"
3476         print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3477 fi
3478 
3479 if [[ -n "$iflag" ]]; then
3480         print "<tr><th>Author comments:</th><td><div>"
3481         cat /tmp/$$.include
3482         print "</div></td></tr>"
3483 fi
3484 print "</table>"
3485 print "</div>"
3486 
3487 #
3488 # Second pass through the files: generate the rest of the index file
3489 #
3490 cat $FLIST | while read LINE
3491 do
3492         set - $LINE
3493         P=$1
3494 
3495         if [[ $# == 2 ]]; then
3496                 PP=$2
3497                 oldname="$PP"
3498         else
3499                 PP=$P
3500                 oldname=""
3501         fi
3502 
3503         mv_but_nodiff=
3504         cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3505         if [[ $? == 0 && -n "$oldname" ]]; then
3506                 mv_but_nodiff=1
3507         fi
3508 
3509         DIR=${P%/*}
3510         if [[ $DIR == $P ]]; then
3511                 DIR="."   # File at root of workspace
3512         fi
3513 
3514         # Avoid processing the same file twice.
3515         # It's possible for renamed files to
3516         # appear twice in the file list
3517 
3518         F=$WDIR/$P
3519 
3520         print "<p>"
3521 
3522         # If there's a diffs file, make diffs links
3523 
3524         if [[ -f $F.cdiff.html ]]; then
3525                 cdiff_url="$(print $P.cdiff.html | url_encode)"
3526                 udiff_url="$(print $P.udiff.html | url_encode)"
3527                 sdiff_url="$(print $P.sdiff.html | url_encode)"
3528                 frames_url="$(print $P.frames.html | url_encode)"
3529                 print "<a href=\"$cdiff_url\">Cdiffs</a>"
3530                 print "<a href=\"$udiff_url\">Udiffs</a>"
3531                 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3532                         wdiff_url="$(print $P.wdiff.html | url_encode)"
3533                         print "<a href=\"$wdiff_url\">Wdiffs</a>"
3534                 fi
3535                 print "<a href=\"$sdiff_url\">Sdiffs</a>"
3536                 print "<a href=\"$frames_url\">Frames</a>"
3537         else
3538                 print " ------ ------"
3539                 if [[ -x $WDIFF ]]; then
3540                         print " ------"
3541                 fi
3542                 print " ------ ------"
3543         fi
3544 
3545         # If there's an old file, make the link
3546 
3547         if [[ -f $F-.html ]]; then
3548                 oldfile_url="$(print $P-.html | url_encode)"
3549                 print "<a href=\"$oldfile_url\">Old</a>"
3550         else
3551                 print " ---"
3552         fi
3553 
3554         # If there's an new file, make the link
3555 
3556         if [[ -f $F.html ]]; then
3557                 newfile_url="$(print $P.html | url_encode)"
3558                 print "<a href=\"$newfile_url\">New</a>"
3559         else
3560                 print " ---"
3561         fi
3562 
3563         if [[ -f $F.patch ]]; then
3564                 patch_url="$(print $P.patch | url_encode)"
3565                 print "<a href=\"$patch_url\">Patch</a>"
3566         else
3567                 print " -----"
3568         fi
3569 
3570         if [[ -f $WDIR/raw_files/new/$P ]]; then
3571                 rawfiles_url="$(print raw_files/new/$P | url_encode)"
3572                 print "<a href=\"$rawfiles_url\">Raw</a>"
3573         else
3574                 print " ---"
3575         fi
3576 
3577         print "<b>$P</b>"
3578 
3579         # For renamed files, clearly state whether or not they are modified
3580         if [[ -f "$oldname" ]]; then
3581                 if [[ -n "$mv_but_nodiff" ]]; then
3582                         print "<i>(copied from $oldname)</i>"
3583                 else
3584                         print "<i>(copied and modified from $oldname)</i>"
3585                 fi
3586         elif [[ -n "$oldname" ]]; then
3587                 if [[ -n "$mv_but_nodiff" ]]; then
3588                         print "<i>(renamed from $oldname)</i>"
3589                 else
3590                         print "<i>(renamed and modified from $oldname)</i>"
3591                 fi
3592         fi
3593 
3594         # If there's an old file, but no new file, the file was deleted
3595         if [[ -f $F-.html && ! -f $F.html ]]; then
3596                 print " <i>(deleted)</i>"
3597         fi
3598 
3599         # Check for usr/closed and deleted_files/usr/closed
3600         if [ ! -z "$Oflag" ]; then
3601                 if [[ $P == usr/closed/* || \
3602                     $P == deleted_files/usr/closed/* ]]; then
3603                         print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3604                             "this review</i>"
3605                 fi
3606         fi
3607 
3608         manpage=
3609         if [[ -f $F.man.cdiff.html || \
3610             -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3611                 manpage=1
3612                 print "<br/>man:"
3613         fi
3614 
3615         if [[ -f $F.man.cdiff.html ]]; then
3616                 mancdiff_url="$(print $P.man.cdiff.html | url_encode)"
3617                 manudiff_url="$(print $P.man.udiff.html | url_encode)"
3618                 mansdiff_url="$(print $P.man.sdiff.html | url_encode)"
3619                 manframes_url="$(print $P.man.frames.html | url_encode)"
3620                 print "<a href=\"$mancdiff_url\">Cdiffs</a>"
3621                 print "<a href=\"$manudiff_url\">Udiffs</a>"
3622                 if [[ -f $F.man.wdiff.html && -x $WDIFF ]]; then
3623                         manwdiff_url="$(print $P.man.wdiff.html | url_encode)"
3624                         print "<a href=\"$manwdiff_url\">Wdiffs</a>"
3625                 fi
3626                 print "<a href=\"$mansdiff_url\">Sdiffs</a>"
3627                 print "<a href=\"$manframes_url\">Frames</a>"
3628         elif [[ -n $manpage ]]; then
3629                 print " ------ ------"
3630                 if [[ -x $WDIFF ]]; then
3631                         print " ------"
3632                 fi
3633                 print " ------ ------"
3634         fi
3635 
3636         if [[ -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3637                 mantxt_url="$(print raw_files/new/$P.man.txt.html | url_encode)"
3638                 print "<a href=\"$mantxt_url\">TXT</a>"
3639                 manhtml_url="$(print raw_files/new/$P.man.html | url_encode)"
3640                 print "<a href=\"$manhtml_url\">HTML</a>"
3641                 manraw_url="$(print raw_files/new/$P.man.raw | url_encode)"
3642                 print "<a href=\"$manraw_url\">Raw</a>"
3643         elif [[ -n $manpage ]]; then
3644                 print " --- ---- ---"
3645         fi
3646 
3647         print "</p>"
3648 
3649         # Insert delta comments
3650         print "<blockquote><pre>"
3651         getcomments html $P $PP
3652         print "</pre>"
3653 
3654         # Add additional comments comment
3655         print "<!-- Add comments to explain changes in $P here -->"
3656 
3657         # Add count of changes.
3658         if [[ -f $F.count ]]; then
3659             cat $F.count
3660             rm $F.count
3661         fi
3662 
3663         if [[ $SCM_MODE == "mercurial" ||
3664             $SCM_MODE == "unknown" ]]; then
3665                 # Include warnings for important file mode situations:
3666                 # 1) New executable files
3667                 # 2) Permission changes of any kind
3668                 # 3) Existing executable files
3669                 old_mode=
3670                 if [[ -f $WDIR/raw_files/old/$PP ]]; then
3671                         old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3672                 fi
3673 
3674                 new_mode=
3675                 if [[ -f $WDIR/raw_files/new/$P ]]; then
3676                         new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3677                 fi
3678 
3679                 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3680                         print "<span class=\"chmod\">"
3681                         print "<p>new executable file: mode $new_mode</p>"
3682                         print "</span>"
3683                 elif [[ -n "$old_mode" && -n "$new_mode" &&
3684                     "$old_mode" != "$new_mode" ]]; then
3685                         print "<span class=\"chmod\">"
3686                         print "<p>mode change: $old_mode to $new_mode</p>"
3687                         print "</span>"
3688                 elif [[ "$new_mode" = *[1357]* ]]; then
3689                         print "<span class=\"chmod\">"
3690                         print "<p>executable file: mode $new_mode</p>"
3691                         print "</span>"
3692                 fi
3693         fi
3694 
3695         print "</blockquote>"
3696 done
3697 
3698 print
3699 print
3700 print "<hr></hr>"
3701 print "<p style=\"font-size: small\">"
3702 print "This code review page was prepared using <b>$0</b>."
3703 print "Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3704 print "illumos</a> project.  The latest version may be obtained"
3705 print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3706 print "</body>"
3707 print "</html>"
3708 
3709 exec 1<&-                        # Close FD 1.
3710 exec 1<&3                        # dup FD 3 to restore stdout.
3711 exec 3<&-                        # close FD 3.
3712 
3713 print "Done."
3714 
3715 #
3716 # If remote deletion was specified and fails do not continue.
3717 #
3718 if [[ -n $Dflag ]]; then
3719         delete_webrev 1 1
3720         (( $? == 0 )) || exit $?
3721 fi
3722 
3723 if [[ -n $Uflag ]]; then
3724         upload_webrev
3725         exit $?
3726 fi