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