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 chomp $msg; 1830 $files{$fname} .= $msg; 1831 } else { 1832 if ($state == 1) { 1833 $state = 0; 1834 $msg = /^\n/ ? "" : "\n"; 1835 } 1836 $msg .= "$_\n" if ($_); 1837 } 1838 } 1839 close(F); 1840 1841 for (sort keys %files) { 1842 if ($realfiles{$_} ne $_) { 1843 print "$_ $realfiles{$_}\n$files{$_}\n\n"; 1844 } else { 1845 print "$_\n$files{$_}\n\n" 1846 } 1847 }' ${parent} > $TMPFLIST 1848 1849 wxfile=$TMPFLIST 1850} 1851 1852# 1853# flist_from_git 1854# Build a wx-style active list, and hand it off to flist_from_wx 1855# 1856function flist_from_git 1857{ 1858 typeset child=$1 1859 typeset parent=$2 1860 1861 print " File list from: git ...\c" 1862 git_wxfile "$child" "$parent"; 1863 1864 # flist_from_wx prints the Done, so we don't have to. 1865 flist_from_wx $TMPFLIST 1866} 1867 1868# 1869# flist_from_subversion 1870# 1871# Generate the file list by extracting file names from svn status. 1872# 1873function flist_from_subversion 1874{ 1875 CWS=$1 1876 OLDPWD=$2 1877 1878 cd $CWS 1879 print -u2 " File list from: svn status ... \c" 1880 svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST 1881 print -u2 " Done." 1882 cd $OLDPWD 1883} 1884 1885function env_from_flist 1886{ 1887 [[ -r $FLIST ]] || return 1888 1889 # 1890 # Use "eval" to set env variables that are listed in the file 1891 # list. Then copy those into our local versions of those 1892 # variables if they have not been set already. 1893 # 1894 eval `$SED -e "s/#.*$//" $FLIST | $GREP = ` 1895 1896 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then 1897 codemgr_ws=$CODEMGR_WS 1898 export CODEMGR_WS 1899 fi 1900 1901 # 1902 # Check to see if CODEMGR_PARENT is set in the flist file. 1903 # 1904 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then 1905 codemgr_parent=$CODEMGR_PARENT 1906 export CODEMGR_PARENT 1907 fi 1908} 1909 1910function look_for_prog 1911{ 1912 typeset path 1913 typeset ppath 1914 typeset progname=$1 1915 1916 ppath=$PATH 1917 ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin 1918 ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin 1919 ppath=$ppath:/opt/onbld/bin/`uname -p` 1920 1921 PATH=$ppath prog=`whence $progname` 1922 if [[ -n $prog ]]; then 1923 print $prog 1924 fi 1925} 1926 1927function get_file_mode 1928{ 1929 $PERL -e ' 1930 if (@stat = stat($ARGV[0])) { 1931 $mode = $stat[2] & 0777; 1932 printf "%03o\n", $mode; 1933 exit 0; 1934 } else { 1935 exit 1; 1936 } 1937 ' $1 1938} 1939 1940function build_old_new_teamware 1941{ 1942 typeset olddir="$1" 1943 typeset newdir="$2" 1944 1945 # If the child's version doesn't exist then 1946 # get a readonly copy. 1947 1948 if [[ ! -f $CWS/$DIR/$F && -f $CWS/$DIR/SCCS/s.$F ]]; then 1949 $SCCS get -s -p $CWS/$DIR/$F > $CWS/$DIR/$F 1950 fi 1951 1952 # The following two sections propagate file permissions the 1953 # same way SCCS does. If the file is already under version 1954 # control, always use permissions from the SCCS/s.file. If 1955 # the file is not under SCCS control, use permissions from the 1956 # working copy. In all cases, the file copied to the webrev 1957 # is set to read only, and group/other permissions are set to 1958 # match those of the file owner. This way, even if the file 1959 # is currently checked out, the webrev will display the final 1960 # permissions that would result after check in. 1961 1962 # 1963 # Snag new version of file. 1964 # 1965 rm -f $newdir/$DIR/$F 1966 cp $CWS/$DIR/$F $newdir/$DIR/$F 1967 if [[ -f $CWS/$DIR/SCCS/s.$F ]]; then 1968 chmod `get_file_mode $CWS/$DIR/SCCS/s.$F` \ 1969 $newdir/$DIR/$F 1970 fi 1971 chmod u-w,go=u $newdir/$DIR/$F 1972 1973 # 1974 # Get the parent's version of the file. First see whether the 1975 # child's version is checked out and get the parent's version 1976 # with keywords expanded or unexpanded as appropriate. 1977 # 1978 if [[ -f $PWS/$PDIR/$PF && ! -f $PWS/$PDIR/SCCS/s.$PF && \ 1979 ! -f $PWS/$PDIR/SCCS/p.$PF ]]; then 1980 # Parent is not a real workspace, but just a raw 1981 # directory tree - use the file that's there as 1982 # the old file. 1983 1984 rm -f $olddir/$PDIR/$PF 1985 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 1986 else 1987 if [[ -f $PWS/$PDIR/SCCS/s.$PF ]]; then 1988 real_parent=$PWS 1989 else 1990 real_parent=$RWS 1991 fi 1992 1993 rm -f $olddir/$PDIR/$PF 1994 1995 if [[ -f $real_parent/$PDIR/$PF ]]; then 1996 if [ -f $CWS/$DIR/SCCS/p.$F ]; then 1997 $SCCS get -s -p -k $real_parent/$PDIR/$PF > \ 1998 $olddir/$PDIR/$PF 1999 else 2000 $SCCS get -s -p $real_parent/$PDIR/$PF > \ 2001 $olddir/$PDIR/$PF 2002 fi 2003 chmod `get_file_mode $real_parent/$PDIR/SCCS/s.$PF` \ 2004 $olddir/$PDIR/$PF 2005 fi 2006 fi 2007 if [[ -f $olddir/$PDIR/$PF ]]; then 2008 chmod u-w,go=u $olddir/$PDIR/$PF 2009 fi 2010} 2011 2012function build_old_new_mercurial 2013{ 2014 typeset olddir="$1" 2015 typeset newdir="$2" 2016 typeset old_mode= 2017 typeset new_mode= 2018 typeset file 2019 2020 # 2021 # Get old file mode, from the parent revision manifest entry. 2022 # Mercurial only stores a "file is executable" flag, but the 2023 # manifest will display an octal mode "644" or "755". 2024 # 2025 if [[ "$PDIR" == "." ]]; then 2026 file="$PF" 2027 else 2028 file="$PDIR/$PF" 2029 fi 2030 file=`echo $file | $SED 's#/#\\\/#g'` 2031 # match the exact filename, and return only the permission digits 2032 old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \ 2033 < $HG_PARENT_MANIFEST` 2034 2035 # 2036 # Get new file mode, directly from the filesystem. 2037 # Normalize the mode to match Mercurial's behavior. 2038 # 2039 new_mode=`get_file_mode $CWS/$DIR/$F` 2040 if [[ -n "$new_mode" ]]; then 2041 if [[ "$new_mode" = *[1357]* ]]; then 2042 new_mode=755 2043 else 2044 new_mode=644 2045 fi 2046 fi 2047 2048 # 2049 # new version of the file. 2050 # 2051 rm -rf $newdir/$DIR/$F 2052 if [[ -e $CWS/$DIR/$F ]]; then 2053 cp $CWS/$DIR/$F $newdir/$DIR/$F 2054 if [[ -n $new_mode ]]; then 2055 chmod $new_mode $newdir/$DIR/$F 2056 else 2057 # should never happen 2058 print -u2 "ERROR: set mode of $newdir/$DIR/$F" 2059 fi 2060 fi 2061 2062 # 2063 # parent's version of the file 2064 # 2065 # Note that we get this from the last version common to both 2066 # ourselves and the parent. References are via $CWS since we have no 2067 # guarantee that the parent workspace is reachable via the filesystem. 2068 # 2069 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then 2070 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 2071 elif [[ -n $HG_PARENT ]]; then 2072 hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \ 2073 $olddir/$PDIR/$PF 2>/dev/null 2074 2075 if (( $? != 0 )); then 2076 rm -f $olddir/$PDIR/$PF 2077 else 2078 if [[ -n $old_mode ]]; then 2079 chmod $old_mode $olddir/$PDIR/$PF 2080 else 2081 # should never happen 2082 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF" 2083 fi 2084 fi 2085 fi 2086} 2087 2088function build_old_new_git 2089{ 2090 typeset olddir="$1" 2091 typeset newdir="$2" 2092 typeset o_mode= 2093 typeset n_mode= 2094 typeset o_object= 2095 typeset n_object= 2096 typeset OWD=$PWD 2097 typeset file 2098 typeset type 2099 2100 cd $CWS 2101 2102 # 2103 # Get old file and its mode from the git object tree 2104 # 2105 if [[ "$PDIR" == "." ]]; then 2106 file="$PF" 2107 else 2108 file="$PDIR/$PF" 2109 fi 2110 2111 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then 2112 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 2113 else 2114 $GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk 2115 $GIT cat-file $type $o_object > $olddir/$file 2>/dev/null 2116 2117 if (( $? != 0 )); then 2118 rm -f $olddir/$file 2119 elif [[ -n $o_mode ]]; then 2120 # Strip the first 3 digits, to get a regular octal mode 2121 o_mode=${o_mode/???/} 2122 chmod $o_mode $olddir/$file 2123 else 2124 # should never happen 2125 print -u2 "ERROR: set mode of $olddir/$file" 2126 fi 2127 fi 2128 2129 # 2130 # new version of the file. 2131 # 2132 if [[ "$DIR" == "." ]]; then 2133 file="$F" 2134 else 2135 file="$DIR/$F" 2136 fi 2137 rm -rf $newdir/$file 2138 2139 if [[ -e $CWS/$DIR/$F ]]; then 2140 cp $CWS/$DIR/$F $newdir/$DIR/$F 2141 chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F 2142 fi 2143 cd $OWD 2144} 2145 2146function build_old_new_subversion 2147{ 2148 typeset olddir="$1" 2149 typeset newdir="$2" 2150 2151 # Snag new version of file. 2152 rm -f $newdir/$DIR/$F 2153 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F 2154 2155 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then 2156 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 2157 else 2158 # Get the parent's version of the file. 2159 svn status $CWS/$DIR/$F | read stat file 2160 if [[ $stat != "A" ]]; then 2161 svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF 2162 fi 2163 fi 2164} 2165 2166function build_old_new_unknown 2167{ 2168 typeset olddir="$1" 2169 typeset newdir="$2" 2170 2171 # 2172 # Snag new version of file. 2173 # 2174 rm -f $newdir/$DIR/$F 2175 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F 2176 2177 # 2178 # Snag the parent's version of the file. 2179 # 2180 if [[ -f $PWS/$PDIR/$PF ]]; then 2181 rm -f $olddir/$PDIR/$PF 2182 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF 2183 fi 2184} 2185 2186function build_old_new 2187{ 2188 typeset WDIR=$1 2189 typeset PWS=$2 2190 typeset PDIR=$3 2191 typeset PF=$4 2192 typeset CWS=$5 2193 typeset DIR=$6 2194 typeset F=$7 2195 2196 typeset olddir="$WDIR/raw_files/old" 2197 typeset newdir="$WDIR/raw_files/new" 2198 2199 mkdir -p $olddir/$PDIR 2200 mkdir -p $newdir/$DIR 2201 2202 if [[ $SCM_MODE == "teamware" ]]; then 2203 build_old_new_teamware "$olddir" "$newdir" 2204 elif [[ $SCM_MODE == "mercurial" ]]; then 2205 build_old_new_mercurial "$olddir" "$newdir" 2206 elif [[ $SCM_MODE == "git" ]]; then 2207 build_old_new_git "$olddir" "$newdir" 2208 elif [[ $SCM_MODE == "subversion" ]]; then 2209 build_old_new_subversion "$olddir" "$newdir" 2210 elif [[ $SCM_MODE == "unknown" ]]; then 2211 build_old_new_unknown "$olddir" "$newdir" 2212 fi 2213 2214 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then 2215 print "*** Error: file not in parent or child" 2216 return 1 2217 fi 2218 return 0 2219} 2220 2221 2222# 2223# Usage message. 2224# 2225function usage 2226{ 2227 print 'Usage:\twebrev [common-options] 2228 webrev [common-options] ( <file> | - ) 2229 webrev [common-options] -w <wx file> 2230 2231Options: 2232 -C <filename>: Use <filename> for the information tracking configuration. 2233 -D: delete remote webrev 2234 -i <filename>: Include <filename> in the index.html file. 2235 -I <filename>: Use <filename> for the information tracking registry. 2236 -n: do not generate the webrev (useful with -U) 2237 -O: Print bugids/arc cases suitable for OpenSolaris. 2238 -o <outdir>: Output webrev to specified directory. 2239 -p <compare-against>: Use specified parent wkspc or basis for comparison 2240 -t <remote_target>: Specify remote destination for webrev upload 2241 -U: upload the webrev to remote destination 2242 -w <wxfile>: Use specified wx active file. 2243 2244Environment: 2245 WDIR: Control the output directory. 2246 WEBREV_TRASH_DIR: Set directory for webrev delete. 2247 2248SCM Specific Options: 2249 TeamWare: webrev [common-options] -l [arguments to 'putback'] 2250 2251SCM Environment: 2252 CODEMGR_WS: Workspace location. 2253 CODEMGR_PARENT: Parent workspace location. 2254' 2255 2256 exit 2 2257} 2258 2259# 2260# 2261# Main program starts here 2262# 2263# 2264 2265trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15 2266 2267set +o noclobber 2268 2269PATH=$(/bin/dirname "$(whence $0)"):$PATH 2270 2271[[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff` 2272[[ -z $WX ]] && WX=`look_for_prog wx` 2273[[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active` 2274[[ -z $GIT ]] && GIT=`look_for_prog git` 2275[[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm` 2276[[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview` 2277[[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf` 2278[[ -z $PERL ]] && PERL=`look_for_prog perl` 2279[[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync` 2280[[ -z $SCCS ]] && SCCS=`look_for_prog sccs` 2281[[ -z $AWK ]] && AWK=`look_for_prog nawk` 2282[[ -z $AWK ]] && AWK=`look_for_prog gawk` 2283[[ -z $AWK ]] && AWK=`look_for_prog awk` 2284[[ -z $SCP ]] && SCP=`look_for_prog scp` 2285[[ -z $SED ]] && SED=`look_for_prog sed` 2286[[ -z $SFTP ]] && SFTP=`look_for_prog sftp` 2287[[ -z $SORT ]] && SORT=`look_for_prog sort` 2288[[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp` 2289[[ -z $GREP ]] && GREP=`look_for_prog grep` 2290[[ -z $FIND ]] && FIND=`look_for_prog find` 2291 2292# set name of trash directory for remote webrev deletion 2293TRASH_DIR=".trash" 2294[[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR 2295 2296if [[ ! -x $PERL ]]; then 2297 print -u2 "Error: No perl interpreter found. Exiting." 2298 exit 1 2299fi 2300 2301if [[ ! -x $WHICH_SCM ]]; then 2302 print -u2 "Error: Could not find which_scm. Exiting." 2303 exit 1 2304fi 2305 2306# 2307# These aren't fatal, but we want to note them to the user. 2308# We don't warn on the absence of 'wx' until later when we've 2309# determined that we actually need to try to invoke it. 2310# 2311[[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found." 2312[[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found." 2313[[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found." 2314 2315# Declare global total counters. 2316integer TOTL TINS TDEL TMOD TUNC 2317 2318# default remote host for upload/delete 2319typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org" 2320# prefixes for upload targets 2321typeset -r rsync_prefix="rsync://" 2322typeset -r ssh_prefix="ssh://" 2323 2324Cflag= 2325Dflag= 2326flist_mode= 2327flist_file= 2328iflag= 2329Iflag= 2330lflag= 2331Nflag= 2332nflag= 2333Oflag= 2334oflag= 2335pflag= 2336tflag= 2337uflag= 2338Uflag= 2339wflag= 2340remote_target= 2341 2342# 2343# NOTE: when adding/removing options it is necessary to sync the list 2344# with usr/src/tools/onbld/hgext/cdm.py 2345# 2346while getopts "C:Di:I:lnNo:Op:t:Uw" opt 2347do 2348 case $opt in 2349 C) Cflag=1 2350 ITSCONF=$OPTARG;; 2351 2352 D) Dflag=1;; 2353 2354 i) iflag=1 2355 INCLUDE_FILE=$OPTARG;; 2356 2357 I) Iflag=1 2358 ITSREG=$OPTARG;; 2359 2360 # 2361 # If -l has been specified, we need to abort further options 2362 # processing, because subsequent arguments are going to be 2363 # arguments to 'putback -n'. 2364 # 2365 l) lflag=1 2366 break;; 2367 2368 N) Nflag=1;; 2369 2370 n) nflag=1;; 2371 2372 O) Oflag=1;; 2373 2374 o) oflag=1 2375 # Strip the trailing slash to correctly form remote target. 2376 WDIR=${OPTARG%/};; 2377 2378 p) pflag=1 2379 codemgr_parent=$OPTARG;; 2380 2381 t) tflag=1 2382 remote_target=$OPTARG;; 2383 2384 U) Uflag=1;; 2385 2386 w) wflag=1;; 2387 2388 ?) usage;; 2389 esac 2390done 2391 2392FLIST=/tmp/$$.flist 2393 2394if [[ -n $wflag && -n $lflag ]]; then 2395 usage 2396fi 2397 2398# more sanity checking 2399if [[ -n $nflag && -z $Uflag ]]; then 2400 print "it does not make sense to skip webrev generation" \ 2401 "without -U" 2402 exit 1 2403fi 2404 2405if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then 2406 echo "remote target has to be used only for upload or delete" 2407 exit 1 2408fi 2409 2410# 2411# For the invocation "webrev -n -U" with no other options, webrev will assume 2412# that the webrev exists in ${CWS}/webrev, but will upload it using the name 2413# $(basename ${CWS}). So we need to get CWS set before we skip any remaining 2414# logic. 2415# 2416$WHICH_SCM | read SCM_MODE junk || exit 1 2417if [[ $SCM_MODE == "teamware" ]]; then 2418 # 2419 # Teamware priorities: 2420 # 1. CODEMGR_WS from the environment 2421 # 2. workspace name 2422 # 2423 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS 2424 if [[ -n $codemgr_ws && ! -d $codemgr_ws ]]; then 2425 print -u2 "$codemgr_ws: no such workspace" 2426 exit 1 2427 fi 2428 [[ -z $codemgr_ws ]] && codemgr_ws=$(workspace name) 2429 codemgr_ws=$(cd $codemgr_ws;print $PWD) 2430 CODEMGR_WS=$codemgr_ws 2431 CWS=$codemgr_ws 2432elif [[ $SCM_MODE == "mercurial" ]]; then 2433 # 2434 # Mercurial priorities: 2435 # 1. hg root from CODEMGR_WS environment variable 2436 # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under 2437 # usr/closed when we run webrev 2438 # 2. hg root from directory of invocation 2439 # 2440 if [[ ${PWD} =~ "usr/closed" ]]; then 2441 testparent=${CODEMGR_WS}/usr/closed 2442 # If we're in OpenSolaris mode, we enforce a minor policy: 2443 # help to make sure the reviewer doesn't accidentally publish 2444 # source which is under usr/closed 2445 if [[ -n "$Oflag" ]]; then 2446 print -u2 "OpenSolaris output not permitted with" \ 2447 "usr/closed changes" 2448 exit 1 2449 fi 2450 else 2451 testparent=${CODEMGR_WS} 2452 fi 2453 [[ -z $codemgr_ws && -n $testparent ]] && \ 2454 codemgr_ws=$(hg root -R $testparent 2>/dev/null) 2455 [[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null) 2456 CWS=$codemgr_ws 2457elif [[ $SCM_MODE == "git" ]]; then 2458 # 2459 # Git priorities: 2460 # 1. git rev-parse --git-dir from CODEMGR_WS environment variable 2461 # 2. git rev-parse --git-dir from directory of invocation 2462 # 2463 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \ 2464 codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \ 2465 2>/dev/null) 2466 [[ -z $codemgr_ws ]] && \ 2467 codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null) 2468 2469 if [[ "$codemgr_ws" == ".git" ]]; then 2470 codemgr_ws="${PWD}/${codemgr_ws}" 2471 fi 2472 2473 codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git' 2474 CWS="$codemgr_ws" 2475elif [[ $SCM_MODE == "subversion" ]]; then 2476 # 2477 # Subversion priorities: 2478 # 1. CODEMGR_WS from environment 2479 # 2. Relative path from current directory to SVN repository root 2480 # 2481 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then 2482 CWS=$CODEMGR_WS 2483 else 2484 svn info | while read line; do 2485 if [[ $line == "URL: "* ]]; then 2486 url=${line#URL: } 2487 elif [[ $line == "Repository Root: "* ]]; then 2488 repo=${line#Repository Root: } 2489 fi 2490 done 2491 2492 rel=${url#$repo} 2493 CWS=${PWD%$rel} 2494 fi 2495fi 2496 2497# 2498# If no SCM has been determined, take either the environment setting 2499# setting for CODEMGR_WS, or the current directory if that wasn't set. 2500# 2501if [[ -z ${CWS} ]]; then 2502 CWS=${CODEMGR_WS:-.} 2503fi 2504 2505# 2506# If the command line options indicate no webrev generation, either 2507# explicitly (-n) or implicitly (-D but not -U), then there's a whole 2508# ton of logic we can skip. 2509# 2510# Instead of increasing indentation, we intentionally leave this loop 2511# body open here, and exit via break from multiple points within. 2512# Search for DO_EVERYTHING below to find the break points and closure. 2513# 2514for do_everything in 1; do 2515 2516# DO_EVERYTHING: break point 2517if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then 2518 break 2519fi 2520 2521# 2522# If this manually set as the parent, and it appears to be an earlier webrev, 2523# then note that fact and set the parent to the raw_files/new subdirectory. 2524# 2525if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then 2526 parent_webrev=$(readlink -f "$codemgr_parent") 2527 codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new") 2528fi 2529 2530if [[ -z $wflag && -z $lflag ]]; then 2531 shift $(($OPTIND - 1)) 2532 2533 if [[ $1 == "-" ]]; then 2534 cat > $FLIST 2535 flist_mode="stdin" 2536 flist_done=1 2537 shift 2538 elif [[ -n $1 ]]; then 2539 if [[ ! -r $1 ]]; then 2540 print -u2 "$1: no such file or not readable" 2541 usage 2542 fi 2543 cat $1 > $FLIST 2544 flist_mode="file" 2545 flist_file=$1 2546 flist_done=1 2547 shift 2548 else 2549 flist_mode="auto" 2550 fi 2551fi 2552 2553# 2554# Before we go on to further consider -l and -w, work out which SCM we think 2555# is in use. 2556# 2557case "$SCM_MODE" in 2558teamware|mercurial|git|subversion) 2559 ;; 2560unknown) 2561 if [[ $flist_mode == "auto" ]]; then 2562 print -u2 "Unable to determine SCM in use and file list not specified" 2563 print -u2 "See which_scm(1) for SCM detection information." 2564 exit 1 2565 fi 2566 ;; 2567*) 2568 if [[ $flist_mode == "auto" ]]; then 2569 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified" 2570 exit 1 2571 fi 2572 ;; 2573esac 2574 2575print -u2 " SCM detected: $SCM_MODE" 2576 2577if [[ -n $lflag ]]; then 2578 # 2579 # If the -l flag is given instead of the name of a file list, 2580 # then generate the file list by extracting file names from a 2581 # putback -n. 2582 # 2583 shift $(($OPTIND - 1)) 2584 if [[ $SCM_MODE == "teamware" ]]; then 2585 flist_from_teamware "$*" 2586 else 2587 print -u2 -- "Error: -l option only applies to TeamWare" 2588 exit 1 2589 fi 2590 flist_done=1 2591 shift $# 2592elif [[ -n $wflag ]]; then 2593 # 2594 # If the -w is given then assume the file list is in Bonwick's "wx" 2595 # command format, i.e. pathname lines alternating with SCCS comment 2596 # lines with blank lines as separators. Use the SCCS comments later 2597 # in building the index.html file. 2598 # 2599 shift $(($OPTIND - 1)) 2600 wxfile=$1 2601 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then 2602 if [[ -r $CODEMGR_WS/wx/active ]]; then 2603 wxfile=$CODEMGR_WS/wx/active 2604 fi 2605 fi 2606 2607 [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \ 2608 "be auto-detected (check \$CODEMGR_WS)" && exit 1 2609 2610 if [[ ! -r $wxfile ]]; then 2611 print -u2 "$wxfile: no such file or not readable" 2612 usage 2613 fi 2614 2615 print -u2 " File list from: wx 'active' file '$wxfile' ... \c" 2616 flist_from_wx $wxfile 2617 flist_done=1 2618 if [[ -n "$*" ]]; then 2619 shift 2620 fi 2621elif [[ $flist_mode == "stdin" ]]; then 2622 print -u2 " File list from: standard input" 2623elif [[ $flist_mode == "file" ]]; then 2624 print -u2 " File list from: $flist_file" 2625fi 2626 2627if [[ $# -gt 0 ]]; then 2628 print -u2 "WARNING: unused arguments: $*" 2629fi 2630 2631# 2632# Before we entered the DO_EVERYTHING loop, we should have already set CWS 2633# and CODEMGR_WS as needed. Here, we set the parent workspace. 2634# 2635 2636if [[ $SCM_MODE == "teamware" ]]; then 2637 2638 # 2639 # Teamware priorities: 2640 # 2641 # 1) via -p command line option 2642 # 2) in the user environment 2643 # 3) in the flist 2644 # 4) automatically based on the workspace 2645 # 2646 2647 # 2648 # For 1, codemgr_parent will already be set. Here's 2: 2649 # 2650 [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \ 2651 codemgr_parent=$CODEMGR_PARENT 2652 if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then 2653 print -u2 "$codemgr_parent: no such directory" 2654 exit 1 2655 fi 2656 2657 # 2658 # If we're in auto-detect mode and we haven't already gotten the file 2659 # list, then see if we can get it by probing for wx. 2660 # 2661 if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then 2662 if [[ ! -x $WX ]]; then 2663 print -u2 "WARNING: wx not found!" 2664 fi 2665 2666 # 2667 # We need to use wx list -w so that we get renamed files, etc. 2668 # but only if a wx active file exists-- otherwise wx will 2669 # hang asking us to initialize our wx information. 2670 # 2671 if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then 2672 print -u2 " File list from: 'wx list -w' ... \c" 2673 $WX list -w > $FLIST 2674 $WX comments > /tmp/$$.wx_comments 2675 wxfile=/tmp/$$.wx_comments 2676 print -u2 "done" 2677 flist_done=1 2678 fi 2679 fi 2680 2681 # 2682 # If by hook or by crook we've gotten a file list by now (perhaps 2683 # from the command line), eval it to extract environment variables from 2684 # it: This is method 3 for finding the parent. 2685 # 2686 if [[ -z $flist_done ]]; then 2687 flist_from_teamware 2688 fi 2689 env_from_flist 2690 2691 # 2692 # (4) If we still don't have a value for codemgr_parent, get it 2693 # from workspace. 2694 # 2695 [[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent` 2696 if [[ ! -d $codemgr_parent ]]; then 2697 print -u2 "$CODEMGR_PARENT: no such parent workspace" 2698 exit 1 2699 fi 2700 2701 PWS=$codemgr_parent 2702 2703 [[ -n $parent_webrev ]] && RWS=$(workspace parent $CWS) 2704 2705elif [[ $SCM_MODE == "mercurial" ]]; then 2706 # 2707 # Parent can either be specified with -p 2708 # Specified with CODEMGR_PARENT in the environment 2709 # or taken from hg's default path. 2710 # 2711 2712 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then 2713 codemgr_parent=$CODEMGR_PARENT 2714 fi 2715 2716 if [[ -z $codemgr_parent ]]; then 2717 codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null` 2718 fi 2719 2720 PWS=$codemgr_parent 2721 2722 # 2723 # If the parent is a webrev, we want to do some things against 2724 # the natural workspace parent (file list, comments, etc) 2725 # 2726 if [[ -n $parent_webrev ]]; then 2727 real_parent=$(hg path -R $codemgr_ws default 2>/dev/null) 2728 else 2729 real_parent=$PWS 2730 fi 2731 2732 # 2733 # If hg-active exists, then we run it. In the case of no explicit 2734 # flist given, we'll use it for our comments. In the case of an 2735 # explicit flist given we'll try to use it for comments for any 2736 # files mentioned in the flist. 2737 # 2738 if [[ -z $flist_done ]]; then 2739 flist_from_mercurial $CWS $real_parent 2740 flist_done=1 2741 fi 2742 2743 # 2744 # If we have a file list now, pull out any variables set 2745 # therein. We do this now (rather than when we possibly use 2746 # hg-active to find comments) to avoid stomping specifications 2747 # in the user-specified flist. 2748 # 2749 if [[ -n $flist_done ]]; then 2750 env_from_flist 2751 fi 2752 2753 # 2754 # Only call hg-active if we don't have a wx formatted file already 2755 # 2756 if [[ -x $HG_ACTIVE && -z $wxfile ]]; then 2757 print " Comments from: hg-active -p $real_parent ...\c" 2758 hg_active_wxfile $CWS $real_parent 2759 print " Done." 2760 fi 2761 2762 # 2763 # At this point we must have a wx flist either from hg-active, 2764 # or in general. Use it to try and find our parent revision, 2765 # if we don't have one. 2766 # 2767 if [[ -z $HG_PARENT ]]; then 2768 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=` 2769 fi 2770 2771 # 2772 # If we still don't have a parent, we must have been given a 2773 # wx-style active list with no HG_PARENT specification, run 2774 # hg-active and pull an HG_PARENT out of it, ignore the rest. 2775 # 2776 if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then 2777 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \ 2778 eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=` 2779 elif [[ -z $HG_PARENT ]]; then 2780 print -u2 "Error: Cannot discover parent revision" 2781 exit 1 2782 fi 2783 2784 pnode=$(trim_digest $HG_PARENT) 2785 PRETTY_PWS="${PWS} (at ${pnode})" 2786 cnode=$(hg parent -R $codemgr_ws --template '{node|short}' \ 2787 2>/dev/null) 2788 PRETTY_CWS="${CWS} (at ${cnode})"} 2789elif [[ $SCM_MODE == "git" ]]; then 2790 # 2791 # Parent can either be specified with -p, or specified with 2792 # CODEMGR_PARENT in the environment. 2793 # 2794 2795 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then 2796 codemgr_parent=$CODEMGR_PARENT 2797 fi 2798 2799 # Try to figure out the parent based on the branch the current 2800 # branch is tracking, if we fail, use origin/master 2801 this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }') 2802 par_branch="origin/master" 2803 2804 # If we're not on a branch there's nothing we can do 2805 if [[ $this_branch != "(no branch)" ]]; then 2806 $GIT for-each-ref \ 2807 --format='%(refname:short) %(upstream:short)' refs/heads/ | \ 2808 while read local remote; do \ 2809 [[ "$local" == "$this_branch" ]] && par_branch="$remote"; \ 2810 done 2811 fi 2812 2813 if [[ -z $codemgr_parent ]]; then 2814 codemgr_parent=$par_branch 2815 fi 2816 PWS=$codemgr_parent 2817 2818 # 2819 # If the parent is a webrev, we want to do some things against 2820 # the natural workspace parent (file list, comments, etc) 2821 # 2822 if [[ -n $parent_webrev ]]; then 2823 real_parent=$par_branch 2824 else 2825 real_parent=$PWS 2826 fi 2827 2828 if [[ -z $flist_done ]]; then 2829 flist_from_git "$CWS" "$real_parent" 2830 flist_done=1 2831 fi 2832 2833 # 2834 # If we have a file list now, pull out any variables set 2835 # therein. 2836 # 2837 if [[ -n $flist_done ]]; then 2838 env_from_flist 2839 fi 2840 2841 # 2842 # If we don't have a wx-format file list, build one we can pull change 2843 # comments from. 2844 # 2845 if [[ -z $wxfile ]]; then 2846 print " Comments from: git...\c" 2847 git_wxfile "$CWS" "$real_parent" 2848 print " Done." 2849 fi 2850 2851 if [[ -z $GIT_PARENT ]]; then 2852 GIT_PARENT=$($GIT merge-base "$real_parent" HEAD) 2853 fi 2854 if [[ -z $GIT_PARENT ]]; then 2855 print -u2 "Error: Cannot discover parent revision" 2856 exit 1 2857 fi 2858 2859 pnode=$(trim_digest $GIT_PARENT) 2860 2861 if [[ $real_parent == */* ]]; then 2862 origin=$(echo $real_parent | cut -d/ -f1) 2863 origin=$($GIT remote -v | \ 2864 $AWK '$1 == "'$origin'" { print $2; exit }') 2865 PRETTY_PWS="${PWS} (${origin} at ${pnode})" 2866 else 2867 PRETTY_PWS="${PWS} (at ${pnode})" 2868 fi 2869 2870 cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 HEAD \ 2871 2>/dev/null) 2872 PRETTY_CWS="${CWS} (at ${cnode})" 2873elif [[ $SCM_MODE == "subversion" ]]; then 2874 2875 # 2876 # We only will have a real parent workspace in the case one 2877 # was specified (be it an older webrev, or another checkout). 2878 # 2879 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent 2880 2881 if [[ -z $flist_done && $flist_mode == "auto" ]]; then 2882 flist_from_subversion $CWS $OLDPWD 2883 fi 2884else 2885 if [[ $SCM_MODE == "unknown" ]]; then 2886 print -u2 " Unknown type of SCM in use" 2887 else 2888 print -u2 " Unsupported SCM in use: $SCM_MODE" 2889 fi 2890 2891 env_from_flist 2892 2893 if [[ -z $CODEMGR_WS ]]; then 2894 print -u2 "SCM not detected/supported and CODEMGR_WS not specified" 2895 exit 1 2896 fi 2897 2898 if [[ -z $CODEMGR_PARENT ]]; then 2899 print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified" 2900 exit 1 2901 fi 2902 2903 CWS=$CODEMGR_WS 2904 PWS=$CODEMGR_PARENT 2905fi 2906 2907# 2908# If the user didn't specify a -i option, check to see if there is a 2909# webrev-info file in the workspace directory. 2910# 2911if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then 2912 iflag=1 2913 INCLUDE_FILE="$CWS/webrev-info" 2914fi 2915 2916if [[ -n $iflag ]]; then 2917 if [[ ! -r $INCLUDE_FILE ]]; then 2918 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \ 2919 "not readable." 2920 exit 1 2921 else 2922 # 2923 # $INCLUDE_FILE may be a relative path, and the script alters 2924 # PWD, so we just stash a copy in /tmp. 2925 # 2926 cp $INCLUDE_FILE /tmp/$$.include 2927 fi 2928fi 2929 2930# DO_EVERYTHING: break point 2931if [[ -n $Nflag ]]; then 2932 break 2933fi 2934 2935typeset -A itsinfo 2936typeset -r its_sed_script=/tmp/$$.its_sed 2937valid_prefixes= 2938if [[ -z $nflag ]]; then 2939 DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg" 2940 if [[ -n $Iflag ]]; then 2941 REGFILE=$ITSREG 2942 elif [[ -r $HOME/.its.reg ]]; then 2943 REGFILE=$HOME/.its.reg 2944 else 2945 REGFILE=$DEFREGFILE 2946 fi 2947 if [[ ! -r $REGFILE ]]; then 2948 print "ERROR: Unable to read database registry file $REGFILE" 2949 exit 1 2950 elif [[ $REGFILE != $DEFREGFILE ]]; then 2951 print " its.reg from: $REGFILE" 2952 fi 2953 2954 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do 2955 2956 name=${LINE%%=*} 2957 value="${LINE#*=}" 2958 2959 if [[ $name == PREFIX ]]; then 2960 p=${value} 2961 valid_prefixes="${p} ${valid_prefixes}" 2962 else 2963 itsinfo["${p}_${name}"]="${value}" 2964 fi 2965 done 2966 2967 2968 DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf" 2969 CONFFILES=$DEFCONFFILE 2970 if [[ -r $HOME/.its.conf ]]; then 2971 CONFFILES="${CONFFILES} $HOME/.its.conf" 2972 fi 2973 if [[ -n $Cflag ]]; then 2974 CONFFILES="${CONFFILES} ${ITSCONF}" 2975 fi 2976 its_domain= 2977 its_priority= 2978 for cf in ${CONFFILES}; do 2979 if [[ ! -r $cf ]]; then 2980 print "ERROR: Unable to read database configuration file $cf" 2981 exit 1 2982 elif [[ $cf != $DEFCONFFILE ]]; then 2983 print " its.conf: reading $cf" 2984 fi 2985 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do 2986 eval "${LINE}" 2987 done 2988 done 2989 2990 # 2991 # If an information tracking system is explicitly identified by prefix, 2992 # we want to disregard the specified priorities and resolve it accordingly. 2993 # 2994 # To that end, we'll build a sed script to do each valid prefix in turn. 2995 # 2996 for p in ${valid_prefixes}; do 2997 # 2998 # When an informational URL was provided, translate it to a 2999 # hyperlink. When omitted, simply use the prefix text. 3000 # 3001 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then 3002 itsinfo["${p}_INFO"]=${p} 3003 else 3004 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>" 3005 fi 3006 3007 # 3008 # Assume that, for this invocation of webrev, all references 3009 # to this information tracking system should resolve through 3010 # the same URL. 3011 # 3012 # If the caller specified -O, then always use EXTERNAL_URL. 3013 # 3014 # Otherwise, look in the list of domains for a matching 3015 # INTERNAL_URL. 3016 # 3017 [[ -z $Oflag ]] && for d in ${its_domain}; do 3018 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then 3019 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}" 3020 break 3021 fi 3022 done 3023 if [[ -z ${itsinfo["${p}_URL"]} ]]; then 3024 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}" 3025 fi 3026 3027 # 3028 # Turn the destination URL into a hyperlink 3029 # 3030 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>" 3031 3032 # The character class below contains a literal tab 3033 print "/^${p}[: ]/ { 3034 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g 3035 s;^${p};${itsinfo[${p}_INFO]}; 3036 }" >> ${its_sed_script} 3037 done 3038 3039 # 3040 # The previous loop took care of explicit specification. Now use 3041 # the configured priorities to attempt implicit translations. 3042 # 3043 for p in ${its_priority}; do 3044 print "/^${itsinfo[${p}_REGEX]}[ ]/ { 3045 s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g 3046 }" >> ${its_sed_script} 3047 done 3048fi 3049 3050# 3051# Search for DO_EVERYTHING above for matching "for" statement 3052# and explanation of this terminator. 3053# 3054done 3055 3056# 3057# Output directory. 3058# 3059WDIR=${WDIR:-$CWS/webrev} 3060 3061# 3062# Name of the webrev, derived from the workspace name or output directory; 3063# in the future this could potentially be an option. 3064# 3065if [[ -n $oflag ]]; then 3066 WNAME=${WDIR##*/} 3067else 3068 WNAME=${CWS##*/} 3069fi 3070 3071# Make sure remote target is well formed for remote upload/delete. 3072if [[ -n $Dflag || -n $Uflag ]]; then 3073 # 3074 # If remote target is not specified, build it from scratch using 3075 # the default values. 3076 # 3077 if [[ -z $tflag ]]; then 3078 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME} 3079 else 3080 # 3081 # Check upload target prefix first. 3082 # 3083 if [[ "${remote_target}" != ${rsync_prefix}* && 3084 "${remote_target}" != ${ssh_prefix}* ]]; then 3085 print "ERROR: invalid prefix of upload URI" \ 3086 "($remote_target)" 3087 exit 1 3088 fi 3089 # 3090 # If destination specification is not in the form of 3091 # host_spec:remote_dir then assume it is just remote hostname 3092 # and append a colon and destination directory formed from 3093 # local webrev directory name. 3094 # 3095 typeset target_no_prefix=${remote_target##*://} 3096 if [[ ${target_no_prefix} == *:* ]]; then 3097 if [[ "${remote_target}" == *: ]]; then 3098 remote_target=${remote_target}${WNAME} 3099 fi 3100 else 3101 if [[ ${target_no_prefix} == */* ]]; then 3102 print "ERROR: badly formed upload URI" \ 3103 "($remote_target)" 3104 exit 1 3105 else 3106 remote_target=${remote_target}:${WNAME} 3107 fi 3108 fi 3109 fi 3110 3111 # 3112 # Strip trailing slash. Each upload method will deal with directory 3113 # specification separately. 3114 # 3115 remote_target=${remote_target%/} 3116fi 3117 3118# 3119# Option -D by itself (option -U not present) implies no webrev generation. 3120# 3121if [[ -z $Uflag && -n $Dflag ]]; then 3122 delete_webrev 1 1 3123 exit $? 3124fi 3125 3126# 3127# Do not generate the webrev, just upload it or delete it. 3128# 3129if [[ -n $nflag ]]; then 3130 if [[ -n $Dflag ]]; then 3131 delete_webrev 1 1 3132 (( $? == 0 )) || exit $? 3133 fi 3134 if [[ -n $Uflag ]]; then 3135 upload_webrev 3136 exit $? 3137 fi 3138fi 3139 3140if [ "${WDIR%%/*}" ]; then 3141 WDIR=$PWD/$WDIR 3142fi 3143 3144if [[ ! -d $WDIR ]]; then 3145 mkdir -p $WDIR 3146 (( $? != 0 )) && exit 1 3147fi 3148 3149# 3150# Summarize what we're going to do. 3151# 3152print " Workspace: ${PRETTY_CWS:-$CWS}" 3153if [[ -n $parent_webrev ]]; then 3154 print "Compare against: webrev at $parent_webrev" 3155else 3156 print "Compare against: ${PRETTY_PWS:-$PWS}" 3157fi 3158 3159[[ -n $INCLUDE_FILE ]] && print " Including: $INCLUDE_FILE" 3160print " Output to: $WDIR" 3161 3162# 3163# Save the file list in the webrev dir 3164# 3165[[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list 3166 3167rm -f $WDIR/$WNAME.patch 3168rm -f $WDIR/$WNAME.ps 3169rm -f $WDIR/$WNAME.pdf 3170 3171touch $WDIR/$WNAME.patch 3172 3173print " Output Files:" 3174 3175# 3176# Clean up the file list: Remove comments, blank lines and env variables. 3177# 3178$SED -e "s/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean 3179FLIST=/tmp/$$.flist.clean 3180 3181# 3182# For Mercurial, create a cache of manifest entries. 3183# 3184if [[ $SCM_MODE == "mercurial" ]]; then 3185 # 3186 # Transform the FLIST into a temporary sed script that matches 3187 # relevant entries in the Mercurial manifest as follows: 3188 # 1) The script will be used against the parent revision manifest, 3189 # so for FLIST lines that have two filenames (a renamed file) 3190 # keep only the old name. 3191 # 2) Escape all forward slashes the filename. 3192 # 3) Change the filename into another sed command that matches 3193 # that file in "hg manifest -v" output: start of line, three 3194 # octal digits for file permissions, space, a file type flag 3195 # character, space, the filename, end of line. 3196 # 4) Eliminate any duplicate entries. (This can occur if a 3197 # file has been used as the source of an hg cp and it's 3198 # also been modified in the same changeset.) 3199 # 3200 SEDFILE=/tmp/$$.manifest.sed 3201 $SED ' 3202 s#^[^ ]* ## 3203 s#/#\\\/#g 3204 s#^.*$#/^... . &$/p# 3205 ' < $FLIST | $SORT -u > $SEDFILE 3206 3207 # 3208 # Apply the generated script to the output of "hg manifest -v" 3209 # to get the relevant subset for this webrev. 3210 # 3211 HG_PARENT_MANIFEST=/tmp/$$.manifest 3212 hg -R $CWS manifest -v -r $HG_PARENT | 3213 $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST 3214fi 3215 3216# 3217# First pass through the files: generate the per-file webrev HTML-files. 3218# 3219cat $FLIST | while read LINE 3220do 3221 set - $LINE 3222 P=$1 3223 3224 # 3225 # Normally, each line in the file list is just a pathname of a 3226 # file that has been modified or created in the child. A file 3227 # that is renamed in the child workspace has two names on the 3228 # line: new name followed by the old name. 3229 # 3230 oldname="" 3231 oldpath="" 3232 rename= 3233 if [[ $# -eq 2 ]]; then 3234 PP=$2 # old filename 3235 if [[ -f $PP ]]; then 3236 oldname=" (copied from $PP)" 3237 else 3238 oldname=" (renamed from $PP)" 3239 fi 3240 oldpath="$PP" 3241 rename=1 3242 PDIR=${PP%/*} 3243 if [[ $PDIR == $PP ]]; then 3244 PDIR="." # File at root of workspace 3245 fi 3246 3247 PF=${PP##*/} 3248 3249 DIR=${P%/*} 3250 if [[ $DIR == $P ]]; then 3251 DIR="." # File at root of workspace 3252 fi 3253 3254 F=${P##*/} 3255 3256 else 3257 DIR=${P%/*} 3258 if [[ "$DIR" == "$P" ]]; then 3259 DIR="." # File at root of workspace 3260 fi 3261 3262 F=${P##*/} 3263 3264 PP=$P 3265 PDIR=$DIR 3266 PF=$F 3267 fi 3268 3269 COMM=`getcomments html $P $PP` 3270 3271 print "\t$P$oldname\n\t\t\c" 3272 3273 # Make the webrev mirror directory if necessary 3274 mkdir -p $WDIR/$DIR 3275 3276 # 3277 # We stash old and new files into parallel directories in $WDIR 3278 # and do our diffs there. This makes it possible to generate 3279 # clean looking diffs which don't have absolute paths present. 3280 # 3281 3282 build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \ 3283 continue 3284 3285 # 3286 # Keep the old PWD around, so we can safely switch back after 3287 # diff generation, such that build_old_new runs in a 3288 # consistent environment. 3289 # 3290 OWD=$PWD 3291 cd $WDIR/raw_files 3292 ofile=old/$PDIR/$PF 3293 nfile=new/$DIR/$F 3294 3295 mv_but_nodiff= 3296 cmp $ofile $nfile > /dev/null 2>&1 3297 if [[ $? == 0 && $rename == 1 ]]; then 3298 mv_but_nodiff=1 3299 fi 3300 3301 # 3302 # If we have old and new versions of the file then run the appropriate 3303 # diffs. This is complicated by a couple of factors: 3304 # 3305 # - renames must be handled specially: we emit a 'remove' 3306 # diff and an 'add' diff 3307 # - new files and deleted files must be handled specially 3308 # - Solaris patch(1m) can't cope with file creation 3309 # (and hence renames) as of this writing. 3310 # - To make matters worse, gnu patch doesn't interpret the 3311 # output of Solaris diff properly when it comes to 3312 # adds and deletes. We need to do some "cleansing" 3313 # transformations: 3314 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@ 3315 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@ 3316 # 3317 cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'" 3318 cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'" 3319 3320 rm -f $WDIR/$DIR/$F.patch 3321 if [[ -z $rename ]]; then 3322 if [ ! -f "$ofile" ]; then 3323 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \ 3324 > $WDIR/$DIR/$F.patch 3325 elif [ ! -f "$nfile" ]; then 3326 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \ 3327 > $WDIR/$DIR/$F.patch 3328 else 3329 diff -u $ofile $nfile > $WDIR/$DIR/$F.patch 3330 fi 3331 else 3332 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \ 3333 > $WDIR/$DIR/$F.patch 3334 3335 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \ 3336 >> $WDIR/$DIR/$F.patch 3337 fi 3338 3339 # 3340 # Tack the patch we just made onto the accumulated patch for the 3341 # whole wad. 3342 # 3343 cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch 3344 3345 print " patch\c" 3346 3347 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then 3348 3349 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff 3350 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \ 3351 > $WDIR/$DIR/$F.cdiff.html 3352 print " cdiffs\c" 3353 3354 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff 3355 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \ 3356 > $WDIR/$DIR/$F.udiff.html 3357 3358 print " udiffs\c" 3359 3360 if [[ -x $WDIFF ]]; then 3361 $WDIFF -c "$COMM" \ 3362 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \ 3363 $WDIR/$DIR/$F.wdiff.html 2>/dev/null 3364 if [[ $? -eq 0 ]]; then 3365 print " wdiffs\c" 3366 else 3367 print " wdiffs[fail]\c" 3368 fi 3369 fi 3370 3371 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \ 3372 > $WDIR/$DIR/$F.sdiff.html 3373 print " sdiffs\c" 3374 3375 print " frames\c" 3376 3377 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff 3378 3379 difflines $ofile $nfile > $WDIR/$DIR/$F.count 3380 3381 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then 3382 # renamed file: may also have differences 3383 difflines $ofile $nfile > $WDIR/$DIR/$F.count 3384 elif [[ -f $nfile ]]; then 3385 # new file: count added lines 3386 difflines /dev/null $nfile > $WDIR/$DIR/$F.count 3387 elif [[ -f $ofile ]]; then 3388 # old file: count deleted lines 3389 difflines $ofile /dev/null > $WDIR/$DIR/$F.count 3390 fi 3391 3392 # 3393 # Now we generate the postscript for this file. We generate diffs 3394 # only in the event that there is delta, or the file is new (it seems 3395 # tree-killing to print out the contents of deleted files). 3396 # 3397 if [[ -f $nfile ]]; then 3398 ocr=$ofile 3399 [[ ! -f $ofile ]] && ocr=/dev/null 3400 3401 if [[ -z $mv_but_nodiff ]]; then 3402 textcomm=`getcomments text $P $PP` 3403 if [[ -x $CODEREVIEW ]]; then 3404 $CODEREVIEW -y "$textcomm" \ 3405 -e $ocr $nfile \ 3406 > /tmp/$$.psfile 2>/dev/null && 3407 cat /tmp/$$.psfile >> $WDIR/$WNAME.ps 3408 if [[ $? -eq 0 ]]; then 3409 print " ps\c" 3410 else 3411 print " ps[fail]\c" 3412 fi 3413 fi 3414 fi 3415 fi 3416 3417 if [[ -f $ofile ]]; then 3418 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html 3419 print " old\c" 3420 fi 3421 3422 if [[ -f $nfile ]]; then 3423 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html 3424 print " new\c" 3425 fi 3426 3427 cd $OWD 3428 3429 print 3430done 3431 3432frame_nav_js > $WDIR/ancnav.js 3433frame_navigation > $WDIR/ancnav.html 3434 3435if [[ ! -f $WDIR/$WNAME.ps ]]; then 3436 print " Generating PDF: Skipped: no output available" 3437elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then 3438 print " Generating PDF: \c" 3439 fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf 3440 print "Done." 3441else 3442 print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'" 3443fi 3444 3445# If we're in OpenSolaris mode and there's a closed dir under $WDIR, 3446# delete it - prevent accidental publishing of closed source 3447 3448if [[ -n "$Oflag" ]]; then 3449 $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \; 3450fi 3451 3452# Now build the index.html file that contains 3453# links to the source files and their diffs. 3454 3455cd $CWS 3456 3457# Save total changed lines for Code Inspection. 3458print "$TOTL" > $WDIR/TotalChangedLines 3459 3460print " index.html: \c" 3461INDEXFILE=$WDIR/index.html 3462exec 3<&1 # duplicate stdout to FD3. 3463exec 1<&- # Close stdout. 3464exec > $INDEXFILE # Open stdout to index file. 3465 3466print "$HTML<head>$STDHEAD" 3467print "<title>$WNAME</title>" 3468print "</head>" 3469print "<body id=\"SUNWwebrev\">" 3470print "<div class=\"summary\">" 3471print "<h2>Code Review for $WNAME</h2>" 3472 3473print "<table>" 3474 3475# 3476# Get the preparer's name: 3477# 3478# If the SCM detected is Mercurial, and the configuration property 3479# ui.username is available, use that, but be careful to properly escape 3480# angle brackets (HTML syntax characters) in the email address. 3481# 3482# Otherwise, use the current userid in the form "John Doe (jdoe)", but 3483# to maintain compatibility with passwd(4), we must support '&' substitutions. 3484# 3485preparer= 3486if [[ "$SCM_MODE" == mercurial ]]; then 3487 preparer=`hg showconfig ui.username 2>/dev/null` 3488 if [[ -n "$preparer" ]]; then 3489 preparer="$(echo "$preparer" | html_quote)" 3490 fi 3491fi 3492if [[ -z "$preparer" ]]; then 3493 preparer=$( 3494 $PERL -e ' 3495 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<); 3496 if ($login) { 3497 $gcos =~ s/\&/ucfirst($login)/e; 3498 printf "%s (%s)\n", $gcos, $login; 3499 } else { 3500 printf "(unknown)\n"; 3501 } 3502 ') 3503fi 3504 3505PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z) 3506print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>" 3507print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}" 3508print "</td></tr>" 3509print "<tr><th>Compare against:</th><td>" 3510if [[ -n $parent_webrev ]]; then 3511 print "webrev at $parent_webrev" 3512else 3513 print "${PRETTY_PWS:-$PWS}" 3514fi 3515print "</td></tr>" 3516print "<tr><th>Summary of changes:</th><td>" 3517printCI $TOTL $TINS $TDEL $TMOD $TUNC 3518print "</td></tr>" 3519 3520if [[ -f $WDIR/$WNAME.patch ]]; then 3521 wpatch_url="$(print $WNAME.patch | url_encode)" 3522 print "<tr><th>Patch of changes:</th><td>" 3523 print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>" 3524fi 3525if [[ -f $WDIR/$WNAME.pdf ]]; then 3526 wpdf_url="$(print $WNAME.pdf | url_encode)" 3527 print "<tr><th>Printable review:</th><td>" 3528 print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>" 3529fi 3530 3531if [[ -n "$iflag" ]]; then 3532 print "<tr><th>Author comments:</th><td><div>" 3533 cat /tmp/$$.include 3534 print "</div></td></tr>" 3535fi 3536print "</table>" 3537print "</div>" 3538 3539# 3540# Second pass through the files: generate the rest of the index file 3541# 3542cat $FLIST | while read LINE 3543do 3544 set - $LINE 3545 P=$1 3546 3547 if [[ $# == 2 ]]; then 3548 PP=$2 3549 oldname="$PP" 3550 else 3551 PP=$P 3552 oldname="" 3553 fi 3554 3555 mv_but_nodiff= 3556 cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1 3557 if [[ $? == 0 && -n "$oldname" ]]; then 3558 mv_but_nodiff=1 3559 fi 3560 3561 DIR=${P%/*} 3562 if [[ $DIR == $P ]]; then 3563 DIR="." # File at root of workspace 3564 fi 3565 3566 # Avoid processing the same file twice. 3567 # It's possible for renamed files to 3568 # appear twice in the file list 3569 3570 F=$WDIR/$P 3571 3572 print "<p>" 3573 3574 # If there's a diffs file, make diffs links 3575 3576 if [[ -f $F.cdiff.html ]]; then 3577 cdiff_url="$(print $P.cdiff.html | url_encode)" 3578 udiff_url="$(print $P.udiff.html | url_encode)" 3579 print "<a href=\"$cdiff_url\">Cdiffs</a>" 3580 print "<a href=\"$udiff_url\">Udiffs</a>" 3581 3582 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then 3583 wdiff_url="$(print $P.wdiff.html | url_encode)" 3584 print "<a href=\"$wdiff_url\">Wdiffs</a>" 3585 fi 3586 3587 sdiff_url="$(print $P.sdiff.html | url_encode)" 3588 print "<a href=\"$sdiff_url\">Sdiffs</a>" 3589 3590 frames_url="$(print $P.frames.html | url_encode)" 3591 print "<a href=\"$frames_url\">Frames</a>" 3592 else 3593 print " ------ ------ ------" 3594 3595 if [[ -x $WDIFF ]]; then 3596 print " ------" 3597 fi 3598 3599 print " ------" 3600 fi 3601 3602 # If there's an old file, make the link 3603 3604 if [[ -f $F-.html ]]; then 3605 oldfile_url="$(print $P-.html | url_encode)" 3606 print "<a href=\"$oldfile_url\">Old</a>" 3607 else 3608 print " ---" 3609 fi 3610 3611 # If there's an new file, make the link 3612 3613 if [[ -f $F.html ]]; then 3614 newfile_url="$(print $P.html | url_encode)" 3615 print "<a href=\"$newfile_url\">New</a>" 3616 else 3617 print " ---" 3618 fi 3619 3620 if [[ -f $F.patch ]]; then 3621 patch_url="$(print $P.patch | url_encode)" 3622 print "<a href=\"$patch_url\">Patch</a>" 3623 else 3624 print " -----" 3625 fi 3626 3627 if [[ -f $WDIR/raw_files/new/$P ]]; then 3628 rawfiles_url="$(print raw_files/new/$P | url_encode)" 3629 print "<a href=\"$rawfiles_url\">Raw</a>" 3630 else 3631 print " ---" 3632 fi 3633 3634 print "<b>$P</b>" 3635 3636 # For renamed files, clearly state whether or not they are modified 3637 if [[ -f "$oldname" ]]; then 3638 if [[ -n "$mv_but_nodiff" ]]; then 3639 print "<i>(copied from $oldname)</i>" 3640 else 3641 print "<i>(copied and modified from $oldname)</i>" 3642 fi 3643 elif [[ -n "$oldname" ]]; then 3644 if [[ -n "$mv_but_nodiff" ]]; then 3645 print "<i>(renamed from $oldname)</i>" 3646 else 3647 print "<i>(renamed and modified from $oldname)</i>" 3648 fi 3649 fi 3650 3651 # If there's an old file, but no new file, the file was deleted 3652 if [[ -f $F-.html && ! -f $F.html ]]; then 3653 print " <i>(deleted)</i>" 3654 fi 3655 3656 # 3657 # Check for usr/closed and deleted_files/usr/closed 3658 # 3659 if [ ! -z "$Oflag" ]; then 3660 if [[ $P == usr/closed/* || \ 3661 $P == deleted_files/usr/closed/* ]]; then 3662 print " <i>Closed source: omitted from" \ 3663 "this review</i>" 3664 fi 3665 fi 3666 3667 print "</p>" 3668 # Insert delta comments 3669 3670 print "<blockquote><pre>" 3671 getcomments html $P $PP 3672 print "</pre>" 3673 3674 # Add additional comments comment 3675 3676 print "<!-- Add comments to explain changes in $P here -->" 3677 3678 # Add count of changes. 3679 3680 if [[ -f $F.count ]]; then 3681 cat $F.count 3682 rm $F.count 3683 fi 3684 3685 if [[ $SCM_MODE == "teamware" || 3686 $SCM_MODE == "mercurial" || 3687 $SCM_MODE == "unknown" ]]; then 3688 3689 # Include warnings for important file mode situations: 3690 # 1) New executable files 3691 # 2) Permission changes of any kind 3692 # 3) Existing executable files 3693 3694 old_mode= 3695 if [[ -f $WDIR/raw_files/old/$PP ]]; then 3696 old_mode=`get_file_mode $WDIR/raw_files/old/$PP` 3697 fi 3698 3699 new_mode= 3700 if [[ -f $WDIR/raw_files/new/$P ]]; then 3701 new_mode=`get_file_mode $WDIR/raw_files/new/$P` 3702 fi 3703 3704 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then 3705 print "<span class=\"chmod\">" 3706 print "<p>new executable file: mode $new_mode</p>" 3707 print "</span>" 3708 elif [[ -n "$old_mode" && -n "$new_mode" && 3709 "$old_mode" != "$new_mode" ]]; then 3710 print "<span class=\"chmod\">" 3711 print "<p>mode change: $old_mode to $new_mode</p>" 3712 print "</span>" 3713 elif [[ "$new_mode" = *[1357]* ]]; then 3714 print "<span class=\"chmod\">" 3715 print "<p>executable file: mode $new_mode</p>" 3716 print "</span>" 3717 fi 3718 fi 3719 3720 print "</blockquote>" 3721done 3722 3723print 3724print 3725print "<hr></hr>" 3726print "<p style=\"font-size: small\">" 3727print "This code review page was prepared using <b>$0</b>." 3728print "Webrev is maintained by the <a href=\"http://www.illumos.org\">" 3729print "illumos</a> project. The latest version may be obtained" 3730print "<a href=\"http://src.illumos.org/source/xref/illumos-opengrok/usr/src/tools/scripts/webrev.sh\">here</a>.</p>" 3731print "</body>" 3732print "</html>" 3733 3734exec 1<&- # Close FD 1. 3735exec 1<&3 # dup FD 3 to restore stdout. 3736exec 3<&- # close FD 3. 3737 3738print "Done." 3739 3740# 3741# If remote deletion was specified and fails do not continue. 3742# 3743if [[ -n $Dflag ]]; then 3744 delete_webrev 1 1 3745 (( $? == 0 )) || exit $? 3746fi 3747 3748if [[ -n $Uflag ]]; then 3749 upload_webrev 3750 exit $? 3751fi 3752