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