1 #!/usr/bin/ksh93 -p
   2 #
   3 # CDDL HEADER START
   4 #
   5 # The contents of this file are subject to the terms of the
   6 # Common Development and Distribution License (the "License").
   7 # You may not use this file except in compliance with the License.
   8 #
   9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10 # or http://www.opensolaris.org/os/licensing.
  11 # See the License for the specific language governing permissions
  12 # and limitations under the License.
  13 #
  14 # When distributing Covered Code, include this CDDL HEADER in each
  15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16 # If applicable, add the following below this CDDL HEADER, with the
  17 # fields enclosed by brackets "[]" replaced with your own identifying
  18 # information: Portions Copyright [yyyy] [name of copyright owner]
  19 #
  20 # CDDL HEADER END
  21 #
  22 
  23 #
  24 # Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
  25 # Copyright 2008, 2010, Richard Lowe
  26 # Copyright 2012 Marcel Telka <marcel@telka.sk>
  27 # Copyright 2014 Bart Coddens <bart.coddens@gmail.com>
  28 # Copyright 2017 Nexenta Systems, Inc.
  29 # Copyright 2016 Joyent, Inc.
  30 # Copyright 2016 RackTop Systems.
  31 #
  32 
  33 #
  34 # This script takes a file list and a workspace and builds a set of html files
  35 # suitable for doing a code review of source changes via a web page.
  36 # Documentation is available via the manual page, webrev.1, or just
  37 # type 'webrev -h'.
  38 #
  39 # Acknowledgements to contributors to webrev are listed in the webrev(1)
  40 # man page.
  41 #
  42 
  43 REMOVED_COLOR=brown
  44 CHANGED_COLOR=blue
  45 NEW_COLOR=blue
  46 
  47 HTML='<?xml version="1.0"?>
  48 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  49     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  50 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
  51 
  52 FRAMEHTML='<?xml version="1.0"?>
  53 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
  54     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
  55 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
  56 
  57 STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta>
  58 <meta http-equiv="Content-Type" content="text/xhtml;charset=utf-8"></meta>
  59 <meta http-equiv="Pragma" content="no-cache"></meta>
  60 <meta http-equiv="Expires" content="-1"></meta>
  61 <!--
  62    Note to customizers: the body of the webrev is IDed as SUNWwebrev
  63    to allow easy overriding by users of webrev via the userContent.css
  64    mechanism available in some browsers.
  65 
  66    For example, to have all "removed" information be red instead of
  67    brown, set a rule in your userContent.css file like:
  68 
  69        body#SUNWwebrev span.removed { color: red ! important; }
  70 -->
  71 <style type="text/css" media="screen">
  72 body {
  73     background-color: #eeeeee;
  74 }
  75 hr {
  76     border: none 0;
  77     border-top: 1px solid #aaa;
  78     height: 1px;
  79 }
  80 div.summary {
  81     font-size: .8em;
  82     border-bottom: 1px solid #aaa;
  83     padding-left: 1em;
  84     padding-right: 1em;
  85 }
  86 div.summary h2 {
  87     margin-bottom: 0.3em;
  88 }
  89 div.summary table th {
  90     text-align: right;
  91     vertical-align: top;
  92     white-space: nowrap;
  93 }
  94 span.lineschanged {
  95     font-size: 0.7em;
  96 }
  97 span.oldmarker {
  98     color: red;
  99     font-size: large;
 100     font-weight: bold;
 101 }
 102 span.newmarker {
 103     color: green;
 104     font-size: large;
 105     font-weight: bold;
 106 }
 107 span.removed {
 108     color: brown;
 109 }
 110 span.changed {
 111     color: blue;
 112 }
 113 span.new {
 114     color: blue;
 115     font-weight: bold;
 116 }
 117 span.chmod {
 118     font-size: 0.7em;
 119     color: #db7800;
 120 }
 121 a.print { font-size: x-small; }
 122 a:hover { background-color: #ffcc99; }
 123 </style>
 124 
 125 <style type="text/css" media="print">
 126 pre { font-size: 0.8em; font-family: courier, monospace; }
 127 span.removed { color: #444; font-style: italic }
 128 span.changed { font-weight: bold; }
 129 span.new { font-weight: bold; }
 130 span.newmarker { font-size: 1.2em; font-weight: bold; }
 131 span.oldmarker { font-size: 1.2em; font-weight: bold; }
 132 a.print {display: none}
 133 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
 134 </style>
 135 '
 136 
 137 #
 138 # UDiffs need a slightly different CSS rule for 'new' items (we don't
 139 # want them to be bolded as we do in cdiffs or sdiffs).
 140 #
 141 UDIFFCSS='
 142 <style type="text/css" media="screen">
 143 span.new {
 144     color: blue;
 145     font-weight: normal;
 146 }
 147 </style>
 148 '
 149 
 150 # CSS for the HTML version of the man pages.
 151 # Current version is from mandoc 1.14.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 # fix_postscript
1284 #
1285 # Merge codereview output files to a single conforming postscript file, by:
1286 #       - removing all extraneous headers/trailers
1287 #       - making the page numbers right
1288 #       - removing pages devoid of contents which confuse some
1289 #         postscript readers.
1290 #
1291 # From Casper.
1292 #
1293 function fix_postscript
1294 {
1295         infile=$1
1296 
1297         cat > /tmp/$$.crmerge.pl << \EOF
1298 
1299         print scalar(<>);         # %!PS-Adobe---
1300         print "%%Orientation: Landscape\n";
1301 
1302         $pno = 0;
1303         $doprint = 1;
1304 
1305         $page = "";
1306 
1307         while (<>) {
1308                 next if (/^%%Pages:\s*\d+/);
1309 
1310                 if (/^%%Page:/) {
1311                         if ($pno == 0 || $page =~ /\)S/) {
1312                                 # Header or single page containing text
1313                                 print "%%Page: ? $pno\n" if ($pno > 0);
1314                                 print $page;
1315                                 $pno++;
1316                         } else {
1317                                 # Empty page, skip it.
1318                         }
1319                         $page = "";
1320                         $doprint = 1;
1321                         next;
1322                 }
1323 
1324                 # Skip from %%Trailer of one document to Endprolog
1325                 # %%Page of the next
1326                 $doprint = 0 if (/^%%Trailer/);
1327                 $page .= $_ if ($doprint);
1328         }
1329 
1330         if ($page =~ /\)S/) {
1331                 print "%%Page: ? $pno\n";
1332                 print $page;
1333         } else {
1334                 $pno--;
1335         }
1336         print "%%Trailer\n%%Pages: $pno\n";
1337 EOF
1338 
1339         $PERL /tmp/$$.crmerge.pl < $infile
1340 }
1341 
1342 
1343 #
1344 # input_cmd | insert_anchors | output_cmd
1345 #
1346 # Flag blocks of difference with sequentially numbered invisible
1347 # anchors.  These are used to drive the frames version of the
1348 # sdiffs output.
1349 #
1350 # NOTE: Anchor zero flags the top of the file irrespective of changes,
1351 # an additional anchor is also appended to flag the bottom.
1352 #
1353 # The script detects changed lines as any line that has a "<span
1354 # class=" string embedded (unchanged lines have no class set and are
1355 # not part of a <span>.  Blank lines (without a sequence number)
1356 # are also detected since they flag lines that have been inserted or
1357 # deleted.
1358 #
1359 function insert_anchors
1360 {
1361         $AWK '
1362         function ia() {
1363                 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1364         }
1365 
1366         BEGIN {
1367                 anc=1;
1368                 inblock=1;
1369                 printf "<pre>\n";
1370         }
1371         NF == 0 || /^<span class=/ {
1372                 if (inblock == 0) {
1373                         ia();
1374                         inblock=1;
1375                 }
1376                 print;
1377                 next;
1378         }
1379         {
1380                 inblock=0;
1381                 print;
1382         }
1383         END {
1384                 ia();
1385 
1386                 printf "<b style=\"font-size: large; color: red\">";
1387                 printf "--- EOF ---</b>"
1388                 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1389                 printf "</pre>"
1390                 printf "<form name=\"eof\">";
1391                 printf "<input name=\"value\" value=\"%d\" " \
1392                     "type=\"hidden\"></input>", anc - 1;
1393                 printf "</form>";
1394         }
1395         ' $1
1396 }
1397 
1398 
1399 #
1400 # relative_dir
1401 #
1402 # Print a relative return path from $1 to $2.  For example if
1403 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1404 # this function would print "../../../../".
1405 #
1406 # In the event that $1 is not in $2 a warning is printed to stderr,
1407 # and $2 is returned-- the result of this is that the resulting webrev
1408 # is not relocatable.
1409 #
1410 function relative_dir
1411 {
1412         typeset cur="${1##$2?(/)}"
1413 
1414         #
1415         # If the first path was specified absolutely, and it does
1416         # not start with the second path, it's an error.
1417         #
1418         if [[ "$cur" = "/${1#/}" ]]; then
1419                 # Should never happen.
1420                 print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1421                 print -u2 "to \"$2\".  Check input paths.  Framed webrev "
1422                 print -u2 "will not be relocatable!"
1423                 print $2
1424                 return
1425         fi
1426 
1427         #
1428         # This is kind of ugly.  The sed script will do the following:
1429         #
1430         # 1. Strip off a leading "." or "./": this is important to get
1431         #    the correct arcnav links for files in $WDIR.
1432         # 2. Strip off a trailing "/": this is not strictly necessary,
1433         #    but is kind of nice, since it doesn't end up in "//" at
1434         #    the end of a relative path.
1435         # 3. Replace all remaining sequences of non-"/" with "..": the
1436         #    assumption here is that each dirname represents another
1437         #    level of relative separation.
1438         # 4. Append a trailing "/" only for non-empty paths: this way
1439         #    the caller doesn't need to duplicate this logic, and does
1440         #    not end up using $RTOP/file for files in $WDIR.
1441         #
1442         print $cur | $SED -e '{
1443                 s:^\./*::
1444                 s:/$::
1445                 s:[^/][^/]*:..:g
1446                 s:^\(..*\)$:\1/:
1447         }'
1448 }
1449 
1450 #
1451 # frame_nav_js
1452 #
1453 # Emit javascript for frame navigation
1454 #
1455 function frame_nav_js
1456 {
1457 cat << \EOF
1458 var myInt;
1459 var scrolling = 0;
1460 var sfactor = 3;
1461 var scount = 10;
1462 
1463 function scrollByPix()
1464 {
1465         if (scount <= 0) {
1466                 sfactor *= 1.2;
1467                 scount = 10;
1468         }
1469         parent.lhs.scrollBy(0, sfactor);
1470         parent.rhs.scrollBy(0, sfactor);
1471         scount--;
1472 }
1473 
1474 function scrollToAnc(num)
1475 {
1476         // Update the value of the anchor in the form which we use as
1477         // storage for this value.  setAncValue() will take care of
1478         // correcting for overflow and underflow of the value and return
1479         // us the new value.
1480         num = setAncValue(num);
1481 
1482         // Set location and scroll back a little to expose previous
1483         // lines.
1484         //
1485         // Note that this could be improved: it is possible although
1486         // complex to compute the x and y position of an anchor, and to
1487         // scroll to that location directly.
1488         //
1489         parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1490         parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1491 
1492         parent.lhs.scrollBy(0, -30);
1493         parent.rhs.scrollBy(0, -30);
1494 }
1495 
1496 function getAncValue()
1497 {
1498         return (parseInt(parent.nav.document.diff.real.value));
1499 }
1500 
1501 function setAncValue(val)
1502 {
1503         if (val <= 0) {
1504                 val = 0;
1505                 parent.nav.document.diff.real.value = val;
1506                 parent.nav.document.diff.display.value = "BOF";
1507                 return (val);
1508         }
1509 
1510         //
1511         // The way we compute the max anchor value is to stash it
1512         // inline in the left and right hand side pages-- it's the same
1513         // on each side, so we pluck from the left.
1514         //
1515         maxval = parent.lhs.document.eof.value.value;
1516         if (val < maxval) {
1517                 parent.nav.document.diff.real.value = val;
1518                 parent.nav.document.diff.display.value = val.toString();
1519                 return (val);
1520         }
1521 
1522         // this must be: val >= maxval
1523         val = maxval;
1524         parent.nav.document.diff.real.value = val;
1525         parent.nav.document.diff.display.value = "EOF";
1526         return (val);
1527 }
1528 
1529 function stopScroll()
1530 {
1531         if (scrolling == 1) {
1532                 clearInterval(myInt);
1533                 scrolling = 0;
1534         }
1535 }
1536 
1537 function startScroll()
1538 {
1539         stopScroll();
1540         scrolling = 1;
1541         myInt = setInterval("scrollByPix()", 10);
1542 }
1543 
1544 function handlePress(b)
1545 {
1546         switch (b) {
1547         case 1:
1548                 scrollToAnc(-1);
1549                 break;
1550         case 2:
1551                 scrollToAnc(getAncValue() - 1);
1552                 break;
1553         case 3:
1554                 sfactor = -3;
1555                 startScroll();
1556                 break;
1557         case 4:
1558                 sfactor = 3;
1559                 startScroll();
1560                 break;
1561         case 5:
1562                 scrollToAnc(getAncValue() + 1);
1563                 break;
1564         case 6:
1565                 scrollToAnc(999999);
1566                 break;
1567         }
1568 }
1569 
1570 function handleRelease(b)
1571 {
1572         stopScroll();
1573 }
1574 
1575 function keypress(ev)
1576 {
1577         var keynum;
1578         var keychar;
1579 
1580         if (window.event) { // IE
1581                 keynum = ev.keyCode;
1582         } else if (ev.which) { // non-IE
1583                 keynum = ev.which;
1584         }
1585 
1586         keychar = String.fromCharCode(keynum);
1587 
1588         if (keychar == "k") {
1589                 handlePress(2);
1590                 return (0);
1591         } else if (keychar == "j" || keychar == " ") {
1592                 handlePress(5);
1593                 return (0);
1594         }
1595 
1596         return (1);
1597 }
1598 
1599 function ValidateDiffNum()
1600 {
1601         var val;
1602         var i;
1603 
1604         val = parent.nav.document.diff.display.value;
1605         if (val == "EOF") {
1606                 scrollToAnc(999999);
1607                 return;
1608         }
1609 
1610         if (val == "BOF") {
1611                 scrollToAnc(0);
1612                 return;
1613         }
1614 
1615         i = parseInt(val);
1616         if (isNaN(i)) {
1617                 parent.nav.document.diff.display.value = getAncValue();
1618         } else {
1619                 scrollToAnc(i);
1620         }
1621 
1622         return (false);
1623 }
1624 EOF
1625 }
1626 
1627 #
1628 # frame_navigation
1629 #
1630 # Output anchor navigation file for framed sdiffs.
1631 #
1632 function frame_navigation
1633 {
1634         print "$HTML<head>$STDHEAD"
1635 
1636         cat << \EOF
1637 <title>Anchor Navigation</title>
1638 <meta http-equiv="Content-Script-Type" content="text/javascript">
1639 <meta http-equiv="Content-Type" content="text/html">
1640 
1641 <style type="text/css">
1642     div.button td { padding-left: 5px; padding-right: 5px;
1643                     background-color: #eee; text-align: center;
1644                     border: 1px #444 outset; cursor: pointer; }
1645     div.button a { font-weight: bold; color: black }
1646     div.button td:hover { background: #ffcc99; }
1647 </style>
1648 EOF
1649 
1650         print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1651 
1652         cat << \EOF
1653 </head>
1654 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1655         onkeypress="keypress(event);">
1656     <noscript lang="javascript">
1657       <center>
1658         <p><big>Framed Navigation controls require Javascript</big><br></br>
1659         Either this browser is incompatable or javascript is not enabled</p>
1660       </center>
1661     </noscript>
1662     <table width="100%" border="0" align="center">
1663         <tr>
1664           <td valign="middle" width="25%">Diff navigation:
1665           Use 'j' and 'k' for next and previous diffs; or use buttons
1666           at right</td>
1667           <td align="center" valign="top" width="50%">
1668             <div class="button">
1669               <table border="0" align="center">
1670                   <tr>
1671                     <td>
1672                       <a onMouseDown="handlePress(1);return true;"
1673                          onMouseUp="handleRelease(1);return true;"
1674                          onMouseOut="handleRelease(1);return true;"
1675                          onClick="return false;"
1676                          title="Go to Beginning Of file">BOF</a></td>
1677                     <td>
1678                       <a onMouseDown="handlePress(3);return true;"
1679                          onMouseUp="handleRelease(3);return true;"
1680                          onMouseOut="handleRelease(3);return true;"
1681                          title="Scroll Up: Press and Hold to accelerate"
1682                          onClick="return false;">Scroll Up</a></td>
1683                     <td>
1684                       <a onMouseDown="handlePress(2);return true;"
1685                          onMouseUp="handleRelease(2);return true;"
1686                          onMouseOut="handleRelease(2);return true;"
1687                          title="Go to previous Diff"
1688                          onClick="return false;">Prev Diff</a>
1689                     </td></tr>
1690 
1691                   <tr>
1692                     <td>
1693                       <a onMouseDown="handlePress(6);return true;"
1694                          onMouseUp="handleRelease(6);return true;"
1695                          onMouseOut="handleRelease(6);return true;"
1696                          onClick="return false;"
1697                          title="Go to End Of File">EOF</a></td>
1698                     <td>
1699                       <a onMouseDown="handlePress(4);return true;"
1700                          onMouseUp="handleRelease(4);return true;"
1701                          onMouseOut="handleRelease(4);return true;"
1702                          title="Scroll Down: Press and Hold to accelerate"
1703                          onClick="return false;">Scroll Down</a></td>
1704                     <td>
1705                       <a onMouseDown="handlePress(5);return true;"
1706                          onMouseUp="handleRelease(5);return true;"
1707                          onMouseOut="handleRelease(5);return true;"
1708                          title="Go to next Diff"
1709                          onClick="return false;">Next Diff</a></td>
1710                   </tr>
1711               </table>
1712             </div>
1713           </td>
1714           <th valign="middle" width="25%">
1715             <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1716                 <input name="display" value="BOF" size="8" type="text"></input>
1717                 <input name="real" value="0" size="8" type="hidden"></input>
1718             </form>
1719           </th>
1720         </tr>
1721     </table>
1722   </body>
1723 </html>
1724 EOF
1725 }
1726 
1727 
1728 
1729 #
1730 # diff_to_html <filename> <filepath> { U | C } <comment>
1731 #
1732 # Processes the output of diff to produce an HTML file representing either
1733 # context or unified diffs.
1734 #
1735 diff_to_html()
1736 {
1737         TNAME=$1
1738         TPATH=$2
1739         DIFFTYPE=$3
1740         COMMENT=$4
1741 
1742         print "$HTML<head>$STDHEAD"
1743         print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1744 
1745         if [[ $DIFFTYPE == "U" ]]; then
1746                 print "$UDIFFCSS"
1747         fi
1748 
1749         cat <<-EOF
1750         </head>
1751         <body id="SUNWwebrev">
1752         <a class="print" href="javascript:print()">Print this page</a>
1753         <pre>$COMMENT</pre>
1754         <pre>
1755         EOF
1756 
1757         html_quote | $AWK '
1758         /^--- new/      { next }
1759         /^\+\+\+ new/   { next }
1760         /^--- old/      { next }
1761         /^\*\*\* old/   { next }
1762         /^\*\*\*\*/     { next }
1763         /^-------/      { printf "<center><h1>%s</h1></center>\n", $0; next }
1764         /^\@\@.*\@\@$/  { printf "</pre><hr></hr><pre>\n";
1765                           printf "<span class=\"newmarker\">%s</span>\n", $0;
1766                           next}
1767 
1768         /^\*\*\*/       { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1769                           next}
1770         /^---/          { printf "<span class=\"newmarker\">%s</span>\n", $0;
1771                           next}
1772         /^\+/           {printf "<span class=\"new\">%s</span>\n", $0; next}
1773         /^!/            {printf "<span class=\"changed\">%s</span>\n", $0; next}
1774         /^-/            {printf "<span class=\"removed\">%s</span>\n", $0; next}
1775                         {printf "%s\n", $0; next}
1776         '
1777 
1778         print "</pre></body></html>\n"
1779 }
1780 
1781 
1782 #
1783 # source_to_html { new | old } <filename>
1784 #
1785 # Process a plain vanilla source file to transform it into an HTML file.
1786 #
1787 source_to_html()
1788 {
1789         WHICH=$1
1790         TNAME=$2
1791 
1792         print "$HTML<head>$STDHEAD"
1793         print "<title>$WNAME $WHICH $TNAME</title>"
1794         print "<body id=\"SUNWwebrev\">"
1795         print "<pre>"
1796         html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1797         print "</pre></body></html>"
1798 }
1799 
1800 #
1801 # comments_from_wx {text|html} filepath
1802 #
1803 # Given the pathname of a file, find its location in a "wx" active
1804 # file list and print the following comment.  Output is either text or
1805 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1806 # are turned into URLs.
1807 #
1808 comments_from_wx()
1809 {
1810         typeset fmt=$1
1811         typeset p=$2
1812 
1813         comm=`$AWK '
1814         $1 == "'$p'" {
1815                 do getline ; while (NF > 0)
1816                 getline
1817                 while (NF > 0) { print ; getline }
1818                 exit
1819         }' < $wxfile`
1820 
1821         if [[ -z $comm ]]; then
1822                 comm="*** NO COMMENTS ***"
1823         fi
1824 
1825         if [[ $fmt == "text" ]]; then
1826                 print -- "$comm"
1827                 return
1828         fi
1829 
1830         print -- "$comm" | html_quote | its2url
1831 
1832 }
1833 
1834 #
1835 # getcomments {text|html} filepath parentpath
1836 #
1837 # Fetch the comments depending on what SCM mode we're in.
1838 #
1839 getcomments()
1840 {
1841         typeset fmt=$1
1842         typeset p=$2
1843         typeset pp=$3
1844 
1845         if [[ -n $Nflag ]]; then
1846                 return
1847         fi
1848 
1849         if [[ -n $wxfile ]]; then
1850                 comments_from_wx $fmt $p
1851         fi
1852 }
1853 
1854 #
1855 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1856 #
1857 # Print out Code Inspection figures similar to sccs-prt(1) format.
1858 #
1859 function printCI
1860 {
1861         integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1862         typeset str
1863         if (( tot == 1 )); then
1864                 str="line"
1865         else
1866                 str="lines"
1867         fi
1868         printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1869             $tot $str $ins $del $mod $unc
1870 }
1871 
1872 
1873 #
1874 # difflines <oldfile> <newfile>
1875 #
1876 # Calculate and emit number of added, removed, modified and unchanged lines,
1877 # and total lines changed, the sum of added + removed + modified.
1878 #
1879 function difflines
1880 {
1881         integer tot mod del ins unc err
1882         typeset filename
1883 
1884         eval $( diff -e $1 $2 | $AWK '
1885         # Change range of lines: N,Nc
1886         /^[0-9]*,[0-9]*c$/ {
1887                 n=split(substr($1,1,length($1)-1), counts, ",");
1888                 if (n != 2) {
1889                         error=2
1890                         exit;
1891                 }
1892                 #
1893                 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1894                 # following would be 5 - 3 = 2! Hence +1 for correction.
1895                 #
1896                 r=(counts[2]-counts[1])+1;
1897 
1898                 #
1899                 # Now count replacement lines: each represents a change instead
1900                 # of a delete, so increment c and decrement r.
1901                 #
1902                 while (getline != /^\.$/) {
1903                         c++;
1904                         r--;
1905                 }
1906                 #
1907                 # If there were more replacement lines than original lines,
1908                 # then r will be negative; in this case there are no deletions,
1909                 # but there are r changes that should be counted as adds, and
1910                 # since r is negative, subtract it from a and add it to c.
1911                 #
1912                 if (r < 0) {
1913                         a-=r;
1914                         c+=r;
1915                 }
1916 
1917                 #
1918                 # If there were more original lines than replacement lines, then
1919                 # r will be positive; in this case, increment d by that much.
1920                 #
1921                 if (r > 0) {
1922                         d+=r;
1923                 }
1924                 next;
1925         }
1926 
1927         # Change lines: Nc
1928         /^[0-9].*c$/ {
1929                 # The first line is a replacement; any more are additions.
1930                 if (getline != /^\.$/) {
1931                         c++;
1932                         while (getline != /^\.$/) a++;
1933                 }
1934                 next;
1935         }
1936 
1937         # Add lines: both Na and N,Na
1938         /^[0-9].*a$/ {
1939                 while (getline != /^\.$/) a++;
1940                 next;
1941         }
1942 
1943         # Delete range of lines: N,Nd
1944         /^[0-9]*,[0-9]*d$/ {
1945                 n=split(substr($1,1,length($1)-1), counts, ",");
1946                 if (n != 2) {
1947                         error=2
1948                         exit;
1949                 }
1950                 #
1951                 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1952                 # following would be 5 - 3 = 2! Hence +1 for correction.
1953                 #
1954                 r=(counts[2]-counts[1])+1;
1955                 d+=r;
1956                 next;
1957         }
1958 
1959         # Delete line: Nd.   For example 10d says line 10 is deleted.
1960         /^[0-9]*d$/ {d++; next}
1961 
1962         # Should not get here!
1963         {
1964                 error=1;
1965                 exit;
1966         }
1967 
1968         # Finish off - print results
1969         END {
1970                 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1971                     (c+d+a), c, d, a, error);
1972         }' )
1973 
1974         # End of $AWK, Check to see if any trouble occurred.
1975         if (( $? > 0 || err > 0 )); then
1976                 print "Unexpected Error occurred reading" \
1977                     "\`diff -e $1 $2\`: \$?=$?, err=" $err
1978                 return
1979         fi
1980 
1981         # Accumulate totals
1982         (( TOTL += tot ))
1983         (( TMOD += mod ))
1984         (( TDEL += del ))
1985         (( TINS += ins ))
1986         # Calculate unchanged lines
1987         unc=`wc -l < $1`
1988         if (( unc > 0 )); then
1989                 (( unc -= del + mod ))
1990                 (( TUNC += unc ))
1991         fi
1992         # print summary
1993         print "<span class=\"lineschanged\">"
1994         printCI $tot $ins $del $mod $unc
1995         print "</span>"
1996 }
1997 
1998 
1999 #
2000 # flist_from_wx
2001 #
2002 # Sets up webrev to source its information from a wx-formatted file.
2003 # Sets the global 'wxfile' variable.
2004 #
2005 function flist_from_wx
2006 {
2007         typeset argfile=$1
2008         if [[ -n ${argfile%%/*} ]]; then
2009                 #
2010                 # If the wx file pathname is relative then make it absolute
2011                 # because the webrev does a "cd" later on.
2012                 #
2013                 wxfile=$PWD/$argfile
2014         else
2015                 wxfile=$argfile
2016         fi
2017 
2018         $AWK '{ c = 1; print;
2019           while (getline) {
2020                 if (NF == 0) { c = -c; continue }
2021                 if (c > 0) print
2022           }
2023         }' $wxfile > $FLIST
2024 
2025         print " Done."
2026 }
2027 
2028 #
2029 # Transform a specified 'git log' output format into a wx-like active list.
2030 #
2031 function git_wxfile
2032 {
2033         typeset child="$1"
2034         typeset parent="$2"
2035 
2036         TMPFLIST=/tmp/$$.active
2037         $PERL -e 'my (%files, %realfiles, $msg);
2038         my $parent = $ARGV[0];
2039         my $child = $ARGV[1];
2040 
2041         open(F, "git diff -M --name-status $parent..$child |");
2042         while (<F>) {
2043             chomp;
2044             if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
2045                 if ($1 >= 75) {               # Probably worth treating as a rename
2046                     $realfiles{$3} = $2;
2047                 } else {
2048                     $realfiles{$3} = $3;
2049                     $realfiles{$2} = $2;
2050                 }
2051             } else {
2052                 my $f = (split /\s+/, $_)[1];
2053                 $realfiles{$f} = $f;
2054             }
2055         }
2056         close(F);
2057 
2058         my $state = 1;              # 0|comments, 1|files
2059         open(F, "git whatchanged --pretty=format:%B $parent..$child |");
2060         while (<F>) {
2061             chomp;
2062             if (/^:[0-9]{6}/) {
2063                 my ($unused, $fname, $fname2) = split(/\t/, $_);
2064                 $fname = $fname2 if defined($fname2);
2065                 next if !defined($realfiles{$fname}); # No real change
2066                 $state = 1;
2067                 chomp $msg;
2068                 $files{$fname} .= $msg;
2069             } else {
2070                 if ($state == 1) {
2071                     $state = 0;
2072                     $msg = /^\n/ ? "" : "\n";
2073                 }
2074                 $msg .= "$_\n" if ($_);
2075             }
2076         }
2077         close(F);
2078 
2079         for (sort keys %files) {
2080             if ($realfiles{$_} ne $_) {
2081                 print "$_ $realfiles{$_}\n$files{$_}\n\n";
2082             } else {
2083                 print "$_\n$files{$_}\n\n"
2084             }
2085         }' ${parent} ${child} > $TMPFLIST
2086 
2087         wxfile=$TMPFLIST
2088 }
2089 
2090 #
2091 # flist_from_git
2092 # Build a wx-style active list, and hand it off to flist_from_wx
2093 #
2094 function flist_from_git
2095 {
2096         typeset child=$1
2097         typeset parent=$2
2098 
2099         print " File list from: git ...\c"
2100         git_wxfile "$child" "$parent";
2101 
2102         # flist_from_wx prints the Done, so we don't have to.
2103         flist_from_wx $TMPFLIST
2104 }
2105 
2106 #
2107 # flist_from_subversion
2108 #
2109 # Generate the file list by extracting file names from svn status.
2110 #
2111 function flist_from_subversion
2112 {
2113         CWS=$1
2114         OLDPWD=$2
2115 
2116         cd $CWS
2117         print -u2 " File list from: svn status ... \c"
2118         svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
2119         print -u2 " Done."
2120         cd $OLDPWD
2121 }
2122 
2123 function env_from_flist
2124 {
2125         [[ -r $FLIST ]] || return
2126 
2127         #
2128         # Use "eval" to set env variables that are listed in the file
2129         # list.  Then copy those into our local versions of those
2130         # variables if they have not been set already.
2131         #
2132         eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
2133 
2134         if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
2135                 codemgr_ws=$CODEMGR_WS
2136                 export CODEMGR_WS
2137         fi
2138 
2139         #
2140         # Check to see if CODEMGR_PARENT is set in the flist file.
2141         #
2142         if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2143                 codemgr_parent=$CODEMGR_PARENT
2144                 export CODEMGR_PARENT
2145         fi
2146 }
2147 
2148 function look_for_prog
2149 {
2150         typeset path
2151         typeset ppath
2152         typeset progname=$1
2153 
2154         ppath=$PATH
2155         ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
2156         ppath=$ppath:/opt/onbld/bin
2157         ppath=$ppath:/opt/onbld/bin/`uname -p`
2158 
2159         PATH=$ppath prog=`whence $progname`
2160         if [[ -n $prog ]]; then
2161                 print $prog
2162         fi
2163 }
2164 
2165 function get_file_mode
2166 {
2167         $PERL -e '
2168                 if (@stat = stat($ARGV[0])) {
2169                         $mode = $stat[2] & 0777;
2170                         printf "%03o\n", $mode;
2171                         exit 0;
2172                 } else {
2173                         exit 1;
2174                 }
2175             ' $1
2176 }
2177 
2178 function build_old_new_git
2179 {
2180         typeset olddir="$1"
2181         typeset newdir="$2"
2182         typeset o_mode=
2183         typeset n_mode=
2184         typeset o_object=
2185         typeset n_object=
2186         typeset OWD=$PWD
2187         typeset file
2188         typeset type
2189 
2190         cd $CWS
2191 
2192         #
2193         # Get old file and its mode from the git object tree
2194         #
2195         if [[ "$PDIR" == "." ]]; then
2196                 file="$PF"
2197         else
2198                 file="$PDIR/$PF"
2199         fi
2200 
2201         if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2202                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2203         else
2204                 $GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk
2205                 $GIT cat-file $type $o_object > $olddir/$file 2>/dev/null
2206 
2207                 if (( $? != 0 )); then
2208                         rm -f $olddir/$file
2209                 elif [[ -n $o_mode ]]; then
2210                         # Strip the first 3 digits, to get a regular octal mode
2211                         o_mode=${o_mode/???/}
2212                         chmod $o_mode $olddir/$file
2213                 else
2214                         # should never happen
2215                         print -u2 "ERROR: set mode of $olddir/$file"
2216                 fi
2217         fi
2218 
2219         #
2220         # new version of the file.
2221         #
2222         if [[ "$DIR" == "." ]]; then
2223                 file="$F"
2224         else
2225                 file="$DIR/$F"
2226         fi
2227         rm -rf $newdir/$file
2228 
2229         if [[ -e $CWS/$DIR/$F ]]; then
2230                 cp $CWS/$DIR/$F $newdir/$DIR/$F
2231                 chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F
2232         fi
2233         cd $OWD
2234 }
2235 
2236 function build_old_new_subversion
2237 {
2238         typeset olddir="$1"
2239         typeset newdir="$2"
2240 
2241         # Snag new version of file.
2242         rm -f $newdir/$DIR/$F
2243         [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2244 
2245         if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2246                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2247         else
2248                 # Get the parent's version of the file.
2249                 svn status $CWS/$DIR/$F | read stat file
2250                 if [[ $stat != "A" ]]; then
2251                         svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2252                 fi
2253         fi
2254 }
2255 
2256 function build_old_new_unknown
2257 {
2258         typeset olddir="$1"
2259         typeset newdir="$2"
2260 
2261         #
2262         # Snag new version of file.
2263         #
2264         rm -f $newdir/$DIR/$F
2265         [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2266 
2267         #
2268         # Snag the parent's version of the file.
2269         #
2270         if [[ -f $PWS/$PDIR/$PF ]]; then
2271                 rm -f $olddir/$PDIR/$PF
2272                 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2273         fi
2274 }
2275 
2276 function build_old_new
2277 {
2278         typeset WDIR=$1
2279         typeset PWS=$2
2280         typeset PDIR=$3
2281         typeset PF=$4
2282         typeset CWS=$5
2283         typeset DIR=$6
2284         typeset F=$7
2285 
2286         typeset olddir="$WDIR/raw_files/old"
2287         typeset newdir="$WDIR/raw_files/new"
2288 
2289         mkdir -p $olddir/$PDIR
2290         mkdir -p $newdir/$DIR
2291 
2292         if [[ $SCM_MODE == "git" ]]; then
2293                 build_old_new_git "$olddir" "$newdir"
2294         elif [[ $SCM_MODE == "subversion" ]]; then
2295                 build_old_new_subversion "$olddir" "$newdir"
2296         elif [[ $SCM_MODE == "unknown" ]]; then
2297                 build_old_new_unknown "$olddir" "$newdir"
2298         fi
2299 
2300         if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2301                 print "*** Error: file not in parent or child"
2302                 return 1
2303         fi
2304         return 0
2305 }
2306 
2307 
2308 #
2309 # Usage message.
2310 #
2311 function usage
2312 {
2313         print 'Usage:\twebrev [common-options]
2314         webrev [common-options] ( <file> | - )
2315         webrev [common-options] -w <wx file>
2316 
2317 Options:
2318         -c <revision>: generate webrev for single revision (git only)
2319         -C <filename>: Use <filename> for the information tracking configuration.
2320         -D: delete remote webrev
2321         -h <revision>: specify "head" revision for comparison (git only)
2322         -i <filename>: Include <filename> in the index.html file.
2323         -I <filename>: Use <filename> for the information tracking registry.
2324         -n: do not generate the webrev (useful with -U)
2325         -O: Print bugids/arc cases suitable for OpenSolaris.
2326         -o <outdir>: Output webrev to specified directory.
2327         -p <compare-against>: Use specified parent wkspc or basis for comparison
2328         -t <remote_target>: Specify remote destination for webrev upload
2329         -U: upload the webrev to remote destination
2330         -w <wxfile>: Use specified wx active file.
2331 
2332 Environment:
2333         WDIR: Control the output directory.
2334         WEBREV_TRASH_DIR: Set directory for webrev delete.
2335 
2336 SCM Environment:
2337         CODEMGR_WS: Workspace location.
2338         CODEMGR_PARENT: Parent workspace location.
2339 '
2340 
2341         exit 2
2342 }
2343 
2344 #
2345 #
2346 # Main program starts here
2347 #
2348 #
2349 
2350 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2351 
2352 set +o noclobber
2353 
2354 PATH=$(/bin/dirname "$(whence $0)"):$PATH
2355 
2356 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2357 [[ -z $WX ]] && WX=`look_for_prog wx`
2358 [[ -z $GIT ]] && GIT=`look_for_prog git`
2359 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2360 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2361 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2362 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2363 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2364 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2365 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2366 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2367 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2368 [[ -z $SCP ]] && SCP=`look_for_prog scp`
2369 [[ -z $SED ]] && SED=`look_for_prog sed`
2370 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2371 [[ -z $SORT ]] && SORT=`look_for_prog sort`
2372 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2373 [[ -z $GREP ]] && GREP=`look_for_prog grep`
2374 [[ -z $FIND ]] && FIND=`look_for_prog find`
2375 [[ -z $MANDOC ]] && MANDOC=`look_for_prog mandoc`
2376 [[ -z $COL ]] && COL=`look_for_prog col`
2377 
2378 # set name of trash directory for remote webrev deletion
2379 TRASH_DIR=".trash"
2380 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2381 
2382 if [[ ! -x $PERL ]]; then
2383         print -u2 "Error: No perl interpreter found.  Exiting."
2384         exit 1
2385 fi
2386 
2387 if [[ ! -x $WHICH_SCM ]]; then
2388         print -u2 "Error: Could not find which_scm.  Exiting."
2389         exit 1
2390 fi
2391 
2392 #
2393 # These aren't fatal, but we want to note them to the user.
2394 # We don't warn on the absence of 'wx' until later when we've
2395 # determined that we actually need to try to invoke it.
2396 #
2397 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2398 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2399 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2400 
2401 # Declare global total counters.
2402 integer TOTL TINS TDEL TMOD TUNC
2403 
2404 # default remote host for upload/delete
2405 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2406 # prefixes for upload targets
2407 typeset -r rsync_prefix="rsync://"
2408 typeset -r ssh_prefix="ssh://"
2409 
2410 cflag=
2411 Cflag=
2412 Dflag=
2413 flist_mode=
2414 flist_file=
2415 hflag=
2416 iflag=
2417 Iflag=
2418 lflag=
2419 Nflag=
2420 nflag=
2421 Oflag=
2422 oflag=
2423 pflag=
2424 tflag=
2425 uflag=
2426 Uflag=
2427 wflag=
2428 remote_target=
2429 
2430 while getopts "c:C:Dh:i:I:lnNo:Op:t:Uw" opt
2431 do
2432         case $opt in
2433         c)      cflag=1
2434                 codemgr_head=$OPTARG
2435                 codemgr_parent=$OPTARG~1;;
2436 
2437         C)      Cflag=1
2438                 ITSCONF=$OPTARG;;
2439 
2440         D)      Dflag=1;;
2441 
2442         h)      hflag=1
2443                 codemgr_head=$OPTARG;;
2444 
2445         i)      iflag=1
2446                 INCLUDE_FILE=$OPTARG;;
2447 
2448         I)      Iflag=1
2449                 ITSREG=$OPTARG;;
2450 
2451         N)      Nflag=1;;
2452 
2453         n)      nflag=1;;
2454 
2455         O)      Oflag=1;;
2456 
2457         o)      oflag=1
2458                 # Strip the trailing slash to correctly form remote target.
2459                 WDIR=${OPTARG%/};;
2460 
2461         p)      pflag=1
2462                 codemgr_parent=$OPTARG;;
2463 
2464         t)      tflag=1
2465                 remote_target=$OPTARG;;
2466 
2467         U)      Uflag=1;;
2468 
2469         w)      wflag=1;;
2470 
2471         ?)      usage;;
2472         esac
2473 done
2474 
2475 FLIST=/tmp/$$.flist
2476 
2477 if [[ -n $wflag && -n $lflag ]]; then
2478         usage
2479 fi
2480 
2481 # more sanity checking
2482 if [[ -n $nflag && -z $Uflag ]]; then
2483         print "it does not make sense to skip webrev generation" \
2484             "without -U"
2485         exit 1
2486 fi
2487 
2488 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2489         echo "remote target has to be used only for upload or delete"
2490         exit 1
2491 fi
2492 
2493 #
2494 # For the invocation "webrev -n -U" with no other options, webrev will assume
2495 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2496 # $(basename ${CWS}).  So we need to get CWS set before we skip any remaining
2497 # logic.
2498 #
2499 $WHICH_SCM | read SCM_MODE junk || exit 1
2500 
2501 if [[ $SCM_MODE == "git" ]]; then
2502         #
2503         # Git priorities:
2504         # 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2505         # 2. git rev-parse --git-dir from directory of invocation
2506         #
2507         [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2508             codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \
2509                 2>/dev/null)
2510         [[ -z $codemgr_ws ]] && \
2511             codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null)
2512 
2513         if [[ "$codemgr_ws" == ".git" ]]; then
2514                 codemgr_ws="${PWD}/${codemgr_ws}"
2515         fi
2516 
2517         if [[ "$codemgr_ws" = *"/.git" ]]; then
2518                 codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git'
2519         fi
2520         CWS="$codemgr_ws"
2521 elif [[ $SCM_MODE == "subversion" ]]; then
2522         #
2523         # Subversion priorities:
2524         # 1. CODEMGR_WS from environment
2525         # 2. Relative path from current directory to SVN repository root
2526         #
2527         if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2528                 CWS=$CODEMGR_WS
2529         else
2530                 svn info | while read line; do
2531                         if [[ $line == "URL: "* ]]; then
2532                                 url=${line#URL: }
2533                         elif [[ $line == "Repository Root: "* ]]; then
2534                                 repo=${line#Repository Root: }
2535                         fi
2536                 done
2537 
2538                 rel=${url#$repo}
2539                 CWS=${PWD%$rel}
2540         fi
2541 fi
2542 
2543 #
2544 # If no SCM has been determined, take either the environment setting
2545 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2546 #
2547 if [[ -z ${CWS} ]]; then
2548         CWS=${CODEMGR_WS:-.}
2549 fi
2550 
2551 #
2552 # If the command line options indicate no webrev generation, either
2553 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2554 # ton of logic we can skip.
2555 #
2556 # Instead of increasing indentation, we intentionally leave this loop
2557 # body open here, and exit via break from multiple points within.
2558 # Search for DO_EVERYTHING below to find the break points and closure.
2559 #
2560 for do_everything in 1; do
2561 
2562 # DO_EVERYTHING: break point
2563 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2564         break
2565 fi
2566 
2567 #
2568 # If this manually set as the parent, and it appears to be an earlier webrev,
2569 # then note that fact and set the parent to the raw_files/new subdirectory.
2570 #
2571 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2572         parent_webrev=$(readlink -f "$codemgr_parent")
2573         codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new")
2574 fi
2575 
2576 if [[ -z $wflag && -z $lflag ]]; then
2577         shift $(($OPTIND - 1))
2578 
2579         if [[ $1 == "-" ]]; then
2580                 cat > $FLIST
2581                 flist_mode="stdin"
2582                 flist_done=1
2583                 shift
2584         elif [[ -n $1 ]]; then
2585                 if [[ ! -r $1 ]]; then
2586                         print -u2 "$1: no such file or not readable"
2587                         usage
2588                 fi
2589                 cat $1 > $FLIST
2590                 flist_mode="file"
2591                 flist_file=$1
2592                 flist_done=1
2593                 shift
2594         else
2595                 flist_mode="auto"
2596         fi
2597 fi
2598 
2599 #
2600 # Before we go on to further consider -l and -w, work out which SCM we think
2601 # is in use.
2602 #
2603 case "$SCM_MODE" in
2604 git|subversion)
2605         ;;
2606 unknown)
2607         if [[ $flist_mode == "auto" ]]; then
2608                 print -u2 "Unable to determine SCM in use and file list not specified"
2609                 print -u2 "See which_scm(1) for SCM detection information."
2610                 exit 1
2611         fi
2612         ;;
2613 *)
2614         if [[ $flist_mode == "auto" ]]; then
2615                 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2616                 exit 1
2617         fi
2618         ;;
2619 esac
2620 
2621 print -u2 "   SCM detected: $SCM_MODE"
2622 
2623 if [[ -n $wflag ]]; then
2624         #
2625         # If the -w is given then assume the file list is in Bonwick's "wx"
2626         # command format, i.e.  pathname lines alternating with SCCS comment
2627         # lines with blank lines as separators.  Use the SCCS comments later
2628         # in building the index.html file.
2629         #
2630         shift $(($OPTIND - 1))
2631         wxfile=$1
2632         if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2633                 if [[ -r $CODEMGR_WS/wx/active ]]; then
2634                         wxfile=$CODEMGR_WS/wx/active
2635                 fi
2636         fi
2637 
2638         [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2639             "be auto-detected (check \$CODEMGR_WS)" && exit 1
2640 
2641         if [[ ! -r $wxfile ]]; then
2642                 print -u2 "$wxfile: no such file or not readable"
2643                 usage
2644         fi
2645 
2646         print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2647         flist_from_wx $wxfile
2648         flist_done=1
2649         if [[ -n "$*" ]]; then
2650                 shift
2651         fi
2652 elif [[ $flist_mode == "stdin" ]]; then
2653         print -u2 " File list from: standard input"
2654 elif [[ $flist_mode == "file" ]]; then
2655         print -u2 " File list from: $flist_file"
2656 fi
2657 
2658 if [[ $# -gt 0 ]]; then
2659         print -u2 "WARNING: unused arguments: $*"
2660 fi
2661 
2662 
2663 if [[ $SCM_MODE == "git" ]]; then
2664         # Check that "head" revision specified with -c or -h is sane
2665         if [[ -n $cflag || -n $hflag ]]; then
2666                 head_rev=$($GIT rev-parse --verify --quiet "$codemgr_head")
2667                 if [[ -z $head_rev ]]; then
2668                         print -u2 "Error: bad revision ${codemgr_head}"
2669                         exit 1
2670                 fi
2671         fi
2672 
2673         if [[ -z $codemgr_head ]]; then
2674                 codemgr_head="HEAD";
2675         fi
2676 
2677         # Parent can either be specified with -p, or specified with
2678         # CODEMGR_PARENT in the environment.
2679         if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2680                 codemgr_parent=$CODEMGR_PARENT
2681         fi
2682 
2683         # Try to figure out the parent based on the branch the current
2684         # branch is tracking, if we fail, use origin/master
2685         this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2686         par_branch="origin/master"
2687 
2688         # If we're not on a branch there's nothing we can do
2689         if [[ $this_branch != "(no branch)" ]]; then
2690                 $GIT for-each-ref                                       \
2691                     --format='%(refname:short) %(upstream:short)'       \
2692                     refs/heads/ |                                       \
2693                     while read local remote; do
2694                         if [[ "$local" == "$this_branch" ]]; then
2695                                 par_branch="$remote"
2696                         fi
2697                 done
2698         fi
2699 
2700         if [[ -z $codemgr_parent ]]; then
2701                 codemgr_parent=$par_branch
2702         fi
2703         PWS=$codemgr_parent
2704 
2705         #
2706         # If the parent is a webrev, we want to do some things against
2707         # the natural workspace parent (file list, comments, etc)
2708         #
2709         if [[ -n $parent_webrev ]]; then
2710                 real_parent=$par_branch
2711         else
2712                 real_parent=$PWS
2713         fi
2714 
2715         if [[ -z $flist_done ]]; then
2716                 flist_from_git "$codemgr_head" "$real_parent"
2717                 flist_done=1
2718         fi
2719 
2720         #
2721         # If we have a file list now, pull out any variables set
2722         # therein.
2723         #
2724         if [[ -n $flist_done ]]; then
2725                 env_from_flist
2726         fi
2727 
2728         #
2729         # If we don't have a wx-format file list, build one we can pull change
2730         # comments from.
2731         #
2732         if [[ -z $wxfile ]]; then
2733                 print "  Comments from: git...\c"
2734                 git_wxfile "$codemgr_head" "$real_parent"
2735                 print " Done."
2736         fi
2737 
2738         if [[ -z $GIT_PARENT ]]; then
2739                 GIT_PARENT=$($GIT merge-base "$real_parent" "$codemgr_head")
2740         fi
2741         if [[ -z $GIT_PARENT ]]; then
2742                 print -u2 "Error: Cannot discover parent revision"
2743                 exit 1
2744         fi
2745 
2746         pnode=$(trim_digest $GIT_PARENT)
2747 
2748         if [[ -n $cflag ]]; then
2749                 PRETTY_PWS="previous revision (at ${pnode})"
2750         elif [[ $real_parent == */* ]]; then
2751                 origin=$(echo $real_parent | cut -d/ -f1)
2752                 origin=$($GIT remote -v | \
2753                     $AWK '$1 == "'$origin'" { print $2; exit }')
2754                 PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2755         elif [[ -n $pflag && -z $parent_webrev ]]; then
2756                 PRETTY_PWS="${CWS} (explicit revision ${pnode})"
2757         else
2758                 PRETTY_PWS="${PWS} (at ${pnode})"
2759         fi
2760 
2761         cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 \
2762             ${codemgr_head} 2>/dev/null)
2763 
2764         if [[ -n $cflag || -n $hflag ]]; then
2765                 PRETTY_CWS="${CWS} (explicit head at ${cnode})"
2766         else
2767                 PRETTY_CWS="${CWS} (at ${cnode})"
2768         fi
2769 elif [[ $SCM_MODE == "subversion" ]]; then
2770 
2771         #
2772         # We only will have a real parent workspace in the case one
2773         # was specified (be it an older webrev, or another checkout).
2774         #
2775         [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2776 
2777         if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2778                 flist_from_subversion $CWS $OLDPWD
2779         fi
2780 else
2781         if [[ $SCM_MODE == "unknown" ]]; then
2782                 print -u2 "    Unknown type of SCM in use"
2783         else
2784                 print -u2 "    Unsupported SCM in use: $SCM_MODE"
2785         fi
2786 
2787         env_from_flist
2788 
2789         if [[ -z $CODEMGR_WS ]]; then
2790                 print -u2 "SCM not detected/supported and " \
2791                     "CODEMGR_WS not specified"
2792                 exit 1
2793                 fi
2794 
2795         if [[ -z $CODEMGR_PARENT ]]; then
2796                 print -u2 "SCM not detected/supported and " \
2797                     "CODEMGR_PARENT not specified"
2798                 exit 1
2799         fi
2800 
2801         CWS=$CODEMGR_WS
2802         PWS=$CODEMGR_PARENT
2803 fi
2804 
2805 #
2806 # If the user didn't specify a -i option, check to see if there is a
2807 # webrev-info file in the workspace directory.
2808 #
2809 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2810         iflag=1
2811         INCLUDE_FILE="$CWS/webrev-info"
2812 fi
2813 
2814 if [[ -n $iflag ]]; then
2815         if [[ ! -r $INCLUDE_FILE ]]; then
2816                 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2817                     "not readable."
2818                 exit 1
2819         else
2820                 #
2821                 # $INCLUDE_FILE may be a relative path, and the script alters
2822                 # PWD, so we just stash a copy in /tmp.
2823                 #
2824                 cp $INCLUDE_FILE /tmp/$$.include
2825         fi
2826 fi
2827 
2828 # DO_EVERYTHING: break point
2829 if [[ -n $Nflag ]]; then
2830         break
2831 fi
2832 
2833 typeset -A itsinfo
2834 typeset -r its_sed_script=/tmp/$$.its_sed
2835 valid_prefixes=
2836 if [[ -z $nflag ]]; then
2837         DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg"
2838         if [[ -n $Iflag ]]; then
2839                 REGFILE=$ITSREG
2840         elif [[ -r $HOME/.its.reg ]]; then
2841                 REGFILE=$HOME/.its.reg
2842         else
2843                 REGFILE=$DEFREGFILE
2844         fi
2845         if [[ ! -r $REGFILE ]]; then
2846                 print "ERROR: Unable to read database registry file $REGFILE"
2847                 exit 1
2848         elif [[ $REGFILE != $DEFREGFILE ]]; then
2849                 print "   its.reg from: $REGFILE"
2850         fi
2851 
2852         $SED -e '/^#/d' -e '/^[         ]*$/d' $REGFILE | while read LINE; do
2853 
2854                 name=${LINE%%=*}
2855                 value="${LINE#*=}"
2856 
2857                 if [[ $name == PREFIX ]]; then
2858                         p=${value}
2859                         valid_prefixes="${p} ${valid_prefixes}"
2860                 else
2861                         itsinfo["${p}_${name}"]="${value}"
2862                 fi
2863         done
2864 
2865 
2866         DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf"
2867         CONFFILES=$DEFCONFFILE
2868         if [[ -r $HOME/.its.conf ]]; then
2869                 CONFFILES="${CONFFILES} $HOME/.its.conf"
2870         fi
2871         if [[ -n $Cflag ]]; then
2872                 CONFFILES="${CONFFILES} ${ITSCONF}"
2873         fi
2874         its_domain=
2875         its_priority=
2876         for cf in ${CONFFILES}; do
2877                 if [[ ! -r $cf ]]; then
2878                         print "ERROR: Unable to read database configuration file $cf"
2879                         exit 1
2880                 elif [[ $cf != $DEFCONFFILE ]]; then
2881                         print "       its.conf: reading $cf"
2882                 fi
2883                 $SED -e '/^#/d' -e '/^[         ]*$/d' $cf | while read LINE; do
2884                     eval "${LINE}"
2885                 done
2886         done
2887 
2888         #
2889         # If an information tracking system is explicitly identified by prefix,
2890         # we want to disregard the specified priorities and resolve it accordingly.
2891         #
2892         # To that end, we'll build a sed script to do each valid prefix in turn.
2893         #
2894         for p in ${valid_prefixes}; do
2895                 #
2896                 # When an informational URL was provided, translate it to a
2897                 # hyperlink.  When omitted, simply use the prefix text.
2898                 #
2899                 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2900                         itsinfo["${p}_INFO"]=${p}
2901                 else
2902                         itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2903                 fi
2904 
2905                 #
2906                 # Assume that, for this invocation of webrev, all references
2907                 # to this information tracking system should resolve through
2908                 # the same URL.
2909                 #
2910                 # If the caller specified -O, then always use EXTERNAL_URL.
2911                 #
2912                 # Otherwise, look in the list of domains for a matching
2913                 # INTERNAL_URL.
2914                 #
2915                 [[ -z $Oflag ]] && for d in ${its_domain}; do
2916                         if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2917                                 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2918                                 break
2919                         fi
2920                 done
2921                 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2922                         itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2923                 fi
2924 
2925                 #
2926                 # Turn the destination URL into a hyperlink
2927                 #
2928                 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2929 
2930                 # The character class below contains a literal tab
2931                 print "/^${p}[:         ]/ {
2932                                 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2933                                 s;^${p};${itsinfo[${p}_INFO]};
2934                         }" >> ${its_sed_script}
2935         done
2936 
2937         #
2938         # The previous loop took care of explicit specification.  Now use
2939         # the configured priorities to attempt implicit translations.
2940         #
2941         for p in ${its_priority}; do
2942                 print "/^${itsinfo[${p}_REGEX]}[        ]/ {
2943                                 s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2944                         }" >> ${its_sed_script}
2945         done
2946 fi
2947 
2948 #
2949 # Search for DO_EVERYTHING above for matching "for" statement
2950 # and explanation of this terminator.
2951 #
2952 done
2953 
2954 #
2955 # Output directory.
2956 #
2957 WDIR=${WDIR:-$CWS/webrev}
2958 
2959 #
2960 # Name of the webrev, derived from the workspace name or output directory;
2961 # in the future this could potentially be an option.
2962 #
2963 if [[ -n $oflag ]]; then
2964         WNAME=${WDIR##*/}
2965 else
2966         WNAME=${CWS##*/}
2967 fi
2968 
2969 # Make sure remote target is well formed for remote upload/delete.
2970 if [[ -n $Dflag || -n $Uflag ]]; then
2971         #
2972         # If remote target is not specified, build it from scratch using
2973         # the default values.
2974         #
2975         if [[ -z $tflag ]]; then
2976                 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2977         else
2978                 #
2979                 # Check upload target prefix first.
2980                 #
2981                 if [[ "${remote_target}" != ${rsync_prefix}* &&
2982                     "${remote_target}" != ${ssh_prefix}* ]]; then
2983                         print "ERROR: invalid prefix of upload URI" \
2984                             "($remote_target)"
2985                         exit 1
2986                 fi
2987                 #
2988                 # If destination specification is not in the form of
2989                 # host_spec:remote_dir then assume it is just remote hostname
2990                 # and append a colon and destination directory formed from
2991                 # local webrev directory name.
2992                 #
2993                 typeset target_no_prefix=${remote_target##*://}
2994                 if [[ ${target_no_prefix} == *:* ]]; then
2995                         if [[ "${remote_target}" == *: ]]; then
2996                                 remote_target=${remote_target}${WNAME}
2997                         fi
2998                 else
2999                         if [[ ${target_no_prefix} == */* ]]; then
3000                                 print "ERROR: badly formed upload URI" \
3001                                         "($remote_target)"
3002                                 exit 1
3003                         else
3004                                 remote_target=${remote_target}:${WNAME}
3005                         fi
3006                 fi
3007         fi
3008 
3009         #
3010         # Strip trailing slash. Each upload method will deal with directory
3011         # specification separately.
3012         #
3013         remote_target=${remote_target%/}
3014 fi
3015 
3016 #
3017 # Option -D by itself (option -U not present) implies no webrev generation.
3018 #
3019 if [[ -z $Uflag && -n $Dflag ]]; then
3020         delete_webrev 1 1
3021         exit $?
3022 fi
3023 
3024 #
3025 # Do not generate the webrev, just upload it or delete it.
3026 #
3027 if [[ -n $nflag ]]; then
3028         if [[ -n $Dflag ]]; then
3029                 delete_webrev 1 1
3030                 (( $? == 0 )) || exit $?
3031         fi
3032         if [[ -n $Uflag ]]; then
3033                 upload_webrev
3034                 exit $?
3035         fi
3036 fi
3037 
3038 if [ "${WDIR%%/*}" ]; then
3039         WDIR=$PWD/$WDIR
3040 fi
3041 
3042 if [[ ! -d $WDIR ]]; then
3043         mkdir -p $WDIR
3044         (( $? != 0 )) && exit 1
3045 fi
3046 
3047 #
3048 # Summarize what we're going to do.
3049 #
3050 print "      Workspace: ${PRETTY_CWS:-$CWS}"
3051 if [[ -n $parent_webrev ]]; then
3052         print "Compare against: webrev at $parent_webrev"
3053 else
3054         print "Compare against: ${PRETTY_PWS:-$PWS}"
3055 fi
3056 
3057 [[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
3058 print "      Output to: $WDIR"
3059 
3060 #
3061 # Save the file list in the webrev dir
3062 #
3063 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
3064 
3065 rm -f $WDIR/$WNAME.patch
3066 rm -f $WDIR/$WNAME.ps
3067 rm -f $WDIR/$WNAME.pdf
3068 
3069 touch $WDIR/$WNAME.patch
3070 
3071 print "   Output Files:"
3072 
3073 #
3074 # Clean up the file list: Remove comments, blank lines and env variables.
3075 #
3076 $SED -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
3077 FLIST=/tmp/$$.flist.clean
3078 
3079 #
3080 # First pass through the files: generate the per-file webrev HTML-files.
3081 #
3082 cat $FLIST | while read LINE
3083 do
3084         set - $LINE
3085         P=$1
3086 
3087         #
3088         # Normally, each line in the file list is just a pathname of a
3089         # file that has been modified or created in the child.  A file
3090         # that is renamed in the child workspace has two names on the
3091         # line: new name followed by the old name.
3092         #
3093         oldname=""
3094         oldpath=""
3095         rename=
3096         if [[ $# -eq 2 ]]; then
3097                 PP=$2                   # old filename
3098                 if [[ -f $PP ]]; then
3099                         oldname=" (copied from $PP)"
3100                 else
3101                         oldname=" (renamed from $PP)"
3102                 fi
3103                 oldpath="$PP"
3104                 rename=1
3105                 PDIR=${PP%/*}
3106                 if [[ $PDIR == $PP ]]; then
3107                         PDIR="."   # File at root of workspace
3108                 fi
3109 
3110                 PF=${PP##*/}
3111 
3112                 DIR=${P%/*}
3113                 if [[ $DIR == $P ]]; then
3114                         DIR="."   # File at root of workspace
3115                 fi
3116 
3117                 F=${P##*/}
3118 
3119         else
3120                 DIR=${P%/*}
3121                 if [[ "$DIR" == "$P" ]]; then
3122                         DIR="."   # File at root of workspace
3123                 fi
3124 
3125                 F=${P##*/}
3126 
3127                 PP=$P
3128                 PDIR=$DIR
3129                 PF=$F
3130         fi
3131 
3132         COMM=`getcomments html $P $PP`
3133 
3134         print "\t$P$oldname\n\t\t\c"
3135 
3136         # Make the webrev mirror directory if necessary
3137         mkdir -p $WDIR/$DIR
3138 
3139         #
3140         # We stash old and new files into parallel directories in $WDIR
3141         # and do our diffs there.  This makes it possible to generate
3142         # clean looking diffs which don't have absolute paths present.
3143         #
3144 
3145         build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3146             continue
3147 
3148         #
3149         # Keep the old PWD around, so we can safely switch back after
3150         # diff generation, such that build_old_new runs in a
3151         # consistent environment.
3152         #
3153         OWD=$PWD
3154         cd $WDIR/raw_files
3155 
3156         #
3157         # The "git apply" command does not tolerate the spurious
3158         # "./" that we otherwise insert; be careful not to include
3159         # it in the paths that we pass to diff(1).
3160         #
3161         if [[ $PDIR == "." ]]; then
3162                 ofile=old/$PF
3163         else
3164                 ofile=old/$PDIR/$PF
3165         fi
3166         if [[ $DIR == "." ]]; then
3167                 nfile=new/$F
3168         else
3169                 nfile=new/$DIR/$F
3170         fi
3171 
3172         mv_but_nodiff=
3173         cmp $ofile $nfile > /dev/null 2>&1
3174         if [[ $? == 0 && $rename == 1 ]]; then
3175                 mv_but_nodiff=1
3176         fi
3177 
3178         #
3179         # If we have old and new versions of the file then run the appropriate
3180         # diffs.  This is complicated by a couple of factors:
3181         #
3182         #       - renames must be handled specially: we emit a 'remove'
3183         #         diff and an 'add' diff
3184         #       - new files and deleted files must be handled specially
3185         #       - GNU patch doesn't interpret the output of illumos diff
3186         #         properly when it comes to adds and deletes.  We need to
3187         #         do some "cleansing" transformations:
3188         #           [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
3189         #           [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
3190         #
3191         cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3192         cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3193 
3194         rm -f $WDIR/$DIR/$F.patch
3195         if [[ -z $rename ]]; then
3196                 if [ ! -f "$ofile" ]; then
3197                         diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3198                             > $WDIR/$DIR/$F.patch
3199                 elif [ ! -f "$nfile" ]; then
3200                         diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3201                             > $WDIR/$DIR/$F.patch
3202                 else
3203                         diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3204                 fi
3205         else
3206                 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3207                     > $WDIR/$DIR/$F.patch
3208 
3209                 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3210                     >> $WDIR/$DIR/$F.patch
3211         fi
3212 
3213         #
3214         # Tack the patch we just made onto the accumulated patch for the
3215         # whole wad.
3216         #
3217         cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3218         print " patch\c"
3219 
3220         if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3221                 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3222                 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3223                     > $WDIR/$DIR/$F.cdiff.html
3224                 print " cdiffs\c"
3225 
3226                 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3227                 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3228                     > $WDIR/$DIR/$F.udiff.html
3229                 print " udiffs\c"
3230 
3231                 if [[ -x $WDIFF ]]; then
3232                         $WDIFF -c "$COMM" \
3233                             -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3234                             $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3235                         if [[ $? -eq 0 ]]; then
3236                                 print " wdiffs\c"
3237                         else
3238                                 print " wdiffs[fail]\c"
3239                         fi
3240                 fi
3241 
3242                 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3243                     > $WDIR/$DIR/$F.sdiff.html
3244                 print " sdiffs\c"
3245                 print " frames\c"
3246 
3247                 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3248                 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3249         elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3250                 # renamed file: may also have differences
3251                 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3252         elif [[ -f $nfile ]]; then
3253                 # new file: count added lines
3254                 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3255         elif [[ -f $ofile ]]; then
3256                 # old file: count deleted lines
3257                 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3258         fi
3259 
3260         #
3261         # Check if it's man page, and create plain text, html and raw (ascii)
3262         # output for the new version, as well as diffs against old version.
3263         #
3264         if [[ -f "$nfile" && "$nfile" = *.+([0-9])*([a-zA-Z]) && \
3265             -x $MANDOC && -x $COL ]]; then
3266                 $MANDOC -Tascii $nfile | $COL -b > $nfile.man.txt
3267                 source_to_html txt < $nfile.man.txt > $nfile.man.txt.html
3268                 print " man-txt\c"
3269                 print "$MANCSS" > $WDIR/raw_files/new/$DIR/man.css
3270                 $MANDOC -Thtml -Ostyle=man.css $nfile > $nfile.man.html
3271                 print " man-html\c"
3272                 $MANDOC -Tascii $nfile > $nfile.man.raw
3273                 print " man-raw\c"
3274                 if [[ -f "$ofile" && -z $mv_but_nodiff ]]; then
3275                         $MANDOC -Tascii $ofile | $COL -b > $ofile.man.txt
3276                         ${CDIFFCMD:-diff -bt -C 5} $ofile.man.txt \
3277                             $nfile.man.txt > $WDIR/$DIR/$F.man.cdiff
3278                         diff_to_html $F $DIR/$F "C" "$COMM" < \
3279                             $WDIR/$DIR/$F.man.cdiff > \
3280                             $WDIR/$DIR/$F.man.cdiff.html
3281                         print " man-cdiffs\c"
3282                         ${UDIFFCMD:-diff -bt -U 5} $ofile.man.txt \
3283                             $nfile.man.txt > $WDIR/$DIR/$F.man.udiff
3284                         diff_to_html $F $DIR/$F "U" "$COMM" < \
3285                             $WDIR/$DIR/$F.man.udiff > \
3286                             $WDIR/$DIR/$F.man.udiff.html
3287                         print " man-udiffs\c"
3288                         if [[ -x $WDIFF ]]; then
3289                                 $WDIFF -c "$COMM" -t "$WNAME Wdiff $DIR/$F" \
3290                                     $ofile.man.txt $nfile.man.txt > \
3291                                     $WDIR/$DIR/$F.man.wdiff.html 2>/dev/null
3292                                 if [[ $? -eq 0 ]]; then
3293                                         print " man-wdiffs\c"
3294                                 else
3295                                         print " man-wdiffs[fail]\c"
3296                                 fi
3297                         fi
3298                         sdiff_to_html $ofile.man.txt $nfile.man.txt $F.man $DIR \
3299                             "$COMM" > $WDIR/$DIR/$F.man.sdiff.html
3300                         print " man-sdiffs\c"
3301                         print " man-frames\c"
3302                 fi
3303                 rm -f $ofile.man.txt $nfile.man.txt
3304                 rm -f $WDIR/$DIR/$F.man.cdiff $WDIR/$DIR/$F.man.udiff
3305         fi
3306 
3307         #
3308         # Now we generate the postscript for this file.  We generate diffs
3309         # only in the event that there is delta, or the file is new (it seems
3310         # tree-killing to print out the contents of deleted files).
3311         #
3312         if [[ -f $nfile ]]; then
3313                 ocr=$ofile
3314                 [[ ! -f $ofile ]] && ocr=/dev/null
3315 
3316                 if [[ -z $mv_but_nodiff ]]; then
3317                         textcomm=`getcomments text $P $PP`
3318                         if [[ -x $CODEREVIEW ]]; then
3319                                 $CODEREVIEW -y "$textcomm" \
3320                                     -e $ocr $nfile \
3321                                     > /tmp/$$.psfile 2>/dev/null &&
3322                                     cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3323                                 if [[ $? -eq 0 ]]; then
3324                                         print " ps\c"
3325                                 else
3326                                         print " ps[fail]\c"
3327                                 fi
3328                         fi
3329                 fi
3330         fi
3331 
3332         if [[ -f $ofile ]]; then
3333                 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3334                 print " old\c"
3335         fi
3336 
3337         if [[ -f $nfile ]]; then
3338                 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3339                 print " new\c"
3340         fi
3341 
3342         cd $OWD
3343 
3344         print
3345 done
3346 
3347 frame_nav_js > $WDIR/ancnav.js
3348 frame_navigation > $WDIR/ancnav.html
3349 
3350 if [[ ! -f $WDIR/$WNAME.ps ]]; then
3351         print " Generating PDF: Skipped: no output available"
3352 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3353         print " Generating PDF: \c"
3354         fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3355         print "Done."
3356 else
3357         print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3358 fi
3359 
3360 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3361 # delete it - prevent accidental publishing of closed source
3362 
3363 if [[ -n "$Oflag" ]]; then
3364         $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3365 fi
3366 
3367 # Now build the index.html file that contains
3368 # links to the source files and their diffs.
3369 
3370 cd $CWS
3371 
3372 # Save total changed lines for Code Inspection.
3373 print "$TOTL" > $WDIR/TotalChangedLines
3374 
3375 print "     index.html: \c"
3376 INDEXFILE=$WDIR/index.html
3377 exec 3<&1                        # duplicate stdout to FD3.
3378 exec 1<&-                        # Close stdout.
3379 exec > $INDEXFILE            # Open stdout to index file.
3380 
3381 print "$HTML<head>$STDHEAD"
3382 print "<title>$WNAME</title>"
3383 print "</head>"
3384 print "<body id=\"SUNWwebrev\">"
3385 print "<div class=\"summary\">"
3386 print "<h2>Code Review for $WNAME</h2>"
3387 
3388 print "<table>"
3389 
3390 #
3391 # Get the preparer's name:
3392 #
3393 # If the SCM detected is Git, and the configuration property user.name is
3394 # available, use that, but be careful to properly escape angle brackets (HTML
3395 # syntax characters) in the email address.
3396 #
3397 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3398 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3399 #
3400 preparer=
3401 if [[ "$SCM_MODE" == git ]]; then
3402         preparer=$(git config user.name 2>/dev/null)
3403         if [[ -n "$preparer" ]]; then
3404                 preparer="$(echo "$preparer" | html_quote)"
3405         fi
3406 fi
3407 if [[ -z "$preparer" ]]; then
3408         preparer=$(
3409             $PERL -e '
3410                 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3411                 if ($login) {
3412                     $gcos =~ s/\&/ucfirst($login)/e;
3413                     printf "%s (%s)\n", $gcos, $login;
3414                 } else {
3415                     printf "(unknown)\n";
3416                 }
3417         ')
3418 fi
3419 
3420 PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3421 print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3422 print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3423 print "</td></tr>"
3424 print "<tr><th>Compare against:</th><td>"
3425 if [[ -n $parent_webrev ]]; then
3426         print "webrev at $parent_webrev"
3427 else
3428         print "${PRETTY_PWS:-$PWS}"
3429 fi
3430 print "</td></tr>"
3431 print "<tr><th>Summary of changes:</th><td>"
3432 printCI $TOTL $TINS $TDEL $TMOD $TUNC
3433 print "</td></tr>"
3434 
3435 if [[ -f $WDIR/$WNAME.patch ]]; then
3436         wpatch_url="$(print $WNAME.patch | url_encode)"
3437         print "<tr><th>Patch of changes:</th><td>"
3438         print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3439 fi
3440 if [[ -f $WDIR/$WNAME.pdf ]]; then
3441         wpdf_url="$(print $WNAME.pdf | url_encode)"
3442         print "<tr><th>Printable review:</th><td>"
3443         print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3444 fi
3445 
3446 if [[ -n "$iflag" ]]; then
3447         print "<tr><th>Author comments:</th><td><div>"
3448         cat /tmp/$$.include
3449         print "</div></td></tr>"
3450 fi
3451 print "</table>"
3452 print "</div>"
3453 
3454 #
3455 # Second pass through the files: generate the rest of the index file
3456 #
3457 cat $FLIST | while read LINE
3458 do
3459         set - $LINE
3460         P=$1
3461 
3462         if [[ $# == 2 ]]; then
3463                 PP=$2
3464                 oldname="$PP"
3465         else
3466                 PP=$P
3467                 oldname=""
3468         fi
3469 
3470         mv_but_nodiff=
3471         cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3472         if [[ $? == 0 && -n "$oldname" ]]; then
3473                 mv_but_nodiff=1
3474         fi
3475 
3476         DIR=${P%/*}
3477         if [[ $DIR == $P ]]; then
3478                 DIR="."   # File at root of workspace
3479         fi
3480 
3481         # Avoid processing the same file twice.
3482         # It's possible for renamed files to
3483         # appear twice in the file list
3484 
3485         F=$WDIR/$P
3486 
3487         print "<p>"
3488 
3489         # If there's a diffs file, make diffs links
3490 
3491         if [[ -f $F.cdiff.html ]]; then
3492                 cdiff_url="$(print $P.cdiff.html | url_encode)"
3493                 udiff_url="$(print $P.udiff.html | url_encode)"
3494                 sdiff_url="$(print $P.sdiff.html | url_encode)"
3495                 frames_url="$(print $P.frames.html | url_encode)"
3496                 print "<a href=\"$cdiff_url\">Cdiffs</a>"
3497                 print "<a href=\"$udiff_url\">Udiffs</a>"
3498                 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3499                         wdiff_url="$(print $P.wdiff.html | url_encode)"
3500                         print "<a href=\"$wdiff_url\">Wdiffs</a>"
3501                 fi
3502                 print "<a href=\"$sdiff_url\">Sdiffs</a>"
3503                 print "<a href=\"$frames_url\">Frames</a>"
3504         else
3505                 print " ------ ------"
3506                 if [[ -x $WDIFF ]]; then
3507                         print " ------"
3508                 fi
3509                 print " ------ ------"
3510         fi
3511 
3512         # If there's an old file, make the link
3513 
3514         if [[ -f $F-.html ]]; then
3515                 oldfile_url="$(print $P-.html | url_encode)"
3516                 print "<a href=\"$oldfile_url\">Old</a>"
3517         else
3518                 print " ---"
3519         fi
3520 
3521         # If there's an new file, make the link
3522 
3523         if [[ -f $F.html ]]; then
3524                 newfile_url="$(print $P.html | url_encode)"
3525                 print "<a href=\"$newfile_url\">New</a>"
3526         else
3527                 print " ---"
3528         fi
3529 
3530         if [[ -f $F.patch ]]; then
3531                 patch_url="$(print $P.patch | url_encode)"
3532                 print "<a href=\"$patch_url\">Patch</a>"
3533         else
3534                 print " -----"
3535         fi
3536 
3537         if [[ -f $WDIR/raw_files/new/$P ]]; then
3538                 rawfiles_url="$(print raw_files/new/$P | url_encode)"
3539                 print "<a href=\"$rawfiles_url\">Raw</a>"
3540         else
3541                 print " ---"
3542         fi
3543 
3544         print "<b>$P</b>"
3545 
3546         # For renamed files, clearly state whether or not they are modified
3547         if [[ -f "$oldname" ]]; then
3548                 if [[ -n "$mv_but_nodiff" ]]; then
3549                         print "<i>(copied from $oldname)</i>"
3550                 else
3551                         print "<i>(copied and modified from $oldname)</i>"
3552                 fi
3553         elif [[ -n "$oldname" ]]; then
3554                 if [[ -n "$mv_but_nodiff" ]]; then
3555                         print "<i>(renamed from $oldname)</i>"
3556                 else
3557                         print "<i>(renamed and modified from $oldname)</i>"
3558                 fi
3559         fi
3560 
3561         # If there's an old file, but no new file, the file was deleted
3562         if [[ -f $F-.html && ! -f $F.html ]]; then
3563                 print " <i>(deleted)</i>"
3564         fi
3565 
3566         # Check for usr/closed and deleted_files/usr/closed
3567         if [ ! -z "$Oflag" ]; then
3568                 if [[ $P == usr/closed/* || \
3569                     $P == deleted_files/usr/closed/* ]]; then
3570                         print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3571                             "this review</i>"
3572                 fi
3573         fi
3574 
3575         manpage=
3576         if [[ -f $F.man.cdiff.html || \
3577             -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3578                 manpage=1
3579                 print "<br/>man:"
3580         fi
3581 
3582         if [[ -f $F.man.cdiff.html ]]; then
3583                 mancdiff_url="$(print $P.man.cdiff.html | url_encode)"
3584                 manudiff_url="$(print $P.man.udiff.html | url_encode)"
3585                 mansdiff_url="$(print $P.man.sdiff.html | url_encode)"
3586                 manframes_url="$(print $P.man.frames.html | url_encode)"
3587                 print "<a href=\"$mancdiff_url\">Cdiffs</a>"
3588                 print "<a href=\"$manudiff_url\">Udiffs</a>"
3589                 if [[ -f $F.man.wdiff.html && -x $WDIFF ]]; then
3590                         manwdiff_url="$(print $P.man.wdiff.html | url_encode)"
3591                         print "<a href=\"$manwdiff_url\">Wdiffs</a>"
3592                 fi
3593                 print "<a href=\"$mansdiff_url\">Sdiffs</a>"
3594                 print "<a href=\"$manframes_url\">Frames</a>"
3595         elif [[ -n $manpage ]]; then
3596                 print " ------ ------"
3597                 if [[ -x $WDIFF ]]; then
3598                         print " ------"
3599                 fi
3600                 print " ------ ------"
3601         fi
3602 
3603         if [[ -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3604                 mantxt_url="$(print raw_files/new/$P.man.txt.html | url_encode)"
3605                 print "<a href=\"$mantxt_url\">TXT</a>"
3606                 manhtml_url="$(print raw_files/new/$P.man.html | url_encode)"
3607                 print "<a href=\"$manhtml_url\">HTML</a>"
3608                 manraw_url="$(print raw_files/new/$P.man.raw | url_encode)"
3609                 print "<a href=\"$manraw_url\">Raw</a>"
3610         elif [[ -n $manpage ]]; then
3611                 print " --- ---- ---"
3612         fi
3613 
3614         print "</p>"
3615 
3616         # Insert delta comments
3617         print "<blockquote><pre>"
3618         getcomments html $P $PP
3619         print "</pre>"
3620 
3621         # Add additional comments comment
3622         print "<!-- Add comments to explain changes in $P here -->"
3623 
3624         # Add count of changes.
3625         if [[ -f $F.count ]]; then
3626             cat $F.count
3627             rm $F.count
3628         fi
3629 
3630         if [[ $SCM_MODE == "unknown" ]]; then
3631                 # Include warnings for important file mode situations:
3632                 # 1) New executable files
3633                 # 2) Permission changes of any kind
3634                 # 3) Existing executable files
3635                 old_mode=
3636                 if [[ -f $WDIR/raw_files/old/$PP ]]; then
3637                         old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3638                 fi
3639 
3640                 new_mode=
3641                 if [[ -f $WDIR/raw_files/new/$P ]]; then
3642                         new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3643                 fi
3644 
3645                 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3646                         print "<span class=\"chmod\">"
3647                         print "<p>new executable file: mode $new_mode</p>"
3648                         print "</span>"
3649                 elif [[ -n "$old_mode" && -n "$new_mode" &&
3650                     "$old_mode" != "$new_mode" ]]; then
3651                         print "<span class=\"chmod\">"
3652                         print "<p>mode change: $old_mode to $new_mode</p>"
3653                         print "</span>"
3654                 elif [[ "$new_mode" = *[1357]* ]]; then
3655                         print "<span class=\"chmod\">"
3656                         print "<p>executable file: mode $new_mode</p>"
3657                         print "</span>"
3658                 fi
3659         fi
3660 
3661         print "</blockquote>"
3662 done
3663 
3664 print
3665 print
3666 print "<hr></hr>"
3667 print "<p style=\"font-size: small\">"
3668 print "This code review page was prepared using <b>$0</b>."
3669 print "Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3670 print "illumos</a> project.  The latest version may be obtained"
3671 print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3672 print "</body>"
3673 print "</html>"
3674 
3675 exec 1<&-                        # Close FD 1.
3676 exec 1<&3                        # dup FD 3 to restore stdout.
3677 exec 3<&-                        # close FD 3.
3678 
3679 print "Done."
3680 
3681 #
3682 # If remote deletion was specified and fails do not continue.
3683 #
3684 if [[ -n $Dflag ]]; then
3685         delete_webrev 1 1
3686         (( $? == 0 )) || exit $?
3687 fi
3688 
3689 if [[ -n $Uflag ]]; then
3690         upload_webrev
3691         exit $?
3692 fi