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