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). 650 # 651 sdiff_to_html() 652 { 653 diff -b $1 $2 > /tmp/$$.diffs 654 655 TNAME=$3 656 TPATH=$4 657 COMMENT=$5 658 659 # 660 # Now we have the diffs, generate the HTML for the old file. 661 # 662 $AWK ' 663 BEGIN { 664 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n" 665 printf "function removed() " 666 printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n" 667 printf "function changed() " 668 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n" 669 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n" 670 } 671 /^</ {next} 672 /^>/ {next} 673 /^---/ {next} 674 675 { 676 split($1, a, /[cad]/) ; 677 if (index($1, "a")) { 678 if (a[1] == 0) { 679 n = split(a[2], r, /,/); 680 if (n == 1) 681 printf "BEGIN\t\t{sp(1)}\n" 682 else 683 printf "BEGIN\t\t{sp(%d)}\n",\ 684 (r[2] - r[1]) + 1 685 next 686 } 687 688 printf "NR==%s\t\t{", a[1] 689 n = split(a[2], r, /,/); 690 s = r[1]; 691 if (n == 1) 692 printf "bl();printf \"\\n\"; next}\n" 693 else { 694 n = r[2] - r[1] 695 printf "bl();sp(%d);next}\n",\ 696 (r[2] - r[1]) + 1 697 } 698 next 699 } 700 if (index($1, "d")) { 701 n = split(a[1], r, /,/); 702 n1 = r[1] 703 n2 = r[2] 704 if (n == 1) 705 printf "NR==%s\t\t{removed(); next}\n" , n1 706 else 707 printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2 708 next 709 } 710 if (index($1, "c")) { 711 n = split(a[1], r, /,/); 712 n1 = r[1] 713 n2 = r[2] 714 final = n2 715 d1 = 0 716 if (n == 1) 717 printf "NR==%s\t\t{changed();" , n1 718 else { 719 d1 = n2 - n1 720 printf "NR==%s,NR==%s\t{changed();" , n1, n2 721 } 722 m = split(a[2], r, /,/); 723 n1 = r[1] 724 n2 = r[2] 725 if (m > 1) { 726 d2 = n2 - n1 727 if (d2 > d1) { 728 if (n > 1) printf "if (NR==%d)", final 729 printf "sp(%d);", d2 - d1 730 } 731 } 732 printf "next}\n" ; 733 734 next 735 } 736 } 737 738 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" } 739 ' /tmp/$$.diffs > /tmp/$$.file1 740 741 # 742 # Now generate the HTML for the new file 743 # 744 $AWK ' 745 BEGIN { 746 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n" 747 printf "function new() " 748 printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n" 749 printf "function changed() " 750 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n" 751 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n" 752 } 753 754 /^</ {next} 755 /^>/ {next} 756 /^---/ {next} 757 758 { 759 split($1, a, /[cad]/) ; 760 if (index($1, "d")) { 761 if (a[2] == 0) { 762 n = split(a[1], r, /,/); 763 if (n == 1) 764 printf "BEGIN\t\t{sp(1)}\n" 765 else 766 printf "BEGIN\t\t{sp(%d)}\n",\ 767 (r[2] - r[1]) + 1 768 next 769 } 770 771 printf "NR==%s\t\t{", a[2] 772 n = split(a[1], r, /,/); 773 s = r[1]; 774 if (n == 1) 775 printf "bl();printf \"\\n\"; next}\n" 776 else { 777 n = r[2] - r[1] 778 printf "bl();sp(%d);next}\n",\ 779 (r[2] - r[1]) + 1 780 } 781 next 782 } 783 if (index($1, "a")) { 784 n = split(a[2], r, /,/); 785 n1 = r[1] 786 n2 = r[2] 787 if (n == 1) 788 printf "NR==%s\t\t{new() ; next}\n" , n1 789 else 790 printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2 791 next 792 } 793 if (index($1, "c")) { 794 n = split(a[2], r, /,/); 795 n1 = r[1] 796 n2 = r[2] 797 final = n2 798 d2 = 0; 799 if (n == 1) { 800 final = n1 801 printf "NR==%s\t\t{changed();" , n1 802 } else { 803 d2 = n2 - n1 804 printf "NR==%s,NR==%s\t{changed();" , n1, n2 805 } 806 m = split(a[1], r, /,/); 807 n1 = r[1] 808 n2 = r[2] 809 if (m > 1) { 810 d1 = n2 - n1 811 if (d1 > d2) { 812 if (n > 1) printf "if (NR==%d)", final 813 printf "sp(%d);", d1 - d2 814 } 815 } 816 printf "next}\n" ; 817 next 818 } 819 } 820 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" } 821 ' /tmp/$$.diffs > /tmp/$$.file2 822 823 # 824 # Post-process the HTML files by running them back through $AWK 825 # 826 html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html 827 828 html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html 829 830 # 831 # Now combine into a valid HTML file and side-by-side into a table 832 # 833 print "$HTML<head>$STDHEAD" 834 print "<title>$WNAME Sdiff $TPATH/$TNAME</title>" 835 print "</head><body id=\"SUNWwebrev\">" 836 print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>" 837 print "<pre>$COMMENT</pre>\n" 838 print "<table><tr valign=\"top\">" 839 print "<td><pre>" 840 841 strip_unchanged /tmp/$$.file1.html 842 843 print "</pre></td><td><pre>" 844 845 strip_unchanged /tmp/$$.file2.html 846 847 print "</pre></td>" 848 print "</tr></table>" 849 print "</body></html>" 850 851 framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \ 852 "$COMMENT" 853 } 854 855 856 # 857 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment> 858 # 859 # Expects lefthand and righthand side html files created by sdiff_to_html. 860 # We use insert_anchors() to augment those with HTML navigation anchors, 861 # and then emit the main frame. Content is placed into: 862 # 863 # $WDIR/DIR/$TNAME.lhs.html 864 # $WDIR/DIR/$TNAME.rhs.html 865 # $WDIR/DIR/$TNAME.frames.html 866 # 867 # NOTE: We rely on standard usage of $WDIR and $DIR. 868 # 869 function framed_sdiff 870 { 871 typeset TNAME=$1 872 typeset TPATH=$2 873 typeset lhsfile=$3 874 typeset rhsfile=$4 875 typeset comments=$5 876 typeset RTOP 877 878 # Enable html files to access WDIR via a relative path. 879 RTOP=$(relative_dir $TPATH $WDIR) 880 881 # Make the rhs/lhs files and output the frameset file. 882 print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html 883 884 cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF 885 <script type="text/javascript" src="${RTOP}ancnav.js"></script> 886 </head> 887 <body id="SUNWwebrev" onkeypress="keypress(event);"> 888 <a name="0"></a> 889 <pre>$comments</pre><hr></hr> 890 EOF 891 892 cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html 893 894 insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html 895 insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html 896 897 close='</body></html>' 898 899 print $close >> $WDIR/$DIR/$TNAME.lhs.html 900 print $close >> $WDIR/$DIR/$TNAME.rhs.html 901 902 print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html 903 print "<title>$WNAME Framed-Sdiff " \ 904 "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html 905 cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF 906 <frameset rows="*,60"> 907 <frameset cols="50%,50%"> 908 <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame> 909 <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame> 910 </frameset> 911 <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0" 912 marginheight="0" name="nav"></frame> 913 <noframes> 914 <body id="SUNWwebrev"> 915 Alas 'frames' webrev requires that your browser supports frames 916 and has the feature enabled. 917 </body> 918 </noframes> 919 </frameset> 920 </html> 921 EOF 922 } 923 924 925 # 926 # fix_postscript 927 # 928 # Merge codereview output files to a single conforming postscript file, by: 929 # - removing all extraneous headers/trailers 930 # - making the page numbers right 931 # - removing pages devoid of contents which confuse some 932 # postscript readers. 933 # 934 # From Casper. 935 # 936 function fix_postscript 937 { 938 infile=$1 939 940 cat > /tmp/$$.crmerge.pl << \EOF 941 942 print scalar(<>); # %!PS-Adobe--- 943 print "%%Orientation: Landscape\n"; 944 945 $pno = 0; 946 $doprint = 1; 947 948 $page = ""; 949 950 while (<>) { 951 next if (/^%%Pages:\s*\d+/); 952 953 if (/^%%Page:/) { 954 if ($pno == 0 || $page =~ /\)S/) { 955 # Header or single page containing text 956 print "%%Page: ? $pno\n" if ($pno > 0); 957 print $page; 958 $pno++; 959 } else { 960 # Empty page, skip it. 961 } 962 $page = ""; 963 $doprint = 1; 964 next; 965 } 966 967 # Skip from %%Trailer of one document to Endprolog 968 # %%Page of the next 969 $doprint = 0 if (/^%%Trailer/); 970 $page .= $_ if ($doprint); 971 } 972 973 if ($page =~ /\)S/) { 974 print "%%Page: ? $pno\n"; 975 print $page; 976 } else { 977 $pno--; 978 } 979 print "%%Trailer\n%%Pages: $pno\n"; 980 EOF 981 982 $PERL /tmp/$$.crmerge.pl < $infile 983 } 984 985 986 # 987 # input_cmd | insert_anchors | output_cmd 988 # 989 # Flag blocks of difference with sequentially numbered invisible 990 # anchors. These are used to drive the frames version of the 991 # sdiffs output. 992 # 993 # NOTE: Anchor zero flags the top of the file irrespective of changes, 994 # an additional anchor is also appended to flag the bottom. 995 # 996 # The script detects changed lines as any line that has a "<span 997 # class=" string embedded (unchanged lines have no class set and are 998 # not part of a <span>. Blank lines (without a sequence number) 999 # are also detected since they flag lines that have been inserted or 1000 # deleted. 1001 # 1002 function insert_anchors 1003 { 1004 $AWK ' 1005 function ia() { 1006 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++; 1007 } 1008 1009 BEGIN { 1010 anc=1; 1011 inblock=1; 1012 printf "<pre>\n"; 1013 } 1014 NF == 0 || /^<span class=/ { 1015 if (inblock == 0) { 1016 ia(); 1017 inblock=1; 1018 } 1019 print; 1020 next; 1021 } 1022 { 1023 inblock=0; 1024 print; 1025 } 1026 END { 1027 ia(); 1028 1029 printf "<b style=\"font-size: large; color: red\">"; 1030 printf "--- EOF ---</b>" 1031 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n"; 1032 printf "</pre>" 1033 printf "<form name=\"eof\">"; 1034 printf "<input name=\"value\" value=\"%d\" " \ 1035 "type=\"hidden\"></input>", anc - 1; 1036 printf "</form>"; 1037 } 1038 ' $1 1039 } 1040 1041 1042 # 1043 # relative_dir 1044 # 1045 # Print a relative return path from $1 to $2. For example if 1046 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview, 1047 # this function would print "../../../../". 1048 # 1049 # In the event that $1 is not in $2 a warning is printed to stderr, 1050 # and $2 is returned-- the result of this is that the resulting webrev 1051 # is not relocatable. 1052 # 1053 function relative_dir 1054 { 1055 typeset cur="${1##$2?(/)}" 1056 1057 # 1058 # If the first path was specified absolutely, and it does 1059 # not start with the second path, it's an error. 1060 # 1061 if [[ "$cur" = "/${1#/}" ]]; then 1062 # Should never happen. 1063 print -u2 "\nWARNING: relative_dir: \"$1\" not relative " 1064 print -u2 "to \"$2\". Check input paths. Framed webrev " 1065 print -u2 "will not be relocatable!" 1066 print $2 1067 return 1068 fi 1069 1070 # 1071 # This is kind of ugly. The sed script will do the following: 1072 # 1073 # 1. Strip off a leading "." or "./": this is important to get 1074 # the correct arcnav links for files in $WDIR. 1075 # 2. Strip off a trailing "/": this is not strictly necessary, 1076 # but is kind of nice, since it doesn't end up in "//" at 1077 # the end of a relative path. 1078 # 3. Replace all remaining sequences of non-"/" with "..": the 1079 # assumption here is that each dirname represents another 1080 # level of relative separation. 1081 # 4. Append a trailing "/" only for non-empty paths: this way 1082 # the caller doesn't need to duplicate this logic, and does 1083 # not end up using $RTOP/file for files in $WDIR. 1084 # 1085 print $cur | $SED -e '{ 1086 s:^\./*:: 1087 s:/$:: 1088 s:[^/][^/]*:..:g 1089 s:^\(..*\)$:\1/: 1090 }' 1091 } 1092 1093 # 1094 # frame_nav_js 1095 # 1096 # Emit javascript for frame navigation 1097 # 1098 function frame_nav_js 1099 { 1100 cat << \EOF 1101 var myInt; 1102 var scrolling=0; 1103 var sfactor = 3; 1104 var scount=10; 1105 1106 function scrollByPix() { 1107 if (scount<=0) { 1108 sfactor*=1.2; 1109 scount=10; 1110 } 1111 parent.lhs.scrollBy(0,sfactor); 1112 parent.rhs.scrollBy(0,sfactor); 1113 scount--; 1114 } 1115 1116 function scrollToAnc(num) { 1117 1118 // Update the value of the anchor in the form which we use as 1119 // storage for this value. setAncValue() will take care of 1120 // correcting for overflow and underflow of the value and return 1121 // us the new value. 1122 num = setAncValue(num); 1123 1124 // Set location and scroll back a little to expose previous 1125 // lines. 1126 // 1127 // Note that this could be improved: it is possible although 1128 // complex to compute the x and y position of an anchor, and to 1129 // scroll to that location directly. 1130 // 1131 parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num); 1132 parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num); 1133 1134 parent.lhs.scrollBy(0,-30); 1135 parent.rhs.scrollBy(0,-30); 1136 } 1137 1138 function getAncValue() 1139 { 1140 return (parseInt(parent.nav.document.diff.real.value)); 1141 } 1142 1143 function setAncValue(val) 1144 { 1145 if (val <= 0) { 1146 val = 0; 1147 parent.nav.document.diff.real.value = val; 1148 parent.nav.document.diff.display.value = "BOF"; 1149 return (val); 1150 } 1151 1152 // 1153 // The way we compute the max anchor value is to stash it 1154 // inline in the left and right hand side pages-- it's the same 1155 // on each side, so we pluck from the left. 1156 // 1157 maxval = parent.lhs.document.eof.value.value; 1158 if (val < maxval) { 1159 parent.nav.document.diff.real.value = val; 1160 parent.nav.document.diff.display.value = val.toString(); 1161 return (val); 1162 } 1163 1164 // this must be: val >= maxval 1165 val = maxval; 1166 parent.nav.document.diff.real.value = val; 1167 parent.nav.document.diff.display.value = "EOF"; 1168 return (val); 1169 } 1170 1171 function stopScroll() { 1172 if (scrolling==1) { 1173 clearInterval(myInt); 1174 scrolling=0; 1175 } 1176 } 1177 1178 function startScroll() { 1179 stopScroll(); 1180 scrolling=1; 1181 myInt=setInterval("scrollByPix()",10); 1182 } 1183 1184 function handlePress(b) { 1185 1186 switch (b) { 1187 case 1 : 1188 scrollToAnc(-1); 1189 break; 1190 case 2 : 1191 scrollToAnc(getAncValue() - 1); 1192 break; 1193 case 3 : 1194 sfactor=-3; 1195 startScroll(); 1196 break; 1197 case 4 : 1198 sfactor=3; 1199 startScroll(); 1200 break; 1201 case 5 : 1202 scrollToAnc(getAncValue() + 1); 1203 break; 1204 case 6 : 1205 scrollToAnc(999999); 1206 break; 1207 } 1208 } 1209 1210 function handleRelease(b) { 1211 stopScroll(); 1212 } 1213 1214 function keypress(ev) { 1215 var keynum; 1216 var keychar; 1217 1218 if (window.event) { // IE 1219 keynum = ev.keyCode; 1220 } else if (ev.which) { // non-IE 1221 keynum = ev.which; 1222 } 1223 1224 keychar = String.fromCharCode(keynum); 1225 1226 if (keychar == "k") { 1227 handlePress(2); 1228 return (0); 1229 } else if (keychar == "j" || keychar == " ") { 1230 handlePress(5); 1231 return (0); 1232 } 1233 return (1); 1234 } 1235 1236 function ValidateDiffNum(){ 1237 val = parent.nav.document.diff.display.value; 1238 if (val == "EOF") { 1239 scrollToAnc(999999); 1240 return; 1241 } 1242 1243 if (val == "BOF") { 1244 scrollToAnc(0); 1245 return; 1246 } 1247 1248 i=parseInt(val); 1249 if (isNaN(i)) { 1250 parent.nav.document.diff.display.value = getAncValue(); 1251 } else { 1252 scrollToAnc(i); 1253 } 1254 return false; 1255 } 1256 1257 EOF 1258 } 1259 1260 # 1261 # frame_navigation 1262 # 1263 # Output anchor navigation file for framed sdiffs. 1264 # 1265 function frame_navigation 1266 { 1267 print "$HTML<head>$STDHEAD" 1268 1269 cat << \EOF 1270 <title>Anchor Navigation</title> 1271 <meta http-equiv="Content-Script-Type" content="text/javascript"> 1272 <meta http-equiv="Content-Type" content="text/html"> 1273 1274 <style type="text/css"> 1275 div.button td { padding-left: 5px; padding-right: 5px; 1276 background-color: #eee; text-align: center; 1277 border: 1px #444 outset; cursor: pointer; } 1278 div.button a { font-weight: bold; color: black } 1279 div.button td:hover { background: #ffcc99; } 1280 </style> 1281 EOF 1282 1283 print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>" 1284 1285 cat << \EOF 1286 </head> 1287 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();" 1288 onkeypress="keypress(event);"> 1289 <noscript lang="javascript"> 1290 <center> 1291 <p><big>Framed Navigation controls require Javascript</big><br></br> 1292 Either this browser is incompatable or javascript is not enabled</p> 1293 </center> 1294 </noscript> 1295 <table width="100%" border="0" align="center"> 1296 <tr> 1297 <td valign="middle" width="25%">Diff navigation: 1298 Use 'j' and 'k' for next and previous diffs; or use buttons 1299 at right</td> 1300 <td align="center" valign="top" width="50%"> 1301 <div class="button"> 1302 <table border="0" align="center"> 1303 <tr> 1304 <td> 1305 <a onMouseDown="handlePress(1);return true;" 1306 onMouseUp="handleRelease(1);return true;" 1307 onMouseOut="handleRelease(1);return true;" 1308 onClick="return false;" 1309 title="Go to Beginning Of file">BOF</a></td> 1310 <td> 1311 <a onMouseDown="handlePress(3);return true;" 1312 onMouseUp="handleRelease(3);return true;" 1313 onMouseOut="handleRelease(3);return true;" 1314 title="Scroll Up: Press and Hold to accelerate" 1315 onClick="return false;">Scroll Up</a></td> 1316 <td> 1317 <a onMouseDown="handlePress(2);return true;" 1318 onMouseUp="handleRelease(2);return true;" 1319 onMouseOut="handleRelease(2);return true;" 1320 title="Go to previous Diff" 1321 onClick="return false;">Prev Diff</a> 1322 </td></tr> 1323 1324 <tr> 1325 <td> 1326 <a onMouseDown="handlePress(6);return true;" 1327 onMouseUp="handleRelease(6);return true;" 1328 onMouseOut="handleRelease(6);return true;" 1329 onClick="return false;" 1330 title="Go to End Of File">EOF</a></td> 1331 <td> 1332 <a onMouseDown="handlePress(4);return true;" 1333 onMouseUp="handleRelease(4);return true;" 1334 onMouseOut="handleRelease(4);return true;" 1335 title="Scroll Down: Press and Hold to accelerate" 1336 onClick="return false;">Scroll Down</a></td> 1337 <td> 1338 <a onMouseDown="handlePress(5);return true;" 1339 onMouseUp="handleRelease(5);return true;" 1340 onMouseOut="handleRelease(5);return true;" 1341 title="Go to next Diff" 1342 onClick="return false;">Next Diff</a></td> 1343 </tr> 1344 </table> 1345 </div> 1346 </td> 1347 <th valign="middle" width="25%"> 1348 <form action="" name="diff" onsubmit="return ValidateDiffNum();"> 1349 <input name="display" value="BOF" size="8" type="text"></input> 1350 <input name="real" value="0" size="8" type="hidden"></input> 1351 </form> 1352 </th> 1353 </tr> 1354 </table> 1355 </body> 1356 </html> 1357 EOF 1358 } 1359 1360 1361 1362 # 1363 # diff_to_html <filename> <filepath> { U | C } <comment> 1364 # 1365 # Processes the output of diff to produce an HTML file representing either 1366 # context or unified diffs. 1367 # 1368 diff_to_html() 1369 { 1370 TNAME=$1 1371 TPATH=$2 1372 DIFFTYPE=$3 1373 COMMENT=$4 1374 1375 print "$HTML<head>$STDHEAD" 1376 print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>" 1377 1378 if [[ $DIFFTYPE == "U" ]]; then 1379 print "$UDIFFCSS" 1380 fi 1381 1382 cat <<-EOF 1383 </head> 1384 <body id="SUNWwebrev"> 1385 <a class="print" href="javascript:print()">Print this page</a> 1386 <pre>$COMMENT</pre> 1387 <pre> 1388 EOF 1389 1390 html_quote | $AWK ' 1391 /^--- new/ { next } 1392 /^\+\+\+ new/ { next } 1393 /^--- old/ { next } 1394 /^\*\*\* old/ { next } 1395 /^\*\*\*\*/ { next } 1396 /^-------/ { printf "<center><h1>%s</h1></center>\n", $0; next } 1397 /^\@\@.*\@\@$/ { printf "</pre><hr></hr><pre>\n"; 1398 printf "<span class=\"newmarker\">%s</span>\n", $0; 1399 next} 1400 1401 /^\*\*\*/ { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0; 1402 next} 1403 /^---/ { printf "<span class=\"newmarker\">%s</span>\n", $0; 1404 next} 1405 /^\+/ {printf "<span class=\"new\">%s</span>\n", $0; next} 1406 /^!/ {printf "<span class=\"changed\">%s</span>\n", $0; next} 1407 /^-/ {printf "<span class=\"removed\">%s</span>\n", $0; next} 1408 {printf "%s\n", $0; next} 1409 ' 1410 1411 print "</pre></body></html>\n" 1412 } 1413 1414 1415 # 1416 # source_to_html { new | old } <filename> 1417 # 1418 # Process a plain vanilla source file to transform it into an HTML file. 1419 # 1420 source_to_html() 1421 { 1422 WHICH=$1 1423 TNAME=$2 1424 1425 print "$HTML<head>$STDHEAD" 1426 print "<title>$WNAME $WHICH $TNAME</title>" 1427 print "<body id=\"SUNWwebrev\">" 1428 print "<pre>" 1429 html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }' 1430 print "</pre></body></html>" 1431 } 1432 1433 # 1434 # comments_from_wx {text|html} filepath 1435 # 1436 # Given the pathname of a file, find its location in a "wx" active 1437 # file list and print the following comment. Output is either text or 1438 # HTML; if the latter, embedded bugids (sequence of 5 or more digits) 1439 # are turned into URLs. 1440 # 1441 # This is also used with Mercurial and the file list provided by hg-active. 1442 # 1443 comments_from_wx() 1444 { 1445 typeset fmt=$1 1446 typeset p=$2 1447 1448 comm=`$AWK ' 1449 $1 == "'$p'" { 1450 do getline ; while (NF > 0) 1451 getline 1452 while (NF > 0) { print ; getline } 1453 exit 1454 }' < $wxfile` 1455 1456 if [[ -z $comm ]]; then 1457 comm="*** NO COMMENTS ***" 1458 fi 1459 1460 if [[ $fmt == "text" ]]; then 1461 print -- "$comm" 1462 return 1463 fi 1464 1465 print -- "$comm" | html_quote | its2url 1466 1467 } 1468 1469 # 1470 # getcomments {text|html} filepath parentpath 1471 # 1472 # Fetch the comments depending on what SCM mode we're in. 1473 # 1474 getcomments() 1475 { 1476 typeset fmt=$1 1477 typeset p=$2 1478 typeset pp=$3 1479 1480 if [[ -n $Nflag ]]; then 1481 return 1482 fi 1483 # 1484 # Mercurial support uses a file list in wx format, so this 1485 # will be used there, too 1486 # 1487 if [[ -n $wxfile ]]; then 1488 comments_from_wx $fmt $p 1489 fi 1490 } 1491 1492 # 1493 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged> 1494 # 1495 # Print out Code Inspection figures similar to sccs-prt(1) format. 1496 # 1497 function printCI 1498 { 1499 integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5 1500 typeset str 1501 if (( tot == 1 )); then 1502 str="line" 1503 else 1504 str="lines" 1505 fi 1506 printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \ 1507 $tot $str $ins $del $mod $unc 1508 } 1509 1510 1511 # 1512 # difflines <oldfile> <newfile> 1513 # 1514 # Calculate and emit number of added, removed, modified and unchanged lines, 1515 # and total lines changed, the sum of added + removed + modified. 1516 # 1517 function difflines 1518 { 1519 integer tot mod del ins unc err 1520 typeset filename 1521 1522 eval $( diff -e $1 $2 | $AWK ' 1523 # Change range of lines: N,Nc 1524 /^[0-9]*,[0-9]*c$/ { 1525 n=split(substr($1,1,length($1)-1), counts, ","); 1526 if (n != 2) { 1527 error=2 1528 exit; 1529 } 1530 # 1531 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines. 1532 # following would be 5 - 3 = 2! Hence +1 for correction. 1533 # 1534 r=(counts[2]-counts[1])+1; 1535 1536 # 1537 # Now count replacement lines: each represents a change instead 1538 # of a delete, so increment c and decrement r. 1539 # 1540 while (getline != /^\.$/) { 1541 c++; 1542 r--; 1543 } 1544 # 1545 # If there were more replacement lines than original lines, 1546 # then r will be negative; in this case there are no deletions, 1547 # but there are r changes that should be counted as adds, and 1548 # since r is negative, subtract it from a and add it to c. 1549 # 1550 if (r < 0) { 1551 a-=r; 1552 c+=r; 1553 } 1554 1555 # 1556 # If there were more original lines than replacement lines, then 1557 # r will be positive; in this case, increment d by that much. 1558 # 1559 if (r > 0) { 1560 d+=r; 1561 } 1562 next; 1563 } 1564 1565 # Change lines: Nc 1566 /^[0-9].*c$/ { 1567 # The first line is a replacement; any more are additions. 1568 if (getline != /^\.$/) { 1569 c++; 1570 while (getline != /^\.$/) a++; 1571 } 1572 next; 1573 } 1574 1575 # Add lines: both Na and N,Na 1576 /^[0-9].*a$/ { 1577 while (getline != /^\.$/) a++; 1578 next; 1579 } 1580 1581 # Delete range of lines: N,Nd 1582 /^[0-9]*,[0-9]*d$/ { 1583 n=split(substr($1,1,length($1)-1), counts, ","); 1584 if (n != 2) { 1585 error=2 1586 exit; 1587 } 1588 # 1589 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines. 1590 # following would be 5 - 3 = 2! Hence +1 for correction. 1591 # 1592 r=(counts[2]-counts[1])+1; 1593 d+=r; 1594 next; 1595 } 1596 1597 # Delete line: Nd. For example 10d says line 10 is deleted. 1598 /^[0-9]*d$/ {d++; next} 1599 1600 # Should not get here! 1601 { 1602 error=1; 1603 exit; 1604 } 1605 1606 # Finish off - print results 1607 END { 1608 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n", 1609 (c+d+a), c, d, a, error); 1610 }' ) 1611 1612 # End of $AWK, Check to see if any trouble occurred. 1613 if (( $? > 0 || err > 0 )); then 1614 print "Unexpected Error occurred reading" \ 1615 "\`diff -e $1 $2\`: \$?=$?, err=" $err 1616 return 1617 fi 1618 1619 # Accumulate totals 1620 (( TOTL += tot )) 1621 (( TMOD += mod )) 1622 (( TDEL += del )) 1623 (( TINS += ins )) 1624 # Calculate unchanged lines 1625 unc=`wc -l < $1` 1626 if (( unc > 0 )); then 1627 (( unc -= del + mod )) 1628 (( TUNC += unc )) 1629 fi 1630 # print summary 1631 print "<span class=\"lineschanged\">" 1632 printCI $tot $ins $del $mod $unc 1633 print "</span>" 1634 } 1635 1636 1637 # 1638 # flist_from_wx 1639 # 1640 # Sets up webrev to source its information from a wx-formatted file. 1641 # Sets the global 'wxfile' variable. 1642 # 1643 function flist_from_wx 1644 { 1645 typeset argfile=$1 1646 if [[ -n ${argfile%%/*} ]]; then 1647 # 1648 # If the wx file pathname is relative then make it absolute 1649 # because the webrev does a "cd" later on. 1650 # 1651 wxfile=$PWD/$argfile 1652 else 1653 wxfile=$argfile 1654 fi 1655 1656 $AWK '{ c = 1; print; 1657 while (getline) { 1658 if (NF == 0) { c = -c; continue } 1659 if (c > 0) print 1660 } 1661 }' $wxfile > $FLIST 1662 1663 print " Done." 1664 } 1665 1666 # 1667 # Call hg-active to get the active list output in the wx active list format 1668 # 1669 function hg_active_wxfile 1670 { 1671 typeset child=$1 1672 typeset parent=$2 1673 1674 TMPFLIST=/tmp/$$.active 1675 $HG_ACTIVE -w $child -p $parent -o $TMPFLIST 1676 wxfile=$TMPFLIST 1677 } 1678 1679 # 1680 # flist_from_mercurial 1681 # Call hg-active to get a wx-style active list, and hand it off to 1682 # flist_from_wx 1683 # 1684 function flist_from_mercurial 1685 { 1686 typeset child=$1 1687 typeset parent=$2 1688 1689 print " File list from: hg-active -p $parent ...\c" 1690 if [[ ! -x $HG_ACTIVE ]]; then 1691 print # Blank line for the \c above 1692 print -u2 "Error: hg-active tool not found. Exiting" 1693 exit 1 1694 fi 1695 hg_active_wxfile $child $parent 1696 1697 # flist_from_wx prints the Done, so we don't have to. 1698 flist_from_wx $TMPFLIST 1699 } 1700 1701 # 1702 # Transform a specified 'git log' output format into a wx-like active list. 1703 # 1704 function git_wxfile 1705 { 1706 typeset child="$1" 1707 typeset parent="$2" 1708 1709 TMPFLIST=/tmp/$$.active 1710 $PERL -e 'my (%files, %realfiles, $msg); 1711 my $branch = $ARGV[0]; 1712 1713 open(F, "git diff -M --name-status $branch |"); 1714 while (<F>) { 1715 chomp; 1716 if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename 1717 if ($1 >= 75) { # Probably worth treating as a rename 1718 $realfiles{$3} = $2; 1719 } else { 1720 $realfiles{$3} = $3; 1721 $realfiles{$2} = $2; 1722 } 1723 } else { 1724 my $f = (split /\s+/, $_)[1]; 1725 $realfiles{$f} = $f; 1726 } 1727 } 1728 close(F); 1729 1730 my $state = 1; # 0|comments, 1|files 1731 open(F, "git whatchanged --pretty=format:%B $branch.. |"); 1732 while (<F>) { 1733 chomp; 1734 if (/^:[0-9]{6}/) { 1735 my $fname = (split /\t/, $_)[1]; 1736 next if !defined($realfiles{$fname}); # No real change 1737 $state = 1; 1738 chomp $msg; 1739 $files{$fname} .= $msg; 1740 } else { 1741 if ($state == 1) { 1742 $state = 0; 1743 $msg = /^\n/ ? "" : "\n"; 1744 } 1745 $msg .= "$_\n" if ($_); 1746 } 1747 } 1748 close(F); 1749 1750 for (sort keys %files) { 1751 if ($realfiles{$_} ne $_) { 1752 print "$_ $realfiles{$_}\n$files{$_}\n\n"; 1753 } else { 1754 print "$_\n$files{$_}\n\n" 1755 } 1756 }' ${parent} > $TMPFLIST 1757 1758 wxfile=$TMPFLIST 1759 } 1760 1761 # 1762 # flist_from_git 1763 # Build a wx-style active list, and hand it off to flist_from_wx 1764 # 1765 function flist_from_git 1766 { 1767 typeset child=$1 1768 typeset parent=$2 1769 1770 print " File list from: git ...\c" 1771 git_wxfile "$child" "$parent"; 1772 1773 # flist_from_wx prints the Done, so we don't have to. 1774 flist_from_wx $TMPFLIST 1775 } 1776 1777 # 1778 # flist_from_subversion 1779 # 1780 # Generate the file list by extracting file names from svn status. 1781 # 1782 function flist_from_subversion 1783 { 1784 CWS=$1 1785 OLDPWD=$2 1786 1787 cd $CWS 1788 print -u2 " File list from: svn status ... \c" 1789 svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST 1790 print -u2 " Done." 1791 cd $OLDPWD 1792 } 1793 1794 function env_from_flist 1795 { 1796 [[ -r $FLIST ]] || return 1797 1798 # 1799 # Use "eval" to set env variables that are listed in the file 1800 # list. Then copy those into our local versions of those 1801 # variables if they have not been set already. 1802 # 1803 eval `$SED -e "s/#.*$//" $FLIST | $GREP = ` 1804 1805 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then 1806 codemgr_ws=$CODEMGR_WS 1807 export CODEMGR_WS 1808 fi 1809 1810 # 1811 # Check to see if CODEMGR_PARENT is set in the flist file. 1812 # 1813 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then 1814 codemgr_parent=$CODEMGR_PARENT 1815 export CODEMGR_PARENT 1816 fi 1817 } 1818 1819 function look_for_prog 1820 { 1821 typeset path 1822 typeset ppath 1823 typeset progname=$1 1824 1825 ppath=$PATH 1826 ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin 1827 ppath=$ppath:/opt/onbld/bin:/opt/onbld/bin/`uname -p` 1828 1829 PATH=$ppath prog=`whence $progname` 1830 if [[ -n $prog ]]; then 1831 print $prog 1832 fi 1833 } 1834 1835 function get_file_mode 1836 { 1837 $PERL -e ' 1838 if (@stat = stat($ARGV[0])) { 1839 $mode = $stat[2] & 0777; 1840 printf "%03o\n", $mode; 1841 exit 0; 1842 } else { 1843 exit 1; 1844 } 1845 ' $1 1846 } 1847 1848 function build_old_new_mercurial 1849 { 1850 typeset olddir="$1" 1851 typeset newdir="$2" 1852 typeset old_mode= 1853 typeset new_mode= 1854 typeset file 1855 1856 # 1857 # Get old file mode, from the parent revision manifest entry. 1858 # Mercurial only stores a "file is executable" flag, but the 1859 # manifest will display an octal mode "644" or "755". 1860 # 1861 if [[ "$PDIR" == "." ]]; then 1862 file="$PF" 1863 else 1864 file="$PDIR/$PF" 1865 fi 1866 file=`echo $file | $SED 's#/#\\\/#g'` 1867 # match the exact filename, and return only the permission digits 1868 old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \ 1869 < $HG_PARENT_MANIFEST` 1870 1871 # 1872 # Get new file mode, directly from the filesystem. 1873 # Normalize the mode to match Mercurial's behavior. 1874 # 1875 new_mode=`get_file_mode $CWS/$DIR/$F` 1876 if [[ -n "$new_mode" ]]; then 1877 if [[ "$new_mode" = *[1357]* ]]; then 1878 new_mode=755 1879 else 1880 new_mode=644 1881 fi 1882 fi 1883 1884 # 1885 # new version of the file. 1886 # 1887 rm -rf $newdir/$DIR/$F 1888 if [[ -e $CWS/$DIR/$F ]]; then 1889 cp $CWS/$DIR/$F $newdir/$DIR/$F 1890 if [[ -n $new_mode ]]; then 1891 chmod $new_mode $newdir/$DIR/$F 1892 else 1893 # should never happen 1894 print -u2 "ERROR: set mode of $newdir/$DIR/$F" 1895 fi 1896 fi 1897 1898 # 1899 # parent's version of the file 1900 # 1901 # Note that we get this from the last version common to both 1902 # ourselves and the parent. References are via $CWS since we have no 1903 # guarantee that the parent workspace is reachable via the filesystem. 1904 # 1905 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then 1906 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 1907 elif [[ -n $HG_PARENT ]]; then 1908 hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \ 1909 $olddir/$PDIR/$PF 2>/dev/null 1910 1911 if (( $? != 0 )); then 1912 rm -f $olddir/$PDIR/$PF 1913 else 1914 if [[ -n $old_mode ]]; then 1915 chmod $old_mode $olddir/$PDIR/$PF 1916 else 1917 # should never happen 1918 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF" 1919 fi 1920 fi 1921 fi 1922 } 1923 1924 function build_old_new_git 1925 { 1926 typeset olddir="$1" 1927 typeset newdir="$2" 1928 typeset o_mode= 1929 typeset n_mode= 1930 typeset o_object= 1931 typeset n_object= 1932 typeset OWD=$PWD 1933 typeset file 1934 typeset type 1935 1936 cd $CWS 1937 1938 # 1939 # Get old file and its mode from the git object tree 1940 # 1941 if [[ "$PDIR" == "." ]]; then 1942 file="$PF" 1943 else 1944 file="$PDIR/$PF" 1945 fi 1946 1947 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then 1948 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 1949 else 1950 $GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk 1951 $GIT cat-file $type $o_object > $olddir/$file 2>/dev/null 1952 1953 if (( $? != 0 )); then 1954 rm -f $olddir/$file 1955 elif [[ -n $o_mode ]]; then 1956 # Strip the first 3 digits, to get a regular octal mode 1957 o_mode=${o_mode/???/} 1958 chmod $o_mode $olddir/$file 1959 else 1960 # should never happen 1961 print -u2 "ERROR: set mode of $olddir/$file" 1962 fi 1963 fi 1964 1965 # 1966 # new version of the file. 1967 # 1968 if [[ "$DIR" == "." ]]; then 1969 file="$F" 1970 else 1971 file="$DIR/$F" 1972 fi 1973 rm -rf $newdir/$file 1974 1975 if [[ -e $CWS/$DIR/$F ]]; then 1976 cp $CWS/$DIR/$F $newdir/$DIR/$F 1977 chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F 1978 fi 1979 cd $OWD 1980 } 1981 1982 function build_old_new_subversion 1983 { 1984 typeset olddir="$1" 1985 typeset newdir="$2" 1986 1987 # Snag new version of file. 1988 rm -f $newdir/$DIR/$F 1989 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F 1990 1991 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then 1992 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 1993 else 1994 # Get the parent's version of the file. 1995 svn status $CWS/$DIR/$F | read stat file 1996 if [[ $stat != "A" ]]; then 1997 svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF 1998 fi 1999 fi 2000 } 2001 2002 function build_old_new_unknown 2003 { 2004 typeset olddir="$1" 2005 typeset newdir="$2" 2006 2007 # 2008 # Snag new version of file. 2009 # 2010 rm -f $newdir/$DIR/$F 2011 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F 2012 2013 # 2014 # Snag the parent's version of the file. 2015 # 2016 if [[ -f $PWS/$PDIR/$PF ]]; then 2017 rm -f $olddir/$PDIR/$PF 2018 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 2019 fi 2020 } 2021 2022 function build_old_new 2023 { 2024 typeset WDIR=$1 2025 typeset PWS=$2 2026 typeset PDIR=$3 2027 typeset PF=$4 2028 typeset CWS=$5 2029 typeset DIR=$6 2030 typeset F=$7 2031 2032 typeset olddir="$WDIR/raw_files/old" 2033 typeset newdir="$WDIR/raw_files/new" 2034 2035 mkdir -p $olddir/$PDIR 2036 mkdir -p $newdir/$DIR 2037 2038 if [[ $SCM_MODE == "mercurial" ]]; then 2039 build_old_new_mercurial "$olddir" "$newdir" 2040 elif [[ $SCM_MODE == "git" ]]; then 2041 build_old_new_git "$olddir" "$newdir" 2042 elif [[ $SCM_MODE == "subversion" ]]; then 2043 build_old_new_subversion "$olddir" "$newdir" 2044 elif [[ $SCM_MODE == "unknown" ]]; then 2045 build_old_new_unknown "$olddir" "$newdir" 2046 fi 2047 2048 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then 2049 print "*** Error: file not in parent or child" 2050 return 1 2051 fi 2052 return 0 2053 } 2054 2055 2056 # 2057 # Usage message. 2058 # 2059 function usage 2060 { 2061 print 'Usage:\twebrev [common-options] 2062 webrev [common-options] ( <file> | - ) 2063 webrev [common-options] -w <wx file> 2064 2065 Options: 2066 -C <filename>: Use <filename> for the information tracking configuration. 2067 -D: delete remote webrev 2068 -i <filename>: Include <filename> in the index.html file. 2069 -I <filename>: Use <filename> for the information tracking registry. 2070 -n: do not generate the webrev (useful with -U) 2071 -O: Print bugids/arc cases suitable for OpenSolaris. 2072 -o <outdir>: Output webrev to specified directory. 2073 -p <compare-against>: Use specified parent wkspc or basis for comparison 2074 -t <remote_target>: Specify remote destination for webrev upload 2075 -U: upload the webrev to remote destination 2076 -w <wxfile>: Use specified wx active file. 2077 2078 Environment: 2079 WDIR: Control the output directory. 2080 WEBREV_TRASH_DIR: Set directory for webrev delete. 2081 2082 SCM Environment: 2083 CODEMGR_WS: Workspace location. 2084 CODEMGR_PARENT: Parent workspace location. 2085 ' 2086 exit 2 2087 } 2088 2089 # 2090 # 2091 # Main program starts here 2092 # 2093 # 2094 2095 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15 2096 2097 set +o noclobber 2098 2099 PATH=$(/bin/dirname "$(whence $0)"):$PATH 2100 2101 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff` 2102 [[ -z $WX ]] && WX=`look_for_prog wx` 2103 [[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active` 2104 [[ -z $GIT ]] && GIT=`look_for_prog git` 2105 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm` 2106 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview` 2107 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf` 2108 [[ -z $PERL ]] && PERL=`look_for_prog perl` 2109 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync` 2110 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs` 2111 [[ -z $AWK ]] && AWK=`look_for_prog nawk` 2112 [[ -z $AWK ]] && AWK=`look_for_prog gawk` 2113 [[ -z $AWK ]] && AWK=`look_for_prog awk` 2114 [[ -z $SCP ]] && SCP=`look_for_prog scp` 2115 [[ -z $SED ]] && SED=`look_for_prog sed` 2116 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp` 2117 [[ -z $SORT ]] && SORT=`look_for_prog sort` 2118 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp` 2119 [[ -z $GREP ]] && GREP=`look_for_prog grep` 2120 [[ -z $FIND ]] && FIND=`look_for_prog find` 2121 2122 # set name of trash directory for remote webrev deletion 2123 TRASH_DIR=".trash" 2124 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR 2125 2126 if [[ ! -x $PERL ]]; then 2127 print -u2 "Error: No perl interpreter found. Exiting." 2128 exit 1 2129 fi 2130 2131 if [[ ! -x $WHICH_SCM ]]; then 2132 print -u2 "Error: Could not find which_scm. Exiting." 2133 exit 1 2134 fi 2135 2136 # 2137 # These aren't fatal, but we want to note them to the user. 2138 # We don't warn on the absence of 'wx' until later when we've 2139 # determined that we actually need to try to invoke it. 2140 # 2141 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found." 2142 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found." 2143 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found." 2144 2145 # Declare global total counters. 2146 integer TOTL TINS TDEL TMOD TUNC 2147 2148 # default remote host for upload/delete 2149 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org" 2150 # prefixes for upload targets 2151 typeset -r rsync_prefix="rsync://" 2152 typeset -r ssh_prefix="ssh://" 2153 2154 Cflag= 2155 Dflag= 2156 flist_mode= 2157 flist_file= 2158 iflag= 2159 Iflag= 2160 lflag= 2161 Nflag= 2162 nflag= 2163 Oflag= 2164 oflag= 2165 pflag= 2166 tflag= 2167 uflag= 2168 Uflag= 2169 wflag= 2170 remote_target= 2171 2172 # 2173 # NOTE: when adding/removing options it is necessary to sync the list 2174 # with usr/src/tools/onbld/hgext/cdm.py 2175 # 2176 while getopts "C:Di:I:lnNo:Op:t:Uw" opt 2177 do 2178 case $opt in 2179 C) Cflag=1 2180 ITSCONF=$OPTARG;; 2181 2182 D) Dflag=1;; 2183 2184 i) iflag=1 2185 INCLUDE_FILE=$OPTARG;; 2186 2187 I) Iflag=1 2188 ITSREG=$OPTARG;; 2189 2190 # 2191 # If -l has been specified, we need to abort further options 2192 # processing, because subsequent arguments are going to be 2193 # arguments to 'putback -n'. 2194 # 2195 l) lflag=1 2196 break;; 2197 2198 N) Nflag=1;; 2199 2200 n) nflag=1;; 2201 2202 O) Oflag=1;; 2203 2204 o) oflag=1 2205 # Strip the trailing slash to correctly form remote target. 2206 WDIR=${OPTARG%/};; 2207 2208 p) pflag=1 2209 codemgr_parent=$OPTARG;; 2210 2211 t) tflag=1 2212 remote_target=$OPTARG;; 2213 2214 U) Uflag=1;; 2215 2216 w) wflag=1;; 2217 2218 ?) usage;; 2219 esac 2220 done 2221 2222 FLIST=/tmp/$$.flist 2223 2224 if [[ -n $wflag && -n $lflag ]]; then 2225 usage 2226 fi 2227 2228 # more sanity checking 2229 if [[ -n $nflag && -z $Uflag ]]; then 2230 print "it does not make sense to skip webrev generation" \ 2231 "without -U" 2232 exit 1 2233 fi 2234 2235 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then 2236 echo "remote target has to be used only for upload or delete" 2237 exit 1 2238 fi 2239 2240 # 2241 # For the invocation "webrev -n -U" with no other options, webrev will assume 2242 # that the webrev exists in ${CWS}/webrev, but will upload it using the name 2243 # $(basename ${CWS}). So we need to get CWS set before we skip any remaining 2244 # logic. 2245 # 2246 $WHICH_SCM | read SCM_MODE junk || exit 1 2247 if [[ $SCM_MODE == "mercurial" ]]; then 2248 # 2249 # Mercurial priorities: 2250 # 1. hg root from CODEMGR_WS environment variable 2251 # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under 2252 # usr/closed when we run webrev 2253 # 2. hg root from directory of invocation 2254 # 2255 if [[ ${PWD} =~ "usr/closed" ]]; then 2256 testparent=${CODEMGR_WS}/usr/closed 2257 # If we're in OpenSolaris mode, we enforce a minor policy: 2258 # help to make sure the reviewer doesn't accidentally publish 2259 # source which is under usr/closed 2260 if [[ -n "$Oflag" ]]; then 2261 print -u2 "OpenSolaris output not permitted with" \ 2262 "usr/closed changes" 2263 exit 1 2264 fi 2265 else 2266 testparent=${CODEMGR_WS} 2267 fi 2268 [[ -z $codemgr_ws && -n $testparent ]] && \ 2269 codemgr_ws=$(hg root -R $testparent 2>/dev/null) 2270 [[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null) 2271 CWS=$codemgr_ws 2272 elif [[ $SCM_MODE == "git" ]]; then 2273 # 2274 # Git priorities: 2275 # 1. git rev-parse --git-dir from CODEMGR_WS environment variable 2276 # 2. git rev-parse --git-dir from directory of invocation 2277 # 2278 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \ 2279 codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \ 2280 2>/dev/null) 2281 [[ -z $codemgr_ws ]] && \ 2282 codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null) 2283 2284 if [[ "$codemgr_ws" == ".git" ]]; then 2285 codemgr_ws="${PWD}/${codemgr_ws}" 2286 fi 2287 2288 codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git' 2289 CWS="$codemgr_ws" 2290 elif [[ $SCM_MODE == "subversion" ]]; then 2291 # 2292 # Subversion priorities: 2293 # 1. CODEMGR_WS from environment 2294 # 2. Relative path from current directory to SVN repository root 2295 # 2296 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then 2297 CWS=$CODEMGR_WS 2298 else 2299 svn info | while read line; do 2300 if [[ $line == "URL: "* ]]; then 2301 url=${line#URL: } 2302 elif [[ $line == "Repository Root: "* ]]; then 2303 repo=${line#Repository Root: } 2304 fi 2305 done 2306 2307 rel=${url#$repo} 2308 CWS=${PWD%$rel} 2309 fi 2310 fi 2311 2312 # 2313 # If no SCM has been determined, take either the environment setting 2314 # setting for CODEMGR_WS, or the current directory if that wasn't set. 2315 # 2316 if [[ -z ${CWS} ]]; then 2317 CWS=${CODEMGR_WS:-.} 2318 fi 2319 2320 # 2321 # If the command line options indicate no webrev generation, either 2322 # explicitly (-n) or implicitly (-D but not -U), then there's a whole 2323 # ton of logic we can skip. 2324 # 2325 # Instead of increasing indentation, we intentionally leave this loop 2326 # body open here, and exit via break from multiple points within. 2327 # Search for DO_EVERYTHING below to find the break points and closure. 2328 # 2329 for do_everything in 1; do 2330 2331 # DO_EVERYTHING: break point 2332 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then 2333 break 2334 fi 2335 2336 # 2337 # If this manually set as the parent, and it appears to be an earlier webrev, 2338 # then note that fact and set the parent to the raw_files/new subdirectory. 2339 # 2340 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then 2341 parent_webrev=$(readlink -f "$codemgr_parent") 2342 codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new") 2343 fi 2344 2345 if [[ -z $wflag && -z $lflag ]]; then 2346 shift $(($OPTIND - 1)) 2347 2348 if [[ $1 == "-" ]]; then 2349 cat > $FLIST 2350 flist_mode="stdin" 2351 flist_done=1 2352 shift 2353 elif [[ -n $1 ]]; then 2354 if [[ ! -r $1 ]]; then 2355 print -u2 "$1: no such file or not readable" 2356 usage 2357 fi 2358 cat $1 > $FLIST 2359 flist_mode="file" 2360 flist_file=$1 2361 flist_done=1 2362 shift 2363 else 2364 flist_mode="auto" 2365 fi 2366 fi 2367 2368 # 2369 # Before we go on to further consider -l and -w, work out which SCM we think 2370 # is in use. 2371 # 2372 case "$SCM_MODE" in 2373 mercurial|git|subversion) 2374 ;; 2375 unknown) 2376 if [[ $flist_mode == "auto" ]]; then 2377 print -u2 "Unable to determine SCM in use and file list not specified" 2378 print -u2 "See which_scm(1) for SCM detection information." 2379 exit 1 2380 fi 2381 ;; 2382 *) 2383 if [[ $flist_mode == "auto" ]]; then 2384 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified" 2385 exit 1 2386 fi 2387 ;; 2388 esac 2389 2390 print -u2 " SCM detected: $SCM_MODE" 2391 2392 if [[ -n $wflag ]]; then 2393 # 2394 # If the -w is given then assume the file list is in Bonwick's "wx" 2395 # command format, i.e. pathname lines alternating with SCCS comment 2396 # lines with blank lines as separators. Use the SCCS comments later 2397 # in building the index.html file. 2398 # 2399 shift $(($OPTIND - 1)) 2400 wxfile=$1 2401 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then 2402 if [[ -r $CODEMGR_WS/wx/active ]]; then 2403 wxfile=$CODEMGR_WS/wx/active 2404 fi 2405 fi 2406 2407 [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \ 2408 "be auto-detected (check \$CODEMGR_WS)" && exit 1 2409 2410 if [[ ! -r $wxfile ]]; then 2411 print -u2 "$wxfile: no such file or not readable" 2412 usage 2413 fi 2414 2415 print -u2 " File list from: wx 'active' file '$wxfile' ... \c" 2416 flist_from_wx $wxfile 2417 flist_done=1 2418 if [[ -n "$*" ]]; then 2419 shift 2420 fi 2421 elif [[ $flist_mode == "stdin" ]]; then 2422 print -u2 " File list from: standard input" 2423 elif [[ $flist_mode == "file" ]]; then 2424 print -u2 " File list from: $flist_file" 2425 fi 2426 2427 if [[ $# -gt 0 ]]; then 2428 print -u2 "WARNING: unused arguments: $*" 2429 fi 2430 2431 # 2432 # Before we entered the DO_EVERYTHING loop, we should have already set CWS 2433 # and CODEMGR_WS as needed. Here, we set the parent workspace. 2434 # 2435 2436 if [[ $SCM_MODE == "mercurial" ]]; then 2437 # 2438 # Parent can either be specified with -p 2439 # Specified with CODEMGR_PARENT in the environment 2440 # or taken from hg's default path. 2441 # 2442 2443 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then 2444 codemgr_parent=$CODEMGR_PARENT 2445 fi 2446 2447 if [[ -z $codemgr_parent ]]; then 2448 codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null` 2449 fi 2450 2451 PWS=$codemgr_parent 2452 2453 # 2454 # If the parent is a webrev, we want to do some things against 2455 # the natural workspace parent (file list, comments, etc) 2456 # 2457 if [[ -n $parent_webrev ]]; then 2458 real_parent=$(hg path -R $codemgr_ws default 2>/dev/null) 2459 else 2460 real_parent=$PWS 2461 fi 2462 2463 # 2464 # If hg-active exists, then we run it. In the case of no explicit 2465 # flist given, we'll use it for our comments. In the case of an 2466 # explicit flist given we'll try to use it for comments for any 2467 # files mentioned in the flist. 2468 # 2469 if [[ -z $flist_done ]]; then 2470 flist_from_mercurial $CWS $real_parent 2471 flist_done=1 2472 fi 2473 2474 # 2475 # If we have a file list now, pull out any variables set 2476 # therein. We do this now (rather than when we possibly use 2477 # hg-active to find comments) to avoid stomping specifications 2478 # in the user-specified flist. 2479 # 2480 if [[ -n $flist_done ]]; then 2481 env_from_flist 2482 fi 2483 2484 # 2485 # Only call hg-active if we don't have a wx formatted file already 2486 # 2487 if [[ -x $HG_ACTIVE && -z $wxfile ]]; then 2488 print " Comments from: hg-active -p $real_parent ...\c" 2489 hg_active_wxfile $CWS $real_parent 2490 print " Done." 2491 fi 2492 2493 # 2494 # At this point we must have a wx flist either from hg-active, 2495 # or in general. Use it to try and find our parent revision, 2496 # if we don't have one. 2497 # 2498 if [[ -z $HG_PARENT ]]; then 2499 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=` 2500 fi 2501 2502 # 2503 # If we still don't have a parent, we must have been given a 2504 # wx-style active list with no HG_PARENT specification, run 2505 # hg-active and pull an HG_PARENT out of it, ignore the rest. 2506 # 2507 if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then 2508 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \ 2509 eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=` 2510 elif [[ -z $HG_PARENT ]]; then 2511 print -u2 "Error: Cannot discover parent revision" 2512 exit 1 2513 fi 2514 2515 pnode=$(trim_digest $HG_PARENT) 2516 PRETTY_PWS="${PWS} (at ${pnode})" 2517 cnode=$(hg parent -R $codemgr_ws --template '{node|short}' \ 2518 2>/dev/null) 2519 PRETTY_CWS="${CWS} (at ${cnode})"} 2520 elif [[ $SCM_MODE == "git" ]]; then 2521 # 2522 # Parent can either be specified with -p, or specified with 2523 # CODEMGR_PARENT in the environment. 2524 # 2525 2526 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then 2527 codemgr_parent=$CODEMGR_PARENT 2528 fi 2529 2530 # Try to figure out the parent based on the branch the current 2531 # branch is tracking, if we fail, use origin/master 2532 this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }') 2533 par_branch="origin/master" 2534 2535 # If we're not on a branch there's nothing we can do 2536 if [[ $this_branch != "(no branch)" ]]; then 2537 $GIT for-each-ref \ 2538 --format='%(refname:short) %(upstream:short)' refs/heads/ | \ 2539 while read local remote; do \ 2540 [[ "$local" == "$this_branch" ]] && par_branch="$remote"; \ 2541 done 2542 fi 2543 2544 if [[ -z $codemgr_parent ]]; then 2545 codemgr_parent=$par_branch 2546 fi 2547 PWS=$codemgr_parent 2548 2549 # 2550 # If the parent is a webrev, we want to do some things against 2551 # the natural workspace parent (file list, comments, etc) 2552 # 2553 if [[ -n $parent_webrev ]]; then 2554 real_parent=$par_branch 2555 else 2556 real_parent=$PWS 2557 fi 2558 2559 if [[ -z $flist_done ]]; then 2560 flist_from_git "$CWS" "$real_parent" 2561 flist_done=1 2562 fi 2563 2564 # 2565 # If we have a file list now, pull out any variables set 2566 # therein. 2567 # 2568 if [[ -n $flist_done ]]; then 2569 env_from_flist 2570 fi 2571 2572 # 2573 # If we don't have a wx-format file list, build one we can pull change 2574 # comments from. 2575 # 2576 if [[ -z $wxfile ]]; then 2577 print " Comments from: git...\c" 2578 git_wxfile "$CWS" "$real_parent" 2579 print " Done." 2580 fi 2581 2582 if [[ -z $GIT_PARENT ]]; then 2583 GIT_PARENT=$($GIT merge-base "$real_parent" HEAD) 2584 fi 2585 if [[ -z $GIT_PARENT ]]; then 2586 print -u2 "Error: Cannot discover parent revision" 2587 exit 1 2588 fi 2589 2590 pnode=$(trim_digest $GIT_PARENT) 2591 2592 if [[ $real_parent == */* ]]; then 2593 origin=$(echo $real_parent | cut -d/ -f1) 2594 origin=$($GIT remote -v | \ 2595 $AWK '$1 == "'$origin'" { print $2; exit }') 2596 PRETTY_PWS="${PWS} (${origin} at ${pnode})" 2597 else 2598 PRETTY_PWS="${PWS} (at ${pnode})" 2599 fi 2600 2601 cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 HEAD \ 2602 2>/dev/null) 2603 PRETTY_CWS="${CWS} (at ${cnode})" 2604 elif [[ $SCM_MODE == "subversion" ]]; then 2605 2606 # 2607 # We only will have a real parent workspace in the case one 2608 # was specified (be it an older webrev, or another checkout). 2609 # 2610 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent 2611 2612 if [[ -z $flist_done && $flist_mode == "auto" ]]; then 2613 flist_from_subversion $CWS $OLDPWD 2614 fi 2615 else 2616 if [[ $SCM_MODE == "unknown" ]]; then 2617 print -u2 " Unknown type of SCM in use" 2618 else 2619 print -u2 " Unsupported SCM in use: $SCM_MODE" 2620 fi 2621 2622 env_from_flist 2623 2624 if [[ -z $CODEMGR_WS ]]; then 2625 print -u2 "SCM not detected/supported and CODEMGR_WS not specified" 2626 exit 1 2627 fi 2628 2629 if [[ -z $CODEMGR_PARENT ]]; then 2630 print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified" 2631 exit 1 2632 fi 2633 2634 CWS=$CODEMGR_WS 2635 PWS=$CODEMGR_PARENT 2636 fi 2637 2638 # 2639 # If the user didn't specify a -i option, check to see if there is a 2640 # webrev-info file in the workspace directory. 2641 # 2642 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then 2643 iflag=1 2644 INCLUDE_FILE="$CWS/webrev-info" 2645 fi 2646 2647 if [[ -n $iflag ]]; then 2648 if [[ ! -r $INCLUDE_FILE ]]; then 2649 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \ 2650 "not readable." 2651 exit 1 2652 else 2653 # 2654 # $INCLUDE_FILE may be a relative path, and the script alters 2655 # PWD, so we just stash a copy in /tmp. 2656 # 2657 cp $INCLUDE_FILE /tmp/$$.include 2658 fi 2659 fi 2660 2661 # DO_EVERYTHING: break point 2662 if [[ -n $Nflag ]]; then 2663 break 2664 fi 2665 2666 typeset -A itsinfo 2667 typeset -r its_sed_script=/tmp/$$.its_sed 2668 valid_prefixes= 2669 if [[ -z $nflag ]]; then 2670 DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg" 2671 if [[ -n $Iflag ]]; then 2672 REGFILE=$ITSREG 2673 elif [[ -r $HOME/.its.reg ]]; then 2674 REGFILE=$HOME/.its.reg 2675 else 2676 REGFILE=$DEFREGFILE 2677 fi 2678 if [[ ! -r $REGFILE ]]; then 2679 print "ERROR: Unable to read database registry file $REGFILE" 2680 exit 1 2681 elif [[ $REGFILE != $DEFREGFILE ]]; then 2682 print " its.reg from: $REGFILE" 2683 fi 2684 2685 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do 2686 2687 name=${LINE%%=*} 2688 value="${LINE#*=}" 2689 2690 if [[ $name == PREFIX ]]; then 2691 p=${value} 2692 valid_prefixes="${p} ${valid_prefixes}" 2693 else 2694 itsinfo["${p}_${name}"]="${value}" 2695 fi 2696 done 2697 2698 2699 DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf" 2700 CONFFILES=$DEFCONFFILE 2701 if [[ -r $HOME/.its.conf ]]; then 2702 CONFFILES="${CONFFILES} $HOME/.its.conf" 2703 fi 2704 if [[ -n $Cflag ]]; then 2705 CONFFILES="${CONFFILES} ${ITSCONF}" 2706 fi 2707 its_domain= 2708 its_priority= 2709 for cf in ${CONFFILES}; do 2710 if [[ ! -r $cf ]]; then 2711 print "ERROR: Unable to read database configuration file $cf" 2712 exit 1 2713 elif [[ $cf != $DEFCONFFILE ]]; then 2714 print " its.conf: reading $cf" 2715 fi 2716 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do 2717 eval "${LINE}" 2718 done 2719 done 2720 2721 # 2722 # If an information tracking system is explicitly identified by prefix, 2723 # we want to disregard the specified priorities and resolve it accordingly. 2724 # 2725 # To that end, we'll build a sed script to do each valid prefix in turn. 2726 # 2727 for p in ${valid_prefixes}; do 2728 # 2729 # When an informational URL was provided, translate it to a 2730 # hyperlink. When omitted, simply use the prefix text. 2731 # 2732 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then 2733 itsinfo["${p}_INFO"]=${p} 2734 else 2735 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>" 2736 fi 2737 2738 # 2739 # Assume that, for this invocation of webrev, all references 2740 # to this information tracking system should resolve through 2741 # the same URL. 2742 # 2743 # If the caller specified -O, then always use EXTERNAL_URL. 2744 # 2745 # Otherwise, look in the list of domains for a matching 2746 # INTERNAL_URL. 2747 # 2748 [[ -z $Oflag ]] && for d in ${its_domain}; do 2749 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then 2750 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}" 2751 break 2752 fi 2753 done 2754 if [[ -z ${itsinfo["${p}_URL"]} ]]; then 2755 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}" 2756 fi 2757 2758 # 2759 # Turn the destination URL into a hyperlink 2760 # 2761 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>" 2762 2763 # The character class below contains a literal tab 2764 print "/^${p}[: ]/ { 2765 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g 2766 s;^${p};${itsinfo[${p}_INFO]}; 2767 }" >> ${its_sed_script} 2768 done 2769 2770 # 2771 # The previous loop took care of explicit specification. Now use 2772 # the configured priorities to attempt implicit translations. 2773 # 2774 for p in ${its_priority}; do 2775 print "/^${itsinfo[${p}_REGEX]}[ ]/ { 2776 s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g 2777 }" >> ${its_sed_script} 2778 done 2779 fi 2780 2781 # 2782 # Search for DO_EVERYTHING above for matching "for" statement 2783 # and explanation of this terminator. 2784 # 2785 done 2786 2787 # 2788 # Output directory. 2789 # 2790 WDIR=${WDIR:-$CWS/webrev} 2791 2792 # 2793 # Name of the webrev, derived from the workspace name or output directory; 2794 # in the future this could potentially be an option. 2795 # 2796 if [[ -n $oflag ]]; then 2797 WNAME=${WDIR##*/} 2798 else 2799 WNAME=${CWS##*/} 2800 fi 2801 2802 # Make sure remote target is well formed for remote upload/delete. 2803 if [[ -n $Dflag || -n $Uflag ]]; then 2804 # 2805 # If remote target is not specified, build it from scratch using 2806 # the default values. 2807 # 2808 if [[ -z $tflag ]]; then 2809 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME} 2810 else 2811 # 2812 # Check upload target prefix first. 2813 # 2814 if [[ "${remote_target}" != ${rsync_prefix}* && 2815 "${remote_target}" != ${ssh_prefix}* ]]; then 2816 print "ERROR: invalid prefix of upload URI" \ 2817 "($remote_target)" 2818 exit 1 2819 fi 2820 # 2821 # If destination specification is not in the form of 2822 # host_spec:remote_dir then assume it is just remote hostname 2823 # and append a colon and destination directory formed from 2824 # local webrev directory name. 2825 # 2826 typeset target_no_prefix=${remote_target##*://} 2827 if [[ ${target_no_prefix} == *:* ]]; then 2828 if [[ "${remote_target}" == *: ]]; then 2829 remote_target=${remote_target}${WNAME} 2830 fi 2831 else 2832 if [[ ${target_no_prefix} == */* ]]; then 2833 print "ERROR: badly formed upload URI" \ 2834 "($remote_target)" 2835 exit 1 2836 else 2837 remote_target=${remote_target}:${WNAME} 2838 fi 2839 fi 2840 fi 2841 2842 # 2843 # Strip trailing slash. Each upload method will deal with directory 2844 # specification separately. 2845 # 2846 remote_target=${remote_target%/} 2847 fi 2848 2849 # 2850 # Option -D by itself (option -U not present) implies no webrev generation. 2851 # 2852 if [[ -z $Uflag && -n $Dflag ]]; then 2853 delete_webrev 1 1 2854 exit $? 2855 fi 2856 2857 # 2858 # Do not generate the webrev, just upload it or delete it. 2859 # 2860 if [[ -n $nflag ]]; then 2861 if [[ -n $Dflag ]]; then 2862 delete_webrev 1 1 2863 (( $? == 0 )) || exit $? 2864 fi 2865 if [[ -n $Uflag ]]; then 2866 upload_webrev 2867 exit $? 2868 fi 2869 fi 2870 2871 if [ "${WDIR%%/*}" ]; then 2872 WDIR=$PWD/$WDIR 2873 fi 2874 2875 if [[ ! -d $WDIR ]]; then 2876 mkdir -p $WDIR 2877 (( $? != 0 )) && exit 1 2878 fi 2879 2880 # 2881 # Summarize what we're going to do. 2882 # 2883 print " Workspace: ${PRETTY_CWS:-$CWS}" 2884 if [[ -n $parent_webrev ]]; then 2885 print "Compare against: webrev at $parent_webrev" 2886 else 2887 print "Compare against: ${PRETTY_PWS:-$PWS}" 2888 fi 2889 2890 [[ -n $INCLUDE_FILE ]] && print " Including: $INCLUDE_FILE" 2891 print " Output to: $WDIR" 2892 2893 # 2894 # Save the file list in the webrev dir 2895 # 2896 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list 2897 2898 rm -f $WDIR/$WNAME.patch 2899 rm -f $WDIR/$WNAME.ps 2900 rm -f $WDIR/$WNAME.pdf 2901 2902 touch $WDIR/$WNAME.patch 2903 2904 print " Output Files:" 2905 2906 # 2907 # Clean up the file list: Remove comments, blank lines and env variables. 2908 # 2909 $SED -e "s/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean 2910 FLIST=/tmp/$$.flist.clean 2911 2912 # 2913 # For Mercurial, create a cache of manifest entries. 2914 # 2915 if [[ $SCM_MODE == "mercurial" ]]; then 2916 # 2917 # Transform the FLIST into a temporary sed script that matches 2918 # relevant entries in the Mercurial manifest as follows: 2919 # 1) The script will be used against the parent revision manifest, 2920 # so for FLIST lines that have two filenames (a renamed file) 2921 # keep only the old name. 2922 # 2) Escape all forward slashes the filename. 2923 # 3) Change the filename into another sed command that matches 2924 # that file in "hg manifest -v" output: start of line, three 2925 # octal digits for file permissions, space, a file type flag 2926 # character, space, the filename, end of line. 2927 # 4) Eliminate any duplicate entries. (This can occur if a 2928 # file has been used as the source of an hg cp and it's 2929 # also been modified in the same changeset.) 2930 # 2931 SEDFILE=/tmp/$$.manifest.sed 2932 $SED ' 2933 s#^[^ ]* ## 2934 s#/#\\\/#g 2935 s#^.*$#/^... . &$/p# 2936 ' < $FLIST | $SORT -u > $SEDFILE 2937 2938 # 2939 # Apply the generated script to the output of "hg manifest -v" 2940 # to get the relevant subset for this webrev. 2941 # 2942 HG_PARENT_MANIFEST=/tmp/$$.manifest 2943 hg -R $CWS manifest -v -r $HG_PARENT | 2944 $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST 2945 fi 2946 2947 # 2948 # First pass through the files: generate the per-file webrev HTML-files. 2949 # 2950 cat $FLIST | while read LINE 2951 do 2952 set - $LINE 2953 P=$1 2954 2955 # 2956 # Normally, each line in the file list is just a pathname of a 2957 # file that has been modified or created in the child. A file 2958 # that is renamed in the child workspace has two names on the 2959 # line: new name followed by the old name. 2960 # 2961 oldname="" 2962 oldpath="" 2963 rename= 2964 if [[ $# -eq 2 ]]; then 2965 PP=$2 # old filename 2966 if [[ -f $PP ]]; then 2967 oldname=" (copied from $PP)" 2968 else 2969 oldname=" (renamed from $PP)" 2970 fi 2971 oldpath="$PP" 2972 rename=1 2973 PDIR=${PP%/*} 2974 if [[ $PDIR == $PP ]]; then 2975 PDIR="." # File at root of workspace 2976 fi 2977 2978 PF=${PP##*/} 2979 2980 DIR=${P%/*} 2981 if [[ $DIR == $P ]]; then 2982 DIR="." # File at root of workspace 2983 fi 2984 2985 F=${P##*/} 2986 2987 else 2988 DIR=${P%/*} 2989 if [[ "$DIR" == "$P" ]]; then 2990 DIR="." # File at root of workspace 2991 fi 2992 2993 F=${P##*/} 2994 2995 PP=$P 2996 PDIR=$DIR 2997 PF=$F 2998 fi 2999 3000 COMM=`getcomments html $P $PP` 3001 3002 print "\t$P$oldname\n\t\t\c" 3003 3004 # Make the webrev mirror directory if necessary 3005 mkdir -p $WDIR/$DIR 3006 3007 # 3008 # We stash old and new files into parallel directories in $WDIR 3009 # and do our diffs there. This makes it possible to generate 3010 # clean looking diffs which don't have absolute paths present. 3011 # 3012 3013 build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \ 3014 continue 3015 3016 # 3017 # Keep the old PWD around, so we can safely switch back after 3018 # diff generation, such that build_old_new runs in a 3019 # consistent environment. 3020 # 3021 OWD=$PWD 3022 cd $WDIR/raw_files 3023 ofile=old/$PDIR/$PF 3024 nfile=new/$DIR/$F 3025 3026 mv_but_nodiff= 3027 cmp $ofile $nfile > /dev/null 2>&1 3028 if [[ $? == 0 && $rename == 1 ]]; then 3029 mv_but_nodiff=1 3030 fi 3031 3032 # 3033 # If we have old and new versions of the file then run the appropriate 3034 # diffs. This is complicated by a couple of factors: 3035 # 3036 # - renames must be handled specially: we emit a 'remove' 3037 # diff and an 'add' diff 3038 # - new files and deleted files must be handled specially 3039 # - Solaris patch(1m) can't cope with file creation 3040 # (and hence renames) as of this writing. 3041 # - To make matters worse, gnu patch doesn't interpret the 3042 # output of Solaris diff properly when it comes to 3043 # adds and deletes. We need to do some "cleansing" 3044 # transformations: 3045 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@ 3046 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@ 3047 # 3048 cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'" 3049 cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'" 3050 3051 rm -f $WDIR/$DIR/$F.patch 3052 if [[ -z $rename ]]; then 3053 if [ ! -f "$ofile" ]; then 3054 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \ 3055 > $WDIR/$DIR/$F.patch 3056 elif [ ! -f "$nfile" ]; then 3057 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \ 3058 > $WDIR/$DIR/$F.patch 3059 else 3060 diff -u $ofile $nfile > $WDIR/$DIR/$F.patch 3061 fi 3062 else 3063 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \ 3064 > $WDIR/$DIR/$F.patch 3065 3066 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \ 3067 >> $WDIR/$DIR/$F.patch 3068 fi 3069 3070 # 3071 # Tack the patch we just made onto the accumulated patch for the 3072 # whole wad. 3073 # 3074 cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch 3075 3076 print " patch\c" 3077 3078 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then 3079 3080 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff 3081 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \ 3082 > $WDIR/$DIR/$F.cdiff.html 3083 print " cdiffs\c" 3084 3085 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff 3086 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \ 3087 > $WDIR/$DIR/$F.udiff.html 3088 3089 print " udiffs\c" 3090 3091 if [[ -x $WDIFF ]]; then 3092 $WDIFF -c "$COMM" \ 3093 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \ 3094 $WDIR/$DIR/$F.wdiff.html 2>/dev/null 3095 if [[ $? -eq 0 ]]; then 3096 print " wdiffs\c" 3097 else 3098 print " wdiffs[fail]\c" 3099 fi 3100 fi 3101 3102 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \ 3103 > $WDIR/$DIR/$F.sdiff.html 3104 print " sdiffs\c" 3105 3106 print " frames\c" 3107 3108 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff 3109 3110 difflines $ofile $nfile > $WDIR/$DIR/$F.count 3111 3112 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then 3113 # renamed file: may also have differences 3114 difflines $ofile $nfile > $WDIR/$DIR/$F.count 3115 elif [[ -f $nfile ]]; then 3116 # new file: count added lines 3117 difflines /dev/null $nfile > $WDIR/$DIR/$F.count 3118 elif [[ -f $ofile ]]; then 3119 # old file: count deleted lines 3120 difflines $ofile /dev/null > $WDIR/$DIR/$F.count 3121 fi 3122 3123 # 3124 # Now we generate the postscript for this file. We generate diffs 3125 # only in the event that there is delta, or the file is new (it seems 3126 # tree-killing to print out the contents of deleted files). 3127 # 3128 if [[ -f $nfile ]]; then 3129 ocr=$ofile 3130 [[ ! -f $ofile ]] && ocr=/dev/null 3131 3132 if [[ -z $mv_but_nodiff ]]; then 3133 textcomm=`getcomments text $P $PP` 3134 if [[ -x $CODEREVIEW ]]; then 3135 $CODEREVIEW -y "$textcomm" \ 3136 -e $ocr $nfile \ 3137 > /tmp/$$.psfile 2>/dev/null && 3138 cat /tmp/$$.psfile >> $WDIR/$WNAME.ps 3139 if [[ $? -eq 0 ]]; then 3140 print " ps\c" 3141 else 3142 print " ps[fail]\c" 3143 fi 3144 fi 3145 fi 3146 fi 3147 3148 if [[ -f $ofile ]]; then 3149 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html 3150 print " old\c" 3151 fi 3152 3153 if [[ -f $nfile ]]; then 3154 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html 3155 print " new\c" 3156 fi 3157 3158 cd $OWD 3159 3160 print 3161 done 3162 3163 frame_nav_js > $WDIR/ancnav.js 3164 frame_navigation > $WDIR/ancnav.html 3165 3166 if [[ ! -f $WDIR/$WNAME.ps ]]; then 3167 print " Generating PDF: Skipped: no output available" 3168 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then 3169 print " Generating PDF: \c" 3170 fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf 3171 print "Done." 3172 else 3173 print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'" 3174 fi 3175 3176 # If we're in OpenSolaris mode and there's a closed dir under $WDIR, 3177 # delete it - prevent accidental publishing of closed source 3178 3179 if [[ -n "$Oflag" ]]; then 3180 $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \; 3181 fi 3182 3183 # Now build the index.html file that contains 3184 # links to the source files and their diffs. 3185 3186 cd $CWS 3187 3188 # Save total changed lines for Code Inspection. 3189 print "$TOTL" > $WDIR/TotalChangedLines 3190 3191 print " index.html: \c" 3192 INDEXFILE=$WDIR/index.html 3193 exec 3<&1 # duplicate stdout to FD3. 3194 exec 1<&- # Close stdout. 3195 exec > $INDEXFILE # Open stdout to index file. 3196 3197 print "$HTML<head>$STDHEAD" 3198 print "<title>$WNAME</title>" 3199 print "</head>" 3200 print "<body id=\"SUNWwebrev\">" 3201 print "<div class=\"summary\">" 3202 print "<h2>Code Review for $WNAME</h2>" 3203 3204 print "<table>" 3205 3206 # 3207 # Get the preparer's name: 3208 # 3209 # If the SCM detected is Mercurial, and the configuration property 3210 # ui.username is available, use that, but be careful to properly escape 3211 # angle brackets (HTML syntax characters) in the email address. 3212 # 3213 # Otherwise, use the current userid in the form "John Doe (jdoe)", but 3214 # to maintain compatibility with passwd(4), we must support '&' substitutions. 3215 # 3216 preparer= 3217 if [[ "$SCM_MODE" == mercurial ]]; then 3218 preparer=`hg showconfig ui.username 2>/dev/null` 3219 if [[ -n "$preparer" ]]; then 3220 preparer="$(echo "$preparer" | html_quote)" 3221 fi 3222 fi 3223 if [[ -z "$preparer" ]]; then 3224 preparer=$( 3225 $PERL -e ' 3226 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<); 3227 if ($login) { 3228 $gcos =~ s/\&/ucfirst($login)/e; 3229 printf "%s (%s)\n", $gcos, $login; 3230 } else { 3231 printf "(unknown)\n"; 3232 } 3233 ') 3234 fi 3235 3236 PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z) 3237 print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>" 3238 print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}" 3239 print "</td></tr>" 3240 print "<tr><th>Compare against:</th><td>" 3241 if [[ -n $parent_webrev ]]; then 3242 print "webrev at $parent_webrev" 3243 else 3244 print "${PRETTY_PWS:-$PWS}" 3245 fi 3246 print "</td></tr>" 3247 print "<tr><th>Summary of changes:</th><td>" 3248 printCI $TOTL $TINS $TDEL $TMOD $TUNC 3249 print "</td></tr>" 3250 3251 if [[ -f $WDIR/$WNAME.patch ]]; then 3252 wpatch_url="$(print $WNAME.patch | url_encode)" 3253 print "<tr><th>Patch of changes:</th><td>" 3254 print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>" 3255 fi 3256 if [[ -f $WDIR/$WNAME.pdf ]]; then 3257 wpdf_url="$(print $WNAME.pdf | url_encode)" 3258 print "<tr><th>Printable review:</th><td>" 3259 print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>" 3260 fi 3261 3262 if [[ -n "$iflag" ]]; then 3263 print "<tr><th>Author comments:</th><td><div>" 3264 cat /tmp/$$.include 3265 print "</div></td></tr>" 3266 fi 3267 print "</table>" 3268 print "</div>" 3269 3270 # 3271 # Second pass through the files: generate the rest of the index file 3272 # 3273 cat $FLIST | while read LINE 3274 do 3275 set - $LINE 3276 P=$1 3277 3278 if [[ $# == 2 ]]; then 3279 PP=$2 3280 oldname="$PP" 3281 else 3282 PP=$P 3283 oldname="" 3284 fi 3285 3286 mv_but_nodiff= 3287 cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1 3288 if [[ $? == 0 && -n "$oldname" ]]; then 3289 mv_but_nodiff=1 3290 fi 3291 3292 DIR=${P%/*} 3293 if [[ $DIR == $P ]]; then 3294 DIR="." # File at root of workspace 3295 fi 3296 3297 # Avoid processing the same file twice. 3298 # It's possible for renamed files to 3299 # appear twice in the file list 3300 3301 F=$WDIR/$P 3302 3303 print "<p>" 3304 3305 # If there's a diffs file, make diffs links 3306 3307 if [[ -f $F.cdiff.html ]]; then 3308 cdiff_url="$(print $P.cdiff.html | url_encode)" 3309 udiff_url="$(print $P.udiff.html | url_encode)" 3310 print "<a href=\"$cdiff_url\">Cdiffs</a>" 3311 print "<a href=\"$udiff_url\">Udiffs</a>" 3312 3313 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then 3314 wdiff_url="$(print $P.wdiff.html | url_encode)" 3315 print "<a href=\"$wdiff_url\">Wdiffs</a>" 3316 fi 3317 3318 sdiff_url="$(print $P.sdiff.html | url_encode)" 3319 print "<a href=\"$sdiff_url\">Sdiffs</a>" 3320 3321 frames_url="$(print $P.frames.html | url_encode)" 3322 print "<a href=\"$frames_url\">Frames</a>" 3323 else 3324 print " ------ ------ ------" 3325 3326 if [[ -x $WDIFF ]]; then 3327 print " ------" 3328 fi 3329 3330 print " ------" 3331 fi 3332 3333 # If there's an old file, make the link 3334 3335 if [[ -f $F-.html ]]; then 3336 oldfile_url="$(print $P-.html | url_encode)" 3337 print "<a href=\"$oldfile_url\">Old</a>" 3338 else 3339 print " ---" 3340 fi 3341 3342 # If there's an new file, make the link 3343 3344 if [[ -f $F.html ]]; then 3345 newfile_url="$(print $P.html | url_encode)" 3346 print "<a href=\"$newfile_url\">New</a>" 3347 else 3348 print " ---" 3349 fi 3350 3351 if [[ -f $F.patch ]]; then 3352 patch_url="$(print $P.patch | url_encode)" 3353 print "<a href=\"$patch_url\">Patch</a>" 3354 else 3355 print " -----" 3356 fi 3357 3358 if [[ -f $WDIR/raw_files/new/$P ]]; then 3359 rawfiles_url="$(print raw_files/new/$P | url_encode)" 3360 print "<a href=\"$rawfiles_url\">Raw</a>" 3361 else 3362 print " ---" 3363 fi 3364 3365 print "<b>$P</b>" 3366 3367 # For renamed files, clearly state whether or not they are modified 3368 if [[ -f "$oldname" ]]; then 3369 if [[ -n "$mv_but_nodiff" ]]; then 3370 print "<i>(copied from $oldname)</i>" 3371 else 3372 print "<i>(copied and modified from $oldname)</i>" 3373 fi 3374 elif [[ -n "$oldname" ]]; then 3375 if [[ -n "$mv_but_nodiff" ]]; then 3376 print "<i>(renamed from $oldname)</i>" 3377 else 3378 print "<i>(renamed and modified from $oldname)</i>" 3379 fi 3380 fi 3381 3382 # If there's an old file, but no new file, the file was deleted 3383 if [[ -f $F-.html && ! -f $F.html ]]; then 3384 print " <i>(deleted)</i>" 3385 fi 3386 3387 # 3388 # Check for usr/closed and deleted_files/usr/closed 3389 # 3390 if [ ! -z "$Oflag" ]; then 3391 if [[ $P == usr/closed/* || \ 3392 $P == deleted_files/usr/closed/* ]]; then 3393 print " <i>Closed source: omitted from" \ 3394 "this review</i>" 3395 fi 3396 fi 3397 3398 print "</p>" 3399 # Insert delta comments 3400 3401 print "<blockquote><pre>" 3402 getcomments html $P $PP 3403 print "</pre>" 3404 3405 # Add additional comments comment 3406 3407 print "<!-- Add comments to explain changes in $P here -->" 3408 3409 # Add count of changes. 3410 3411 if [[ -f $F.count ]]; then 3412 cat $F.count 3413 rm $F.count 3414 fi 3415 3416 if [[ $SCM_MODE == "mercurial" || 3417 $SCM_MODE == "unknown" ]]; then 3418 3419 # Include warnings for important file mode situations: 3420 # 1) New executable files 3421 # 2) Permission changes of any kind 3422 # 3) Existing executable files 3423 3424 old_mode= 3425 if [[ -f $WDIR/raw_files/old/$PP ]]; then 3426 old_mode=`get_file_mode $WDIR/raw_files/old/$PP` 3427 fi 3428 3429 new_mode= 3430 if [[ -f $WDIR/raw_files/new/$P ]]; then 3431 new_mode=`get_file_mode $WDIR/raw_files/new/$P` 3432 fi 3433 3434 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then 3435 print "<span class=\"chmod\">" 3436 print "<p>new executable file: mode $new_mode</p>" 3437 print "</span>" 3438 elif [[ -n "$old_mode" && -n "$new_mode" && 3439 "$old_mode" != "$new_mode" ]]; then 3440 print "<span class=\"chmod\">" 3441 print "<p>mode change: $old_mode to $new_mode</p>" 3442 print "</span>" 3443 elif [[ "$new_mode" = *[1357]* ]]; then 3444 print "<span class=\"chmod\">" 3445 print "<p>executable file: mode $new_mode</p>" 3446 print "</span>" 3447 fi 3448 fi 3449 3450 print "</blockquote>" 3451 done 3452 3453 print 3454 print 3455 print "<hr></hr>" 3456 print "<p style=\"font-size: small\">" 3457 print "This code review page was prepared using <b>$0</b>." 3458 print "Webrev is maintained by the <a href=\"http://www.illumos.org\">" 3459 print "illumos</a> project. The latest version may be obtained" 3460 print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>" 3461 print "</body>" 3462 print "</html>" 3463 3464 exec 1<&- # Close FD 1. 3465 exec 1<&3 # dup FD 3 to restore stdout. 3466 exec 3<&- # close FD 3. 3467 3468 print "Done." 3469 3470 # 3471 # If remote deletion was specified and fails do not continue. 3472 # 3473 if [[ -n $Dflag ]]; then 3474 delete_webrev 1 1 3475 (( $? == 0 )) || exit $? 3476 fi 3477 3478 if [[ -n $Uflag ]]; then 3479 upload_webrev 3480 exit $? 3481 fi