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