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