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