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