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