8322 nl: misleading-indentation
[unleashed/tickless.git] / usr / src / tools / scripts / webrev.sh
blobfc2eefe5c24bf163ae50499a3661bb59f5522fd7
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 2017 Nexenta Systems, Inc.
29 # Copyright 2016 Joyent, Inc.
30 # Copyright 2016 RackTop Systems.
34 # This script takes a file list and a workspace and builds a set of html files
35 # suitable for doing a code review of source changes via a web page.
36 # Documentation is available via the manual page, webrev.1, or just
37 # type 'webrev -h'.
39 # Acknowledgements to contributors to webrev are listed in the webrev(1)
40 # man page.
43 REMOVED_COLOR=brown
44 CHANGED_COLOR=blue
45 NEW_COLOR=blue
47 HTML='<?xml version="1.0"?>
48 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
49 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
50 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
52 FRAMEHTML='<?xml version="1.0"?>
53 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
54 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
55 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
57 STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta>
58 <meta http-equiv="Content-Type" content="text/xhtml;charset=utf-8"></meta>
59 <meta http-equiv="Pragma" content="no-cache"></meta>
60 <meta http-equiv="Expires" content="-1"></meta>
61 <!--
62 Note to customizers: the body of the webrev is IDed as SUNWwebrev
63 to allow easy overriding by users of webrev via the userContent.css
64 mechanism available in some browsers.
66 For example, to have all "removed" information be red instead of
67 brown, set a rule in your userContent.css file like:
69 body#SUNWwebrev span.removed { color: red ! important; }
70 -->
71 <style type="text/css" media="screen">
72 body {
73 background-color: #eeeeee;
75 hr {
76 border: none 0;
77 border-top: 1px solid #aaa;
78 height: 1px;
80 div.summary {
81 font-size: .8em;
82 border-bottom: 1px solid #aaa;
83 padding-left: 1em;
84 padding-right: 1em;
86 div.summary h2 {
87 margin-bottom: 0.3em;
89 div.summary table th {
90 text-align: right;
91 vertical-align: top;
92 white-space: nowrap;
94 span.lineschanged {
95 font-size: 0.7em;
97 span.oldmarker {
98 color: red;
99 font-size: large;
100 font-weight: bold;
102 span.newmarker {
103 color: green;
104 font-size: large;
105 font-weight: bold;
107 span.removed {
108 color: brown;
110 span.changed {
111 color: blue;
113 span.new {
114 color: blue;
115 font-weight: bold;
117 span.chmod {
118 font-size: 0.7em;
119 color: #db7800;
121 a.print { font-size: x-small; }
122 a:hover { background-color: #ffcc99; }
123 </style>
125 <style type="text/css" media="print">
126 pre { font-size: 0.8em; font-family: courier, monospace; }
127 span.removed { color: #444; font-style: italic }
128 span.changed { font-weight: bold; }
129 span.new { font-weight: bold; }
130 span.newmarker { font-size: 1.2em; font-weight: bold; }
131 span.oldmarker { font-size: 1.2em; font-weight: bold; }
132 a.print {display: none}
133 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
134 </style>
138 # UDiffs need a slightly different CSS rule for 'new' items (we don't
139 # want them to be bolded as we do in cdiffs or sdiffs).
141 UDIFFCSS='
142 <style type="text/css" media="screen">
143 span.new {
144 color: blue;
145 font-weight: normal;
147 </style>
151 # CSS for the HTML version of the man pages.
153 MANCSS='
154 html { max-width: 880px; margin-left: 1em; }
155 body { font-size: smaller; font-family: Helvetica,Arial,sans-serif; }
156 h1 { margin-bottom: 1ex; font-size: 110%; margin-left: -4ex; }
157 h2 { margin-bottom: 1ex; font-size: 105%; margin-left: -2ex; }
158 table { width: 100%; margin-top: 0ex; margin-bottom: 0ex; }
159 td { vertical-align: top; }
160 blockquote { margin-left: 5ex; margin-top: 0ex; margin-bottom: 0ex; }
161 div.section { margin-bottom: 2ex; margin-left: 5ex; }
162 table.foot { font-size: smaller; margin-top: 1em;
163 border-top: 1px dotted #dddddd; }
164 td.foot-date { width: 50%; }
165 td.foot-os { width: 50%; text-align: right; }
166 table.head { font-size: smaller; margin-bottom: 1em;
167 border-bottom: 1px dotted #dddddd; }
168 td.head-ltitle { width: 10%; }
169 td.head-vol { width: 80%; text-align: center; }
170 td.head-rtitle { width: 10%; text-align: right; }
171 .emph { font-style: italic; font-weight: normal; }
172 .symb { font-style: normal; font-weight: bold; }
173 .lit { font-style: normal; font-weight: normal; font-family: monospace; }
174 i.addr { font-weight: normal; }
175 i.arg { font-weight: normal; }
176 b.cmd { font-style: normal; }
177 b.config { font-style: normal; }
178 b.diag { font-style: normal; }
179 i.farg { font-weight: normal; }
180 i.file { font-weight: normal; }
181 b.flag { font-style: normal; }
182 b.fname { font-style: normal; }
183 i.ftype { font-weight: normal; }
184 b.includes { font-style: normal; }
185 i.link-sec { font-weight: normal; }
186 b.macro { font-style: normal; }
187 b.name { font-style: normal; }
188 i.ref-book { font-weight: normal; }
189 i.ref-issue { font-weight: normal; }
190 i.ref-jrnl { font-weight: normal; }
191 span.ref-title { text-decoration: underline; }
192 span.type { font-style: italic; font-weight: normal; }
193 b.utility { font-style: normal; }
194 b.var { font-style: normal; }
195 dd.list-ohang { margin-left: 0ex; }
196 ul.list-bul { list-style-type: disc; padding-left: 1em; }
197 ul.list-dash { list-style-type: none; padding-left: 0em; }
198 li.list-dash:before { content: "\2014 "; }
199 ul.list-hyph { list-style-type: none; padding-left: 0em; }
200 li.list-hyph:before { content: "\2013 "; }
201 ul.list-item { list-style-type: none; padding-left: 0em; }
202 ol.list-enum { padding-left: 2em; }
206 # Display remote target with prefix and trailing slash.
208 function print_upload_header
210 typeset -r prefix=$1
211 typeset display_target
213 if [[ -z $tflag ]]; then
214 display_target=${prefix}${remote_target}
215 else
216 display_target=${remote_target}
219 if [[ ${display_target} != */ ]]; then
220 display_target=${display_target}/
223 print " Upload to: ${display_target}\n" \
224 " Uploading: \c"
228 # Upload the webrev via rsync. Return 0 on success, 1 on error.
230 function rsync_upload
232 if (( $# != 2 )); then
233 print "\nERROR: rsync_upload: wrong usage ($#)"
234 exit 1
237 typeset -r dst=$1
238 integer -r print_err_msg=$2
240 print_upload_header ${rsync_prefix}
241 print "rsync ... \c"
242 typeset -r err_msg=$( $MKTEMP /tmp/rsync_err.XXXXXX )
243 if [[ -z $err_msg ]]; then
244 print "\nERROR: rsync_upload: cannot create temporary file"
245 return 1
248 # The source directory must end with a slash in order to copy just
249 # directory contents, not the whole directory.
251 typeset src_dir=$WDIR
252 if [[ ${src_dir} != */ ]]; then
253 src_dir=${src_dir}/
255 $RSYNC -r -q ${src_dir} $dst 2>$err_msg
256 if (( $? != 0 )); then
257 if (( ${print_err_msg} > 0 )); then
258 print "Failed.\nERROR: rsync failed"
259 print "src dir: '${src_dir}'\ndst dir: '$dst'"
260 print "error messages:"
261 $SED 's/^/> /' $err_msg
262 rm -f $err_msg
264 return 1
267 rm -f $err_msg
268 print "Done."
269 return 0
273 # Create directories on remote host using SFTP. Return 0 on success,
274 # 1 on failure.
276 function remote_mkdirs
278 typeset -r dir_spec=$1
279 typeset -r host_spec=$2
282 # If the supplied path is absolute we assume all directories are
283 # created, otherwise try to create all directories in the path
284 # except the last one which will be created by scp.
286 if [[ "${dir_spec}" == */* && "${dir_spec}" != /* ]]; then
287 print "mkdirs \c"
289 # Remove the last directory from directory specification.
291 typeset -r dirs_mk=${dir_spec%/*}
292 typeset -r batch_file_mkdir=$( $MKTEMP \
293 /tmp/webrev_mkdir.XXXXXX )
294 if [[ -z $batch_file_mkdir ]]; then
295 print "\nERROR: remote_mkdirs:" \
296 "cannot create temporary file for batch file"
297 return 1
299 OLDIFS=$IFS
300 IFS=/
301 typeset dir
302 for dir in ${dirs_mk}; do
304 # Use the '-' prefix to ignore mkdir errors in order
305 # to avoid an error in case the directory already
306 # exists. We check the directory with chdir to be sure
307 # there is one.
309 print -- "-mkdir ${dir}" >> ${batch_file_mkdir}
310 print "chdir ${dir}" >> ${batch_file_mkdir}
311 done
312 IFS=$OLDIFS
313 typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
314 if [[ -z ${sftp_err_msg} ]]; then
315 print "\nERROR: remote_mkdirs:" \
316 "cannot create temporary file for error messages"
317 return 1
319 $SFTP -b ${batch_file_mkdir} ${host_spec} 2>${sftp_err_msg} 1>&2
320 if (( $? != 0 )); then
321 print "\nERROR: failed to create remote directories"
322 print "error messages:"
323 $SED 's/^/> /' ${sftp_err_msg}
324 rm -f ${sftp_err_msg} ${batch_file_mkdir}
325 return 1
327 rm -f ${sftp_err_msg} ${batch_file_mkdir}
330 return 0
334 # Upload the webrev via SSH. Return 0 on success, 1 on error.
336 function ssh_upload
338 if (( $# != 1 )); then
339 print "\nERROR: ssh_upload: wrong number of arguments"
340 exit 1
343 typeset dst=$1
344 typeset -r host_spec=${dst%%:*}
345 typeset -r dir_spec=${dst#*:}
348 # Display the upload information before calling delete_webrev
349 # because it will also print its progress.
351 print_upload_header ${ssh_prefix}
354 # If the deletion was explicitly requested there is no need
355 # to perform it again.
357 if [[ -z $Dflag ]]; then
359 # We do not care about return value because this might be
360 # the first time this directory is uploaded.
362 delete_webrev 0
366 # Create remote directories. Any error reporting will be done
367 # in remote_mkdirs function.
369 remote_mkdirs ${dir_spec} ${host_spec}
370 if (( $? != 0 )); then
371 return 1
374 print "upload ... \c"
375 typeset -r scp_err_msg=$( $MKTEMP /tmp/scp_err.XXXXXX )
376 if [[ -z ${scp_err_msg} ]]; then
377 print "\nERROR: ssh_upload:" \
378 "cannot create temporary file for error messages"
379 return 1
381 $SCP -q -C -B -o PreferredAuthentications=publickey -r \
382 $WDIR $dst 2>${scp_err_msg}
383 if (( $? != 0 )); then
384 print "Failed.\nERROR: scp failed"
385 print "src dir: '$WDIR'\ndst dir: '$dst'"
386 print "error messages:"
387 $SED 's/^/> /' ${scp_err_msg}
388 rm -f ${scp_err_msg}
389 return 1
392 rm -f ${scp_err_msg}
393 print "Done."
394 return 0
398 # Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp
399 # on failure. If first argument is 1 then perform the check of sftp return
400 # value otherwise ignore it. If second argument is present it means this run
401 # only performs deletion.
403 function delete_webrev
405 if (( $# < 1 )); then
406 print "delete_webrev: wrong number of arguments"
407 exit 1
410 integer -r check=$1
411 integer delete_only=0
412 if (( $# == 2 )); then
413 delete_only=1
417 # Strip the transport specification part of remote target first.
419 typeset -r stripped_target=${remote_target##*://}
420 typeset -r host_spec=${stripped_target%%:*}
421 typeset -r dir_spec=${stripped_target#*:}
422 typeset dir_rm
425 # Do not accept an absolute path.
427 if [[ ${dir_spec} == /* ]]; then
428 return 1
432 # Strip the ending slash.
434 if [[ ${dir_spec} == */ ]]; then
435 dir_rm=${dir_spec%%/}
436 else
437 dir_rm=${dir_spec}
440 if (( ${delete_only} > 0 )); then
441 print " Removing: \c"
442 else
443 print "rmdir \c"
445 if [[ -z "$dir_rm" ]]; then
446 print "\nERROR: empty directory for removal"
447 return 1
451 # Prepare batch file.
453 typeset -r batch_file_rm=$( $MKTEMP /tmp/webrev_remove.XXXXXX )
454 if [[ -z $batch_file_rm ]]; then
455 print "\nERROR: delete_webrev: cannot create temporary file"
456 return 1
458 print "rename $dir_rm $TRASH_DIR/removed.$$" > $batch_file_rm
461 # Perform remote deletion and remove the batch file.
463 typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
464 if [[ -z ${sftp_err_msg} ]]; then
465 print "\nERROR: delete_webrev:" \
466 "cannot create temporary file for error messages"
467 return 1
469 $SFTP -b $batch_file_rm $host_spec 2>${sftp_err_msg} 1>&2
470 integer -r ret=$?
471 rm -f $batch_file_rm
472 if (( $ret != 0 && $check > 0 )); then
473 print "Failed.\nERROR: failed to remove remote directories"
474 print "error messages:"
475 $SED 's/^/> /' ${sftp_err_msg}
476 rm -f ${sftp_err_msg}
477 return $ret
479 rm -f ${sftp_err_msg}
480 if (( ${delete_only} > 0 )); then
481 print "Done."
484 return 0
488 # Upload webrev to remote site
490 function upload_webrev
492 integer ret
494 if [[ ! -d "$WDIR" ]]; then
495 print "\nERROR: webrev directory '$WDIR' does not exist"
496 return 1
500 # Perform a late check to make sure we do not upload closed source
501 # to remote target when -n is used. If the user used custom remote
502 # target he probably knows what he is doing.
504 if [[ -n $nflag && -z $tflag ]]; then
505 $FIND $WDIR -type d -name closed \
506 | $GREP closed >/dev/null
507 if (( $? == 0 )); then
508 print "\nERROR: directory '$WDIR' contains" \
509 "\"closed\" directory"
510 return 1
516 # We have the URI for remote destination now so let's start the upload.
518 if [[ -n $tflag ]]; then
519 if [[ "${remote_target}" == ${rsync_prefix}?* ]]; then
520 rsync_upload ${remote_target##$rsync_prefix} 1
521 ret=$?
522 return $ret
523 elif [[ "${remote_target}" == ${ssh_prefix}?* ]]; then
524 ssh_upload ${remote_target##$ssh_prefix}
525 ret=$?
526 return $ret
528 else
530 # Try rsync first and fallback to SSH in case it fails.
532 rsync_upload ${remote_target} 0
533 ret=$?
534 if (( $ret != 0 )); then
535 print "Failed. (falling back to SSH)"
536 ssh_upload ${remote_target}
537 ret=$?
539 return $ret
544 # input_cmd | url_encode | output_cmd
546 # URL-encode (percent-encode) reserved characters as defined in RFC 3986.
548 # Reserved characters are: :/?#[]@!$&'()*+,;=
550 # While not a reserved character itself, percent '%' is reserved by definition
551 # so encode it first to avoid recursive transformation, and skip '/' which is
552 # a path delimiter.
554 # The quotation character is deliberately not escaped in order to make
555 # the substitution work with GNU sed.
557 function url_encode
559 $SED -e "s|%|%25|g" -e "s|:|%3A|g" -e "s|\&|%26|g" \
560 -e "s|?|%3F|g" -e "s|#|%23|g" -e "s|\[|%5B|g" \
561 -e "s|*|%2A|g" -e "s|@|%40|g" -e "s|\!|%21|g" \
562 -e "s|=|%3D|g" -e "s|;|%3B|g" -e "s|\]|%5D|g" \
563 -e "s|(|%28|g" -e "s|)|%29|g" -e "s|'|%27|g" \
564 -e "s|+|%2B|g" -e "s|\,|%2C|g" -e "s|\\\$|%24|g"
568 # input_cmd | html_quote | output_cmd
569 # or
570 # html_quote filename | output_cmd
572 # Make a piece of source code safe for display in an HTML <pre> block.
574 html_quote()
576 $SED -e "s/&/\&amp;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g" "$@" | expand
580 # Trim a digest-style revision to a conventionally readable yet useful length
582 trim_digest()
584 typeset digest=$1
586 echo $digest | $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'
590 # input_cmd | its2url | output_cmd
592 # Scan for information tracking system references and insert <a> links to the
593 # relevant databases.
595 its2url()
597 $SED -f ${its_sed_script}
601 # strip_unchanged <infile> | output_cmd
603 # Removes chunks of sdiff documents that have not changed. This makes it
604 # easier for a code reviewer to find the bits that have changed.
606 # Deleted lines of text are replaced by a horizontal rule. Some
607 # identical lines are retained before and after the changed lines to
608 # provide some context. The number of these lines is controlled by the
609 # variable C in the $AWK script below.
611 # The script detects changed lines as any line that has a "<span class="
612 # string embedded (unchanged lines have no particular class and are not
613 # part of a <span>). Blank lines (without a sequence number) are also
614 # detected since they flag lines that have been inserted or deleted.
616 strip_unchanged()
618 $AWK '
619 BEGIN { C = c = 20 }
620 NF == 0 || /<span class="/ {
621 if (c > C) {
622 c -= C
623 inx = 0
624 if (c > C) {
625 print "\n</pre><hr></hr><pre>"
626 inx = c % C
627 c = C
630 for (i = 0; i < c; i++)
631 print ln[(inx + i) % C]
633 c = 0;
634 print
635 next
637 { if (c >= C) {
638 ln[c % C] = $0
639 c++;
640 next;
642 c++;
643 print
645 END { if (c > (C * 2)) print "\n</pre><hr></hr>" }
647 ' $1
651 # sdiff_to_html
653 # This function takes two files as arguments, obtains their diff, and
654 # processes the diff output to present the files as an HTML document with
655 # the files displayed side-by-side, differences shown in color. It also
656 # takes a delta comment, rendered as an HTML snippet, as the third
657 # argument. The function takes two files as arguments, then the name of
658 # file, the path, and the comment. The HTML will be delivered on stdout,
659 # e.g.
661 # $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
662 # new/usr/src/tools/scripts/webrev.sh \
663 # webrev.sh usr/src/tools/scripts \
664 # '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
665 # 1234567</a> my bugid' > <file>.html
667 # framed_sdiff() is then called which creates $2.frames.html
668 # in the webrev tree.
670 # FYI: This function is rather unusual in its use of awk. The initial
671 # diff run produces conventional diff output showing changed lines mixed
672 # with editing codes. The changed lines are ignored - we're interested in
673 # the editing codes, e.g.
675 # 8c8
676 # 57a61
677 # 63c66,76
678 # 68,93d80
679 # 106d90
680 # 108,110d91
682 # These editing codes are parsed by the awk script and used to generate
683 # another awk script that generates HTML, e.g the above lines would turn
684 # into something like this:
686 # BEGIN { printf "<pre>\n" }
687 # function sp(n) {for (i=0;i<n;i++)printf "\n"}
688 # function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
689 # NR==8 {wl("#7A7ADD");next}
690 # NR==54 {wl("#7A7ADD");sp(3);next}
691 # NR==56 {wl("#7A7ADD");next}
692 # NR==57 {wl("black");printf "\n"; next}
693 # : :
695 # This script is then run on the original source file to generate the
696 # HTML that corresponds to the source file.
698 # The two HTML files are then combined into a single piece of HTML that
699 # uses an HTML table construct to present the files side by side. You'll
700 # notice that the changes are color-coded:
702 # black - unchanged lines
703 # blue - changed lines
704 # bold blue - new lines
705 # brown - deleted lines
707 # Blank lines are inserted in each file to keep unchanged lines in sync
708 # (side-by-side). This format is familiar to users of sdiff(1) or
709 # Teamware's filemerge tool.
711 sdiff_to_html()
713 diff -b $1 $2 > /tmp/$$.diffs
715 TNAME=$3
716 TPATH=$4
717 COMMENT=$5
720 # Now we have the diffs, generate the HTML for the old file.
722 $AWK '
723 BEGIN {
724 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
725 printf "function removed() "
726 printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
727 printf "function changed() "
728 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
729 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
731 /^</ {next}
732 /^>/ {next}
733 /^---/ {next}
736 split($1, a, /[cad]/) ;
737 if (index($1, "a")) {
738 if (a[1] == 0) {
739 n = split(a[2], r, /,/);
740 if (n == 1)
741 printf "BEGIN\t\t{sp(1)}\n"
742 else
743 printf "BEGIN\t\t{sp(%d)}\n",\
744 (r[2] - r[1]) + 1
745 next
748 printf "NR==%s\t\t{", a[1]
749 n = split(a[2], r, /,/);
750 s = r[1];
751 if (n == 1)
752 printf "bl();printf \"\\n\"; next}\n"
753 else {
754 n = r[2] - r[1]
755 printf "bl();sp(%d);next}\n",\
756 (r[2] - r[1]) + 1
758 next
760 if (index($1, "d")) {
761 n = split(a[1], r, /,/);
762 n1 = r[1]
763 n2 = r[2]
764 if (n == 1)
765 printf "NR==%s\t\t{removed(); next}\n" , n1
766 else
767 printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
768 next
770 if (index($1, "c")) {
771 n = split(a[1], r, /,/);
772 n1 = r[1]
773 n2 = r[2]
774 final = n2
775 d1 = 0
776 if (n == 1)
777 printf "NR==%s\t\t{changed();" , n1
778 else {
779 d1 = n2 - n1
780 printf "NR==%s,NR==%s\t{changed();" , n1, n2
782 m = split(a[2], r, /,/);
783 n1 = r[1]
784 n2 = r[2]
785 if (m > 1) {
786 d2 = n2 - n1
787 if (d2 > d1) {
788 if (n > 1) printf "if (NR==%d)", final
789 printf "sp(%d);", d2 - d1
792 printf "next}\n" ;
794 next
798 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
799 ' /tmp/$$.diffs > /tmp/$$.file1
802 # Now generate the HTML for the new file
804 $AWK '
805 BEGIN {
806 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
807 printf "function new() "
808 printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
809 printf "function changed() "
810 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
811 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
814 /^</ {next}
815 /^>/ {next}
816 /^---/ {next}
819 split($1, a, /[cad]/) ;
820 if (index($1, "d")) {
821 if (a[2] == 0) {
822 n = split(a[1], r, /,/);
823 if (n == 1)
824 printf "BEGIN\t\t{sp(1)}\n"
825 else
826 printf "BEGIN\t\t{sp(%d)}\n",\
827 (r[2] - r[1]) + 1
828 next
831 printf "NR==%s\t\t{", a[2]
832 n = split(a[1], r, /,/);
833 s = r[1];
834 if (n == 1)
835 printf "bl();printf \"\\n\"; next}\n"
836 else {
837 n = r[2] - r[1]
838 printf "bl();sp(%d);next}\n",\
839 (r[2] - r[1]) + 1
841 next
843 if (index($1, "a")) {
844 n = split(a[2], r, /,/);
845 n1 = r[1]
846 n2 = r[2]
847 if (n == 1)
848 printf "NR==%s\t\t{new() ; next}\n" , n1
849 else
850 printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
851 next
853 if (index($1, "c")) {
854 n = split(a[2], r, /,/);
855 n1 = r[1]
856 n2 = r[2]
857 final = n2
858 d2 = 0;
859 if (n == 1) {
860 final = n1
861 printf "NR==%s\t\t{changed();" , n1
862 } else {
863 d2 = n2 - n1
864 printf "NR==%s,NR==%s\t{changed();" , n1, n2
866 m = split(a[1], r, /,/);
867 n1 = r[1]
868 n2 = r[2]
869 if (m > 1) {
870 d1 = n2 - n1
871 if (d1 > d2) {
872 if (n > 1) printf "if (NR==%d)", final
873 printf "sp(%d);", d1 - d2
876 printf "next}\n" ;
877 next
880 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
881 ' /tmp/$$.diffs > /tmp/$$.file2
884 # Post-process the HTML files by running them back through $AWK
886 html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html
888 html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html
891 # Now combine into a valid HTML file and side-by-side into a table
893 print "$HTML<head>$STDHEAD"
894 print "<title>$WNAME Sdiff $TPATH/$TNAME</title>"
895 print "</head><body id=\"SUNWwebrev\">"
896 print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
897 print "<pre>$COMMENT</pre>\n"
898 print "<table><tr valign=\"top\">"
899 print "<td><pre>"
901 strip_unchanged /tmp/$$.file1.html
903 print "</pre></td><td><pre>"
905 strip_unchanged /tmp/$$.file2.html
907 print "</pre></td>"
908 print "</tr></table>"
909 print "</body></html>"
911 framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
912 "$COMMENT"
917 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
919 # Expects lefthand and righthand side html files created by sdiff_to_html.
920 # We use insert_anchors() to augment those with HTML navigation anchors,
921 # and then emit the main frame. Content is placed into:
923 # $WDIR/DIR/$TNAME.lhs.html
924 # $WDIR/DIR/$TNAME.rhs.html
925 # $WDIR/DIR/$TNAME.frames.html
927 # NOTE: We rely on standard usage of $WDIR and $DIR.
929 function framed_sdiff
931 typeset TNAME=$1
932 typeset TPATH=$2
933 typeset lhsfile=$3
934 typeset rhsfile=$4
935 typeset comments=$5
936 typeset RTOP
938 # Enable html files to access WDIR via a relative path.
939 RTOP=$(relative_dir $TPATH $WDIR)
941 # Make the rhs/lhs files and output the frameset file.
942 print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
944 cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
945 <script type="text/javascript" src="${RTOP}ancnav.js"></script>
946 </head>
947 <body id="SUNWwebrev" onkeypress="keypress(event);">
948 <a name="0"></a>
949 <pre>$comments</pre><hr></hr>
952 cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
954 insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
955 insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
957 close='</body></html>'
959 print $close >> $WDIR/$DIR/$TNAME.lhs.html
960 print $close >> $WDIR/$DIR/$TNAME.rhs.html
962 print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
963 print "<title>$WNAME Framed-Sdiff " \
964 "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
965 cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
966 <frameset rows="*,60">
967 <frameset cols="50%,50%">
968 <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
969 <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
970 </frameset>
971 <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0"
972 marginheight="0" name="nav"></frame>
973 <noframes>
974 <body id="SUNWwebrev">
975 Alas 'frames' webrev requires that your browser supports frames
976 and has the feature enabled.
977 </body>
978 </noframes>
979 </frameset>
980 </html>
986 # fix_postscript
988 # Merge codereview output files to a single conforming postscript file, by:
989 # - removing all extraneous headers/trailers
990 # - making the page numbers right
991 # - removing pages devoid of contents which confuse some
992 # postscript readers.
994 # From Casper.
996 function fix_postscript
998 infile=$1
1000 cat > /tmp/$$.crmerge.pl << \EOF
1002 print scalar(<>); # %!PS-Adobe---
1003 print "%%Orientation: Landscape\n";
1005 $pno = 0;
1006 $doprint = 1;
1008 $page = "";
1010 while (<>) {
1011 next if (/^%%Pages:\s*\d+/);
1013 if (/^%%Page:/) {
1014 if ($pno == 0 || $page =~ /\)S/) {
1015 # Header or single page containing text
1016 print "%%Page: ? $pno\n" if ($pno > 0);
1017 print $page;
1018 $pno++;
1019 } else {
1020 # Empty page, skip it.
1022 $page = "";
1023 $doprint = 1;
1024 next;
1027 # Skip from %%Trailer of one document to Endprolog
1028 # %%Page of the next
1029 $doprint = 0 if (/^%%Trailer/);
1030 $page .= $_ if ($doprint);
1033 if ($page =~ /\)S/) {
1034 print "%%Page: ? $pno\n";
1035 print $page;
1036 } else {
1037 $pno--;
1039 print "%%Trailer\n%%Pages: $pno\n";
1042 $PERL /tmp/$$.crmerge.pl < $infile
1047 # input_cmd | insert_anchors | output_cmd
1049 # Flag blocks of difference with sequentially numbered invisible
1050 # anchors. These are used to drive the frames version of the
1051 # sdiffs output.
1053 # NOTE: Anchor zero flags the top of the file irrespective of changes,
1054 # an additional anchor is also appended to flag the bottom.
1056 # The script detects changed lines as any line that has a "<span
1057 # class=" string embedded (unchanged lines have no class set and are
1058 # not part of a <span>. Blank lines (without a sequence number)
1059 # are also detected since they flag lines that have been inserted or
1060 # deleted.
1062 function insert_anchors
1064 $AWK '
1065 function ia() {
1066 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1069 BEGIN {
1070 anc=1;
1071 inblock=1;
1072 printf "<pre>\n";
1074 NF == 0 || /^<span class=/ {
1075 if (inblock == 0) {
1076 ia();
1077 inblock=1;
1079 print;
1080 next;
1083 inblock=0;
1084 print;
1086 END {
1087 ia();
1089 printf "<b style=\"font-size: large; color: red\">";
1090 printf "--- EOF ---</b>"
1091 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1092 printf "</pre>"
1093 printf "<form name=\"eof\">";
1094 printf "<input name=\"value\" value=\"%d\" " \
1095 "type=\"hidden\"></input>", anc - 1;
1096 printf "</form>";
1098 ' $1
1103 # relative_dir
1105 # Print a relative return path from $1 to $2. For example if
1106 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1107 # this function would print "../../../../".
1109 # In the event that $1 is not in $2 a warning is printed to stderr,
1110 # and $2 is returned-- the result of this is that the resulting webrev
1111 # is not relocatable.
1113 function relative_dir
1115 typeset cur="${1##$2?(/)}"
1118 # If the first path was specified absolutely, and it does
1119 # not start with the second path, it's an error.
1121 if [[ "$cur" = "/${1#/}" ]]; then
1122 # Should never happen.
1123 print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1124 print -u2 "to \"$2\". Check input paths. Framed webrev "
1125 print -u2 "will not be relocatable!"
1126 print $2
1127 return
1131 # This is kind of ugly. The sed script will do the following:
1133 # 1. Strip off a leading "." or "./": this is important to get
1134 # the correct arcnav links for files in $WDIR.
1135 # 2. Strip off a trailing "/": this is not strictly necessary,
1136 # but is kind of nice, since it doesn't end up in "//" at
1137 # the end of a relative path.
1138 # 3. Replace all remaining sequences of non-"/" with "..": the
1139 # assumption here is that each dirname represents another
1140 # level of relative separation.
1141 # 4. Append a trailing "/" only for non-empty paths: this way
1142 # the caller doesn't need to duplicate this logic, and does
1143 # not end up using $RTOP/file for files in $WDIR.
1145 print $cur | $SED -e '{
1146 s:^\./*::
1147 s:/$::
1148 s:[^/][^/]*:..:g
1149 s:^\(..*\)$:\1/:
1154 # frame_nav_js
1156 # Emit javascript for frame navigation
1158 function frame_nav_js
1160 cat << \EOF
1161 var myInt;
1162 var scrolling = 0;
1163 var sfactor = 3;
1164 var scount = 10;
1166 function scrollByPix()
1168 if (scount <= 0) {
1169 sfactor *= 1.2;
1170 scount = 10;
1172 parent.lhs.scrollBy(0, sfactor);
1173 parent.rhs.scrollBy(0, sfactor);
1174 scount--;
1177 function scrollToAnc(num)
1179 // Update the value of the anchor in the form which we use as
1180 // storage for this value. setAncValue() will take care of
1181 // correcting for overflow and underflow of the value and return
1182 // us the new value.
1183 num = setAncValue(num);
1185 // Set location and scroll back a little to expose previous
1186 // lines.
1188 // Note that this could be improved: it is possible although
1189 // complex to compute the x and y position of an anchor, and to
1190 // scroll to that location directly.
1192 parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1193 parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1195 parent.lhs.scrollBy(0, -30);
1196 parent.rhs.scrollBy(0, -30);
1199 function getAncValue()
1201 return (parseInt(parent.nav.document.diff.real.value));
1204 function setAncValue(val)
1206 if (val <= 0) {
1207 val = 0;
1208 parent.nav.document.diff.real.value = val;
1209 parent.nav.document.diff.display.value = "BOF";
1210 return (val);
1214 // The way we compute the max anchor value is to stash it
1215 // inline in the left and right hand side pages-- it's the same
1216 // on each side, so we pluck from the left.
1218 maxval = parent.lhs.document.eof.value.value;
1219 if (val < maxval) {
1220 parent.nav.document.diff.real.value = val;
1221 parent.nav.document.diff.display.value = val.toString();
1222 return (val);
1225 // this must be: val >= maxval
1226 val = maxval;
1227 parent.nav.document.diff.real.value = val;
1228 parent.nav.document.diff.display.value = "EOF";
1229 return (val);
1232 function stopScroll()
1234 if (scrolling == 1) {
1235 clearInterval(myInt);
1236 scrolling = 0;
1240 function startScroll()
1242 stopScroll();
1243 scrolling = 1;
1244 myInt = setInterval("scrollByPix()", 10);
1247 function handlePress(b)
1249 switch (b) {
1250 case 1:
1251 scrollToAnc(-1);
1252 break;
1253 case 2:
1254 scrollToAnc(getAncValue() - 1);
1255 break;
1256 case 3:
1257 sfactor = -3;
1258 startScroll();
1259 break;
1260 case 4:
1261 sfactor = 3;
1262 startScroll();
1263 break;
1264 case 5:
1265 scrollToAnc(getAncValue() + 1);
1266 break;
1267 case 6:
1268 scrollToAnc(999999);
1269 break;
1273 function handleRelease(b)
1275 stopScroll();
1278 function keypress(ev)
1280 var keynum;
1281 var keychar;
1283 if (window.event) { // IE
1284 keynum = ev.keyCode;
1285 } else if (ev.which) { // non-IE
1286 keynum = ev.which;
1289 keychar = String.fromCharCode(keynum);
1291 if (keychar == "k") {
1292 handlePress(2);
1293 return (0);
1294 } else if (keychar == "j" || keychar == " ") {
1295 handlePress(5);
1296 return (0);
1299 return (1);
1302 function ValidateDiffNum()
1304 var val;
1305 var i;
1307 val = parent.nav.document.diff.display.value;
1308 if (val == "EOF") {
1309 scrollToAnc(999999);
1310 return;
1313 if (val == "BOF") {
1314 scrollToAnc(0);
1315 return;
1318 i = parseInt(val);
1319 if (isNaN(i)) {
1320 parent.nav.document.diff.display.value = getAncValue();
1321 } else {
1322 scrollToAnc(i);
1325 return (false);
1331 # frame_navigation
1333 # Output anchor navigation file for framed sdiffs.
1335 function frame_navigation
1337 print "$HTML<head>$STDHEAD"
1339 cat << \EOF
1340 <title>Anchor Navigation</title>
1341 <meta http-equiv="Content-Script-Type" content="text/javascript">
1342 <meta http-equiv="Content-Type" content="text/html">
1344 <style type="text/css">
1345 div.button td { padding-left: 5px; padding-right: 5px;
1346 background-color: #eee; text-align: center;
1347 border: 1px #444 outset; cursor: pointer; }
1348 div.button a { font-weight: bold; color: black }
1349 div.button td:hover { background: #ffcc99; }
1350 </style>
1353 print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1355 cat << \EOF
1356 </head>
1357 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1358 onkeypress="keypress(event);">
1359 <noscript lang="javascript">
1360 <center>
1361 <p><big>Framed Navigation controls require Javascript</big><br></br>
1362 Either this browser is incompatable or javascript is not enabled</p>
1363 </center>
1364 </noscript>
1365 <table width="100%" border="0" align="center">
1366 <tr>
1367 <td valign="middle" width="25%">Diff navigation:
1368 Use 'j' and 'k' for next and previous diffs; or use buttons
1369 at right</td>
1370 <td align="center" valign="top" width="50%">
1371 <div class="button">
1372 <table border="0" align="center">
1373 <tr>
1374 <td>
1375 <a onMouseDown="handlePress(1);return true;"
1376 onMouseUp="handleRelease(1);return true;"
1377 onMouseOut="handleRelease(1);return true;"
1378 onClick="return false;"
1379 title="Go to Beginning Of file">BOF</a></td>
1380 <td>
1381 <a onMouseDown="handlePress(3);return true;"
1382 onMouseUp="handleRelease(3);return true;"
1383 onMouseOut="handleRelease(3);return true;"
1384 title="Scroll Up: Press and Hold to accelerate"
1385 onClick="return false;">Scroll Up</a></td>
1386 <td>
1387 <a onMouseDown="handlePress(2);return true;"
1388 onMouseUp="handleRelease(2);return true;"
1389 onMouseOut="handleRelease(2);return true;"
1390 title="Go to previous Diff"
1391 onClick="return false;">Prev Diff</a>
1392 </td></tr>
1394 <tr>
1395 <td>
1396 <a onMouseDown="handlePress(6);return true;"
1397 onMouseUp="handleRelease(6);return true;"
1398 onMouseOut="handleRelease(6);return true;"
1399 onClick="return false;"
1400 title="Go to End Of File">EOF</a></td>
1401 <td>
1402 <a onMouseDown="handlePress(4);return true;"
1403 onMouseUp="handleRelease(4);return true;"
1404 onMouseOut="handleRelease(4);return true;"
1405 title="Scroll Down: Press and Hold to accelerate"
1406 onClick="return false;">Scroll Down</a></td>
1407 <td>
1408 <a onMouseDown="handlePress(5);return true;"
1409 onMouseUp="handleRelease(5);return true;"
1410 onMouseOut="handleRelease(5);return true;"
1411 title="Go to next Diff"
1412 onClick="return false;">Next Diff</a></td>
1413 </tr>
1414 </table>
1415 </div>
1416 </td>
1417 <th valign="middle" width="25%">
1418 <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1419 <input name="display" value="BOF" size="8" type="text"></input>
1420 <input name="real" value="0" size="8" type="hidden"></input>
1421 </form>
1422 </th>
1423 </tr>
1424 </table>
1425 </body>
1426 </html>
1433 # diff_to_html <filename> <filepath> { U | C } <comment>
1435 # Processes the output of diff to produce an HTML file representing either
1436 # context or unified diffs.
1438 diff_to_html()
1440 TNAME=$1
1441 TPATH=$2
1442 DIFFTYPE=$3
1443 COMMENT=$4
1445 print "$HTML<head>$STDHEAD"
1446 print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1448 if [[ $DIFFTYPE == "U" ]]; then
1449 print "$UDIFFCSS"
1452 cat <<-EOF
1453 </head>
1454 <body id="SUNWwebrev">
1455 <a class="print" href="javascript:print()">Print this page</a>
1456 <pre>$COMMENT</pre>
1457 <pre>
1460 html_quote | $AWK '
1461 /^--- new/ { next }
1462 /^\+\+\+ new/ { next }
1463 /^--- old/ { next }
1464 /^\*\*\* old/ { next }
1465 /^\*\*\*\*/ { next }
1466 /^-------/ { printf "<center><h1>%s</h1></center>\n", $0; next }
1467 /^\@\@.*\@\@$/ { printf "</pre><hr></hr><pre>\n";
1468 printf "<span class=\"newmarker\">%s</span>\n", $0;
1469 next}
1471 /^\*\*\*/ { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1472 next}
1473 /^---/ { printf "<span class=\"newmarker\">%s</span>\n", $0;
1474 next}
1475 /^\+/ {printf "<span class=\"new\">%s</span>\n", $0; next}
1476 /^!/ {printf "<span class=\"changed\">%s</span>\n", $0; next}
1477 /^-/ {printf "<span class=\"removed\">%s</span>\n", $0; next}
1478 {printf "%s\n", $0; next}
1481 print "</pre></body></html>\n"
1486 # source_to_html { new | old } <filename>
1488 # Process a plain vanilla source file to transform it into an HTML file.
1490 source_to_html()
1492 WHICH=$1
1493 TNAME=$2
1495 print "$HTML<head>$STDHEAD"
1496 print "<title>$WNAME $WHICH $TNAME</title>"
1497 print "<body id=\"SUNWwebrev\">"
1498 print "<pre>"
1499 html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1500 print "</pre></body></html>"
1504 # comments_from_wx {text|html} filepath
1506 # Given the pathname of a file, find its location in a "wx" active
1507 # file list and print the following comment. Output is either text or
1508 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1509 # are turned into URLs.
1511 # This is also used with Mercurial and the file list provided by hg-active.
1513 comments_from_wx()
1515 typeset fmt=$1
1516 typeset p=$2
1518 comm=`$AWK '
1519 $1 == "'$p'" {
1520 do getline ; while (NF > 0)
1521 getline
1522 while (NF > 0) { print ; getline }
1523 exit
1524 }' < $wxfile`
1526 if [[ -z $comm ]]; then
1527 comm="*** NO COMMENTS ***"
1530 if [[ $fmt == "text" ]]; then
1531 print -- "$comm"
1532 return
1535 print -- "$comm" | html_quote | its2url
1540 # getcomments {text|html} filepath parentpath
1542 # Fetch the comments depending on what SCM mode we're in.
1544 getcomments()
1546 typeset fmt=$1
1547 typeset p=$2
1548 typeset pp=$3
1550 if [[ -n $Nflag ]]; then
1551 return
1554 # Mercurial support uses a file list in wx format, so this
1555 # will be used there, too
1557 if [[ -n $wxfile ]]; then
1558 comments_from_wx $fmt $p
1563 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1565 # Print out Code Inspection figures similar to sccs-prt(1) format.
1567 function printCI
1569 integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1570 typeset str
1571 if (( tot == 1 )); then
1572 str="line"
1573 else
1574 str="lines"
1576 printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1577 $tot $str $ins $del $mod $unc
1582 # difflines <oldfile> <newfile>
1584 # Calculate and emit number of added, removed, modified and unchanged lines,
1585 # and total lines changed, the sum of added + removed + modified.
1587 function difflines
1589 integer tot mod del ins unc err
1590 typeset filename
1592 eval $( diff -e $1 $2 | $AWK '
1593 # Change range of lines: N,Nc
1594 /^[0-9]*,[0-9]*c$/ {
1595 n=split(substr($1,1,length($1)-1), counts, ",");
1596 if (n != 2) {
1597 error=2
1598 exit;
1601 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1602 # following would be 5 - 3 = 2! Hence +1 for correction.
1604 r=(counts[2]-counts[1])+1;
1607 # Now count replacement lines: each represents a change instead
1608 # of a delete, so increment c and decrement r.
1610 while (getline != /^\.$/) {
1611 c++;
1612 r--;
1615 # If there were more replacement lines than original lines,
1616 # then r will be negative; in this case there are no deletions,
1617 # but there are r changes that should be counted as adds, and
1618 # since r is negative, subtract it from a and add it to c.
1620 if (r < 0) {
1621 a-=r;
1622 c+=r;
1626 # If there were more original lines than replacement lines, then
1627 # r will be positive; in this case, increment d by that much.
1629 if (r > 0) {
1630 d+=r;
1632 next;
1635 # Change lines: Nc
1636 /^[0-9].*c$/ {
1637 # The first line is a replacement; any more are additions.
1638 if (getline != /^\.$/) {
1639 c++;
1640 while (getline != /^\.$/) a++;
1642 next;
1645 # Add lines: both Na and N,Na
1646 /^[0-9].*a$/ {
1647 while (getline != /^\.$/) a++;
1648 next;
1651 # Delete range of lines: N,Nd
1652 /^[0-9]*,[0-9]*d$/ {
1653 n=split(substr($1,1,length($1)-1), counts, ",");
1654 if (n != 2) {
1655 error=2
1656 exit;
1659 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1660 # following would be 5 - 3 = 2! Hence +1 for correction.
1662 r=(counts[2]-counts[1])+1;
1663 d+=r;
1664 next;
1667 # Delete line: Nd. For example 10d says line 10 is deleted.
1668 /^[0-9]*d$/ {d++; next}
1670 # Should not get here!
1672 error=1;
1673 exit;
1676 # Finish off - print results
1677 END {
1678 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1679 (c+d+a), c, d, a, error);
1680 }' )
1682 # End of $AWK, Check to see if any trouble occurred.
1683 if (( $? > 0 || err > 0 )); then
1684 print "Unexpected Error occurred reading" \
1685 "\`diff -e $1 $2\`: \$?=$?, err=" $err
1686 return
1689 # Accumulate totals
1690 (( TOTL += tot ))
1691 (( TMOD += mod ))
1692 (( TDEL += del ))
1693 (( TINS += ins ))
1694 # Calculate unchanged lines
1695 unc=`wc -l < $1`
1696 if (( unc > 0 )); then
1697 (( unc -= del + mod ))
1698 (( TUNC += unc ))
1700 # print summary
1701 print "<span class=\"lineschanged\">"
1702 printCI $tot $ins $del $mod $unc
1703 print "</span>"
1708 # flist_from_wx
1710 # Sets up webrev to source its information from a wx-formatted file.
1711 # Sets the global 'wxfile' variable.
1713 function flist_from_wx
1715 typeset argfile=$1
1716 if [[ -n ${argfile%%/*} ]]; then
1718 # If the wx file pathname is relative then make it absolute
1719 # because the webrev does a "cd" later on.
1721 wxfile=$PWD/$argfile
1722 else
1723 wxfile=$argfile
1726 $AWK '{ c = 1; print;
1727 while (getline) {
1728 if (NF == 0) { c = -c; continue }
1729 if (c > 0) print
1731 }' $wxfile > $FLIST
1733 print " Done."
1737 # Call hg-active to get the active list output in the wx active list format
1739 function hg_active_wxfile
1741 typeset child=$1
1742 typeset parent=$2
1744 TMPFLIST=/tmp/$$.active
1745 $HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1746 wxfile=$TMPFLIST
1750 # flist_from_mercurial
1751 # Call hg-active to get a wx-style active list, and hand it off to
1752 # flist_from_wx
1754 function flist_from_mercurial
1756 typeset child=$1
1757 typeset parent=$2
1759 print " File list from: hg-active -p $parent ...\c"
1760 if [[ ! -x $HG_ACTIVE ]]; then
1761 print # Blank line for the \c above
1762 print -u2 "Error: hg-active tool not found. Exiting"
1763 exit 1
1765 hg_active_wxfile $child $parent
1767 # flist_from_wx prints the Done, so we don't have to.
1768 flist_from_wx $TMPFLIST
1772 # Transform a specified 'git log' output format into a wx-like active list.
1774 function git_wxfile
1776 typeset child="$1"
1777 typeset parent="$2"
1779 TMPFLIST=/tmp/$$.active
1780 $PERL -e 'my (%files, %realfiles, $msg);
1781 my $parent = $ARGV[0];
1782 my $child = $ARGV[1];
1784 open(F, "git diff -M --name-status $parent..$child |");
1785 while (<F>) {
1786 chomp;
1787 if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
1788 if ($1 >= 75) { # Probably worth treating as a rename
1789 $realfiles{$3} = $2;
1790 } else {
1791 $realfiles{$3} = $3;
1792 $realfiles{$2} = $2;
1794 } else {
1795 my $f = (split /\s+/, $_)[1];
1796 $realfiles{$f} = $f;
1799 close(F);
1801 my $state = 1; # 0|comments, 1|files
1802 open(F, "git whatchanged --pretty=format:%B $parent..$child |");
1803 while (<F>) {
1804 chomp;
1805 if (/^:[0-9]{6}/) {
1806 my ($unused, $fname, $fname2) = split(/\t/, $_);
1807 $fname = $fname2 if defined($fname2);
1808 next if !defined($realfiles{$fname}); # No real change
1809 $state = 1;
1810 chomp $msg;
1811 $files{$fname} .= $msg;
1812 } else {
1813 if ($state == 1) {
1814 $state = 0;
1815 $msg = /^\n/ ? "" : "\n";
1817 $msg .= "$_\n" if ($_);
1820 close(F);
1822 for (sort keys %files) {
1823 if ($realfiles{$_} ne $_) {
1824 print "$_ $realfiles{$_}\n$files{$_}\n\n";
1825 } else {
1826 print "$_\n$files{$_}\n\n"
1828 }' ${parent} ${child} > $TMPFLIST
1830 wxfile=$TMPFLIST
1834 # flist_from_git
1835 # Build a wx-style active list, and hand it off to flist_from_wx
1837 function flist_from_git
1839 typeset child=$1
1840 typeset parent=$2
1842 print " File list from: git ...\c"
1843 git_wxfile "$child" "$parent";
1845 # flist_from_wx prints the Done, so we don't have to.
1846 flist_from_wx $TMPFLIST
1850 # flist_from_subversion
1852 # Generate the file list by extracting file names from svn status.
1854 function flist_from_subversion
1856 CWS=$1
1857 OLDPWD=$2
1859 cd $CWS
1860 print -u2 " File list from: svn status ... \c"
1861 svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1862 print -u2 " Done."
1863 cd $OLDPWD
1866 function env_from_flist
1868 [[ -r $FLIST ]] || return
1871 # Use "eval" to set env variables that are listed in the file
1872 # list. Then copy those into our local versions of those
1873 # variables if they have not been set already.
1875 eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1877 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1878 codemgr_ws=$CODEMGR_WS
1879 export CODEMGR_WS
1883 # Check to see if CODEMGR_PARENT is set in the flist file.
1885 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1886 codemgr_parent=$CODEMGR_PARENT
1887 export CODEMGR_PARENT
1891 function look_for_prog
1893 typeset path
1894 typeset ppath
1895 typeset progname=$1
1897 ppath=$PATH
1898 ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1899 ppath=$ppath:/opt/onbld/bin
1900 ppath=$ppath:/opt/onbld/bin/`uname -p`
1902 PATH=$ppath prog=`whence $progname`
1903 if [[ -n $prog ]]; then
1904 print $prog
1908 function get_file_mode
1910 $PERL -e '
1911 if (@stat = stat($ARGV[0])) {
1912 $mode = $stat[2] & 0777;
1913 printf "%03o\n", $mode;
1914 exit 0;
1915 } else {
1916 exit 1;
1918 ' $1
1921 function build_old_new_mercurial
1923 typeset olddir="$1"
1924 typeset newdir="$2"
1925 typeset old_mode=
1926 typeset new_mode=
1927 typeset file
1930 # Get old file mode, from the parent revision manifest entry.
1931 # Mercurial only stores a "file is executable" flag, but the
1932 # manifest will display an octal mode "644" or "755".
1934 if [[ "$PDIR" == "." ]]; then
1935 file="$PF"
1936 else
1937 file="$PDIR/$PF"
1939 file=`echo $file | $SED 's#/#\\\/#g'`
1940 # match the exact filename, and return only the permission digits
1941 old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
1942 < $HG_PARENT_MANIFEST`
1945 # Get new file mode, directly from the filesystem.
1946 # Normalize the mode to match Mercurial's behavior.
1948 new_mode=`get_file_mode $CWS/$DIR/$F`
1949 if [[ -n "$new_mode" ]]; then
1950 if [[ "$new_mode" = *[1357]* ]]; then
1951 new_mode=755
1952 else
1953 new_mode=644
1958 # new version of the file.
1960 rm -rf $newdir/$DIR/$F
1961 if [[ -e $CWS/$DIR/$F ]]; then
1962 cp $CWS/$DIR/$F $newdir/$DIR/$F
1963 if [[ -n $new_mode ]]; then
1964 chmod $new_mode $newdir/$DIR/$F
1965 else
1966 # should never happen
1967 print -u2 "ERROR: set mode of $newdir/$DIR/$F"
1972 # parent's version of the file
1974 # Note that we get this from the last version common to both
1975 # ourselves and the parent. References are via $CWS since we have no
1976 # guarantee that the parent workspace is reachable via the filesystem.
1978 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
1979 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1980 elif [[ -n $HG_PARENT ]]; then
1981 hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
1982 $olddir/$PDIR/$PF 2>/dev/null
1984 if (( $? != 0 )); then
1985 rm -f $olddir/$PDIR/$PF
1986 else
1987 if [[ -n $old_mode ]]; then
1988 chmod $old_mode $olddir/$PDIR/$PF
1989 else
1990 # should never happen
1991 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
1997 function build_old_new_git
1999 typeset olddir="$1"
2000 typeset newdir="$2"
2001 typeset o_mode=
2002 typeset n_mode=
2003 typeset o_object=
2004 typeset n_object=
2005 typeset OWD=$PWD
2006 typeset file
2007 typeset type
2009 cd $CWS
2012 # Get old file and its mode from the git object tree
2014 if [[ "$PDIR" == "." ]]; then
2015 file="$PF"
2016 else
2017 file="$PDIR/$PF"
2020 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2021 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2022 else
2023 $GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk
2024 $GIT cat-file $type $o_object > $olddir/$file 2>/dev/null
2026 if (( $? != 0 )); then
2027 rm -f $olddir/$file
2028 elif [[ -n $o_mode ]]; then
2029 # Strip the first 3 digits, to get a regular octal mode
2030 o_mode=${o_mode/???/}
2031 chmod $o_mode $olddir/$file
2032 else
2033 # should never happen
2034 print -u2 "ERROR: set mode of $olddir/$file"
2039 # new version of the file.
2041 if [[ "$DIR" == "." ]]; then
2042 file="$F"
2043 else
2044 file="$DIR/$F"
2046 rm -rf $newdir/$file
2048 if [[ -e $CWS/$DIR/$F ]]; then
2049 cp $CWS/$DIR/$F $newdir/$DIR/$F
2050 chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F
2052 cd $OWD
2055 function build_old_new_subversion
2057 typeset olddir="$1"
2058 typeset newdir="$2"
2060 # Snag new version of file.
2061 rm -f $newdir/$DIR/$F
2062 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2064 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2065 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2066 else
2067 # Get the parent's version of the file.
2068 svn status $CWS/$DIR/$F | read stat file
2069 if [[ $stat != "A" ]]; then
2070 svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2075 function build_old_new_unknown
2077 typeset olddir="$1"
2078 typeset newdir="$2"
2081 # Snag new version of file.
2083 rm -f $newdir/$DIR/$F
2084 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2087 # Snag the parent's version of the file.
2089 if [[ -f $PWS/$PDIR/$PF ]]; then
2090 rm -f $olddir/$PDIR/$PF
2091 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2095 function build_old_new
2097 typeset WDIR=$1
2098 typeset PWS=$2
2099 typeset PDIR=$3
2100 typeset PF=$4
2101 typeset CWS=$5
2102 typeset DIR=$6
2103 typeset F=$7
2105 typeset olddir="$WDIR/raw_files/old"
2106 typeset newdir="$WDIR/raw_files/new"
2108 mkdir -p $olddir/$PDIR
2109 mkdir -p $newdir/$DIR
2111 if [[ $SCM_MODE == "mercurial" ]]; then
2112 build_old_new_mercurial "$olddir" "$newdir"
2113 elif [[ $SCM_MODE == "git" ]]; then
2114 build_old_new_git "$olddir" "$newdir"
2115 elif [[ $SCM_MODE == "subversion" ]]; then
2116 build_old_new_subversion "$olddir" "$newdir"
2117 elif [[ $SCM_MODE == "unknown" ]]; then
2118 build_old_new_unknown "$olddir" "$newdir"
2121 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2122 print "*** Error: file not in parent or child"
2123 return 1
2125 return 0
2130 # Usage message.
2132 function usage
2134 print 'Usage:\twebrev [common-options]
2135 webrev [common-options] ( <file> | - )
2136 webrev [common-options] -w <wx file>
2138 Options:
2139 -c <revision>: generate webrev for single revision (git only)
2140 -C <filename>: Use <filename> for the information tracking configuration.
2141 -D: delete remote webrev
2142 -h <revision>: specify "head" revision for comparison (git only)
2143 -i <filename>: Include <filename> in the index.html file.
2144 -I <filename>: Use <filename> for the information tracking registry.
2145 -n: do not generate the webrev (useful with -U)
2146 -O: Print bugids/arc cases suitable for OpenSolaris.
2147 -o <outdir>: Output webrev to specified directory.
2148 -p <compare-against>: Use specified parent wkspc or basis for comparison
2149 -t <remote_target>: Specify remote destination for webrev upload
2150 -U: upload the webrev to remote destination
2151 -w <wxfile>: Use specified wx active file.
2153 Environment:
2154 WDIR: Control the output directory.
2155 WEBREV_TRASH_DIR: Set directory for webrev delete.
2157 SCM Environment:
2158 CODEMGR_WS: Workspace location.
2159 CODEMGR_PARENT: Parent workspace location.
2162 exit 2
2167 # Main program starts here
2171 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2173 set +o noclobber
2175 PATH=$(/bin/dirname "$(whence $0)"):$PATH
2177 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2178 [[ -z $WX ]] && WX=`look_for_prog wx`
2179 [[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
2180 [[ -z $GIT ]] && GIT=`look_for_prog git`
2181 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2182 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2183 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2184 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2185 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2186 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2187 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2188 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2189 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2190 [[ -z $SCP ]] && SCP=`look_for_prog scp`
2191 [[ -z $SED ]] && SED=`look_for_prog sed`
2192 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2193 [[ -z $SORT ]] && SORT=`look_for_prog sort`
2194 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2195 [[ -z $GREP ]] && GREP=`look_for_prog grep`
2196 [[ -z $FIND ]] && FIND=`look_for_prog find`
2197 [[ -z $MANDOC ]] && MANDOC=`look_for_prog mandoc`
2198 [[ -z $COL ]] && COL=`look_for_prog col`
2200 # set name of trash directory for remote webrev deletion
2201 TRASH_DIR=".trash"
2202 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2204 if [[ ! -x $PERL ]]; then
2205 print -u2 "Error: No perl interpreter found. Exiting."
2206 exit 1
2209 if [[ ! -x $WHICH_SCM ]]; then
2210 print -u2 "Error: Could not find which_scm. Exiting."
2211 exit 1
2215 # These aren't fatal, but we want to note them to the user.
2216 # We don't warn on the absence of 'wx' until later when we've
2217 # determined that we actually need to try to invoke it.
2219 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2220 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2221 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2223 # Declare global total counters.
2224 integer TOTL TINS TDEL TMOD TUNC
2226 # default remote host for upload/delete
2227 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2228 # prefixes for upload targets
2229 typeset -r rsync_prefix="rsync://"
2230 typeset -r ssh_prefix="ssh://"
2232 cflag=
2233 Cflag=
2234 Dflag=
2235 flist_mode=
2236 flist_file=
2237 hflag=
2238 iflag=
2239 Iflag=
2240 lflag=
2241 Nflag=
2242 nflag=
2243 Oflag=
2244 oflag=
2245 pflag=
2246 tflag=
2247 uflag=
2248 Uflag=
2249 wflag=
2250 remote_target=
2253 # NOTE: when adding/removing options it is necessary to sync the list
2254 # with usr/src/tools/onbld/hgext/cdm.py
2256 while getopts "c:C:Dh:i:I:lnNo:Op:t:Uw" opt
2258 case $opt in
2259 c) cflag=1
2260 codemgr_head=$OPTARG
2261 codemgr_parent=$OPTARG~1;;
2263 C) Cflag=1
2264 ITSCONF=$OPTARG;;
2266 D) Dflag=1;;
2268 h) hflag=1
2269 codemgr_head=$OPTARG;;
2271 i) iflag=1
2272 INCLUDE_FILE=$OPTARG;;
2274 I) Iflag=1
2275 ITSREG=$OPTARG;;
2277 N) Nflag=1;;
2279 n) nflag=1;;
2281 O) Oflag=1;;
2283 o) oflag=1
2284 # Strip the trailing slash to correctly form remote target.
2285 WDIR=${OPTARG%/};;
2287 p) pflag=1
2288 codemgr_parent=$OPTARG;;
2290 t) tflag=1
2291 remote_target=$OPTARG;;
2293 U) Uflag=1;;
2295 w) wflag=1;;
2297 ?) usage;;
2298 esac
2299 done
2301 FLIST=/tmp/$$.flist
2303 if [[ -n $wflag && -n $lflag ]]; then
2304 usage
2307 # more sanity checking
2308 if [[ -n $nflag && -z $Uflag ]]; then
2309 print "it does not make sense to skip webrev generation" \
2310 "without -U"
2311 exit 1
2314 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2315 echo "remote target has to be used only for upload or delete"
2316 exit 1
2320 # For the invocation "webrev -n -U" with no other options, webrev will assume
2321 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2322 # $(basename ${CWS}). So we need to get CWS set before we skip any remaining
2323 # logic.
2325 $WHICH_SCM | read SCM_MODE junk || exit 1
2326 if [[ $SCM_MODE == "mercurial" ]]; then
2328 # Mercurial priorities:
2329 # 1. hg root from CODEMGR_WS environment variable
2330 # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under
2331 # usr/closed when we run webrev
2332 # 2. hg root from directory of invocation
2334 if [[ ${PWD} =~ "usr/closed" ]]; then
2335 testparent=${CODEMGR_WS}/usr/closed
2336 # If we're in OpenSolaris mode, we enforce a minor policy:
2337 # help to make sure the reviewer doesn't accidentally publish
2338 # source which is under usr/closed
2339 if [[ -n "$Oflag" ]]; then
2340 print -u2 "OpenSolaris output not permitted with" \
2341 "usr/closed changes"
2342 exit 1
2344 else
2345 testparent=${CODEMGR_WS}
2347 [[ -z $codemgr_ws && -n $testparent ]] && \
2348 codemgr_ws=$(hg root -R $testparent 2>/dev/null)
2349 [[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null)
2350 CWS=$codemgr_ws
2351 elif [[ $SCM_MODE == "git" ]]; then
2353 # Git priorities:
2354 # 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2355 # 2. git rev-parse --git-dir from directory of invocation
2357 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2358 codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \
2359 2>/dev/null)
2360 [[ -z $codemgr_ws ]] && \
2361 codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null)
2363 if [[ "$codemgr_ws" == ".git" ]]; then
2364 codemgr_ws="${PWD}/${codemgr_ws}"
2367 if [[ "$codemgr_ws" = *"/.git" ]]; then
2368 codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git'
2370 CWS="$codemgr_ws"
2371 elif [[ $SCM_MODE == "subversion" ]]; then
2373 # Subversion priorities:
2374 # 1. CODEMGR_WS from environment
2375 # 2. Relative path from current directory to SVN repository root
2377 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2378 CWS=$CODEMGR_WS
2379 else
2380 svn info | while read line; do
2381 if [[ $line == "URL: "* ]]; then
2382 url=${line#URL: }
2383 elif [[ $line == "Repository Root: "* ]]; then
2384 repo=${line#Repository Root: }
2386 done
2388 rel=${url#$repo}
2389 CWS=${PWD%$rel}
2394 # If no SCM has been determined, take either the environment setting
2395 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2397 if [[ -z ${CWS} ]]; then
2398 CWS=${CODEMGR_WS:-.}
2402 # If the command line options indicate no webrev generation, either
2403 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2404 # ton of logic we can skip.
2406 # Instead of increasing indentation, we intentionally leave this loop
2407 # body open here, and exit via break from multiple points within.
2408 # Search for DO_EVERYTHING below to find the break points and closure.
2410 for do_everything in 1; do
2412 # DO_EVERYTHING: break point
2413 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2414 break
2418 # If this manually set as the parent, and it appears to be an earlier webrev,
2419 # then note that fact and set the parent to the raw_files/new subdirectory.
2421 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2422 parent_webrev=$(readlink -f "$codemgr_parent")
2423 codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new")
2426 if [[ -z $wflag && -z $lflag ]]; then
2427 shift $(($OPTIND - 1))
2429 if [[ $1 == "-" ]]; then
2430 cat > $FLIST
2431 flist_mode="stdin"
2432 flist_done=1
2433 shift
2434 elif [[ -n $1 ]]; then
2435 if [[ ! -r $1 ]]; then
2436 print -u2 "$1: no such file or not readable"
2437 usage
2439 cat $1 > $FLIST
2440 flist_mode="file"
2441 flist_file=$1
2442 flist_done=1
2443 shift
2444 else
2445 flist_mode="auto"
2450 # Before we go on to further consider -l and -w, work out which SCM we think
2451 # is in use.
2453 case "$SCM_MODE" in
2454 mercurial|git|subversion)
2456 unknown)
2457 if [[ $flist_mode == "auto" ]]; then
2458 print -u2 "Unable to determine SCM in use and file list not specified"
2459 print -u2 "See which_scm(1) for SCM detection information."
2460 exit 1
2464 if [[ $flist_mode == "auto" ]]; then
2465 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2466 exit 1
2469 esac
2471 print -u2 " SCM detected: $SCM_MODE"
2473 if [[ -n $wflag ]]; then
2475 # If the -w is given then assume the file list is in Bonwick's "wx"
2476 # command format, i.e. pathname lines alternating with SCCS comment
2477 # lines with blank lines as separators. Use the SCCS comments later
2478 # in building the index.html file.
2480 shift $(($OPTIND - 1))
2481 wxfile=$1
2482 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2483 if [[ -r $CODEMGR_WS/wx/active ]]; then
2484 wxfile=$CODEMGR_WS/wx/active
2488 [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2489 "be auto-detected (check \$CODEMGR_WS)" && exit 1
2491 if [[ ! -r $wxfile ]]; then
2492 print -u2 "$wxfile: no such file or not readable"
2493 usage
2496 print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2497 flist_from_wx $wxfile
2498 flist_done=1
2499 if [[ -n "$*" ]]; then
2500 shift
2502 elif [[ $flist_mode == "stdin" ]]; then
2503 print -u2 " File list from: standard input"
2504 elif [[ $flist_mode == "file" ]]; then
2505 print -u2 " File list from: $flist_file"
2508 if [[ $# -gt 0 ]]; then
2509 print -u2 "WARNING: unused arguments: $*"
2513 # Before we entered the DO_EVERYTHING loop, we should have already set CWS
2514 # and CODEMGR_WS as needed. Here, we set the parent workspace.
2516 if [[ $SCM_MODE == "mercurial" ]]; then
2518 # Parent can either be specified with -p
2519 # Specified with CODEMGR_PARENT in the environment
2520 # or taken from hg's default path.
2523 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2524 codemgr_parent=$CODEMGR_PARENT
2527 if [[ -z $codemgr_parent ]]; then
2528 codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
2531 PWS=$codemgr_parent
2534 # If the parent is a webrev, we want to do some things against
2535 # the natural workspace parent (file list, comments, etc)
2537 if [[ -n $parent_webrev ]]; then
2538 real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
2539 else
2540 real_parent=$PWS
2544 # If hg-active exists, then we run it. In the case of no explicit
2545 # flist given, we'll use it for our comments. In the case of an
2546 # explicit flist given we'll try to use it for comments for any
2547 # files mentioned in the flist.
2549 if [[ -z $flist_done ]]; then
2550 flist_from_mercurial $CWS $real_parent
2551 flist_done=1
2555 # If we have a file list now, pull out any variables set
2556 # therein. We do this now (rather than when we possibly use
2557 # hg-active to find comments) to avoid stomping specifications
2558 # in the user-specified flist.
2560 if [[ -n $flist_done ]]; then
2561 env_from_flist
2565 # Only call hg-active if we don't have a wx formatted file already
2567 if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2568 print " Comments from: hg-active -p $real_parent ...\c"
2569 hg_active_wxfile $CWS $real_parent
2570 print " Done."
2574 # At this point we must have a wx flist either from hg-active,
2575 # or in general. Use it to try and find our parent revision,
2576 # if we don't have one.
2578 if [[ -z $HG_PARENT ]]; then
2579 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2583 # If we still don't have a parent, we must have been given a
2584 # wx-style active list with no HG_PARENT specification, run
2585 # hg-active and pull an HG_PARENT out of it, ignore the rest.
2587 if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2588 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2589 eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2590 elif [[ -z $HG_PARENT ]]; then
2591 print -u2 "Error: Cannot discover parent revision"
2592 exit 1
2595 pnode=$(trim_digest $HG_PARENT)
2596 PRETTY_PWS="${PWS} (at ${pnode})"
2597 cnode=$(hg parent -R $codemgr_ws --template '{node|short}' \
2598 2>/dev/null)
2599 PRETTY_CWS="${CWS} (at ${cnode})"}
2600 elif [[ $SCM_MODE == "git" ]]; then
2601 # Check that "head" revision specified with -c or -h is sane
2602 if [[ -n $cflag || -n $hflag ]]; then
2603 head_rev=$($GIT rev-parse --verify --quiet "$codemgr_head")
2604 if [[ -z $head_rev ]]; then
2605 print -u2 "Error: bad revision ${codemgr_head}"
2606 exit 1
2610 if [[ -z $codemgr_head ]]; then
2611 codemgr_head="HEAD";
2614 # Parent can either be specified with -p, or specified with
2615 # CODEMGR_PARENT in the environment.
2616 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2617 codemgr_parent=$CODEMGR_PARENT
2620 # Try to figure out the parent based on the branch the current
2621 # branch is tracking, if we fail, use origin/master
2622 this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2623 par_branch="origin/master"
2625 # If we're not on a branch there's nothing we can do
2626 if [[ $this_branch != "(no branch)" ]]; then
2627 $GIT for-each-ref \
2628 --format='%(refname:short) %(upstream:short)' \
2629 refs/heads/ | \
2630 while read local remote; do
2631 if [[ "$local" == "$this_branch" ]]; then
2632 par_branch="$remote"
2634 done
2637 if [[ -z $codemgr_parent ]]; then
2638 codemgr_parent=$par_branch
2640 PWS=$codemgr_parent
2643 # If the parent is a webrev, we want to do some things against
2644 # the natural workspace parent (file list, comments, etc)
2646 if [[ -n $parent_webrev ]]; then
2647 real_parent=$par_branch
2648 else
2649 real_parent=$PWS
2652 if [[ -z $flist_done ]]; then
2653 flist_from_git "$codemgr_head" "$real_parent"
2654 flist_done=1
2658 # If we have a file list now, pull out any variables set
2659 # therein.
2661 if [[ -n $flist_done ]]; then
2662 env_from_flist
2666 # If we don't have a wx-format file list, build one we can pull change
2667 # comments from.
2669 if [[ -z $wxfile ]]; then
2670 print " Comments from: git...\c"
2671 git_wxfile "$codemgr_head" "$real_parent"
2672 print " Done."
2675 if [[ -z $GIT_PARENT ]]; then
2676 GIT_PARENT=$($GIT merge-base "$real_parent" "$codemgr_head")
2678 if [[ -z $GIT_PARENT ]]; then
2679 print -u2 "Error: Cannot discover parent revision"
2680 exit 1
2683 pnode=$(trim_digest $GIT_PARENT)
2685 if [[ -n $cflag ]]; then
2686 PRETTY_PWS="previous revision (at ${pnode})"
2687 elif [[ $real_parent == */* ]]; then
2688 origin=$(echo $real_parent | cut -d/ -f1)
2689 origin=$($GIT remote -v | \
2690 $AWK '$1 == "'$origin'" { print $2; exit }')
2691 PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2692 elif [[ -n $pflag && -z $parent_webrev ]]; then
2693 PRETTY_PWS="${CWS} (explicit revision ${pnode})"
2694 else
2695 PRETTY_PWS="${PWS} (at ${pnode})"
2698 cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 \
2699 ${codemgr_head} 2>/dev/null)
2701 if [[ -n $cflag || -n $hflag ]]; then
2702 PRETTY_CWS="${CWS} (explicit head at ${cnode})"
2703 else
2704 PRETTY_CWS="${CWS} (at ${cnode})"
2706 elif [[ $SCM_MODE == "subversion" ]]; then
2709 # We only will have a real parent workspace in the case one
2710 # was specified (be it an older webrev, or another checkout).
2712 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2714 if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2715 flist_from_subversion $CWS $OLDPWD
2717 else
2718 if [[ $SCM_MODE == "unknown" ]]; then
2719 print -u2 " Unknown type of SCM in use"
2720 else
2721 print -u2 " Unsupported SCM in use: $SCM_MODE"
2724 env_from_flist
2726 if [[ -z $CODEMGR_WS ]]; then
2727 print -u2 "SCM not detected/supported and " \
2728 "CODEMGR_WS not specified"
2729 exit 1
2732 if [[ -z $CODEMGR_PARENT ]]; then
2733 print -u2 "SCM not detected/supported and " \
2734 "CODEMGR_PARENT not specified"
2735 exit 1
2738 CWS=$CODEMGR_WS
2739 PWS=$CODEMGR_PARENT
2743 # If the user didn't specify a -i option, check to see if there is a
2744 # webrev-info file in the workspace directory.
2746 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2747 iflag=1
2748 INCLUDE_FILE="$CWS/webrev-info"
2751 if [[ -n $iflag ]]; then
2752 if [[ ! -r $INCLUDE_FILE ]]; then
2753 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2754 "not readable."
2755 exit 1
2756 else
2758 # $INCLUDE_FILE may be a relative path, and the script alters
2759 # PWD, so we just stash a copy in /tmp.
2761 cp $INCLUDE_FILE /tmp/$$.include
2765 # DO_EVERYTHING: break point
2766 if [[ -n $Nflag ]]; then
2767 break
2770 typeset -A itsinfo
2771 typeset -r its_sed_script=/tmp/$$.its_sed
2772 valid_prefixes=
2773 if [[ -z $nflag ]]; then
2774 DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg"
2775 if [[ -n $Iflag ]]; then
2776 REGFILE=$ITSREG
2777 elif [[ -r $HOME/.its.reg ]]; then
2778 REGFILE=$HOME/.its.reg
2779 else
2780 REGFILE=$DEFREGFILE
2782 if [[ ! -r $REGFILE ]]; then
2783 print "ERROR: Unable to read database registry file $REGFILE"
2784 exit 1
2785 elif [[ $REGFILE != $DEFREGFILE ]]; then
2786 print " its.reg from: $REGFILE"
2789 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do
2791 name=${LINE%%=*}
2792 value="${LINE#*=}"
2794 if [[ $name == PREFIX ]]; then
2795 p=${value}
2796 valid_prefixes="${p} ${valid_prefixes}"
2797 else
2798 itsinfo["${p}_${name}"]="${value}"
2800 done
2803 DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf"
2804 CONFFILES=$DEFCONFFILE
2805 if [[ -r $HOME/.its.conf ]]; then
2806 CONFFILES="${CONFFILES} $HOME/.its.conf"
2808 if [[ -n $Cflag ]]; then
2809 CONFFILES="${CONFFILES} ${ITSCONF}"
2811 its_domain=
2812 its_priority=
2813 for cf in ${CONFFILES}; do
2814 if [[ ! -r $cf ]]; then
2815 print "ERROR: Unable to read database configuration file $cf"
2816 exit 1
2817 elif [[ $cf != $DEFCONFFILE ]]; then
2818 print " its.conf: reading $cf"
2820 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do
2821 eval "${LINE}"
2822 done
2823 done
2826 # If an information tracking system is explicitly identified by prefix,
2827 # we want to disregard the specified priorities and resolve it accordingly.
2829 # To that end, we'll build a sed script to do each valid prefix in turn.
2831 for p in ${valid_prefixes}; do
2833 # When an informational URL was provided, translate it to a
2834 # hyperlink. When omitted, simply use the prefix text.
2836 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2837 itsinfo["${p}_INFO"]=${p}
2838 else
2839 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2843 # Assume that, for this invocation of webrev, all references
2844 # to this information tracking system should resolve through
2845 # the same URL.
2847 # If the caller specified -O, then always use EXTERNAL_URL.
2849 # Otherwise, look in the list of domains for a matching
2850 # INTERNAL_URL.
2852 [[ -z $Oflag ]] && for d in ${its_domain}; do
2853 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2854 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2855 break
2857 done
2858 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2859 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2863 # Turn the destination URL into a hyperlink
2865 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2867 # The character class below contains a literal tab
2868 print "/^${p}[: ]/ {
2869 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2870 s;^${p};${itsinfo[${p}_INFO]};
2871 }" >> ${its_sed_script}
2872 done
2875 # The previous loop took care of explicit specification. Now use
2876 # the configured priorities to attempt implicit translations.
2878 for p in ${its_priority}; do
2879 print "/^${itsinfo[${p}_REGEX]}[ ]/ {
2880 s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2881 }" >> ${its_sed_script}
2882 done
2886 # Search for DO_EVERYTHING above for matching "for" statement
2887 # and explanation of this terminator.
2889 done
2892 # Output directory.
2894 WDIR=${WDIR:-$CWS/webrev}
2897 # Name of the webrev, derived from the workspace name or output directory;
2898 # in the future this could potentially be an option.
2900 if [[ -n $oflag ]]; then
2901 WNAME=${WDIR##*/}
2902 else
2903 WNAME=${CWS##*/}
2906 # Make sure remote target is well formed for remote upload/delete.
2907 if [[ -n $Dflag || -n $Uflag ]]; then
2909 # If remote target is not specified, build it from scratch using
2910 # the default values.
2912 if [[ -z $tflag ]]; then
2913 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2914 else
2916 # Check upload target prefix first.
2918 if [[ "${remote_target}" != ${rsync_prefix}* &&
2919 "${remote_target}" != ${ssh_prefix}* ]]; then
2920 print "ERROR: invalid prefix of upload URI" \
2921 "($remote_target)"
2922 exit 1
2925 # If destination specification is not in the form of
2926 # host_spec:remote_dir then assume it is just remote hostname
2927 # and append a colon and destination directory formed from
2928 # local webrev directory name.
2930 typeset target_no_prefix=${remote_target##*://}
2931 if [[ ${target_no_prefix} == *:* ]]; then
2932 if [[ "${remote_target}" == *: ]]; then
2933 remote_target=${remote_target}${WNAME}
2935 else
2936 if [[ ${target_no_prefix} == */* ]]; then
2937 print "ERROR: badly formed upload URI" \
2938 "($remote_target)"
2939 exit 1
2940 else
2941 remote_target=${remote_target}:${WNAME}
2947 # Strip trailing slash. Each upload method will deal with directory
2948 # specification separately.
2950 remote_target=${remote_target%/}
2954 # Option -D by itself (option -U not present) implies no webrev generation.
2956 if [[ -z $Uflag && -n $Dflag ]]; then
2957 delete_webrev 1 1
2958 exit $?
2962 # Do not generate the webrev, just upload it or delete it.
2964 if [[ -n $nflag ]]; then
2965 if [[ -n $Dflag ]]; then
2966 delete_webrev 1 1
2967 (( $? == 0 )) || exit $?
2969 if [[ -n $Uflag ]]; then
2970 upload_webrev
2971 exit $?
2975 if [ "${WDIR%%/*}" ]; then
2976 WDIR=$PWD/$WDIR
2979 if [[ ! -d $WDIR ]]; then
2980 mkdir -p $WDIR
2981 (( $? != 0 )) && exit 1
2985 # Summarize what we're going to do.
2987 print " Workspace: ${PRETTY_CWS:-$CWS}"
2988 if [[ -n $parent_webrev ]]; then
2989 print "Compare against: webrev at $parent_webrev"
2990 else
2991 print "Compare against: ${PRETTY_PWS:-$PWS}"
2994 [[ -n $INCLUDE_FILE ]] && print " Including: $INCLUDE_FILE"
2995 print " Output to: $WDIR"
2998 # Save the file list in the webrev dir
3000 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
3002 rm -f $WDIR/$WNAME.patch
3003 rm -f $WDIR/$WNAME.ps
3004 rm -f $WDIR/$WNAME.pdf
3006 touch $WDIR/$WNAME.patch
3008 print " Output Files:"
3011 # Clean up the file list: Remove comments, blank lines and env variables.
3013 $SED -e "s/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean
3014 FLIST=/tmp/$$.flist.clean
3017 # For Mercurial, create a cache of manifest entries.
3019 if [[ $SCM_MODE == "mercurial" ]]; then
3021 # Transform the FLIST into a temporary sed script that matches
3022 # relevant entries in the Mercurial manifest as follows:
3023 # 1) The script will be used against the parent revision manifest,
3024 # so for FLIST lines that have two filenames (a renamed file)
3025 # keep only the old name.
3026 # 2) Escape all forward slashes the filename.
3027 # 3) Change the filename into another sed command that matches
3028 # that file in "hg manifest -v" output: start of line, three
3029 # octal digits for file permissions, space, a file type flag
3030 # character, space, the filename, end of line.
3031 # 4) Eliminate any duplicate entries. (This can occur if a
3032 # file has been used as the source of an hg cp and it's
3033 # also been modified in the same changeset.)
3035 SEDFILE=/tmp/$$.manifest.sed
3036 $SED '
3037 s#^[^ ]* ##
3038 s#/#\\\/#g
3039 s#^.*$#/^... . &$/p#
3040 ' < $FLIST | $SORT -u > $SEDFILE
3043 # Apply the generated script to the output of "hg manifest -v"
3044 # to get the relevant subset for this webrev.
3046 HG_PARENT_MANIFEST=/tmp/$$.manifest
3047 hg -R $CWS manifest -v -r $HG_PARENT |
3048 $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
3052 # First pass through the files: generate the per-file webrev HTML-files.
3054 cat $FLIST | while read LINE
3056 set - $LINE
3057 P=$1
3060 # Normally, each line in the file list is just a pathname of a
3061 # file that has been modified or created in the child. A file
3062 # that is renamed in the child workspace has two names on the
3063 # line: new name followed by the old name.
3065 oldname=""
3066 oldpath=""
3067 rename=
3068 if [[ $# -eq 2 ]]; then
3069 PP=$2 # old filename
3070 if [[ -f $PP ]]; then
3071 oldname=" (copied from $PP)"
3072 else
3073 oldname=" (renamed from $PP)"
3075 oldpath="$PP"
3076 rename=1
3077 PDIR=${PP%/*}
3078 if [[ $PDIR == $PP ]]; then
3079 PDIR="." # File at root of workspace
3082 PF=${PP##*/}
3084 DIR=${P%/*}
3085 if [[ $DIR == $P ]]; then
3086 DIR="." # File at root of workspace
3089 F=${P##*/}
3091 else
3092 DIR=${P%/*}
3093 if [[ "$DIR" == "$P" ]]; then
3094 DIR="." # File at root of workspace
3097 F=${P##*/}
3099 PP=$P
3100 PDIR=$DIR
3101 PF=$F
3104 COMM=`getcomments html $P $PP`
3106 print "\t$P$oldname\n\t\t\c"
3108 # Make the webrev mirror directory if necessary
3109 mkdir -p $WDIR/$DIR
3112 # We stash old and new files into parallel directories in $WDIR
3113 # and do our diffs there. This makes it possible to generate
3114 # clean looking diffs which don't have absolute paths present.
3117 build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3118 continue
3121 # Keep the old PWD around, so we can safely switch back after
3122 # diff generation, such that build_old_new runs in a
3123 # consistent environment.
3125 OWD=$PWD
3126 cd $WDIR/raw_files
3129 # The "git apply" command does not tolerate the spurious
3130 # "./" that we otherwise insert; be careful not to include
3131 # it in the paths that we pass to diff(1).
3133 if [[ $PDIR == "." ]]; then
3134 ofile=old/$PF
3135 else
3136 ofile=old/$PDIR/$PF
3138 if [[ $DIR == "." ]]; then
3139 nfile=new/$F
3140 else
3141 nfile=new/$DIR/$F
3144 mv_but_nodiff=
3145 cmp $ofile $nfile > /dev/null 2>&1
3146 if [[ $? == 0 && $rename == 1 ]]; then
3147 mv_but_nodiff=1
3151 # If we have old and new versions of the file then run the appropriate
3152 # diffs. This is complicated by a couple of factors:
3154 # - renames must be handled specially: we emit a 'remove'
3155 # diff and an 'add' diff
3156 # - new files and deleted files must be handled specially
3157 # - GNU patch doesn't interpret the output of illumos diff
3158 # properly when it comes to adds and deletes. We need to
3159 # do some "cleansing" transformations:
3160 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@
3161 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@
3163 cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3164 cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3166 rm -f $WDIR/$DIR/$F.patch
3167 if [[ -z $rename ]]; then
3168 if [ ! -f "$ofile" ]; then
3169 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3170 > $WDIR/$DIR/$F.patch
3171 elif [ ! -f "$nfile" ]; then
3172 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3173 > $WDIR/$DIR/$F.patch
3174 else
3175 diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3177 else
3178 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3179 > $WDIR/$DIR/$F.patch
3181 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3182 >> $WDIR/$DIR/$F.patch
3186 # Tack the patch we just made onto the accumulated patch for the
3187 # whole wad.
3189 cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3190 print " patch\c"
3192 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3193 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3194 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3195 > $WDIR/$DIR/$F.cdiff.html
3196 print " cdiffs\c"
3198 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3199 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3200 > $WDIR/$DIR/$F.udiff.html
3201 print " udiffs\c"
3203 if [[ -x $WDIFF ]]; then
3204 $WDIFF -c "$COMM" \
3205 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3206 $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3207 if [[ $? -eq 0 ]]; then
3208 print " wdiffs\c"
3209 else
3210 print " wdiffs[fail]\c"
3214 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3215 > $WDIR/$DIR/$F.sdiff.html
3216 print " sdiffs\c"
3217 print " frames\c"
3219 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3220 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3221 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3222 # renamed file: may also have differences
3223 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3224 elif [[ -f $nfile ]]; then
3225 # new file: count added lines
3226 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3227 elif [[ -f $ofile ]]; then
3228 # old file: count deleted lines
3229 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3233 # Check if it's man page, and create plain text, html and raw (ascii)
3234 # output for the new version, as well as diffs against old version.
3236 if [[ -f "$nfile" && "$nfile" = *.+([0-9])*([a-zA-Z]) && \
3237 -x $MANDOC && -x $COL ]]; then
3238 $MANDOC -Tascii $nfile | $COL -b > $nfile.man.txt
3239 source_to_html txt < $nfile.man.txt > $nfile.man.txt.html
3240 print " man-txt\c"
3241 print "$MANCSS" > $WDIR/raw_files/new/$DIR/man.css
3242 $MANDOC -Thtml -Ostyle=man.css $nfile > $nfile.man.html
3243 print " man-html\c"
3244 $MANDOC -Tascii $nfile > $nfile.man.raw
3245 print " man-raw\c"
3246 if [[ -f "$ofile" && -z $mv_but_nodiff ]]; then
3247 $MANDOC -Tascii $ofile | $COL -b > $ofile.man.txt
3248 ${CDIFFCMD:-diff -bt -C 5} $ofile.man.txt \
3249 $nfile.man.txt > $WDIR/$DIR/$F.man.cdiff
3250 diff_to_html $F $DIR/$F "C" "$COMM" < \
3251 $WDIR/$DIR/$F.man.cdiff > \
3252 $WDIR/$DIR/$F.man.cdiff.html
3253 print " man-cdiffs\c"
3254 ${UDIFFCMD:-diff -bt -U 5} $ofile.man.txt \
3255 $nfile.man.txt > $WDIR/$DIR/$F.man.udiff
3256 diff_to_html $F $DIR/$F "U" "$COMM" < \
3257 $WDIR/$DIR/$F.man.udiff > \
3258 $WDIR/$DIR/$F.man.udiff.html
3259 print " man-udiffs\c"
3260 if [[ -x $WDIFF ]]; then
3261 $WDIFF -c "$COMM" -t "$WNAME Wdiff $DIR/$F" \
3262 $ofile.man.txt $nfile.man.txt > \
3263 $WDIR/$DIR/$F.man.wdiff.html 2>/dev/null
3264 if [[ $? -eq 0 ]]; then
3265 print " man-wdiffs\c"
3266 else
3267 print " man-wdiffs[fail]\c"
3270 sdiff_to_html $ofile.man.txt $nfile.man.txt $F.man $DIR \
3271 "$COMM" > $WDIR/$DIR/$F.man.sdiff.html
3272 print " man-sdiffs\c"
3273 print " man-frames\c"
3275 rm -f $ofile.man.txt $nfile.man.txt
3276 rm -f $WDIR/$DIR/$F.man.cdiff $WDIR/$DIR/$F.man.udiff
3280 # Now we generate the postscript for this file. We generate diffs
3281 # only in the event that there is delta, or the file is new (it seems
3282 # tree-killing to print out the contents of deleted files).
3284 if [[ -f $nfile ]]; then
3285 ocr=$ofile
3286 [[ ! -f $ofile ]] && ocr=/dev/null
3288 if [[ -z $mv_but_nodiff ]]; then
3289 textcomm=`getcomments text $P $PP`
3290 if [[ -x $CODEREVIEW ]]; then
3291 $CODEREVIEW -y "$textcomm" \
3292 -e $ocr $nfile \
3293 > /tmp/$$.psfile 2>/dev/null &&
3294 cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3295 if [[ $? -eq 0 ]]; then
3296 print " ps\c"
3297 else
3298 print " ps[fail]\c"
3304 if [[ -f $ofile ]]; then
3305 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3306 print " old\c"
3309 if [[ -f $nfile ]]; then
3310 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3311 print " new\c"
3314 cd $OWD
3316 print
3317 done
3319 frame_nav_js > $WDIR/ancnav.js
3320 frame_navigation > $WDIR/ancnav.html
3322 if [[ ! -f $WDIR/$WNAME.ps ]]; then
3323 print " Generating PDF: Skipped: no output available"
3324 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3325 print " Generating PDF: \c"
3326 fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3327 print "Done."
3328 else
3329 print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3332 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3333 # delete it - prevent accidental publishing of closed source
3335 if [[ -n "$Oflag" ]]; then
3336 $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3339 # Now build the index.html file that contains
3340 # links to the source files and their diffs.
3342 cd $CWS
3344 # Save total changed lines for Code Inspection.
3345 print "$TOTL" > $WDIR/TotalChangedLines
3347 print " index.html: \c"
3348 INDEXFILE=$WDIR/index.html
3349 exec 3<&1 # duplicate stdout to FD3.
3350 exec 1<&- # Close stdout.
3351 exec > $INDEXFILE # Open stdout to index file.
3353 print "$HTML<head>$STDHEAD"
3354 print "<title>$WNAME</title>"
3355 print "</head>"
3356 print "<body id=\"SUNWwebrev\">"
3357 print "<div class=\"summary\">"
3358 print "<h2>Code Review for $WNAME</h2>"
3360 print "<table>"
3363 # Get the preparer's name:
3365 # If the SCM detected is Mercurial, and the configuration property
3366 # ui.username is available, use that, but be careful to properly escape
3367 # angle brackets (HTML syntax characters) in the email address.
3369 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3370 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3372 preparer=
3373 if [[ "$SCM_MODE" == mercurial ]]; then
3374 preparer=`hg showconfig ui.username 2>/dev/null`
3375 if [[ -n "$preparer" ]]; then
3376 preparer="$(echo "$preparer" | html_quote)"
3379 if [[ -z "$preparer" ]]; then
3380 preparer=$(
3381 $PERL -e '
3382 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3383 if ($login) {
3384 $gcos =~ s/\&/ucfirst($login)/e;
3385 printf "%s (%s)\n", $gcos, $login;
3386 } else {
3387 printf "(unknown)\n";
3392 PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3393 print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3394 print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3395 print "</td></tr>"
3396 print "<tr><th>Compare against:</th><td>"
3397 if [[ -n $parent_webrev ]]; then
3398 print "webrev at $parent_webrev"
3399 else
3400 print "${PRETTY_PWS:-$PWS}"
3402 print "</td></tr>"
3403 print "<tr><th>Summary of changes:</th><td>"
3404 printCI $TOTL $TINS $TDEL $TMOD $TUNC
3405 print "</td></tr>"
3407 if [[ -f $WDIR/$WNAME.patch ]]; then
3408 wpatch_url="$(print $WNAME.patch | url_encode)"
3409 print "<tr><th>Patch of changes:</th><td>"
3410 print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3412 if [[ -f $WDIR/$WNAME.pdf ]]; then
3413 wpdf_url="$(print $WNAME.pdf | url_encode)"
3414 print "<tr><th>Printable review:</th><td>"
3415 print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3418 if [[ -n "$iflag" ]]; then
3419 print "<tr><th>Author comments:</th><td><div>"
3420 cat /tmp/$$.include
3421 print "</div></td></tr>"
3423 print "</table>"
3424 print "</div>"
3427 # Second pass through the files: generate the rest of the index file
3429 cat $FLIST | while read LINE
3431 set - $LINE
3432 P=$1
3434 if [[ $# == 2 ]]; then
3435 PP=$2
3436 oldname="$PP"
3437 else
3438 PP=$P
3439 oldname=""
3442 mv_but_nodiff=
3443 cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3444 if [[ $? == 0 && -n "$oldname" ]]; then
3445 mv_but_nodiff=1
3448 DIR=${P%/*}
3449 if [[ $DIR == $P ]]; then
3450 DIR="." # File at root of workspace
3453 # Avoid processing the same file twice.
3454 # It's possible for renamed files to
3455 # appear twice in the file list
3457 F=$WDIR/$P
3459 print "<p>"
3461 # If there's a diffs file, make diffs links
3463 if [[ -f $F.cdiff.html ]]; then
3464 cdiff_url="$(print $P.cdiff.html | url_encode)"
3465 udiff_url="$(print $P.udiff.html | url_encode)"
3466 sdiff_url="$(print $P.sdiff.html | url_encode)"
3467 frames_url="$(print $P.frames.html | url_encode)"
3468 print "<a href=\"$cdiff_url\">Cdiffs</a>"
3469 print "<a href=\"$udiff_url\">Udiffs</a>"
3470 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3471 wdiff_url="$(print $P.wdiff.html | url_encode)"
3472 print "<a href=\"$wdiff_url\">Wdiffs</a>"
3474 print "<a href=\"$sdiff_url\">Sdiffs</a>"
3475 print "<a href=\"$frames_url\">Frames</a>"
3476 else
3477 print " ------ ------"
3478 if [[ -x $WDIFF ]]; then
3479 print " ------"
3481 print " ------ ------"
3484 # If there's an old file, make the link
3486 if [[ -f $F-.html ]]; then
3487 oldfile_url="$(print $P-.html | url_encode)"
3488 print "<a href=\"$oldfile_url\">Old</a>"
3489 else
3490 print " ---"
3493 # If there's an new file, make the link
3495 if [[ -f $F.html ]]; then
3496 newfile_url="$(print $P.html | url_encode)"
3497 print "<a href=\"$newfile_url\">New</a>"
3498 else
3499 print " ---"
3502 if [[ -f $F.patch ]]; then
3503 patch_url="$(print $P.patch | url_encode)"
3504 print "<a href=\"$patch_url\">Patch</a>"
3505 else
3506 print " -----"
3509 if [[ -f $WDIR/raw_files/new/$P ]]; then
3510 rawfiles_url="$(print raw_files/new/$P | url_encode)"
3511 print "<a href=\"$rawfiles_url\">Raw</a>"
3512 else
3513 print " ---"
3516 print "<b>$P</b>"
3518 # For renamed files, clearly state whether or not they are modified
3519 if [[ -f "$oldname" ]]; then
3520 if [[ -n "$mv_but_nodiff" ]]; then
3521 print "<i>(copied from $oldname)</i>"
3522 else
3523 print "<i>(copied and modified from $oldname)</i>"
3525 elif [[ -n "$oldname" ]]; then
3526 if [[ -n "$mv_but_nodiff" ]]; then
3527 print "<i>(renamed from $oldname)</i>"
3528 else
3529 print "<i>(renamed and modified from $oldname)</i>"
3533 # If there's an old file, but no new file, the file was deleted
3534 if [[ -f $F-.html && ! -f $F.html ]]; then
3535 print " <i>(deleted)</i>"
3538 # Check for usr/closed and deleted_files/usr/closed
3539 if [ ! -z "$Oflag" ]; then
3540 if [[ $P == usr/closed/* || \
3541 $P == deleted_files/usr/closed/* ]]; then
3542 print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3543 "this review</i>"
3547 manpage=
3548 if [[ -f $F.man.cdiff.html || \
3549 -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3550 manpage=1
3551 print "<br/>man:"
3554 if [[ -f $F.man.cdiff.html ]]; then
3555 mancdiff_url="$(print $P.man.cdiff.html | url_encode)"
3556 manudiff_url="$(print $P.man.udiff.html | url_encode)"
3557 mansdiff_url="$(print $P.man.sdiff.html | url_encode)"
3558 manframes_url="$(print $P.man.frames.html | url_encode)"
3559 print "<a href=\"$mancdiff_url\">Cdiffs</a>"
3560 print "<a href=\"$manudiff_url\">Udiffs</a>"
3561 if [[ -f $F.man.wdiff.html && -x $WDIFF ]]; then
3562 manwdiff_url="$(print $P.man.wdiff.html | url_encode)"
3563 print "<a href=\"$manwdiff_url\">Wdiffs</a>"
3565 print "<a href=\"$mansdiff_url\">Sdiffs</a>"
3566 print "<a href=\"$manframes_url\">Frames</a>"
3567 elif [[ -n $manpage ]]; then
3568 print " ------ ------"
3569 if [[ -x $WDIFF ]]; then
3570 print " ------"
3572 print " ------ ------"
3575 if [[ -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3576 mantxt_url="$(print raw_files/new/$P.man.txt.html | url_encode)"
3577 print "<a href=\"$mantxt_url\">TXT</a>"
3578 manhtml_url="$(print raw_files/new/$P.man.html | url_encode)"
3579 print "<a href=\"$manhtml_url\">HTML</a>"
3580 manraw_url="$(print raw_files/new/$P.man.raw | url_encode)"
3581 print "<a href=\"$manraw_url\">Raw</a>"
3582 elif [[ -n $manpage ]]; then
3583 print " --- ---- ---"
3586 print "</p>"
3588 # Insert delta comments
3589 print "<blockquote><pre>"
3590 getcomments html $P $PP
3591 print "</pre>"
3593 # Add additional comments comment
3594 print "<!-- Add comments to explain changes in $P here -->"
3596 # Add count of changes.
3597 if [[ -f $F.count ]]; then
3598 cat $F.count
3599 rm $F.count
3602 if [[ $SCM_MODE == "mercurial" ||
3603 $SCM_MODE == "unknown" ]]; then
3604 # Include warnings for important file mode situations:
3605 # 1) New executable files
3606 # 2) Permission changes of any kind
3607 # 3) Existing executable files
3608 old_mode=
3609 if [[ -f $WDIR/raw_files/old/$PP ]]; then
3610 old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3613 new_mode=
3614 if [[ -f $WDIR/raw_files/new/$P ]]; then
3615 new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3618 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3619 print "<span class=\"chmod\">"
3620 print "<p>new executable file: mode $new_mode</p>"
3621 print "</span>"
3622 elif [[ -n "$old_mode" && -n "$new_mode" &&
3623 "$old_mode" != "$new_mode" ]]; then
3624 print "<span class=\"chmod\">"
3625 print "<p>mode change: $old_mode to $new_mode</p>"
3626 print "</span>"
3627 elif [[ "$new_mode" = *[1357]* ]]; then
3628 print "<span class=\"chmod\">"
3629 print "<p>executable file: mode $new_mode</p>"
3630 print "</span>"
3634 print "</blockquote>"
3635 done
3637 print
3638 print
3639 print "<hr></hr>"
3640 print "<p style=\"font-size: small\">"
3641 print "This code review page was prepared using <b>$0</b>."
3642 print "Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3643 print "illumos</a> project. The latest version may be obtained"
3644 print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3645 print "</body>"
3646 print "</html>"
3648 exec 1<&- # Close FD 1.
3649 exec 1<&3 # dup FD 3 to restore stdout.
3650 exec 3<&- # close FD 3.
3652 print "Done."
3655 # If remote deletion was specified and fails do not continue.
3657 if [[ -n $Dflag ]]; then
3658 delete_webrev 1 1
3659 (( $? == 0 )) || exit $?
3662 if [[ -n $Uflag ]]; then
3663 upload_webrev
3664 exit $?