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