6857 webrev: add -c and -h options to specify "head" revision
[unleashed.git] / usr / src / tools / scripts / webrev.sh
blob5fbed75864cc59a40f5f3cbd758dbe69afcb2049
1 #!/usr/bin/ksh93 -p
3 # CDDL HEADER START
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.
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.
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]
20 # CDDL HEADER END
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.
32 # This script takes a file list and a workspace and builds a set of html files
33 # suitable for doing a code review of source changes via a web page.
34 # Documentation is available via the manual page, webrev.1, or just
35 # type 'webrev -h'.
37 # Acknowledgements to contributors to webrev are listed in the webrev(1)
38 # man page.
41 REMOVED_COLOR=brown
42 CHANGED_COLOR=blue
43 NEW_COLOR=blue
45 HTML='<?xml version="1.0"?>
46 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
47 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
48 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
50 FRAMEHTML='<?xml version="1.0"?>
51 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
52 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
53 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
55 STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta>
56 <meta http-equiv="Content-Type" content="text/xhtml;charset=utf-8"></meta>
57 <meta http-equiv="Pragma" content="no-cache"></meta>
58 <meta http-equiv="Expires" content="-1"></meta>
59 <!--
60 Note to customizers: the body of the webrev is IDed as SUNWwebrev
61 to allow easy overriding by users of webrev via the userContent.css
62 mechanism available in some browsers.
64 For example, to have all "removed" information be red instead of
65 brown, set a rule in your userContent.css file like:
67 body#SUNWwebrev span.removed { color: red ! important; }
68 -->
69 <style type="text/css" media="screen">
70 body {
71 background-color: #eeeeee;
73 hr {
74 border: none 0;
75 border-top: 1px solid #aaa;
76 height: 1px;
78 div.summary {
79 font-size: .8em;
80 border-bottom: 1px solid #aaa;
81 padding-left: 1em;
82 padding-right: 1em;
84 div.summary h2 {
85 margin-bottom: 0.3em;
87 div.summary table th {
88 text-align: right;
89 vertical-align: top;
90 white-space: nowrap;
92 span.lineschanged {
93 font-size: 0.7em;
95 span.oldmarker {
96 color: red;
97 font-size: large;
98 font-weight: bold;
100 span.newmarker {
101 color: green;
102 font-size: large;
103 font-weight: bold;
105 span.removed {
106 color: brown;
108 span.changed {
109 color: blue;
111 span.new {
112 color: blue;
113 font-weight: bold;
115 span.chmod {
116 font-size: 0.7em;
117 color: #db7800;
119 a.print { font-size: x-small; }
120 a:hover { background-color: #ffcc99; }
121 </style>
123 <style type="text/css" media="print">
124 pre { font-size: 0.8em; font-family: courier, monospace; }
125 span.removed { color: #444; font-style: italic }
126 span.changed { font-weight: bold; }
127 span.new { font-weight: bold; }
128 span.newmarker { font-size: 1.2em; font-weight: bold; }
129 span.oldmarker { font-size: 1.2em; font-weight: bold; }
130 a.print {display: none}
131 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
132 </style>
136 # UDiffs need a slightly different CSS rule for 'new' items (we don't
137 # want them to be bolded as we do in cdiffs or sdiffs).
139 UDIFFCSS='
140 <style type="text/css" media="screen">
141 span.new {
142 color: blue;
143 font-weight: normal;
145 </style>
149 # CSS for the HTML version of the man pages.
151 MANCSS='
152 html { max-width: 880px; margin-left: 1em; }
153 body { font-size: smaller; font-family: Helvetica,Arial,sans-serif; }
154 h1 { margin-bottom: 1ex; font-size: 110%; margin-left: -4ex; }
155 h2 { margin-bottom: 1ex; font-size: 105%; margin-left: -2ex; }
156 table { width: 100%; margin-top: 0ex; margin-bottom: 0ex; }
157 td { vertical-align: top; }
158 blockquote { margin-left: 5ex; margin-top: 0ex; margin-bottom: 0ex; }
159 div.section { margin-bottom: 2ex; margin-left: 5ex; }
160 table.foot { font-size: smaller; margin-top: 1em;
161 border-top: 1px dotted #dddddd; }
162 td.foot-date { width: 50%; }
163 td.foot-os { width: 50%; text-align: right; }
164 table.head { font-size: smaller; margin-bottom: 1em;
165 border-bottom: 1px dotted #dddddd; }
166 td.head-ltitle { width: 10%; }
167 td.head-vol { width: 80%; text-align: center; }
168 td.head-rtitle { width: 10%; text-align: right; }
169 .emph { font-style: italic; font-weight: normal; }
170 .symb { font-style: normal; font-weight: bold; }
171 .lit { font-style: normal; font-weight: normal; font-family: monospace; }
172 i.addr { font-weight: normal; }
173 i.arg { font-weight: normal; }
174 b.cmd { font-style: normal; }
175 b.config { font-style: normal; }
176 b.diag { font-style: normal; }
177 i.farg { font-weight: normal; }
178 i.file { font-weight: normal; }
179 b.flag { font-style: normal; }
180 b.fname { font-style: normal; }
181 i.ftype { font-weight: normal; }
182 b.includes { font-style: normal; }
183 i.link-sec { font-weight: normal; }
184 b.macro { font-style: normal; }
185 b.name { font-style: normal; }
186 i.ref-book { font-weight: normal; }
187 i.ref-issue { font-weight: normal; }
188 i.ref-jrnl { font-weight: normal; }
189 span.ref-title { text-decoration: underline; }
190 span.type { font-style: italic; font-weight: normal; }
191 b.utility { font-style: normal; }
192 b.var { font-style: normal; }
193 dd.list-ohang { margin-left: 0ex; }
194 ul.list-bul { list-style-type: disc; padding-left: 1em; }
195 ul.list-dash { list-style-type: none; padding-left: 0em; }
196 li.list-dash:before { content: "\2014 "; }
197 ul.list-hyph { list-style-type: none; padding-left: 0em; }
198 li.list-hyph:before { content: "\2013 "; }
199 ul.list-item { list-style-type: none; padding-left: 0em; }
200 ol.list-enum { padding-left: 2em; }
204 # Display remote target with prefix and trailing slash.
206 function print_upload_header
208 typeset -r prefix=$1
209 typeset display_target
211 if [[ -z $tflag ]]; then
212 display_target=${prefix}${remote_target}
213 else
214 display_target=${remote_target}
217 if [[ ${display_target} != */ ]]; then
218 display_target=${display_target}/
221 print " Upload to: ${display_target}\n" \
222 " Uploading: \c"
226 # Upload the webrev via rsync. Return 0 on success, 1 on error.
228 function rsync_upload
230 if (( $# != 2 )); then
231 print "\nERROR: rsync_upload: wrong usage ($#)"
232 exit 1
235 typeset -r dst=$1
236 integer -r print_err_msg=$2
238 print_upload_header ${rsync_prefix}
239 print "rsync ... \c"
240 typeset -r err_msg=$( $MKTEMP /tmp/rsync_err.XXXXXX )
241 if [[ -z $err_msg ]]; then
242 print "\nERROR: rsync_upload: cannot create temporary file"
243 return 1
246 # The source directory must end with a slash in order to copy just
247 # directory contents, not the whole directory.
249 typeset src_dir=$WDIR
250 if [[ ${src_dir} != */ ]]; then
251 src_dir=${src_dir}/
253 $RSYNC -r -q ${src_dir} $dst 2>$err_msg
254 if (( $? != 0 )); then
255 if (( ${print_err_msg} > 0 )); then
256 print "Failed.\nERROR: rsync failed"
257 print "src dir: '${src_dir}'\ndst dir: '$dst'"
258 print "error messages:"
259 $SED 's/^/> /' $err_msg
260 rm -f $err_msg
262 return 1
265 rm -f $err_msg
266 print "Done."
267 return 0
271 # Create directories on remote host using SFTP. Return 0 on success,
272 # 1 on failure.
274 function remote_mkdirs
276 typeset -r dir_spec=$1
277 typeset -r host_spec=$2
280 # If the supplied path is absolute we assume all directories are
281 # created, otherwise try to create all directories in the path
282 # except the last one which will be created by scp.
284 if [[ "${dir_spec}" == */* && "${dir_spec}" != /* ]]; then
285 print "mkdirs \c"
287 # Remove the last directory from directory specification.
289 typeset -r dirs_mk=${dir_spec%/*}
290 typeset -r batch_file_mkdir=$( $MKTEMP \
291 /tmp/webrev_mkdir.XXXXXX )
292 if [[ -z $batch_file_mkdir ]]; then
293 print "\nERROR: remote_mkdirs:" \
294 "cannot create temporary file for batch file"
295 return 1
297 OLDIFS=$IFS
298 IFS=/
299 typeset dir
300 for dir in ${dirs_mk}; do
302 # Use the '-' prefix to ignore mkdir errors in order
303 # to avoid an error in case the directory already
304 # exists. We check the directory with chdir to be sure
305 # there is one.
307 print -- "-mkdir ${dir}" >> ${batch_file_mkdir}
308 print "chdir ${dir}" >> ${batch_file_mkdir}
309 done
310 IFS=$OLDIFS
311 typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
312 if [[ -z ${sftp_err_msg} ]]; then
313 print "\nERROR: remote_mkdirs:" \
314 "cannot create temporary file for error messages"
315 return 1
317 $SFTP -b ${batch_file_mkdir} ${host_spec} 2>${sftp_err_msg} 1>&2
318 if (( $? != 0 )); then
319 print "\nERROR: failed to create remote directories"
320 print "error messages:"
321 $SED 's/^/> /' ${sftp_err_msg}
322 rm -f ${sftp_err_msg} ${batch_file_mkdir}
323 return 1
325 rm -f ${sftp_err_msg} ${batch_file_mkdir}
328 return 0
332 # Upload the webrev via SSH. Return 0 on success, 1 on error.
334 function ssh_upload
336 if (( $# != 1 )); then
337 print "\nERROR: ssh_upload: wrong number of arguments"
338 exit 1
341 typeset dst=$1
342 typeset -r host_spec=${dst%%:*}
343 typeset -r dir_spec=${dst#*:}
346 # Display the upload information before calling delete_webrev
347 # because it will also print its progress.
349 print_upload_header ${ssh_prefix}
352 # If the deletion was explicitly requested there is no need
353 # to perform it again.
355 if [[ -z $Dflag ]]; then
357 # We do not care about return value because this might be
358 # the first time this directory is uploaded.
360 delete_webrev 0
364 # Create remote directories. Any error reporting will be done
365 # in remote_mkdirs function.
367 remote_mkdirs ${dir_spec} ${host_spec}
368 if (( $? != 0 )); then
369 return 1
372 print "upload ... \c"
373 typeset -r scp_err_msg=$( $MKTEMP /tmp/scp_err.XXXXXX )
374 if [[ -z ${scp_err_msg} ]]; then
375 print "\nERROR: ssh_upload:" \
376 "cannot create temporary file for error messages"
377 return 1
379 $SCP -q -C -B -o PreferredAuthentications=publickey -r \
380 $WDIR $dst 2>${scp_err_msg}
381 if (( $? != 0 )); then
382 print "Failed.\nERROR: scp failed"
383 print "src dir: '$WDIR'\ndst dir: '$dst'"
384 print "error messages:"
385 $SED 's/^/> /' ${scp_err_msg}
386 rm -f ${scp_err_msg}
387 return 1
390 rm -f ${scp_err_msg}
391 print "Done."
392 return 0
396 # Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp
397 # on failure. If first argument is 1 then perform the check of sftp return
398 # value otherwise ignore it. If second argument is present it means this run
399 # only performs deletion.
401 function delete_webrev
403 if (( $# < 1 )); then
404 print "delete_webrev: wrong number of arguments"
405 exit 1
408 integer -r check=$1
409 integer delete_only=0
410 if (( $# == 2 )); then
411 delete_only=1
415 # Strip the transport specification part of remote target first.
417 typeset -r stripped_target=${remote_target##*://}
418 typeset -r host_spec=${stripped_target%%:*}
419 typeset -r dir_spec=${stripped_target#*:}
420 typeset dir_rm
423 # Do not accept an absolute path.
425 if [[ ${dir_spec} == /* ]]; then
426 return 1
430 # Strip the ending slash.
432 if [[ ${dir_spec} == */ ]]; then
433 dir_rm=${dir_spec%%/}
434 else
435 dir_rm=${dir_spec}
438 if (( ${delete_only} > 0 )); then
439 print " Removing: \c"
440 else
441 print "rmdir \c"
443 if [[ -z "$dir_rm" ]]; then
444 print "\nERROR: empty directory for removal"
445 return 1
449 # Prepare batch file.
451 typeset -r batch_file_rm=$( $MKTEMP /tmp/webrev_remove.XXXXXX )
452 if [[ -z $batch_file_rm ]]; then
453 print "\nERROR: delete_webrev: cannot create temporary file"
454 return 1
456 print "rename $dir_rm $TRASH_DIR/removed.$$" > $batch_file_rm
459 # Perform remote deletion and remove the batch file.
461 typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
462 if [[ -z ${sftp_err_msg} ]]; then
463 print "\nERROR: delete_webrev:" \
464 "cannot create temporary file for error messages"
465 return 1
467 $SFTP -b $batch_file_rm $host_spec 2>${sftp_err_msg} 1>&2
468 integer -r ret=$?
469 rm -f $batch_file_rm
470 if (( $ret != 0 && $check > 0 )); then
471 print "Failed.\nERROR: failed to remove remote directories"
472 print "error messages:"
473 $SED 's/^/> /' ${sftp_err_msg}
474 rm -f ${sftp_err_msg}
475 return $ret
477 rm -f ${sftp_err_msg}
478 if (( ${delete_only} > 0 )); then
479 print "Done."
482 return 0
486 # Upload webrev to remote site
488 function upload_webrev
490 integer ret
492 if [[ ! -d "$WDIR" ]]; then
493 print "\nERROR: webrev directory '$WDIR' does not exist"
494 return 1
498 # Perform a late check to make sure we do not upload closed source
499 # to remote target when -n is used. If the user used custom remote
500 # target he probably knows what he is doing.
502 if [[ -n $nflag && -z $tflag ]]; then
503 $FIND $WDIR -type d -name closed \
504 | $GREP closed >/dev/null
505 if (( $? == 0 )); then
506 print "\nERROR: directory '$WDIR' contains" \
507 "\"closed\" directory"
508 return 1
514 # We have the URI for remote destination now so let's start the upload.
516 if [[ -n $tflag ]]; then
517 if [[ "${remote_target}" == ${rsync_prefix}?* ]]; then
518 rsync_upload ${remote_target##$rsync_prefix} 1
519 ret=$?
520 return $ret
521 elif [[ "${remote_target}" == ${ssh_prefix}?* ]]; then
522 ssh_upload ${remote_target##$ssh_prefix}
523 ret=$?
524 return $ret
526 else
528 # Try rsync first and fallback to SSH in case it fails.
530 rsync_upload ${remote_target} 0
531 ret=$?
532 if (( $ret != 0 )); then
533 print "Failed. (falling back to SSH)"
534 ssh_upload ${remote_target}
535 ret=$?
537 return $ret
542 # input_cmd | url_encode | output_cmd
544 # URL-encode (percent-encode) reserved characters as defined in RFC 3986.
546 # Reserved characters are: :/?#[]@!$&'()*+,;=
548 # While not a reserved character itself, percent '%' is reserved by definition
549 # so encode it first to avoid recursive transformation, and skip '/' which is
550 # a path delimiter.
552 # The quotation character is deliberately not escaped in order to make
553 # the substitution work with GNU sed.
555 function url_encode
557 $SED -e "s|%|%25|g" -e "s|:|%3A|g" -e "s|\&|%26|g" \
558 -e "s|?|%3F|g" -e "s|#|%23|g" -e "s|\[|%5B|g" \
559 -e "s|*|%2A|g" -e "s|@|%40|g" -e "s|\!|%21|g" \
560 -e "s|=|%3D|g" -e "s|;|%3B|g" -e "s|\]|%5D|g" \
561 -e "s|(|%28|g" -e "s|)|%29|g" -e "s|'|%27|g" \
562 -e "s|+|%2B|g" -e "s|\,|%2C|g" -e "s|\\\$|%24|g"
566 # input_cmd | html_quote | output_cmd
567 # or
568 # html_quote filename | output_cmd
570 # Make a piece of source code safe for display in an HTML <pre> block.
572 html_quote()
574 $SED -e "s/&/\&amp;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g" "$@" | expand
578 # Trim a digest-style revision to a conventionally readable yet useful length
580 trim_digest()
582 typeset digest=$1
584 echo $digest | $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'
588 # input_cmd | its2url | output_cmd
590 # Scan for information tracking system references and insert <a> links to the
591 # relevant databases.
593 its2url()
595 $SED -f ${its_sed_script}
599 # strip_unchanged <infile> | output_cmd
601 # Removes chunks of sdiff documents that have not changed. This makes it
602 # easier for a code reviewer to find the bits that have changed.
604 # Deleted lines of text are replaced by a horizontal rule. Some
605 # identical lines are retained before and after the changed lines to
606 # provide some context. The number of these lines is controlled by the
607 # variable C in the $AWK script below.
609 # The script detects changed lines as any line that has a "<span class="
610 # string embedded (unchanged lines have no particular class and are not
611 # part of a <span>). Blank lines (without a sequence number) are also
612 # detected since they flag lines that have been inserted or deleted.
614 strip_unchanged()
616 $AWK '
617 BEGIN { C = c = 20 }
618 NF == 0 || /<span class="/ {
619 if (c > C) {
620 c -= C
621 inx = 0
622 if (c > C) {
623 print "\n</pre><hr></hr><pre>"
624 inx = c % C
625 c = C
628 for (i = 0; i < c; i++)
629 print ln[(inx + i) % C]
631 c = 0;
632 print
633 next
635 { if (c >= C) {
636 ln[c % C] = $0
637 c++;
638 next;
640 c++;
641 print
643 END { if (c > (C * 2)) print "\n</pre><hr></hr>" }
645 ' $1
649 # sdiff_to_html
651 # This function takes two files as arguments, obtains their diff, and
652 # processes the diff output to present the files as an HTML document with
653 # the files displayed side-by-side, differences shown in color. It also
654 # takes a delta comment, rendered as an HTML snippet, as the third
655 # argument. The function takes two files as arguments, then the name of
656 # file, the path, and the comment. The HTML will be delivered on stdout,
657 # e.g.
659 # $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
660 # new/usr/src/tools/scripts/webrev.sh \
661 # webrev.sh usr/src/tools/scripts \
662 # '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
663 # 1234567</a> my bugid' > <file>.html
665 # framed_sdiff() is then called which creates $2.frames.html
666 # in the webrev tree.
668 # FYI: This function is rather unusual in its use of awk. The initial
669 # diff run produces conventional diff output showing changed lines mixed
670 # with editing codes. The changed lines are ignored - we're interested in
671 # the editing codes, e.g.
673 # 8c8
674 # 57a61
675 # 63c66,76
676 # 68,93d80
677 # 106d90
678 # 108,110d91
680 # These editing codes are parsed by the awk script and used to generate
681 # another awk script that generates HTML, e.g the above lines would turn
682 # into something like this:
684 # BEGIN { printf "<pre>\n" }
685 # function sp(n) {for (i=0;i<n;i++)printf "\n"}
686 # function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
687 # NR==8 {wl("#7A7ADD");next}
688 # NR==54 {wl("#7A7ADD");sp(3);next}
689 # NR==56 {wl("#7A7ADD");next}
690 # NR==57 {wl("black");printf "\n"; next}
691 # : :
693 # This script is then run on the original source file to generate the
694 # HTML that corresponds to the source file.
696 # The two HTML files are then combined into a single piece of HTML that
697 # uses an HTML table construct to present the files side by side. You'll
698 # notice that the changes are color-coded:
700 # black - unchanged lines
701 # blue - changed lines
702 # bold blue - new lines
703 # brown - deleted lines
705 # Blank lines are inserted in each file to keep unchanged lines in sync
706 # (side-by-side). This format is familiar to users of sdiff(1) or
707 # Teamware's filemerge tool.
709 sdiff_to_html()
711 diff -b $1 $2 > /tmp/$$.diffs
713 TNAME=$3
714 TPATH=$4
715 COMMENT=$5
718 # Now we have the diffs, generate the HTML for the old file.
720 $AWK '
721 BEGIN {
722 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
723 printf "function removed() "
724 printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
725 printf "function changed() "
726 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
727 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
729 /^</ {next}
730 /^>/ {next}
731 /^---/ {next}
734 split($1, a, /[cad]/) ;
735 if (index($1, "a")) {
736 if (a[1] == 0) {
737 n = split(a[2], r, /,/);
738 if (n == 1)
739 printf "BEGIN\t\t{sp(1)}\n"
740 else
741 printf "BEGIN\t\t{sp(%d)}\n",\
742 (r[2] - r[1]) + 1
743 next
746 printf "NR==%s\t\t{", a[1]
747 n = split(a[2], r, /,/);
748 s = r[1];
749 if (n == 1)
750 printf "bl();printf \"\\n\"; next}\n"
751 else {
752 n = r[2] - r[1]
753 printf "bl();sp(%d);next}\n",\
754 (r[2] - r[1]) + 1
756 next
758 if (index($1, "d")) {
759 n = split(a[1], r, /,/);
760 n1 = r[1]
761 n2 = r[2]
762 if (n == 1)
763 printf "NR==%s\t\t{removed(); next}\n" , n1
764 else
765 printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
766 next
768 if (index($1, "c")) {
769 n = split(a[1], r, /,/);
770 n1 = r[1]
771 n2 = r[2]
772 final = n2
773 d1 = 0
774 if (n == 1)
775 printf "NR==%s\t\t{changed();" , n1
776 else {
777 d1 = n2 - n1
778 printf "NR==%s,NR==%s\t{changed();" , n1, n2
780 m = split(a[2], r, /,/);
781 n1 = r[1]
782 n2 = r[2]
783 if (m > 1) {
784 d2 = n2 - n1
785 if (d2 > d1) {
786 if (n > 1) printf "if (NR==%d)", final
787 printf "sp(%d);", d2 - d1
790 printf "next}\n" ;
792 next
796 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
797 ' /tmp/$$.diffs > /tmp/$$.file1
800 # Now generate the HTML for the new file
802 $AWK '
803 BEGIN {
804 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
805 printf "function new() "
806 printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
807 printf "function changed() "
808 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
809 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
812 /^</ {next}
813 /^>/ {next}
814 /^---/ {next}
817 split($1, a, /[cad]/) ;
818 if (index($1, "d")) {
819 if (a[2] == 0) {
820 n = split(a[1], r, /,/);
821 if (n == 1)
822 printf "BEGIN\t\t{sp(1)}\n"
823 else
824 printf "BEGIN\t\t{sp(%d)}\n",\
825 (r[2] - r[1]) + 1
826 next
829 printf "NR==%s\t\t{", a[2]
830 n = split(a[1], r, /,/);
831 s = r[1];
832 if (n == 1)
833 printf "bl();printf \"\\n\"; next}\n"
834 else {
835 n = r[2] - r[1]
836 printf "bl();sp(%d);next}\n",\
837 (r[2] - r[1]) + 1
839 next
841 if (index($1, "a")) {
842 n = split(a[2], r, /,/);
843 n1 = r[1]
844 n2 = r[2]
845 if (n == 1)
846 printf "NR==%s\t\t{new() ; next}\n" , n1
847 else
848 printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
849 next
851 if (index($1, "c")) {
852 n = split(a[2], r, /,/);
853 n1 = r[1]
854 n2 = r[2]
855 final = n2
856 d2 = 0;
857 if (n == 1) {
858 final = n1
859 printf "NR==%s\t\t{changed();" , n1
860 } else {
861 d2 = n2 - n1
862 printf "NR==%s,NR==%s\t{changed();" , n1, n2
864 m = split(a[1], r, /,/);
865 n1 = r[1]
866 n2 = r[2]
867 if (m > 1) {
868 d1 = n2 - n1
869 if (d1 > d2) {
870 if (n > 1) printf "if (NR==%d)", final
871 printf "sp(%d);", d1 - d2
874 printf "next}\n" ;
875 next
878 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
879 ' /tmp/$$.diffs > /tmp/$$.file2
882 # Post-process the HTML files by running them back through $AWK
884 html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html
886 html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html
889 # Now combine into a valid HTML file and side-by-side into a table
891 print "$HTML<head>$STDHEAD"
892 print "<title>$WNAME Sdiff $TPATH/$TNAME</title>"
893 print "</head><body id=\"SUNWwebrev\">"
894 print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
895 print "<pre>$COMMENT</pre>\n"
896 print "<table><tr valign=\"top\">"
897 print "<td><pre>"
899 strip_unchanged /tmp/$$.file1.html
901 print "</pre></td><td><pre>"
903 strip_unchanged /tmp/$$.file2.html
905 print "</pre></td>"
906 print "</tr></table>"
907 print "</body></html>"
909 framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
910 "$COMMENT"
915 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
917 # Expects lefthand and righthand side html files created by sdiff_to_html.
918 # We use insert_anchors() to augment those with HTML navigation anchors,
919 # and then emit the main frame. Content is placed into:
921 # $WDIR/DIR/$TNAME.lhs.html
922 # $WDIR/DIR/$TNAME.rhs.html
923 # $WDIR/DIR/$TNAME.frames.html
925 # NOTE: We rely on standard usage of $WDIR and $DIR.
927 function framed_sdiff
929 typeset TNAME=$1
930 typeset TPATH=$2
931 typeset lhsfile=$3
932 typeset rhsfile=$4
933 typeset comments=$5
934 typeset RTOP
936 # Enable html files to access WDIR via a relative path.
937 RTOP=$(relative_dir $TPATH $WDIR)
939 # Make the rhs/lhs files and output the frameset file.
940 print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
942 cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
943 <script type="text/javascript" src="${RTOP}ancnav.js"></script>
944 </head>
945 <body id="SUNWwebrev" onkeypress="keypress(event);">
946 <a name="0"></a>
947 <pre>$comments</pre><hr></hr>
950 cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
952 insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
953 insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
955 close='</body></html>'
957 print $close >> $WDIR/$DIR/$TNAME.lhs.html
958 print $close >> $WDIR/$DIR/$TNAME.rhs.html
960 print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
961 print "<title>$WNAME Framed-Sdiff " \
962 "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
963 cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
964 <frameset rows="*,60">
965 <frameset cols="50%,50%">
966 <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
967 <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
968 </frameset>
969 <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0"
970 marginheight="0" name="nav"></frame>
971 <noframes>
972 <body id="SUNWwebrev">
973 Alas 'frames' webrev requires that your browser supports frames
974 and has the feature enabled.
975 </body>
976 </noframes>
977 </frameset>
978 </html>
984 # fix_postscript
986 # Merge codereview output files to a single conforming postscript file, by:
987 # - removing all extraneous headers/trailers
988 # - making the page numbers right
989 # - removing pages devoid of contents which confuse some
990 # postscript readers.
992 # From Casper.
994 function fix_postscript
996 infile=$1
998 cat > /tmp/$$.crmerge.pl << \EOF
1000 print scalar(<>); # %!PS-Adobe---
1001 print "%%Orientation: Landscape\n";
1003 $pno = 0;
1004 $doprint = 1;
1006 $page = "";
1008 while (<>) {
1009 next if (/^%%Pages:\s*\d+/);
1011 if (/^%%Page:/) {
1012 if ($pno == 0 || $page =~ /\)S/) {
1013 # Header or single page containing text
1014 print "%%Page: ? $pno\n" if ($pno > 0);
1015 print $page;
1016 $pno++;
1017 } else {
1018 # Empty page, skip it.
1020 $page = "";
1021 $doprint = 1;
1022 next;
1025 # Skip from %%Trailer of one document to Endprolog
1026 # %%Page of the next
1027 $doprint = 0 if (/^%%Trailer/);
1028 $page .= $_ if ($doprint);
1031 if ($page =~ /\)S/) {
1032 print "%%Page: ? $pno\n";
1033 print $page;
1034 } else {
1035 $pno--;
1037 print "%%Trailer\n%%Pages: $pno\n";
1040 $PERL /tmp/$$.crmerge.pl < $infile
1045 # input_cmd | insert_anchors | output_cmd
1047 # Flag blocks of difference with sequentially numbered invisible
1048 # anchors. These are used to drive the frames version of the
1049 # sdiffs output.
1051 # NOTE: Anchor zero flags the top of the file irrespective of changes,
1052 # an additional anchor is also appended to flag the bottom.
1054 # The script detects changed lines as any line that has a "<span
1055 # class=" string embedded (unchanged lines have no class set and are
1056 # not part of a <span>. Blank lines (without a sequence number)
1057 # are also detected since they flag lines that have been inserted or
1058 # deleted.
1060 function insert_anchors
1062 $AWK '
1063 function ia() {
1064 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1067 BEGIN {
1068 anc=1;
1069 inblock=1;
1070 printf "<pre>\n";
1072 NF == 0 || /^<span class=/ {
1073 if (inblock == 0) {
1074 ia();
1075 inblock=1;
1077 print;
1078 next;
1081 inblock=0;
1082 print;
1084 END {
1085 ia();
1087 printf "<b style=\"font-size: large; color: red\">";
1088 printf "--- EOF ---</b>"
1089 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1090 printf "</pre>"
1091 printf "<form name=\"eof\">";
1092 printf "<input name=\"value\" value=\"%d\" " \
1093 "type=\"hidden\"></input>", anc - 1;
1094 printf "</form>";
1096 ' $1
1101 # relative_dir
1103 # Print a relative return path from $1 to $2. For example if
1104 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1105 # this function would print "../../../../".
1107 # In the event that $1 is not in $2 a warning is printed to stderr,
1108 # and $2 is returned-- the result of this is that the resulting webrev
1109 # is not relocatable.
1111 function relative_dir
1113 typeset cur="${1##$2?(/)}"
1116 # If the first path was specified absolutely, and it does
1117 # not start with the second path, it's an error.
1119 if [[ "$cur" = "/${1#/}" ]]; then
1120 # Should never happen.
1121 print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1122 print -u2 "to \"$2\". Check input paths. Framed webrev "
1123 print -u2 "will not be relocatable!"
1124 print $2
1125 return
1129 # This is kind of ugly. The sed script will do the following:
1131 # 1. Strip off a leading "." or "./": this is important to get
1132 # the correct arcnav links for files in $WDIR.
1133 # 2. Strip off a trailing "/": this is not strictly necessary,
1134 # but is kind of nice, since it doesn't end up in "//" at
1135 # the end of a relative path.
1136 # 3. Replace all remaining sequences of non-"/" with "..": the
1137 # assumption here is that each dirname represents another
1138 # level of relative separation.
1139 # 4. Append a trailing "/" only for non-empty paths: this way
1140 # the caller doesn't need to duplicate this logic, and does
1141 # not end up using $RTOP/file for files in $WDIR.
1143 print $cur | $SED -e '{
1144 s:^\./*::
1145 s:/$::
1146 s:[^/][^/]*:..:g
1147 s:^\(..*\)$:\1/:
1152 # frame_nav_js
1154 # Emit javascript for frame navigation
1156 function frame_nav_js
1158 cat << \EOF
1159 var myInt;
1160 var scrolling=0;
1161 var sfactor = 3;
1162 var scount=10;
1164 function scrollByPix() {
1165 if (scount<=0) {
1166 sfactor*=1.2;
1167 scount=10;
1169 parent.lhs.scrollBy(0,sfactor);
1170 parent.rhs.scrollBy(0,sfactor);
1171 scount--;
1174 function scrollToAnc(num) {
1176 // Update the value of the anchor in the form which we use as
1177 // storage for this value. setAncValue() will take care of
1178 // correcting for overflow and underflow of the value and return
1179 // us the new value.
1180 num = setAncValue(num);
1182 // Set location and scroll back a little to expose previous
1183 // lines.
1185 // Note that this could be improved: it is possible although
1186 // complex to compute the x and y position of an anchor, and to
1187 // scroll to that location directly.
1189 parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1190 parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1192 parent.lhs.scrollBy(0,-30);
1193 parent.rhs.scrollBy(0,-30);
1196 function getAncValue()
1198 return (parseInt(parent.nav.document.diff.real.value));
1201 function setAncValue(val)
1203 if (val <= 0) {
1204 val = 0;
1205 parent.nav.document.diff.real.value = val;
1206 parent.nav.document.diff.display.value = "BOF";
1207 return (val);
1211 // The way we compute the max anchor value is to stash it
1212 // inline in the left and right hand side pages-- it's the same
1213 // on each side, so we pluck from the left.
1215 maxval = parent.lhs.document.eof.value.value;
1216 if (val < maxval) {
1217 parent.nav.document.diff.real.value = val;
1218 parent.nav.document.diff.display.value = val.toString();
1219 return (val);
1222 // this must be: val >= maxval
1223 val = maxval;
1224 parent.nav.document.diff.real.value = val;
1225 parent.nav.document.diff.display.value = "EOF";
1226 return (val);
1229 function stopScroll() {
1230 if (scrolling==1) {
1231 clearInterval(myInt);
1232 scrolling=0;
1236 function startScroll() {
1237 stopScroll();
1238 scrolling=1;
1239 myInt=setInterval("scrollByPix()",10);
1242 function handlePress(b) {
1244 switch (b) {
1245 case 1 :
1246 scrollToAnc(-1);
1247 break;
1248 case 2 :
1249 scrollToAnc(getAncValue() - 1);
1250 break;
1251 case 3 :
1252 sfactor=-3;
1253 startScroll();
1254 break;
1255 case 4 :
1256 sfactor=3;
1257 startScroll();
1258 break;
1259 case 5 :
1260 scrollToAnc(getAncValue() + 1);
1261 break;
1262 case 6 :
1263 scrollToAnc(999999);
1264 break;
1268 function handleRelease(b) {
1269 stopScroll();
1272 function keypress(ev) {
1273 var keynum;
1274 var keychar;
1276 if (window.event) { // IE
1277 keynum = ev.keyCode;
1278 } else if (ev.which) { // non-IE
1279 keynum = ev.which;
1282 keychar = String.fromCharCode(keynum);
1284 if (keychar == "k") {
1285 handlePress(2);
1286 return (0);
1287 } else if (keychar == "j" || keychar == " ") {
1288 handlePress(5);
1289 return (0);
1291 return (1);
1294 function ValidateDiffNum(){
1295 val = parent.nav.document.diff.display.value;
1296 if (val == "EOF") {
1297 scrollToAnc(999999);
1298 return;
1301 if (val == "BOF") {
1302 scrollToAnc(0);
1303 return;
1306 i=parseInt(val);
1307 if (isNaN(i)) {
1308 parent.nav.document.diff.display.value = getAncValue();
1309 } else {
1310 scrollToAnc(i);
1312 return false;
1319 # frame_navigation
1321 # Output anchor navigation file for framed sdiffs.
1323 function frame_navigation
1325 print "$HTML<head>$STDHEAD"
1327 cat << \EOF
1328 <title>Anchor Navigation</title>
1329 <meta http-equiv="Content-Script-Type" content="text/javascript">
1330 <meta http-equiv="Content-Type" content="text/html">
1332 <style type="text/css">
1333 div.button td { padding-left: 5px; padding-right: 5px;
1334 background-color: #eee; text-align: center;
1335 border: 1px #444 outset; cursor: pointer; }
1336 div.button a { font-weight: bold; color: black }
1337 div.button td:hover { background: #ffcc99; }
1338 </style>
1341 print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1343 cat << \EOF
1344 </head>
1345 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1346 onkeypress="keypress(event);">
1347 <noscript lang="javascript">
1348 <center>
1349 <p><big>Framed Navigation controls require Javascript</big><br></br>
1350 Either this browser is incompatable or javascript is not enabled</p>
1351 </center>
1352 </noscript>
1353 <table width="100%" border="0" align="center">
1354 <tr>
1355 <td valign="middle" width="25%">Diff navigation:
1356 Use 'j' and 'k' for next and previous diffs; or use buttons
1357 at right</td>
1358 <td align="center" valign="top" width="50%">
1359 <div class="button">
1360 <table border="0" align="center">
1361 <tr>
1362 <td>
1363 <a onMouseDown="handlePress(1);return true;"
1364 onMouseUp="handleRelease(1);return true;"
1365 onMouseOut="handleRelease(1);return true;"
1366 onClick="return false;"
1367 title="Go to Beginning Of file">BOF</a></td>
1368 <td>
1369 <a onMouseDown="handlePress(3);return true;"
1370 onMouseUp="handleRelease(3);return true;"
1371 onMouseOut="handleRelease(3);return true;"
1372 title="Scroll Up: Press and Hold to accelerate"
1373 onClick="return false;">Scroll Up</a></td>
1374 <td>
1375 <a onMouseDown="handlePress(2);return true;"
1376 onMouseUp="handleRelease(2);return true;"
1377 onMouseOut="handleRelease(2);return true;"
1378 title="Go to previous Diff"
1379 onClick="return false;">Prev Diff</a>
1380 </td></tr>
1382 <tr>
1383 <td>
1384 <a onMouseDown="handlePress(6);return true;"
1385 onMouseUp="handleRelease(6);return true;"
1386 onMouseOut="handleRelease(6);return true;"
1387 onClick="return false;"
1388 title="Go to End Of File">EOF</a></td>
1389 <td>
1390 <a onMouseDown="handlePress(4);return true;"
1391 onMouseUp="handleRelease(4);return true;"
1392 onMouseOut="handleRelease(4);return true;"
1393 title="Scroll Down: Press and Hold to accelerate"
1394 onClick="return false;">Scroll Down</a></td>
1395 <td>
1396 <a onMouseDown="handlePress(5);return true;"
1397 onMouseUp="handleRelease(5);return true;"
1398 onMouseOut="handleRelease(5);return true;"
1399 title="Go to next Diff"
1400 onClick="return false;">Next Diff</a></td>
1401 </tr>
1402 </table>
1403 </div>
1404 </td>
1405 <th valign="middle" width="25%">
1406 <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1407 <input name="display" value="BOF" size="8" type="text"></input>
1408 <input name="real" value="0" size="8" type="hidden"></input>
1409 </form>
1410 </th>
1411 </tr>
1412 </table>
1413 </body>
1414 </html>
1421 # diff_to_html <filename> <filepath> { U | C } <comment>
1423 # Processes the output of diff to produce an HTML file representing either
1424 # context or unified diffs.
1426 diff_to_html()
1428 TNAME=$1
1429 TPATH=$2
1430 DIFFTYPE=$3
1431 COMMENT=$4
1433 print "$HTML<head>$STDHEAD"
1434 print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1436 if [[ $DIFFTYPE == "U" ]]; then
1437 print "$UDIFFCSS"
1440 cat <<-EOF
1441 </head>
1442 <body id="SUNWwebrev">
1443 <a class="print" href="javascript:print()">Print this page</a>
1444 <pre>$COMMENT</pre>
1445 <pre>
1448 html_quote | $AWK '
1449 /^--- new/ { next }
1450 /^\+\+\+ new/ { next }
1451 /^--- old/ { next }
1452 /^\*\*\* old/ { next }
1453 /^\*\*\*\*/ { next }
1454 /^-------/ { printf "<center><h1>%s</h1></center>\n", $0; next }
1455 /^\@\@.*\@\@$/ { printf "</pre><hr></hr><pre>\n";
1456 printf "<span class=\"newmarker\">%s</span>\n", $0;
1457 next}
1459 /^\*\*\*/ { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1460 next}
1461 /^---/ { printf "<span class=\"newmarker\">%s</span>\n", $0;
1462 next}
1463 /^\+/ {printf "<span class=\"new\">%s</span>\n", $0; next}
1464 /^!/ {printf "<span class=\"changed\">%s</span>\n", $0; next}
1465 /^-/ {printf "<span class=\"removed\">%s</span>\n", $0; next}
1466 {printf "%s\n", $0; next}
1469 print "</pre></body></html>\n"
1474 # source_to_html { new | old } <filename>
1476 # Process a plain vanilla source file to transform it into an HTML file.
1478 source_to_html()
1480 WHICH=$1
1481 TNAME=$2
1483 print "$HTML<head>$STDHEAD"
1484 print "<title>$WNAME $WHICH $TNAME</title>"
1485 print "<body id=\"SUNWwebrev\">"
1486 print "<pre>"
1487 html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1488 print "</pre></body></html>"
1492 # comments_from_wx {text|html} filepath
1494 # Given the pathname of a file, find its location in a "wx" active
1495 # file list and print the following comment. Output is either text or
1496 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1497 # are turned into URLs.
1499 # This is also used with Mercurial and the file list provided by hg-active.
1501 comments_from_wx()
1503 typeset fmt=$1
1504 typeset p=$2
1506 comm=`$AWK '
1507 $1 == "'$p'" {
1508 do getline ; while (NF > 0)
1509 getline
1510 while (NF > 0) { print ; getline }
1511 exit
1512 }' < $wxfile`
1514 if [[ -z $comm ]]; then
1515 comm="*** NO COMMENTS ***"
1518 if [[ $fmt == "text" ]]; then
1519 print -- "$comm"
1520 return
1523 print -- "$comm" | html_quote | its2url
1528 # getcomments {text|html} filepath parentpath
1530 # Fetch the comments depending on what SCM mode we're in.
1532 getcomments()
1534 typeset fmt=$1
1535 typeset p=$2
1536 typeset pp=$3
1538 if [[ -n $Nflag ]]; then
1539 return
1542 # Mercurial support uses a file list in wx format, so this
1543 # will be used there, too
1545 if [[ -n $wxfile ]]; then
1546 comments_from_wx $fmt $p
1551 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1553 # Print out Code Inspection figures similar to sccs-prt(1) format.
1555 function printCI
1557 integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1558 typeset str
1559 if (( tot == 1 )); then
1560 str="line"
1561 else
1562 str="lines"
1564 printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1565 $tot $str $ins $del $mod $unc
1570 # difflines <oldfile> <newfile>
1572 # Calculate and emit number of added, removed, modified and unchanged lines,
1573 # and total lines changed, the sum of added + removed + modified.
1575 function difflines
1577 integer tot mod del ins unc err
1578 typeset filename
1580 eval $( diff -e $1 $2 | $AWK '
1581 # Change range of lines: N,Nc
1582 /^[0-9]*,[0-9]*c$/ {
1583 n=split(substr($1,1,length($1)-1), counts, ",");
1584 if (n != 2) {
1585 error=2
1586 exit;
1589 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1590 # following would be 5 - 3 = 2! Hence +1 for correction.
1592 r=(counts[2]-counts[1])+1;
1595 # Now count replacement lines: each represents a change instead
1596 # of a delete, so increment c and decrement r.
1598 while (getline != /^\.$/) {
1599 c++;
1600 r--;
1603 # If there were more replacement lines than original lines,
1604 # then r will be negative; in this case there are no deletions,
1605 # but there are r changes that should be counted as adds, and
1606 # since r is negative, subtract it from a and add it to c.
1608 if (r < 0) {
1609 a-=r;
1610 c+=r;
1614 # If there were more original lines than replacement lines, then
1615 # r will be positive; in this case, increment d by that much.
1617 if (r > 0) {
1618 d+=r;
1620 next;
1623 # Change lines: Nc
1624 /^[0-9].*c$/ {
1625 # The first line is a replacement; any more are additions.
1626 if (getline != /^\.$/) {
1627 c++;
1628 while (getline != /^\.$/) a++;
1630 next;
1633 # Add lines: both Na and N,Na
1634 /^[0-9].*a$/ {
1635 while (getline != /^\.$/) a++;
1636 next;
1639 # Delete range of lines: N,Nd
1640 /^[0-9]*,[0-9]*d$/ {
1641 n=split(substr($1,1,length($1)-1), counts, ",");
1642 if (n != 2) {
1643 error=2
1644 exit;
1647 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1648 # following would be 5 - 3 = 2! Hence +1 for correction.
1650 r=(counts[2]-counts[1])+1;
1651 d+=r;
1652 next;
1655 # Delete line: Nd. For example 10d says line 10 is deleted.
1656 /^[0-9]*d$/ {d++; next}
1658 # Should not get here!
1660 error=1;
1661 exit;
1664 # Finish off - print results
1665 END {
1666 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1667 (c+d+a), c, d, a, error);
1668 }' )
1670 # End of $AWK, Check to see if any trouble occurred.
1671 if (( $? > 0 || err > 0 )); then
1672 print "Unexpected Error occurred reading" \
1673 "\`diff -e $1 $2\`: \$?=$?, err=" $err
1674 return
1677 # Accumulate totals
1678 (( TOTL += tot ))
1679 (( TMOD += mod ))
1680 (( TDEL += del ))
1681 (( TINS += ins ))
1682 # Calculate unchanged lines
1683 unc=`wc -l < $1`
1684 if (( unc > 0 )); then
1685 (( unc -= del + mod ))
1686 (( TUNC += unc ))
1688 # print summary
1689 print "<span class=\"lineschanged\">"
1690 printCI $tot $ins $del $mod $unc
1691 print "</span>"
1696 # flist_from_wx
1698 # Sets up webrev to source its information from a wx-formatted file.
1699 # Sets the global 'wxfile' variable.
1701 function flist_from_wx
1703 typeset argfile=$1
1704 if [[ -n ${argfile%%/*} ]]; then
1706 # If the wx file pathname is relative then make it absolute
1707 # because the webrev does a "cd" later on.
1709 wxfile=$PWD/$argfile
1710 else
1711 wxfile=$argfile
1714 $AWK '{ c = 1; print;
1715 while (getline) {
1716 if (NF == 0) { c = -c; continue }
1717 if (c > 0) print
1719 }' $wxfile > $FLIST
1721 print " Done."
1725 # Call hg-active to get the active list output in the wx active list format
1727 function hg_active_wxfile
1729 typeset child=$1
1730 typeset parent=$2
1732 TMPFLIST=/tmp/$$.active
1733 $HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1734 wxfile=$TMPFLIST
1738 # flist_from_mercurial
1739 # Call hg-active to get a wx-style active list, and hand it off to
1740 # flist_from_wx
1742 function flist_from_mercurial
1744 typeset child=$1
1745 typeset parent=$2
1747 print " File list from: hg-active -p $parent ...\c"
1748 if [[ ! -x $HG_ACTIVE ]]; then
1749 print # Blank line for the \c above
1750 print -u2 "Error: hg-active tool not found. Exiting"
1751 exit 1
1753 hg_active_wxfile $child $parent
1755 # flist_from_wx prints the Done, so we don't have to.
1756 flist_from_wx $TMPFLIST
1760 # Transform a specified 'git log' output format into a wx-like active list.
1762 function git_wxfile
1764 typeset child="$1"
1765 typeset parent="$2"
1767 TMPFLIST=/tmp/$$.active
1768 $PERL -e 'my (%files, %realfiles, $msg);
1769 my $parent = $ARGV[0];
1770 my $child = $ARGV[1];
1772 open(F, "git diff -M --name-status $parent..$child |");
1773 while (<F>) {
1774 chomp;
1775 if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
1776 if ($1 >= 75) { # Probably worth treating as a rename
1777 $realfiles{$3} = $2;
1778 } else {
1779 $realfiles{$3} = $3;
1780 $realfiles{$2} = $2;
1782 } else {
1783 my $f = (split /\s+/, $_)[1];
1784 $realfiles{$f} = $f;
1787 close(F);
1789 my $state = 1; # 0|comments, 1|files
1790 open(F, "git whatchanged --pretty=format:%B $parent..$child |");
1791 while (<F>) {
1792 chomp;
1793 if (/^:[0-9]{6}/) {
1794 my $fname = (split /\t/, $_)[1];
1795 next if !defined($realfiles{$fname}); # No real change
1796 $state = 1;
1797 chomp $msg;
1798 $files{$fname} .= $msg;
1799 } else {
1800 if ($state == 1) {
1801 $state = 0;
1802 $msg = /^\n/ ? "" : "\n";
1804 $msg .= "$_\n" if ($_);
1807 close(F);
1809 for (sort keys %files) {
1810 if ($realfiles{$_} ne $_) {
1811 print "$_ $realfiles{$_}\n$files{$_}\n\n";
1812 } else {
1813 print "$_\n$files{$_}\n\n"
1815 }' ${parent} ${child} > $TMPFLIST
1817 wxfile=$TMPFLIST
1821 # flist_from_git
1822 # Build a wx-style active list, and hand it off to flist_from_wx
1824 function flist_from_git
1826 typeset child=$1
1827 typeset parent=$2
1829 print " File list from: git ...\c"
1830 git_wxfile "$child" "$parent";
1832 # flist_from_wx prints the Done, so we don't have to.
1833 flist_from_wx $TMPFLIST
1837 # flist_from_subversion
1839 # Generate the file list by extracting file names from svn status.
1841 function flist_from_subversion
1843 CWS=$1
1844 OLDPWD=$2
1846 cd $CWS
1847 print -u2 " File list from: svn status ... \c"
1848 svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1849 print -u2 " Done."
1850 cd $OLDPWD
1853 function env_from_flist
1855 [[ -r $FLIST ]] || return
1858 # Use "eval" to set env variables that are listed in the file
1859 # list. Then copy those into our local versions of those
1860 # variables if they have not been set already.
1862 eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1864 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1865 codemgr_ws=$CODEMGR_WS
1866 export CODEMGR_WS
1870 # Check to see if CODEMGR_PARENT is set in the flist file.
1872 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1873 codemgr_parent=$CODEMGR_PARENT
1874 export CODEMGR_PARENT
1878 function look_for_prog
1880 typeset path
1881 typeset ppath
1882 typeset progname=$1
1884 ppath=$PATH
1885 ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1886 ppath=$ppath:/opt/onbld/bin
1887 ppath=$ppath:/opt/onbld/bin/`uname -p`
1889 PATH=$ppath prog=`whence $progname`
1890 if [[ -n $prog ]]; then
1891 print $prog
1895 function get_file_mode
1897 $PERL -e '
1898 if (@stat = stat($ARGV[0])) {
1899 $mode = $stat[2] & 0777;
1900 printf "%03o\n", $mode;
1901 exit 0;
1902 } else {
1903 exit 1;
1905 ' $1
1908 function build_old_new_mercurial
1910 typeset olddir="$1"
1911 typeset newdir="$2"
1912 typeset old_mode=
1913 typeset new_mode=
1914 typeset file
1917 # Get old file mode, from the parent revision manifest entry.
1918 # Mercurial only stores a "file is executable" flag, but the
1919 # manifest will display an octal mode "644" or "755".
1921 if [[ "$PDIR" == "." ]]; then
1922 file="$PF"
1923 else
1924 file="$PDIR/$PF"
1926 file=`echo $file | $SED 's#/#\\\/#g'`
1927 # match the exact filename, and return only the permission digits
1928 old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
1929 < $HG_PARENT_MANIFEST`
1932 # Get new file mode, directly from the filesystem.
1933 # Normalize the mode to match Mercurial's behavior.
1935 new_mode=`get_file_mode $CWS/$DIR/$F`
1936 if [[ -n "$new_mode" ]]; then
1937 if [[ "$new_mode" = *[1357]* ]]; then
1938 new_mode=755
1939 else
1940 new_mode=644
1945 # new version of the file.
1947 rm -rf $newdir/$DIR/$F
1948 if [[ -e $CWS/$DIR/$F ]]; then
1949 cp $CWS/$DIR/$F $newdir/$DIR/$F
1950 if [[ -n $new_mode ]]; then
1951 chmod $new_mode $newdir/$DIR/$F
1952 else
1953 # should never happen
1954 print -u2 "ERROR: set mode of $newdir/$DIR/$F"
1959 # parent's version of the file
1961 # Note that we get this from the last version common to both
1962 # ourselves and the parent. References are via $CWS since we have no
1963 # guarantee that the parent workspace is reachable via the filesystem.
1965 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
1966 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1967 elif [[ -n $HG_PARENT ]]; then
1968 hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
1969 $olddir/$PDIR/$PF 2>/dev/null
1971 if (( $? != 0 )); then
1972 rm -f $olddir/$PDIR/$PF
1973 else
1974 if [[ -n $old_mode ]]; then
1975 chmod $old_mode $olddir/$PDIR/$PF
1976 else
1977 # should never happen
1978 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
1984 function build_old_new_git
1986 typeset olddir="$1"
1987 typeset newdir="$2"
1988 typeset o_mode=
1989 typeset n_mode=
1990 typeset o_object=
1991 typeset n_object=
1992 typeset OWD=$PWD
1993 typeset file
1994 typeset type
1996 cd $CWS
1999 # Get old file and its mode from the git object tree
2001 if [[ "$PDIR" == "." ]]; then
2002 file="$PF"
2003 else
2004 file="$PDIR/$PF"
2007 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2008 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2009 else
2010 $GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk
2011 $GIT cat-file $type $o_object > $olddir/$file 2>/dev/null
2013 if (( $? != 0 )); then
2014 rm -f $olddir/$file
2015 elif [[ -n $o_mode ]]; then
2016 # Strip the first 3 digits, to get a regular octal mode
2017 o_mode=${o_mode/???/}
2018 chmod $o_mode $olddir/$file
2019 else
2020 # should never happen
2021 print -u2 "ERROR: set mode of $olddir/$file"
2026 # new version of the file.
2028 if [[ "$DIR" == "." ]]; then
2029 file="$F"
2030 else
2031 file="$DIR/$F"
2033 rm -rf $newdir/$file
2035 if [[ -e $CWS/$DIR/$F ]]; then
2036 cp $CWS/$DIR/$F $newdir/$DIR/$F
2037 chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F
2039 cd $OWD
2042 function build_old_new_subversion
2044 typeset olddir="$1"
2045 typeset newdir="$2"
2047 # Snag new version of file.
2048 rm -f $newdir/$DIR/$F
2049 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2051 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2052 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2053 else
2054 # Get the parent's version of the file.
2055 svn status $CWS/$DIR/$F | read stat file
2056 if [[ $stat != "A" ]]; then
2057 svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2062 function build_old_new_unknown
2064 typeset olddir="$1"
2065 typeset newdir="$2"
2068 # Snag new version of file.
2070 rm -f $newdir/$DIR/$F
2071 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2074 # Snag the parent's version of the file.
2076 if [[ -f $PWS/$PDIR/$PF ]]; then
2077 rm -f $olddir/$PDIR/$PF
2078 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2082 function build_old_new
2084 typeset WDIR=$1
2085 typeset PWS=$2
2086 typeset PDIR=$3
2087 typeset PF=$4
2088 typeset CWS=$5
2089 typeset DIR=$6
2090 typeset F=$7
2092 typeset olddir="$WDIR/raw_files/old"
2093 typeset newdir="$WDIR/raw_files/new"
2095 mkdir -p $olddir/$PDIR
2096 mkdir -p $newdir/$DIR
2098 if [[ $SCM_MODE == "mercurial" ]]; then
2099 build_old_new_mercurial "$olddir" "$newdir"
2100 elif [[ $SCM_MODE == "git" ]]; then
2101 build_old_new_git "$olddir" "$newdir"
2102 elif [[ $SCM_MODE == "subversion" ]]; then
2103 build_old_new_subversion "$olddir" "$newdir"
2104 elif [[ $SCM_MODE == "unknown" ]]; then
2105 build_old_new_unknown "$olddir" "$newdir"
2108 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2109 print "*** Error: file not in parent or child"
2110 return 1
2112 return 0
2117 # Usage message.
2119 function usage
2121 print 'Usage:\twebrev [common-options]
2122 webrev [common-options] ( <file> | - )
2123 webrev [common-options] -w <wx file>
2125 Options:
2126 -c <revision>: generate webrev for single revision (git only)
2127 -C <filename>: Use <filename> for the information tracking configuration.
2128 -D: delete remote webrev
2129 -h <revision>: specify "head" revision for comparison (git only)
2130 -i <filename>: Include <filename> in the index.html file.
2131 -I <filename>: Use <filename> for the information tracking registry.
2132 -n: do not generate the webrev (useful with -U)
2133 -O: Print bugids/arc cases suitable for OpenSolaris.
2134 -o <outdir>: Output webrev to specified directory.
2135 -p <compare-against>: Use specified parent wkspc or basis for comparison
2136 -t <remote_target>: Specify remote destination for webrev upload
2137 -U: upload the webrev to remote destination
2138 -w <wxfile>: Use specified wx active file.
2140 Environment:
2141 WDIR: Control the output directory.
2142 WEBREV_TRASH_DIR: Set directory for webrev delete.
2144 SCM Environment:
2145 CODEMGR_WS: Workspace location.
2146 CODEMGR_PARENT: Parent workspace location.
2149 exit 2
2154 # Main program starts here
2158 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2160 set +o noclobber
2162 PATH=$(/bin/dirname "$(whence $0)"):$PATH
2164 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2165 [[ -z $WX ]] && WX=`look_for_prog wx`
2166 [[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
2167 [[ -z $GIT ]] && GIT=`look_for_prog git`
2168 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2169 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2170 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2171 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2172 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2173 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2174 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2175 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2176 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2177 [[ -z $SCP ]] && SCP=`look_for_prog scp`
2178 [[ -z $SED ]] && SED=`look_for_prog sed`
2179 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2180 [[ -z $SORT ]] && SORT=`look_for_prog sort`
2181 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2182 [[ -z $GREP ]] && GREP=`look_for_prog grep`
2183 [[ -z $FIND ]] && FIND=`look_for_prog find`
2184 [[ -z $MANDOC ]] && MANDOC=`look_for_prog mandoc`
2185 [[ -z $COL ]] && COL=`look_for_prog col`
2187 # set name of trash directory for remote webrev deletion
2188 TRASH_DIR=".trash"
2189 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2191 if [[ ! -x $PERL ]]; then
2192 print -u2 "Error: No perl interpreter found. Exiting."
2193 exit 1
2196 if [[ ! -x $WHICH_SCM ]]; then
2197 print -u2 "Error: Could not find which_scm. Exiting."
2198 exit 1
2202 # These aren't fatal, but we want to note them to the user.
2203 # We don't warn on the absence of 'wx' until later when we've
2204 # determined that we actually need to try to invoke it.
2206 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2207 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2208 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2210 # Declare global total counters.
2211 integer TOTL TINS TDEL TMOD TUNC
2213 # default remote host for upload/delete
2214 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2215 # prefixes for upload targets
2216 typeset -r rsync_prefix="rsync://"
2217 typeset -r ssh_prefix="ssh://"
2219 cflag=
2220 Cflag=
2221 Dflag=
2222 flist_mode=
2223 flist_file=
2224 hflag=
2225 iflag=
2226 Iflag=
2227 lflag=
2228 Nflag=
2229 nflag=
2230 Oflag=
2231 oflag=
2232 pflag=
2233 tflag=
2234 uflag=
2235 Uflag=
2236 wflag=
2237 remote_target=
2240 # NOTE: when adding/removing options it is necessary to sync the list
2241 # with usr/src/tools/onbld/hgext/cdm.py
2243 while getopts "c:C:Dh:i:I:lnNo:Op:t:Uw" opt
2245 case $opt in
2246 c) cflag=1
2247 codemgr_head=$OPTARG
2248 codemgr_parent=$OPTARG~1;;
2250 C) Cflag=1
2251 ITSCONF=$OPTARG;;
2253 D) Dflag=1;;
2255 h) hflag=1
2256 codemgr_head=$OPTARG;;
2258 i) iflag=1
2259 INCLUDE_FILE=$OPTARG;;
2261 I) Iflag=1
2262 ITSREG=$OPTARG;;
2264 N) Nflag=1;;
2266 n) nflag=1;;
2268 O) Oflag=1;;
2270 o) oflag=1
2271 # Strip the trailing slash to correctly form remote target.
2272 WDIR=${OPTARG%/};;
2274 p) pflag=1
2275 codemgr_parent=$OPTARG;;
2277 t) tflag=1
2278 remote_target=$OPTARG;;
2280 U) Uflag=1;;
2282 w) wflag=1;;
2284 ?) usage;;
2285 esac
2286 done
2288 FLIST=/tmp/$$.flist
2290 if [[ -n $wflag && -n $lflag ]]; then
2291 usage
2294 # more sanity checking
2295 if [[ -n $nflag && -z $Uflag ]]; then
2296 print "it does not make sense to skip webrev generation" \
2297 "without -U"
2298 exit 1
2301 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2302 echo "remote target has to be used only for upload or delete"
2303 exit 1
2307 # For the invocation "webrev -n -U" with no other options, webrev will assume
2308 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2309 # $(basename ${CWS}). So we need to get CWS set before we skip any remaining
2310 # logic.
2312 $WHICH_SCM | read SCM_MODE junk || exit 1
2313 if [[ $SCM_MODE == "mercurial" ]]; then
2315 # Mercurial priorities:
2316 # 1. hg root from CODEMGR_WS environment variable
2317 # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under
2318 # usr/closed when we run webrev
2319 # 2. hg root from directory of invocation
2321 if [[ ${PWD} =~ "usr/closed" ]]; then
2322 testparent=${CODEMGR_WS}/usr/closed
2323 # If we're in OpenSolaris mode, we enforce a minor policy:
2324 # help to make sure the reviewer doesn't accidentally publish
2325 # source which is under usr/closed
2326 if [[ -n "$Oflag" ]]; then
2327 print -u2 "OpenSolaris output not permitted with" \
2328 "usr/closed changes"
2329 exit 1
2331 else
2332 testparent=${CODEMGR_WS}
2334 [[ -z $codemgr_ws && -n $testparent ]] && \
2335 codemgr_ws=$(hg root -R $testparent 2>/dev/null)
2336 [[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null)
2337 CWS=$codemgr_ws
2338 elif [[ $SCM_MODE == "git" ]]; then
2340 # Git priorities:
2341 # 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2342 # 2. git rev-parse --git-dir from directory of invocation
2344 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2345 codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \
2346 2>/dev/null)
2347 [[ -z $codemgr_ws ]] && \
2348 codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null)
2350 if [[ "$codemgr_ws" == ".git" ]]; then
2351 codemgr_ws="${PWD}/${codemgr_ws}"
2354 codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git'
2355 CWS="$codemgr_ws"
2356 elif [[ $SCM_MODE == "subversion" ]]; then
2358 # Subversion priorities:
2359 # 1. CODEMGR_WS from environment
2360 # 2. Relative path from current directory to SVN repository root
2362 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2363 CWS=$CODEMGR_WS
2364 else
2365 svn info | while read line; do
2366 if [[ $line == "URL: "* ]]; then
2367 url=${line#URL: }
2368 elif [[ $line == "Repository Root: "* ]]; then
2369 repo=${line#Repository Root: }
2371 done
2373 rel=${url#$repo}
2374 CWS=${PWD%$rel}
2379 # If no SCM has been determined, take either the environment setting
2380 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2382 if [[ -z ${CWS} ]]; then
2383 CWS=${CODEMGR_WS:-.}
2387 # If the command line options indicate no webrev generation, either
2388 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2389 # ton of logic we can skip.
2391 # Instead of increasing indentation, we intentionally leave this loop
2392 # body open here, and exit via break from multiple points within.
2393 # Search for DO_EVERYTHING below to find the break points and closure.
2395 for do_everything in 1; do
2397 # DO_EVERYTHING: break point
2398 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2399 break
2403 # If this manually set as the parent, and it appears to be an earlier webrev,
2404 # then note that fact and set the parent to the raw_files/new subdirectory.
2406 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2407 parent_webrev=$(readlink -f "$codemgr_parent")
2408 codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new")
2411 if [[ -z $wflag && -z $lflag ]]; then
2412 shift $(($OPTIND - 1))
2414 if [[ $1 == "-" ]]; then
2415 cat > $FLIST
2416 flist_mode="stdin"
2417 flist_done=1
2418 shift
2419 elif [[ -n $1 ]]; then
2420 if [[ ! -r $1 ]]; then
2421 print -u2 "$1: no such file or not readable"
2422 usage
2424 cat $1 > $FLIST
2425 flist_mode="file"
2426 flist_file=$1
2427 flist_done=1
2428 shift
2429 else
2430 flist_mode="auto"
2435 # Before we go on to further consider -l and -w, work out which SCM we think
2436 # is in use.
2438 case "$SCM_MODE" in
2439 mercurial|git|subversion)
2441 unknown)
2442 if [[ $flist_mode == "auto" ]]; then
2443 print -u2 "Unable to determine SCM in use and file list not specified"
2444 print -u2 "See which_scm(1) for SCM detection information."
2445 exit 1
2449 if [[ $flist_mode == "auto" ]]; then
2450 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2451 exit 1
2454 esac
2456 print -u2 " SCM detected: $SCM_MODE"
2458 if [[ -n $wflag ]]; then
2460 # If the -w is given then assume the file list is in Bonwick's "wx"
2461 # command format, i.e. pathname lines alternating with SCCS comment
2462 # lines with blank lines as separators. Use the SCCS comments later
2463 # in building the index.html file.
2465 shift $(($OPTIND - 1))
2466 wxfile=$1
2467 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2468 if [[ -r $CODEMGR_WS/wx/active ]]; then
2469 wxfile=$CODEMGR_WS/wx/active
2473 [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2474 "be auto-detected (check \$CODEMGR_WS)" && exit 1
2476 if [[ ! -r $wxfile ]]; then
2477 print -u2 "$wxfile: no such file or not readable"
2478 usage
2481 print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2482 flist_from_wx $wxfile
2483 flist_done=1
2484 if [[ -n "$*" ]]; then
2485 shift
2487 elif [[ $flist_mode == "stdin" ]]; then
2488 print -u2 " File list from: standard input"
2489 elif [[ $flist_mode == "file" ]]; then
2490 print -u2 " File list from: $flist_file"
2493 if [[ $# -gt 0 ]]; then
2494 print -u2 "WARNING: unused arguments: $*"
2498 # Before we entered the DO_EVERYTHING loop, we should have already set CWS
2499 # and CODEMGR_WS as needed. Here, we set the parent workspace.
2501 if [[ $SCM_MODE == "mercurial" ]]; then
2503 # Parent can either be specified with -p
2504 # Specified with CODEMGR_PARENT in the environment
2505 # or taken from hg's default path.
2508 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2509 codemgr_parent=$CODEMGR_PARENT
2512 if [[ -z $codemgr_parent ]]; then
2513 codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
2516 PWS=$codemgr_parent
2519 # If the parent is a webrev, we want to do some things against
2520 # the natural workspace parent (file list, comments, etc)
2522 if [[ -n $parent_webrev ]]; then
2523 real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
2524 else
2525 real_parent=$PWS
2529 # If hg-active exists, then we run it. In the case of no explicit
2530 # flist given, we'll use it for our comments. In the case of an
2531 # explicit flist given we'll try to use it for comments for any
2532 # files mentioned in the flist.
2534 if [[ -z $flist_done ]]; then
2535 flist_from_mercurial $CWS $real_parent
2536 flist_done=1
2540 # If we have a file list now, pull out any variables set
2541 # therein. We do this now (rather than when we possibly use
2542 # hg-active to find comments) to avoid stomping specifications
2543 # in the user-specified flist.
2545 if [[ -n $flist_done ]]; then
2546 env_from_flist
2550 # Only call hg-active if we don't have a wx formatted file already
2552 if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2553 print " Comments from: hg-active -p $real_parent ...\c"
2554 hg_active_wxfile $CWS $real_parent
2555 print " Done."
2559 # At this point we must have a wx flist either from hg-active,
2560 # or in general. Use it to try and find our parent revision,
2561 # if we don't have one.
2563 if [[ -z $HG_PARENT ]]; then
2564 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2568 # If we still don't have a parent, we must have been given a
2569 # wx-style active list with no HG_PARENT specification, run
2570 # hg-active and pull an HG_PARENT out of it, ignore the rest.
2572 if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2573 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2574 eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2575 elif [[ -z $HG_PARENT ]]; then
2576 print -u2 "Error: Cannot discover parent revision"
2577 exit 1
2580 pnode=$(trim_digest $HG_PARENT)
2581 PRETTY_PWS="${PWS} (at ${pnode})"
2582 cnode=$(hg parent -R $codemgr_ws --template '{node|short}' \
2583 2>/dev/null)
2584 PRETTY_CWS="${CWS} (at ${cnode})"}
2585 elif [[ $SCM_MODE == "git" ]]; then
2586 # Check that "head" revision specified with -c or -h is sane
2587 if [[ -n $cflag || -n $hflag ]]; then
2588 head_rev=$($GIT rev-parse --verify --quiet "$codemgr_head")
2589 if [[ -z $head_rev ]]; then
2590 print -u2 "Error: bad revision ${codemgr_head}"
2591 exit 1
2595 if [[ -z $codemgr_head ]]; then
2596 codemgr_head="HEAD";
2599 # Parent can either be specified with -p, or specified with
2600 # CODEMGR_PARENT in the environment.
2601 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2602 codemgr_parent=$CODEMGR_PARENT
2605 # Try to figure out the parent based on the branch the current
2606 # branch is tracking, if we fail, use origin/master
2607 this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2608 par_branch="origin/master"
2610 # If we're not on a branch there's nothing we can do
2611 if [[ $this_branch != "(no branch)" ]]; then
2612 $GIT for-each-ref \
2613 --format='%(refname:short) %(upstream:short)' refs/heads/ | \
2614 while read local remote; do \
2615 [[ "$local" == "$this_branch" ]] && par_branch="$remote"; \
2616 done
2619 if [[ -z $codemgr_parent ]]; then
2620 codemgr_parent=$par_branch
2622 PWS=$codemgr_parent
2625 # If the parent is a webrev, we want to do some things against
2626 # the natural workspace parent (file list, comments, etc)
2628 if [[ -n $parent_webrev ]]; then
2629 real_parent=$par_branch
2630 else
2631 real_parent=$PWS
2634 if [[ -z $flist_done ]]; then
2635 flist_from_git "$codemgr_head" "$real_parent"
2636 flist_done=1
2640 # If we have a file list now, pull out any variables set
2641 # therein.
2643 if [[ -n $flist_done ]]; then
2644 env_from_flist
2648 # If we don't have a wx-format file list, build one we can pull change
2649 # comments from.
2651 if [[ -z $wxfile ]]; then
2652 print " Comments from: git...\c"
2653 git_wxfile "$codemgr_head" "$real_parent"
2654 print " Done."
2657 if [[ -z $GIT_PARENT ]]; then
2658 GIT_PARENT=$($GIT merge-base "$real_parent" "$codemgr_head")
2660 if [[ -z $GIT_PARENT ]]; then
2661 print -u2 "Error: Cannot discover parent revision"
2662 exit 1
2665 pnode=$(trim_digest $GIT_PARENT)
2667 if [[ -n $cflag ]]; then
2668 PRETTY_PWS="previous revision (at ${pnode})"
2669 elif [[ $real_parent == */* ]]; then
2670 origin=$(echo $real_parent | cut -d/ -f1)
2671 origin=$($GIT remote -v | \
2672 $AWK '$1 == "'$origin'" { print $2; exit }')
2673 PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2674 elif [[ -n $pflag && -z $parent_webrev ]]; then
2675 PRETTY_PWS="${CWS} (explicit revision ${pnode})"
2676 else
2677 PRETTY_PWS="${PWS} (at ${pnode})"
2680 cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 \
2681 ${codemgr_head} 2>/dev/null)
2683 if [[ -n $cflag || -n $hflag ]]; then
2684 PRETTY_CWS="${CWS} (explicit head at ${cnode})"
2685 else
2686 PRETTY_CWS="${CWS} (at ${cnode})"
2688 elif [[ $SCM_MODE == "subversion" ]]; then
2691 # We only will have a real parent workspace in the case one
2692 # was specified (be it an older webrev, or another checkout).
2694 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2696 if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2697 flist_from_subversion $CWS $OLDPWD
2699 else
2700 if [[ $SCM_MODE == "unknown" ]]; then
2701 print -u2 " Unknown type of SCM in use"
2702 else
2703 print -u2 " Unsupported SCM in use: $SCM_MODE"
2706 env_from_flist
2708 if [[ -z $CODEMGR_WS ]]; then
2709 print -u2 "SCM not detected/supported and CODEMGR_WS not specified"
2710 exit 1
2713 if [[ -z $CODEMGR_PARENT ]]; then
2714 print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified"
2715 exit 1
2718 CWS=$CODEMGR_WS
2719 PWS=$CODEMGR_PARENT
2723 # If the user didn't specify a -i option, check to see if there is a
2724 # webrev-info file in the workspace directory.
2726 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2727 iflag=1
2728 INCLUDE_FILE="$CWS/webrev-info"
2731 if [[ -n $iflag ]]; then
2732 if [[ ! -r $INCLUDE_FILE ]]; then
2733 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2734 "not readable."
2735 exit 1
2736 else
2738 # $INCLUDE_FILE may be a relative path, and the script alters
2739 # PWD, so we just stash a copy in /tmp.
2741 cp $INCLUDE_FILE /tmp/$$.include
2745 # DO_EVERYTHING: break point
2746 if [[ -n $Nflag ]]; then
2747 break
2750 typeset -A itsinfo
2751 typeset -r its_sed_script=/tmp/$$.its_sed
2752 valid_prefixes=
2753 if [[ -z $nflag ]]; then
2754 DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg"
2755 if [[ -n $Iflag ]]; then
2756 REGFILE=$ITSREG
2757 elif [[ -r $HOME/.its.reg ]]; then
2758 REGFILE=$HOME/.its.reg
2759 else
2760 REGFILE=$DEFREGFILE
2762 if [[ ! -r $REGFILE ]]; then
2763 print "ERROR: Unable to read database registry file $REGFILE"
2764 exit 1
2765 elif [[ $REGFILE != $DEFREGFILE ]]; then
2766 print " its.reg from: $REGFILE"
2769 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do
2771 name=${LINE%%=*}
2772 value="${LINE#*=}"
2774 if [[ $name == PREFIX ]]; then
2775 p=${value}
2776 valid_prefixes="${p} ${valid_prefixes}"
2777 else
2778 itsinfo["${p}_${name}"]="${value}"
2780 done
2783 DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf"
2784 CONFFILES=$DEFCONFFILE
2785 if [[ -r $HOME/.its.conf ]]; then
2786 CONFFILES="${CONFFILES} $HOME/.its.conf"
2788 if [[ -n $Cflag ]]; then
2789 CONFFILES="${CONFFILES} ${ITSCONF}"
2791 its_domain=
2792 its_priority=
2793 for cf in ${CONFFILES}; do
2794 if [[ ! -r $cf ]]; then
2795 print "ERROR: Unable to read database configuration file $cf"
2796 exit 1
2797 elif [[ $cf != $DEFCONFFILE ]]; then
2798 print " its.conf: reading $cf"
2800 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do
2801 eval "${LINE}"
2802 done
2803 done
2806 # If an information tracking system is explicitly identified by prefix,
2807 # we want to disregard the specified priorities and resolve it accordingly.
2809 # To that end, we'll build a sed script to do each valid prefix in turn.
2811 for p in ${valid_prefixes}; do
2813 # When an informational URL was provided, translate it to a
2814 # hyperlink. When omitted, simply use the prefix text.
2816 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2817 itsinfo["${p}_INFO"]=${p}
2818 else
2819 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2823 # Assume that, for this invocation of webrev, all references
2824 # to this information tracking system should resolve through
2825 # the same URL.
2827 # If the caller specified -O, then always use EXTERNAL_URL.
2829 # Otherwise, look in the list of domains for a matching
2830 # INTERNAL_URL.
2832 [[ -z $Oflag ]] && for d in ${its_domain}; do
2833 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2834 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2835 break
2837 done
2838 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2839 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2843 # Turn the destination URL into a hyperlink
2845 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2847 # The character class below contains a literal tab
2848 print "/^${p}[: ]/ {
2849 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2850 s;^${p};${itsinfo[${p}_INFO]};
2851 }" >> ${its_sed_script}
2852 done
2855 # The previous loop took care of explicit specification. Now use
2856 # the configured priorities to attempt implicit translations.
2858 for p in ${its_priority}; do
2859 print "/^${itsinfo[${p}_REGEX]}[ ]/ {
2860 s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2861 }" >> ${its_sed_script}
2862 done
2866 # Search for DO_EVERYTHING above for matching "for" statement
2867 # and explanation of this terminator.
2869 done
2872 # Output directory.
2874 WDIR=${WDIR:-$CWS/webrev}
2877 # Name of the webrev, derived from the workspace name or output directory;
2878 # in the future this could potentially be an option.
2880 if [[ -n $oflag ]]; then
2881 WNAME=${WDIR##*/}
2882 else
2883 WNAME=${CWS##*/}
2886 # Make sure remote target is well formed for remote upload/delete.
2887 if [[ -n $Dflag || -n $Uflag ]]; then
2889 # If remote target is not specified, build it from scratch using
2890 # the default values.
2892 if [[ -z $tflag ]]; then
2893 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2894 else
2896 # Check upload target prefix first.
2898 if [[ "${remote_target}" != ${rsync_prefix}* &&
2899 "${remote_target}" != ${ssh_prefix}* ]]; then
2900 print "ERROR: invalid prefix of upload URI" \
2901 "($remote_target)"
2902 exit 1
2905 # If destination specification is not in the form of
2906 # host_spec:remote_dir then assume it is just remote hostname
2907 # and append a colon and destination directory formed from
2908 # local webrev directory name.
2910 typeset target_no_prefix=${remote_target##*://}
2911 if [[ ${target_no_prefix} == *:* ]]; then
2912 if [[ "${remote_target}" == *: ]]; then
2913 remote_target=${remote_target}${WNAME}
2915 else
2916 if [[ ${target_no_prefix} == */* ]]; then
2917 print "ERROR: badly formed upload URI" \
2918 "($remote_target)"
2919 exit 1
2920 else
2921 remote_target=${remote_target}:${WNAME}
2927 # Strip trailing slash. Each upload method will deal with directory
2928 # specification separately.
2930 remote_target=${remote_target%/}
2934 # Option -D by itself (option -U not present) implies no webrev generation.
2936 if [[ -z $Uflag && -n $Dflag ]]; then
2937 delete_webrev 1 1
2938 exit $?
2942 # Do not generate the webrev, just upload it or delete it.
2944 if [[ -n $nflag ]]; then
2945 if [[ -n $Dflag ]]; then
2946 delete_webrev 1 1
2947 (( $? == 0 )) || exit $?
2949 if [[ -n $Uflag ]]; then
2950 upload_webrev
2951 exit $?
2955 if [ "${WDIR%%/*}" ]; then
2956 WDIR=$PWD/$WDIR
2959 if [[ ! -d $WDIR ]]; then
2960 mkdir -p $WDIR
2961 (( $? != 0 )) && exit 1
2965 # Summarize what we're going to do.
2967 print " Workspace: ${PRETTY_CWS:-$CWS}"
2968 if [[ -n $parent_webrev ]]; then
2969 print "Compare against: webrev at $parent_webrev"
2970 else
2971 print "Compare against: ${PRETTY_PWS:-$PWS}"
2974 [[ -n $INCLUDE_FILE ]] && print " Including: $INCLUDE_FILE"
2975 print " Output to: $WDIR"
2978 # Save the file list in the webrev dir
2980 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
2982 rm -f $WDIR/$WNAME.patch
2983 rm -f $WDIR/$WNAME.ps
2984 rm -f $WDIR/$WNAME.pdf
2986 touch $WDIR/$WNAME.patch
2988 print " Output Files:"
2991 # Clean up the file list: Remove comments, blank lines and env variables.
2993 $SED -e "s/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean
2994 FLIST=/tmp/$$.flist.clean
2997 # For Mercurial, create a cache of manifest entries.
2999 if [[ $SCM_MODE == "mercurial" ]]; then
3001 # Transform the FLIST into a temporary sed script that matches
3002 # relevant entries in the Mercurial manifest as follows:
3003 # 1) The script will be used against the parent revision manifest,
3004 # so for FLIST lines that have two filenames (a renamed file)
3005 # keep only the old name.
3006 # 2) Escape all forward slashes the filename.
3007 # 3) Change the filename into another sed command that matches
3008 # that file in "hg manifest -v" output: start of line, three
3009 # octal digits for file permissions, space, a file type flag
3010 # character, space, the filename, end of line.
3011 # 4) Eliminate any duplicate entries. (This can occur if a
3012 # file has been used as the source of an hg cp and it's
3013 # also been modified in the same changeset.)
3015 SEDFILE=/tmp/$$.manifest.sed
3016 $SED '
3017 s#^[^ ]* ##
3018 s#/#\\\/#g
3019 s#^.*$#/^... . &$/p#
3020 ' < $FLIST | $SORT -u > $SEDFILE
3023 # Apply the generated script to the output of "hg manifest -v"
3024 # to get the relevant subset for this webrev.
3026 HG_PARENT_MANIFEST=/tmp/$$.manifest
3027 hg -R $CWS manifest -v -r $HG_PARENT |
3028 $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
3032 # First pass through the files: generate the per-file webrev HTML-files.
3034 cat $FLIST | while read LINE
3036 set - $LINE
3037 P=$1
3040 # Normally, each line in the file list is just a pathname of a
3041 # file that has been modified or created in the child. A file
3042 # that is renamed in the child workspace has two names on the
3043 # line: new name followed by the old name.
3045 oldname=""
3046 oldpath=""
3047 rename=
3048 if [[ $# -eq 2 ]]; then
3049 PP=$2 # old filename
3050 if [[ -f $PP ]]; then
3051 oldname=" (copied from $PP)"
3052 else
3053 oldname=" (renamed from $PP)"
3055 oldpath="$PP"
3056 rename=1
3057 PDIR=${PP%/*}
3058 if [[ $PDIR == $PP ]]; then
3059 PDIR="." # File at root of workspace
3062 PF=${PP##*/}
3064 DIR=${P%/*}
3065 if [[ $DIR == $P ]]; then
3066 DIR="." # File at root of workspace
3069 F=${P##*/}
3071 else
3072 DIR=${P%/*}
3073 if [[ "$DIR" == "$P" ]]; then
3074 DIR="." # File at root of workspace
3077 F=${P##*/}
3079 PP=$P
3080 PDIR=$DIR
3081 PF=$F
3084 COMM=`getcomments html $P $PP`
3086 print "\t$P$oldname\n\t\t\c"
3088 # Make the webrev mirror directory if necessary
3089 mkdir -p $WDIR/$DIR
3092 # We stash old and new files into parallel directories in $WDIR
3093 # and do our diffs there. This makes it possible to generate
3094 # clean looking diffs which don't have absolute paths present.
3097 build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3098 continue
3101 # Keep the old PWD around, so we can safely switch back after
3102 # diff generation, such that build_old_new runs in a
3103 # consistent environment.
3105 OWD=$PWD
3106 cd $WDIR/raw_files
3107 ofile=old/$PDIR/$PF
3108 nfile=new/$DIR/$F
3110 mv_but_nodiff=
3111 cmp $ofile $nfile > /dev/null 2>&1
3112 if [[ $? == 0 && $rename == 1 ]]; then
3113 mv_but_nodiff=1
3117 # If we have old and new versions of the file then run the appropriate
3118 # diffs. This is complicated by a couple of factors:
3120 # - renames must be handled specially: we emit a 'remove'
3121 # diff and an 'add' diff
3122 # - new files and deleted files must be handled specially
3123 # - GNU patch doesn't interpret the output of illumos diff
3124 # properly when it comes to adds and deletes. We need to
3125 # do some "cleansing" transformations:
3126 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@
3127 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@
3129 cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3130 cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3132 rm -f $WDIR/$DIR/$F.patch
3133 if [[ -z $rename ]]; then
3134 if [ ! -f "$ofile" ]; then
3135 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3136 > $WDIR/$DIR/$F.patch
3137 elif [ ! -f "$nfile" ]; then
3138 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3139 > $WDIR/$DIR/$F.patch
3140 else
3141 diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3143 else
3144 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3145 > $WDIR/$DIR/$F.patch
3147 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3148 >> $WDIR/$DIR/$F.patch
3152 # Tack the patch we just made onto the accumulated patch for the
3153 # whole wad.
3155 cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3156 print " patch\c"
3158 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3159 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3160 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3161 > $WDIR/$DIR/$F.cdiff.html
3162 print " cdiffs\c"
3164 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3165 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3166 > $WDIR/$DIR/$F.udiff.html
3167 print " udiffs\c"
3169 if [[ -x $WDIFF ]]; then
3170 $WDIFF -c "$COMM" \
3171 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3172 $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3173 if [[ $? -eq 0 ]]; then
3174 print " wdiffs\c"
3175 else
3176 print " wdiffs[fail]\c"
3180 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3181 > $WDIR/$DIR/$F.sdiff.html
3182 print " sdiffs\c"
3183 print " frames\c"
3185 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3186 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3187 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3188 # renamed file: may also have differences
3189 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3190 elif [[ -f $nfile ]]; then
3191 # new file: count added lines
3192 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3193 elif [[ -f $ofile ]]; then
3194 # old file: count deleted lines
3195 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3199 # Check if it's man page, and create plain text, html and raw (ascii)
3200 # output for the new version, as well as diffs against old version.
3202 if [[ -f "$nfile" && "$nfile" = *.+([0-9])*([a-zA-Z]) && \
3203 -x $MANDOC && -x $COL ]]; then
3204 $MANDOC -Tascii $nfile | $COL -b > $nfile.man.txt
3205 source_to_html txt < $nfile.man.txt > $nfile.man.txt.html
3206 print " man-txt\c"
3207 print "$MANCSS" > $WDIR/raw_files/new/$DIR/man.css
3208 $MANDOC -Thtml -Ostyle=man.css $nfile > $nfile.man.html
3209 print " man-html\c"
3210 $MANDOC -Tascii $nfile > $nfile.man.raw
3211 print " man-raw\c"
3212 if [[ -f "$ofile" && -z $mv_but_nodiff ]]; then
3213 $MANDOC -Tascii $ofile | $COL -b > $ofile.man.txt
3214 ${CDIFFCMD:-diff -bt -C 5} $ofile.man.txt \
3215 $nfile.man.txt > $WDIR/$DIR/$F.man.cdiff
3216 diff_to_html $F $DIR/$F "C" "$COMM" < \
3217 $WDIR/$DIR/$F.man.cdiff > \
3218 $WDIR/$DIR/$F.man.cdiff.html
3219 print " man-cdiffs\c"
3220 ${UDIFFCMD:-diff -bt -U 5} $ofile.man.txt \
3221 $nfile.man.txt > $WDIR/$DIR/$F.man.udiff
3222 diff_to_html $F $DIR/$F "U" "$COMM" < \
3223 $WDIR/$DIR/$F.man.udiff > \
3224 $WDIR/$DIR/$F.man.udiff.html
3225 print " man-udiffs\c"
3226 if [[ -x $WDIFF ]]; then
3227 $WDIFF -c "$COMM" -t "$WNAME Wdiff $DIR/$F" \
3228 $ofile.man.txt $nfile.man.txt > \
3229 $WDIR/$DIR/$F.man.wdiff.html 2>/dev/null
3230 if [[ $? -eq 0 ]]; then
3231 print " man-wdiffs\c"
3232 else
3233 print " man-wdiffs[fail]\c"
3236 sdiff_to_html $ofile.man.txt $nfile.man.txt $F.man $DIR \
3237 "$COMM" > $WDIR/$DIR/$F.man.sdiff.html
3238 print " man-sdiffs\c"
3239 print " man-frames\c"
3241 rm -f $ofile.man.txt $nfile.man.txt
3242 rm -f $WDIR/$DIR/$F.man.cdiff $WDIR/$DIR/$F.man.udiff
3246 # Now we generate the postscript for this file. We generate diffs
3247 # only in the event that there is delta, or the file is new (it seems
3248 # tree-killing to print out the contents of deleted files).
3250 if [[ -f $nfile ]]; then
3251 ocr=$ofile
3252 [[ ! -f $ofile ]] && ocr=/dev/null
3254 if [[ -z $mv_but_nodiff ]]; then
3255 textcomm=`getcomments text $P $PP`
3256 if [[ -x $CODEREVIEW ]]; then
3257 $CODEREVIEW -y "$textcomm" \
3258 -e $ocr $nfile \
3259 > /tmp/$$.psfile 2>/dev/null &&
3260 cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3261 if [[ $? -eq 0 ]]; then
3262 print " ps\c"
3263 else
3264 print " ps[fail]\c"
3270 if [[ -f $ofile ]]; then
3271 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3272 print " old\c"
3275 if [[ -f $nfile ]]; then
3276 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3277 print " new\c"
3280 cd $OWD
3282 print
3283 done
3285 frame_nav_js > $WDIR/ancnav.js
3286 frame_navigation > $WDIR/ancnav.html
3288 if [[ ! -f $WDIR/$WNAME.ps ]]; then
3289 print " Generating PDF: Skipped: no output available"
3290 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3291 print " Generating PDF: \c"
3292 fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3293 print "Done."
3294 else
3295 print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3298 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3299 # delete it - prevent accidental publishing of closed source
3301 if [[ -n "$Oflag" ]]; then
3302 $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3305 # Now build the index.html file that contains
3306 # links to the source files and their diffs.
3308 cd $CWS
3310 # Save total changed lines for Code Inspection.
3311 print "$TOTL" > $WDIR/TotalChangedLines
3313 print " index.html: \c"
3314 INDEXFILE=$WDIR/index.html
3315 exec 3<&1 # duplicate stdout to FD3.
3316 exec 1<&- # Close stdout.
3317 exec > $INDEXFILE # Open stdout to index file.
3319 print "$HTML<head>$STDHEAD"
3320 print "<title>$WNAME</title>"
3321 print "</head>"
3322 print "<body id=\"SUNWwebrev\">"
3323 print "<div class=\"summary\">"
3324 print "<h2>Code Review for $WNAME</h2>"
3326 print "<table>"
3329 # Get the preparer's name:
3331 # If the SCM detected is Mercurial, and the configuration property
3332 # ui.username is available, use that, but be careful to properly escape
3333 # angle brackets (HTML syntax characters) in the email address.
3335 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3336 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3338 preparer=
3339 if [[ "$SCM_MODE" == mercurial ]]; then
3340 preparer=`hg showconfig ui.username 2>/dev/null`
3341 if [[ -n "$preparer" ]]; then
3342 preparer="$(echo "$preparer" | html_quote)"
3345 if [[ -z "$preparer" ]]; then
3346 preparer=$(
3347 $PERL -e '
3348 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3349 if ($login) {
3350 $gcos =~ s/\&/ucfirst($login)/e;
3351 printf "%s (%s)\n", $gcos, $login;
3352 } else {
3353 printf "(unknown)\n";
3358 PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3359 print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3360 print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3361 print "</td></tr>"
3362 print "<tr><th>Compare against:</th><td>"
3363 if [[ -n $parent_webrev ]]; then
3364 print "webrev at $parent_webrev"
3365 else
3366 print "${PRETTY_PWS:-$PWS}"
3368 print "</td></tr>"
3369 print "<tr><th>Summary of changes:</th><td>"
3370 printCI $TOTL $TINS $TDEL $TMOD $TUNC
3371 print "</td></tr>"
3373 if [[ -f $WDIR/$WNAME.patch ]]; then
3374 wpatch_url="$(print $WNAME.patch | url_encode)"
3375 print "<tr><th>Patch of changes:</th><td>"
3376 print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3378 if [[ -f $WDIR/$WNAME.pdf ]]; then
3379 wpdf_url="$(print $WNAME.pdf | url_encode)"
3380 print "<tr><th>Printable review:</th><td>"
3381 print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3384 if [[ -n "$iflag" ]]; then
3385 print "<tr><th>Author comments:</th><td><div>"
3386 cat /tmp/$$.include
3387 print "</div></td></tr>"
3389 print "</table>"
3390 print "</div>"
3393 # Second pass through the files: generate the rest of the index file
3395 cat $FLIST | while read LINE
3397 set - $LINE
3398 P=$1
3400 if [[ $# == 2 ]]; then
3401 PP=$2
3402 oldname="$PP"
3403 else
3404 PP=$P
3405 oldname=""
3408 mv_but_nodiff=
3409 cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3410 if [[ $? == 0 && -n "$oldname" ]]; then
3411 mv_but_nodiff=1
3414 DIR=${P%/*}
3415 if [[ $DIR == $P ]]; then
3416 DIR="." # File at root of workspace
3419 # Avoid processing the same file twice.
3420 # It's possible for renamed files to
3421 # appear twice in the file list
3423 F=$WDIR/$P
3425 print "<p>"
3427 # If there's a diffs file, make diffs links
3429 if [[ -f $F.cdiff.html ]]; then
3430 cdiff_url="$(print $P.cdiff.html | url_encode)"
3431 udiff_url="$(print $P.udiff.html | url_encode)"
3432 sdiff_url="$(print $P.sdiff.html | url_encode)"
3433 frames_url="$(print $P.frames.html | url_encode)"
3434 print "<a href=\"$cdiff_url\">Cdiffs</a>"
3435 print "<a href=\"$udiff_url\">Udiffs</a>"
3436 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3437 wdiff_url="$(print $P.wdiff.html | url_encode)"
3438 print "<a href=\"$wdiff_url\">Wdiffs</a>"
3440 print "<a href=\"$sdiff_url\">Sdiffs</a>"
3441 print "<a href=\"$frames_url\">Frames</a>"
3442 else
3443 print " ------ ------"
3444 if [[ -x $WDIFF ]]; then
3445 print " ------"
3447 print " ------ ------"
3450 # If there's an old file, make the link
3452 if [[ -f $F-.html ]]; then
3453 oldfile_url="$(print $P-.html | url_encode)"
3454 print "<a href=\"$oldfile_url\">Old</a>"
3455 else
3456 print " ---"
3459 # If there's an new file, make the link
3461 if [[ -f $F.html ]]; then
3462 newfile_url="$(print $P.html | url_encode)"
3463 print "<a href=\"$newfile_url\">New</a>"
3464 else
3465 print " ---"
3468 if [[ -f $F.patch ]]; then
3469 patch_url="$(print $P.patch | url_encode)"
3470 print "<a href=\"$patch_url\">Patch</a>"
3471 else
3472 print " -----"
3475 if [[ -f $WDIR/raw_files/new/$P ]]; then
3476 rawfiles_url="$(print raw_files/new/$P | url_encode)"
3477 print "<a href=\"$rawfiles_url\">Raw</a>"
3478 else
3479 print " ---"
3482 print "<b>$P</b>"
3484 # For renamed files, clearly state whether or not they are modified
3485 if [[ -f "$oldname" ]]; then
3486 if [[ -n "$mv_but_nodiff" ]]; then
3487 print "<i>(copied from $oldname)</i>"
3488 else
3489 print "<i>(copied and modified from $oldname)</i>"
3491 elif [[ -n "$oldname" ]]; then
3492 if [[ -n "$mv_but_nodiff" ]]; then
3493 print "<i>(renamed from $oldname)</i>"
3494 else
3495 print "<i>(renamed and modified from $oldname)</i>"
3499 # If there's an old file, but no new file, the file was deleted
3500 if [[ -f $F-.html && ! -f $F.html ]]; then
3501 print " <i>(deleted)</i>"
3504 # Check for usr/closed and deleted_files/usr/closed
3505 if [ ! -z "$Oflag" ]; then
3506 if [[ $P == usr/closed/* || \
3507 $P == deleted_files/usr/closed/* ]]; then
3508 print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3509 "this review</i>"
3513 manpage=
3514 if [[ -f $F.man.cdiff.html || \
3515 -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3516 manpage=1
3517 print "<br/>man:"
3520 if [[ -f $F.man.cdiff.html ]]; then
3521 mancdiff_url="$(print $P.man.cdiff.html | url_encode)"
3522 manudiff_url="$(print $P.man.udiff.html | url_encode)"
3523 mansdiff_url="$(print $P.man.sdiff.html | url_encode)"
3524 manframes_url="$(print $P.man.frames.html | url_encode)"
3525 print "<a href=\"$mancdiff_url\">Cdiffs</a>"
3526 print "<a href=\"$manudiff_url\">Udiffs</a>"
3527 if [[ -f $F.man.wdiff.html && -x $WDIFF ]]; then
3528 manwdiff_url="$(print $P.man.wdiff.html | url_encode)"
3529 print "<a href=\"$manwdiff_url\">Wdiffs</a>"
3531 print "<a href=\"$mansdiff_url\">Sdiffs</a>"
3532 print "<a href=\"$manframes_url\">Frames</a>"
3533 elif [[ -n $manpage ]]; then
3534 print " ------ ------"
3535 if [[ -x $WDIFF ]]; then
3536 print " ------"
3538 print " ------ ------"
3541 if [[ -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3542 mantxt_url="$(print raw_files/new/$P.man.txt.html | url_encode)"
3543 print "<a href=\"$mantxt_url\">TXT</a>"
3544 manhtml_url="$(print raw_files/new/$P.man.html | url_encode)"
3545 print "<a href=\"$manhtml_url\">HTML</a>"
3546 manraw_url="$(print raw_files/new/$P.man.raw | url_encode)"
3547 print "<a href=\"$manraw_url\">Raw</a>"
3548 elif [[ -n $manpage ]]; then
3549 print " --- ---- ---"
3552 print "</p>"
3554 # Insert delta comments
3555 print "<blockquote><pre>"
3556 getcomments html $P $PP
3557 print "</pre>"
3559 # Add additional comments comment
3560 print "<!-- Add comments to explain changes in $P here -->"
3562 # Add count of changes.
3563 if [[ -f $F.count ]]; then
3564 cat $F.count
3565 rm $F.count
3568 if [[ $SCM_MODE == "mercurial" ||
3569 $SCM_MODE == "unknown" ]]; then
3570 # Include warnings for important file mode situations:
3571 # 1) New executable files
3572 # 2) Permission changes of any kind
3573 # 3) Existing executable files
3574 old_mode=
3575 if [[ -f $WDIR/raw_files/old/$PP ]]; then
3576 old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3579 new_mode=
3580 if [[ -f $WDIR/raw_files/new/$P ]]; then
3581 new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3584 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3585 print "<span class=\"chmod\">"
3586 print "<p>new executable file: mode $new_mode</p>"
3587 print "</span>"
3588 elif [[ -n "$old_mode" && -n "$new_mode" &&
3589 "$old_mode" != "$new_mode" ]]; then
3590 print "<span class=\"chmod\">"
3591 print "<p>mode change: $old_mode to $new_mode</p>"
3592 print "</span>"
3593 elif [[ "$new_mode" = *[1357]* ]]; then
3594 print "<span class=\"chmod\">"
3595 print "<p>executable file: mode $new_mode</p>"
3596 print "</span>"
3600 print "</blockquote>"
3601 done
3603 print
3604 print
3605 print "<hr></hr>"
3606 print "<p style=\"font-size: small\">"
3607 print "This code review page was prepared using <b>$0</b>."
3608 print "Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3609 print "illumos</a> project. The latest version may be obtained"
3610 print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3611 print "</body>"
3612 print "</html>"
3614 exec 1<&- # Close FD 1.
3615 exec 1<&3 # dup FD 3 to restore stdout.
3616 exec 3<&- # close FD 3.
3618 print "Done."
3621 # If remote deletion was specified and fails do not continue.
3623 if [[ -n $Dflag ]]; then
3624 delete_webrev 1 1
3625 (( $? == 0 )) || exit $?
3628 if [[ -n $Uflag ]]; then
3629 upload_webrev
3630 exit $?