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