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