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