1#!/usr/bin/ksh93 -p 2# 3# CDDL HEADER START 4# 5# The contents of this file are subject to the terms of the 6# Common Development and Distribution License (the "License"). 7# You may not use this file except in compliance with the License. 8# 9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10# or http://www.opensolaris.org/os/licensing. 11# See the License for the specific language governing permissions 12# and limitations under the License. 13# 14# When distributing Covered Code, include this CDDL HEADER in each 15# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16# If applicable, add the following below this CDDL HEADER, with the 17# fields enclosed by brackets "[]" replaced with your own identifying 18# information: Portions Copyright [yyyy] [name of copyright owner] 19# 20# CDDL HEADER END 21# 22 23# 24# Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. 25# 26 27# 28# This script takes a file list and a workspace and builds a set of html files 29# suitable for doing a code review of source changes via a web page. 30# Documentation is available via the manual page, webrev.1, or just 31# type 'webrev -h'. 32# 33# Acknowledgements to contributors to webrev are listed in the webrev(1) 34# man page. 35# 36 37REMOVED_COLOR=brown 38CHANGED_COLOR=blue 39NEW_COLOR=blue 40 41HTML='<?xml version="1.0"?> 42<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 43 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 44<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n' 45 46FRAMEHTML='<?xml version="1.0"?> 47<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" 48 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"> 49<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n' 50 51STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta> 52<meta http-equiv="Pragma" content="no-cache"></meta> 53<meta http-equiv="Expires" content="-1"></meta> 54<!-- 55 Note to customizers: the body of the webrev is IDed as SUNWwebrev 56 to allow easy overriding by users of webrev via the userContent.css 57 mechanism available in some browsers. 58 59 For example, to have all "removed" information be red instead of 60 brown, set a rule in your userContent.css file like: 61 62 body#SUNWwebrev span.removed { color: red ! important; } 63--> 64<style type="text/css" media="screen"> 65body { 66 background-color: #eeeeee; 67} 68hr { 69 border: none 0; 70 border-top: 1px solid #aaa; 71 height: 1px; 72} 73div.summary { 74 font-size: .8em; 75 border-bottom: 1px solid #aaa; 76 padding-left: 1em; 77 padding-right: 1em; 78} 79div.summary h2 { 80 margin-bottom: 0.3em; 81} 82div.summary table th { 83 text-align: right; 84 vertical-align: top; 85 white-space: nowrap; 86} 87span.lineschanged { 88 font-size: 0.7em; 89} 90span.oldmarker { 91 color: red; 92 font-size: large; 93 font-weight: bold; 94} 95span.newmarker { 96 color: green; 97 font-size: large; 98 font-weight: bold; 99} 100span.removed { 101 color: brown; 102} 103span.changed { 104 color: blue; 105} 106span.new { 107 color: blue; 108 font-weight: bold; 109} 110span.chmod { 111 font-size: 0.7em; 112 color: #db7800; 113} 114a.print { font-size: x-small; } 115a:hover { background-color: #ffcc99; } 116</style> 117 118<style type="text/css" media="print"> 119pre { font-size: 0.8em; font-family: courier, monospace; } 120span.removed { color: #444; font-style: italic } 121span.changed { font-weight: bold; } 122span.new { font-weight: bold; } 123span.newmarker { font-size: 1.2em; font-weight: bold; } 124span.oldmarker { font-size: 1.2em; font-weight: bold; } 125a.print {display: none} 126hr { border: none 0; border-top: 1px solid #aaa; height: 1px; } 127</style> 128' 129 130# 131# UDiffs need a slightly different CSS rule for 'new' items (we don't 132# want them to be bolded as we do in cdiffs or sdiffs). 133# 134UDIFFCSS=' 135<style type="text/css" media="screen"> 136span.new { 137 color: blue; 138 font-weight: normal; 139} 140</style> 141' 142 143# 144# Display remote target with prefix and trailing slash. 145# 146function print_upload_header 147{ 148 typeset -r prefix=$1 149 typeset display_target 150 151 if [[ -z $tflag ]]; then 152 display_target=${prefix}${remote_target} 153 else 154 display_target=${remote_target} 155 fi 156 157 if [[ ${display_target} != */ ]]; then 158 display_target=${display_target}/ 159 fi 160 161 print " Upload to: ${display_target}\n" \ 162 " Uploading: \c" 163} 164 165# 166# Upload the webrev via rsync. Return 0 on success, 1 on error. 167# 168function rsync_upload 169{ 170 if (( $# != 2 )); then 171 print "\nERROR: rsync_upload: wrong usage ($#)" 172 exit 1 173 fi 174 175 typeset -r dst=$1 176 integer -r print_err_msg=$2 177 178 print_upload_header ${rsync_prefix} 179 print "rsync ... \c" 180 typeset -r err_msg=$( $MKTEMP /tmp/rsync_err.XXXXXX ) 181 if [[ -z $err_msg ]]; then 182 print "\nERROR: rsync_upload: cannot create temporary file" 183 return 1 184 fi 185 # 186 # The source directory must end with a slash in order to copy just 187 # directory contents, not the whole directory. 188 # 189 typeset src_dir=$WDIR 190 if [[ ${src_dir} != */ ]]; then 191 src_dir=${src_dir}/ 192 fi 193 $RSYNC -r -q ${src_dir} $dst 2>$err_msg 194 if (( $? != 0 )); then 195 if (( ${print_err_msg} > 0 )); then 196 print "Failed.\nERROR: rsync failed" 197 print "src dir: '${src_dir}'\ndst dir: '$dst'" 198 print "error messages:" 199 $SED 's/^/> /' $err_msg 200 rm -f $err_msg 201 fi 202 return 1 203 fi 204 205 rm -f $err_msg 206 print "Done." 207 return 0 208} 209 210# 211# Create directories on remote host using SFTP. Return 0 on success, 212# 1 on failure. 213# 214function remote_mkdirs 215{ 216 typeset -r dir_spec=$1 217 typeset -r host_spec=$2 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} ${host_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 if [[ ! -x $HG_ACTIVE ]]; then 1770 print # Blank line for the \c above 1771 print -u2 "Error: hg-active tool not found. Exiting" 1772 exit 1 1773 fi 1774 hg_active_wxfile $child $parent 1775 1776 # flist_from_wx prints the Done, so we don't have to. 1777 flist_from_wx $TMPFLIST 1778} 1779 1780# 1781# flist_from_subversion 1782# 1783# Generate the file list by extracting file names from svn status. 1784# 1785function flist_from_subversion 1786{ 1787 CWS=$1 1788 OLDPWD=$2 1789 1790 cd $CWS 1791 print -u2 " File list from: svn status ... \c" 1792 svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST 1793 print -u2 " Done." 1794 cd $OLDPWD 1795} 1796 1797function env_from_flist 1798{ 1799 [[ -r $FLIST ]] || return 1800 1801 # 1802 # Use "eval" to set env variables that are listed in the file 1803 # list. Then copy those into our local versions of those 1804 # variables if they have not been set already. 1805 # 1806 eval `$SED -e "s/#.*$//" $FLIST | $GREP = ` 1807 1808 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then 1809 codemgr_ws=$CODEMGR_WS 1810 export CODEMGR_WS 1811 fi 1812 1813 # 1814 # Check to see if CODEMGR_PARENT is set in the flist file. 1815 # 1816 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then 1817 codemgr_parent=$CODEMGR_PARENT 1818 export CODEMGR_PARENT 1819 fi 1820} 1821 1822function look_for_prog 1823{ 1824 typeset path 1825 typeset ppath 1826 typeset progname=$1 1827 1828 ppath=$PATH 1829 ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin 1830 ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin 1831 ppath=$ppath:/opt/onbld/bin/`uname -p` 1832 1833 PATH=$ppath prog=`whence $progname` 1834 if [[ -n $prog ]]; then 1835 print $prog 1836 fi 1837} 1838 1839function get_file_mode 1840{ 1841 $PERL -e ' 1842 if (@stat = stat($ARGV[0])) { 1843 $mode = $stat[2] & 0777; 1844 printf "%03o\n", $mode; 1845 exit 0; 1846 } else { 1847 exit 1; 1848 } 1849 ' $1 1850} 1851 1852function build_old_new_teamware 1853{ 1854 typeset olddir="$1" 1855 typeset newdir="$2" 1856 1857 # If the child's version doesn't exist then 1858 # get a readonly copy. 1859 1860 if [[ ! -f $CWS/$DIR/$F && -f $CWS/$DIR/SCCS/s.$F ]]; then 1861 $SCCS get -s -p $CWS/$DIR/$F > $CWS/$DIR/$F 1862 fi 1863 1864 # The following two sections propagate file permissions the 1865 # same way SCCS does. If the file is already under version 1866 # control, always use permissions from the SCCS/s.file. If 1867 # the file is not under SCCS control, use permissions from the 1868 # working copy. In all cases, the file copied to the webrev 1869 # is set to read only, and group/other permissions are set to 1870 # match those of the file owner. This way, even if the file 1871 # is currently checked out, the webrev will display the final 1872 # permissions that would result after check in. 1873 1874 # 1875 # Snag new version of file. 1876 # 1877 rm -f $newdir/$DIR/$F 1878 cp $CWS/$DIR/$F $newdir/$DIR/$F 1879 if [[ -f $CWS/$DIR/SCCS/s.$F ]]; then 1880 chmod `get_file_mode $CWS/$DIR/SCCS/s.$F` \ 1881 $newdir/$DIR/$F 1882 fi 1883 chmod u-w,go=u $newdir/$DIR/$F 1884 1885 # 1886 # Get the parent's version of the file. First see whether the 1887 # child's version is checked out and get the parent's version 1888 # with keywords expanded or unexpanded as appropriate. 1889 # 1890 if [[ -f $PWS/$PDIR/$PF && ! -f $PWS/$PDIR/SCCS/s.$PF && \ 1891 ! -f $PWS/$PDIR/SCCS/p.$PF ]]; then 1892 # Parent is not a real workspace, but just a raw 1893 # directory tree - use the file that's there as 1894 # the old file. 1895 1896 rm -f $olddir/$PDIR/$PF 1897 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 1898 else 1899 if [[ -f $PWS/$PDIR/SCCS/s.$PF ]]; then 1900 real_parent=$PWS 1901 else 1902 real_parent=$RWS 1903 fi 1904 1905 rm -f $olddir/$PDIR/$PF 1906 1907 if [[ -f $real_parent/$PDIR/$PF ]]; then 1908 if [ -f $CWS/$DIR/SCCS/p.$F ]; then 1909 $SCCS get -s -p -k $real_parent/$PDIR/$PF > \ 1910 $olddir/$PDIR/$PF 1911 else 1912 $SCCS get -s -p $real_parent/$PDIR/$PF > \ 1913 $olddir/$PDIR/$PF 1914 fi 1915 chmod `get_file_mode $real_parent/$PDIR/SCCS/s.$PF` \ 1916 $olddir/$PDIR/$PF 1917 fi 1918 fi 1919 if [[ -f $olddir/$PDIR/$PF ]]; then 1920 chmod u-w,go=u $olddir/$PDIR/$PF 1921 fi 1922} 1923 1924function build_old_new_mercurial 1925{ 1926 typeset olddir="$1" 1927 typeset newdir="$2" 1928 typeset old_mode= 1929 typeset new_mode= 1930 typeset file 1931 1932 # 1933 # Get old file mode, from the parent revision manifest entry. 1934 # Mercurial only stores a "file is executable" flag, but the 1935 # manifest will display an octal mode "644" or "755". 1936 # 1937 if [[ "$PDIR" == "." ]]; then 1938 file="$PF" 1939 else 1940 file="$PDIR/$PF" 1941 fi 1942 file=`echo $file | $SED 's#/#\\\/#g'` 1943 # match the exact filename, and return only the permission digits 1944 old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \ 1945 < $HG_PARENT_MANIFEST` 1946 1947 # 1948 # Get new file mode, directly from the filesystem. 1949 # Normalize the mode to match Mercurial's behavior. 1950 # 1951 new_mode=`get_file_mode $CWS/$DIR/$F` 1952 if [[ -n "$new_mode" ]]; then 1953 if [[ "$new_mode" = *[1357]* ]]; then 1954 new_mode=755 1955 else 1956 new_mode=644 1957 fi 1958 fi 1959 1960 # 1961 # new version of the file. 1962 # 1963 rm -rf $newdir/$DIR/$F 1964 if [[ -e $CWS/$DIR/$F ]]; then 1965 cp $CWS/$DIR/$F $newdir/$DIR/$F 1966 if [[ -n $new_mode ]]; then 1967 chmod $new_mode $newdir/$DIR/$F 1968 else 1969 # should never happen 1970 print -u2 "ERROR: set mode of $newdir/$DIR/$F" 1971 fi 1972 fi 1973 1974 # 1975 # parent's version of the file 1976 # 1977 # Note that we get this from the last version common to both 1978 # ourselves and the parent. References are via $CWS since we have no 1979 # guarantee that the parent workspace is reachable via the filesystem. 1980 # 1981 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then 1982 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 1983 elif [[ -n $HG_PARENT ]]; then 1984 hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \ 1985 $olddir/$PDIR/$PF 2>/dev/null 1986 1987 if (( $? != 0 )); then 1988 rm -f $olddir/$PDIR/$PF 1989 else 1990 if [[ -n $old_mode ]]; then 1991 chmod $old_mode $olddir/$PDIR/$PF 1992 else 1993 # should never happen 1994 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF" 1995 fi 1996 fi 1997 fi 1998} 1999 2000function build_old_new_subversion 2001{ 2002 typeset olddir="$1" 2003 typeset newdir="$2" 2004 2005 # Snag new version of file. 2006 rm -f $newdir/$DIR/$F 2007 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F 2008 2009 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then 2010 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 2011 else 2012 # Get the parent's version of the file. 2013 svn status $CWS/$DIR/$F | read stat file 2014 if [[ $stat != "A" ]]; then 2015 svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF 2016 fi 2017 fi 2018} 2019 2020function build_old_new_unknown 2021{ 2022 typeset olddir="$1" 2023 typeset newdir="$2" 2024 2025 # 2026 # Snag new version of file. 2027 # 2028 rm -f $newdir/$DIR/$F 2029 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F 2030 2031 # 2032 # Snag the parent's version of the file. 2033 # 2034 if [[ -f $PWS/$PDIR/$PF ]]; then 2035 rm -f $olddir/$PDIR/$PF 2036 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 2037 fi 2038} 2039 2040function build_old_new 2041{ 2042 typeset WDIR=$1 2043 typeset PWS=$2 2044 typeset PDIR=$3 2045 typeset PF=$4 2046 typeset CWS=$5 2047 typeset DIR=$6 2048 typeset F=$7 2049 2050 typeset olddir="$WDIR/raw_files/old" 2051 typeset newdir="$WDIR/raw_files/new" 2052 2053 mkdir -p $olddir/$PDIR 2054 mkdir -p $newdir/$DIR 2055 2056 if [[ $SCM_MODE == "teamware" ]]; then 2057 build_old_new_teamware "$olddir" "$newdir" 2058 elif [[ $SCM_MODE == "mercurial" ]]; then 2059 build_old_new_mercurial "$olddir" "$newdir" 2060 elif [[ $SCM_MODE == "subversion" ]]; then 2061 build_old_new_subversion "$olddir" "$newdir" 2062 elif [[ $SCM_MODE == "unknown" ]]; then 2063 build_old_new_unknown "$olddir" "$newdir" 2064 fi 2065 2066 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then 2067 print "*** Error: file not in parent or child" 2068 return 1 2069 fi 2070 return 0 2071} 2072 2073 2074# 2075# Usage message. 2076# 2077function usage 2078{ 2079 print 'Usage:\twebrev [common-options] 2080 webrev [common-options] ( <file> | - ) 2081 webrev [common-options] -w <wx file> 2082 2083Options: 2084 -C <filename>: Use <filename> for the information tracking configuration. 2085 -D: delete remote webrev 2086 -i <filename>: Include <filename> in the index.html file. 2087 -I <filename>: Use <filename> for the information tracking registry. 2088 -n: do not generate the webrev (useful with -U) 2089 -O: Print bugids/arc cases suitable for OpenSolaris. 2090 -o <outdir>: Output webrev to specified directory. 2091 -p <compare-against>: Use specified parent wkspc or basis for comparison 2092 -t <remote_target>: Specify remote destination for webrev upload 2093 -U: upload the webrev to remote destination 2094 -w <wxfile>: Use specified wx active file. 2095 2096Environment: 2097 WDIR: Control the output directory. 2098 WEBREV_TRASH_DIR: Set directory for webrev delete. 2099 2100SCM Specific Options: 2101 TeamWare: webrev [common-options] -l [arguments to 'putback'] 2102 2103SCM Environment: 2104 CODEMGR_WS: Workspace location. 2105 CODEMGR_PARENT: Parent workspace location. 2106' 2107 2108 exit 2 2109} 2110 2111# 2112# 2113# Main program starts here 2114# 2115# 2116 2117trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15 2118 2119set +o noclobber 2120 2121PATH=$(dirname $(whence $0)):$PATH 2122 2123[[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff` 2124[[ -z $WX ]] && WX=`look_for_prog wx` 2125[[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active` 2126[[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm` 2127[[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview` 2128[[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf` 2129[[ -z $PERL ]] && PERL=`look_for_prog perl` 2130[[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync` 2131[[ -z $SCCS ]] && SCCS=`look_for_prog sccs` 2132[[ -z $AWK ]] && AWK=`look_for_prog nawk` 2133[[ -z $AWK ]] && AWK=`look_for_prog gawk` 2134[[ -z $AWK ]] && AWK=`look_for_prog awk` 2135[[ -z $SCP ]] && SCP=`look_for_prog scp` 2136[[ -z $SED ]] && SED=`look_for_prog sed` 2137[[ -z $SFTP ]] && SFTP=`look_for_prog sftp` 2138[[ -z $SORT ]] && SORT=`look_for_prog sort` 2139[[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp` 2140[[ -z $GREP ]] && GREP=`look_for_prog grep` 2141[[ -z $FIND ]] && FIND=`look_for_prog find` 2142 2143# set name of trash directory for remote webrev deletion 2144TRASH_DIR=".trash" 2145[[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR 2146 2147if [[ ! -x $PERL ]]; then 2148 print -u2 "Error: No perl interpreter found. Exiting." 2149 exit 1 2150fi 2151 2152if [[ ! -x $WHICH_SCM ]]; then 2153 print -u2 "Error: Could not find which_scm. Exiting." 2154 exit 1 2155fi 2156 2157# 2158# These aren't fatal, but we want to note them to the user. 2159# We don't warn on the absence of 'wx' until later when we've 2160# determined that we actually need to try to invoke it. 2161# 2162[[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found." 2163[[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found." 2164[[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found." 2165 2166# Declare global total counters. 2167integer TOTL TINS TDEL TMOD TUNC 2168 2169# default remote host for upload/delete 2170typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org" 2171# prefixes for upload targets 2172typeset -r rsync_prefix="rsync://" 2173typeset -r ssh_prefix="ssh://" 2174 2175Cflag= 2176Dflag= 2177flist_mode= 2178flist_file= 2179iflag= 2180Iflag= 2181lflag= 2182Nflag= 2183nflag= 2184Oflag= 2185oflag= 2186pflag= 2187tflag= 2188uflag= 2189Uflag= 2190wflag= 2191remote_target= 2192 2193# 2194# NOTE: when adding/removing options it is necessary to sync the list 2195# with usr/src/tools/onbld/hgext/cdm.py 2196# 2197while getopts "C:Di:I:lnNo:Op:t:Uw" opt 2198do 2199 case $opt in 2200 C) Cflag=1 2201 ITSCONF=$OPTARG;; 2202 2203 D) Dflag=1;; 2204 2205 i) iflag=1 2206 INCLUDE_FILE=$OPTARG;; 2207 2208 I) Iflag=1 2209 ITSREG=$OPTARG;; 2210 2211 # 2212 # If -l has been specified, we need to abort further options 2213 # processing, because subsequent arguments are going to be 2214 # arguments to 'putback -n'. 2215 # 2216 l) lflag=1 2217 break;; 2218 2219 N) Nflag=1;; 2220 2221 n) nflag=1;; 2222 2223 O) Oflag=1;; 2224 2225 o) oflag=1 2226 # Strip the trailing slash to correctly form remote target. 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 # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under 2288 # usr/closed when we run webrev 2289 # 2. hg root from directory of invocation 2290 # 2291 if [[ ${PWD} =~ "usr/closed" ]]; then 2292 testparent=${CODEMGR_WS}/usr/closed 2293 # If we're in OpenSolaris mode, we enforce a minor policy: 2294 # help to make sure the reviewer doesn't accidentally publish 2295 # source which is under usr/closed 2296 if [[ -n "$Oflag" ]]; then 2297 print -u2 "OpenSolaris output not permitted with" \ 2298 "usr/closed changes" 2299 exit 1 2300 fi 2301 else 2302 testparent=${CODEMGR_WS} 2303 fi 2304 [[ -z $codemgr_ws && -n $testparent ]] && \ 2305 codemgr_ws=$(hg root -R $testparent 2>/dev/null) 2306 [[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null) 2307 CWS=$codemgr_ws 2308elif [[ $SCM_MODE == "subversion" ]]; then 2309 # 2310 # Subversion priorities: 2311 # 1. CODEMGR_WS from environment 2312 # 2. Relative path from current directory to SVN repository root 2313 # 2314 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then 2315 CWS=$CODEMGR_WS 2316 else 2317 svn info | while read line; do 2318 if [[ $line == "URL: "* ]]; then 2319 url=${line#URL: } 2320 elif [[ $line == "Repository Root: "* ]]; then 2321 repo=${line#Repository Root: } 2322 fi 2323 done 2324 2325 rel=${url#$repo} 2326 CWS=${PWD%$rel} 2327 fi 2328fi 2329 2330# 2331# If no SCM has been determined, take either the environment setting 2332# setting for CODEMGR_WS, or the current directory if that wasn't set. 2333# 2334if [[ -z ${CWS} ]]; then 2335 CWS=${CODEMGR_WS:-.} 2336fi 2337 2338# 2339# If the command line options indicate no webrev generation, either 2340# explicitly (-n) or implicitly (-D but not -U), then there's a whole 2341# ton of logic we can skip. 2342# 2343# Instead of increasing indentation, we intentionally leave this loop 2344# body open here, and exit via break from multiple points within. 2345# Search for DO_EVERYTHING below to find the break points and closure. 2346# 2347for do_everything in 1; do 2348 2349# DO_EVERYTHING: break point 2350if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then 2351 break 2352fi 2353 2354# 2355# If this manually set as the parent, and it appears to be an earlier webrev, 2356# then note that fact and set the parent to the raw_files/new subdirectory. 2357# 2358if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then 2359 parent_webrev="$codemgr_parent" 2360 codemgr_parent="$codemgr_parent/raw_files/new" 2361fi 2362 2363if [[ -z $wflag && -z $lflag ]]; then 2364 shift $(($OPTIND - 1)) 2365 2366 if [[ $1 == "-" ]]; then 2367 cat > $FLIST 2368 flist_mode="stdin" 2369 flist_done=1 2370 shift 2371 elif [[ -n $1 ]]; then 2372 if [[ ! -r $1 ]]; then 2373 print -u2 "$1: no such file or not readable" 2374 usage 2375 fi 2376 cat $1 > $FLIST 2377 flist_mode="file" 2378 flist_file=$1 2379 flist_done=1 2380 shift 2381 else 2382 flist_mode="auto" 2383 fi 2384fi 2385 2386# 2387# Before we go on to further consider -l and -w, work out which SCM we think 2388# is in use. 2389# 2390case "$SCM_MODE" in 2391teamware|mercurial|subversion) 2392 ;; 2393unknown) 2394 if [[ $flist_mode == "auto" ]]; then 2395 print -u2 "Unable to determine SCM in use and file list not specified" 2396 print -u2 "See which_scm(1) for SCM detection information." 2397 exit 1 2398 fi 2399 ;; 2400*) 2401 if [[ $flist_mode == "auto" ]]; then 2402 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified" 2403 exit 1 2404 fi 2405 ;; 2406esac 2407 2408print -u2 " SCM detected: $SCM_MODE" 2409 2410if [[ -n $lflag ]]; then 2411 # 2412 # If the -l flag is given instead of the name of a file list, 2413 # then generate the file list by extracting file names from a 2414 # putback -n. 2415 # 2416 shift $(($OPTIND - 1)) 2417 if [[ $SCM_MODE == "teamware" ]]; then 2418 flist_from_teamware "$*" 2419 else 2420 print -u2 -- "Error: -l option only applies to TeamWare" 2421 exit 1 2422 fi 2423 flist_done=1 2424 shift $# 2425elif [[ -n $wflag ]]; then 2426 # 2427 # If the -w is given then assume the file list is in Bonwick's "wx" 2428 # command format, i.e. pathname lines alternating with SCCS comment 2429 # lines with blank lines as separators. Use the SCCS comments later 2430 # in building the index.html file. 2431 # 2432 shift $(($OPTIND - 1)) 2433 wxfile=$1 2434 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then 2435 if [[ -r $CODEMGR_WS/wx/active ]]; then 2436 wxfile=$CODEMGR_WS/wx/active 2437 fi 2438 fi 2439 2440 [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \ 2441 "be auto-detected (check \$CODEMGR_WS)" && exit 1 2442 2443 if [[ ! -r $wxfile ]]; then 2444 print -u2 "$wxfile: no such file or not readable" 2445 usage 2446 fi 2447 2448 print -u2 " File list from: wx 'active' file '$wxfile' ... \c" 2449 flist_from_wx $wxfile 2450 flist_done=1 2451 if [[ -n "$*" ]]; then 2452 shift 2453 fi 2454elif [[ $flist_mode == "stdin" ]]; then 2455 print -u2 " File list from: standard input" 2456elif [[ $flist_mode == "file" ]]; then 2457 print -u2 " File list from: $flist_file" 2458fi 2459 2460if [[ $# -gt 0 ]]; then 2461 print -u2 "WARNING: unused arguments: $*" 2462fi 2463 2464# 2465# Before we entered the DO_EVERYTHING loop, we should have already set CWS 2466# and CODEMGR_WS as needed. Here, we set the parent workspace. 2467# 2468 2469if [[ $SCM_MODE == "teamware" ]]; then 2470 2471 # 2472 # Teamware priorities: 2473 # 2474 # 1) via -p command line option 2475 # 2) in the user environment 2476 # 3) in the flist 2477 # 4) automatically based on the workspace 2478 # 2479 2480 # 2481 # For 1, codemgr_parent will already be set. Here's 2: 2482 # 2483 [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \ 2484 codemgr_parent=$CODEMGR_PARENT 2485 if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then 2486 print -u2 "$codemgr_parent: no such directory" 2487 exit 1 2488 fi 2489 2490 # 2491 # If we're in auto-detect mode and we haven't already gotten the file 2492 # list, then see if we can get it by probing for wx. 2493 # 2494 if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then 2495 if [[ ! -x $WX ]]; then 2496 print -u2 "WARNING: wx not found!" 2497 fi 2498 2499 # 2500 # We need to use wx list -w so that we get renamed files, etc. 2501 # but only if a wx active file exists-- otherwise wx will 2502 # hang asking us to initialize our wx information. 2503 # 2504 if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then 2505 print -u2 " File list from: 'wx list -w' ... \c" 2506 $WX list -w > $FLIST 2507 $WX comments > /tmp/$$.wx_comments 2508 wxfile=/tmp/$$.wx_comments 2509 print -u2 "done" 2510 flist_done=1 2511 fi 2512 fi 2513 2514 # 2515 # If by hook or by crook we've gotten a file list by now (perhaps 2516 # from the command line), eval it to extract environment variables from 2517 # it: This is method 3 for finding the parent. 2518 # 2519 if [[ -z $flist_done ]]; then 2520 flist_from_teamware 2521 fi 2522 env_from_flist 2523 2524 # 2525 # (4) If we still don't have a value for codemgr_parent, get it 2526 # from workspace. 2527 # 2528 [[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent` 2529 if [[ ! -d $codemgr_parent ]]; then 2530 print -u2 "$CODEMGR_PARENT: no such parent workspace" 2531 exit 1 2532 fi 2533 2534 PWS=$codemgr_parent 2535 2536 [[ -n $parent_webrev ]] && RWS=$(workspace parent $CWS) 2537 2538elif [[ $SCM_MODE == "mercurial" ]]; then 2539 # 2540 # Parent can either be specified with -p 2541 # Specified with CODEMGR_PARENT in the environment 2542 # or taken from hg's default path. 2543 # 2544 2545 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then 2546 codemgr_parent=$CODEMGR_PARENT 2547 fi 2548 2549 if [[ -z $codemgr_parent ]]; then 2550 codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null` 2551 fi 2552 2553 CWS_REV=`hg parent -R $codemgr_ws --template '{node|short}' 2>/dev/null` 2554 PWS=$codemgr_parent 2555 2556 # 2557 # If the parent is a webrev, we want to do some things against 2558 # the natural workspace parent (file list, comments, etc) 2559 # 2560 if [[ -n $parent_webrev ]]; then 2561 real_parent=$(hg path -R $codemgr_ws default 2>/dev/null) 2562 else 2563 real_parent=$PWS 2564 fi 2565 2566 # 2567 # If hg-active exists, then we run it. In the case of no explicit 2568 # flist given, we'll use it for our comments. In the case of an 2569 # explicit flist given we'll try to use it for comments for any 2570 # files mentioned in the flist. 2571 # 2572 if [[ -z $flist_done ]]; then 2573 flist_from_mercurial $CWS $real_parent 2574 flist_done=1 2575 fi 2576 2577 # 2578 # If we have a file list now, pull out any variables set 2579 # therein. We do this now (rather than when we possibly use 2580 # hg-active to find comments) to avoid stomping specifications 2581 # in the user-specified flist. 2582 # 2583 if [[ -n $flist_done ]]; then 2584 env_from_flist 2585 fi 2586 2587 # 2588 # Only call hg-active if we don't have a wx formatted file already 2589 # 2590 if [[ -x $HG_ACTIVE && -z $wxfile ]]; then 2591 print " Comments from: hg-active -p $real_parent ...\c" 2592 hg_active_wxfile $CWS $real_parent 2593 print " Done." 2594 fi 2595 2596 # 2597 # At this point we must have a wx flist either from hg-active, 2598 # or in general. Use it to try and find our parent revision, 2599 # if we don't have one. 2600 # 2601 if [[ -z $HG_PARENT ]]; then 2602 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=` 2603 fi 2604 2605 # 2606 # If we still don't have a parent, we must have been given a 2607 # wx-style active list with no HG_PARENT specification, run 2608 # hg-active and pull an HG_PARENT out of it, ignore the rest. 2609 # 2610 if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then 2611 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \ 2612 eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=` 2613 elif [[ -z $HG_PARENT ]]; then 2614 print -u2 "Error: Cannot discover parent revision" 2615 exit 1 2616 fi 2617elif [[ $SCM_MODE == "subversion" ]]; then 2618 2619 # 2620 # We only will have a real parent workspace in the case one 2621 # was specified (be it an older webrev, or another checkout). 2622 # 2623 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent 2624 2625 if [[ -z $flist_done && $flist_mode == "auto" ]]; then 2626 flist_from_subversion $CWS $OLDPWD 2627 fi 2628else 2629 if [[ $SCM_MODE == "unknown" ]]; then 2630 print -u2 " Unknown type of SCM in use" 2631 else 2632 print -u2 " Unsupported SCM in use: $SCM_MODE" 2633 fi 2634 2635 env_from_flist 2636 2637 if [[ -z $CODEMGR_WS ]]; then 2638 print -u2 "SCM not detected/supported and CODEMGR_WS not specified" 2639 exit 1 2640 fi 2641 2642 if [[ -z $CODEMGR_PARENT ]]; then 2643 print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified" 2644 exit 1 2645 fi 2646 2647 CWS=$CODEMGR_WS 2648 PWS=$CODEMGR_PARENT 2649fi 2650 2651# 2652# If the user didn't specify a -i option, check to see if there is a 2653# webrev-info file in the workspace directory. 2654# 2655if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then 2656 iflag=1 2657 INCLUDE_FILE="$CWS/webrev-info" 2658fi 2659 2660if [[ -n $iflag ]]; then 2661 if [[ ! -r $INCLUDE_FILE ]]; then 2662 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \ 2663 "not readable." 2664 exit 1 2665 else 2666 # 2667 # $INCLUDE_FILE may be a relative path, and the script alters 2668 # PWD, so we just stash a copy in /tmp. 2669 # 2670 cp $INCLUDE_FILE /tmp/$$.include 2671 fi 2672fi 2673 2674# DO_EVERYTHING: break point 2675if [[ -n $Nflag ]]; then 2676 break 2677fi 2678 2679typeset -A itsinfo 2680typeset -r its_sed_script=/tmp/$$.its_sed 2681valid_prefixes= 2682if [[ -z $nflag ]]; then 2683 DEFREGFILE="$(dirname $(whence $0))/../etc/its.reg" 2684 if [[ -n $Iflag ]]; then 2685 REGFILE=$ITSREG 2686 elif [[ -r $HOME/.its.reg ]]; then 2687 REGFILE=$HOME/.its.reg 2688 else 2689 REGFILE=$DEFREGFILE 2690 fi 2691 if [[ ! -r $REGFILE ]]; then 2692 print "ERROR: Unable to read database registry file $REGFILE" 2693 exit 1 2694 elif [[ $REGFILE != $DEFREGFILE ]]; then 2695 print " its.reg from: $REGFILE" 2696 fi 2697 2698 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do 2699 2700 name=${LINE%%=*} 2701 value="${LINE#*=}" 2702 2703 if [[ $name == PREFIX ]]; then 2704 p=${value} 2705 valid_prefixes="${p} ${valid_prefixes}" 2706 else 2707 itsinfo["${p}_${name}"]="${value}" 2708 fi 2709 done 2710 2711 2712 DEFCONFFILE="$(dirname $(whence $0))/../etc/its.conf" 2713 CONFFILES=$DEFCONFFILE 2714 if [[ -r $HOME/.its.conf ]]; then 2715 CONFFILES="${CONFFILES} $HOME/.its.conf" 2716 fi 2717 if [[ -n $Cflag ]]; then 2718 CONFFILES="${CONFFILES} ${ITSCONF}" 2719 fi 2720 its_domain= 2721 its_priority= 2722 for cf in ${CONFFILES}; do 2723 if [[ ! -r $cf ]]; then 2724 print "ERROR: Unable to read database configuration file $cf" 2725 exit 1 2726 elif [[ $cf != $DEFCONFFILE ]]; then 2727 print " its.conf: reading $cf" 2728 fi 2729 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do 2730 eval "${LINE}" 2731 done 2732 done 2733 2734 # 2735 # If an information tracking system is explicitly identified by prefix, 2736 # we want to disregard the specified priorities and resolve it accordingly. 2737 # 2738 # To that end, we'll build a sed script to do each valid prefix in turn. 2739 # 2740 for p in ${valid_prefixes}; do 2741 # 2742 # When an informational URL was provided, translate it to a 2743 # hyperlink. When omitted, simply use the prefix text. 2744 # 2745 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then 2746 itsinfo["${p}_INFO"]=${p} 2747 else 2748 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>" 2749 fi 2750 2751 # 2752 # Assume that, for this invocation of webrev, all references 2753 # to this information tracking system should resolve through 2754 # the same URL. 2755 # 2756 # If the caller specified -O, then always use EXTERNAL_URL. 2757 # 2758 # Otherwise, look in the list of domains for a matching 2759 # INTERNAL_URL. 2760 # 2761 [[ -z $Oflag ]] && for d in ${its_domain}; do 2762 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then 2763 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}" 2764 break 2765 fi 2766 done 2767 if [[ -z ${itsinfo["${p}_URL"]} ]]; then 2768 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}" 2769 fi 2770 2771 # 2772 # Turn the destination URL into a hyperlink 2773 # 2774 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>" 2775 2776 print "/^${p}[ ]/ { 2777 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g 2778 s;^${p};${itsinfo[${p}_INFO]}; 2779 }" >> ${its_sed_script} 2780 done 2781 2782 # 2783 # The previous loop took care of explicit specification. Now use 2784 # the configured priorities to attempt implicit translations. 2785 # 2786 for p in ${its_priority}; do 2787 print "/^${itsinfo[${p}_REGEX]}[ ]/ { 2788 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g 2789 }" >> ${its_sed_script} 2790 done 2791fi 2792 2793# 2794# Search for DO_EVERYTHING above for matching "for" statement 2795# and explanation of this terminator. 2796# 2797done 2798 2799# 2800# Output directory. 2801# 2802WDIR=${WDIR:-$CWS/webrev} 2803 2804# 2805# Name of the webrev, derived from the workspace name or output directory; 2806# in the future this could potentially be an option. 2807# 2808if [[ -n $oflag ]]; then 2809 WNAME=${WDIR##*/} 2810else 2811 WNAME=${CWS##*/} 2812fi 2813 2814# Make sure remote target is well formed for remote upload/delete. 2815if [[ -n $Dflag || -n $Uflag ]]; then 2816 # 2817 # If remote target is not specified, build it from scratch using 2818 # the default values. 2819 # 2820 if [[ -z $tflag ]]; then 2821 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME} 2822 else 2823 # 2824 # Check upload target prefix first. 2825 # 2826 if [[ "${remote_target}" != ${rsync_prefix}* && 2827 "${remote_target}" != ${ssh_prefix}* ]]; then 2828 print "ERROR: invalid prefix of upload URI" \ 2829 "($remote_target)" 2830 exit 1 2831 fi 2832 # 2833 # If destination specification is not in the form of 2834 # host_spec:remote_dir then assume it is just remote hostname 2835 # and append a colon and destination directory formed from 2836 # local webrev directory name. 2837 # 2838 typeset target_no_prefix=${remote_target##*://} 2839 if [[ ${target_no_prefix} == *:* ]]; then 2840 if [[ "${remote_target}" == *: ]]; then 2841 remote_target=${remote_target}${WNAME} 2842 fi 2843 else 2844 if [[ ${target_no_prefix} == */* ]]; then 2845 print "ERROR: badly formed upload URI" \ 2846 "($remote_target)" 2847 exit 1 2848 else 2849 remote_target=${remote_target}:${WNAME} 2850 fi 2851 fi 2852 fi 2853 2854 # 2855 # Strip trailing slash. Each upload method will deal with directory 2856 # specification separately. 2857 # 2858 remote_target=${remote_target%/} 2859fi 2860 2861# 2862# Option -D by itself (option -U not present) implies no webrev generation. 2863# 2864if [[ -z $Uflag && -n $Dflag ]]; then 2865 delete_webrev 1 1 2866 exit $? 2867fi 2868 2869# 2870# Do not generate the webrev, just upload it or delete it. 2871# 2872if [[ -n $nflag ]]; then 2873 if [[ -n $Dflag ]]; then 2874 delete_webrev 1 1 2875 (( $? == 0 )) || exit $? 2876 fi 2877 if [[ -n $Uflag ]]; then 2878 upload_webrev 2879 exit $? 2880 fi 2881fi 2882 2883if [ "${WDIR%%/*}" ]; then 2884 WDIR=$PWD/$WDIR 2885fi 2886 2887if [[ ! -d $WDIR ]]; then 2888 mkdir -p $WDIR 2889 (( $? != 0 )) && exit 1 2890fi 2891 2892# 2893# Summarize what we're going to do. 2894# 2895if [[ -n $CWS_REV ]]; then 2896 print " Workspace: $CWS (at $CWS_REV)" 2897else 2898 print " Workspace: $CWS" 2899fi 2900if [[ -n $parent_webrev ]]; then 2901 print "Compare against: webrev at $parent_webrev" 2902else 2903 if [[ -n $HG_PARENT ]]; then 2904 hg_parent_short=`echo $HG_PARENT \ 2905 | $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'` 2906 print "Compare against: $PWS (at $hg_parent_short)" 2907 else 2908 print "Compare against: $PWS" 2909 fi 2910fi 2911 2912[[ -n $INCLUDE_FILE ]] && print " Including: $INCLUDE_FILE" 2913print " Output to: $WDIR" 2914 2915# 2916# Save the file list in the webrev dir 2917# 2918[[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list 2919 2920rm -f $WDIR/$WNAME.patch 2921rm -f $WDIR/$WNAME.ps 2922rm -f $WDIR/$WNAME.pdf 2923 2924touch $WDIR/$WNAME.patch 2925 2926print " Output Files:" 2927 2928# 2929# Clean up the file list: Remove comments, blank lines and env variables. 2930# 2931$SED -e "s/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean 2932FLIST=/tmp/$$.flist.clean 2933 2934# 2935# For Mercurial, create a cache of manifest entries. 2936# 2937if [[ $SCM_MODE == "mercurial" ]]; then 2938 # 2939 # Transform the FLIST into a temporary sed script that matches 2940 # relevant entries in the Mercurial manifest as follows: 2941 # 1) The script will be used against the parent revision manifest, 2942 # so for FLIST lines that have two filenames (a renamed file) 2943 # keep only the old name. 2944 # 2) Escape all forward slashes the filename. 2945 # 3) Change the filename into another sed command that matches 2946 # that file in "hg manifest -v" output: start of line, three 2947 # octal digits for file permissions, space, a file type flag 2948 # character, space, the filename, end of line. 2949 # 4) Eliminate any duplicate entries. (This can occur if a 2950 # file has been used as the source of an hg cp and it's 2951 # also been modified in the same changeset.) 2952 # 2953 SEDFILE=/tmp/$$.manifest.sed 2954 $SED ' 2955 s#^[^ ]* ## 2956 s#/#\\\/#g 2957 s#^.*$#/^... . &$/p# 2958 ' < $FLIST | $SORT -u > $SEDFILE 2959 2960 # 2961 # Apply the generated script to the output of "hg manifest -v" 2962 # to get the relevant subset for this webrev. 2963 # 2964 HG_PARENT_MANIFEST=/tmp/$$.manifest 2965 hg -R $CWS manifest -v -r $HG_PARENT | 2966 $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST 2967fi 2968 2969# 2970# First pass through the files: generate the per-file webrev HTML-files. 2971# 2972cat $FLIST | while read LINE 2973do 2974 set - $LINE 2975 P=$1 2976 2977 # 2978 # Normally, each line in the file list is just a pathname of a 2979 # file that has been modified or created in the child. A file 2980 # that is renamed in the child workspace has two names on the 2981 # line: new name followed by the old name. 2982 # 2983 oldname="" 2984 oldpath="" 2985 rename= 2986 if [[ $# -eq 2 ]]; then 2987 PP=$2 # old filename 2988 if [[ -f $PP ]]; then 2989 oldname=" (copied from $PP)" 2990 else 2991 oldname=" (renamed from $PP)" 2992 fi 2993 oldpath="$PP" 2994 rename=1 2995 PDIR=${PP%/*} 2996 if [[ $PDIR == $PP ]]; then 2997 PDIR="." # File at root of workspace 2998 fi 2999 3000 PF=${PP##*/} 3001 3002 DIR=${P%/*} 3003 if [[ $DIR == $P ]]; then 3004 DIR="." # File at root of workspace 3005 fi 3006 3007 F=${P##*/} 3008 3009 else 3010 DIR=${P%/*} 3011 if [[ "$DIR" == "$P" ]]; then 3012 DIR="." # File at root of workspace 3013 fi 3014 3015 F=${P##*/} 3016 3017 PP=$P 3018 PDIR=$DIR 3019 PF=$F 3020 fi 3021 3022 COMM=`getcomments html $P $PP` 3023 3024 print "\t$P$oldname\n\t\t\c" 3025 3026 # Make the webrev mirror directory if necessary 3027 mkdir -p $WDIR/$DIR 3028 3029 # 3030 # We stash old and new files into parallel directories in $WDIR 3031 # and do our diffs there. This makes it possible to generate 3032 # clean looking diffs which don't have absolute paths present. 3033 # 3034 3035 build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \ 3036 continue 3037 3038 # 3039 # Keep the old PWD around, so we can safely switch back after 3040 # diff generation, such that build_old_new runs in a 3041 # consistent environment. 3042 # 3043 OWD=$PWD 3044 cd $WDIR/raw_files 3045 ofile=old/$PDIR/$PF 3046 nfile=new/$DIR/$F 3047 3048 mv_but_nodiff= 3049 cmp $ofile $nfile > /dev/null 2>&1 3050 if [[ $? == 0 && $rename == 1 ]]; then 3051 mv_but_nodiff=1 3052 fi 3053 3054 # 3055 # If we have old and new versions of the file then run the appropriate 3056 # diffs. This is complicated by a couple of factors: 3057 # 3058 # - renames must be handled specially: we emit a 'remove' 3059 # diff and an 'add' diff 3060 # - new files and deleted files must be handled specially 3061 # - Solaris patch(1m) can't cope with file creation 3062 # (and hence renames) as of this writing. 3063 # - To make matters worse, gnu patch doesn't interpret the 3064 # output of Solaris diff properly when it comes to 3065 # adds and deletes. We need to do some "cleansing" 3066 # transformations: 3067 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@ 3068 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@ 3069 # 3070 cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'" 3071 cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'" 3072 3073 rm -f $WDIR/$DIR/$F.patch 3074 if [[ -z $rename ]]; then 3075 if [ ! -f "$ofile" ]; then 3076 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \ 3077 > $WDIR/$DIR/$F.patch 3078 elif [ ! -f "$nfile" ]; then 3079 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \ 3080 > $WDIR/$DIR/$F.patch 3081 else 3082 diff -u $ofile $nfile > $WDIR/$DIR/$F.patch 3083 fi 3084 else 3085 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \ 3086 > $WDIR/$DIR/$F.patch 3087 3088 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \ 3089 >> $WDIR/$DIR/$F.patch 3090 fi 3091 3092 # 3093 # Tack the patch we just made onto the accumulated patch for the 3094 # whole wad. 3095 # 3096 cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch 3097 3098 print " patch\c" 3099 3100 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then 3101 3102 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff 3103 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \ 3104 > $WDIR/$DIR/$F.cdiff.html 3105 print " cdiffs\c" 3106 3107 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff 3108 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \ 3109 > $WDIR/$DIR/$F.udiff.html 3110 3111 print " udiffs\c" 3112 3113 if [[ -x $WDIFF ]]; then 3114 $WDIFF -c "$COMM" \ 3115 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \ 3116 $WDIR/$DIR/$F.wdiff.html 2>/dev/null 3117 if [[ $? -eq 0 ]]; then 3118 print " wdiffs\c" 3119 else 3120 print " wdiffs[fail]\c" 3121 fi 3122 fi 3123 3124 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \ 3125 > $WDIR/$DIR/$F.sdiff.html 3126 print " sdiffs\c" 3127 3128 print " frames\c" 3129 3130 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff 3131 3132 difflines $ofile $nfile > $WDIR/$DIR/$F.count 3133 3134 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then 3135 # renamed file: may also have differences 3136 difflines $ofile $nfile > $WDIR/$DIR/$F.count 3137 elif [[ -f $nfile ]]; then 3138 # new file: count added lines 3139 difflines /dev/null $nfile > $WDIR/$DIR/$F.count 3140 elif [[ -f $ofile ]]; then 3141 # old file: count deleted lines 3142 difflines $ofile /dev/null > $WDIR/$DIR/$F.count 3143 fi 3144 3145 # 3146 # Now we generate the postscript for this file. We generate diffs 3147 # only in the event that there is delta, or the file is new (it seems 3148 # tree-killing to print out the contents of deleted files). 3149 # 3150 if [[ -f $nfile ]]; then 3151 ocr=$ofile 3152 [[ ! -f $ofile ]] && ocr=/dev/null 3153 3154 if [[ -z $mv_but_nodiff ]]; then 3155 textcomm=`getcomments text $P $PP` 3156 if [[ -x $CODEREVIEW ]]; then 3157 $CODEREVIEW -y "$textcomm" \ 3158 -e $ocr $nfile \ 3159 > /tmp/$$.psfile 2>/dev/null && 3160 cat /tmp/$$.psfile >> $WDIR/$WNAME.ps 3161 if [[ $? -eq 0 ]]; then 3162 print " ps\c" 3163 else 3164 print " ps[fail]\c" 3165 fi 3166 fi 3167 fi 3168 fi 3169 3170 if [[ -f $ofile ]]; then 3171 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html 3172 print " old\c" 3173 fi 3174 3175 if [[ -f $nfile ]]; then 3176 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html 3177 print " new\c" 3178 fi 3179 3180 cd $OWD 3181 3182 print 3183done 3184 3185frame_nav_js > $WDIR/ancnav.js 3186frame_navigation > $WDIR/ancnav.html 3187 3188if [[ ! -f $WDIR/$WNAME.ps ]]; then 3189 print " Generating PDF: Skipped: no output available" 3190elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then 3191 print " Generating PDF: \c" 3192 fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf 3193 print "Done." 3194else 3195 print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'" 3196fi 3197 3198# If we're in OpenSolaris mode and there's a closed dir under $WDIR, 3199# delete it - prevent accidental publishing of closed source 3200 3201if [[ -n "$Oflag" ]]; then 3202 $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \; 3203fi 3204 3205# Now build the index.html file that contains 3206# links to the source files and their diffs. 3207 3208cd $CWS 3209 3210# Save total changed lines for Code Inspection. 3211print "$TOTL" > $WDIR/TotalChangedLines 3212 3213print " index.html: \c" 3214INDEXFILE=$WDIR/index.html 3215exec 3<&1 # duplicate stdout to FD3. 3216exec 1<&- # Close stdout. 3217exec > $INDEXFILE # Open stdout to index file. 3218 3219print "$HTML<head>$STDHEAD" 3220print "<title>$WNAME</title>" 3221print "</head>" 3222print "<body id=\"SUNWwebrev\">" 3223print "<div class=\"summary\">" 3224print "<h2>Code Review for $WNAME</h2>" 3225 3226print "<table>" 3227 3228# 3229# Get the preparer's name: 3230# 3231# If the SCM detected is Mercurial, and the configuration property 3232# ui.username is available, use that, but be careful to properly escape 3233# angle brackets (HTML syntax characters) in the email address. 3234# 3235# Otherwise, use the current userid in the form "John Doe (jdoe)", but 3236# to maintain compatibility with passwd(4), we must support '&' substitutions. 3237# 3238preparer= 3239if [[ "$SCM_MODE" == mercurial ]]; then 3240 preparer=`hg showconfig ui.username 2>/dev/null` 3241 if [[ -n "$preparer" ]]; then 3242 preparer="$(echo "$preparer" | html_quote)" 3243 fi 3244fi 3245if [[ -z "$preparer" ]]; then 3246 preparer=$( 3247 $PERL -e ' 3248 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<); 3249 if ($login) { 3250 $gcos =~ s/\&/ucfirst($login)/e; 3251 printf "%s (%s)\n", $gcos, $login; 3252 } else { 3253 printf "(unknown)\n"; 3254 } 3255 ') 3256fi 3257 3258PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z) 3259print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>" 3260print "<tr><th>Workspace:</th><td>$CWS" 3261if [[ -n $CWS_REV ]]; then 3262 print "(at $CWS_REV)" 3263fi 3264print "</td></tr>" 3265print "<tr><th>Compare against:</th><td>" 3266if [[ -n $parent_webrev ]]; then 3267 print "webrev at $parent_webrev" 3268else 3269 print "$PWS" 3270 if [[ -n $hg_parent_short ]]; then 3271 print "(at $hg_parent_short)" 3272 fi 3273fi 3274print "</td></tr>" 3275print "<tr><th>Summary of changes:</th><td>" 3276printCI $TOTL $TINS $TDEL $TMOD $TUNC 3277print "</td></tr>" 3278 3279if [[ -f $WDIR/$WNAME.patch ]]; then 3280 wpatch_url="$(print $WNAME.patch | url_encode)" 3281 print "<tr><th>Patch of changes:</th><td>" 3282 print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>" 3283fi 3284if [[ -f $WDIR/$WNAME.pdf ]]; then 3285 wpdf_url="$(print $WNAME.pdf | url_encode)" 3286 print "<tr><th>Printable review:</th><td>" 3287 print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>" 3288fi 3289 3290if [[ -n "$iflag" ]]; then 3291 print "<tr><th>Author comments:</th><td><div>" 3292 cat /tmp/$$.include 3293 print "</div></td></tr>" 3294fi 3295print "</table>" 3296print "</div>" 3297 3298# 3299# Second pass through the files: generate the rest of the index file 3300# 3301cat $FLIST | while read LINE 3302do 3303 set - $LINE 3304 P=$1 3305 3306 if [[ $# == 2 ]]; then 3307 PP=$2 3308 oldname="$PP" 3309 else 3310 PP=$P 3311 oldname="" 3312 fi 3313 3314 mv_but_nodiff= 3315 cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1 3316 if [[ $? == 0 && -n "$oldname" ]]; then 3317 mv_but_nodiff=1 3318 fi 3319 3320 DIR=${P%/*} 3321 if [[ $DIR == $P ]]; then 3322 DIR="." # File at root of workspace 3323 fi 3324 3325 # Avoid processing the same file twice. 3326 # It's possible for renamed files to 3327 # appear twice in the file list 3328 3329 F=$WDIR/$P 3330 3331 print "<p>" 3332 3333 # If there's a diffs file, make diffs links 3334 3335 if [[ -f $F.cdiff.html ]]; then 3336 cdiff_url="$(print $P.cdiff.html | url_encode)" 3337 udiff_url="$(print $P.udiff.html | url_encode)" 3338 print "<a href=\"$cdiff_url\">Cdiffs</a>" 3339 print "<a href=\"$udiff_url\">Udiffs</a>" 3340 3341 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then 3342 wdiff_url="$(print $P.wdiff.html | url_encode)" 3343 print "<a href=\"$wdiff_url\">Wdiffs</a>" 3344 fi 3345 3346 sdiff_url="$(print $P.sdiff.html | url_encode)" 3347 print "<a href=\"$sdiff_url\">Sdiffs</a>" 3348 3349 frames_url="$(print $P.frames.html | url_encode)" 3350 print "<a href=\"$frames_url\">Frames</a>" 3351 else 3352 print " ------ ------ ------" 3353 3354 if [[ -x $WDIFF ]]; then 3355 print " ------" 3356 fi 3357 3358 print " ------" 3359 fi 3360 3361 # If there's an old file, make the link 3362 3363 if [[ -f $F-.html ]]; then 3364 oldfile_url="$(print $P-.html | url_encode)" 3365 print "<a href=\"$oldfile_url\">Old</a>" 3366 else 3367 print " ---" 3368 fi 3369 3370 # If there's an new file, make the link 3371 3372 if [[ -f $F.html ]]; then 3373 newfile_url="$(print $P.html | url_encode)" 3374 print "<a href=\"$newfile_url\">New</a>" 3375 else 3376 print " ---" 3377 fi 3378 3379 if [[ -f $F.patch ]]; then 3380 patch_url="$(print $P.patch | url_encode)" 3381 print "<a href=\"$patch_url\">Patch</a>" 3382 else 3383 print " -----" 3384 fi 3385 3386 if [[ -f $WDIR/raw_files/new/$P ]]; then 3387 rawfiles_url="$(print raw_files/new/$P | url_encode)" 3388 print "<a href=\"$rawfiles_url\">Raw</a>" 3389 else 3390 print " ---" 3391 fi 3392 3393 print "<b>$P</b>" 3394 3395 # For renamed files, clearly state whether or not they are modified 3396 if [[ -f "$oldname" ]]; then 3397 if [[ -n "$mv_but_nodiff" ]]; then 3398 print "<i>(copied from $oldname)</i>" 3399 else 3400 print "<i>(copied and modified from $oldname)</i>" 3401 fi 3402 elif [[ -n "$oldname" ]]; then 3403 if [[ -n "$mv_but_nodiff" ]]; then 3404 print "<i>(renamed from $oldname)</i>" 3405 else 3406 print "<i>(renamed and modified from $oldname)</i>" 3407 fi 3408 fi 3409 3410 # If there's an old file, but no new file, the file was deleted 3411 if [[ -f $F-.html && ! -f $F.html ]]; then 3412 print " <i>(deleted)</i>" 3413 fi 3414 3415 # 3416 # Check for usr/closed and deleted_files/usr/closed 3417 # 3418 if [ ! -z "$Oflag" ]; then 3419 if [[ $P == usr/closed/* || \ 3420 $P == deleted_files/usr/closed/* ]]; then 3421 print " <i>Closed source: omitted from" \ 3422 "this review</i>" 3423 fi 3424 fi 3425 3426 print "</p>" 3427 # Insert delta comments 3428 3429 print "<blockquote><pre>" 3430 getcomments html $P $PP 3431 print "</pre>" 3432 3433 # Add additional comments comment 3434 3435 print "<!-- Add comments to explain changes in $P here -->" 3436 3437 # Add count of changes. 3438 3439 if [[ -f $F.count ]]; then 3440 cat $F.count 3441 rm $F.count 3442 fi 3443 3444 if [[ $SCM_MODE == "teamware" || 3445 $SCM_MODE == "mercurial" || 3446 $SCM_MODE == "unknown" ]]; then 3447 3448 # Include warnings for important file mode situations: 3449 # 1) New executable files 3450 # 2) Permission changes of any kind 3451 # 3) Existing executable files 3452 3453 old_mode= 3454 if [[ -f $WDIR/raw_files/old/$PP ]]; then 3455 old_mode=`get_file_mode $WDIR/raw_files/old/$PP` 3456 fi 3457 3458 new_mode= 3459 if [[ -f $WDIR/raw_files/new/$P ]]; then 3460 new_mode=`get_file_mode $WDIR/raw_files/new/$P` 3461 fi 3462 3463 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then 3464 print "<span class=\"chmod\">" 3465 print "<p>new executable file: mode $new_mode</p>" 3466 print "</span>" 3467 elif [[ -n "$old_mode" && -n "$new_mode" && 3468 "$old_mode" != "$new_mode" ]]; then 3469 print "<span class=\"chmod\">" 3470 print "<p>mode change: $old_mode to $new_mode</p>" 3471 print "</span>" 3472 elif [[ "$new_mode" = *[1357]* ]]; then 3473 print "<span class=\"chmod\">" 3474 print "<p>executable file: mode $new_mode</p>" 3475 print "</span>" 3476 fi 3477 fi 3478 3479 print "</blockquote>" 3480done 3481 3482print 3483print 3484print "<hr></hr>" 3485print "<p style=\"font-size: small\">" 3486print "This code review page was prepared using <b>$0</b>." 3487print "Webrev is maintained by the <a href=\"http://www.opensolaris.org\">" 3488print "OpenSolaris</a> project. The latest version may be obtained" 3489print "<a href=\"http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>" 3490print "</body>" 3491print "</html>" 3492 3493exec 1<&- # Close FD 1. 3494exec 1<&3 # dup FD 3 to restore stdout. 3495exec 3<&- # close FD 3. 3496 3497print "Done." 3498 3499# 3500# If remote deletion was specified and fails do not continue. 3501# 3502if [[ -n $Dflag ]]; then 3503 delete_webrev 1 1 3504 (( $? == 0 )) || exit $? 3505fi 3506 3507if [[ -n $Uflag ]]; then 3508 upload_webrev 3509 exit $? 3510fi 3511