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