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