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