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