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