1 #!/bin/ksh -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 # Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
  23 # Use is subject to license terms.
  24 #
  25 # This script takes a file list and a workspace and builds a set of html files
  26 # suitable for doing a code review of source changes via a web page.
  27 # Documentation is available via 'webrev -h'.
  28 #
  29 
  30 WEBREV_UPDATED=23.18-hg
  31 
  32 HTML='<?xml version="1.0"?>
  33 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  34     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  35 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
  36 
  37 FRAMEHTML='<?xml version="1.0"?>
  38 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
  39     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
  40 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
  41 
  42 STDHEAD='<meta http-equiv="cache-control" content="no-cache" />
  43 <meta http-equiv="Pragma" content="no-cache" />
  44 <meta http-equiv="Expires" content="-1" />
  45 <!--
  46    Note to customizers: the body of the webrev is IDed as SUNWwebrev
  47    to allow easy overriding by users of webrev via the userContent.css
  48    mechanism available in some browsers.
  49 
  50    For example, to have all "removed" information be red instead of
  51    brown, set a rule in your userContent.css file like:
  52 
  53        body#SUNWwebrev span.removed { color: red ! important; }
  54 -->
  55 <style type="text/css" media="screen">
  56 body {
  57     background-color: #eeeeee;
  58 }
  59 hr {
  60     border: none 0;
  61     border-top: 1px solid #aaa;
  62     height: 1px;
  63 }
  64 div.summary {
  65     font-size: .8em;
  66     border-bottom: 1px solid #aaa;
  67     padding-left: 1em;
  68     padding-right: 1em;
  69 }
  70 div.summary h2 {
  71     margin-bottom: 0.3em;
  72 }
  73 div.summary table th {
  74     text-align: right;
  75     vertical-align: top;
  76     white-space: nowrap;
  77 }
  78 span.lineschanged {
  79     font-size: 0.7em;
  80 }
  81 span.oldmarker {
  82     color: red;
  83     font-size: large;
  84     font-weight: bold;
  85 }
  86 span.newmarker {
  87     color: green;
  88     font-size: large;
  89     font-weight: bold;
  90 }
  91 span.removed {
  92     color: #A52A2A; /*brown*/
  93 }
  94 span.changed {
  95     color: blue;
  96 }
  97 span.new {
  98     color: blue;
  99     font-weight: bold;
 100 }
 101 a.print { font-size: x-small; }
 102 
 103 </style>
 104 
 105 <style type="text/css" media="print">
 106 pre { font-size: 0.8em; font-family: courier, monospace; }
 107 span.removed { color: #444; font-style: italic }
 108 span.changed { font-weight: bold; }
 109 span.new { font-weight: bold; }
 110 span.newmarker { font-size: 1.2em; font-weight: bold; }
 111 span.oldmarker { font-size: 1.2em; font-weight: bold; }
 112 a.print {display: none}
 113 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
 114 </style>
 115 '
 116 
 117 #
 118 # UDiffs need a slightly different CSS rule for 'new' items (we don't
 119 # want them to be bolded as we do in cdiffs or sdiffs).
 120 #
 121 UDIFFCSS='
 122 <style type="text/css" media="screen">
 123 span.new {
 124     color: blue;
 125     font-weight: normal;
 126 }
 127 </style>
 128 '
 129 
 130 #
 131 # input_cmd | html_quote | output_cmd
 132 # or
 133 # html_quote filename | output_cmd
 134 #
 135 # Make a piece of source code safe for display in an HTML <pre> block.
 136 #
 137 html_quote()
 138 {
 139         sed -e "s/&/\&amp;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g" "$@" | expand
 140 }
 141 
 142 #
 143 # input_cmd | bug2url | output_cmd
 144 #
 145 # Scan for bugids and insert <a> links to the relevent bug database.
 146 #
 147 bug2url()
 148 {
 149         sed -e 's|[0-9]\{5,\}|<a href=\"'$BUGURL'&\">&</a>|g'
 150 }
 151 
 152 #
 153 # input_cmd | sac2url | output_cmd
 154 #
 155 # Scan for ARC cases and insert <a> links to the relevent SAC database.
 156 # This is slightly complicated because inside the SWAN, SAC cases are
 157 # grouped by ARC: PSARC/2006/123.  But on OpenSolaris.org, they are
 158 # referenced as 2006/123 (without labelling the ARC).
 159 #
 160 sac2url()
 161 {
 162         if [[ -z $Oflag ]]; then
 163             sed -e 's|\([A-Z]\{1,2\}ARC\)[ /]\([0-9]\{4\}\)/\([0-9]\{3\}\)|<a href=\"'$SACURL'\1/\2/\3\">\1 \2/\3</a>|g'
 164         else
 165             sed -e 's|\([A-Z]\{1,2\}ARC\)[ /]\([0-9]\{4\}\)/\([0-9]\{3\}\)|<a href=\"'$SACURL'/\2/\3\">\1 \2/\3</a>|g'
 166         fi
 167 }
 168 
 169 #
 170 # strip_unchanged <infile> | output_cmd
 171 #
 172 # Removes chunks of sdiff documents that have not changed. This makes it
 173 # easier for a code reviewer to find the bits that have changed.
 174 #
 175 # Deleted lines of text are replaced by a horizontal rule. Some
 176 # identical lines are retained before and after the changed lines to
 177 # provide some context.  The number of these lines is controlled by the
 178 # variable C in the $AWK script below.
 179 #
 180 # The script detects changed lines as any line that has a "<span class="
 181 # string embedded (unchanged lines have no particular class and are not
 182 # part of a <span>).  Blank lines (without a sequence number) are also
 183 # detected since they flag lines that have been inserted or deleted.
 184 #
 185 strip_unchanged()
 186 {
 187         $AWK '
 188         BEGIN   { C = c = 20 }
 189         NF == 0 || /span class=/ {
 190                 if (c > C) {
 191                         c -= C
 192                         inx = 0
 193                         if (c > C) {
 194                                 print "\n</pre><hr></hr><pre>"
 195                                 inx = c % C
 196                                 c = C
 197                         }
 198 
 199                         for (i = 0; i < c; i++)
 200                                 print ln[(inx + i) % C]
 201                 }
 202                 c = 0;
 203                 print
 204                 next
 205         }
 206         {       if (c >= C) {
 207                         ln[c % C] = $0
 208                         c++;
 209                         next;
 210                 }
 211                 c++;
 212                 print
 213         }
 214         END     { if (c > (C * 2)) print "\n</pre><hr></hr>" }
 215 
 216         ' $1
 217 }
 218 
 219 #
 220 # sdiff_to_html
 221 #
 222 # This function takes two files as arguments, obtains their diff, and
 223 # processes the diff output to present the files as an HTML document with
 224 # the files displayed side-by-side, differences shown in color.  It also
 225 # takes a delta comment, rendered as an HTML snippet, as the third
 226 # argument.  The function takes two files as arguments, then the name of
 227 # file, the path, and the comment.  The HTML will be delivered on stdout,
 228 # e.g.
 229 #
 230 #   $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
 231 #         new/usr/src/tools/scripts/webrev.sh \
 232 #         webrev.sh usr/src/tools/scripts \
 233 #         '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
 234 #          1234567</a> my bugid' > <file>.html
 235 #
 236 # framed_sdiff() is then called which creates $2.frames.html
 237 # in the webrev tree.
 238 #
 239 # FYI: This function is rather unusual in its use of awk.  The initial
 240 # diff run produces conventional diff output showing changed lines mixed
 241 # with editing codes.  The changed lines are ignored - we're interested in
 242 # the editing codes, e.g.
 243 #
 244 #      8c8
 245 #      57a61
 246 #      63c66,76
 247 #      68,93d80
 248 #      106d90
 249 #      108,110d91
 250 #
 251 #  These editing codes are parsed by the awk script and used to generate
 252 #  another awk script that generates HTML, e.g the above lines would turn
 253 #  into something like this:
 254 #
 255 #      BEGIN { printf "<pre>\n" }
 256 #      function sp(n) {for (i=0;i<n;i++)printf "\n"}
 257 #      function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
 258 #      NR==8           {wl("#7A7ADD");next}
 259 #      NR==54          {wl("#7A7ADD");sp(3);next}
 260 #      NR==56          {wl("#7A7ADD");next}
 261 #      NR==57          {wl("black");printf "\n"; next}
 262 #        :               :
 263 #
 264 #  This script is then run on the original source file to generate the
 265 #  HTML that corresponds to the source file.
 266 #
 267 #  The two HTML files are then combined into a single piece of HTML that
 268 #  uses an HTML table construct to present the files side by side.  You'll
 269 #  notice that the changes are color-coded:
 270 #
 271 #   black     - unchanged lines
 272 #   blue      - changed lines
 273 #   bold blue - new lines
 274 #   brown     - deleted lines
 275 #
 276 #  Blank lines are inserted in each file to keep unchanged lines in sync
 277 #  (side-by-side).  This format is familiar to users of sdiff(1) or
 278 #  Teamware's filemerge tool.
 279 #
 280 sdiff_to_html()
 281 {
 282         diff -b $1 $2 > /tmp/$$.diffs
 283 
 284         TNAME=$3
 285         TPATH=$4
 286         COMMENT=$5
 287 
 288         #
 289         #  Now we have the diffs, generate the HTML for the old file.
 290         #
 291         $AWK '
 292         BEGIN   {
 293                 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
 294                 printf "function removed() "
 295                 printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
 296                 printf "function changed() "
 297                 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
 298                 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
 299 }
 300         /^</ {next}
 301         /^>/ {next}
 302         /^---/  {next}
 303 
 304         {
 305         split($1, a, /[cad]/) ;
 306         if (index($1, "a")) {
 307                 if (a[1] == 0) {
 308                         n = split(a[2], r, /,/);
 309                         if (n == 1)
 310                                 printf "BEGIN\t\t{sp(1)}\n"
 311                         else
 312                                 printf "BEGIN\t\t{sp(%d)}\n",\
 313                                 (r[2] - r[1]) + 1
 314                         next
 315                 }
 316 
 317                 printf "NR==%s\t\t{", a[1]
 318                 n = split(a[2], r, /,/);
 319                 s = r[1];
 320                 if (n == 1)
 321                         printf "bl();printf \"\\n\"; next}\n"
 322                 else {
 323                         n = r[2] - r[1]
 324                         printf "bl();sp(%d);next}\n",\
 325                         (r[2] - r[1]) + 1
 326                 }
 327                 next
 328         }
 329         if (index($1, "d")) {
 330                 n = split(a[1], r, /,/);
 331                 n1 = r[1]
 332                 n2 = r[2]
 333                 if (n == 1)
 334                         printf "NR==%s\t\t{removed(); next}\n" , n1
 335                 else
 336                         printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
 337                 next
 338         }
 339         if (index($1, "c")) {
 340                 n = split(a[1], r, /,/);
 341                 n1 = r[1]
 342                 n2 = r[2]
 343                 final = n2
 344                 d1 = 0
 345                 if (n == 1)
 346                         printf "NR==%s\t\t{changed();" , n1
 347                 else {
 348                         d1 = n2 - n1
 349                         printf "NR==%s,NR==%s\t{changed();" , n1, n2
 350                 }
 351                 m = split(a[2], r, /,/);
 352                 n1 = r[1]
 353                 n2 = r[2]
 354                 if (m > 1) {
 355                         d2  = n2 - n1
 356                         if (d2 > d1) {
 357                                 if (n > 1) printf "if (NR==%d)", final
 358                                 printf "sp(%d);", d2 - d1
 359                         }
 360                 }
 361                 printf "next}\n" ;
 362 
 363                 next
 364         }
 365         }
 366 
 367         END     { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
 368         ' /tmp/$$.diffs > /tmp/$$.file1
 369 
 370         #
 371         #  Now generate the HTML for the new file
 372         #
 373         $AWK '
 374         BEGIN   {
 375                 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
 376                 printf "function new() "
 377                 printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
 378                 printf "function changed() "
 379                 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
 380                 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
 381         }
 382 
 383         /^</ {next}
 384         /^>/ {next}
 385         /^---/  {next}
 386 
 387         {
 388         split($1, a, /[cad]/) ;
 389         if (index($1, "d")) {
 390                 if (a[2] == 0) {
 391                         n = split(a[1], r, /,/);
 392                         if (n == 1)
 393                                 printf "BEGIN\t\t{sp(1)}\n"
 394                         else
 395                                 printf "BEGIN\t\t{sp(%d)}\n",\
 396                                 (r[2] - r[1]) + 1
 397                         next
 398                 }
 399 
 400                 printf "NR==%s\t\t{", a[2]
 401                 n = split(a[1], r, /,/);
 402                 s = r[1];
 403                 if (n == 1)
 404                         printf "bl();printf \"\\n\"; next}\n"
 405                 else {
 406                         n = r[2] - r[1]
 407                         printf "bl();sp(%d);next}\n",\
 408                         (r[2] - r[1]) + 1
 409                 }
 410                 next
 411         }
 412         if (index($1, "a")) {
 413                 n = split(a[2], r, /,/);
 414                 n1 = r[1]
 415                 n2 = r[2]
 416                 if (n == 1)
 417                         printf "NR==%s\t\t{new() ; next}\n" , n1
 418                 else
 419                         printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
 420                 next
 421         }
 422         if (index($1, "c")) {
 423                 n = split(a[2], r, /,/);
 424                 n1 = r[1]
 425                 n2 = r[2]
 426                 final = n2
 427                 d2 = 0;
 428                 if (n == 1) {
 429                         final = n1
 430                         printf "NR==%s\t\t{changed();" , n1
 431                 } else {
 432                         d2 = n2 - n1
 433                         printf "NR==%s,NR==%s\t{changed();" , n1, n2
 434                 }
 435                 m = split(a[1], r, /,/);
 436                 n1 = r[1]
 437                 n2 = r[2]
 438                 if (m > 1) {
 439                         d1  = n2 - n1
 440                         if (d1 > d2) {
 441                                 if (n > 1) printf "if (NR==%d)", final
 442                                 printf "sp(%d);", d1 - d2
 443                         }
 444                 }
 445                 printf "next}\n" ;
 446                 next
 447         }
 448         }
 449         END     { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
 450         ' /tmp/$$.diffs > /tmp/$$.file2
 451 
 452         #
 453         # Post-process the HTML files by running them back through $AWK
 454         #
 455         html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html
 456 
 457         html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html
 458 
 459         #
 460         # Now combine into a valid HTML file and side-by-side into a table
 461         #
 462         print "$HTML<head>$STDHEAD"
 463         print "<title>$WNAME Sdiff $TPATH </title>"
 464         print "</head><body id=\"SUNWwebrev\">"
 465         print "<h2>$TPATH/$TNAME</h2>"
 466         print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
 467         print "<pre>$COMMENT</pre>\n"
 468         print "<table><tr valign=\"top\">"
 469         print "<td><pre>"
 470 
 471         strip_unchanged /tmp/$$.file1.html
 472 
 473         print "</pre></td><td><pre>"
 474 
 475         strip_unchanged /tmp/$$.file2.html
 476 
 477         print "</pre></td>"
 478         print "</tr></table>"
 479         print "</body></html>"
 480 
 481         framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
 482             "$COMMENT"
 483 }
 484 
 485 
 486 #
 487 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
 488 #
 489 # Expects lefthand and righthand side html files created by sdiff_to_html.
 490 # We use insert_anchors() to augment those with HTML navigation anchors,
 491 # and then emit the main frame.  Content is placed into:
 492 #
 493 #    $WDIR/DIR/$TNAME.lhs.html
 494 #    $WDIR/DIR/$TNAME.rhs.html
 495 #    $WDIR/DIR/$TNAME.frames.html
 496 #
 497 # NOTE: We rely on standard usage of $WDIR and $DIR.
 498 #
 499 function framed_sdiff
 500 {
 501         typeset TNAME=$1
 502         typeset TPATH=$2
 503         typeset lhsfile=$3
 504         typeset rhsfile=$4
 505         typeset comments=$5
 506         typeset RTOP
 507 
 508         # Enable html files to access WDIR via a relative path.
 509         RTOP=$(relative_dir $TPATH $WDIR)
 510 
 511         # Make the rhs/lhs files and output the frameset file.
 512         print "$HTML<head>$STDHEAD <title>$WNAME rhs/lhs "\
 513                 "$TPATH/$TNAME</title>" > $WDIR/$DIR/$TNAME.lhs.html
 514 
 515         cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
 516             <script type="text/javascript" src="$RTOP/ancnav.js"></script>
 517             </head>
 518             <body id="SUNWwebrev" onkeypress="keypress(event);">
 519             <a name="0"></a>
 520             <pre>$comments</pre><hr></hr>
 521         EOF
 522 
 523         cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
 524 
 525         insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
 526         insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
 527 
 528         close='</body></html>'
 529 
 530         print $close >> $WDIR/$DIR/$TNAME.lhs.html
 531         print $close >> $WDIR/$DIR/$TNAME.rhs.html
 532 
 533         print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
 534         print "<title>$WNAME Framed-Sdiff " \
 535             "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
 536         cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
 537           <frameset rows="*,60">
 538             <frameset cols="50%,50%">
 539               <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs" />
 540               <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs" />
 541             </frameset>
 542           <frame src="$RTOP/ancnav.html" scrolling="no" marginwidth="0"
 543            marginheight="0" name="nav" />
 544           <noframes>
 545             <body id="SUNWwebrev">
 546               Alas 'frames' webrev requires that your browser supports frames
 547               and has the feature enabled.
 548             </body>
 549           </noframes>
 550           </frameset>
 551         </html>
 552         EOF
 553 }
 554 
 555 
 556 #
 557 # fix_postscript
 558 #
 559 # Merge codereview output files to a single conforming postscript file, by:
 560 #       - removing all extraneous headers/trailers
 561 #       - making the page numbers right
 562 #       - removing pages devoid of contents which confuse some
 563 #         postscript readers.
 564 #
 565 # From Casper.
 566 #
 567 function fix_postscript
 568 {
 569         infile=$1
 570 
 571         cat > /tmp/$$.crmerge.pl << \EOF
 572 
 573         print scalar(<>);         # %!PS-Adobe---
 574         print "%%Orientation: Landscape\n";
 575 
 576         $pno = 0;
 577         $doprint = 1;
 578 
 579         $page = "";
 580 
 581         while (<>) {
 582                 next if (/^%%Pages:\s*\d+/);
 583 
 584                 if (/^%%Page:/) {
 585                         if ($pno == 0 || $page =~ /\)S/) {
 586                                 # Header or single page containing text
 587                                 print "%%Page: ? $pno\n" if ($pno > 0);
 588                                 print $page;
 589                                 $pno++;
 590                         } else {
 591                                 # Empty page, skip it.
 592                         }
 593                         $page = "";
 594                         $doprint = 1;
 595                         next;
 596                 }
 597 
 598                 # Skip from %%Trailer of one document to Endprolog
 599                 # %%Page of the next
 600                 $doprint = 0 if (/^%%Trailer/);
 601                 $page .= $_ if ($doprint);
 602         }
 603 
 604         if ($page =~ /\)S/) {
 605                 print "%%Page: ? $pno\n";
 606                 print $page;
 607         } else {
 608                 $pno--;
 609         }
 610         print "%%Trailer\n%%Pages: $pno\n";
 611 EOF
 612 
 613         $PERL /tmp/$$.crmerge.pl < $infile
 614 }
 615 
 616 
 617 #
 618 # input_cmd | insert_anchors | output_cmd
 619 #
 620 # Flag blocks of difference with sequentially numbered invisible
 621 # anchors.  These are used to drive the frames version of the
 622 # sdiffs output.
 623 #
 624 # NOTE: Anchor zero flags the top of the file irrespective of changes,
 625 # an additional anchor is also appended to flag the bottom.
 626 #
 627 # The script detects changed lines as any line that has a "<span
 628 # class=" string embedded (unchanged lines have no class set and are
 629 # not part of a <span>.  Blank lines (without a sequence number)
 630 # are also detected since they flag lines that have been inserted or
 631 # deleted.
 632 #
 633 function insert_anchors
 634 {
 635         $AWK '
 636         function ia() {
 637                 # This should be able to be a singleton <a /> but that
 638                 # seems to trigger a bug in firefox a:hover rule processing
 639                 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
 640         }
 641 
 642         BEGIN {
 643                 anc=1;
 644                 inblock=1;
 645                 printf "<pre>\n";
 646         }
 647         NF == 0 || /^<span class=/ {
 648                 if (inblock == 0) {
 649                         ia();
 650                         inblock=1;
 651                 }
 652                 print;
 653                 next;
 654         }
 655         {
 656                 inblock=0;
 657                 print;
 658         }
 659         END {
 660                 ia();
 661 
 662                 printf "<b style=\"font-size: large; color: red\">";
 663                 printf "--- EOF ---</b>"
 664                 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
 665                 printf "</pre>"
 666                 printf "<form name=\"eof\" action=\"#\">";
 667                 printf "<input name=\"value\" value=\"%d\" type=\"hidden\" />",
 668                     anc - 1;
 669                 printf "</form>";
 670         }
 671         ' $1
 672 }
 673 
 674 
 675 #
 676 # relative_dir
 677 #
 678 # Print a relative return path from $1 to $2.  For example if
 679 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
 680 # this function would print "../../../../".
 681 #
 682 # In the event that $1 is not in $2 a warning is printed to stderr,
 683 # and $2 is returned-- the result of this is that the resulting webrev
 684 # is not relocatable.
 685 #
 686 function relative_dir
 687 {
 688     d1=$1
 689     d2=$2
 690     if [[ "$d1" == "." ]]; then
 691         print "."
 692     else
 693         typeset cur="${d1##$d2?(/)}"
 694         typeset ret=""
 695         if [[ $d2 == $cur ]]; then   # Should never happen.
 696                 # Should never happen.
 697                 print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
 698                 print -u2 "to \"$2\".  Check input paths.  Framed webrev "
 699                 print -u2 "will not be relocatable!"
 700                 print $2
 701                 return
 702         fi
 703 
 704         while [[ -n ${cur} ]];
 705         do
 706                 cur=${cur%%*(/)*([!/])}
 707                 if [[ -z $ret ]]; then
 708                         ret=".."
 709                 else
 710                         ret="../$ret"
 711                 fi
 712         done
 713         print $ret
 714     fi
 715 }
 716 
 717 
 718 #
 719 # frame_nav_js
 720 #
 721 # Emit javascript for frame navigation
 722 #
 723 function frame_nav_js
 724 {
 725 cat << \EOF
 726 var myInt;
 727 var scrolling=0;
 728 var sfactor = 3;
 729 var scount=10;
 730 
 731 function scrollByPix() {
 732         if (scount<=0) {
 733                 sfactor*=1.2;
 734                 scount=10;
 735         }
 736         parent.lhs.scrollBy(0,sfactor);
 737         parent.rhs.scrollBy(0,sfactor);
 738         scount--;
 739 }
 740 
 741 function scrollToAnc(num) {
 742 
 743         // Update the value of the anchor in the form which we use as
 744         // storage for this value.  setAncValue() will take care of
 745         // correcting for overflow and underflow of the value and return
 746         // us the new value.
 747         num = setAncValue(num);
 748 
 749         // Set location and scroll back a little to expose previous
 750         // lines.
 751         //
 752         // Note that this could be improved: it is possible although
 753         // complex to compute the x and y position of an anchor, and to
 754         // scroll to that location directly.
 755         //
 756         parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
 757         parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
 758 
 759         parent.lhs.scrollBy(0,-30);
 760         parent.rhs.scrollBy(0,-30);
 761 }
 762 
 763 function getAncValue()
 764 {
 765         return (parseInt(parent.nav.document.diff.real.value));
 766 }
 767 
 768 function setAncValue(val)
 769 {
 770         if (val <= 0) {
 771                 val = 0;
 772                 parent.nav.document.diff.real.value = val;
 773                 parent.nav.document.diff.display.value = "BOF";
 774                 return (val);
 775         }
 776 
 777         //
 778         // The way we compute the max anchor value is to stash it
 779         // inline in the left and right hand side pages-- it's the same
 780         // on each side, so we pluck from the left.
 781         //
 782         maxval = parent.lhs.document.eof.value.value;
 783         if (val < maxval) {
 784                 parent.nav.document.diff.real.value = val;
 785                 parent.nav.document.diff.display.value = val.toString();
 786                 return (val);
 787         }
 788 
 789         // this must be: val >= maxval
 790         val = maxval;
 791         parent.nav.document.diff.real.value = val;
 792         parent.nav.document.diff.display.value = "EOF";
 793         return (val);
 794 }
 795 
 796 function stopScroll() {
 797         if (scrolling==1) {
 798                 clearInterval(myInt);
 799                 scrolling=0;
 800         }
 801 }
 802 
 803 function startScroll() {
 804         stopScroll();
 805         scrolling=1;
 806         myInt=setInterval("scrollByPix()",10);
 807 }
 808 
 809 function handlePress(b) {
 810 
 811         switch (b) {
 812             case 1 :
 813                 scrollToAnc(-1);
 814                 break;
 815             case 2 :
 816                 scrollToAnc(getAncValue() - 1);
 817                 break;
 818             case 3 :
 819                 sfactor=-3;
 820                 startScroll();
 821                 break;
 822             case 4 :
 823                 sfactor=3;
 824                 startScroll();
 825                 break;
 826             case 5 :
 827                 scrollToAnc(getAncValue() + 1);
 828                 break;
 829             case 6 :
 830                 scrollToAnc(999999);
 831                 break;
 832         }
 833 }
 834 
 835 function handleRelease(b) {
 836         stopScroll();
 837 }
 838 
 839 function keypress(ev) {
 840         var keynum;
 841         var keychar;
 842 
 843         if (window.event) { // IE
 844                 keynum = ev.keyCode;
 845         } else if (ev.which) { // non-IE
 846                 keynum = ev.which;
 847         }
 848 
 849         keychar = String.fromCharCode(keynum);
 850 
 851         if (keychar == "k") {
 852                 handlePress(2);
 853                 return (0);
 854         } else if (keychar == "j" || keychar == " ") {
 855                 handlePress(5);
 856                 return (0);
 857         }
 858         return (1);
 859 }
 860 
 861 function ValidateDiffNum(){
 862         val = parent.nav.document.diff.display.value;
 863         if (val == "EOF") {
 864                 scrollToAnc(999999);
 865                 return;
 866         }
 867 
 868         if (val == "BOF") {
 869                 scrollToAnc(0);
 870                 return;
 871         }
 872 
 873         i=parseInt(val);
 874         if (isNaN(i)) {
 875                 parent.nav.document.diff.display.value = getAncValue();
 876         } else {
 877                 scrollToAnc(i);
 878         }
 879         return false;
 880 }
 881 
 882 EOF
 883 }
 884 
 885 #
 886 # frame_navigation
 887 #
 888 # Output anchor navigation file for framed sdiffs.
 889 #
 890 function frame_navigation
 891 {
 892         print "$HTML<head>$STDHEAD"
 893 
 894         cat << \EOF
 895 <title>Anchor Navigation</title>
 896 <meta http-equiv="Content-Script-Type" content="text/javascript" />
 897 <meta http-equiv="Content-Type" content="text/html" />
 898 
 899 <style type="text/css">
 900     div.button td { padding-left: 5px; padding-right: 5px;
 901                     background-color: #eee; text-align: center;
 902                     border: 1px #444 outset; cursor: pointer; }
 903     div.button a { font-weight: bold; color: black }
 904     div.button td:hover { background: #ffcc99; }
 905 </style>
 906 EOF
 907 
 908         print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
 909 
 910         cat << \EOF
 911 </head>
 912 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
 913         onkeypress="keypress(event);">
 914     <noscript lang="javascript">
 915       <center>
 916         <p><big>Framed Navigation controls require Javascript</big><br />
 917         Either this browser is incompatable or javascript is not enabled</p>
 918       </center>
 919     </noscript>
 920     <table width="100%" border="0" align="center">
 921         <tr>
 922           <td valign="middle" width="25%">Diff navigation:
 923           Use 'j' and 'k' for next and previous diffs; or use buttons
 924           at right</td>
 925           <td align="center" valign="top" width="50%">
 926             <div class="button">
 927               <table border="0" align="center">
 928                   <tr>
 929                     <td>
 930                       <a onmousedown="handlePress(1);return true;"
 931                          onmouseup="handleRelease(1);return true;"
 932                          onmouseout="handleRelease(1);return true;"
 933                          onclick="return false;"
 934                          title="Go to Beginning Of file">BOF</a></td>
 935                     <td>
 936                       <a onmousedown="handlePress(3);return true;"
 937                          onmouseup="handleRelease(3);return true;"
 938                          onmouseout="handleRelease(3);return true;"
 939                          title="Scroll Up: Press and Hold to accelerate"
 940                          onclick="return false;">Scroll Up</a></td>
 941                     <td>
 942                       <a onmousedown="handlePress(2);return true;"
 943                          onmouseup="handleRelease(2);return true;"
 944                          onmouseout="handleRelease(2);return true;"
 945                          title="Go to previous Diff"
 946                          onclick="return false;">Prev Diff</a>
 947                     </td></tr>
 948 
 949                   <tr>
 950                     <td>
 951                       <a onmousedown="handlePress(6);return true;"
 952                          onmouseup="handleRelease(6);return true;"
 953                          onmouseout="handleRelease(6);return true;"
 954                          onclick="return false;"
 955                          title="Go to End Of File">EOF</a></td>
 956                     <td>
 957                       <a onmousedown="handlePress(4);return true;"
 958                          onmouseup="handleRelease(4);return true;"
 959                          onmouseout="handleRelease(4);return true;"
 960                          title="Scroll Down: Press and Hold to accelerate"
 961                          onclick="return false;">Scroll Down</a></td>
 962                     <td>
 963                       <a onmousedown="handlePress(5);return true;"
 964                          onmouseup="handleRelease(5);return true;"
 965                          onmouseout="handleRelease(5);return true;"
 966                          title="Go to next Diff"
 967                          onclick="return false;">Next Diff</a></td>
 968                   </tr>
 969               </table>
 970             </div>
 971           </td>
 972           <th valign="middle" width="25%">
 973             <form action="" name="diff" onsubmit="return ValidateDiffNum();">
 974                 <input name="display" value="BOF" size="8" type="text" />
 975                 <input name="real" value="0" size="8" type="hidden" />
 976             </form>
 977           </th>
 978         </tr>
 979     </table>
 980   </body>
 981 </html>
 982 EOF
 983 }
 984 
 985 
 986 
 987 #
 988 # diff_to_html <filename> <filepath> { U | C } <comment>
 989 #
 990 # Processes the output of diff to produce an HTML file representing either
 991 # context or unified diffs.
 992 #
 993 diff_to_html()
 994 {
 995         TNAME=$1
 996         TPATH=$2
 997         DIFFTYPE=$3
 998         COMMENT=$4
 999 
1000         print "$HTML<head>$STDHEAD"
1001         print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1002 
1003         if [[ $DIFFTYPE == "U" ]]; then
1004                 print "$UDIFFCSS"
1005         fi
1006 
1007         cat <<-EOF
1008         </head>
1009         <body id="SUNWwebrev">
1010         <h2>$TPATH</h2>
1011         <a class="print" href="javascript:print()">Print this page</a>
1012         <pre>$COMMENT</pre>
1013         <pre>
1014 EOF
1015 
1016         html_quote | $AWK '
1017         /^--- new/      { next }
1018         /^\+\+\+ new/   { next }
1019         /^--- old/      { next }
1020         /^\*\*\* old/   { next }
1021         /^\*\*\*\*/     { next }
1022         /^-------/      { printf "<center><h1>%s</h1></center>\n", $0; next }
1023         /^\@\@.*\@\@$/  { printf "</pre><hr /><pre>\n";
1024                           printf "<span class=\"newmarker\">%s</span>\n", $0;
1025                           next}
1026 
1027         /^\*\*\*/       { printf "</pre><hr /><pre><span class=\"oldmarker\">%s</span>\n", $0;
1028                           next}
1029         /^---/          { printf "<span class=\"newmarker\">%s</span>\n", $0;
1030                           next}
1031         /^\+/           {printf "<span class=\"new\">%s</span>\n", $0; next}
1032         /^!/            {printf "<span class=\"changed\">%s</span>\n", $0; next}
1033         /^-/            {printf "<span class=\"removed\">%s</span>\n", $0; next}
1034                         {printf "%s\n", $0; next}
1035         '
1036 
1037         print "</pre></body></html>\n"
1038 }
1039 
1040 
1041 #
1042 # source_to_html { new | old } <filename>
1043 #
1044 # Process a plain vanilla source file to transform it into an HTML file.
1045 #
1046 source_to_html()
1047 {
1048         WHICH=$1
1049         TNAME=$2
1050 
1051         print "$HTML<head>$STDHEAD"
1052         print "<title>$WHICH $TNAME</title>"
1053         print "</head><body id=\"SUNWwebrev\">"
1054         print "<pre>"
1055         html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1056         print "</pre></body></html>"
1057 }
1058 
1059 #
1060 # teamwarecomments {text|html} parent-file child-file
1061 #
1062 # Find the first delta in the child that's not in the parent.  Get the
1063 # newest delta from the parent, get all deltas from the child starting
1064 # with that delta, and then get all info starting with the second oldest
1065 # delta in that list (the first delta unique to the child).
1066 #
1067 # This code adapted from Bill Shannon's "spc" script
1068 #
1069 comments_from_teamware()
1070 {
1071         fmt=$1
1072         pfile=$PWS/$2
1073         cfile=$CWS/$3
1074 
1075         psid=$($SCCS prs -d:I: $pfile 2>/dev/null)
1076         if [[ -z "$psid" ]]; then
1077             psid=1.1
1078         fi
1079 
1080         set -A sids $($SCCS prs -l -r$psid -d:I: $cfile 2>/dev/null)
1081         N=${#sids[@]}
1082 
1083         nawkprg='
1084                 /^COMMENTS:/    {p=1; next}
1085                 /^D [0-9]+\.[0-9]+/ {printf "--- %s ---\n", $2; p=0; }
1086                 NF == 0u        { next }
1087                 {if (p==0) next; print $0 }'
1088 
1089         if [[ $N -ge 2 ]]; then
1090                 sid1=${sids[$((N-2))]}  # Gets 2nd to last sid
1091 
1092                 if [[ $fmt == "text" ]]; then
1093                         $SCCS prs -l -r$sid1 $cfile  2>/dev/null | \
1094                             $AWK "$nawkprg"
1095                         return
1096                 fi
1097 
1098                 $SCCS prs -l -r$sid1 $cfile  2>/dev/null | \
1099                     html_quote | bug2url | sac2url | $AWK "$nawkprg"
1100         fi
1101 }
1102 
1103 #
1104 # wxcomments {text|html} filepath
1105 #
1106 # Given the pathname of a file, find its location in a "wx" active file
1107 # list and print the following sccs comment.  Output is either text or
1108 # HTML; if the latter, embedded bugids (sequence of 5 or more digits) are
1109 # turned into URLs.
1110 #
1111 comments_from_wx()
1112 {
1113         typeset fmt=$1
1114         typeset p=$2
1115 
1116         comm=`$AWK '
1117         $1 == "'$p'" {
1118                 do getline ; while (NF > 0)
1119                 getline
1120                 while (NF > 0) { print ; getline }
1121                 exit
1122         }' < $wxfile`
1123 
1124         if [[ $fmt == "text" ]]; then
1125                 print "$comm"
1126                 return
1127         fi
1128 
1129         print "$comm" | html_quote | bug2url | sac2url
1130 }
1131 
1132 comments_from_mercurial()
1133 {
1134         fmt=$1
1135         pfile=$PWS/$2
1136         cfile=$CWS/$3
1137 
1138         logdir=`dirname $cfile`
1139         logf=`basename $cfile`
1140         if [ -d $logdir ]; then
1141             ( cd $logdir;
1142                 active=`hg status $logf 2>/dev/null`
1143                 # If the output from 'hg status' is not empty, it means the file
1144                 # hasn't been committed, so don't fetch comments.
1145                 if [[ -z $active ]] ; then
1146                     if [[ -n $ALL_CREV ]]; then
1147                         rev_opt=
1148                         for rev in $ALL_CREV; do
1149                             rev_opt="$rev_opt --rev $rev"
1150                         done
1151                         comm=`hg log $rev_opt --follow --template 'rev {rev} : {desc}\n' $logf`
1152                     elif [[ -n $FIRST_CREV ]]; then
1153                         comm=`hg log --rev $FIRST_CREV:tip --follow --template 'rev {rev} : {desc}\n' $logf`
1154                     else
1155                         comm=`hg log -l1 --follow --template 'rev {rev} : {desc}\n' $logf`
1156                     fi
1157                 else
1158                     comm=""
1159                 fi
1160                 if [[ $fmt == "text" ]]; then
1161                     print "$comm"
1162                     return
1163                 fi
1164           
1165                 print "$comm" | html_quote | bug2url | sac2url
1166                 )
1167         fi
1168 }
1169 
1170 
1171 #
1172 # getcomments {text|html} filepath parentpath
1173 #
1174 # Fetch the comments depending on what SCM mode we're in.
1175 #
1176 getcomments()
1177 {
1178         typeset fmt=$1
1179         typeset p=$2
1180         typeset pp=$3
1181 
1182         if [[ -n $wxfile ]]; then
1183                 comments_from_wx $fmt $p
1184         else
1185                 if [[ $SCM_MODE == "teamware" ]]; then
1186                         comments_from_teamware $fmt $pp $p
1187                 elif [[ $SCM_MODE == "mercurial" ]]; then
1188                         comments_from_mercurial $fmt $pp $p
1189                 fi
1190         fi
1191 }
1192 
1193 #
1194 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1195 #
1196 # Print out Code Inspection figures similar to sccs-prt(1) format.
1197 #
1198 function printCI
1199 {
1200         integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1201         typeset str
1202         if (( tot == 1 )); then
1203                 str="line"
1204         else
1205                 str="lines"
1206         fi
1207         printf '%d %s changed: %d ins; %d del; %d mod; %d unchg' \
1208             $tot $str $ins $del $mod $unc
1209 }
1210 
1211 
1212 #
1213 # difflines <oldfile> <newfile>
1214 #
1215 # Calculate and emit number of added, removed, modified and unchanged lines,
1216 # and total lines changed, the sum of added + removed + modified.
1217 #
1218 function difflines
1219 {
1220         integer tot mod del ins unc err
1221         typeset filename
1222 
1223         eval $( diff -e $1 $2 | $AWK '
1224         # Change range of lines: N,Nc
1225         /^[0-9]*,[0-9]*c$/ {
1226                 n=split(substr($1,1,length($1)-1), counts, ",");
1227                 if (n != 2) {
1228                     error=2
1229                     exit;
1230                 }
1231                 #
1232                 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1233                 # following would be 5 - 3 = 2! Hence +1 for correction.
1234                 #
1235                 r=(counts[2]-counts[1])+1;
1236 
1237                 #
1238                 # Now count replacement lines: each represents a change instead
1239                 # of a delete, so increment c and decrement r.
1240                 #
1241                 while (getline != /^\.$/) {
1242                         c++;
1243                         r--;
1244                 }
1245                 #
1246                 # If there were more replacement lines than original lines,
1247                 # then r will be negative; in this case there are no deletions,
1248                 # but there are r changes that should be counted as adds, and
1249                 # since r is negative, subtract it from a and add it to c.
1250                 #
1251                 if (r < 0) {
1252                         a-=r;
1253                         c+=r;
1254                 }
1255 
1256                 #
1257                 # If there were more original lines than replacement lines, then
1258                 # r will be positive; in this case, increment d by that much.
1259                 #
1260                 if (r > 0) {
1261                         d+=r;
1262                 }
1263                 next;
1264         }
1265 
1266         # Change lines: Nc
1267         /^[0-9].*c$/ {
1268                 # The first line is a replacement; any more are additions.
1269                 if (getline != /^\.$/) {
1270                         c++;
1271                         while (getline != /^\.$/) a++;
1272                 }
1273                 next;
1274         }
1275 
1276         # Add lines: both Na and N,Na
1277         /^[0-9].*a$/ {
1278                 while (getline != /^\.$/) a++;
1279                 next;
1280         }
1281 
1282         # Delete range of lines: N,Nd
1283         /^[0-9]*,[0-9]*d$/ {
1284                 n=split(substr($1,1,length($1)-1), counts, ",");
1285                 if (n != 2) {
1286                         error=2
1287                         exit;
1288                 }
1289                 #
1290                 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1291                 # following would be 5 - 3 = 2! Hence +1 for correction.
1292                 #
1293                 r=(counts[2]-counts[1])+1;
1294                 d+=r;
1295                 next;
1296         }
1297 
1298         # Delete line: Nd.   For example 10d says line 10 is deleted.
1299         /^[0-9]*d$/ {d++; next}
1300 
1301         # Should not get here!
1302         {
1303                 error=1;
1304                 exit;
1305         }
1306 
1307         # Finish off - print results
1308         END {
1309                 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1310                     (c+d+a), c, d, a, error);
1311         }' )
1312 
1313         # End of $AWK, Check to see if any trouble occurred.
1314         if (( $? > 0 || err > 0 )); then
1315                 print "Unexpected Error occurred reading" \
1316                     "\`diff -e $1 $2\`: \$?=$?, err=" $err
1317                 return
1318         fi
1319 
1320         # Accumulate totals
1321         (( TOTL += tot ))
1322         (( TMOD += mod ))
1323         (( TDEL += del ))
1324         (( TINS += ins ))
1325         # Calculate unchanged lines
1326         unc=`wc -l < $1`
1327         if (( unc > 0 )); then
1328                 (( unc -= del + mod ))
1329                 (( TUNC += unc ))
1330         fi
1331         # print summary
1332         print "<span class=\"lineschanged\">\c"
1333         printCI $tot $ins $del $mod $unc
1334         print "</span>"
1335 }
1336 
1337 
1338 #
1339 # flist_from_wx
1340 #
1341 # Sets up webrev to source its information from a wx-formatted file.
1342 # Sets the global 'wxfile' variable.
1343 #
1344 function flist_from_wx
1345 {
1346         typeset argfile=$1
1347         if [[ -n ${argfile%%/*} ]]; then
1348                 #
1349                 # If the wx file pathname is relative then make it absolute
1350                 # because the webrev does a "cd" later on.
1351                 #
1352                 wxfile=$PWD/$argfile
1353         else
1354                 wxfile=$argfile
1355         fi
1356 
1357         $AWK '{ c = 1; print;
1358           while (getline) {
1359                 if (NF == 0) { c = -c; continue }
1360                 if (c > 0) print
1361           }
1362         }' $wxfile > $FLIST
1363 
1364         print " Done."
1365 }
1366 
1367 #
1368 # flist_from_teamware [ <args-to-putback-n> ]
1369 #
1370 # Generate the file list by extracting file names from a putback -n.  Some
1371 # names may come from the "update/create" messages and others from the
1372 # "currently checked out" warning.  Renames are detected here too.  Extract
1373 # values for CODEMGR_WS and CODEMGR_PARENT from the output of the putback
1374 # -n as well, but remove them if they are already defined.
1375 #
1376 function flist_from_teamware
1377 {
1378         if [[ -n $codemgr_parent ]]; then
1379                 if [[ ! -d $codemgr_parent/Codemgr_wsdata ]]; then
1380                         print -u2 "parent $codemgr_parent doesn't look like a" \
1381                             "valid teamware workspace"
1382                         exit 1
1383                 fi
1384                 parent_args="-p $codemgr_parent"
1385         fi
1386 
1387         print " File list from: 'putback -n $parent_args $*' ... \c"
1388 
1389         putback -n $parent_args $* 2>&1 |
1390             $AWK '
1391                 /^update:|^create:/     {print $2}
1392                 /^Parent workspace:/    {printf("CODEMGR_PARENT=%s\n",$3)}
1393                 /^Child workspace:/     {printf("CODEMGR_WS=%s\n",$3)}
1394                 /^The following files are currently checked out/ {p = 1; next}
1395                 NF == 0                 {p=0 ; next}
1396                 /^rename/               {old=$3}
1397                 $1 == "to:"             {print $2, old}
1398                 /^"/                    {next}
1399                 p == 1                  {print $1}' |
1400             sort -r -k 1,1 -u | sort > $FLIST
1401 
1402         print " Done."
1403 }
1404 
1405 function outgoing_from_mercurial_forest
1406 {
1407     hg foutgoing --template 'rev: {rev}\n' $OUTPWS | $FILTER | $AWK '
1408         BEGIN           {ntree=0}
1409         /^comparing/    {next}
1410         /^no changes/   {next}
1411         /^searching/    {next}
1412         /^\[.*\]$/      {tree=substr($1,2,length($1)-2);
1413                          trees[ntree++] = tree;
1414                          revs[tree]=-1;
1415                          next}
1416         /^rev:/   {rev=$2+0;
1417                    if (revs[tree] == -1 || rev < revs[tree])
1418                         { revs[tree] = rev; };
1419                   next;}
1420         END       {for (tree in trees)
1421                         { rev=revs[trees[tree]];
1422                           if (rev > 0) 
1423                                 {printf("%s %d\n",trees[tree],rev-1)}
1424                         }}' | while read LINE
1425     do
1426         set - $LINE
1427         TREE=$1
1428         REV=$2
1429         A=`hg -R $CWS/$TREE log --rev $REV --template '{node}'`
1430         FSTAT_OPT="--rev $A"
1431         print "Revision: $A $REV" >> $FLIST
1432         treestatus $TREE
1433     done
1434 }
1435 
1436 function flist_from_mercurial_forest
1437 {
1438     rm -f $FLIST
1439     if [ -z "$Nflag" ]; then
1440         print " File list from hg foutgoing $PWS ..."
1441         outgoing_from_mercurial_forest
1442         HG_LIST_FROM_COMMIT=1
1443     fi
1444     if [ ! -f $FLIST ]; then
1445         # hg commit hasn't been run see what is lying around
1446         print "\n No outgoing, perhaps you haven't commited."
1447         print " File list from hg fstatus -mard ...\c"
1448         FSTAT_OPT=
1449         fstatus
1450         HG_LIST_FROM_COMMIT=0
1451     fi
1452     print " Done."
1453 }
1454 
1455 #
1456 # Used when dealing with the result of 'hg foutgoing'
1457 # When now go down the tree and generate the change list
1458 #
1459 function treestatus
1460 {
1461     TREE=$1
1462     HGCMD="hg -R $CWS/$TREE status $FSTAT_OPT"
1463     
1464     $HGCMD -mdn 2>/dev/null | $FILTER | while read F
1465     do
1466         echo $TREE/$F
1467     done >> $FLIST
1468 
1469     # Then all the added files
1470     # But some of these could have been "moved" or renamed ones
1471     # so let's make sure we get the proper info
1472     # hg status -aC will produce something like:
1473     #   A subdir/File3
1474     #   A subdir/File4
1475     #     File4
1476     #   A subdir/File5
1477     # The first and last are simple addition while the middle one
1478     # is a move/rename
1479 
1480     $HGCMD -aC | $FILTER | while read LINE; do
1481         ldone=""
1482         while [ -z "$ldone" ]; do
1483             ldone="1"
1484             set - $LINE
1485             if [ $# -eq 2 -a "$1" == "A" ]; then
1486                 AFILE=$2
1487                 if read LINE2; then
1488                     set - $LINE2
1489                     if [ $# -eq 1 ]; then
1490                         echo $TREE/$AFILE $TREE/$1 >>$FLIST
1491                     elif [ $# -eq 2 ]; then
1492                         echo $TREE/$AFILE >>$FLIST
1493                         LINE=$LINE2
1494                         ldone=""
1495                     fi
1496                 else
1497                     echo $TREE/$AFILE >>$FLIST
1498                 fi
1499             fi
1500         done
1501     done
1502     $HGCMD -rn | $FILTER | while read RFILE; do
1503         grep "$TREE/$RFILE" $FLIST >/dev/null
1504         if [ $? -eq 1 ]; then
1505             echo $TREE/$RFILE >>$FLIST
1506         fi
1507     done
1508 }
1509 
1510 function fstatus
1511 {
1512     #
1513     # forest extension is still being changed. For instance the output
1514     # of fstatus used to no prepend the tree path to filenames, but
1515     # this has changed recently. AWK code below does try to handle both
1516     # cases
1517     #
1518     hg fstatus -mdn $FSTAT_OPT 2>/dev/null | $FILTER | $AWK '
1519         /^\[.*\]$/      {tree=substr($1,2,length($1)-2); next}
1520         $1 != ""        {n=index($1,tree);
1521                          if (n == 0)
1522                                 { printf("%s/%s\n",tree,$1)}
1523                          else
1524                                 { printf("%s\n",$1)}}' >> $FLIST
1525 
1526     #
1527     # There is a bug in the output of fstatus -aC on recent versions: it
1528     # inserts a space between the name of the tree and the filename of the
1529     # old file. e.g.:
1530     #
1531     # $ hg fstatus -aC
1532     # [.]
1533     #
1534     # [MyWS]
1535     # A MyWS/subdir/File2
1536     #  MyWS/ File2
1537     #
1538     # [MyWS2]
1539     #
1540 
1541     hg fstatus -aC $FSTAT_OPT 2>/dev/null | $FILTER | $AWK '
1542         /^\[.*\]$/      {tree=substr($1,2,length($1)-2); next}
1543         /^A .*/         {n=index($2,tree);
1544                          if (n == 0)
1545                                 { printf("A %s/%s\n",tree,$2)}
1546                          else
1547                                 { printf("A %s\n",$2)}; 
1548                          next}
1549         /^ /            {n=index($1,tree);
1550                          if (n == 0)
1551                                 { printf("%s/%s\n",tree,$1)}
1552                          else
1553                                 { if (NF == 2)
1554                                         printf("%s/%s\n",tree,$2)
1555                                   else
1556                                         printf("%s\n",$1)
1557                                 };
1558                          next}
1559         ' | while read LINE; do
1560         ldone=""
1561         while [ -z "$ldone" ]; do
1562             ldone="1"
1563             set - $LINE
1564             if [ $# -eq 2 -a "$1" == "A" ]; then
1565                 AFILE=$2
1566                 if read LINE2; then
1567                     set - $LINE2
1568                     if [ $# -eq 1 ]; then
1569                         echo $AFILE $1 >>$FLIST
1570                     elif [ $# -eq 2 ]; then
1571                         echo $AFILE >>$FLIST
1572                         LINE=$LINE2
1573                         ldone=""
1574                     fi
1575                 else
1576                     echo $AFILE >>$FLIST
1577                 fi
1578             fi
1579         done
1580     done
1581     hg fstatus -rn $FSTAT_OPT 2>/dev/null | $FILTER | $AWK '
1582         /^\[.*\]$/      {tree=substr($1,2,length($1)-2); next}
1583         $1 != ""        {n=index($1,tree);
1584                          if (n == 0)
1585                                 { printf("%s/%s\n",tree,$1)}
1586                          else
1587                                 { printf("%s\n",$1)}}' | while read RFILE; do
1588         grep "$RFILE" $FLIST >/dev/null
1589         if [ $? -eq 1 ]; then
1590             echo $RFILE >>$FLIST
1591         fi
1592     done
1593 }
1594 
1595 #
1596 # flist_from_mercurial $PWS
1597 #
1598 # Only local file based repositories are supported at present
1599 # since even though we can determine the list from the parent finding
1600 # the changes is harder.
1601 #
1602 # We first look for any outgoing files, this is for when the user has
1603 # run hg commit.  If we don't find any then we look with hg status.
1604 #
1605 # We need at least one of default-push or default paths set in .hg/hgrc
1606 # If neither are set we don't know who to compare with.
1607 
1608 function flist_from_mercurial 
1609 {
1610 #       if [ "${PWS##ssh://}" != "$PWS" -o \
1611 #            "${PWS##http://}" != "$PWS" -o \
1612 #            "${PWS##https://}" != "$PWS" ]; then
1613 #               print "Remote Mercurial repositories not currently supported."
1614 #               print "Set default and/or default-push to a local repository"
1615 #               exit
1616 #       fi
1617     if [[ -n $forestflag ]]; then
1618         HG_LIST_FROM_COMMIT=
1619         flist_from_mercurial_forest
1620     else
1621         STATUS_REV=
1622         if [[ -n $rflag ]]; then
1623             STATUS_REV="--rev $PARENT_REV"
1624         elif [[ -n $OUTREV ]]; then
1625             STATUS_REV="--rev $OUTREV"
1626         else
1627             # hg commit hasn't been run see what is lying around
1628             print "\n No outgoing, perhaps you haven't commited."
1629         fi
1630         # First let's list all the modified or deleted files
1631 
1632         hg status $STATUS_REV -mdn | $FILTER > $FLIST
1633 
1634         # Then all the added files
1635         # But some of these could have been "moved" or renamed ones
1636         # so let's make sure we get the proper info
1637         # hg status -aC will produce something like:
1638         #       A subdir/File3
1639         #       A subdir/File4
1640         #         File4
1641         #       A subdir/File5
1642         # The first and last are simple addition while the middle one
1643         # is a move/rename
1644 
1645         hg status $STATUS_REV -aC | $FILTER >$FLIST.temp
1646         while read LINE; do
1647             ldone=""
1648             while [ -z "$ldone" ]; do
1649                 ldone="1"
1650                 set - $LINE
1651                 if [ $# -eq 2 -a "$1" == "A" ]; then
1652                     AFILE=$2
1653                     if read LINE2; then
1654                         set - $LINE2
1655                         if [ $# -eq 1 ]; then
1656                             echo $AFILE $1 >>$FLIST
1657                         elif [ $# -eq 2 ]; then
1658                             echo $AFILE >>$FLIST
1659                             LINE=$LINE2
1660                             ldone=""
1661                         fi
1662                     else
1663                         echo $AFILE >>$FLIST
1664                     fi
1665                 fi
1666             done
1667         done < $FLIST.temp
1668         hg status $STATUS_REV -rn | $FILTER > $FLIST.temp
1669         while read RFILE; do
1670             grep "$RFILE" $FLIST >/dev/null
1671             if [ $? -eq 1 ]; then
1672                 echo $RFILE >>$FLIST
1673             fi
1674         done < $FLIST.temp
1675         rm -f $FLIST.temp
1676     fi
1677 }
1678 
1679 function env_from_flist
1680 {
1681         [[ -r $FLIST ]] || return
1682 
1683         #
1684         # Use "eval" to set env variables that are listed in the file
1685         # list.  Then copy those into our local versions of those
1686         # variables if they have not been set already.
1687         #
1688         eval `sed -e "s/#.*$//" $FLIST | grep = `
1689 
1690         [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
1691 
1692         #
1693         # Check to see if CODEMGR_PARENT is set in the flist file.
1694         #
1695         [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
1696             codemgr_parent=$CODEMGR_PARENT
1697 }
1698 
1699 #
1700 # detect_scm
1701 #
1702 # We dynamically test the SCM type; this allows future extensions to
1703 # new SCM types
1704 #
1705 function detect_scm
1706 {
1707         #
1708         # If CODEMGR_WS is specified in the flist file, we assume teamware.
1709         #
1710         if [[ -r $FLIST ]]; then
1711                 egrep '^CODEMGR_WS=' $FLIST > /dev/null 2>&1
1712                 if [[ $? -eq 0 ]]; then
1713                         print "teamware"
1714                         return
1715                 fi
1716         fi
1717 
1718         #
1719         # The presence of $CODEMGR_WS and a Codemgr_wsdata directory
1720         # is our clue that this is a teamware workspace.
1721         # Same if true if current directory has a Codemgr_wsdata sub-dir
1722         #
1723         if [[ -z "$CODEMGR_WS" ]]; then
1724             CODEMGR_WS=`workspace name 2>/dev/null`
1725         fi
1726 
1727         if [[ -n $CODEMGR_WS && -d "$CODEMGR_WS/Codemgr_wsdata" ]]; then
1728                 print "teamware"
1729         elif [[ -d $PWD/Codemgr_wsdata ]]; then
1730                 print "teamware"
1731         elif hg root >/dev/null ; then
1732                 print "mercurial"
1733         else
1734                 print "unknown"
1735         fi
1736 }
1737 
1738 #
1739 # Extract the parent workspace from the Codemgr_wsdata/parent file
1740 #
1741 function parent_from_teamware
1742 {
1743     if [[ -f "$1/Codemgr_wsdata/parent" ]]; then
1744         tail -1 "$1/Codemgr_wsdata/parent"
1745     fi
1746 }
1747 
1748 function look_for_prog
1749 {
1750         typeset path
1751         typeset ppath
1752         typeset progname=$1
1753 
1754         DEVTOOLS=
1755         OS=`uname`
1756         if [[ "$OS" == "SunOS" ]]; then
1757             DEVTOOLS="/java/devtools/`uname -p`/bin"
1758         elif [[ "$OS" == "Linux" ]]; then
1759             DEVTOOLS="/java/devtools/linux/bin"
1760         fi
1761             
1762         ppath=$PATH
1763         ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1764         ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin
1765         ppath=$ppath:/opt/onbld/bin/`uname -p`
1766         ppath=$ppath:/java/devtools/share/bin:$DEVTOOLS
1767 
1768         PATH=$ppath prog=`whence $progname`
1769         if [[ -n $prog ]]; then
1770                 print $prog
1771         fi
1772 }
1773 
1774 function build_old_new_teamware
1775 {
1776         # If the child's version doesn't exist then
1777         # get a readonly copy.
1778 
1779         if [[ ! -f $F && -f SCCS/s.$F ]]; then
1780                 $SCCS get -s $F
1781         fi
1782 
1783         #
1784         # Snag new version of file.
1785         #
1786         rm -f $newdir/$DIR/$F
1787         cp $F $newdir/$DIR/$F
1788 
1789         #
1790         # Get the parent's version of the file. First see whether the
1791         # child's version is checked out and get the parent's version
1792         # with keywords expanded or unexpanded as appropriate.
1793         #
1794         if [ -f $PWS/$PDIR/SCCS/s.$PF -o \
1795             -f $PWS/$PDIR/SCCS/p.$PF ]; then
1796                 rm -f $olddir/$PDIR/$PF
1797                 if [ -f SCCS/p.$F ]; then
1798                         $SCCS get -s -p -k $PWS/$PDIR/$PF \
1799                             > $olddir/$PDIR/$PF
1800                 else
1801                         $SCCS get -s -p    $PWS/$PDIR/$PF \
1802                             > $olddir/$PDIR/$PF
1803                 fi
1804         else
1805                 if [[ -f $PWS/$PDIR/$PF ]]; then
1806                         # Parent is not a real workspace, but just a raw
1807                         # directory tree - use the file that's there as
1808                         # the old file.
1809 
1810                         rm -f $olddir/$DIR/$F
1811                         cp $PWS/$PDIR/$PF $olddir/$DIR/$F
1812                 fi
1813         fi
1814 }
1815 
1816 #
1817 # Find the parent for $1
1818 #
1819 function find_outrev
1820 {
1821     crev=$1
1822     prev=`hg log -r $crev --template '{parents}\n'`
1823     if [[ -z "$prev" ]]
1824     then
1825         # No specific parent means previous changeset is parent
1826         prev=`expr $crev - 1`
1827     else
1828         # Format is either of the following two:
1829         # 546:7df6fcf1183b
1830         # 548:16f1915bb5cd 547:ffaa4e775815
1831         prev=`echo $prev | sed -e 's/\([0-9]*\):.*/\1/'`
1832     fi
1833     print $prev
1834 }
1835 
1836 function extract_ssh_infos
1837 {
1838     CMD=$1
1839     if expr "$CMD" : 'ssh://[^/]*@' >/dev/null; then
1840         ssh_user=`echo $CMD | sed -e 's/ssh:\/\/\(.*\)@.*/\1/'`
1841         ssh_host=`echo $CMD | sed -e 's/ssh:\/\/.*@\([^/]*\)\/.*/\1/'`
1842         ssh_dir=`echo $CMD | sed -e 's/ssh:\/\/.*@[^/]*\/\(.*\)/\1/'`
1843     else
1844         ssh_user=
1845         ssh_host=`echo $CMD | sed -e 's/ssh:\/\/\([^/]*\)\/.*/\1/'`
1846         ssh_dir=`echo $CMD | sed -e 's/ssh:\/\/[^/]*\/\(.*\)/\1/'`
1847     fi
1848     
1849 }
1850 
1851 function build_old_new_mercurial
1852 {
1853         olddir=$1
1854         newdir=$2
1855         DIR=$3
1856         F=$4
1857         #
1858         # new version of the file.
1859         #
1860         rm -rf $newdir/$DIR/$F
1861         if [ -f $F ]; then
1862             cp $F  $newdir/$DIR/$F
1863         fi
1864 
1865         #
1866         # Old version of the file.
1867         #
1868         rm -rf $olddir/$DIR/$F
1869 
1870         if [ -n "$PWS" ]; then
1871             if expr "$PWS" : 'ssh://' >/dev/null
1872             then
1873                 extract_ssh_infos $PWS
1874                 if [ -n "$ssh_user" ]; then
1875                     parent="ssh -l $ssh_user $ssh_host hg -R $ssh_dir --cwd $ssh_dir"
1876                 else
1877                     parent="ssh $ssh_host hg -R $ssh_dir --cwd $ssh_dir"
1878                 fi
1879             else
1880                 parent="hg -R $PWS --cwd $PWS"
1881             fi
1882         else
1883             parent=""
1884         fi
1885 
1886         if [ -z "$rename" ]; then
1887             if [ -n "$rflag" ]; then
1888                 parentrev=$PARENT_REV
1889             elif [ "$HG_LIST_FROM_COMMIT" -eq 1 ]; then
1890                 parentrev=$OUTREV
1891             else
1892                 if [[ -n $HG_BRANCH ]]; then
1893                     parentrev=$HG_BRANCH
1894                 else
1895                     parentrev="tip"
1896                 fi
1897             fi
1898 
1899             if [ -n "$parentrev" ]; then
1900                 if [ -z "$parent" ]; then
1901                     hg cat --rev $parentrev --output $olddir/$DIR/$F $F 2>/dev/null
1902                 else
1903                     # when specifying a workspace we have to provide
1904                     # the full path
1905                     $parent cat --rev $parentrev --output $olddir/$DIR/$F $DIR/$F 2>/dev/null
1906                 fi
1907             fi
1908         else
1909             # It's a rename (or a move), so let's make sure we move
1910             # to the right directory first, then restore it once done
1911             current_dir=`pwd`
1912             cd $CWS/$PDIR
1913             if [ -n "$rflag" ]; then
1914                 parentrev=$PARENT_REV
1915             elif [ "$HG_LIST_FROM_COMMIT" -eq 1 ]; then
1916                 parentrev=$OUTREV
1917             fi
1918             if [ -z "$parentrev" ]; then
1919                 parentrev=`hg log -l1 $PF | $AWK -F: '/changeset/ {print $2}'`
1920             fi
1921             if [ -n "$parentrev" ]; then
1922                 mkdir -p $olddir/$PDIR
1923                 if [ -z "$parent" ]; then
1924                     hg cat --rev $parentrev --output $olddir/$PDIR/$PF $PF 2>/dev/null
1925                 else
1926                     $parent cat --rev $parentrev --output $olddir/$PDIR/$PF $PDIR/$PF 2>/dev/null
1927                 fi
1928             fi
1929             cd $current_dir
1930         fi
1931 }
1932 
1933 function build_old_new
1934 {
1935         if [[ $SCM_MODE == "teamware" ]]; then
1936                 build_old_new_teamware $@
1937         fi
1938 
1939         if [[ $SCM_MODE == "mercurial" ]]; then
1940                 build_old_new_mercurial $@
1941         fi
1942 }
1943 
1944 
1945 #
1946 # Usage message.
1947 #
1948 function usage
1949 {
1950         print "Usage:\twebrev [common-options]
1951         webrev [common-options] ( <file> | - )
1952         webrev [common-options] -w <wx file>
1953         webrev [common-options] -l [arguments to 'putback']
1954 
1955 Options:
1956         -v: Print the version of this tool.
1957         -b: Do not ignore changes in the amount of white space.
1958         -c <CR#>: Include link to CR (aka bugid) in the main page.
1959         -O: Print bugids/arc cases suitable for OpenJDK.
1960         -i <filename>: Include <filename> in the index.html file.
1961         -o <outdir>: Output webrev to specified directory.
1962         -p <compare-against>: Use specified parent wkspc or basis for comparison
1963         -w <wxfile>: Use specified wx active file.
1964         -u <username>: Use that username instead of 'guessing' one.
1965         -m: Forces the use of Mercurial
1966         -t: Forces the use of Teamware
1967 
1968 Mercurial only options:
1969         -r rev: Compare against a specified revision
1970         -N: Skip 'hg outgoing', use only 'hg status'
1971         -f: Use the forest extension
1972 
1973 Environment:
1974         WDIR: Control the output directory.
1975         WEBREV_BUGURL: Control the URL prefix for bugids.
1976         WEBREV_SACURL: Control the URL prefix for ARC cases.
1977 
1978 SCM Environment:
1979         Teamware: CODEMGR_WS: Workspace location.
1980         Teamware: CODEMGR_PARENT: Parent workspace location.
1981 
1982 "
1983 
1984         exit 2
1985 }
1986 
1987 #
1988 #
1989 # Main program starts here
1990 #
1991 #
1992 LANG="C"
1993 LC_ALL="C"
1994 export LANG LC_ALL
1995 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
1996 
1997 set +o noclobber
1998 
1999 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2000 [[ -z $WX ]] && WX=`look_for_prog wx`
2001 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2002 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2003 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2004 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2005 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2006 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2007 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2008 [[ -z $WSPACE ]] && WSPACE=`look_for_prog workspace`
2009 [[ -z $JAR ]] && JAR=`look_for_prog jar`
2010 [[ -z $ZIP ]] && ZIP=`look_for_prog zip`
2011 [[ -z $GETENT ]] && GETENT=`look_for_prog getent`
2012 [[ -z $WGET ]] && WGET=`look_for_prog wget`
2013 
2014 if uname | grep CYGWIN >/dev/null
2015 then
2016         ISWIN=1
2017         # Under windows mercurial outputs '\' instead of '/'
2018         FILTER="tr '\\\\' '/'"
2019 else
2020         FILTER="cat"
2021 fi
2022 
2023 if [[ ! -x $PERL ]]; then
2024         print -u2 "Error: No perl interpreter found.  Exiting."
2025         exit 1
2026 fi
2027 
2028 #
2029 # These aren't fatal, but we want to note them to the user.
2030 # We don't warn on the absence of 'wx' until later when we've
2031 # determined that we actually need to try to invoke it.
2032 #
2033 # [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2034 # [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2035 # [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2036 
2037 # Declare global total counters.
2038 integer TOTL TINS TDEL TMOD TUNC
2039 
2040 flist_mode=
2041 flist_file=
2042 bflag=
2043 iflag=
2044 oflag=
2045 pflag=
2046 uflag=
2047 lflag=
2048 wflag=
2049 Oflag=
2050 rflag=
2051 Nflag=
2052 forestflag=
2053 while getopts "c:i:o:p:r:u:lmtwONvfb" opt
2054 do
2055         case $opt in
2056         b)      bflag=1;;
2057 
2058         i)      iflag=1
2059                 INCLUDE_FILE=$OPTARG;;
2060 
2061         o)      oflag=1
2062                 WDIR=$OPTARG;;
2063 
2064         p)      pflag=1
2065                 codemgr_parent=$OPTARG;;
2066 
2067         u)      uflag=1
2068                 username=$OPTARG;;
2069 
2070         c)      if [[ -z $CRID ]]; then
2071                    CRID=$OPTARG
2072                 else
2073                    CRID="$CRID $OPTARG"
2074                 fi;;
2075 
2076         m)      SCM_MODE="mercurial";;
2077 
2078         t)      SCM_MODE="teamware";;
2079 
2080         #
2081         # If -l has been specified, we need to abort further options
2082         # processing, because subsequent arguments are going to be
2083         # arguments to 'putback -n'.
2084         #
2085         l)      lflag=1
2086                 break;;
2087 
2088         w)      wflag=1;;
2089 
2090         O)      Oflag=1;;
2091 
2092         N)      Nflag=1;;
2093 
2094         f)      forestflag=1;;
2095 
2096         r)      rflag=1
2097                 PARENT_REV=$OPTARG;;
2098 
2099         v)      print "$0 version: $WEBREV_UPDATED";;
2100                 
2101 
2102         ?)      usage;;
2103         esac
2104 done
2105 
2106 FLIST=/tmp/$$.flist
2107 
2108 if [[ -n $wflag && -n $lflag ]]; then
2109         usage
2110 fi
2111 
2112 if [[ -n $forestflag && -n $rflag ]]; then
2113     print "The -r <rev> flag is incompatible with the use of forests"
2114     exit 2
2115 fi
2116 
2117 #
2118 # If this manually set as the parent, and it appears to be an earlier webrev,
2119 # then note that fact and set the parent to the raw_files/new subdirectory.
2120 #
2121 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2122         parent_webrev="$codemgr_parent"
2123         codemgr_parent="$codemgr_parent/raw_files/new"
2124 fi
2125 
2126 if [[ -z $wflag && -z $lflag ]]; then
2127         shift $(($OPTIND - 1))
2128 
2129         if [[ $1 == "-" ]]; then
2130                 cat > $FLIST
2131                 flist_mode="stdin"
2132                 flist_done=1
2133                 shift
2134         elif [[ -n $1 ]]; then
2135                 if [[ ! -r $1 ]]; then
2136                         print -u2 "$1: no such file or not readable"
2137                         usage
2138                 fi
2139                 cat $1 > $FLIST
2140                 flist_mode="file"
2141                 flist_file=$1
2142                 flist_done=1
2143                 shift
2144         else
2145                 flist_mode="auto"
2146         fi
2147 fi
2148 
2149 #
2150 # Before we go on to further consider -l and -w, work out which SCM we think
2151 # is in use.
2152 #
2153 if [[ -z $SCM_MODE ]]; then
2154     SCM_MODE=`detect_scm $FLIST`
2155 fi
2156 if [[ $SCM_MODE == "unknown" ]]; then
2157         print -u2 "Unable to determine SCM type currently in use."
2158         print -u2 "For teamware: webrev looks for \$CODEMGR_WS either in"
2159         print -u2 "              the environment or in the file list."
2160         print -u2 "For mercurial: webrev runs 'hg root'."
2161         exit 1
2162 fi
2163 
2164 print -u2 "   SCM detected: $SCM_MODE"
2165 
2166 
2167 if [[ $SCM_MODE == "mercurial" ]]; then
2168     #
2169     # determine Workspace and parent workspace paths
2170     #
2171     CWS=`hg root | $FILTER`
2172     if [[ -n $pflag && -z "$PWS" ]]; then
2173         OUTPWS=$codemgr_parent
2174         # Let's try to expand it if it's an alias defined in [paths]
2175         tmp=`hg path $OUTPWS 2>/dev/null | $FILTER`
2176         if [[ -n $tmp ]]; then
2177             OUTPWS="$tmp"
2178         fi
2179         if [[ -n $rflag ]]; then
2180             if expr "$codemgr_parent" : 'ssh://.*' >/dev/null; then
2181                 PWS=$codemgr_parent
2182             else
2183                 PWS=`hg -R "$codemgr_parent" root 2>/dev/null | $FILTER`
2184             fi
2185         fi
2186     fi
2187     #
2188     # OUTPWS is the parent repository to use when using 'hg outgoing'
2189     #
2190     if [[ -z $Nflag ]]; then
2191         if [[ -n $forestflag ]]; then
2192             #
2193             # for forest we have to rely on properly set default and
2194             # default-push because they can be different from the top one.
2195             # unless of course it was explicitely speficied with -p
2196             if [[ -z $pflag ]]; then
2197                 OUTPWS=
2198             fi
2199         else
2200             #
2201             # Unfortunately mercurial is bugged and doesn't handle
2202             # aliases correctly in 'hg path default'
2203             # So let's do it ourselves. Sigh...
2204             if [[ -z "$OUTPWS" ]]; then
2205                 OUTPWS=`grep default-push $CWS/.hg/hgrc | $AWK '{print $3}' | $FILTER`
2206             fi
2207             # Still empty, means no default-push
2208             if [[ -z "$OUTPWS" ]]; then
2209                 OUTPWS=`grep 'default =' $CWS/.hg/hgrc | $AWK '{print $3}' | $FILTER`
2210             fi
2211             # Let's try to expand it if it's an alias defined in [paths]
2212             tmp=`hg path $OUTPWS 2>/dev/null | $FILTER`
2213             if [[ -n $tmp ]]; then
2214                 OUTPWS="$tmp"
2215             fi
2216         fi
2217     fi
2218     #
2219     # OUTPWS may contain username:password, let's make sure we remove the
2220     # sensitive information before we print out anything in the HTML
2221     #
2222     OUTPWS2=$OUTPWS
2223     if [[ -n $OUTPWS ]]; then
2224         if [[ `expr "$OUTPWS" : '.*://[^/]*@.*'` -gt 0 ]]; then
2225             # Remove everything between '://' and '@'
2226             OUTPWS2=`echo $OUTPWS | sed -e 's/\(.*:\/\/\).*@\(.*\)/\1\2/'`
2227         fi
2228     fi
2229 
2230     if [[ -z $HG_BRANCH ]]; then
2231         HG_BRANCH=`hg branch`
2232         if [ "$HG_BRANCH" == "default" ]; then
2233             #
2234             # 'default' means no particular branch, so let's cancel that
2235             #
2236             HG_BRANCH=
2237         fi
2238     fi
2239 
2240     if [[ -z $forestflag ]]; then
2241         if [[ -z $Nflag ]]; then
2242             #
2243             # If no "-N", always do "hg outgoing" against parent
2244             # repository to determine list of outgoing revisions.
2245             #
2246             ALL_CREV=`hg outgoing -q --template '{rev}\n' $OUTPWS | sort -n`
2247             if [[ -n $ALL_CREV ]]; then
2248                 FIRST_CREV=`echo "$ALL_CREV" | head -1`
2249                 #
2250                 # If no "-r", choose revision to compare against by
2251                 # finding the latest revision not in the outgoing list.
2252                 #
2253                 if [[ -z $rflag ]]; then
2254                     OUTREV=`find_outrev "$FIRST_CREV"`
2255                     if [[ -n $OUTREV ]]; then
2256                         HG_LIST_FROM_COMMIT=1
2257                     fi
2258                 fi
2259             fi
2260         elif [[ -n $rflag ]]; then
2261             #
2262             # If skipping "hg outgoing" but still comparing against a
2263             # specific revision (not the tip), set revision for comment
2264             # accumulation.
2265             #
2266             FIRST_CREV=`hg log --rev $PARENT_REV --template '{rev}'`
2267             FIRST_CREV=`expr $FIRST_CREV + 1`
2268         fi
2269     fi
2270     #Let's check if a merge is needed, if so, issue a warning
2271     PREV=`hg parent | grep '^tag:.*tip$'`
2272     if [[ -z $PREV ]]; then
2273         print "WARNING: parent rev is not tip. Maybe an update or merge is needed"
2274     fi
2275 fi
2276 
2277 if [[ -n $lflag ]]; then
2278         #
2279         # If the -l flag is given instead of the name of a file list,
2280         # then generate the file list by extracting file names from a
2281         # putback -n.
2282         #
2283         shift $(($OPTIND - 1))
2284         if [[ $SCM_MODE == "teamware" ]]; then
2285                 flist_from_teamware "$*"
2286         elif [[ $SCM_MODE == "mercurial" ]]; then
2287                 flist_from_mercurial
2288         fi
2289         flist_done=1
2290         shift $#
2291 
2292 elif [[ -n $wflag ]]; then
2293         #
2294         # If the -w is given then assume the file list is in Bonwick's "wx"
2295         # command format, i.e.  pathname lines alternating with SCCS comment
2296         # lines with blank lines as separators.  Use the SCCS comments later
2297         # in building the index.html file.
2298         #
2299         shift $(($OPTIND - 1))
2300         wxfile=$1
2301         if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2302                 if [[ -r $CODEMGR_WS/wx/active ]]; then
2303                         wxfile=$CODEMGR_WS/wx/active
2304                 fi
2305         fi
2306 
2307         [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2308             "be auto-detected (check \$CODEMGR_WS)" && exit 1
2309 
2310         print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2311         flist_from_wx $wxfile
2312         flist_done=1
2313         if [[ -n "$*" ]]; then
2314                 shift
2315         fi
2316 elif [[ $flist_mode == "stdin" ]]; then
2317         print -u2 " File list from: standard input"
2318 elif [[ $flist_mode == "file" ]]; then
2319         print -u2 " File list from: $flist_file"
2320 fi
2321 
2322 if [[ $# -gt 0 ]]; then
2323         print -u2 "WARNING: unused arguments: $*"
2324 fi
2325 
2326 if [[ $SCM_MODE == "teamware" ]]; then
2327         #
2328         # Parent (internally $codemgr_parent) and workspace ($codemgr_ws) can
2329         # be set in a number of ways, in decreasing precedence:
2330         #
2331         #      1) on the command line (only for the parent)
2332         #      2) in the user environment
2333         #      3) in the flist
2334         #      4) automatically based on the workspace (only for the parent)
2335         #
2336 
2337         #
2338         # Here is case (2): the user environment
2339         #
2340         [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
2341         [[ -z $codemgr_ws && -n $WSPACE ]] && codemgr_ws=`$WSPACE name`
2342             
2343         if [[ -n $codemgr_ws && ! -d $codemgr_ws ]]; then
2344                 print -u2 "$codemgr_ws: no such workspace"
2345                 exit 1
2346         fi
2347 
2348         [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
2349             codemgr_parent=$CODEMGR_PARENT
2350 
2351         if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then
2352                 print -u2 "$codemgr_parent: no such directory"
2353                 exit 1
2354         fi
2355 
2356         #
2357         # If we're in auto-detect mode and we haven't already gotten the file
2358         # list, then see if we can get it by probing for wx.
2359         #
2360         if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then
2361                 if [[ ! -x $WX ]]; then
2362                         print -u2 "WARNING: wx not found!"
2363                 fi
2364 
2365                 #
2366                 # We need to use wx list -w so that we get renamed files, etc.
2367                 # but only if a wx active file exists-- otherwise wx will
2368                 # hang asking us to initialize our wx information.
2369                 #
2370                 if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then
2371                         print -u2 " File list from: 'wx list -w' ... \c"
2372                         $WX list -w > $FLIST
2373                         $WX comments > /tmp/$$.wx_comments
2374                         wxfile=/tmp/$$.wx_comments
2375                         print -u2 "done"
2376                         flist_done=1
2377                 fi
2378         fi
2379 
2380         #
2381         # If by hook or by crook we've gotten a file list by now (perhaps
2382         # from the command line), eval it to extract environment variables from
2383         # it: This is step (3).
2384         #
2385         env_from_flist
2386 
2387         #
2388         # Continuing step (3): If we still have no file list, we'll try to get
2389         # it from teamware.
2390         #
2391         if [[ -z $flist_done ]]; then
2392                 flist_from_teamware
2393                 env_from_flist
2394         fi
2395 
2396         if [[ -z $codemgr_ws && -d $PWD/Codemgr_wsdata ]]; then
2397             codemgr_ws=$PWD
2398         fi
2399         #
2400         # Observe true directory name of CODEMGR_WS, as used later in
2401         # webrev title.
2402         #
2403         if [[ -n $codemgr_ws ]]; then
2404             codemgr_ws=$(cd $codemgr_ws;print $PWD)
2405         fi
2406 
2407         if [[ -n $codemgr_parent ]]; then
2408             codemgr_parent=$(cd $codemgr_parent;print $PWD)
2409         fi
2410 
2411         #
2412         # (4) If we still don't have a value for codemgr_parent, get it
2413         # from workspace.
2414         #
2415         [[ -z $codemgr_parent && -n $WSPACE ]] && codemgr_parent=`$WSPACE parent`
2416         [[ -z $codemgr_parent ]] && codemgr_parent=`parent_from_teamware $codemgr_ws`
2417 
2418         if [[ ! -d $codemgr_parent ]]; then
2419             print -u2 "$CODEMGR_PARENT: no such parent workspace"
2420             exit 1
2421         fi
2422 
2423         #
2424         # Reset CODEMGR_WS to make sure teamware commands are happy.
2425         #
2426         CODEMGR_WS=$codemgr_ws
2427         CWS=$codemgr_ws
2428         PWS=$codemgr_parent
2429 elif [[ $SCM_MODE == "mercurial" ]]; then
2430     if [[ -z $flist_done ]]; then
2431         flist_from_mercurial $PWS
2432     fi
2433 fi
2434 
2435 #
2436 # If the user didn't specify a -i option, check to see if there is a
2437 # webrev-info file in the workspace directory.
2438 #
2439 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2440         iflag=1
2441         INCLUDE_FILE="$CWS/webrev-info"
2442 fi
2443 
2444 if [[ -n $iflag ]]; then
2445         if [[ ! -r $INCLUDE_FILE ]]; then
2446                 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2447                     "not readable."
2448                 exit 1
2449         else
2450                 #
2451                 # $INCLUDE_FILE may be a relative path, and the script alters
2452                 # PWD, so we just stash a copy in /tmp.
2453                 #
2454                 cp $INCLUDE_FILE /tmp/$$.include
2455         fi
2456 fi
2457 
2458 #
2459 # Output directory.
2460 #
2461 if [[ -z $WDIR ]]; then
2462     WDIR=$CWS/webrev
2463 else
2464     # If the output directory doesn't end with '/webrev' or '/webrev/'
2465     # then add '/webrev'. This is for backward compatibility
2466     if ! expr $WDIR : '.*/webrev/\?$' >/dev/null
2467     then
2468         WDIR=$WDIR/webrev
2469     fi
2470 fi
2471 # WDIR=${WDIR:-$CWS/webrev}
2472 
2473 #
2474 # Name of the webrev, derived from the workspace name; in the
2475 # future this could potentially be an option.
2476 #
2477 # Let's keep what's after the last '/'
2478 WNAME=${CWS##*/}
2479 
2480 #
2481 # If WDIR doesn't start with '/' or 'x:' prepend the current dir
2482 #
2483 if [ ${WDIR%%/*} ]; then
2484     if [[ -n $ISWIN ]]; then
2485         if [ ${WDIR%%[A-Za-z]:*} ]; then
2486             WDIR=$PWD/$WDIR
2487         fi
2488     else
2489         WDIR=$PWD/$WDIR
2490     fi
2491 fi
2492 
2493 if [[ ! -d $WDIR ]]; then
2494         mkdir -p $WDIR
2495         [[ $? != 0 ]] && exit 1
2496 fi
2497 
2498 #
2499 # Summarize what we're going to do.
2500 #
2501 print "      Workspace: $CWS"
2502 if [[ -n $parent_webrev ]]; then
2503     print "Compare against: webrev at $parent_webrev"
2504 elif [[ -n $OUTPWS2 ]]; then
2505     print "Compare against: $OUTPWS2"
2506 fi
2507 if [[ -n $HG_BRANCH ]]; then
2508     print "         Branch: $HG_BRANCH"
2509 fi
2510 if [[ -n $rflag ]]; then
2511         print "Compare against version: $PARENT_REV"
2512 fi
2513 [[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
2514 print "      Output to: $WDIR"
2515 
2516 #
2517 # Save the file list in the webrev dir
2518 #
2519 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
2520 
2521 #
2522 #    Bug IDs will be replaced by a URL.  Order of precedence
2523 #    is: default location, $WEBREV_BUGURL, the -O flag.
2524 #
2525 BUGURL='http://monaco.sfbay.sun.com/detail.jsp?cr='
2526 [[ -n $WEBREV_BUGURL ]] && BUGURL="$WEBREV_BUGURL"
2527 [[ -n "$Oflag" ]] && \
2528     BUGURL='http://bugs.sun.com/bugdatabase/view_bug.do?bug_id='
2529 
2530 #
2531 #    Likewise, ARC cases will be replaced by a URL.  Order of precedence
2532 #    is: default, $WEBREV_SACURL, the -O flag.
2533 #
2534 #    Note that -O also triggers different substitution behavior for
2535 #    SACURL.  See sac2url().
2536 #
2537 SACURL='http://sac.eng.sun.com'
2538 [[ -n $WEBREV_SACURL ]] && SACURL="$WEBREV_SACURL"
2539 [[ -n $Oflag ]] && \
2540     SACURL='http://www.opensolaris.org/os/community/arc/caselog'
2541 
2542 rm -f $WDIR/$WNAME.patch
2543 rm -f $WDIR/$WNAME.ps
2544 rm -f $WDIR/$WNAME.pdf
2545 
2546 touch $WDIR/$WNAME.patch
2547 
2548 print "   Output Files:"
2549 
2550 #
2551 # Clean up the file list: Remove comments, blank lines and env variables.
2552 #
2553 sed -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
2554 FLIST=/tmp/$$.flist.clean
2555 
2556 #
2557 # Clean up residual raw files
2558 #
2559 if [ -d $WDIR/raw_files ]; then
2560     rm -rf $WDIR/raw_files 2>/dev/null
2561 fi
2562 
2563 #
2564 # Should we ignore changes in white spaces when generating diffs?
2565 # 
2566 if [[ -n $bflag ]]; then
2567     DIFFOPTS="-t"
2568 else
2569     DIFFOPTS="-bt"
2570 fi
2571 #
2572 # First pass through the files: generate the per-file webrev HTML-files.
2573 #
2574 while read LINE
2575 do
2576         set - $LINE
2577         P=$1
2578 
2579         if [[ $1 == "Revision:" ]]; then
2580             OUTREV=$2
2581             continue
2582         fi
2583         #
2584         # Normally, each line in the file list is just a pathname of a
2585         # file that has been modified or created in the child.  A file
2586         # that is renamed in the child workspace has two names on the
2587         # line: new name followed by the old name.
2588         #
2589         oldname=""
2590         oldpath=""
2591         rename=
2592         if [[ $# -eq 2 ]]; then
2593                 PP=$2                   # old filename
2594                 oldname=" (was $PP)"
2595                 oldpath="$PP"
2596                 rename=1
2597                 PDIR=${PP%/*}
2598                 if [[ $PDIR == $PP ]]; then
2599                         PDIR="."   # File at root of workspace
2600                 fi
2601 
2602                 PF=${PP##*/}
2603 
2604                 DIR=${P%/*}
2605                 if [[ $DIR == $P ]]; then
2606                         DIR="."   # File at root of workspace
2607                 fi
2608 
2609                 F=${P##*/}
2610         else
2611                 DIR=${P%/*}
2612                 if [[ "$DIR" == "$P" ]]; then
2613                         DIR="."   # File at root of workspace
2614                 fi
2615 
2616                 F=${P##*/}
2617 
2618                 PP=$P
2619                 PDIR=$DIR
2620                 PF=$F
2621         fi
2622 
2623         # Make the webrev directory if necessary as it may have been
2624         # removed because it was empty
2625         if [ ! -d $CWS/$DIR ]; then
2626             mkdir -p $CWS/$DIR
2627         fi
2628 
2629         COMM=`getcomments html $P $PP`
2630 
2631         print "\t$P$oldname\n\t\t\c"
2632 
2633         # Make the webrev mirror directory if necessary
2634         mkdir -p $WDIR/$DIR
2635 
2636         # cd to the directory so the names are short
2637         cd $CWS/$DIR
2638 
2639         #
2640         # If we're in OpenSolaris mode, we enforce a minor policy:
2641         # help to make sure the reviewer doesn't accidentally publish
2642         # source which is in usr/closed/*
2643         #
2644         if [[ -n $Oflag ]]; then
2645                 pclosed=${P##usr/closed/}
2646                 if [[ $pclosed != $P ]]; then
2647                         print "*** Omitting closed source for OpenSolaris" \
2648                             "mode review"
2649                         continue
2650                 fi
2651         fi
2652 
2653         #
2654         # We stash old and new files into parallel directories in /tmp
2655         # and do our diffs there.  This makes it possible to generate
2656         # clean looking diffs which don't have absolute paths present.
2657         #
2658         olddir=$WDIR/raw_files/old
2659         newdir=$WDIR/raw_files/new
2660         mkdir -p $olddir
2661         mkdir -p $newdir
2662         mkdir -p $olddir/$PDIR
2663         mkdir -p $newdir/$DIR
2664 
2665         build_old_new $olddir $newdir $DIR $F
2666 
2667         if [[ ! -f $F && ! -f $olddir/$DIR/$F ]]; then
2668                 print "*** Error: file not in parent or child"
2669                 continue
2670         fi
2671 
2672         cd $WDIR/raw_files
2673         ofile=old/$PDIR/$PF
2674         nfile=new/$DIR/$F
2675 
2676         mv_but_nodiff=
2677         cmp $ofile $nfile > /dev/null 2>&1
2678         if [[ $? == 0 && $rename == 1 ]]; then
2679                 mv_but_nodiff=1
2680         fi
2681 
2682         #
2683         # Cleaning up
2684         #
2685         rm -f $WDIR/$DIR/$F.cdiff.html
2686         rm -f $WDIR/$DIR/$F.udiff.html
2687         rm -f $WDIR/$DIR/$F.wdiff.html
2688         rm -f $WDIR/$DIR/$F.sdiff.html
2689         rm -f $WDIR/$DIR/$F-.html
2690         rm -f $WDIR/$DIR/$F.html
2691 
2692         its_a_jar=
2693         if expr $F : '.*\.jar' >/dev/null; then
2694             its_a_jar=1
2695             # It's a JAR file, let's do it differntly
2696             if [[ -z $JAR ]]; then
2697                 print "No access to jar, so can't produce diffs for jar files"
2698             else
2699                 if [ -f $ofile ]; then
2700                     $JAR -tvf $ofile >"$ofile".lst
2701                 fi
2702                 if [ -f $nfile ]; then
2703                     $JAR -tvf $nfile >"$nfile".lst
2704                 fi
2705 
2706                 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
2707 
2708                     ${CDIFFCMD:-diff -bt -C 5} $ofile.lst $nfile.lst > $WDIR/$DIR/$F.cdiff
2709                     diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
2710                         > $WDIR/$DIR/$F.cdiff.html
2711                     print " cdiffs\c"
2712 
2713                     ${UDIFFCMD:-diff -bt -U 5} $ofile.lst $nfile.lst > $WDIR/$DIR/$F.udiff
2714                     diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
2715                         > $WDIR/$DIR/$F.udiff.html
2716 
2717                     print " udiffs\c"
2718 
2719                     if [[ -x $WDIFF ]]; then
2720                         $WDIFF -c "$COMM" \
2721                             -t "$WNAME Wdiff $DIR/$F" $ofile.lst $nfile.lst > \
2722                             $WDIR/$DIR/$F.wdiff.html 2>/dev/null
2723                         if [[ $? -eq 0 ]]; then
2724                             print " wdiffs\c"
2725                         else
2726                             print " wdiffs[fail]\c"
2727                         fi
2728                     fi
2729 
2730                     sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
2731                         > $WDIR/$DIR/$F.sdiff.html
2732                     print " sdiffs\c"
2733 
2734                     print " frames\c"
2735 
2736                     rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
2737 
2738                     difflines $ofile.lst $nfile.lst > $WDIR/$DIR/$F.count
2739 
2740                 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
2741                 # renamed file: may also have differences
2742                     difflines $ofile.lst $nfile.lst > $WDIR/$DIR/$F.count
2743                 elif [[ -f $nfile ]]; then
2744                 # new file: count added lines
2745                     difflines /dev/null $nfile.lst > $WDIR/$DIR/$F.count
2746                 elif [[ -f $ofile ]]; then
2747                 # old file: count deleted lines
2748                     difflines $ofile.lst /dev/null > $WDIR/$DIR/$F.count
2749                 fi
2750             fi
2751         else
2752             
2753             #
2754             # If we have old and new versions of the file then run the
2755             # appropriate diffs.  This is complicated by a couple of factors:
2756             #
2757             #   - renames must be handled specially: we emit a 'remove'
2758             #     diff and an 'add' diff
2759             #   - new files and deleted files must be handled specially
2760             #   - Solaris patch(1m) can't cope with file creation
2761             #     (and hence renames) as of this writing.
2762             #   - To make matters worse, gnu patch doesn't interpret the
2763             #     output of Solaris diff properly when it comes to
2764             #     adds and deletes.  We need to do some "cleansing"
2765             #     transformations:
2766             #       [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
2767             #       [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
2768             #
2769             cleanse_rmfile="sed 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
2770             cleanse_newfile="sed 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
2771 
2772             rm -f $WDIR/$DIR/$F.patch
2773             if [[ -z $rename ]]; then
2774                 if [ ! -f $ofile ]; then
2775                     diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
2776                         > $WDIR/$DIR/$F.patch
2777                 elif [ ! -f $nfile ]; then
2778                     diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
2779                         > $WDIR/$DIR/$F.patch
2780                 else
2781                     diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
2782                 fi
2783             else
2784                 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
2785                     > $WDIR/$DIR/$F.patch
2786 
2787                 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
2788                     >> $WDIR/$DIR/$F.patch
2789 
2790             fi
2791 
2792 
2793         #
2794         # Tack the patch we just made onto the accumulated patch for the
2795         # whole wad.
2796         #
2797             cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
2798 
2799             print " patch\c"
2800 
2801             if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
2802 
2803                 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
2804                 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
2805                     > $WDIR/$DIR/$F.cdiff.html
2806                 print " cdiffs\c"
2807 
2808                 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
2809                 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
2810                     > $WDIR/$DIR/$F.udiff.html
2811 
2812                 print " udiffs\c"
2813 
2814                 if [[ -x $WDIFF ]]; then
2815                     $WDIFF -c "$COMM" \
2816                         -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
2817                         $WDIR/$DIR/$F.wdiff.html 2>/dev/null
2818                     if [[ $? -eq 0 ]]; then
2819                         print " wdiffs\c"
2820                     else
2821                         print " wdiffs[fail]\c"
2822                     fi
2823                 fi
2824 
2825                 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
2826                     > $WDIR/$DIR/$F.sdiff.html
2827                 print " sdiffs\c"
2828 
2829                 print " frames\c"
2830 
2831                 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
2832 
2833                 difflines $ofile $nfile > $WDIR/$DIR/$F.count
2834 
2835             elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
2836                 # renamed file: may also have differences
2837                 difflines $ofile $nfile > $WDIR/$DIR/$F.count
2838             elif [[ -f $nfile ]]; then
2839                 # new file: count added lines
2840                 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
2841             elif [[ -f $ofile ]]; then
2842                 # old file: count deleted lines
2843                 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
2844             fi
2845         fi
2846         #
2847         # Now we generate the postscript for this file.  We generate diffs
2848         # only in the event that there is delta, or the file is new (it seems
2849         # tree-killing to print out the contents of deleted files).
2850         #
2851         if [[ -f $nfile ]]; then
2852                 ocr=$ofile
2853                 [[ ! -f $ofile ]] && ocr=/dev/null
2854 
2855                 if [[ -z $mv_but_nodiff ]]; then
2856                         textcomm=`getcomments text $P $PP`
2857                         if [[ -x $CODEREVIEW ]]; then
2858                                 $CODEREVIEW -y "$textcomm" \
2859                                     -e $ocr $nfile \
2860                                     > /tmp/$$.psfile 2>/dev/null &&
2861                                     cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
2862                                 if [[ $? -eq 0 ]]; then
2863                                         print " ps\c"
2864                                 else
2865                                         print " ps[fail]\c"
2866                                 fi
2867                         fi
2868                 fi
2869         fi
2870 
2871         if [[ -f $ofile && -z $mv_but_nodiff ]]; then
2872             if [[ -n $its_a_jar ]]; then
2873                 source_to_html Old $P < $ofile.lst > $WDIR/$DIR/$F-.html
2874             else
2875                 source_to_html Old $P < $ofile > $WDIR/$DIR/$F-.html
2876             fi
2877                 print " old\c"
2878         fi
2879 
2880         if [[ -f $nfile ]]; then
2881             if [[ -n $its_a_jar ]]; then
2882                 source_to_html New $P < $nfile.lst > $WDIR/$DIR/$F.html
2883             else
2884                 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
2885             fi
2886                 print " new\c"
2887         fi
2888 
2889         print
2890 done < $FLIST
2891 
2892 frame_nav_js > $WDIR/ancnav.js
2893 frame_navigation > $WDIR/ancnav.html
2894 
2895 if [[ -f $WDIR/$WNAME.ps && -x $CODEREVIEW && -x $PS2PDF ]]; then
2896         print " Generating PDF: \c"
2897         fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
2898         print "Done."
2899 fi
2900 
2901 # Now build the index.html file that contains
2902 # links to the source files and their diffs.
2903 
2904 cd $CWS
2905 
2906 # Save total changed lines for Code Inspection.
2907 print "$TOTL" > $WDIR/TotalChangedLines
2908 
2909 print "     index.html: \c"
2910 INDEXFILE=$WDIR/index.html
2911 exec 3<&1                        # duplicate stdout to FD3.
2912 exec 1<&-                        # Close stdout.
2913 exec > $INDEXFILE            # Open stdout to index file.
2914 
2915 print "$HTML<head>"
2916 print "<meta name=\"scm\" content=\"$SCM_MODE\" />"
2917 print "$STDHEAD"
2918 print "<title>$WNAME</title>"
2919 print "</head>"
2920 print "<body id=\"SUNWwebrev\">"
2921 print "<div class=\"summary\">"
2922 print "<h2>Code Review for $WNAME</h2>"
2923 
2924 print "<table>"
2925 
2926 if [[ -z $uflag ]]
2927 then
2928     if [[ $SCM_MODE == "mercurial" ]]
2929     then
2930         #
2931         # Let's try to extract the user name from the .hgrc file
2932         #
2933         username=`grep '^username' $HOME/.hgrc | sed 's/^username[ ]*=[ ]*\(.*\)/\1/'`
2934     fi
2935 
2936     if [[ -z $username ]]
2937     then
2938         #
2939         # Figure out the username and gcos name.  To maintain compatibility
2940         # with passwd(4), we must support '&' substitutions.
2941         #
2942         username=`id | cut -d '(' -f 2 | cut -d ')' -f 1`
2943         if [[ -x $GETENT ]]; then
2944             realname=`$GETENT passwd $username | cut -d':' -f 5 | cut -d ',' -f 1`
2945         fi
2946         userupper=`print "$username" | sed 's/\<./\u&/g'`
2947         realname=`print $realname | sed s/\&/$userupper/`
2948     fi
2949 fi
2950 
2951 date="on `date`"
2952 
2953 if [[ -n "$username" && -n "$realname" ]]; then
2954         print "<tr><th>Prepared by:</th>"
2955         print "<td>$realname ($username) $date</td></tr>"
2956 elif [[ -n "$username" ]]; then
2957         print "<tr><th>Prepared by:</th><td>$username $date</td></tr>"
2958 fi
2959 
2960 print "<tr><th>Workspace:</th><td>$CWS</td></tr>"
2961 if [[ -n $parent_webrev ]]; then
2962         print "<tr><th>Compare against:</th><td>"
2963         print "webrev at $parent_webrev"
2964 else
2965     if [[ -n $OUTPWS2 ]]; then
2966         print "<tr><th>Compare against:</th><td>"
2967         print "$OUTPWS2"
2968     fi
2969 fi
2970 print "</td></tr>"
2971 if [[ -n $rflag ]]; then
2972     print "<tr><th>Compare against version:</th><td>$PARENT_REV</td></tr>"
2973 elif [[ -n $OUTREV ]]; then
2974     if [[ -z $forestflag ]]; then
2975         print "<tr><th>Compare against version:</th><td>$OUTREV</td></tr>"
2976     fi
2977 fi
2978 if [[ -n $HG_BRANCH ]]; then
2979     print "<tr><th>Branch:</th><td>$HG_BRANCH</td></tr>"
2980 fi
2981 
2982 print "<tr><th>Summary of changes:</th><td>"
2983 printCI $TOTL $TINS $TDEL $TMOD $TUNC
2984 print "</td></tr>"
2985 
2986 if [[ -f $WDIR/$WNAME.patch ]]; then
2987         print "<tr><th>Patch of changes:</th><td>"
2988         print "<a href=\"$WNAME.patch\">$WNAME.patch</a></td></tr>"
2989 fi
2990 if [[ -f $WDIR/$WNAME.pdf ]]; then
2991         print "<tr><th>Printable review:</th><td>"
2992         print "<a href=\"$WNAME.pdf\">$WNAME.pdf</a></td></tr>"
2993 fi
2994 
2995 if [[ -n "$iflag" ]]; then
2996         print "<tr><th>Author comments:</th><td><div>"
2997         cat /tmp/$$.include
2998         print "</div></td></tr>"
2999 fi
3000 # Add links to referenced CRs, if any
3001 # external URL has a <title> like:
3002 # <title>Bug ID: 6641309 Wrong Cookie separator used in HttpURLConnection</title>
3003 # while internal URL has <title> like:
3004 # <title>6641309: Wrong Cookie separator used in HttpURLConnection</title>
3005 #
3006 if [[ -n $CRID ]]; then
3007     for id in $CRID
3008     do
3009         print "<tr><th>Bug id:</th><td>"
3010         url="${BUGURL}${id}"
3011         if [[ -n $WGET ]]; then
3012             msg=`$WGET -q $url -O - | grep '<title>' | sed 's/<title>\(.*\)<\/title>/\1/' | sed 's/Bug ID://'`
3013         fi
3014         if [[ -n $msg ]]; then
3015             print "<a href=\"$url\">$msg</a>"
3016         else
3017             print $id | bug2url
3018         fi
3019         
3020         print "</td></tr>"
3021     done
3022 fi
3023 print "<tr><th>Legend:</th><td>"
3024 print "<b>Modified file</b><br /><font color=\"red\"><b>Deleted file</b></font><br /><font color=\"green\"><b>New file</b></font></td></tr>"
3025 print "</table>"
3026 print "</div>"
3027 
3028 #
3029 # Second pass through the files: generate the rest of the index file
3030 #
3031 while read LINE
3032 do
3033         set - $LINE
3034         if [[ $1 == "Revision:" ]]; then
3035             FIRST_CREV=`expr $3 + 1`
3036             continue
3037         fi
3038         P=$1
3039 
3040         if [[ $# == 2 ]]; then
3041                 PP=$2
3042                 oldname=" <i>(was $PP)</i>"
3043 
3044         else
3045                 PP=$P
3046                 oldname=""
3047         fi
3048 
3049         DIR=${P%/*}
3050         if [[ $DIR == $P ]]; then
3051                 DIR="."   # File at root of workspace
3052         fi
3053 
3054         # Avoid processing the same file twice.
3055         # It's possible for renamed files to
3056         # appear twice in the file list
3057 
3058         F=$WDIR/$P
3059 
3060         print "<p><code>"
3061 
3062         # If there's a diffs file, make diffs links
3063 
3064         NODIFFS=
3065         if [[ -f $F.cdiff.html ]]; then
3066                 print "<a href=\"$P.cdiff.html\">Cdiffs</a>"
3067                 print "<a href=\"$P.udiff.html\">Udiffs</a>"
3068 
3069                 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3070                         print "<a href=\"$P.wdiff.html\">Wdiffs</a>"
3071                 fi
3072 
3073                 print "<a href=\"$P.sdiff.html\">Sdiffs</a>"
3074 
3075                 print "<a href=\"$P.frames.html\">Frames</a>"
3076         else
3077                 NODIFFS=1
3078                 print " ------ ------ ------"
3079 
3080                 if [[ -x $WDIFF ]]; then
3081                         print " ------"
3082                 fi
3083 
3084                 print " ------"
3085         fi
3086 
3087         # If there's an old file, make the link
3088 
3089         NOOLD=
3090         if [[ -f $F-.html ]]; then
3091                 print "<a href=\"$P-.html\">Old</a>"
3092         else
3093                 NOOLD=1
3094                 print " ---"
3095         fi
3096 
3097         # If there's an new file, make the link
3098 
3099         NONEW=
3100         if [[ -f $F.html ]]; then
3101                 print "<a href=\"$P.html\">New</a>"
3102         else
3103                 NONEW=1
3104                 print " ---"
3105         fi
3106 
3107         if [[ -f $F.patch ]]; then
3108                 print "<a href=\"$P.patch\">Patch</a>"
3109         else
3110                 print " -----"
3111         fi
3112 
3113         if [[ -f $WDIR/raw_files/new/$P ]]; then
3114                 print "<a href=\"raw_files/new/$P\">Raw</a>"
3115         else
3116                 print " ---"
3117         fi
3118         print "</code>"
3119         if [[ -n $NODIFFS && -z $oldname ]]; then
3120             if [[ -n $NOOLD ]]; then
3121                 print "<font color=green><b>$P</b></font>"
3122             elif [[ -n $NONEW ]]; then
3123                 print "<font color=red><b>$P</b></font>"
3124             fi
3125         else
3126             print "<b>$P</b> $oldname"
3127         fi
3128 
3129         #
3130         # Check for usr/closed
3131         #
3132         if [ ! -z "$Oflag" ]; then
3133                 if [[ $P == usr/closed/* ]]; then
3134                         print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3135                             "this review</i>"
3136                 fi
3137         fi
3138 
3139         print "</p><blockquote>\c"
3140         # Insert delta comments if any
3141         comments=`getcomments html $P $PP`
3142         if [ -n "$comments" ]; then
3143             print "<pre>$comments</pre>"
3144         fi
3145 
3146         # Add additional comments comment
3147 
3148         print "<!-- Add comments to explain changes in $P here -->"
3149 
3150         # Add count of changes.
3151 
3152         if [[ -f $F.count ]]; then
3153             cat $F.count
3154             rm $F.count
3155         fi
3156         print "</blockquote>"
3157 done < $FLIST
3158 
3159 print
3160 print
3161 print "<hr />"
3162 print "<p style=\"font-size: small\">"
3163 print "This code review page was prepared using <b>$0</b>"
3164 print "(vers $WEBREV_UPDATED).</p>"
3165 print "</body>"
3166 print "</html>"
3167 
3168 if [[ -n $ZIP ]]; then
3169     # Let's generate a zip file for convenience
3170     cd $WDIR/..
3171     if [ -f webrev.zip ]; then
3172         rm webrev.zip
3173     fi
3174     $ZIP -r webrev webrev >/dev/null 2>&1
3175 fi
3176 
3177 exec 1<&-                        # Close FD 1.
3178 exec 1<&3                        # dup FD 3 to restore stdout.
3179 exec 3<&-                        # close FD 3.
3180 
3181 print "Done."
3182 print "Output to: $WDIR"
3183