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