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