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