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