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