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