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