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