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