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