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