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