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