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