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