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