43 development tools should be adjusted to understand the brave new world
[illumos-gate.git] / usr / src / tools / scripts / webrev.sh
blob6ebb650de9d8c5af75268e006a8be00056c5e3b3
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.
27 # Copyright 2008, 2010, Richard Lowe
30 # This script takes a file list and a workspace and builds a set of html files
31 # suitable for doing a code review of source changes via a web page.
32 # Documentation is available via the manual page, webrev.1, or just
33 # type 'webrev -h'.
35 # Acknowledgements to contributors to webrev are listed in the webrev(1)
36 # man page.
39 REMOVED_COLOR=brown
40 CHANGED_COLOR=blue
41 NEW_COLOR=blue
43 HTML='<?xml version="1.0"?>
44 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
45 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
46 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
48 FRAMEHTML='<?xml version="1.0"?>
49 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
50 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
51 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
53 STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta>
54 <meta http-equiv="Pragma" content="no-cache"></meta>
55 <meta http-equiv="Expires" content="-1"></meta>
56 <!--
57 Note to customizers: the body of the webrev is IDed as SUNWwebrev
58 to allow easy overriding by users of webrev via the userContent.css
59 mechanism available in some browsers.
61 For example, to have all "removed" information be red instead of
62 brown, set a rule in your userContent.css file like:
64 body#SUNWwebrev span.removed { color: red ! important; }
65 -->
66 <style type="text/css" media="screen">
67 body {
68 background-color: #eeeeee;
70 hr {
71 border: none 0;
72 border-top: 1px solid #aaa;
73 height: 1px;
75 div.summary {
76 font-size: .8em;
77 border-bottom: 1px solid #aaa;
78 padding-left: 1em;
79 padding-right: 1em;
81 div.summary h2 {
82 margin-bottom: 0.3em;
84 div.summary table th {
85 text-align: right;
86 vertical-align: top;
87 white-space: nowrap;
89 span.lineschanged {
90 font-size: 0.7em;
92 span.oldmarker {
93 color: red;
94 font-size: large;
95 font-weight: bold;
97 span.newmarker {
98 color: green;
99 font-size: large;
100 font-weight: bold;
102 span.removed {
103 color: brown;
105 span.changed {
106 color: blue;
108 span.new {
109 color: blue;
110 font-weight: bold;
112 span.chmod {
113 font-size: 0.7em;
114 color: #db7800;
116 a.print { font-size: x-small; }
117 a:hover { background-color: #ffcc99; }
118 </style>
120 <style type="text/css" media="print">
121 pre { font-size: 0.8em; font-family: courier, monospace; }
122 span.removed { color: #444; font-style: italic }
123 span.changed { font-weight: bold; }
124 span.new { font-weight: bold; }
125 span.newmarker { font-size: 1.2em; font-weight: bold; }
126 span.oldmarker { font-size: 1.2em; font-weight: bold; }
127 a.print {display: none}
128 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
129 </style>
133 # UDiffs need a slightly different CSS rule for 'new' items (we don't
134 # want them to be bolded as we do in cdiffs or sdiffs).
136 UDIFFCSS='
137 <style type="text/css" media="screen">
138 span.new {
139 color: blue;
140 font-weight: normal;
142 </style>
146 # Display remote target with prefix and trailing slash.
148 function print_upload_header
150 typeset -r prefix=$1
151 typeset display_target
153 if [[ -z $tflag ]]; then
154 display_target=${prefix}${remote_target}
155 else
156 display_target=${remote_target}
159 if [[ ${display_target} != */ ]]; then
160 display_target=${display_target}/
163 print " Upload to: ${display_target}\n" \
164 " Uploading: \c"
168 # Upload the webrev via rsync. Return 0 on success, 1 on error.
170 function rsync_upload
172 if (( $# != 2 )); then
173 print "\nERROR: rsync_upload: wrong usage ($#)"
174 exit 1
177 typeset -r dst=$1
178 integer -r print_err_msg=$2
180 print_upload_header ${rsync_prefix}
181 print "rsync ... \c"
182 typeset -r err_msg=$( $MKTEMP /tmp/rsync_err.XXXXXX )
183 if [[ -z $err_msg ]]; then
184 print "\nERROR: rsync_upload: cannot create temporary file"
185 return 1
188 # The source directory must end with a slash in order to copy just
189 # directory contents, not the whole directory.
191 typeset src_dir=$WDIR
192 if [[ ${src_dir} != */ ]]; then
193 src_dir=${src_dir}/
195 $RSYNC -r -q ${src_dir} $dst 2>$err_msg
196 if (( $? != 0 )); then
197 if (( ${print_err_msg} > 0 )); then
198 print "Failed.\nERROR: rsync failed"
199 print "src dir: '${src_dir}'\ndst dir: '$dst'"
200 print "error messages:"
201 $SED 's/^/> /' $err_msg
202 rm -f $err_msg
204 return 1
207 rm -f $err_msg
208 print "Done."
209 return 0
213 # Create directories on remote host using SFTP. Return 0 on success,
214 # 1 on failure.
216 function remote_mkdirs
218 typeset -r dir_spec=$1
219 typeset -r host_spec=$2
222 # If the supplied path is absolute we assume all directories are
223 # created, otherwise try to create all directories in the path
224 # except the last one which will be created by scp.
226 if [[ "${dir_spec}" == */* && "${dir_spec}" != /* ]]; then
227 print "mkdirs \c"
229 # Remove the last directory from directory specification.
231 typeset -r dirs_mk=${dir_spec%/*}
232 typeset -r batch_file_mkdir=$( $MKTEMP \
233 /tmp/webrev_mkdir.XXXXXX )
234 if [[ -z $batch_file_mkdir ]]; then
235 print "\nERROR: remote_mkdirs:" \
236 "cannot create temporary file for batch file"
237 return 1
239 OLDIFS=$IFS
240 IFS=/
241 typeset dir
242 for dir in ${dirs_mk}; do
244 # Use the '-' prefix to ignore mkdir errors in order
245 # to avoid an error in case the directory already
246 # exists. We check the directory with chdir to be sure
247 # there is one.
249 print -- "-mkdir ${dir}" >> ${batch_file_mkdir}
250 print "chdir ${dir}" >> ${batch_file_mkdir}
251 done
252 IFS=$OLDIFS
253 typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
254 if [[ -z ${sftp_err_msg} ]]; then
255 print "\nERROR: remote_mkdirs:" \
256 "cannot create temporary file for error messages"
257 return 1
259 $SFTP -b ${batch_file_mkdir} ${host_spec} 2>${sftp_err_msg} 1>&2
260 if (( $? != 0 )); then
261 print "\nERROR: failed to create remote directories"
262 print "error messages:"
263 $SED 's/^/> /' ${sftp_err_msg}
264 rm -f ${sftp_err_msg} ${batch_file_mkdir}
265 return 1
267 rm -f ${sftp_err_msg} ${batch_file_mkdir}
270 return 0
274 # Upload the webrev via SSH. Return 0 on success, 1 on error.
276 function ssh_upload
278 if (( $# != 1 )); then
279 print "\nERROR: ssh_upload: wrong number of arguments"
280 exit 1
283 typeset dst=$1
284 typeset -r host_spec=${dst%%:*}
285 typeset -r dir_spec=${dst#*:}
288 # Display the upload information before calling delete_webrev
289 # because it will also print its progress.
291 print_upload_header ${ssh_prefix}
294 # If the deletion was explicitly requested there is no need
295 # to perform it again.
297 if [[ -z $Dflag ]]; then
299 # We do not care about return value because this might be
300 # the first time this directory is uploaded.
302 delete_webrev 0
306 # Create remote directories. Any error reporting will be done
307 # in remote_mkdirs function.
309 remote_mkdirs ${dir_spec} ${host_spec}
310 if (( $? != 0 )); then
311 return 1
314 print "upload ... \c"
315 typeset -r scp_err_msg=$( $MKTEMP /tmp/scp_err.XXXXXX )
316 if [[ -z ${scp_err_msg} ]]; then
317 print "\nERROR: ssh_upload:" \
318 "cannot create temporary file for error messages"
319 return 1
321 $SCP -q -C -B -o PreferredAuthentications=publickey -r \
322 $WDIR $dst 2>${scp_err_msg}
323 if (( $? != 0 )); then
324 print "Failed.\nERROR: scp failed"
325 print "src dir: '$WDIR'\ndst dir: '$dst'"
326 print "error messages:"
327 $SED 's/^/> /' ${scp_err_msg}
328 rm -f ${scp_err_msg}
329 return 1
332 rm -f ${scp_err_msg}
333 print "Done."
334 return 0
338 # Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp
339 # on failure. If first argument is 1 then perform the check of sftp return
340 # value otherwise ignore it. If second argument is present it means this run
341 # only performs deletion.
343 function delete_webrev
345 if (( $# < 1 )); then
346 print "delete_webrev: wrong number of arguments"
347 exit 1
350 integer -r check=$1
351 integer delete_only=0
352 if (( $# == 2 )); then
353 delete_only=1
357 # Strip the transport specification part of remote target first.
359 typeset -r stripped_target=${remote_target##*://}
360 typeset -r host_spec=${stripped_target%%:*}
361 typeset -r dir_spec=${stripped_target#*:}
362 typeset dir_rm
365 # Do not accept an absolute path.
367 if [[ ${dir_spec} == /* ]]; then
368 return 1
372 # Strip the ending slash.
374 if [[ ${dir_spec} == */ ]]; then
375 dir_rm=${dir_spec%%/}
376 else
377 dir_rm=${dir_spec}
380 if (( ${delete_only} > 0 )); then
381 print " Removing: \c"
382 else
383 print "rmdir \c"
385 if [[ -z "$dir_rm" ]]; then
386 print "\nERROR: empty directory for removal"
387 return 1
391 # Prepare batch file.
393 typeset -r batch_file_rm=$( $MKTEMP /tmp/webrev_remove.XXXXXX )
394 if [[ -z $batch_file_rm ]]; then
395 print "\nERROR: delete_webrev: cannot create temporary file"
396 return 1
398 print "rename $dir_rm $TRASH_DIR/removed.$$" > $batch_file_rm
401 # Perform remote deletion and remove the batch file.
403 typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
404 if [[ -z ${sftp_err_msg} ]]; then
405 print "\nERROR: delete_webrev:" \
406 "cannot create temporary file for error messages"
407 return 1
409 $SFTP -b $batch_file_rm $host_spec 2>${sftp_err_msg} 1>&2
410 integer -r ret=$?
411 rm -f $batch_file_rm
412 if (( $ret != 0 && $check > 0 )); then
413 print "Failed.\nERROR: failed to remove remote directories"
414 print "error messages:"
415 $SED 's/^/> /' ${sftp_err_msg}
416 rm -f ${sftp_err_msg}
417 return $ret
419 rm -f ${sftp_err_msg}
420 if (( ${delete_only} > 0 )); then
421 print "Done."
424 return 0
428 # Upload webrev to remote site
430 function upload_webrev
432 integer ret
434 if [[ ! -d "$WDIR" ]]; then
435 print "\nERROR: webrev directory '$WDIR' does not exist"
436 return 1
440 # Perform a late check to make sure we do not upload closed source
441 # to remote target when -n is used. If the user used custom remote
442 # target he probably knows what he is doing.
444 if [[ -n $nflag && -z $tflag ]]; then
445 $FIND $WDIR -type d -name closed \
446 | $GREP closed >/dev/null
447 if (( $? == 0 )); then
448 print "\nERROR: directory '$WDIR' contains" \
449 "\"closed\" directory"
450 return 1
456 # We have the URI for remote destination now so let's start the upload.
458 if [[ -n $tflag ]]; then
459 if [[ "${remote_target}" == ${rsync_prefix}?* ]]; then
460 rsync_upload ${remote_target##$rsync_prefix} 1
461 ret=$?
462 return $ret
463 elif [[ "${remote_target}" == ${ssh_prefix}?* ]]; then
464 ssh_upload ${remote_target##$ssh_prefix}
465 ret=$?
466 return $ret
468 else
470 # Try rsync first and fallback to SSH in case it fails.
472 rsync_upload ${remote_target} 0
473 ret=$?
474 if (( $ret != 0 )); then
475 print "Failed. (falling back to SSH)"
476 ssh_upload ${remote_target}
477 ret=$?
479 return $ret
484 # input_cmd | url_encode | output_cmd
486 # URL-encode (percent-encode) reserved characters as defined in RFC 3986.
488 # Reserved characters are: :/?#[]@!$&'()*+,;=
490 # While not a reserved character itself, percent '%' is reserved by definition
491 # so encode it first to avoid recursive transformation, and skip '/' which is
492 # a path delimiter.
494 # The quotation character is deliberately not escaped in order to make
495 # the substitution work with GNU sed.
497 function url_encode
499 $SED -e "s|%|%25|g" -e "s|:|%3A|g" -e "s|\&|%26|g" \
500 -e "s|?|%3F|g" -e "s|#|%23|g" -e "s|\[|%5B|g" \
501 -e "s|*|%2A|g" -e "s|@|%40|g" -e "s|\!|%21|g" \
502 -e "s|=|%3D|g" -e "s|;|%3B|g" -e "s|\]|%5D|g" \
503 -e "s|(|%28|g" -e "s|)|%29|g" -e "s|'|%27|g" \
504 -e "s|+|%2B|g" -e "s|\,|%2C|g" -e "s|\\\$|%24|g"
508 # input_cmd | html_quote | output_cmd
509 # or
510 # html_quote filename | output_cmd
512 # Make a piece of source code safe for display in an HTML <pre> block.
514 html_quote()
516 $SED -e "s/&/\&amp;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g" "$@" | expand
520 # input_cmd | its2url | output_cmd
522 # Scan for information tracking system references and insert <a> links to the
523 # relevant databases.
525 its2url()
527 $SED -f ${its_sed_script}
531 # strip_unchanged <infile> | output_cmd
533 # Removes chunks of sdiff documents that have not changed. This makes it
534 # easier for a code reviewer to find the bits that have changed.
536 # Deleted lines of text are replaced by a horizontal rule. Some
537 # identical lines are retained before and after the changed lines to
538 # provide some context. The number of these lines is controlled by the
539 # variable C in the $AWK script below.
541 # The script detects changed lines as any line that has a "<span class="
542 # string embedded (unchanged lines have no particular class and are not
543 # part of a <span>). Blank lines (without a sequence number) are also
544 # detected since they flag lines that have been inserted or deleted.
546 strip_unchanged()
548 $AWK '
549 BEGIN { C = c = 20 }
550 NF == 0 || /<span class="/ {
551 if (c > C) {
552 c -= C
553 inx = 0
554 if (c > C) {
555 print "\n</pre><hr></hr><pre>"
556 inx = c % C
557 c = C
560 for (i = 0; i < c; i++)
561 print ln[(inx + i) % C]
563 c = 0;
564 print
565 next
567 { if (c >= C) {
568 ln[c % C] = $0
569 c++;
570 next;
572 c++;
573 print
575 END { if (c > (C * 2)) print "\n</pre><hr></hr>" }
577 ' $1
581 # sdiff_to_html
583 # This function takes two files as arguments, obtains their diff, and
584 # processes the diff output to present the files as an HTML document with
585 # the files displayed side-by-side, differences shown in color. It also
586 # takes a delta comment, rendered as an HTML snippet, as the third
587 # argument. The function takes two files as arguments, then the name of
588 # file, the path, and the comment. The HTML will be delivered on stdout,
589 # e.g.
591 # $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
592 # new/usr/src/tools/scripts/webrev.sh \
593 # webrev.sh usr/src/tools/scripts \
594 # '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
595 # 1234567</a> my bugid' > <file>.html
597 # framed_sdiff() is then called which creates $2.frames.html
598 # in the webrev tree.
600 # FYI: This function is rather unusual in its use of awk. The initial
601 # diff run produces conventional diff output showing changed lines mixed
602 # with editing codes. The changed lines are ignored - we're interested in
603 # the editing codes, e.g.
605 # 8c8
606 # 57a61
607 # 63c66,76
608 # 68,93d80
609 # 106d90
610 # 108,110d91
612 # These editing codes are parsed by the awk script and used to generate
613 # another awk script that generates HTML, e.g the above lines would turn
614 # into something like this:
616 # BEGIN { printf "<pre>\n" }
617 # function sp(n) {for (i=0;i<n;i++)printf "\n"}
618 # function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
619 # NR==8 {wl("#7A7ADD");next}
620 # NR==54 {wl("#7A7ADD");sp(3);next}
621 # NR==56 {wl("#7A7ADD");next}
622 # NR==57 {wl("black");printf "\n"; next}
623 # : :
625 # This script is then run on the original source file to generate the
626 # HTML that corresponds to the source file.
628 # The two HTML files are then combined into a single piece of HTML that
629 # uses an HTML table construct to present the files side by side. You'll
630 # notice that the changes are color-coded:
632 # black - unchanged lines
633 # blue - changed lines
634 # bold blue - new lines
635 # brown - deleted lines
637 # Blank lines are inserted in each file to keep unchanged lines in sync
638 # (side-by-side). This format is familiar to users of sdiff(1) or
639 # Teamware's filemerge tool.
641 sdiff_to_html()
643 diff -b $1 $2 > /tmp/$$.diffs
645 TNAME=$3
646 TPATH=$4
647 COMMENT=$5
650 # Now we have the diffs, generate the HTML for the old file.
652 $AWK '
653 BEGIN {
654 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
655 printf "function removed() "
656 printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
657 printf "function changed() "
658 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
659 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
661 /^</ {next}
662 /^>/ {next}
663 /^---/ {next}
666 split($1, a, /[cad]/) ;
667 if (index($1, "a")) {
668 if (a[1] == 0) {
669 n = split(a[2], r, /,/);
670 if (n == 1)
671 printf "BEGIN\t\t{sp(1)}\n"
672 else
673 printf "BEGIN\t\t{sp(%d)}\n",\
674 (r[2] - r[1]) + 1
675 next
678 printf "NR==%s\t\t{", a[1]
679 n = split(a[2], r, /,/);
680 s = r[1];
681 if (n == 1)
682 printf "bl();printf \"\\n\"; next}\n"
683 else {
684 n = r[2] - r[1]
685 printf "bl();sp(%d);next}\n",\
686 (r[2] - r[1]) + 1
688 next
690 if (index($1, "d")) {
691 n = split(a[1], r, /,/);
692 n1 = r[1]
693 n2 = r[2]
694 if (n == 1)
695 printf "NR==%s\t\t{removed(); next}\n" , n1
696 else
697 printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
698 next
700 if (index($1, "c")) {
701 n = split(a[1], r, /,/);
702 n1 = r[1]
703 n2 = r[2]
704 final = n2
705 d1 = 0
706 if (n == 1)
707 printf "NR==%s\t\t{changed();" , n1
708 else {
709 d1 = n2 - n1
710 printf "NR==%s,NR==%s\t{changed();" , n1, n2
712 m = split(a[2], r, /,/);
713 n1 = r[1]
714 n2 = r[2]
715 if (m > 1) {
716 d2 = n2 - n1
717 if (d2 > d1) {
718 if (n > 1) printf "if (NR==%d)", final
719 printf "sp(%d);", d2 - d1
722 printf "next}\n" ;
724 next
728 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
729 ' /tmp/$$.diffs > /tmp/$$.file1
732 # Now generate the HTML for the new file
734 $AWK '
735 BEGIN {
736 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
737 printf "function new() "
738 printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
739 printf "function changed() "
740 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
741 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
744 /^</ {next}
745 /^>/ {next}
746 /^---/ {next}
749 split($1, a, /[cad]/) ;
750 if (index($1, "d")) {
751 if (a[2] == 0) {
752 n = split(a[1], r, /,/);
753 if (n == 1)
754 printf "BEGIN\t\t{sp(1)}\n"
755 else
756 printf "BEGIN\t\t{sp(%d)}\n",\
757 (r[2] - r[1]) + 1
758 next
761 printf "NR==%s\t\t{", a[2]
762 n = split(a[1], r, /,/);
763 s = r[1];
764 if (n == 1)
765 printf "bl();printf \"\\n\"; next}\n"
766 else {
767 n = r[2] - r[1]
768 printf "bl();sp(%d);next}\n",\
769 (r[2] - r[1]) + 1
771 next
773 if (index($1, "a")) {
774 n = split(a[2], r, /,/);
775 n1 = r[1]
776 n2 = r[2]
777 if (n == 1)
778 printf "NR==%s\t\t{new() ; next}\n" , n1
779 else
780 printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
781 next
783 if (index($1, "c")) {
784 n = split(a[2], r, /,/);
785 n1 = r[1]
786 n2 = r[2]
787 final = n2
788 d2 = 0;
789 if (n == 1) {
790 final = n1
791 printf "NR==%s\t\t{changed();" , n1
792 } else {
793 d2 = n2 - n1
794 printf "NR==%s,NR==%s\t{changed();" , n1, n2
796 m = split(a[1], r, /,/);
797 n1 = r[1]
798 n2 = r[2]
799 if (m > 1) {
800 d1 = n2 - n1
801 if (d1 > d2) {
802 if (n > 1) printf "if (NR==%d)", final
803 printf "sp(%d);", d1 - d2
806 printf "next}\n" ;
807 next
810 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
811 ' /tmp/$$.diffs > /tmp/$$.file2
814 # Post-process the HTML files by running them back through $AWK
816 html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html
818 html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html
821 # Now combine into a valid HTML file and side-by-side into a table
823 print "$HTML<head>$STDHEAD"
824 print "<title>$WNAME Sdiff $TPATH/$TNAME</title>"
825 print "</head><body id=\"SUNWwebrev\">"
826 print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
827 print "<pre>$COMMENT</pre>\n"
828 print "<table><tr valign=\"top\">"
829 print "<td><pre>"
831 strip_unchanged /tmp/$$.file1.html
833 print "</pre></td><td><pre>"
835 strip_unchanged /tmp/$$.file2.html
837 print "</pre></td>"
838 print "</tr></table>"
839 print "</body></html>"
841 framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
842 "$COMMENT"
847 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
849 # Expects lefthand and righthand side html files created by sdiff_to_html.
850 # We use insert_anchors() to augment those with HTML navigation anchors,
851 # and then emit the main frame. Content is placed into:
853 # $WDIR/DIR/$TNAME.lhs.html
854 # $WDIR/DIR/$TNAME.rhs.html
855 # $WDIR/DIR/$TNAME.frames.html
857 # NOTE: We rely on standard usage of $WDIR and $DIR.
859 function framed_sdiff
861 typeset TNAME=$1
862 typeset TPATH=$2
863 typeset lhsfile=$3
864 typeset rhsfile=$4
865 typeset comments=$5
866 typeset RTOP
868 # Enable html files to access WDIR via a relative path.
869 RTOP=$(relative_dir $TPATH $WDIR)
871 # Make the rhs/lhs files and output the frameset file.
872 print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
874 cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
875 <script type="text/javascript" src="${RTOP}ancnav.js"></script>
876 </head>
877 <body id="SUNWwebrev" onkeypress="keypress(event);">
878 <a name="0"></a>
879 <pre>$comments</pre><hr></hr>
882 cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
884 insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
885 insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
887 close='</body></html>'
889 print $close >> $WDIR/$DIR/$TNAME.lhs.html
890 print $close >> $WDIR/$DIR/$TNAME.rhs.html
892 print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
893 print "<title>$WNAME Framed-Sdiff " \
894 "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
895 cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
896 <frameset rows="*,60">
897 <frameset cols="50%,50%">
898 <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
899 <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
900 </frameset>
901 <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0"
902 marginheight="0" name="nav"></frame>
903 <noframes>
904 <body id="SUNWwebrev">
905 Alas 'frames' webrev requires that your browser supports frames
906 and has the feature enabled.
907 </body>
908 </noframes>
909 </frameset>
910 </html>
916 # fix_postscript
918 # Merge codereview output files to a single conforming postscript file, by:
919 # - removing all extraneous headers/trailers
920 # - making the page numbers right
921 # - removing pages devoid of contents which confuse some
922 # postscript readers.
924 # From Casper.
926 function fix_postscript
928 infile=$1
930 cat > /tmp/$$.crmerge.pl << \EOF
932 print scalar(<>); # %!PS-Adobe---
933 print "%%Orientation: Landscape\n";
935 $pno = 0;
936 $doprint = 1;
938 $page = "";
940 while (<>) {
941 next if (/^%%Pages:\s*\d+/);
943 if (/^%%Page:/) {
944 if ($pno == 0 || $page =~ /\)S/) {
945 # Header or single page containing text
946 print "%%Page: ? $pno\n" if ($pno > 0);
947 print $page;
948 $pno++;
949 } else {
950 # Empty page, skip it.
952 $page = "";
953 $doprint = 1;
954 next;
957 # Skip from %%Trailer of one document to Endprolog
958 # %%Page of the next
959 $doprint = 0 if (/^%%Trailer/);
960 $page .= $_ if ($doprint);
963 if ($page =~ /\)S/) {
964 print "%%Page: ? $pno\n";
965 print $page;
966 } else {
967 $pno--;
969 print "%%Trailer\n%%Pages: $pno\n";
972 $PERL /tmp/$$.crmerge.pl < $infile
977 # input_cmd | insert_anchors | output_cmd
979 # Flag blocks of difference with sequentially numbered invisible
980 # anchors. These are used to drive the frames version of the
981 # sdiffs output.
983 # NOTE: Anchor zero flags the top of the file irrespective of changes,
984 # an additional anchor is also appended to flag the bottom.
986 # The script detects changed lines as any line that has a "<span
987 # class=" string embedded (unchanged lines have no class set and are
988 # not part of a <span>. Blank lines (without a sequence number)
989 # are also detected since they flag lines that have been inserted or
990 # deleted.
992 function insert_anchors
994 $AWK '
995 function ia() {
996 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
999 BEGIN {
1000 anc=1;
1001 inblock=1;
1002 printf "<pre>\n";
1004 NF == 0 || /^<span class=/ {
1005 if (inblock == 0) {
1006 ia();
1007 inblock=1;
1009 print;
1010 next;
1013 inblock=0;
1014 print;
1016 END {
1017 ia();
1019 printf "<b style=\"font-size: large; color: red\">";
1020 printf "--- EOF ---</b>"
1021 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1022 printf "</pre>"
1023 printf "<form name=\"eof\">";
1024 printf "<input name=\"value\" value=\"%d\" " \
1025 "type=\"hidden\"></input>", anc - 1;
1026 printf "</form>";
1028 ' $1
1033 # relative_dir
1035 # Print a relative return path from $1 to $2. For example if
1036 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1037 # this function would print "../../../../".
1039 # In the event that $1 is not in $2 a warning is printed to stderr,
1040 # and $2 is returned-- the result of this is that the resulting webrev
1041 # is not relocatable.
1043 function relative_dir
1045 typeset cur="${1##$2?(/)}"
1048 # If the first path was specified absolutely, and it does
1049 # not start with the second path, it's an error.
1051 if [[ "$cur" = "/${1#/}" ]]; then
1052 # Should never happen.
1053 print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1054 print -u2 "to \"$2\". Check input paths. Framed webrev "
1055 print -u2 "will not be relocatable!"
1056 print $2
1057 return
1061 # This is kind of ugly. The sed script will do the following:
1063 # 1. Strip off a leading "." or "./": this is important to get
1064 # the correct arcnav links for files in $WDIR.
1065 # 2. Strip off a trailing "/": this is not strictly necessary,
1066 # but is kind of nice, since it doesn't end up in "//" at
1067 # the end of a relative path.
1068 # 3. Replace all remaining sequences of non-"/" with "..": the
1069 # assumption here is that each dirname represents another
1070 # level of relative separation.
1071 # 4. Append a trailing "/" only for non-empty paths: this way
1072 # the caller doesn't need to duplicate this logic, and does
1073 # not end up using $RTOP/file for files in $WDIR.
1075 print $cur | $SED -e '{
1076 s:^\./*::
1077 s:/$::
1078 s:[^/][^/]*:..:g
1079 s:^\(..*\)$:\1/:
1084 # frame_nav_js
1086 # Emit javascript for frame navigation
1088 function frame_nav_js
1090 cat << \EOF
1091 var myInt;
1092 var scrolling=0;
1093 var sfactor = 3;
1094 var scount=10;
1096 function scrollByPix() {
1097 if (scount<=0) {
1098 sfactor*=1.2;
1099 scount=10;
1101 parent.lhs.scrollBy(0,sfactor);
1102 parent.rhs.scrollBy(0,sfactor);
1103 scount--;
1106 function scrollToAnc(num) {
1108 // Update the value of the anchor in the form which we use as
1109 // storage for this value. setAncValue() will take care of
1110 // correcting for overflow and underflow of the value and return
1111 // us the new value.
1112 num = setAncValue(num);
1114 // Set location and scroll back a little to expose previous
1115 // lines.
1117 // Note that this could be improved: it is possible although
1118 // complex to compute the x and y position of an anchor, and to
1119 // scroll to that location directly.
1121 parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1122 parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1124 parent.lhs.scrollBy(0,-30);
1125 parent.rhs.scrollBy(0,-30);
1128 function getAncValue()
1130 return (parseInt(parent.nav.document.diff.real.value));
1133 function setAncValue(val)
1135 if (val <= 0) {
1136 val = 0;
1137 parent.nav.document.diff.real.value = val;
1138 parent.nav.document.diff.display.value = "BOF";
1139 return (val);
1143 // The way we compute the max anchor value is to stash it
1144 // inline in the left and right hand side pages-- it's the same
1145 // on each side, so we pluck from the left.
1147 maxval = parent.lhs.document.eof.value.value;
1148 if (val < maxval) {
1149 parent.nav.document.diff.real.value = val;
1150 parent.nav.document.diff.display.value = val.toString();
1151 return (val);
1154 // this must be: val >= maxval
1155 val = maxval;
1156 parent.nav.document.diff.real.value = val;
1157 parent.nav.document.diff.display.value = "EOF";
1158 return (val);
1161 function stopScroll() {
1162 if (scrolling==1) {
1163 clearInterval(myInt);
1164 scrolling=0;
1168 function startScroll() {
1169 stopScroll();
1170 scrolling=1;
1171 myInt=setInterval("scrollByPix()",10);
1174 function handlePress(b) {
1176 switch (b) {
1177 case 1 :
1178 scrollToAnc(-1);
1179 break;
1180 case 2 :
1181 scrollToAnc(getAncValue() - 1);
1182 break;
1183 case 3 :
1184 sfactor=-3;
1185 startScroll();
1186 break;
1187 case 4 :
1188 sfactor=3;
1189 startScroll();
1190 break;
1191 case 5 :
1192 scrollToAnc(getAncValue() + 1);
1193 break;
1194 case 6 :
1195 scrollToAnc(999999);
1196 break;
1200 function handleRelease(b) {
1201 stopScroll();
1204 function keypress(ev) {
1205 var keynum;
1206 var keychar;
1208 if (window.event) { // IE
1209 keynum = ev.keyCode;
1210 } else if (ev.which) { // non-IE
1211 keynum = ev.which;
1214 keychar = String.fromCharCode(keynum);
1216 if (keychar == "k") {
1217 handlePress(2);
1218 return (0);
1219 } else if (keychar == "j" || keychar == " ") {
1220 handlePress(5);
1221 return (0);
1223 return (1);
1226 function ValidateDiffNum(){
1227 val = parent.nav.document.diff.display.value;
1228 if (val == "EOF") {
1229 scrollToAnc(999999);
1230 return;
1233 if (val == "BOF") {
1234 scrollToAnc(0);
1235 return;
1238 i=parseInt(val);
1239 if (isNaN(i)) {
1240 parent.nav.document.diff.display.value = getAncValue();
1241 } else {
1242 scrollToAnc(i);
1244 return false;
1251 # frame_navigation
1253 # Output anchor navigation file for framed sdiffs.
1255 function frame_navigation
1257 print "$HTML<head>$STDHEAD"
1259 cat << \EOF
1260 <title>Anchor Navigation</title>
1261 <meta http-equiv="Content-Script-Type" content="text/javascript">
1262 <meta http-equiv="Content-Type" content="text/html">
1264 <style type="text/css">
1265 div.button td { padding-left: 5px; padding-right: 5px;
1266 background-color: #eee; text-align: center;
1267 border: 1px #444 outset; cursor: pointer; }
1268 div.button a { font-weight: bold; color: black }
1269 div.button td:hover { background: #ffcc99; }
1270 </style>
1273 print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1275 cat << \EOF
1276 </head>
1277 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1278 onkeypress="keypress(event);">
1279 <noscript lang="javascript">
1280 <center>
1281 <p><big>Framed Navigation controls require Javascript</big><br></br>
1282 Either this browser is incompatable or javascript is not enabled</p>
1283 </center>
1284 </noscript>
1285 <table width="100%" border="0" align="center">
1286 <tr>
1287 <td valign="middle" width="25%">Diff navigation:
1288 Use 'j' and 'k' for next and previous diffs; or use buttons
1289 at right</td>
1290 <td align="center" valign="top" width="50%">
1291 <div class="button">
1292 <table border="0" align="center">
1293 <tr>
1294 <td>
1295 <a onMouseDown="handlePress(1);return true;"
1296 onMouseUp="handleRelease(1);return true;"
1297 onMouseOut="handleRelease(1);return true;"
1298 onClick="return false;"
1299 title="Go to Beginning Of file">BOF</a></td>
1300 <td>
1301 <a onMouseDown="handlePress(3);return true;"
1302 onMouseUp="handleRelease(3);return true;"
1303 onMouseOut="handleRelease(3);return true;"
1304 title="Scroll Up: Press and Hold to accelerate"
1305 onClick="return false;">Scroll Up</a></td>
1306 <td>
1307 <a onMouseDown="handlePress(2);return true;"
1308 onMouseUp="handleRelease(2);return true;"
1309 onMouseOut="handleRelease(2);return true;"
1310 title="Go to previous Diff"
1311 onClick="return false;">Prev Diff</a>
1312 </td></tr>
1314 <tr>
1315 <td>
1316 <a onMouseDown="handlePress(6);return true;"
1317 onMouseUp="handleRelease(6);return true;"
1318 onMouseOut="handleRelease(6);return true;"
1319 onClick="return false;"
1320 title="Go to End Of File">EOF</a></td>
1321 <td>
1322 <a onMouseDown="handlePress(4);return true;"
1323 onMouseUp="handleRelease(4);return true;"
1324 onMouseOut="handleRelease(4);return true;"
1325 title="Scroll Down: Press and Hold to accelerate"
1326 onClick="return false;">Scroll Down</a></td>
1327 <td>
1328 <a onMouseDown="handlePress(5);return true;"
1329 onMouseUp="handleRelease(5);return true;"
1330 onMouseOut="handleRelease(5);return true;"
1331 title="Go to next Diff"
1332 onClick="return false;">Next Diff</a></td>
1333 </tr>
1334 </table>
1335 </div>
1336 </td>
1337 <th valign="middle" width="25%">
1338 <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1339 <input name="display" value="BOF" size="8" type="text"></input>
1340 <input name="real" value="0" size="8" type="hidden"></input>
1341 </form>
1342 </th>
1343 </tr>
1344 </table>
1345 </body>
1346 </html>
1353 # diff_to_html <filename> <filepath> { U | C } <comment>
1355 # Processes the output of diff to produce an HTML file representing either
1356 # context or unified diffs.
1358 diff_to_html()
1360 TNAME=$1
1361 TPATH=$2
1362 DIFFTYPE=$3
1363 COMMENT=$4
1365 print "$HTML<head>$STDHEAD"
1366 print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1368 if [[ $DIFFTYPE == "U" ]]; then
1369 print "$UDIFFCSS"
1372 cat <<-EOF
1373 </head>
1374 <body id="SUNWwebrev">
1375 <a class="print" href="javascript:print()">Print this page</a>
1376 <pre>$COMMENT</pre>
1377 <pre>
1380 html_quote | $AWK '
1381 /^--- new/ { next }
1382 /^\+\+\+ new/ { next }
1383 /^--- old/ { next }
1384 /^\*\*\* old/ { next }
1385 /^\*\*\*\*/ { next }
1386 /^-------/ { printf "<center><h1>%s</h1></center>\n", $0; next }
1387 /^\@\@.*\@\@$/ { printf "</pre><hr></hr><pre>\n";
1388 printf "<span class=\"newmarker\">%s</span>\n", $0;
1389 next}
1391 /^\*\*\*/ { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1392 next}
1393 /^---/ { printf "<span class=\"newmarker\">%s</span>\n", $0;
1394 next}
1395 /^\+/ {printf "<span class=\"new\">%s</span>\n", $0; next}
1396 /^!/ {printf "<span class=\"changed\">%s</span>\n", $0; next}
1397 /^-/ {printf "<span class=\"removed\">%s</span>\n", $0; next}
1398 {printf "%s\n", $0; next}
1401 print "</pre></body></html>\n"
1406 # source_to_html { new | old } <filename>
1408 # Process a plain vanilla source file to transform it into an HTML file.
1410 source_to_html()
1412 WHICH=$1
1413 TNAME=$2
1415 print "$HTML<head>$STDHEAD"
1416 print "<title>$WNAME $WHICH $TNAME</title>"
1417 print "<body id=\"SUNWwebrev\">"
1418 print "<pre>"
1419 html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1420 print "</pre></body></html>"
1424 # comments_from_teamware {text|html} parent-file child-file
1426 # Find the first delta in the child that's not in the parent. Get the
1427 # newest delta from the parent, get all deltas from the child starting
1428 # with that delta, and then get all info starting with the second oldest
1429 # delta in that list (the first delta unique to the child).
1431 # This code adapted from Bill Shannon's "spc" script
1433 comments_from_teamware()
1435 fmt=$1
1436 pfile=$PWS/$2
1437 cfile=$CWS/$3
1439 if [[ ! -f $PWS/${2%/*}/SCCS/s.${2##*/} && -n $RWS ]]; then
1440 pfile=$RWS/$2
1443 if [[ -f $pfile ]]; then
1444 psid=$($SCCS prs -d:I: $pfile 2>/dev/null)
1445 else
1446 psid=1.1
1449 set -A sids $($SCCS prs -l -r$psid -d:I: $cfile 2>/dev/null)
1450 N=${#sids[@]}
1452 nawkprg='
1453 /^COMMENTS:/ {p=1; continue}
1454 /^D [0-9]+\.[0-9]+/ {printf "--- %s ---\n", $2; p=0; }
1455 NF == 0u { continue }
1456 {if (p==0) continue; print $0 }'
1458 if [[ $N -ge 2 ]]; then
1459 sid1=${sids[$((N-2))]} # Gets 2nd to last sid
1461 if [[ $fmt == "text" ]]; then
1462 $SCCS prs -l -r$sid1 $cfile 2>/dev/null | \
1463 $AWK "$nawkprg"
1464 return
1467 $SCCS prs -l -r$sid1 $cfile 2>/dev/null | \
1468 html_quote | its2url | $AWK "$nawkprg"
1473 # comments_from_wx {text|html} filepath
1475 # Given the pathname of a file, find its location in a "wx" active
1476 # file list and print the following comment. Output is either text or
1477 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1478 # are turned into URLs.
1480 # This is also used with Mercurial and the file list provided by hg-active.
1482 comments_from_wx()
1484 typeset fmt=$1
1485 typeset p=$2
1487 comm=`$AWK '
1488 $1 == "'$p'" {
1489 do getline ; while (NF > 0)
1490 getline
1491 while (NF > 0) { print ; getline }
1492 exit
1493 }' < $wxfile`
1495 if [[ -z $comm ]]; then
1496 comm="*** NO COMMENTS ***"
1499 if [[ $fmt == "text" ]]; then
1500 print -- "$comm"
1501 return
1504 print -- "$comm" | html_quote | its2url
1509 # getcomments {text|html} filepath parentpath
1511 # Fetch the comments depending on what SCM mode we're in.
1513 getcomments()
1515 typeset fmt=$1
1516 typeset p=$2
1517 typeset pp=$3
1519 if [[ -n $Nflag ]]; then
1520 return
1523 # Mercurial support uses a file list in wx format, so this
1524 # will be used there, too
1526 if [[ -n $wxfile ]]; then
1527 comments_from_wx $fmt $p
1528 else
1529 if [[ $SCM_MODE == "teamware" ]]; then
1530 comments_from_teamware $fmt $pp $p
1536 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1538 # Print out Code Inspection figures similar to sccs-prt(1) format.
1540 function printCI
1542 integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1543 typeset str
1544 if (( tot == 1 )); then
1545 str="line"
1546 else
1547 str="lines"
1549 printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1550 $tot $str $ins $del $mod $unc
1555 # difflines <oldfile> <newfile>
1557 # Calculate and emit number of added, removed, modified and unchanged lines,
1558 # and total lines changed, the sum of added + removed + modified.
1560 function difflines
1562 integer tot mod del ins unc err
1563 typeset filename
1565 eval $( diff -e $1 $2 | $AWK '
1566 # Change range of lines: N,Nc
1567 /^[0-9]*,[0-9]*c$/ {
1568 n=split(substr($1,1,length($1)-1), counts, ",");
1569 if (n != 2) {
1570 error=2
1571 exit;
1574 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1575 # following would be 5 - 3 = 2! Hence +1 for correction.
1577 r=(counts[2]-counts[1])+1;
1580 # Now count replacement lines: each represents a change instead
1581 # of a delete, so increment c and decrement r.
1583 while (getline != /^\.$/) {
1584 c++;
1585 r--;
1588 # If there were more replacement lines than original lines,
1589 # then r will be negative; in this case there are no deletions,
1590 # but there are r changes that should be counted as adds, and
1591 # since r is negative, subtract it from a and add it to c.
1593 if (r < 0) {
1594 a-=r;
1595 c+=r;
1599 # If there were more original lines than replacement lines, then
1600 # r will be positive; in this case, increment d by that much.
1602 if (r > 0) {
1603 d+=r;
1605 next;
1608 # Change lines: Nc
1609 /^[0-9].*c$/ {
1610 # The first line is a replacement; any more are additions.
1611 if (getline != /^\.$/) {
1612 c++;
1613 while (getline != /^\.$/) a++;
1615 next;
1618 # Add lines: both Na and N,Na
1619 /^[0-9].*a$/ {
1620 while (getline != /^\.$/) a++;
1621 next;
1624 # Delete range of lines: N,Nd
1625 /^[0-9]*,[0-9]*d$/ {
1626 n=split(substr($1,1,length($1)-1), counts, ",");
1627 if (n != 2) {
1628 error=2
1629 exit;
1632 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1633 # following would be 5 - 3 = 2! Hence +1 for correction.
1635 r=(counts[2]-counts[1])+1;
1636 d+=r;
1637 next;
1640 # Delete line: Nd. For example 10d says line 10 is deleted.
1641 /^[0-9]*d$/ {d++; next}
1643 # Should not get here!
1645 error=1;
1646 exit;
1649 # Finish off - print results
1650 END {
1651 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1652 (c+d+a), c, d, a, error);
1653 }' )
1655 # End of $AWK, Check to see if any trouble occurred.
1656 if (( $? > 0 || err > 0 )); then
1657 print "Unexpected Error occurred reading" \
1658 "\`diff -e $1 $2\`: \$?=$?, err=" $err
1659 return
1662 # Accumulate totals
1663 (( TOTL += tot ))
1664 (( TMOD += mod ))
1665 (( TDEL += del ))
1666 (( TINS += ins ))
1667 # Calculate unchanged lines
1668 unc=`wc -l < $1`
1669 if (( unc > 0 )); then
1670 (( unc -= del + mod ))
1671 (( TUNC += unc ))
1673 # print summary
1674 print "<span class=\"lineschanged\">"
1675 printCI $tot $ins $del $mod $unc
1676 print "</span>"
1681 # flist_from_wx
1683 # Sets up webrev to source its information from a wx-formatted file.
1684 # Sets the global 'wxfile' variable.
1686 function flist_from_wx
1688 typeset argfile=$1
1689 if [[ -n ${argfile%%/*} ]]; then
1691 # If the wx file pathname is relative then make it absolute
1692 # because the webrev does a "cd" later on.
1694 wxfile=$PWD/$argfile
1695 else
1696 wxfile=$argfile
1699 $AWK '{ c = 1; print;
1700 while (getline) {
1701 if (NF == 0) { c = -c; continue }
1702 if (c > 0) print
1704 }' $wxfile > $FLIST
1706 print " Done."
1710 # flist_from_teamware [ <args-to-putback-n> ]
1712 # Generate the file list by extracting file names from a putback -n. Some
1713 # names may come from the "update/create" messages and others from the
1714 # "currently checked out" warning. Renames are detected here too. Extract
1715 # values for CODEMGR_WS and CODEMGR_PARENT from the output of the putback
1716 # -n as well, but remove them if they are already defined.
1718 function flist_from_teamware
1720 if [[ -n $codemgr_parent && -z $parent_webrev ]]; then
1721 if [[ ! -d $codemgr_parent/Codemgr_wsdata ]]; then
1722 print -u2 "parent $codemgr_parent doesn't look like a" \
1723 "valid teamware workspace"
1724 exit 1
1726 parent_args="-p $codemgr_parent"
1729 print " File list from: 'putback -n $parent_args $*' ... \c"
1731 putback -n $parent_args $* 2>&1 |
1732 $AWK '
1733 /^update:|^create:/ {print $2}
1734 /^Parent workspace:/ {printf("CODEMGR_PARENT=%s\n",$3)}
1735 /^Child workspace:/ {printf("CODEMGR_WS=%s\n",$3)}
1736 /^The following files are currently checked out/ {p = 1; continue}
1737 NF == 0 {p=0 ; continue}
1738 /^rename/ {old=$3}
1739 $1 == "to:" {print $2, old}
1740 /^"/ {continue}
1741 p == 1 {print $1}' |
1742 sort -r -k 1,1 -u | sort > $FLIST
1744 print " Done."
1748 # Call hg-active to get the active list output in the wx active list format
1750 function hg_active_wxfile
1752 typeset child=$1
1753 typeset parent=$2
1755 TMPFLIST=/tmp/$$.active
1756 $HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1757 wxfile=$TMPFLIST
1761 # flist_from_mercurial
1762 # Call hg-active to get a wx-style active list, and hand it off to
1763 # flist_from_wx
1765 function flist_from_mercurial
1767 typeset child=$1
1768 typeset parent=$2
1770 print " File list from: hg-active -p $parent ...\c"
1771 if [[ ! -x $HG_ACTIVE ]]; then
1772 print # Blank line for the \c above
1773 print -u2 "Error: hg-active tool not found. Exiting"
1774 exit 1
1776 hg_active_wxfile $child $parent
1778 # flist_from_wx prints the Done, so we don't have to.
1779 flist_from_wx $TMPFLIST
1783 # flist_from_subversion
1785 # Generate the file list by extracting file names from svn status.
1787 function flist_from_subversion
1789 CWS=$1
1790 OLDPWD=$2
1792 cd $CWS
1793 print -u2 " File list from: svn status ... \c"
1794 svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1795 print -u2 " Done."
1796 cd $OLDPWD
1799 function env_from_flist
1801 [[ -r $FLIST ]] || return
1804 # Use "eval" to set env variables that are listed in the file
1805 # list. Then copy those into our local versions of those
1806 # variables if they have not been set already.
1808 eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1810 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1811 codemgr_ws=$CODEMGR_WS
1812 export CODEMGR_WS
1816 # Check to see if CODEMGR_PARENT is set in the flist file.
1818 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1819 codemgr_parent=$CODEMGR_PARENT
1820 export CODEMGR_PARENT
1824 function look_for_prog
1826 typeset path
1827 typeset ppath
1828 typeset progname=$1
1830 ppath=$PATH
1831 ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1832 ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin
1833 ppath=$ppath:/opt/onbld/bin/`uname -p`
1835 PATH=$ppath prog=`whence $progname`
1836 if [[ -n $prog ]]; then
1837 print $prog
1841 function get_file_mode
1843 $PERL -e '
1844 if (@stat = stat($ARGV[0])) {
1845 $mode = $stat[2] & 0777;
1846 printf "%03o\n", $mode;
1847 exit 0;
1848 } else {
1849 exit 1;
1851 ' $1
1854 function build_old_new_teamware
1856 typeset olddir="$1"
1857 typeset newdir="$2"
1859 # If the child's version doesn't exist then
1860 # get a readonly copy.
1862 if [[ ! -f $CWS/$DIR/$F && -f $CWS/$DIR/SCCS/s.$F ]]; then
1863 $SCCS get -s -p $CWS/$DIR/$F > $CWS/$DIR/$F
1866 # The following two sections propagate file permissions the
1867 # same way SCCS does. If the file is already under version
1868 # control, always use permissions from the SCCS/s.file. If
1869 # the file is not under SCCS control, use permissions from the
1870 # working copy. In all cases, the file copied to the webrev
1871 # is set to read only, and group/other permissions are set to
1872 # match those of the file owner. This way, even if the file
1873 # is currently checked out, the webrev will display the final
1874 # permissions that would result after check in.
1877 # Snag new version of file.
1879 rm -f $newdir/$DIR/$F
1880 cp $CWS/$DIR/$F $newdir/$DIR/$F
1881 if [[ -f $CWS/$DIR/SCCS/s.$F ]]; then
1882 chmod `get_file_mode $CWS/$DIR/SCCS/s.$F` \
1883 $newdir/$DIR/$F
1885 chmod u-w,go=u $newdir/$DIR/$F
1888 # Get the parent's version of the file. First see whether the
1889 # child's version is checked out and get the parent's version
1890 # with keywords expanded or unexpanded as appropriate.
1892 if [[ -f $PWS/$PDIR/$PF && ! -f $PWS/$PDIR/SCCS/s.$PF && \
1893 ! -f $PWS/$PDIR/SCCS/p.$PF ]]; then
1894 # Parent is not a real workspace, but just a raw
1895 # directory tree - use the file that's there as
1896 # the old file.
1898 rm -f $olddir/$PDIR/$PF
1899 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1900 else
1901 if [[ -f $PWS/$PDIR/SCCS/s.$PF ]]; then
1902 real_parent=$PWS
1903 else
1904 real_parent=$RWS
1907 rm -f $olddir/$PDIR/$PF
1909 if [[ -f $real_parent/$PDIR/$PF ]]; then
1910 if [ -f $CWS/$DIR/SCCS/p.$F ]; then
1911 $SCCS get -s -p -k $real_parent/$PDIR/$PF > \
1912 $olddir/$PDIR/$PF
1913 else
1914 $SCCS get -s -p $real_parent/$PDIR/$PF > \
1915 $olddir/$PDIR/$PF
1917 chmod `get_file_mode $real_parent/$PDIR/SCCS/s.$PF` \
1918 $olddir/$PDIR/$PF
1921 if [[ -f $olddir/$PDIR/$PF ]]; then
1922 chmod u-w,go=u $olddir/$PDIR/$PF
1926 function build_old_new_mercurial
1928 typeset olddir="$1"
1929 typeset newdir="$2"
1930 typeset old_mode=
1931 typeset new_mode=
1932 typeset file
1935 # Get old file mode, from the parent revision manifest entry.
1936 # Mercurial only stores a "file is executable" flag, but the
1937 # manifest will display an octal mode "644" or "755".
1939 if [[ "$PDIR" == "." ]]; then
1940 file="$PF"
1941 else
1942 file="$PDIR/$PF"
1944 file=`echo $file | $SED 's#/#\\\/#g'`
1945 # match the exact filename, and return only the permission digits
1946 old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
1947 < $HG_PARENT_MANIFEST`
1950 # Get new file mode, directly from the filesystem.
1951 # Normalize the mode to match Mercurial's behavior.
1953 new_mode=`get_file_mode $CWS/$DIR/$F`
1954 if [[ -n "$new_mode" ]]; then
1955 if [[ "$new_mode" = *[1357]* ]]; then
1956 new_mode=755
1957 else
1958 new_mode=644
1963 # new version of the file.
1965 rm -rf $newdir/$DIR/$F
1966 if [[ -e $CWS/$DIR/$F ]]; then
1967 cp $CWS/$DIR/$F $newdir/$DIR/$F
1968 if [[ -n $new_mode ]]; then
1969 chmod $new_mode $newdir/$DIR/$F
1970 else
1971 # should never happen
1972 print -u2 "ERROR: set mode of $newdir/$DIR/$F"
1977 # parent's version of the file
1979 # Note that we get this from the last version common to both
1980 # ourselves and the parent. References are via $CWS since we have no
1981 # guarantee that the parent workspace is reachable via the filesystem.
1983 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
1984 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1985 elif [[ -n $HG_PARENT ]]; then
1986 hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
1987 $olddir/$PDIR/$PF 2>/dev/null
1989 if (( $? != 0 )); then
1990 rm -f $olddir/$PDIR/$PF
1991 else
1992 if [[ -n $old_mode ]]; then
1993 chmod $old_mode $olddir/$PDIR/$PF
1994 else
1995 # should never happen
1996 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
2002 function build_old_new_subversion
2004 typeset olddir="$1"
2005 typeset newdir="$2"
2007 # Snag new version of file.
2008 rm -f $newdir/$DIR/$F
2009 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2011 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2012 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2013 else
2014 # Get the parent's version of the file.
2015 svn status $CWS/$DIR/$F | read stat file
2016 if [[ $stat != "A" ]]; then
2017 svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2022 function build_old_new_unknown
2024 typeset olddir="$1"
2025 typeset newdir="$2"
2028 # Snag new version of file.
2030 rm -f $newdir/$DIR/$F
2031 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2034 # Snag the parent's version of the file.
2036 if [[ -f $PWS/$PDIR/$PF ]]; then
2037 rm -f $olddir/$PDIR/$PF
2038 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2042 function build_old_new
2044 typeset WDIR=$1
2045 typeset PWS=$2
2046 typeset PDIR=$3
2047 typeset PF=$4
2048 typeset CWS=$5
2049 typeset DIR=$6
2050 typeset F=$7
2052 typeset olddir="$WDIR/raw_files/old"
2053 typeset newdir="$WDIR/raw_files/new"
2055 mkdir -p $olddir/$PDIR
2056 mkdir -p $newdir/$DIR
2058 if [[ $SCM_MODE == "teamware" ]]; then
2059 build_old_new_teamware "$olddir" "$newdir"
2060 elif [[ $SCM_MODE == "mercurial" ]]; then
2061 build_old_new_mercurial "$olddir" "$newdir"
2062 elif [[ $SCM_MODE == "subversion" ]]; then
2063 build_old_new_subversion "$olddir" "$newdir"
2064 elif [[ $SCM_MODE == "unknown" ]]; then
2065 build_old_new_unknown "$olddir" "$newdir"
2068 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2069 print "*** Error: file not in parent or child"
2070 return 1
2072 return 0
2077 # Usage message.
2079 function usage
2081 print 'Usage:\twebrev [common-options]
2082 webrev [common-options] ( <file> | - )
2083 webrev [common-options] -w <wx file>
2085 Options:
2086 -C <filename>: Use <filename> for the information tracking configuration.
2087 -D: delete remote webrev
2088 -i <filename>: Include <filename> in the index.html file.
2089 -I <filename>: Use <filename> for the information tracking registry.
2090 -n: do not generate the webrev (useful with -U)
2091 -O: Print bugids/arc cases suitable for OpenSolaris.
2092 -o <outdir>: Output webrev to specified directory.
2093 -p <compare-against>: Use specified parent wkspc or basis for comparison
2094 -t <remote_target>: Specify remote destination for webrev upload
2095 -U: upload the webrev to remote destination
2096 -w <wxfile>: Use specified wx active file.
2098 Environment:
2099 WDIR: Control the output directory.
2100 WEBREV_TRASH_DIR: Set directory for webrev delete.
2102 SCM Specific Options:
2103 TeamWare: webrev [common-options] -l [arguments to 'putback']
2105 SCM Environment:
2106 CODEMGR_WS: Workspace location.
2107 CODEMGR_PARENT: Parent workspace location.
2110 exit 2
2115 # Main program starts here
2119 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2121 set +o noclobber
2123 PATH=$(dirname $(whence $0)):$PATH
2125 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2126 [[ -z $WX ]] && WX=`look_for_prog wx`
2127 [[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
2128 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2129 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2130 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2131 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2132 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2133 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2134 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2135 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2136 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2137 [[ -z $SCP ]] && SCP=`look_for_prog scp`
2138 [[ -z $SED ]] && SED=`look_for_prog sed`
2139 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2140 [[ -z $SORT ]] && SORT=`look_for_prog sort`
2141 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2142 [[ -z $GREP ]] && GREP=`look_for_prog grep`
2143 [[ -z $FIND ]] && FIND=`look_for_prog find`
2145 # set name of trash directory for remote webrev deletion
2146 TRASH_DIR=".trash"
2147 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2149 if [[ ! -x $PERL ]]; then
2150 print -u2 "Error: No perl interpreter found. Exiting."
2151 exit 1
2154 if [[ ! -x $WHICH_SCM ]]; then
2155 print -u2 "Error: Could not find which_scm. Exiting."
2156 exit 1
2160 # These aren't fatal, but we want to note them to the user.
2161 # We don't warn on the absence of 'wx' until later when we've
2162 # determined that we actually need to try to invoke it.
2164 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2165 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2166 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2168 # Declare global total counters.
2169 integer TOTL TINS TDEL TMOD TUNC
2171 # default remote host for upload/delete
2172 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2173 # prefixes for upload targets
2174 typeset -r rsync_prefix="rsync://"
2175 typeset -r ssh_prefix="ssh://"
2177 Cflag=
2178 Dflag=
2179 flist_mode=
2180 flist_file=
2181 iflag=
2182 Iflag=
2183 lflag=
2184 Nflag=
2185 nflag=
2186 Oflag=
2187 oflag=
2188 pflag=
2189 tflag=
2190 uflag=
2191 Uflag=
2192 wflag=
2193 remote_target=
2196 # NOTE: when adding/removing options it is necessary to sync the list
2197 # with usr/src/tools/onbld/hgext/cdm.py
2199 while getopts "C:Di:I:lnNo:Op:t:Uw" opt
2201 case $opt in
2202 C) Cflag=1
2203 ITSCONF=$OPTARG;;
2205 D) Dflag=1;;
2207 i) iflag=1
2208 INCLUDE_FILE=$OPTARG;;
2210 I) Iflag=1
2211 ITSREG=$OPTARG;;
2214 # If -l has been specified, we need to abort further options
2215 # processing, because subsequent arguments are going to be
2216 # arguments to 'putback -n'.
2218 l) lflag=1
2219 break;;
2221 N) Nflag=1;;
2223 n) nflag=1;;
2225 O) Oflag=1;;
2227 o) oflag=1
2228 # Strip the trailing slash to correctly form remote target.
2229 WDIR=${OPTARG%/};;
2231 p) pflag=1
2232 codemgr_parent=$OPTARG;;
2234 t) tflag=1
2235 remote_target=$OPTARG;;
2237 U) Uflag=1;;
2239 w) wflag=1;;
2241 ?) usage;;
2242 esac
2243 done
2245 FLIST=/tmp/$$.flist
2247 if [[ -n $wflag && -n $lflag ]]; then
2248 usage
2251 # more sanity checking
2252 if [[ -n $nflag && -z $Uflag ]]; then
2253 print "it does not make sense to skip webrev generation" \
2254 "without -U"
2255 exit 1
2258 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2259 echo "remote target has to be used only for upload or delete"
2260 exit 1
2264 # For the invocation "webrev -n -U" with no other options, webrev will assume
2265 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2266 # $(basename ${CWS}). So we need to get CWS set before we skip any remaining
2267 # logic.
2269 $WHICH_SCM | read SCM_MODE junk || exit 1
2270 if [[ $SCM_MODE == "teamware" ]]; then
2272 # Teamware priorities:
2273 # 1. CODEMGR_WS from the environment
2274 # 2. workspace name
2276 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
2277 if [[ -n $codemgr_ws && ! -d $codemgr_ws ]]; then
2278 print -u2 "$codemgr_ws: no such workspace"
2279 exit 1
2281 [[ -z $codemgr_ws ]] && codemgr_ws=$(workspace name)
2282 codemgr_ws=$(cd $codemgr_ws;print $PWD)
2283 CODEMGR_WS=$codemgr_ws
2284 CWS=$codemgr_ws
2285 elif [[ $SCM_MODE == "mercurial" ]]; then
2287 # Mercurial priorities:
2288 # 1. hg root from CODEMGR_WS environment variable
2289 # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under
2290 # usr/closed when we run webrev
2291 # 2. hg root from directory of invocation
2293 if [[ ${PWD} =~ "usr/closed" ]]; then
2294 testparent=${CODEMGR_WS}/usr/closed
2295 # If we're in OpenSolaris mode, we enforce a minor policy:
2296 # help to make sure the reviewer doesn't accidentally publish
2297 # source which is under usr/closed
2298 if [[ -n "$Oflag" ]]; then
2299 print -u2 "OpenSolaris output not permitted with" \
2300 "usr/closed changes"
2301 exit 1
2303 else
2304 testparent=${CODEMGR_WS}
2306 [[ -z $codemgr_ws && -n $testparent ]] && \
2307 codemgr_ws=$(hg root -R $testparent 2>/dev/null)
2308 [[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null)
2309 CWS=$codemgr_ws
2310 elif [[ $SCM_MODE == "subversion" ]]; then
2312 # Subversion priorities:
2313 # 1. CODEMGR_WS from environment
2314 # 2. Relative path from current directory to SVN repository root
2316 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2317 CWS=$CODEMGR_WS
2318 else
2319 svn info | while read line; do
2320 if [[ $line == "URL: "* ]]; then
2321 url=${line#URL: }
2322 elif [[ $line == "Repository Root: "* ]]; then
2323 repo=${line#Repository Root: }
2325 done
2327 rel=${url#$repo}
2328 CWS=${PWD%$rel}
2333 # If no SCM has been determined, take either the environment setting
2334 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2336 if [[ -z ${CWS} ]]; then
2337 CWS=${CODEMGR_WS:-.}
2341 # If the command line options indicate no webrev generation, either
2342 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2343 # ton of logic we can skip.
2345 # Instead of increasing indentation, we intentionally leave this loop
2346 # body open here, and exit via break from multiple points within.
2347 # Search for DO_EVERYTHING below to find the break points and closure.
2349 for do_everything in 1; do
2351 # DO_EVERYTHING: break point
2352 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2353 break
2357 # If this manually set as the parent, and it appears to be an earlier webrev,
2358 # then note that fact and set the parent to the raw_files/new subdirectory.
2360 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2361 parent_webrev="$codemgr_parent"
2362 codemgr_parent="$codemgr_parent/raw_files/new"
2365 if [[ -z $wflag && -z $lflag ]]; then
2366 shift $(($OPTIND - 1))
2368 if [[ $1 == "-" ]]; then
2369 cat > $FLIST
2370 flist_mode="stdin"
2371 flist_done=1
2372 shift
2373 elif [[ -n $1 ]]; then
2374 if [[ ! -r $1 ]]; then
2375 print -u2 "$1: no such file or not readable"
2376 usage
2378 cat $1 > $FLIST
2379 flist_mode="file"
2380 flist_file=$1
2381 flist_done=1
2382 shift
2383 else
2384 flist_mode="auto"
2389 # Before we go on to further consider -l and -w, work out which SCM we think
2390 # is in use.
2392 case "$SCM_MODE" in
2393 teamware|mercurial|subversion)
2395 unknown)
2396 if [[ $flist_mode == "auto" ]]; then
2397 print -u2 "Unable to determine SCM in use and file list not specified"
2398 print -u2 "See which_scm(1) for SCM detection information."
2399 exit 1
2403 if [[ $flist_mode == "auto" ]]; then
2404 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2405 exit 1
2408 esac
2410 print -u2 " SCM detected: $SCM_MODE"
2412 if [[ -n $lflag ]]; then
2414 # If the -l flag is given instead of the name of a file list,
2415 # then generate the file list by extracting file names from a
2416 # putback -n.
2418 shift $(($OPTIND - 1))
2419 if [[ $SCM_MODE == "teamware" ]]; then
2420 flist_from_teamware "$*"
2421 else
2422 print -u2 -- "Error: -l option only applies to TeamWare"
2423 exit 1
2425 flist_done=1
2426 shift $#
2427 elif [[ -n $wflag ]]; then
2429 # If the -w is given then assume the file list is in Bonwick's "wx"
2430 # command format, i.e. pathname lines alternating with SCCS comment
2431 # lines with blank lines as separators. Use the SCCS comments later
2432 # in building the index.html file.
2434 shift $(($OPTIND - 1))
2435 wxfile=$1
2436 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2437 if [[ -r $CODEMGR_WS/wx/active ]]; then
2438 wxfile=$CODEMGR_WS/wx/active
2442 [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2443 "be auto-detected (check \$CODEMGR_WS)" && exit 1
2445 if [[ ! -r $wxfile ]]; then
2446 print -u2 "$wxfile: no such file or not readable"
2447 usage
2450 print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2451 flist_from_wx $wxfile
2452 flist_done=1
2453 if [[ -n "$*" ]]; then
2454 shift
2456 elif [[ $flist_mode == "stdin" ]]; then
2457 print -u2 " File list from: standard input"
2458 elif [[ $flist_mode == "file" ]]; then
2459 print -u2 " File list from: $flist_file"
2462 if [[ $# -gt 0 ]]; then
2463 print -u2 "WARNING: unused arguments: $*"
2467 # Before we entered the DO_EVERYTHING loop, we should have already set CWS
2468 # and CODEMGR_WS as needed. Here, we set the parent workspace.
2471 if [[ $SCM_MODE == "teamware" ]]; then
2474 # Teamware priorities:
2476 # 1) via -p command line option
2477 # 2) in the user environment
2478 # 3) in the flist
2479 # 4) automatically based on the workspace
2483 # For 1, codemgr_parent will already be set. Here's 2:
2485 [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
2486 codemgr_parent=$CODEMGR_PARENT
2487 if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then
2488 print -u2 "$codemgr_parent: no such directory"
2489 exit 1
2493 # If we're in auto-detect mode and we haven't already gotten the file
2494 # list, then see if we can get it by probing for wx.
2496 if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then
2497 if [[ ! -x $WX ]]; then
2498 print -u2 "WARNING: wx not found!"
2502 # We need to use wx list -w so that we get renamed files, etc.
2503 # but only if a wx active file exists-- otherwise wx will
2504 # hang asking us to initialize our wx information.
2506 if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then
2507 print -u2 " File list from: 'wx list -w' ... \c"
2508 $WX list -w > $FLIST
2509 $WX comments > /tmp/$$.wx_comments
2510 wxfile=/tmp/$$.wx_comments
2511 print -u2 "done"
2512 flist_done=1
2517 # If by hook or by crook we've gotten a file list by now (perhaps
2518 # from the command line), eval it to extract environment variables from
2519 # it: This is method 3 for finding the parent.
2521 if [[ -z $flist_done ]]; then
2522 flist_from_teamware
2524 env_from_flist
2527 # (4) If we still don't have a value for codemgr_parent, get it
2528 # from workspace.
2530 [[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent`
2531 if [[ ! -d $codemgr_parent ]]; then
2532 print -u2 "$CODEMGR_PARENT: no such parent workspace"
2533 exit 1
2536 PWS=$codemgr_parent
2538 [[ -n $parent_webrev ]] && RWS=$(workspace parent $CWS)
2540 elif [[ $SCM_MODE == "mercurial" ]]; then
2542 # Parent can either be specified with -p
2543 # Specified with CODEMGR_PARENT in the environment
2544 # or taken from hg's default path.
2547 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2548 codemgr_parent=$CODEMGR_PARENT
2551 if [[ -z $codemgr_parent ]]; then
2552 codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
2555 CWS_REV=`hg parent -R $codemgr_ws --template '{node|short}' 2>/dev/null`
2556 PWS=$codemgr_parent
2559 # If the parent is a webrev, we want to do some things against
2560 # the natural workspace parent (file list, comments, etc)
2562 if [[ -n $parent_webrev ]]; then
2563 real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
2564 else
2565 real_parent=$PWS
2569 # If hg-active exists, then we run it. In the case of no explicit
2570 # flist given, we'll use it for our comments. In the case of an
2571 # explicit flist given we'll try to use it for comments for any
2572 # files mentioned in the flist.
2574 if [[ -z $flist_done ]]; then
2575 flist_from_mercurial $CWS $real_parent
2576 flist_done=1
2580 # If we have a file list now, pull out any variables set
2581 # therein. We do this now (rather than when we possibly use
2582 # hg-active to find comments) to avoid stomping specifications
2583 # in the user-specified flist.
2585 if [[ -n $flist_done ]]; then
2586 env_from_flist
2590 # Only call hg-active if we don't have a wx formatted file already
2592 if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2593 print " Comments from: hg-active -p $real_parent ...\c"
2594 hg_active_wxfile $CWS $real_parent
2595 print " Done."
2599 # At this point we must have a wx flist either from hg-active,
2600 # or in general. Use it to try and find our parent revision,
2601 # if we don't have one.
2603 if [[ -z $HG_PARENT ]]; then
2604 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2608 # If we still don't have a parent, we must have been given a
2609 # wx-style active list with no HG_PARENT specification, run
2610 # hg-active and pull an HG_PARENT out of it, ignore the rest.
2612 if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2613 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2614 eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2615 elif [[ -z $HG_PARENT ]]; then
2616 print -u2 "Error: Cannot discover parent revision"
2617 exit 1
2619 elif [[ $SCM_MODE == "subversion" ]]; then
2622 # We only will have a real parent workspace in the case one
2623 # was specified (be it an older webrev, or another checkout).
2625 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2627 if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2628 flist_from_subversion $CWS $OLDPWD
2630 else
2631 if [[ $SCM_MODE == "unknown" ]]; then
2632 print -u2 " Unknown type of SCM in use"
2633 else
2634 print -u2 " Unsupported SCM in use: $SCM_MODE"
2637 env_from_flist
2639 if [[ -z $CODEMGR_WS ]]; then
2640 print -u2 "SCM not detected/supported and CODEMGR_WS not specified"
2641 exit 1
2644 if [[ -z $CODEMGR_PARENT ]]; then
2645 print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified"
2646 exit 1
2649 CWS=$CODEMGR_WS
2650 PWS=$CODEMGR_PARENT
2654 # If the user didn't specify a -i option, check to see if there is a
2655 # webrev-info file in the workspace directory.
2657 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2658 iflag=1
2659 INCLUDE_FILE="$CWS/webrev-info"
2662 if [[ -n $iflag ]]; then
2663 if [[ ! -r $INCLUDE_FILE ]]; then
2664 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2665 "not readable."
2666 exit 1
2667 else
2669 # $INCLUDE_FILE may be a relative path, and the script alters
2670 # PWD, so we just stash a copy in /tmp.
2672 cp $INCLUDE_FILE /tmp/$$.include
2676 # DO_EVERYTHING: break point
2677 if [[ -n $Nflag ]]; then
2678 break
2681 typeset -A itsinfo
2682 typeset -r its_sed_script=/tmp/$$.its_sed
2683 valid_prefixes=
2684 if [[ -z $nflag ]]; then
2685 DEFREGFILE="$(dirname $(whence $0))/../etc/its.reg"
2686 if [[ -n $Iflag ]]; then
2687 REGFILE=$ITSREG
2688 elif [[ -r $HOME/.its.reg ]]; then
2689 REGFILE=$HOME/.its.reg
2690 else
2691 REGFILE=$DEFREGFILE
2693 if [[ ! -r $REGFILE ]]; then
2694 print "ERROR: Unable to read database registry file $REGFILE"
2695 exit 1
2696 elif [[ $REGFILE != $DEFREGFILE ]]; then
2697 print " its.reg from: $REGFILE"
2700 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do
2702 name=${LINE%%=*}
2703 value="${LINE#*=}"
2705 if [[ $name == PREFIX ]]; then
2706 p=${value}
2707 valid_prefixes="${p} ${valid_prefixes}"
2708 else
2709 itsinfo["${p}_${name}"]="${value}"
2711 done
2714 DEFCONFFILE="$(dirname $(whence $0))/../etc/its.conf"
2715 CONFFILES=$DEFCONFFILE
2716 if [[ -r $HOME/.its.conf ]]; then
2717 CONFFILES="${CONFFILES} $HOME/.its.conf"
2719 if [[ -n $Cflag ]]; then
2720 CONFFILES="${CONFFILES} ${ITSCONF}"
2722 its_domain=
2723 its_priority=
2724 for cf in ${CONFFILES}; do
2725 if [[ ! -r $cf ]]; then
2726 print "ERROR: Unable to read database configuration file $cf"
2727 exit 1
2728 elif [[ $cf != $DEFCONFFILE ]]; then
2729 print " its.conf: reading $cf"
2731 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do
2732 eval "${LINE}"
2733 done
2734 done
2737 # If an information tracking system is explicitly identified by prefix,
2738 # we want to disregard the specified priorities and resolve it accordingly.
2740 # To that end, we'll build a sed script to do each valid prefix in turn.
2742 for p in ${valid_prefixes}; do
2744 # When an informational URL was provided, translate it to a
2745 # hyperlink. When omitted, simply use the prefix text.
2747 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2748 itsinfo["${p}_INFO"]=${p}
2749 else
2750 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2754 # Assume that, for this invocation of webrev, all references
2755 # to this information tracking system should resolve through
2756 # the same URL.
2758 # If the caller specified -O, then always use EXTERNAL_URL.
2760 # Otherwise, look in the list of domains for a matching
2761 # INTERNAL_URL.
2763 [[ -z $Oflag ]] && for d in ${its_domain}; do
2764 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2765 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2766 break
2768 done
2769 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2770 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2774 # Turn the destination URL into a hyperlink
2776 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2778 # The character class below contains a literal tab
2779 print "/^${p}[: ]/ {
2780 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2781 s;^${p};${itsinfo[${p}_INFO]};
2782 }" >> ${its_sed_script}
2783 done
2786 # The previous loop took care of explicit specification. Now use
2787 # the configured priorities to attempt implicit translations.
2789 for p in ${its_priority}; do
2790 print "/^${itsinfo[${p}_REGEX]}[ ]/ {
2791 s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2792 }" >> ${its_sed_script}
2793 done
2797 # Search for DO_EVERYTHING above for matching "for" statement
2798 # and explanation of this terminator.
2800 done
2803 # Output directory.
2805 WDIR=${WDIR:-$CWS/webrev}
2808 # Name of the webrev, derived from the workspace name or output directory;
2809 # in the future this could potentially be an option.
2811 if [[ -n $oflag ]]; then
2812 WNAME=${WDIR##*/}
2813 else
2814 WNAME=${CWS##*/}
2817 # Make sure remote target is well formed for remote upload/delete.
2818 if [[ -n $Dflag || -n $Uflag ]]; then
2820 # If remote target is not specified, build it from scratch using
2821 # the default values.
2823 if [[ -z $tflag ]]; then
2824 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2825 else
2827 # Check upload target prefix first.
2829 if [[ "${remote_target}" != ${rsync_prefix}* &&
2830 "${remote_target}" != ${ssh_prefix}* ]]; then
2831 print "ERROR: invalid prefix of upload URI" \
2832 "($remote_target)"
2833 exit 1
2836 # If destination specification is not in the form of
2837 # host_spec:remote_dir then assume it is just remote hostname
2838 # and append a colon and destination directory formed from
2839 # local webrev directory name.
2841 typeset target_no_prefix=${remote_target##*://}
2842 if [[ ${target_no_prefix} == *:* ]]; then
2843 if [[ "${remote_target}" == *: ]]; then
2844 remote_target=${remote_target}${WNAME}
2846 else
2847 if [[ ${target_no_prefix} == */* ]]; then
2848 print "ERROR: badly formed upload URI" \
2849 "($remote_target)"
2850 exit 1
2851 else
2852 remote_target=${remote_target}:${WNAME}
2858 # Strip trailing slash. Each upload method will deal with directory
2859 # specification separately.
2861 remote_target=${remote_target%/}
2865 # Option -D by itself (option -U not present) implies no webrev generation.
2867 if [[ -z $Uflag && -n $Dflag ]]; then
2868 delete_webrev 1 1
2869 exit $?
2873 # Do not generate the webrev, just upload it or delete it.
2875 if [[ -n $nflag ]]; then
2876 if [[ -n $Dflag ]]; then
2877 delete_webrev 1 1
2878 (( $? == 0 )) || exit $?
2880 if [[ -n $Uflag ]]; then
2881 upload_webrev
2882 exit $?
2886 if [ "${WDIR%%/*}" ]; then
2887 WDIR=$PWD/$WDIR
2890 if [[ ! -d $WDIR ]]; then
2891 mkdir -p $WDIR
2892 (( $? != 0 )) && exit 1
2896 # Summarize what we're going to do.
2898 if [[ -n $CWS_REV ]]; then
2899 print " Workspace: $CWS (at $CWS_REV)"
2900 else
2901 print " Workspace: $CWS"
2903 if [[ -n $parent_webrev ]]; then
2904 print "Compare against: webrev at $parent_webrev"
2905 else
2906 if [[ -n $HG_PARENT ]]; then
2907 hg_parent_short=`echo $HG_PARENT \
2908 | $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'`
2909 print "Compare against: $PWS (at $hg_parent_short)"
2910 else
2911 print "Compare against: $PWS"
2915 [[ -n $INCLUDE_FILE ]] && print " Including: $INCLUDE_FILE"
2916 print " Output to: $WDIR"
2919 # Save the file list in the webrev dir
2921 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
2923 rm -f $WDIR/$WNAME.patch
2924 rm -f $WDIR/$WNAME.ps
2925 rm -f $WDIR/$WNAME.pdf
2927 touch $WDIR/$WNAME.patch
2929 print " Output Files:"
2932 # Clean up the file list: Remove comments, blank lines and env variables.
2934 $SED -e "s/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean
2935 FLIST=/tmp/$$.flist.clean
2938 # For Mercurial, create a cache of manifest entries.
2940 if [[ $SCM_MODE == "mercurial" ]]; then
2942 # Transform the FLIST into a temporary sed script that matches
2943 # relevant entries in the Mercurial manifest as follows:
2944 # 1) The script will be used against the parent revision manifest,
2945 # so for FLIST lines that have two filenames (a renamed file)
2946 # keep only the old name.
2947 # 2) Escape all forward slashes the filename.
2948 # 3) Change the filename into another sed command that matches
2949 # that file in "hg manifest -v" output: start of line, three
2950 # octal digits for file permissions, space, a file type flag
2951 # character, space, the filename, end of line.
2952 # 4) Eliminate any duplicate entries. (This can occur if a
2953 # file has been used as the source of an hg cp and it's
2954 # also been modified in the same changeset.)
2956 SEDFILE=/tmp/$$.manifest.sed
2957 $SED '
2958 s#^[^ ]* ##
2959 s#/#\\\/#g
2960 s#^.*$#/^... . &$/p#
2961 ' < $FLIST | $SORT -u > $SEDFILE
2964 # Apply the generated script to the output of "hg manifest -v"
2965 # to get the relevant subset for this webrev.
2967 HG_PARENT_MANIFEST=/tmp/$$.manifest
2968 hg -R $CWS manifest -v -r $HG_PARENT |
2969 $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
2973 # First pass through the files: generate the per-file webrev HTML-files.
2975 cat $FLIST | while read LINE
2977 set - $LINE
2978 P=$1
2981 # Normally, each line in the file list is just a pathname of a
2982 # file that has been modified or created in the child. A file
2983 # that is renamed in the child workspace has two names on the
2984 # line: new name followed by the old name.
2986 oldname=""
2987 oldpath=""
2988 rename=
2989 if [[ $# -eq 2 ]]; then
2990 PP=$2 # old filename
2991 if [[ -f $PP ]]; then
2992 oldname=" (copied from $PP)"
2993 else
2994 oldname=" (renamed from $PP)"
2996 oldpath="$PP"
2997 rename=1
2998 PDIR=${PP%/*}
2999 if [[ $PDIR == $PP ]]; then
3000 PDIR="." # File at root of workspace
3003 PF=${PP##*/}
3005 DIR=${P%/*}
3006 if [[ $DIR == $P ]]; then
3007 DIR="." # File at root of workspace
3010 F=${P##*/}
3012 else
3013 DIR=${P%/*}
3014 if [[ "$DIR" == "$P" ]]; then
3015 DIR="." # File at root of workspace
3018 F=${P##*/}
3020 PP=$P
3021 PDIR=$DIR
3022 PF=$F
3025 COMM=`getcomments html $P $PP`
3027 print "\t$P$oldname\n\t\t\c"
3029 # Make the webrev mirror directory if necessary
3030 mkdir -p $WDIR/$DIR
3033 # We stash old and new files into parallel directories in $WDIR
3034 # and do our diffs there. This makes it possible to generate
3035 # clean looking diffs which don't have absolute paths present.
3038 build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3039 continue
3042 # Keep the old PWD around, so we can safely switch back after
3043 # diff generation, such that build_old_new runs in a
3044 # consistent environment.
3046 OWD=$PWD
3047 cd $WDIR/raw_files
3048 ofile=old/$PDIR/$PF
3049 nfile=new/$DIR/$F
3051 mv_but_nodiff=
3052 cmp $ofile $nfile > /dev/null 2>&1
3053 if [[ $? == 0 && $rename == 1 ]]; then
3054 mv_but_nodiff=1
3058 # If we have old and new versions of the file then run the appropriate
3059 # diffs. This is complicated by a couple of factors:
3061 # - renames must be handled specially: we emit a 'remove'
3062 # diff and an 'add' diff
3063 # - new files and deleted files must be handled specially
3064 # - Solaris patch(1m) can't cope with file creation
3065 # (and hence renames) as of this writing.
3066 # - To make matters worse, gnu patch doesn't interpret the
3067 # output of Solaris diff properly when it comes to
3068 # adds and deletes. We need to do some "cleansing"
3069 # transformations:
3070 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@
3071 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@
3073 cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3074 cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3076 rm -f $WDIR/$DIR/$F.patch
3077 if [[ -z $rename ]]; then
3078 if [ ! -f "$ofile" ]; then
3079 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3080 > $WDIR/$DIR/$F.patch
3081 elif [ ! -f "$nfile" ]; then
3082 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3083 > $WDIR/$DIR/$F.patch
3084 else
3085 diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3087 else
3088 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3089 > $WDIR/$DIR/$F.patch
3091 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3092 >> $WDIR/$DIR/$F.patch
3096 # Tack the patch we just made onto the accumulated patch for the
3097 # whole wad.
3099 cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3101 print " patch\c"
3103 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3105 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3106 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3107 > $WDIR/$DIR/$F.cdiff.html
3108 print " cdiffs\c"
3110 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3111 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3112 > $WDIR/$DIR/$F.udiff.html
3114 print " udiffs\c"
3116 if [[ -x $WDIFF ]]; then
3117 $WDIFF -c "$COMM" \
3118 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3119 $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3120 if [[ $? -eq 0 ]]; then
3121 print " wdiffs\c"
3122 else
3123 print " wdiffs[fail]\c"
3127 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3128 > $WDIR/$DIR/$F.sdiff.html
3129 print " sdiffs\c"
3131 print " frames\c"
3133 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3135 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3137 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3138 # renamed file: may also have differences
3139 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3140 elif [[ -f $nfile ]]; then
3141 # new file: count added lines
3142 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3143 elif [[ -f $ofile ]]; then
3144 # old file: count deleted lines
3145 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3149 # Now we generate the postscript for this file. We generate diffs
3150 # only in the event that there is delta, or the file is new (it seems
3151 # tree-killing to print out the contents of deleted files).
3153 if [[ -f $nfile ]]; then
3154 ocr=$ofile
3155 [[ ! -f $ofile ]] && ocr=/dev/null
3157 if [[ -z $mv_but_nodiff ]]; then
3158 textcomm=`getcomments text $P $PP`
3159 if [[ -x $CODEREVIEW ]]; then
3160 $CODEREVIEW -y "$textcomm" \
3161 -e $ocr $nfile \
3162 > /tmp/$$.psfile 2>/dev/null &&
3163 cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3164 if [[ $? -eq 0 ]]; then
3165 print " ps\c"
3166 else
3167 print " ps[fail]\c"
3173 if [[ -f $ofile ]]; then
3174 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3175 print " old\c"
3178 if [[ -f $nfile ]]; then
3179 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3180 print " new\c"
3183 cd $OWD
3185 print
3186 done
3188 frame_nav_js > $WDIR/ancnav.js
3189 frame_navigation > $WDIR/ancnav.html
3191 if [[ ! -f $WDIR/$WNAME.ps ]]; then
3192 print " Generating PDF: Skipped: no output available"
3193 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3194 print " Generating PDF: \c"
3195 fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3196 print "Done."
3197 else
3198 print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3201 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3202 # delete it - prevent accidental publishing of closed source
3204 if [[ -n "$Oflag" ]]; then
3205 $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3208 # Now build the index.html file that contains
3209 # links to the source files and their diffs.
3211 cd $CWS
3213 # Save total changed lines for Code Inspection.
3214 print "$TOTL" > $WDIR/TotalChangedLines
3216 print " index.html: \c"
3217 INDEXFILE=$WDIR/index.html
3218 exec 3<&1 # duplicate stdout to FD3.
3219 exec 1<&- # Close stdout.
3220 exec > $INDEXFILE # Open stdout to index file.
3222 print "$HTML<head>$STDHEAD"
3223 print "<title>$WNAME</title>"
3224 print "</head>"
3225 print "<body id=\"SUNWwebrev\">"
3226 print "<div class=\"summary\">"
3227 print "<h2>Code Review for $WNAME</h2>"
3229 print "<table>"
3232 # Get the preparer's name:
3234 # If the SCM detected is Mercurial, and the configuration property
3235 # ui.username is available, use that, but be careful to properly escape
3236 # angle brackets (HTML syntax characters) in the email address.
3238 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3239 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3241 preparer=
3242 if [[ "$SCM_MODE" == mercurial ]]; then
3243 preparer=`hg showconfig ui.username 2>/dev/null`
3244 if [[ -n "$preparer" ]]; then
3245 preparer="$(echo "$preparer" | html_quote)"
3248 if [[ -z "$preparer" ]]; then
3249 preparer=$(
3250 $PERL -e '
3251 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3252 if ($login) {
3253 $gcos =~ s/\&/ucfirst($login)/e;
3254 printf "%s (%s)\n", $gcos, $login;
3255 } else {
3256 printf "(unknown)\n";
3261 PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3262 print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3263 print "<tr><th>Workspace:</th><td>$CWS"
3264 if [[ -n $CWS_REV ]]; then
3265 print "(at $CWS_REV)"
3267 print "</td></tr>"
3268 print "<tr><th>Compare against:</th><td>"
3269 if [[ -n $parent_webrev ]]; then
3270 print "webrev at $parent_webrev"
3271 else
3272 print "$PWS"
3273 if [[ -n $hg_parent_short ]]; then
3274 print "(at $hg_parent_short)"
3277 print "</td></tr>"
3278 print "<tr><th>Summary of changes:</th><td>"
3279 printCI $TOTL $TINS $TDEL $TMOD $TUNC
3280 print "</td></tr>"
3282 if [[ -f $WDIR/$WNAME.patch ]]; then
3283 wpatch_url="$(print $WNAME.patch | url_encode)"
3284 print "<tr><th>Patch of changes:</th><td>"
3285 print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3287 if [[ -f $WDIR/$WNAME.pdf ]]; then
3288 wpdf_url="$(print $WNAME.pdf | url_encode)"
3289 print "<tr><th>Printable review:</th><td>"
3290 print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3293 if [[ -n "$iflag" ]]; then
3294 print "<tr><th>Author comments:</th><td><div>"
3295 cat /tmp/$$.include
3296 print "</div></td></tr>"
3298 print "</table>"
3299 print "</div>"
3302 # Second pass through the files: generate the rest of the index file
3304 cat $FLIST | while read LINE
3306 set - $LINE
3307 P=$1
3309 if [[ $# == 2 ]]; then
3310 PP=$2
3311 oldname="$PP"
3312 else
3313 PP=$P
3314 oldname=""
3317 mv_but_nodiff=
3318 cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3319 if [[ $? == 0 && -n "$oldname" ]]; then
3320 mv_but_nodiff=1
3323 DIR=${P%/*}
3324 if [[ $DIR == $P ]]; then
3325 DIR="." # File at root of workspace
3328 # Avoid processing the same file twice.
3329 # It's possible for renamed files to
3330 # appear twice in the file list
3332 F=$WDIR/$P
3334 print "<p>"
3336 # If there's a diffs file, make diffs links
3338 if [[ -f $F.cdiff.html ]]; then
3339 cdiff_url="$(print $P.cdiff.html | url_encode)"
3340 udiff_url="$(print $P.udiff.html | url_encode)"
3341 print "<a href=\"$cdiff_url\">Cdiffs</a>"
3342 print "<a href=\"$udiff_url\">Udiffs</a>"
3344 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3345 wdiff_url="$(print $P.wdiff.html | url_encode)"
3346 print "<a href=\"$wdiff_url\">Wdiffs</a>"
3349 sdiff_url="$(print $P.sdiff.html | url_encode)"
3350 print "<a href=\"$sdiff_url\">Sdiffs</a>"
3352 frames_url="$(print $P.frames.html | url_encode)"
3353 print "<a href=\"$frames_url\">Frames</a>"
3354 else
3355 print " ------ ------ ------"
3357 if [[ -x $WDIFF ]]; then
3358 print " ------"
3361 print " ------"
3364 # If there's an old file, make the link
3366 if [[ -f $F-.html ]]; then
3367 oldfile_url="$(print $P-.html | url_encode)"
3368 print "<a href=\"$oldfile_url\">Old</a>"
3369 else
3370 print " ---"
3373 # If there's an new file, make the link
3375 if [[ -f $F.html ]]; then
3376 newfile_url="$(print $P.html | url_encode)"
3377 print "<a href=\"$newfile_url\">New</a>"
3378 else
3379 print " ---"
3382 if [[ -f $F.patch ]]; then
3383 patch_url="$(print $P.patch | url_encode)"
3384 print "<a href=\"$patch_url\">Patch</a>"
3385 else
3386 print " -----"
3389 if [[ -f $WDIR/raw_files/new/$P ]]; then
3390 rawfiles_url="$(print raw_files/new/$P | url_encode)"
3391 print "<a href=\"$rawfiles_url\">Raw</a>"
3392 else
3393 print " ---"
3396 print "<b>$P</b>"
3398 # For renamed files, clearly state whether or not they are modified
3399 if [[ -f "$oldname" ]]; then
3400 if [[ -n "$mv_but_nodiff" ]]; then
3401 print "<i>(copied from $oldname)</i>"
3402 else
3403 print "<i>(copied and modified from $oldname)</i>"
3405 elif [[ -n "$oldname" ]]; then
3406 if [[ -n "$mv_but_nodiff" ]]; then
3407 print "<i>(renamed from $oldname)</i>"
3408 else
3409 print "<i>(renamed and modified from $oldname)</i>"
3413 # If there's an old file, but no new file, the file was deleted
3414 if [[ -f $F-.html && ! -f $F.html ]]; then
3415 print " <i>(deleted)</i>"
3419 # Check for usr/closed and deleted_files/usr/closed
3421 if [ ! -z "$Oflag" ]; then
3422 if [[ $P == usr/closed/* || \
3423 $P == deleted_files/usr/closed/* ]]; then
3424 print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3425 "this review</i>"
3429 print "</p>"
3430 # Insert delta comments
3432 print "<blockquote><pre>"
3433 getcomments html $P $PP
3434 print "</pre>"
3436 # Add additional comments comment
3438 print "<!-- Add comments to explain changes in $P here -->"
3440 # Add count of changes.
3442 if [[ -f $F.count ]]; then
3443 cat $F.count
3444 rm $F.count
3447 if [[ $SCM_MODE == "teamware" ||
3448 $SCM_MODE == "mercurial" ||
3449 $SCM_MODE == "unknown" ]]; then
3451 # Include warnings for important file mode situations:
3452 # 1) New executable files
3453 # 2) Permission changes of any kind
3454 # 3) Existing executable files
3456 old_mode=
3457 if [[ -f $WDIR/raw_files/old/$PP ]]; then
3458 old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3461 new_mode=
3462 if [[ -f $WDIR/raw_files/new/$P ]]; then
3463 new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3466 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3467 print "<span class=\"chmod\">"
3468 print "<p>new executable file: mode $new_mode</p>"
3469 print "</span>"
3470 elif [[ -n "$old_mode" && -n "$new_mode" &&
3471 "$old_mode" != "$new_mode" ]]; then
3472 print "<span class=\"chmod\">"
3473 print "<p>mode change: $old_mode to $new_mode</p>"
3474 print "</span>"
3475 elif [[ "$new_mode" = *[1357]* ]]; then
3476 print "<span class=\"chmod\">"
3477 print "<p>executable file: mode $new_mode</p>"
3478 print "</span>"
3482 print "</blockquote>"
3483 done
3485 print
3486 print
3487 print "<hr></hr>"
3488 print "<p style=\"font-size: small\">"
3489 print "This code review page was prepared using <b>$0</b>."
3490 print "Webrev is maintained by the <a href=\"http://www.opensolaris.org\">"
3491 print "OpenSolaris</a> project. The latest version may be obtained"
3492 print "<a href=\"http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3493 print "</body>"
3494 print "</html>"
3496 exec 1<&- # Close FD 1.
3497 exec 1<&3 # dup FD 3 to restore stdout.
3498 exec 3<&- # close FD 3.
3500 print "Done."
3503 # If remote deletion was specified and fails do not continue.
3505 if [[ -n $Dflag ]]; then
3506 delete_webrev 1 1
3507 (( $? == 0 )) || exit $?
3510 if [[ -n $Uflag ]]; then
3511 upload_webrev
3512 exit $?