1 #!/usr/bin/ksh93 -p
2 #
3 # CDDL HEADER START
4 #
5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
8 #
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
13 #
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
19 #
20 # CDDL HEADER END
21 #
22
23 #
24 # Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
25 # Copyright 2008, 2010, Richard Lowe
26 # Copyright 2012 Marcel Telka <marcel@telka.sk>
27 # Copyright 2014 Bart Coddens <bart.coddens@gmail.com>
28 # Copyright 2017 Nexenta Systems, Inc.
29 # Copyright 2016 Joyent, Inc.
30 # Copyright 2016 RackTop Systems.
31 #
32
33 #
34 # This script takes a file list and a workspace and builds a set of html files
35 # suitable for doing a code review of source changes via a web page.
36 # Documentation is available via the manual page, webrev.1, or just
37 # type 'webrev -h'.
38 #
39 # Acknowledgements to contributors to webrev are listed in the webrev(1)
40 # man page.
41 #
42
43 REMOVED_COLOR=brown
44 CHANGED_COLOR=blue
45 NEW_COLOR=blue
46
47 HTML='<?xml version="1.0"?>
48 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
49 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
50 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
51
52 FRAMEHTML='<?xml version="1.0"?>
53 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
54 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
55 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
56
57 STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta>
58 <meta http-equiv="Content-Type" content="text/xhtml;charset=utf-8"></meta>
59 <meta http-equiv="Pragma" content="no-cache"></meta>
60 <meta http-equiv="Expires" content="-1"></meta>
61 <!--
62 Note to customizers: the body of the webrev is IDed as SUNWwebrev
63 to allow easy overriding by users of webrev via the userContent.css
64 mechanism available in some browsers.
65
66 For example, to have all "removed" information be red instead of
67 brown, set a rule in your userContent.css file like:
68
69 body#SUNWwebrev span.removed { color: red ! important; }
70 -->
71 <style type="text/css" media="screen">
72 body {
73 background-color: #eeeeee;
74 }
75 hr {
76 border: none 0;
77 border-top: 1px solid #aaa;
78 height: 1px;
79 }
80 div.summary {
81 font-size: .8em;
82 border-bottom: 1px solid #aaa;
83 padding-left: 1em;
84 padding-right: 1em;
85 }
86 div.summary h2 {
87 margin-bottom: 0.3em;
88 }
89 div.summary table th {
90 text-align: right;
91 vertical-align: top;
92 white-space: nowrap;
93 }
94 span.lineschanged {
95 font-size: 0.7em;
96 }
97 span.oldmarker {
98 color: red;
99 font-size: large;
100 font-weight: bold;
101 }
102 span.newmarker {
103 color: green;
104 font-size: large;
105 font-weight: bold;
106 }
107 span.removed {
108 color: brown;
109 }
110 span.changed {
111 color: blue;
112 }
113 span.new {
114 color: blue;
115 font-weight: bold;
116 }
117 span.chmod {
118 font-size: 0.7em;
119 color: #db7800;
120 }
121 a.print { font-size: x-small; }
122 a:hover { background-color: #ffcc99; }
123 </style>
124
125 <style type="text/css" media="print">
126 pre { font-size: 0.8em; font-family: courier, monospace; }
127 span.removed { color: #444; font-style: italic }
128 span.changed { font-weight: bold; }
129 span.new { font-weight: bold; }
130 span.newmarker { font-size: 1.2em; font-weight: bold; }
131 span.oldmarker { font-size: 1.2em; font-weight: bold; }
132 a.print {display: none}
133 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
134 </style>
135 '
136
137 #
138 # UDiffs need a slightly different CSS rule for 'new' items (we don't
139 # want them to be bolded as we do in cdiffs or sdiffs).
140 #
141 UDIFFCSS='
142 <style type="text/css" media="screen">
143 span.new {
144 color: blue;
145 font-weight: normal;
146 }
147 </style>
148 '
149
150 # CSS for the HTML version of the man pages.
151 # Current version is from mandoc 1.14.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 # fix_postscript
1284 #
1285 # Merge codereview output files to a single conforming postscript file, by:
1286 # - removing all extraneous headers/trailers
1287 # - making the page numbers right
1288 # - removing pages devoid of contents which confuse some
1289 # postscript readers.
1290 #
1291 # From Casper.
1292 #
1293 function fix_postscript
1294 {
1295 infile=$1
1296
1297 cat > /tmp/$$.crmerge.pl << \EOF
1298
1299 print scalar(<>); # %!PS-Adobe---
1300 print "%%Orientation: Landscape\n";
1301
1302 $pno = 0;
1303 $doprint = 1;
1304
1305 $page = "";
1306
1307 while (<>) {
1308 next if (/^%%Pages:\s*\d+/);
1309
1310 if (/^%%Page:/) {
1311 if ($pno == 0 || $page =~ /\)S/) {
1312 # Header or single page containing text
1313 print "%%Page: ? $pno\n" if ($pno > 0);
1314 print $page;
1315 $pno++;
1316 } else {
1317 # Empty page, skip it.
1318 }
1319 $page = "";
1320 $doprint = 1;
1321 next;
1322 }
1323
1324 # Skip from %%Trailer of one document to Endprolog
1325 # %%Page of the next
1326 $doprint = 0 if (/^%%Trailer/);
1327 $page .= $_ if ($doprint);
1328 }
1329
1330 if ($page =~ /\)S/) {
1331 print "%%Page: ? $pno\n";
1332 print $page;
1333 } else {
1334 $pno--;
1335 }
1336 print "%%Trailer\n%%Pages: $pno\n";
1337 EOF
1338
1339 $PERL /tmp/$$.crmerge.pl < $infile
1340 }
1341
1342
1343 #
1344 # input_cmd | insert_anchors | output_cmd
1345 #
1346 # Flag blocks of difference with sequentially numbered invisible
1347 # anchors. These are used to drive the frames version of the
1348 # sdiffs output.
1349 #
1350 # NOTE: Anchor zero flags the top of the file irrespective of changes,
1351 # an additional anchor is also appended to flag the bottom.
1352 #
1353 # The script detects changed lines as any line that has a "<span
1354 # class=" string embedded (unchanged lines have no class set and are
1355 # not part of a <span>. Blank lines (without a sequence number)
1356 # are also detected since they flag lines that have been inserted or
1357 # deleted.
1358 #
1359 function insert_anchors
1360 {
1361 $AWK '
1362 function ia() {
1363 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1364 }
1365
1366 BEGIN {
1367 anc=1;
1368 inblock=1;
1369 printf "<pre>\n";
1370 }
1371 NF == 0 || /^<span class=/ {
1372 if (inblock == 0) {
1373 ia();
1374 inblock=1;
1375 }
1376 print;
1377 next;
1378 }
1379 {
1380 inblock=0;
1381 print;
1382 }
1383 END {
1384 ia();
1385
1386 printf "<b style=\"font-size: large; color: red\">";
1387 printf "--- EOF ---</b>"
1388 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1389 printf "</pre>"
1390 printf "<form name=\"eof\">";
1391 printf "<input name=\"value\" value=\"%d\" " \
1392 "type=\"hidden\"></input>", anc - 1;
1393 printf "</form>";
1394 }
1395 ' $1
1396 }
1397
1398
1399 #
1400 # relative_dir
1401 #
1402 # Print a relative return path from $1 to $2. For example if
1403 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1404 # this function would print "../../../../".
1405 #
1406 # In the event that $1 is not in $2 a warning is printed to stderr,
1407 # and $2 is returned-- the result of this is that the resulting webrev
1408 # is not relocatable.
1409 #
1410 function relative_dir
1411 {
1412 typeset cur="${1##$2?(/)}"
1413
1414 #
1415 # If the first path was specified absolutely, and it does
1416 # not start with the second path, it's an error.
1417 #
1418 if [[ "$cur" = "/${1#/}" ]]; then
1419 # Should never happen.
1420 print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1421 print -u2 "to \"$2\". Check input paths. Framed webrev "
1422 print -u2 "will not be relocatable!"
1423 print $2
1424 return
1425 fi
1426
1427 #
1428 # This is kind of ugly. The sed script will do the following:
1429 #
1430 # 1. Strip off a leading "." or "./": this is important to get
1431 # the correct arcnav links for files in $WDIR.
1432 # 2. Strip off a trailing "/": this is not strictly necessary,
1433 # but is kind of nice, since it doesn't end up in "//" at
1434 # the end of a relative path.
1435 # 3. Replace all remaining sequences of non-"/" with "..": the
1436 # assumption here is that each dirname represents another
1437 # level of relative separation.
1438 # 4. Append a trailing "/" only for non-empty paths: this way
1439 # the caller doesn't need to duplicate this logic, and does
1440 # not end up using $RTOP/file for files in $WDIR.
1441 #
1442 print $cur | $SED -e '{
1443 s:^\./*::
1444 s:/$::
1445 s:[^/][^/]*:..:g
1446 s:^\(..*\)$:\1/:
1447 }'
1448 }
1449
1450 #
1451 # frame_nav_js
1452 #
1453 # Emit javascript for frame navigation
1454 #
1455 function frame_nav_js
1456 {
1457 cat << \EOF
1458 var myInt;
1459 var scrolling = 0;
1460 var sfactor = 3;
1461 var scount = 10;
1462
1463 function scrollByPix()
1464 {
1465 if (scount <= 0) {
1466 sfactor *= 1.2;
1467 scount = 10;
1468 }
1469 parent.lhs.scrollBy(0, sfactor);
1470 parent.rhs.scrollBy(0, sfactor);
1471 scount--;
1472 }
1473
1474 function scrollToAnc(num)
1475 {
1476 // Update the value of the anchor in the form which we use as
1477 // storage for this value. setAncValue() will take care of
1478 // correcting for overflow and underflow of the value and return
1479 // us the new value.
1480 num = setAncValue(num);
1481
1482 // Set location and scroll back a little to expose previous
1483 // lines.
1484 //
1485 // Note that this could be improved: it is possible although
1486 // complex to compute the x and y position of an anchor, and to
1487 // scroll to that location directly.
1488 //
1489 parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1490 parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1491
1492 parent.lhs.scrollBy(0, -30);
1493 parent.rhs.scrollBy(0, -30);
1494 }
1495
1496 function getAncValue()
1497 {
1498 return (parseInt(parent.nav.document.diff.real.value));
1499 }
1500
1501 function setAncValue(val)
1502 {
1503 if (val <= 0) {
1504 val = 0;
1505 parent.nav.document.diff.real.value = val;
1506 parent.nav.document.diff.display.value = "BOF";
1507 return (val);
1508 }
1509
1510 //
1511 // The way we compute the max anchor value is to stash it
1512 // inline in the left and right hand side pages-- it's the same
1513 // on each side, so we pluck from the left.
1514 //
1515 maxval = parent.lhs.document.eof.value.value;
1516 if (val < maxval) {
1517 parent.nav.document.diff.real.value = val;
1518 parent.nav.document.diff.display.value = val.toString();
1519 return (val);
1520 }
1521
1522 // this must be: val >= maxval
1523 val = maxval;
1524 parent.nav.document.diff.real.value = val;
1525 parent.nav.document.diff.display.value = "EOF";
1526 return (val);
1527 }
1528
1529 function stopScroll()
1530 {
1531 if (scrolling == 1) {
1532 clearInterval(myInt);
1533 scrolling = 0;
1534 }
1535 }
1536
1537 function startScroll()
1538 {
1539 stopScroll();
1540 scrolling = 1;
1541 myInt = setInterval("scrollByPix()", 10);
1542 }
1543
1544 function handlePress(b)
1545 {
1546 switch (b) {
1547 case 1:
1548 scrollToAnc(-1);
1549 break;
1550 case 2:
1551 scrollToAnc(getAncValue() - 1);
1552 break;
1553 case 3:
1554 sfactor = -3;
1555 startScroll();
1556 break;
1557 case 4:
1558 sfactor = 3;
1559 startScroll();
1560 break;
1561 case 5:
1562 scrollToAnc(getAncValue() + 1);
1563 break;
1564 case 6:
1565 scrollToAnc(999999);
1566 break;
1567 }
1568 }
1569
1570 function handleRelease(b)
1571 {
1572 stopScroll();
1573 }
1574
1575 function keypress(ev)
1576 {
1577 var keynum;
1578 var keychar;
1579
1580 if (window.event) { // IE
1581 keynum = ev.keyCode;
1582 } else if (ev.which) { // non-IE
1583 keynum = ev.which;
1584 }
1585
1586 keychar = String.fromCharCode(keynum);
1587
1588 if (keychar == "k") {
1589 handlePress(2);
1590 return (0);
1591 } else if (keychar == "j" || keychar == " ") {
1592 handlePress(5);
1593 return (0);
1594 }
1595
1596 return (1);
1597 }
1598
1599 function ValidateDiffNum()
1600 {
1601 var val;
1602 var i;
1603
1604 val = parent.nav.document.diff.display.value;
1605 if (val == "EOF") {
1606 scrollToAnc(999999);
1607 return;
1608 }
1609
1610 if (val == "BOF") {
1611 scrollToAnc(0);
1612 return;
1613 }
1614
1615 i = parseInt(val);
1616 if (isNaN(i)) {
1617 parent.nav.document.diff.display.value = getAncValue();
1618 } else {
1619 scrollToAnc(i);
1620 }
1621
1622 return (false);
1623 }
1624 EOF
1625 }
1626
1627 #
1628 # frame_navigation
1629 #
1630 # Output anchor navigation file for framed sdiffs.
1631 #
1632 function frame_navigation
1633 {
1634 print "$HTML<head>$STDHEAD"
1635
1636 cat << \EOF
1637 <title>Anchor Navigation</title>
1638 <meta http-equiv="Content-Script-Type" content="text/javascript">
1639 <meta http-equiv="Content-Type" content="text/html">
1640
1641 <style type="text/css">
1642 div.button td { padding-left: 5px; padding-right: 5px;
1643 background-color: #eee; text-align: center;
1644 border: 1px #444 outset; cursor: pointer; }
1645 div.button a { font-weight: bold; color: black }
1646 div.button td:hover { background: #ffcc99; }
1647 </style>
1648 EOF
1649
1650 print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1651
1652 cat << \EOF
1653 </head>
1654 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1655 onkeypress="keypress(event);">
1656 <noscript lang="javascript">
1657 <center>
1658 <p><big>Framed Navigation controls require Javascript</big><br></br>
1659 Either this browser is incompatable or javascript is not enabled</p>
1660 </center>
1661 </noscript>
1662 <table width="100%" border="0" align="center">
1663 <tr>
1664 <td valign="middle" width="25%">Diff navigation:
1665 Use 'j' and 'k' for next and previous diffs; or use buttons
1666 at right</td>
1667 <td align="center" valign="top" width="50%">
1668 <div class="button">
1669 <table border="0" align="center">
1670 <tr>
1671 <td>
1672 <a onMouseDown="handlePress(1);return true;"
1673 onMouseUp="handleRelease(1);return true;"
1674 onMouseOut="handleRelease(1);return true;"
1675 onClick="return false;"
1676 title="Go to Beginning Of file">BOF</a></td>
1677 <td>
1678 <a onMouseDown="handlePress(3);return true;"
1679 onMouseUp="handleRelease(3);return true;"
1680 onMouseOut="handleRelease(3);return true;"
1681 title="Scroll Up: Press and Hold to accelerate"
1682 onClick="return false;">Scroll Up</a></td>
1683 <td>
1684 <a onMouseDown="handlePress(2);return true;"
1685 onMouseUp="handleRelease(2);return true;"
1686 onMouseOut="handleRelease(2);return true;"
1687 title="Go to previous Diff"
1688 onClick="return false;">Prev Diff</a>
1689 </td></tr>
1690
1691 <tr>
1692 <td>
1693 <a onMouseDown="handlePress(6);return true;"
1694 onMouseUp="handleRelease(6);return true;"
1695 onMouseOut="handleRelease(6);return true;"
1696 onClick="return false;"
1697 title="Go to End Of File">EOF</a></td>
1698 <td>
1699 <a onMouseDown="handlePress(4);return true;"
1700 onMouseUp="handleRelease(4);return true;"
1701 onMouseOut="handleRelease(4);return true;"
1702 title="Scroll Down: Press and Hold to accelerate"
1703 onClick="return false;">Scroll Down</a></td>
1704 <td>
1705 <a onMouseDown="handlePress(5);return true;"
1706 onMouseUp="handleRelease(5);return true;"
1707 onMouseOut="handleRelease(5);return true;"
1708 title="Go to next Diff"
1709 onClick="return false;">Next Diff</a></td>
1710 </tr>
1711 </table>
1712 </div>
1713 </td>
1714 <th valign="middle" width="25%">
1715 <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1716 <input name="display" value="BOF" size="8" type="text"></input>
1717 <input name="real" value="0" size="8" type="hidden"></input>
1718 </form>
1719 </th>
1720 </tr>
1721 </table>
1722 </body>
1723 </html>
1724 EOF
1725 }
1726
1727
1728
1729 #
1730 # diff_to_html <filename> <filepath> { U | C } <comment>
1731 #
1732 # Processes the output of diff to produce an HTML file representing either
1733 # context or unified diffs.
1734 #
1735 diff_to_html()
1736 {
1737 TNAME=$1
1738 TPATH=$2
1739 DIFFTYPE=$3
1740 COMMENT=$4
1741
1742 print "$HTML<head>$STDHEAD"
1743 print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1744
1745 if [[ $DIFFTYPE == "U" ]]; then
1746 print "$UDIFFCSS"
1747 fi
1748
1749 cat <<-EOF
1750 </head>
1751 <body id="SUNWwebrev">
1752 <a class="print" href="javascript:print()">Print this page</a>
1753 <pre>$COMMENT</pre>
1754 <pre>
1755 EOF
1756
1757 html_quote | $AWK '
1758 /^--- new/ { next }
1759 /^\+\+\+ new/ { next }
1760 /^--- old/ { next }
1761 /^\*\*\* old/ { next }
1762 /^\*\*\*\*/ { next }
1763 /^-------/ { printf "<center><h1>%s</h1></center>\n", $0; next }
1764 /^\@\@.*\@\@$/ { printf "</pre><hr></hr><pre>\n";
1765 printf "<span class=\"newmarker\">%s</span>\n", $0;
1766 next}
1767
1768 /^\*\*\*/ { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1769 next}
1770 /^---/ { printf "<span class=\"newmarker\">%s</span>\n", $0;
1771 next}
1772 /^\+/ {printf "<span class=\"new\">%s</span>\n", $0; next}
1773 /^!/ {printf "<span class=\"changed\">%s</span>\n", $0; next}
1774 /^-/ {printf "<span class=\"removed\">%s</span>\n", $0; next}
1775 {printf "%s\n", $0; next}
1776 '
1777
1778 print "</pre></body></html>\n"
1779 }
1780
1781
1782 #
1783 # source_to_html { new | old } <filename>
1784 #
1785 # Process a plain vanilla source file to transform it into an HTML file.
1786 #
1787 source_to_html()
1788 {
1789 WHICH=$1
1790 TNAME=$2
1791
1792 print "$HTML<head>$STDHEAD"
1793 print "<title>$WNAME $WHICH $TNAME</title>"
1794 print "<body id=\"SUNWwebrev\">"
1795 print "<pre>"
1796 html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1797 print "</pre></body></html>"
1798 }
1799
1800 #
1801 # comments_from_wx {text|html} filepath
1802 #
1803 # Given the pathname of a file, find its location in a "wx" active
1804 # file list and print the following comment. Output is either text or
1805 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1806 # are turned into URLs.
1807 #
1808 comments_from_wx()
1809 {
1810 typeset fmt=$1
1811 typeset p=$2
1812
1813 comm=`$AWK '
1814 $1 == "'$p'" {
1815 do getline ; while (NF > 0)
1816 getline
1817 while (NF > 0) { print ; getline }
1818 exit
1819 }' < $wxfile`
1820
1821 if [[ -z $comm ]]; then
1822 comm="*** NO COMMENTS ***"
1823 fi
1824
1825 if [[ $fmt == "text" ]]; then
1826 print -- "$comm"
1827 return
1828 fi
1829
1830 print -- "$comm" | html_quote | its2url
1831
1832 }
1833
1834 #
1835 # getcomments {text|html} filepath parentpath
1836 #
1837 # Fetch the comments depending on what SCM mode we're in.
1838 #
1839 getcomments()
1840 {
1841 typeset fmt=$1
1842 typeset p=$2
1843 typeset pp=$3
1844
1845 if [[ -n $Nflag ]]; then
1846 return
1847 fi
1848
1849 if [[ -n $wxfile ]]; then
1850 comments_from_wx $fmt $p
1851 fi
1852 }
1853
1854 #
1855 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1856 #
1857 # Print out Code Inspection figures similar to sccs-prt(1) format.
1858 #
1859 function printCI
1860 {
1861 integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1862 typeset str
1863 if (( tot == 1 )); then
1864 str="line"
1865 else
1866 str="lines"
1867 fi
1868 printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1869 $tot $str $ins $del $mod $unc
1870 }
1871
1872
1873 #
1874 # difflines <oldfile> <newfile>
1875 #
1876 # Calculate and emit number of added, removed, modified and unchanged lines,
1877 # and total lines changed, the sum of added + removed + modified.
1878 #
1879 function difflines
1880 {
1881 integer tot mod del ins unc err
1882 typeset filename
1883
1884 eval $( diff -e $1 $2 | $AWK '
1885 # Change range of lines: N,Nc
1886 /^[0-9]*,[0-9]*c$/ {
1887 n=split(substr($1,1,length($1)-1), counts, ",");
1888 if (n != 2) {
1889 error=2
1890 exit;
1891 }
1892 #
1893 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1894 # following would be 5 - 3 = 2! Hence +1 for correction.
1895 #
1896 r=(counts[2]-counts[1])+1;
1897
1898 #
1899 # Now count replacement lines: each represents a change instead
1900 # of a delete, so increment c and decrement r.
1901 #
1902 while (getline != /^\.$/) {
1903 c++;
1904 r--;
1905 }
1906 #
1907 # If there were more replacement lines than original lines,
1908 # then r will be negative; in this case there are no deletions,
1909 # but there are r changes that should be counted as adds, and
1910 # since r is negative, subtract it from a and add it to c.
1911 #
1912 if (r < 0) {
1913 a-=r;
1914 c+=r;
1915 }
1916
1917 #
1918 # If there were more original lines than replacement lines, then
1919 # r will be positive; in this case, increment d by that much.
1920 #
1921 if (r > 0) {
1922 d+=r;
1923 }
1924 next;
1925 }
1926
1927 # Change lines: Nc
1928 /^[0-9].*c$/ {
1929 # The first line is a replacement; any more are additions.
1930 if (getline != /^\.$/) {
1931 c++;
1932 while (getline != /^\.$/) a++;
1933 }
1934 next;
1935 }
1936
1937 # Add lines: both Na and N,Na
1938 /^[0-9].*a$/ {
1939 while (getline != /^\.$/) a++;
1940 next;
1941 }
1942
1943 # Delete range of lines: N,Nd
1944 /^[0-9]*,[0-9]*d$/ {
1945 n=split(substr($1,1,length($1)-1), counts, ",");
1946 if (n != 2) {
1947 error=2
1948 exit;
1949 }
1950 #
1951 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1952 # following would be 5 - 3 = 2! Hence +1 for correction.
1953 #
1954 r=(counts[2]-counts[1])+1;
1955 d+=r;
1956 next;
1957 }
1958
1959 # Delete line: Nd. For example 10d says line 10 is deleted.
1960 /^[0-9]*d$/ {d++; next}
1961
1962 # Should not get here!
1963 {
1964 error=1;
1965 exit;
1966 }
1967
1968 # Finish off - print results
1969 END {
1970 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1971 (c+d+a), c, d, a, error);
1972 }' )
1973
1974 # End of $AWK, Check to see if any trouble occurred.
1975 if (( $? > 0 || err > 0 )); then
1976 print "Unexpected Error occurred reading" \
1977 "\`diff -e $1 $2\`: \$?=$?, err=" $err
1978 return
1979 fi
1980
1981 # Accumulate totals
1982 (( TOTL += tot ))
1983 (( TMOD += mod ))
1984 (( TDEL += del ))
1985 (( TINS += ins ))
1986 # Calculate unchanged lines
1987 unc=`wc -l < $1`
1988 if (( unc > 0 )); then
1989 (( unc -= del + mod ))
1990 (( TUNC += unc ))
1991 fi
1992 # print summary
1993 print "<span class=\"lineschanged\">"
1994 printCI $tot $ins $del $mod $unc
1995 print "</span>"
1996 }
1997
1998
1999 #
2000 # flist_from_wx
2001 #
2002 # Sets up webrev to source its information from a wx-formatted file.
2003 # Sets the global 'wxfile' variable.
2004 #
2005 function flist_from_wx
2006 {
2007 typeset argfile=$1
2008 if [[ -n ${argfile%%/*} ]]; then
2009 #
2010 # If the wx file pathname is relative then make it absolute
2011 # because the webrev does a "cd" later on.
2012 #
2013 wxfile=$PWD/$argfile
2014 else
2015 wxfile=$argfile
2016 fi
2017
2018 $AWK '{ c = 1; print;
2019 while (getline) {
2020 if (NF == 0) { c = -c; continue }
2021 if (c > 0) print
2022 }
2023 }' $wxfile > $FLIST
2024
2025 print " Done."
2026 }
2027
2028 #
2029 # Transform a specified 'git log' output format into a wx-like active list.
2030 #
2031 function git_wxfile
2032 {
2033 typeset child="$1"
2034 typeset parent="$2"
2035
2036 TMPFLIST=/tmp/$$.active
2037 $PERL -e 'my (%files, %realfiles, $msg);
2038 my $parent = $ARGV[0];
2039 my $child = $ARGV[1];
2040
2041 open(F, "git diff -M --name-status $parent..$child |");
2042 while (<F>) {
2043 chomp;
2044 if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
2045 if ($1 >= 75) { # Probably worth treating as a rename
2046 $realfiles{$3} = $2;
2047 } else {
2048 $realfiles{$3} = $3;
2049 $realfiles{$2} = $2;
2050 }
2051 } else {
2052 my $f = (split /\s+/, $_)[1];
2053 $realfiles{$f} = $f;
2054 }
2055 }
2056 close(F);
2057
2058 my $state = 1; # 0|comments, 1|files
2059 open(F, "git whatchanged --pretty=format:%B $parent..$child |");
2060 while (<F>) {
2061 chomp;
2062 if (/^:[0-9]{6}/) {
2063 my ($unused, $fname, $fname2) = split(/\t/, $_);
2064 $fname = $fname2 if defined($fname2);
2065 next if !defined($realfiles{$fname}); # No real change
2066 $state = 1;
2067 chomp $msg;
2068 $files{$fname} .= $msg;
2069 } else {
2070 if ($state == 1) {
2071 $state = 0;
2072 $msg = /^\n/ ? "" : "\n";
2073 }
2074 $msg .= "$_\n" if ($_);
2075 }
2076 }
2077 close(F);
2078
2079 for (sort keys %files) {
2080 if ($realfiles{$_} ne $_) {
2081 print "$_ $realfiles{$_}\n$files{$_}\n\n";
2082 } else {
2083 print "$_\n$files{$_}\n\n"
2084 }
2085 }' ${parent} ${child} > $TMPFLIST
2086
2087 wxfile=$TMPFLIST
2088 }
2089
2090 #
2091 # flist_from_git
2092 # Build a wx-style active list, and hand it off to flist_from_wx
2093 #
2094 function flist_from_git
2095 {
2096 typeset child=$1
2097 typeset parent=$2
2098
2099 print " File list from: git ...\c"
2100 git_wxfile "$child" "$parent";
2101
2102 # flist_from_wx prints the Done, so we don't have to.
2103 flist_from_wx $TMPFLIST
2104 }
2105
2106 #
2107 # flist_from_subversion
2108 #
2109 # Generate the file list by extracting file names from svn status.
2110 #
2111 function flist_from_subversion
2112 {
2113 CWS=$1
2114 OLDPWD=$2
2115
2116 cd $CWS
2117 print -u2 " File list from: svn status ... \c"
2118 svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
2119 print -u2 " Done."
2120 cd $OLDPWD
2121 }
2122
2123 function env_from_flist
2124 {
2125 [[ -r $FLIST ]] || return
2126
2127 #
2128 # Use "eval" to set env variables that are listed in the file
2129 # list. Then copy those into our local versions of those
2130 # variables if they have not been set already.
2131 #
2132 eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
2133
2134 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
2135 codemgr_ws=$CODEMGR_WS
2136 export CODEMGR_WS
2137 fi
2138
2139 #
2140 # Check to see if CODEMGR_PARENT is set in the flist file.
2141 #
2142 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2143 codemgr_parent=$CODEMGR_PARENT
2144 export CODEMGR_PARENT
2145 fi
2146 }
2147
2148 function look_for_prog
2149 {
2150 typeset path
2151 typeset ppath
2152 typeset progname=$1
2153
2154 ppath=$PATH
2155 ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
2156 ppath=$ppath:/opt/onbld/bin
2157 ppath=$ppath:/opt/onbld/bin/`uname -p`
2158
2159 PATH=$ppath prog=`whence $progname`
2160 if [[ -n $prog ]]; then
2161 print $prog
2162 fi
2163 }
2164
2165 function get_file_mode
2166 {
2167 $PERL -e '
2168 if (@stat = stat($ARGV[0])) {
2169 $mode = $stat[2] & 0777;
2170 printf "%03o\n", $mode;
2171 exit 0;
2172 } else {
2173 exit 1;
2174 }
2175 ' $1
2176 }
2177
2178 function build_old_new_git
2179 {
2180 typeset olddir="$1"
2181 typeset newdir="$2"
2182 typeset o_mode=
2183 typeset n_mode=
2184 typeset o_object=
2185 typeset n_object=
2186 typeset OWD=$PWD
2187 typeset file
2188 typeset type
2189
2190 cd $CWS
2191
2192 #
2193 # Get old file and its mode from the git object tree
2194 #
2195 if [[ "$PDIR" == "." ]]; then
2196 file="$PF"
2197 else
2198 file="$PDIR/$PF"
2199 fi
2200
2201 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2202 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2203 else
2204 $GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk
2205 $GIT cat-file $type $o_object > $olddir/$file 2>/dev/null
2206
2207 if (( $? != 0 )); then
2208 rm -f $olddir/$file
2209 elif [[ -n $o_mode ]]; then
2210 # Strip the first 3 digits, to get a regular octal mode
2211 o_mode=${o_mode/???/}
2212 chmod $o_mode $olddir/$file
2213 else
2214 # should never happen
2215 print -u2 "ERROR: set mode of $olddir/$file"
2216 fi
2217 fi
2218
2219 #
2220 # new version of the file.
2221 #
2222 if [[ "$DIR" == "." ]]; then
2223 file="$F"
2224 else
2225 file="$DIR/$F"
2226 fi
2227 rm -rf $newdir/$file
2228
2229 if [[ -e $CWS/$DIR/$F ]]; then
2230 cp $CWS/$DIR/$F $newdir/$DIR/$F
2231 chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F
2232 fi
2233 cd $OWD
2234 }
2235
2236 function build_old_new_subversion
2237 {
2238 typeset olddir="$1"
2239 typeset newdir="$2"
2240
2241 # Snag new version of file.
2242 rm -f $newdir/$DIR/$F
2243 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2244
2245 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2246 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2247 else
2248 # Get the parent's version of the file.
2249 svn status $CWS/$DIR/$F | read stat file
2250 if [[ $stat != "A" ]]; then
2251 svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2252 fi
2253 fi
2254 }
2255
2256 function build_old_new_unknown
2257 {
2258 typeset olddir="$1"
2259 typeset newdir="$2"
2260
2261 #
2262 # Snag new version of file.
2263 #
2264 rm -f $newdir/$DIR/$F
2265 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2266
2267 #
2268 # Snag the parent's version of the file.
2269 #
2270 if [[ -f $PWS/$PDIR/$PF ]]; then
2271 rm -f $olddir/$PDIR/$PF
2272 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2273 fi
2274 }
2275
2276 function build_old_new
2277 {
2278 typeset WDIR=$1
2279 typeset PWS=$2
2280 typeset PDIR=$3
2281 typeset PF=$4
2282 typeset CWS=$5
2283 typeset DIR=$6
2284 typeset F=$7
2285
2286 typeset olddir="$WDIR/raw_files/old"
2287 typeset newdir="$WDIR/raw_files/new"
2288
2289 mkdir -p $olddir/$PDIR
2290 mkdir -p $newdir/$DIR
2291
2292 if [[ $SCM_MODE == "git" ]]; then
2293 build_old_new_git "$olddir" "$newdir"
2294 elif [[ $SCM_MODE == "subversion" ]]; then
2295 build_old_new_subversion "$olddir" "$newdir"
2296 elif [[ $SCM_MODE == "unknown" ]]; then
2297 build_old_new_unknown "$olddir" "$newdir"
2298 fi
2299
2300 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2301 print "*** Error: file not in parent or child"
2302 return 1
2303 fi
2304 return 0
2305 }
2306
2307
2308 #
2309 # Usage message.
2310 #
2311 function usage
2312 {
2313 print 'Usage:\twebrev [common-options]
2314 webrev [common-options] ( <file> | - )
2315 webrev [common-options] -w <wx file>
2316
2317 Options:
2318 -c <revision>: generate webrev for single revision (git only)
2319 -C <filename>: Use <filename> for the information tracking configuration.
2320 -D: delete remote webrev
2321 -h <revision>: specify "head" revision for comparison (git only)
2322 -i <filename>: Include <filename> in the index.html file.
2323 -I <filename>: Use <filename> for the information tracking registry.
2324 -n: do not generate the webrev (useful with -U)
2325 -O: Print bugids/arc cases suitable for OpenSolaris.
2326 -o <outdir>: Output webrev to specified directory.
2327 -p <compare-against>: Use specified parent wkspc or basis for comparison
2328 -t <remote_target>: Specify remote destination for webrev upload
2329 -U: upload the webrev to remote destination
2330 -w <wxfile>: Use specified wx active file.
2331
2332 Environment:
2333 WDIR: Control the output directory.
2334 WEBREV_TRASH_DIR: Set directory for webrev delete.
2335
2336 SCM Environment:
2337 CODEMGR_WS: Workspace location.
2338 CODEMGR_PARENT: Parent workspace location.
2339 '
2340
2341 exit 2
2342 }
2343
2344 #
2345 #
2346 # Main program starts here
2347 #
2348 #
2349
2350 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2351
2352 set +o noclobber
2353
2354 PATH=$(/bin/dirname "$(whence $0)"):$PATH
2355
2356 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2357 [[ -z $WX ]] && WX=`look_for_prog wx`
2358 [[ -z $GIT ]] && GIT=`look_for_prog git`
2359 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2360 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2361 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2362 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2363 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2364 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2365 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2366 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2367 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2368 [[ -z $SCP ]] && SCP=`look_for_prog scp`
2369 [[ -z $SED ]] && SED=`look_for_prog sed`
2370 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2371 [[ -z $SORT ]] && SORT=`look_for_prog sort`
2372 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2373 [[ -z $GREP ]] && GREP=`look_for_prog grep`
2374 [[ -z $FIND ]] && FIND=`look_for_prog find`
2375 [[ -z $MANDOC ]] && MANDOC=`look_for_prog mandoc`
2376 [[ -z $COL ]] && COL=`look_for_prog col`
2377
2378 # set name of trash directory for remote webrev deletion
2379 TRASH_DIR=".trash"
2380 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2381
2382 if [[ ! -x $PERL ]]; then
2383 print -u2 "Error: No perl interpreter found. Exiting."
2384 exit 1
2385 fi
2386
2387 if [[ ! -x $WHICH_SCM ]]; then
2388 print -u2 "Error: Could not find which_scm. Exiting."
2389 exit 1
2390 fi
2391
2392 #
2393 # These aren't fatal, but we want to note them to the user.
2394 # We don't warn on the absence of 'wx' until later when we've
2395 # determined that we actually need to try to invoke it.
2396 #
2397 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2398 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2399 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2400
2401 # Declare global total counters.
2402 integer TOTL TINS TDEL TMOD TUNC
2403
2404 # default remote host for upload/delete
2405 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2406 # prefixes for upload targets
2407 typeset -r rsync_prefix="rsync://"
2408 typeset -r ssh_prefix="ssh://"
2409
2410 cflag=
2411 Cflag=
2412 Dflag=
2413 flist_mode=
2414 flist_file=
2415 hflag=
2416 iflag=
2417 Iflag=
2418 lflag=
2419 Nflag=
2420 nflag=
2421 Oflag=
2422 oflag=
2423 pflag=
2424 tflag=
2425 uflag=
2426 Uflag=
2427 wflag=
2428 remote_target=
2429
2430 while getopts "c:C:Dh:i:I:lnNo:Op:t:Uw" opt
2431 do
2432 case $opt in
2433 c) cflag=1
2434 codemgr_head=$OPTARG
2435 codemgr_parent=$OPTARG~1;;
2436
2437 C) Cflag=1
2438 ITSCONF=$OPTARG;;
2439
2440 D) Dflag=1;;
2441
2442 h) hflag=1
2443 codemgr_head=$OPTARG;;
2444
2445 i) iflag=1
2446 INCLUDE_FILE=$OPTARG;;
2447
2448 I) Iflag=1
2449 ITSREG=$OPTARG;;
2450
2451 N) Nflag=1;;
2452
2453 n) nflag=1;;
2454
2455 O) Oflag=1;;
2456
2457 o) oflag=1
2458 # Strip the trailing slash to correctly form remote target.
2459 WDIR=${OPTARG%/};;
2460
2461 p) pflag=1
2462 codemgr_parent=$OPTARG;;
2463
2464 t) tflag=1
2465 remote_target=$OPTARG;;
2466
2467 U) Uflag=1;;
2468
2469 w) wflag=1;;
2470
2471 ?) usage;;
2472 esac
2473 done
2474
2475 FLIST=/tmp/$$.flist
2476
2477 if [[ -n $wflag && -n $lflag ]]; then
2478 usage
2479 fi
2480
2481 # more sanity checking
2482 if [[ -n $nflag && -z $Uflag ]]; then
2483 print "it does not make sense to skip webrev generation" \
2484 "without -U"
2485 exit 1
2486 fi
2487
2488 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2489 echo "remote target has to be used only for upload or delete"
2490 exit 1
2491 fi
2492
2493 #
2494 # For the invocation "webrev -n -U" with no other options, webrev will assume
2495 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2496 # $(basename ${CWS}). So we need to get CWS set before we skip any remaining
2497 # logic.
2498 #
2499 $WHICH_SCM | read SCM_MODE junk || exit 1
2500
2501 if [[ $SCM_MODE == "git" ]]; then
2502 #
2503 # Git priorities:
2504 # 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2505 # 2. git rev-parse --git-dir from directory of invocation
2506 #
2507 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2508 codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \
2509 2>/dev/null)
2510 [[ -z $codemgr_ws ]] && \
2511 codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null)
2512
2513 if [[ "$codemgr_ws" == ".git" ]]; then
2514 codemgr_ws="${PWD}/${codemgr_ws}"
2515 fi
2516
2517 if [[ "$codemgr_ws" = *"/.git" ]]; then
2518 codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git'
2519 fi
2520 CWS="$codemgr_ws"
2521 elif [[ $SCM_MODE == "subversion" ]]; then
2522 #
2523 # Subversion priorities:
2524 # 1. CODEMGR_WS from environment
2525 # 2. Relative path from current directory to SVN repository root
2526 #
2527 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2528 CWS=$CODEMGR_WS
2529 else
2530 svn info | while read line; do
2531 if [[ $line == "URL: "* ]]; then
2532 url=${line#URL: }
2533 elif [[ $line == "Repository Root: "* ]]; then
2534 repo=${line#Repository Root: }
2535 fi
2536 done
2537
2538 rel=${url#$repo}
2539 CWS=${PWD%$rel}
2540 fi
2541 fi
2542
2543 #
2544 # If no SCM has been determined, take either the environment setting
2545 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2546 #
2547 if [[ -z ${CWS} ]]; then
2548 CWS=${CODEMGR_WS:-.}
2549 fi
2550
2551 #
2552 # If the command line options indicate no webrev generation, either
2553 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2554 # ton of logic we can skip.
2555 #
2556 # Instead of increasing indentation, we intentionally leave this loop
2557 # body open here, and exit via break from multiple points within.
2558 # Search for DO_EVERYTHING below to find the break points and closure.
2559 #
2560 for do_everything in 1; do
2561
2562 # DO_EVERYTHING: break point
2563 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2564 break
2565 fi
2566
2567 #
2568 # If this manually set as the parent, and it appears to be an earlier webrev,
2569 # then note that fact and set the parent to the raw_files/new subdirectory.
2570 #
2571 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2572 parent_webrev=$(readlink -f "$codemgr_parent")
2573 codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new")
2574 fi
2575
2576 if [[ -z $wflag && -z $lflag ]]; then
2577 shift $(($OPTIND - 1))
2578
2579 if [[ $1 == "-" ]]; then
2580 cat > $FLIST
2581 flist_mode="stdin"
2582 flist_done=1
2583 shift
2584 elif [[ -n $1 ]]; then
2585 if [[ ! -r $1 ]]; then
2586 print -u2 "$1: no such file or not readable"
2587 usage
2588 fi
2589 cat $1 > $FLIST
2590 flist_mode="file"
2591 flist_file=$1
2592 flist_done=1
2593 shift
2594 else
2595 flist_mode="auto"
2596 fi
2597 fi
2598
2599 #
2600 # Before we go on to further consider -l and -w, work out which SCM we think
2601 # is in use.
2602 #
2603 case "$SCM_MODE" in
2604 git|subversion)
2605 ;;
2606 unknown)
2607 if [[ $flist_mode == "auto" ]]; then
2608 print -u2 "Unable to determine SCM in use and file list not specified"
2609 print -u2 "See which_scm(1) for SCM detection information."
2610 exit 1
2611 fi
2612 ;;
2613 *)
2614 if [[ $flist_mode == "auto" ]]; then
2615 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2616 exit 1
2617 fi
2618 ;;
2619 esac
2620
2621 print -u2 " SCM detected: $SCM_MODE"
2622
2623 if [[ -n $wflag ]]; then
2624 #
2625 # If the -w is given then assume the file list is in Bonwick's "wx"
2626 # command format, i.e. pathname lines alternating with SCCS comment
2627 # lines with blank lines as separators. Use the SCCS comments later
2628 # in building the index.html file.
2629 #
2630 shift $(($OPTIND - 1))
2631 wxfile=$1
2632 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2633 if [[ -r $CODEMGR_WS/wx/active ]]; then
2634 wxfile=$CODEMGR_WS/wx/active
2635 fi
2636 fi
2637
2638 [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2639 "be auto-detected (check \$CODEMGR_WS)" && exit 1
2640
2641 if [[ ! -r $wxfile ]]; then
2642 print -u2 "$wxfile: no such file or not readable"
2643 usage
2644 fi
2645
2646 print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2647 flist_from_wx $wxfile
2648 flist_done=1
2649 if [[ -n "$*" ]]; then
2650 shift
2651 fi
2652 elif [[ $flist_mode == "stdin" ]]; then
2653 print -u2 " File list from: standard input"
2654 elif [[ $flist_mode == "file" ]]; then
2655 print -u2 " File list from: $flist_file"
2656 fi
2657
2658 if [[ $# -gt 0 ]]; then
2659 print -u2 "WARNING: unused arguments: $*"
2660 fi
2661
2662
2663 if [[ $SCM_MODE == "git" ]]; then
2664 # Check that "head" revision specified with -c or -h is sane
2665 if [[ -n $cflag || -n $hflag ]]; then
2666 head_rev=$($GIT rev-parse --verify --quiet "$codemgr_head")
2667 if [[ -z $head_rev ]]; then
2668 print -u2 "Error: bad revision ${codemgr_head}"
2669 exit 1
2670 fi
2671 fi
2672
2673 if [[ -z $codemgr_head ]]; then
2674 codemgr_head="HEAD";
2675 fi
2676
2677 # Parent can either be specified with -p, or specified with
2678 # CODEMGR_PARENT in the environment.
2679 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2680 codemgr_parent=$CODEMGR_PARENT
2681 fi
2682
2683 # Try to figure out the parent based on the branch the current
2684 # branch is tracking, if we fail, use origin/master
2685 this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2686 par_branch="origin/master"
2687
2688 # If we're not on a branch there's nothing we can do
2689 if [[ $this_branch != "(no branch)" ]]; then
2690 $GIT for-each-ref \
2691 --format='%(refname:short) %(upstream:short)' \
2692 refs/heads/ | \
2693 while read local remote; do
2694 if [[ "$local" == "$this_branch" ]]; then
2695 par_branch="$remote"
2696 fi
2697 done
2698 fi
2699
2700 if [[ -z $codemgr_parent ]]; then
2701 codemgr_parent=$par_branch
2702 fi
2703 PWS=$codemgr_parent
2704
2705 #
2706 # If the parent is a webrev, we want to do some things against
2707 # the natural workspace parent (file list, comments, etc)
2708 #
2709 if [[ -n $parent_webrev ]]; then
2710 real_parent=$par_branch
2711 else
2712 real_parent=$PWS
2713 fi
2714
2715 if [[ -z $flist_done ]]; then
2716 flist_from_git "$codemgr_head" "$real_parent"
2717 flist_done=1
2718 fi
2719
2720 #
2721 # If we have a file list now, pull out any variables set
2722 # therein.
2723 #
2724 if [[ -n $flist_done ]]; then
2725 env_from_flist
2726 fi
2727
2728 #
2729 # If we don't have a wx-format file list, build one we can pull change
2730 # comments from.
2731 #
2732 if [[ -z $wxfile ]]; then
2733 print " Comments from: git...\c"
2734 git_wxfile "$codemgr_head" "$real_parent"
2735 print " Done."
2736 fi
2737
2738 if [[ -z $GIT_PARENT ]]; then
2739 GIT_PARENT=$($GIT merge-base "$real_parent" "$codemgr_head")
2740 fi
2741 if [[ -z $GIT_PARENT ]]; then
2742 print -u2 "Error: Cannot discover parent revision"
2743 exit 1
2744 fi
2745
2746 pnode=$(trim_digest $GIT_PARENT)
2747
2748 if [[ -n $cflag ]]; then
2749 PRETTY_PWS="previous revision (at ${pnode})"
2750 elif [[ $real_parent == */* ]]; then
2751 origin=$(echo $real_parent | cut -d/ -f1)
2752 origin=$($GIT remote -v | \
2753 $AWK '$1 == "'$origin'" { print $2; exit }')
2754 PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2755 elif [[ -n $pflag && -z $parent_webrev ]]; then
2756 PRETTY_PWS="${CWS} (explicit revision ${pnode})"
2757 else
2758 PRETTY_PWS="${PWS} (at ${pnode})"
2759 fi
2760
2761 cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 \
2762 ${codemgr_head} 2>/dev/null)
2763
2764 if [[ -n $cflag || -n $hflag ]]; then
2765 PRETTY_CWS="${CWS} (explicit head at ${cnode})"
2766 else
2767 PRETTY_CWS="${CWS} (at ${cnode})"
2768 fi
2769 elif [[ $SCM_MODE == "subversion" ]]; then
2770
2771 #
2772 # We only will have a real parent workspace in the case one
2773 # was specified (be it an older webrev, or another checkout).
2774 #
2775 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2776
2777 if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2778 flist_from_subversion $CWS $OLDPWD
2779 fi
2780 else
2781 if [[ $SCM_MODE == "unknown" ]]; then
2782 print -u2 " Unknown type of SCM in use"
2783 else
2784 print -u2 " Unsupported SCM in use: $SCM_MODE"
2785 fi
2786
2787 env_from_flist
2788
2789 if [[ -z $CODEMGR_WS ]]; then
2790 print -u2 "SCM not detected/supported and " \
2791 "CODEMGR_WS not specified"
2792 exit 1
2793 fi
2794
2795 if [[ -z $CODEMGR_PARENT ]]; then
2796 print -u2 "SCM not detected/supported and " \
2797 "CODEMGR_PARENT not specified"
2798 exit 1
2799 fi
2800
2801 CWS=$CODEMGR_WS
2802 PWS=$CODEMGR_PARENT
2803 fi
2804
2805 #
2806 # If the user didn't specify a -i option, check to see if there is a
2807 # webrev-info file in the workspace directory.
2808 #
2809 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2810 iflag=1
2811 INCLUDE_FILE="$CWS/webrev-info"
2812 fi
2813
2814 if [[ -n $iflag ]]; then
2815 if [[ ! -r $INCLUDE_FILE ]]; then
2816 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2817 "not readable."
2818 exit 1
2819 else
2820 #
2821 # $INCLUDE_FILE may be a relative path, and the script alters
2822 # PWD, so we just stash a copy in /tmp.
2823 #
2824 cp $INCLUDE_FILE /tmp/$$.include
2825 fi
2826 fi
2827
2828 # DO_EVERYTHING: break point
2829 if [[ -n $Nflag ]]; then
2830 break
2831 fi
2832
2833 typeset -A itsinfo
2834 typeset -r its_sed_script=/tmp/$$.its_sed
2835 valid_prefixes=
2836 if [[ -z $nflag ]]; then
2837 DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg"
2838 if [[ -n $Iflag ]]; then
2839 REGFILE=$ITSREG
2840 elif [[ -r $HOME/.its.reg ]]; then
2841 REGFILE=$HOME/.its.reg
2842 else
2843 REGFILE=$DEFREGFILE
2844 fi
2845 if [[ ! -r $REGFILE ]]; then
2846 print "ERROR: Unable to read database registry file $REGFILE"
2847 exit 1
2848 elif [[ $REGFILE != $DEFREGFILE ]]; then
2849 print " its.reg from: $REGFILE"
2850 fi
2851
2852 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do
2853
2854 name=${LINE%%=*}
2855 value="${LINE#*=}"
2856
2857 if [[ $name == PREFIX ]]; then
2858 p=${value}
2859 valid_prefixes="${p} ${valid_prefixes}"
2860 else
2861 itsinfo["${p}_${name}"]="${value}"
2862 fi
2863 done
2864
2865
2866 DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf"
2867 CONFFILES=$DEFCONFFILE
2868 if [[ -r $HOME/.its.conf ]]; then
2869 CONFFILES="${CONFFILES} $HOME/.its.conf"
2870 fi
2871 if [[ -n $Cflag ]]; then
2872 CONFFILES="${CONFFILES} ${ITSCONF}"
2873 fi
2874 its_domain=
2875 its_priority=
2876 for cf in ${CONFFILES}; do
2877 if [[ ! -r $cf ]]; then
2878 print "ERROR: Unable to read database configuration file $cf"
2879 exit 1
2880 elif [[ $cf != $DEFCONFFILE ]]; then
2881 print " its.conf: reading $cf"
2882 fi
2883 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do
2884 eval "${LINE}"
2885 done
2886 done
2887
2888 #
2889 # If an information tracking system is explicitly identified by prefix,
2890 # we want to disregard the specified priorities and resolve it accordingly.
2891 #
2892 # To that end, we'll build a sed script to do each valid prefix in turn.
2893 #
2894 for p in ${valid_prefixes}; do
2895 #
2896 # When an informational URL was provided, translate it to a
2897 # hyperlink. When omitted, simply use the prefix text.
2898 #
2899 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2900 itsinfo["${p}_INFO"]=${p}
2901 else
2902 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2903 fi
2904
2905 #
2906 # Assume that, for this invocation of webrev, all references
2907 # to this information tracking system should resolve through
2908 # the same URL.
2909 #
2910 # If the caller specified -O, then always use EXTERNAL_URL.
2911 #
2912 # Otherwise, look in the list of domains for a matching
2913 # INTERNAL_URL.
2914 #
2915 [[ -z $Oflag ]] && for d in ${its_domain}; do
2916 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2917 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2918 break
2919 fi
2920 done
2921 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2922 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2923 fi
2924
2925 #
2926 # Turn the destination URL into a hyperlink
2927 #
2928 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2929
2930 # The character class below contains a literal tab
2931 print "/^${p}[: ]/ {
2932 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2933 s;^${p};${itsinfo[${p}_INFO]};
2934 }" >> ${its_sed_script}
2935 done
2936
2937 #
2938 # The previous loop took care of explicit specification. Now use
2939 # the configured priorities to attempt implicit translations.
2940 #
2941 for p in ${its_priority}; do
2942 print "/^${itsinfo[${p}_REGEX]}[ ]/ {
2943 s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2944 }" >> ${its_sed_script}
2945 done
2946 fi
2947
2948 #
2949 # Search for DO_EVERYTHING above for matching "for" statement
2950 # and explanation of this terminator.
2951 #
2952 done
2953
2954 #
2955 # Output directory.
2956 #
2957 WDIR=${WDIR:-$CWS/webrev}
2958
2959 #
2960 # Name of the webrev, derived from the workspace name or output directory;
2961 # in the future this could potentially be an option.
2962 #
2963 if [[ -n $oflag ]]; then
2964 WNAME=${WDIR##*/}
2965 else
2966 WNAME=${CWS##*/}
2967 fi
2968
2969 # Make sure remote target is well formed for remote upload/delete.
2970 if [[ -n $Dflag || -n $Uflag ]]; then
2971 #
2972 # If remote target is not specified, build it from scratch using
2973 # the default values.
2974 #
2975 if [[ -z $tflag ]]; then
2976 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2977 else
2978 #
2979 # Check upload target prefix first.
2980 #
2981 if [[ "${remote_target}" != ${rsync_prefix}* &&
2982 "${remote_target}" != ${ssh_prefix}* ]]; then
2983 print "ERROR: invalid prefix of upload URI" \
2984 "($remote_target)"
2985 exit 1
2986 fi
2987 #
2988 # If destination specification is not in the form of
2989 # host_spec:remote_dir then assume it is just remote hostname
2990 # and append a colon and destination directory formed from
2991 # local webrev directory name.
2992 #
2993 typeset target_no_prefix=${remote_target##*://}
2994 if [[ ${target_no_prefix} == *:* ]]; then
2995 if [[ "${remote_target}" == *: ]]; then
2996 remote_target=${remote_target}${WNAME}
2997 fi
2998 else
2999 if [[ ${target_no_prefix} == */* ]]; then
3000 print "ERROR: badly formed upload URI" \
3001 "($remote_target)"
3002 exit 1
3003 else
3004 remote_target=${remote_target}:${WNAME}
3005 fi
3006 fi
3007 fi
3008
3009 #
3010 # Strip trailing slash. Each upload method will deal with directory
3011 # specification separately.
3012 #
3013 remote_target=${remote_target%/}
3014 fi
3015
3016 #
3017 # Option -D by itself (option -U not present) implies no webrev generation.
3018 #
3019 if [[ -z $Uflag && -n $Dflag ]]; then
3020 delete_webrev 1 1
3021 exit $?
3022 fi
3023
3024 #
3025 # Do not generate the webrev, just upload it or delete it.
3026 #
3027 if [[ -n $nflag ]]; then
3028 if [[ -n $Dflag ]]; then
3029 delete_webrev 1 1
3030 (( $? == 0 )) || exit $?
3031 fi
3032 if [[ -n $Uflag ]]; then
3033 upload_webrev
3034 exit $?
3035 fi
3036 fi
3037
3038 if [ "${WDIR%%/*}" ]; then
3039 WDIR=$PWD/$WDIR
3040 fi
3041
3042 if [[ ! -d $WDIR ]]; then
3043 mkdir -p $WDIR
3044 (( $? != 0 )) && exit 1
3045 fi
3046
3047 #
3048 # Summarize what we're going to do.
3049 #
3050 print " Workspace: ${PRETTY_CWS:-$CWS}"
3051 if [[ -n $parent_webrev ]]; then
3052 print "Compare against: webrev at $parent_webrev"
3053 else
3054 print "Compare against: ${PRETTY_PWS:-$PWS}"
3055 fi
3056
3057 [[ -n $INCLUDE_FILE ]] && print " Including: $INCLUDE_FILE"
3058 print " Output to: $WDIR"
3059
3060 #
3061 # Save the file list in the webrev dir
3062 #
3063 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
3064
3065 rm -f $WDIR/$WNAME.patch
3066 rm -f $WDIR/$WNAME.ps
3067 rm -f $WDIR/$WNAME.pdf
3068
3069 touch $WDIR/$WNAME.patch
3070
3071 print " Output Files:"
3072
3073 #
3074 # Clean up the file list: Remove comments, blank lines and env variables.
3075 #
3076 $SED -e "s/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean
3077 FLIST=/tmp/$$.flist.clean
3078
3079 #
3080 # First pass through the files: generate the per-file webrev HTML-files.
3081 #
3082 cat $FLIST | while read LINE
3083 do
3084 set - $LINE
3085 P=$1
3086
3087 #
3088 # Normally, each line in the file list is just a pathname of a
3089 # file that has been modified or created in the child. A file
3090 # that is renamed in the child workspace has two names on the
3091 # line: new name followed by the old name.
3092 #
3093 oldname=""
3094 oldpath=""
3095 rename=
3096 if [[ $# -eq 2 ]]; then
3097 PP=$2 # old filename
3098 if [[ -f $PP ]]; then
3099 oldname=" (copied from $PP)"
3100 else
3101 oldname=" (renamed from $PP)"
3102 fi
3103 oldpath="$PP"
3104 rename=1
3105 PDIR=${PP%/*}
3106 if [[ $PDIR == $PP ]]; then
3107 PDIR="." # File at root of workspace
3108 fi
3109
3110 PF=${PP##*/}
3111
3112 DIR=${P%/*}
3113 if [[ $DIR == $P ]]; then
3114 DIR="." # File at root of workspace
3115 fi
3116
3117 F=${P##*/}
3118
3119 else
3120 DIR=${P%/*}
3121 if [[ "$DIR" == "$P" ]]; then
3122 DIR="." # File at root of workspace
3123 fi
3124
3125 F=${P##*/}
3126
3127 PP=$P
3128 PDIR=$DIR
3129 PF=$F
3130 fi
3131
3132 COMM=`getcomments html $P $PP`
3133
3134 print "\t$P$oldname\n\t\t\c"
3135
3136 # Make the webrev mirror directory if necessary
3137 mkdir -p $WDIR/$DIR
3138
3139 #
3140 # We stash old and new files into parallel directories in $WDIR
3141 # and do our diffs there. This makes it possible to generate
3142 # clean looking diffs which don't have absolute paths present.
3143 #
3144
3145 build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3146 continue
3147
3148 #
3149 # Keep the old PWD around, so we can safely switch back after
3150 # diff generation, such that build_old_new runs in a
3151 # consistent environment.
3152 #
3153 OWD=$PWD
3154 cd $WDIR/raw_files
3155
3156 #
3157 # The "git apply" command does not tolerate the spurious
3158 # "./" that we otherwise insert; be careful not to include
3159 # it in the paths that we pass to diff(1).
3160 #
3161 if [[ $PDIR == "." ]]; then
3162 ofile=old/$PF
3163 else
3164 ofile=old/$PDIR/$PF
3165 fi
3166 if [[ $DIR == "." ]]; then
3167 nfile=new/$F
3168 else
3169 nfile=new/$DIR/$F
3170 fi
3171
3172 mv_but_nodiff=
3173 cmp $ofile $nfile > /dev/null 2>&1
3174 if [[ $? == 0 && $rename == 1 ]]; then
3175 mv_but_nodiff=1
3176 fi
3177
3178 #
3179 # If we have old and new versions of the file then run the appropriate
3180 # diffs. This is complicated by a couple of factors:
3181 #
3182 # - renames must be handled specially: we emit a 'remove'
3183 # diff and an 'add' diff
3184 # - new files and deleted files must be handled specially
3185 # - GNU patch doesn't interpret the output of illumos diff
3186 # properly when it comes to adds and deletes. We need to
3187 # do some "cleansing" transformations:
3188 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@
3189 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@
3190 #
3191 cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3192 cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3193
3194 rm -f $WDIR/$DIR/$F.patch
3195 if [[ -z $rename ]]; then
3196 if [ ! -f "$ofile" ]; then
3197 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3198 > $WDIR/$DIR/$F.patch
3199 elif [ ! -f "$nfile" ]; then
3200 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3201 > $WDIR/$DIR/$F.patch
3202 else
3203 diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3204 fi
3205 else
3206 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3207 > $WDIR/$DIR/$F.patch
3208
3209 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3210 >> $WDIR/$DIR/$F.patch
3211 fi
3212
3213 #
3214 # Tack the patch we just made onto the accumulated patch for the
3215 # whole wad.
3216 #
3217 cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3218 print " patch\c"
3219
3220 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3221 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3222 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3223 > $WDIR/$DIR/$F.cdiff.html
3224 print " cdiffs\c"
3225
3226 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3227 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3228 > $WDIR/$DIR/$F.udiff.html
3229 print " udiffs\c"
3230
3231 if [[ -x $WDIFF ]]; then
3232 $WDIFF -c "$COMM" \
3233 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3234 $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3235 if [[ $? -eq 0 ]]; then
3236 print " wdiffs\c"
3237 else
3238 print " wdiffs[fail]\c"
3239 fi
3240 fi
3241
3242 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3243 > $WDIR/$DIR/$F.sdiff.html
3244 print " sdiffs\c"
3245 print " frames\c"
3246
3247 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3248 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3249 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3250 # renamed file: may also have differences
3251 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3252 elif [[ -f $nfile ]]; then
3253 # new file: count added lines
3254 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3255 elif [[ -f $ofile ]]; then
3256 # old file: count deleted lines
3257 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3258 fi
3259
3260 #
3261 # Check if it's man page, and create plain text, html and raw (ascii)
3262 # output for the new version, as well as diffs against old version.
3263 #
3264 if [[ -f "$nfile" && "$nfile" = *.+([0-9])*([a-zA-Z]) && \
3265 -x $MANDOC && -x $COL ]]; then
3266 $MANDOC -Tascii $nfile | $COL -b > $nfile.man.txt
3267 source_to_html txt < $nfile.man.txt > $nfile.man.txt.html
3268 print " man-txt\c"
3269 print "$MANCSS" > $WDIR/raw_files/new/$DIR/man.css
3270 $MANDOC -Thtml -Ostyle=man.css $nfile > $nfile.man.html
3271 print " man-html\c"
3272 $MANDOC -Tascii $nfile > $nfile.man.raw
3273 print " man-raw\c"
3274 if [[ -f "$ofile" && -z $mv_but_nodiff ]]; then
3275 $MANDOC -Tascii $ofile | $COL -b > $ofile.man.txt
3276 ${CDIFFCMD:-diff -bt -C 5} $ofile.man.txt \
3277 $nfile.man.txt > $WDIR/$DIR/$F.man.cdiff
3278 diff_to_html $F $DIR/$F "C" "$COMM" < \
3279 $WDIR/$DIR/$F.man.cdiff > \
3280 $WDIR/$DIR/$F.man.cdiff.html
3281 print " man-cdiffs\c"
3282 ${UDIFFCMD:-diff -bt -U 5} $ofile.man.txt \
3283 $nfile.man.txt > $WDIR/$DIR/$F.man.udiff
3284 diff_to_html $F $DIR/$F "U" "$COMM" < \
3285 $WDIR/$DIR/$F.man.udiff > \
3286 $WDIR/$DIR/$F.man.udiff.html
3287 print " man-udiffs\c"
3288 if [[ -x $WDIFF ]]; then
3289 $WDIFF -c "$COMM" -t "$WNAME Wdiff $DIR/$F" \
3290 $ofile.man.txt $nfile.man.txt > \
3291 $WDIR/$DIR/$F.man.wdiff.html 2>/dev/null
3292 if [[ $? -eq 0 ]]; then
3293 print " man-wdiffs\c"
3294 else
3295 print " man-wdiffs[fail]\c"
3296 fi
3297 fi
3298 sdiff_to_html $ofile.man.txt $nfile.man.txt $F.man $DIR \
3299 "$COMM" > $WDIR/$DIR/$F.man.sdiff.html
3300 print " man-sdiffs\c"
3301 print " man-frames\c"
3302 fi
3303 rm -f $ofile.man.txt $nfile.man.txt
3304 rm -f $WDIR/$DIR/$F.man.cdiff $WDIR/$DIR/$F.man.udiff
3305 fi
3306
3307 #
3308 # Now we generate the postscript for this file. We generate diffs
3309 # only in the event that there is delta, or the file is new (it seems
3310 # tree-killing to print out the contents of deleted files).
3311 #
3312 if [[ -f $nfile ]]; then
3313 ocr=$ofile
3314 [[ ! -f $ofile ]] && ocr=/dev/null
3315
3316 if [[ -z $mv_but_nodiff ]]; then
3317 textcomm=`getcomments text $P $PP`
3318 if [[ -x $CODEREVIEW ]]; then
3319 $CODEREVIEW -y "$textcomm" \
3320 -e $ocr $nfile \
3321 > /tmp/$$.psfile 2>/dev/null &&
3322 cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3323 if [[ $? -eq 0 ]]; then
3324 print " ps\c"
3325 else
3326 print " ps[fail]\c"
3327 fi
3328 fi
3329 fi
3330 fi
3331
3332 if [[ -f $ofile ]]; then
3333 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3334 print " old\c"
3335 fi
3336
3337 if [[ -f $nfile ]]; then
3338 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3339 print " new\c"
3340 fi
3341
3342 cd $OWD
3343
3344 print
3345 done
3346
3347 frame_nav_js > $WDIR/ancnav.js
3348 frame_navigation > $WDIR/ancnav.html
3349
3350 if [[ ! -f $WDIR/$WNAME.ps ]]; then
3351 print " Generating PDF: Skipped: no output available"
3352 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3353 print " Generating PDF: \c"
3354 fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3355 print "Done."
3356 else
3357 print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3358 fi
3359
3360 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3361 # delete it - prevent accidental publishing of closed source
3362
3363 if [[ -n "$Oflag" ]]; then
3364 $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3365 fi
3366
3367 # Now build the index.html file that contains
3368 # links to the source files and their diffs.
3369
3370 cd $CWS
3371
3372 # Save total changed lines for Code Inspection.
3373 print "$TOTL" > $WDIR/TotalChangedLines
3374
3375 print " index.html: \c"
3376 INDEXFILE=$WDIR/index.html
3377 exec 3<&1 # duplicate stdout to FD3.
3378 exec 1<&- # Close stdout.
3379 exec > $INDEXFILE # Open stdout to index file.
3380
3381 print "$HTML<head>$STDHEAD"
3382 print "<title>$WNAME</title>"
3383 print "</head>"
3384 print "<body id=\"SUNWwebrev\">"
3385 print "<div class=\"summary\">"
3386 print "<h2>Code Review for $WNAME</h2>"
3387
3388 print "<table>"
3389
3390 #
3391 # Get the preparer's name:
3392 #
3393 # If the SCM detected is Git, and the configuration property user.name is
3394 # available, use that, but be careful to properly escape angle brackets (HTML
3395 # syntax characters) in the email address.
3396 #
3397 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3398 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3399 #
3400 preparer=
3401 if [[ "$SCM_MODE" == git ]]; then
3402 preparer=$(git config user.name 2>/dev/null)
3403 if [[ -n "$preparer" ]]; then
3404 preparer="$(echo "$preparer" | html_quote)"
3405 fi
3406 fi
3407 if [[ -z "$preparer" ]]; then
3408 preparer=$(
3409 $PERL -e '
3410 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3411 if ($login) {
3412 $gcos =~ s/\&/ucfirst($login)/e;
3413 printf "%s (%s)\n", $gcos, $login;
3414 } else {
3415 printf "(unknown)\n";
3416 }
3417 ')
3418 fi
3419
3420 PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3421 print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3422 print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3423 print "</td></tr>"
3424 print "<tr><th>Compare against:</th><td>"
3425 if [[ -n $parent_webrev ]]; then
3426 print "webrev at $parent_webrev"
3427 else
3428 print "${PRETTY_PWS:-$PWS}"
3429 fi
3430 print "</td></tr>"
3431 print "<tr><th>Summary of changes:</th><td>"
3432 printCI $TOTL $TINS $TDEL $TMOD $TUNC
3433 print "</td></tr>"
3434
3435 if [[ -f $WDIR/$WNAME.patch ]]; then
3436 wpatch_url="$(print $WNAME.patch | url_encode)"
3437 print "<tr><th>Patch of changes:</th><td>"
3438 print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3439 fi
3440 if [[ -f $WDIR/$WNAME.pdf ]]; then
3441 wpdf_url="$(print $WNAME.pdf | url_encode)"
3442 print "<tr><th>Printable review:</th><td>"
3443 print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3444 fi
3445
3446 if [[ -n "$iflag" ]]; then
3447 print "<tr><th>Author comments:</th><td><div>"
3448 cat /tmp/$$.include
3449 print "</div></td></tr>"
3450 fi
3451 print "</table>"
3452 print "</div>"
3453
3454 #
3455 # Second pass through the files: generate the rest of the index file
3456 #
3457 cat $FLIST | while read LINE
3458 do
3459 set - $LINE
3460 P=$1
3461
3462 if [[ $# == 2 ]]; then
3463 PP=$2
3464 oldname="$PP"
3465 else
3466 PP=$P
3467 oldname=""
3468 fi
3469
3470 mv_but_nodiff=
3471 cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3472 if [[ $? == 0 && -n "$oldname" ]]; then
3473 mv_but_nodiff=1
3474 fi
3475
3476 DIR=${P%/*}
3477 if [[ $DIR == $P ]]; then
3478 DIR="." # File at root of workspace
3479 fi
3480
3481 # Avoid processing the same file twice.
3482 # It's possible for renamed files to
3483 # appear twice in the file list
3484
3485 F=$WDIR/$P
3486
3487 print "<p>"
3488
3489 # If there's a diffs file, make diffs links
3490
3491 if [[ -f $F.cdiff.html ]]; then
3492 cdiff_url="$(print $P.cdiff.html | url_encode)"
3493 udiff_url="$(print $P.udiff.html | url_encode)"
3494 sdiff_url="$(print $P.sdiff.html | url_encode)"
3495 frames_url="$(print $P.frames.html | url_encode)"
3496 print "<a href=\"$cdiff_url\">Cdiffs</a>"
3497 print "<a href=\"$udiff_url\">Udiffs</a>"
3498 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3499 wdiff_url="$(print $P.wdiff.html | url_encode)"
3500 print "<a href=\"$wdiff_url\">Wdiffs</a>"
3501 fi
3502 print "<a href=\"$sdiff_url\">Sdiffs</a>"
3503 print "<a href=\"$frames_url\">Frames</a>"
3504 else
3505 print " ------ ------"
3506 if [[ -x $WDIFF ]]; then
3507 print " ------"
3508 fi
3509 print " ------ ------"
3510 fi
3511
3512 # If there's an old file, make the link
3513
3514 if [[ -f $F-.html ]]; then
3515 oldfile_url="$(print $P-.html | url_encode)"
3516 print "<a href=\"$oldfile_url\">Old</a>"
3517 else
3518 print " ---"
3519 fi
3520
3521 # If there's an new file, make the link
3522
3523 if [[ -f $F.html ]]; then
3524 newfile_url="$(print $P.html | url_encode)"
3525 print "<a href=\"$newfile_url\">New</a>"
3526 else
3527 print " ---"
3528 fi
3529
3530 if [[ -f $F.patch ]]; then
3531 patch_url="$(print $P.patch | url_encode)"
3532 print "<a href=\"$patch_url\">Patch</a>"
3533 else
3534 print " -----"
3535 fi
3536
3537 if [[ -f $WDIR/raw_files/new/$P ]]; then
3538 rawfiles_url="$(print raw_files/new/$P | url_encode)"
3539 print "<a href=\"$rawfiles_url\">Raw</a>"
3540 else
3541 print " ---"
3542 fi
3543
3544 print "<b>$P</b>"
3545
3546 # For renamed files, clearly state whether or not they are modified
3547 if [[ -f "$oldname" ]]; then
3548 if [[ -n "$mv_but_nodiff" ]]; then
3549 print "<i>(copied from $oldname)</i>"
3550 else
3551 print "<i>(copied and modified from $oldname)</i>"
3552 fi
3553 elif [[ -n "$oldname" ]]; then
3554 if [[ -n "$mv_but_nodiff" ]]; then
3555 print "<i>(renamed from $oldname)</i>"
3556 else
3557 print "<i>(renamed and modified from $oldname)</i>"
3558 fi
3559 fi
3560
3561 # If there's an old file, but no new file, the file was deleted
3562 if [[ -f $F-.html && ! -f $F.html ]]; then
3563 print " <i>(deleted)</i>"
3564 fi
3565
3566 # Check for usr/closed and deleted_files/usr/closed
3567 if [ ! -z "$Oflag" ]; then
3568 if [[ $P == usr/closed/* || \
3569 $P == deleted_files/usr/closed/* ]]; then
3570 print " <i>Closed source: omitted from" \
3571 "this review</i>"
3572 fi
3573 fi
3574
3575 manpage=
3576 if [[ -f $F.man.cdiff.html || \
3577 -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3578 manpage=1
3579 print "<br/>man:"
3580 fi
3581
3582 if [[ -f $F.man.cdiff.html ]]; then
3583 mancdiff_url="$(print $P.man.cdiff.html | url_encode)"
3584 manudiff_url="$(print $P.man.udiff.html | url_encode)"
3585 mansdiff_url="$(print $P.man.sdiff.html | url_encode)"
3586 manframes_url="$(print $P.man.frames.html | url_encode)"
3587 print "<a href=\"$mancdiff_url\">Cdiffs</a>"
3588 print "<a href=\"$manudiff_url\">Udiffs</a>"
3589 if [[ -f $F.man.wdiff.html && -x $WDIFF ]]; then
3590 manwdiff_url="$(print $P.man.wdiff.html | url_encode)"
3591 print "<a href=\"$manwdiff_url\">Wdiffs</a>"
3592 fi
3593 print "<a href=\"$mansdiff_url\">Sdiffs</a>"
3594 print "<a href=\"$manframes_url\">Frames</a>"
3595 elif [[ -n $manpage ]]; then
3596 print " ------ ------"
3597 if [[ -x $WDIFF ]]; then
3598 print " ------"
3599 fi
3600 print " ------ ------"
3601 fi
3602
3603 if [[ -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3604 mantxt_url="$(print raw_files/new/$P.man.txt.html | url_encode)"
3605 print "<a href=\"$mantxt_url\">TXT</a>"
3606 manhtml_url="$(print raw_files/new/$P.man.html | url_encode)"
3607 print "<a href=\"$manhtml_url\">HTML</a>"
3608 manraw_url="$(print raw_files/new/$P.man.raw | url_encode)"
3609 print "<a href=\"$manraw_url\">Raw</a>"
3610 elif [[ -n $manpage ]]; then
3611 print " --- ---- ---"
3612 fi
3613
3614 print "</p>"
3615
3616 # Insert delta comments
3617 print "<blockquote><pre>"
3618 getcomments html $P $PP
3619 print "</pre>"
3620
3621 # Add additional comments comment
3622 print "<!-- Add comments to explain changes in $P here -->"
3623
3624 # Add count of changes.
3625 if [[ -f $F.count ]]; then
3626 cat $F.count
3627 rm $F.count
3628 fi
3629
3630 if [[ $SCM_MODE == "unknown" ]]; then
3631 # Include warnings for important file mode situations:
3632 # 1) New executable files
3633 # 2) Permission changes of any kind
3634 # 3) Existing executable files
3635 old_mode=
3636 if [[ -f $WDIR/raw_files/old/$PP ]]; then
3637 old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3638 fi
3639
3640 new_mode=
3641 if [[ -f $WDIR/raw_files/new/$P ]]; then
3642 new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3643 fi
3644
3645 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3646 print "<span class=\"chmod\">"
3647 print "<p>new executable file: mode $new_mode</p>"
3648 print "</span>"
3649 elif [[ -n "$old_mode" && -n "$new_mode" &&
3650 "$old_mode" != "$new_mode" ]]; then
3651 print "<span class=\"chmod\">"
3652 print "<p>mode change: $old_mode to $new_mode</p>"
3653 print "</span>"
3654 elif [[ "$new_mode" = *[1357]* ]]; then
3655 print "<span class=\"chmod\">"
3656 print "<p>executable file: mode $new_mode</p>"
3657 print "</span>"
3658 fi
3659 fi
3660
3661 print "</blockquote>"
3662 done
3663
3664 print
3665 print
3666 print "<hr></hr>"
3667 print "<p style=\"font-size: small\">"
3668 print "This code review page was prepared using <b>$0</b>."
3669 print "Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3670 print "illumos</a> project. The latest version may be obtained"
3671 print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3672 print "</body>"
3673 print "</html>"
3674
3675 exec 1<&- # Close FD 1.
3676 exec 1<&3 # dup FD 3 to restore stdout.
3677 exec 3<&- # close FD 3.
3678
3679 print "Done."
3680
3681 #
3682 # If remote deletion was specified and fails do not continue.
3683 #
3684 if [[ -n $Dflag ]]; then
3685 delete_webrev 1 1
3686 (( $? == 0 )) || exit $?
3687 fi
3688
3689 if [[ -n $Uflag ]]; then
3690 upload_webrev
3691 exit $?
3692 fi