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