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]
24 # Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
25 # Copyright 2008, 2010, Richard Lowe
26 # Copyright 2012 Marcel Telka <marcel@telka.sk>
27 # Copyright 2014 Bart Coddens <bart.coddens@gmail.com>
28 # Copyright 2016 Nexenta Systems, Inc.
32 # This script takes a file list and a workspace and builds a set of html files
33 # suitable for doing a code review of source changes via a web page.
34 # Documentation is available via the manual page, webrev.1, or just
37 # Acknowledgements to contributors to webrev are listed in the webrev(1)
45 HTML
='<?xml version="1.0"?>
46 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
47 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
48 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
50 FRAMEHTML
='<?xml version="1.0"?>
51 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
52 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
53 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
55 STDHEAD
='<meta http-equiv="cache-control" content="no-cache"></meta>
56 <meta http-equiv="Content-Type" content="text/xhtml;charset=utf-8"></meta>
57 <meta http-equiv="Pragma" content="no-cache"></meta>
58 <meta http-equiv="Expires" content="-1"></meta>
60 Note to customizers: the body of the webrev is IDed as SUNWwebrev
61 to allow easy overriding by users of webrev via the userContent.css
62 mechanism available in some browsers.
64 For example, to have all "removed" information be red instead of
65 brown, set a rule in your userContent.css file like:
67 body#SUNWwebrev span.removed { color: red ! important; }
69 <style type="text/css" media="screen">
71 background-color: #eeeeee;
75 border-top: 1px solid #aaa;
80 border-bottom: 1px solid #aaa;
87 div.summary table th {
119 a.print { font-size: x-small; }
120 a:hover { background-color: #ffcc99; }
123 <style type="text/css" media="print">
124 pre { font-size: 0.8em; font-family: courier, monospace; }
125 span.removed { color: #444; font-style: italic }
126 span.changed { font-weight: bold; }
127 span.new { font-weight: bold; }
128 span.newmarker { font-size: 1.2em; font-weight: bold; }
129 span.oldmarker { font-size: 1.2em; font-weight: bold; }
130 a.print {display: none}
131 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
136 # UDiffs need a slightly different CSS rule for 'new' items (we don't
137 # want them to be bolded as we do in cdiffs or sdiffs).
140 <style type="text/css" media="screen">
149 # CSS for the HTML version of the man pages.
152 html { max-width: 880px; margin-left: 1em; }
153 body { font-size: smaller; font-family: Helvetica,Arial,sans-serif; }
154 h1 { margin-bottom: 1ex; font-size: 110%; margin-left: -4ex; }
155 h2 { margin-bottom: 1ex; font-size: 105%; margin-left: -2ex; }
156 table { width: 100%; margin-top: 0ex; margin-bottom: 0ex; }
157 td { vertical-align: top; }
158 blockquote { margin-left: 5ex; margin-top: 0ex; margin-bottom: 0ex; }
159 div.section { margin-bottom: 2ex; margin-left: 5ex; }
160 table.foot { font-size: smaller; margin-top: 1em;
161 border-top: 1px dotted #dddddd; }
162 td.foot-date { width: 50%; }
163 td.foot-os { width: 50%; text-align: right; }
164 table.head { font-size: smaller; margin-bottom: 1em;
165 border-bottom: 1px dotted #dddddd; }
166 td.head-ltitle { width: 10%; }
167 td.head-vol { width: 80%; text-align: center; }
168 td.head-rtitle { width: 10%; text-align: right; }
169 .emph { font-style: italic; font-weight: normal; }
170 .symb { font-style: normal; font-weight: bold; }
171 .lit { font-style: normal; font-weight: normal; font-family: monospace; }
172 i.addr { font-weight: normal; }
173 i.arg { font-weight: normal; }
174 b.cmd { font-style: normal; }
175 b.config { font-style: normal; }
176 b.diag { font-style: normal; }
177 i.farg { font-weight: normal; }
178 i.file { font-weight: normal; }
179 b.flag { font-style: normal; }
180 b.fname { font-style: normal; }
181 i.ftype { font-weight: normal; }
182 b.includes { font-style: normal; }
183 i.link-sec { font-weight: normal; }
184 b.macro { font-style: normal; }
185 b.name { font-style: normal; }
186 i.ref-book { font-weight: normal; }
187 i.ref-issue { font-weight: normal; }
188 i.ref-jrnl { font-weight: normal; }
189 span.ref-title { text-decoration: underline; }
190 span.type { font-style: italic; font-weight: normal; }
191 b.utility { font-style: normal; }
192 b.var { font-style: normal; }
193 dd.list-ohang { margin-left: 0ex; }
194 ul.list-bul { list-style-type: disc; padding-left: 1em; }
195 ul.list-dash { list-style-type: none; padding-left: 0em; }
196 li.list-dash:before { content: "\2014 "; }
197 ul.list-hyph { list-style-type: none; padding-left: 0em; }
198 li.list-hyph:before { content: "\2013 "; }
199 ul.list-item { list-style-type: none; padding-left: 0em; }
200 ol.list-enum { padding-left: 2em; }
204 # Display remote target with prefix and trailing slash.
206 function print_upload_header
209 typeset display_target
211 if [[ -z $tflag ]]; then
212 display_target
=${prefix}${remote_target}
214 display_target
=${remote_target}
217 if [[ ${display_target} != */ ]]; then
218 display_target
=${display_target}/
221 print
" Upload to: ${display_target}\n" \
226 # Upload the webrev via rsync. Return 0 on success, 1 on error.
228 function rsync_upload
230 if (( $# != 2 )); then
231 print
"\nERROR: rsync_upload: wrong usage ($#)"
236 integer
-r print_err_msg
=$2
238 print_upload_header
${rsync_prefix}
240 typeset
-r err_msg
=$
( $MKTEMP /tmp
/rsync_err.XXXXXX
)
241 if [[ -z $err_msg ]]; then
242 print
"\nERROR: rsync_upload: cannot create temporary file"
246 # The source directory must end with a slash in order to copy just
247 # directory contents, not the whole directory.
249 typeset src_dir
=$WDIR
250 if [[ ${src_dir} != */ ]]; then
253 $RSYNC -r -q ${src_dir} $dst 2>$err_msg
254 if (( $?
!= 0 )); then
255 if (( ${print_err_msg} > 0 )); then
256 print
"Failed.\nERROR: rsync failed"
257 print
"src dir: '${src_dir}'\ndst dir: '$dst'"
258 print
"error messages:"
259 $SED 's/^/> /' $err_msg
271 # Create directories on remote host using SFTP. Return 0 on success,
274 function remote_mkdirs
276 typeset
-r dir_spec
=$1
277 typeset
-r host_spec
=$2
280 # If the supplied path is absolute we assume all directories are
281 # created, otherwise try to create all directories in the path
282 # except the last one which will be created by scp.
284 if [[ "${dir_spec}" == */* && "${dir_spec}" != /* ]]; then
287 # Remove the last directory from directory specification.
289 typeset
-r dirs_mk
=${dir_spec%/*}
290 typeset
-r batch_file_mkdir
=$
( $MKTEMP \
291 /tmp
/webrev_mkdir.XXXXXX
)
292 if [[ -z $batch_file_mkdir ]]; then
293 print
"\nERROR: remote_mkdirs:" \
294 "cannot create temporary file for batch file"
300 for dir
in ${dirs_mk}; do
302 # Use the '-' prefix to ignore mkdir errors in order
303 # to avoid an error in case the directory already
304 # exists. We check the directory with chdir to be sure
307 print
-- "-mkdir ${dir}" >> ${batch_file_mkdir}
308 print
"chdir ${dir}" >> ${batch_file_mkdir}
311 typeset
-r sftp_err_msg
=$
( $MKTEMP /tmp
/webrev_scp_err.XXXXXX
)
312 if [[ -z ${sftp_err_msg} ]]; then
313 print
"\nERROR: remote_mkdirs:" \
314 "cannot create temporary file for error messages"
317 $SFTP -b ${batch_file_mkdir} ${host_spec} 2>${sftp_err_msg} 1>&2
318 if (( $?
!= 0 )); then
319 print
"\nERROR: failed to create remote directories"
320 print
"error messages:"
321 $SED 's/^/> /' ${sftp_err_msg}
322 rm -f ${sftp_err_msg} ${batch_file_mkdir}
325 rm -f ${sftp_err_msg} ${batch_file_mkdir}
332 # Upload the webrev via SSH. Return 0 on success, 1 on error.
336 if (( $# != 1 )); then
337 print
"\nERROR: ssh_upload: wrong number of arguments"
342 typeset
-r host_spec
=${dst%%:*}
343 typeset
-r dir_spec
=${dst#*:}
346 # Display the upload information before calling delete_webrev
347 # because it will also print its progress.
349 print_upload_header
${ssh_prefix}
352 # If the deletion was explicitly requested there is no need
353 # to perform it again.
355 if [[ -z $Dflag ]]; then
357 # We do not care about return value because this might be
358 # the first time this directory is uploaded.
364 # Create remote directories. Any error reporting will be done
365 # in remote_mkdirs function.
367 remote_mkdirs
${dir_spec} ${host_spec}
368 if (( $?
!= 0 )); then
372 print
"upload ... \c"
373 typeset
-r scp_err_msg
=$
( $MKTEMP /tmp
/scp_err.XXXXXX
)
374 if [[ -z ${scp_err_msg} ]]; then
375 print
"\nERROR: ssh_upload:" \
376 "cannot create temporary file for error messages"
379 $SCP -q -C -B -o PreferredAuthentications
=publickey
-r \
380 $WDIR $dst 2>${scp_err_msg}
381 if (( $?
!= 0 )); then
382 print
"Failed.\nERROR: scp failed"
383 print
"src dir: '$WDIR'\ndst dir: '$dst'"
384 print
"error messages:"
385 $SED 's/^/> /' ${scp_err_msg}
396 # Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp
397 # on failure. If first argument is 1 then perform the check of sftp return
398 # value otherwise ignore it. If second argument is present it means this run
399 # only performs deletion.
401 function delete_webrev
403 if (( $# < 1 )); then
404 print
"delete_webrev: wrong number of arguments"
409 integer delete_only
=0
410 if (( $# == 2 )); then
415 # Strip the transport specification part of remote target first.
417 typeset
-r stripped_target
=${remote_target##*://}
418 typeset
-r host_spec
=${stripped_target%%:*}
419 typeset
-r dir_spec
=${stripped_target#*:}
423 # Do not accept an absolute path.
425 if [[ ${dir_spec} == /* ]]; then
430 # Strip the ending slash.
432 if [[ ${dir_spec} == */ ]]; then
433 dir_rm
=${dir_spec%%/}
438 if (( ${delete_only} > 0 )); then
439 print
" Removing: \c"
443 if [[ -z "$dir_rm" ]]; then
444 print
"\nERROR: empty directory for removal"
449 # Prepare batch file.
451 typeset
-r batch_file_rm
=$
( $MKTEMP /tmp
/webrev_remove.XXXXXX
)
452 if [[ -z $batch_file_rm ]]; then
453 print
"\nERROR: delete_webrev: cannot create temporary file"
456 print
"rename $dir_rm $TRASH_DIR/removed.$$" > $batch_file_rm
459 # Perform remote deletion and remove the batch file.
461 typeset
-r sftp_err_msg
=$
( $MKTEMP /tmp
/webrev_scp_err.XXXXXX
)
462 if [[ -z ${sftp_err_msg} ]]; then
463 print
"\nERROR: delete_webrev:" \
464 "cannot create temporary file for error messages"
467 $SFTP -b $batch_file_rm $host_spec 2>${sftp_err_msg} 1>&2
470 if (( $ret != 0 && $check > 0 )); then
471 print
"Failed.\nERROR: failed to remove remote directories"
472 print
"error messages:"
473 $SED 's/^/> /' ${sftp_err_msg}
474 rm -f ${sftp_err_msg}
477 rm -f ${sftp_err_msg}
478 if (( ${delete_only} > 0 )); then
486 # Upload webrev to remote site
488 function upload_webrev
492 if [[ ! -d "$WDIR" ]]; then
493 print
"\nERROR: webrev directory '$WDIR' does not exist"
498 # Perform a late check to make sure we do not upload closed source
499 # to remote target when -n is used. If the user used custom remote
500 # target he probably knows what he is doing.
502 if [[ -n $nflag && -z $tflag ]]; then
503 $FIND $WDIR -type d
-name closed \
504 |
$GREP closed
>/dev
/null
505 if (( $?
== 0 )); then
506 print
"\nERROR: directory '$WDIR' contains" \
507 "\"closed\" directory"
514 # We have the URI for remote destination now so let's start the upload.
516 if [[ -n $tflag ]]; then
517 if [[ "${remote_target}" == ${rsync_prefix}?
* ]]; then
518 rsync_upload
${remote_target##$rsync_prefix} 1
521 elif [[ "${remote_target}" == ${ssh_prefix}?
* ]]; then
522 ssh_upload
${remote_target##$ssh_prefix}
528 # Try rsync first and fallback to SSH in case it fails.
530 rsync_upload
${remote_target} 0
532 if (( $ret != 0 )); then
533 print
"Failed. (falling back to SSH)"
534 ssh_upload
${remote_target}
542 # input_cmd | url_encode | output_cmd
544 # URL-encode (percent-encode) reserved characters as defined in RFC 3986.
546 # Reserved characters are: :/?#[]@!$&'()*+,;=
548 # While not a reserved character itself, percent '%' is reserved by definition
549 # so encode it first to avoid recursive transformation, and skip '/' which is
552 # The quotation character is deliberately not escaped in order to make
553 # the substitution work with GNU sed.
557 $SED -e "s|%|%25|g" -e "s|:|%3A|g" -e "s|\&|%26|g" \
558 -e "s|?|%3F|g" -e "s|#|%23|g" -e "s|\[|%5B|g" \
559 -e "s|*|%2A|g" -e "s|@|%40|g" -e "s|\!|%21|g" \
560 -e "s|=|%3D|g" -e "s|;|%3B|g" -e "s|\]|%5D|g" \
561 -e "s|(|%28|g" -e "s|)|%29|g" -e "s|'|%27|g" \
562 -e "s|+|%2B|g" -e "s|\,|%2C|g" -e "s|\\\$|%24|g"
566 # input_cmd | html_quote | output_cmd
568 # html_quote filename | output_cmd
570 # Make a piece of source code safe for display in an HTML <pre> block.
574 $SED -e "s/&/\&/g" -e "s/</\</g" -e "s/>/\>/g" "$@" |
expand
578 # Trim a digest-style revision to a conventionally readable yet useful length
584 echo $digest |
$SED -e 's/\([0-9a-f]\{12\}\).*/\1/'
588 # input_cmd | its2url | output_cmd
590 # Scan for information tracking system references and insert <a> links to the
591 # relevant databases.
595 $SED -f ${its_sed_script}
599 # strip_unchanged <infile> | output_cmd
601 # Removes chunks of sdiff documents that have not changed. This makes it
602 # easier for a code reviewer to find the bits that have changed.
604 # Deleted lines of text are replaced by a horizontal rule. Some
605 # identical lines are retained before and after the changed lines to
606 # provide some context. The number of these lines is controlled by the
607 # variable C in the $AWK script below.
609 # The script detects changed lines as any line that has a "<span class="
610 # string embedded (unchanged lines have no particular class and are not
611 # part of a <span>). Blank lines (without a sequence number) are also
612 # detected since they flag lines that have been inserted or deleted.
618 NF == 0 || /<span class="/ {
623 print "\n</pre><hr></hr><pre>"
628 for (i = 0; i < c; i++)
629 print ln[(inx + i) % C]
643 END { if (c > (C * 2)) print "\n</pre><hr></hr>" }
651 # This function takes two files as arguments, obtains their diff, and
652 # processes the diff output to present the files as an HTML document with
653 # the files displayed side-by-side, differences shown in color. It also
654 # takes a delta comment, rendered as an HTML snippet, as the third
655 # argument. The function takes two files as arguments, then the name of
656 # file, the path, and the comment. The HTML will be delivered on stdout,
659 # $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
660 # new/usr/src/tools/scripts/webrev.sh \
661 # webrev.sh usr/src/tools/scripts \
662 # '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
663 # 1234567</a> my bugid' > <file>.html
665 # framed_sdiff() is then called which creates $2.frames.html
666 # in the webrev tree.
668 # FYI: This function is rather unusual in its use of awk. The initial
669 # diff run produces conventional diff output showing changed lines mixed
670 # with editing codes. The changed lines are ignored - we're interested in
671 # the editing codes, e.g.
680 # These editing codes are parsed by the awk script and used to generate
681 # another awk script that generates HTML, e.g the above lines would turn
682 # into something like this:
684 # BEGIN { printf "<pre>\n" }
685 # function sp(n) {for (i=0;i<n;i++)printf "\n"}
686 # function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
687 # NR==8 {wl("#7A7ADD");next}
688 # NR==54 {wl("#7A7ADD");sp(3);next}
689 # NR==56 {wl("#7A7ADD");next}
690 # NR==57 {wl("black");printf "\n"; next}
693 # This script is then run on the original source file to generate the
694 # HTML that corresponds to the source file.
696 # The two HTML files are then combined into a single piece of HTML that
697 # uses an HTML table construct to present the files side by side. You'll
698 # notice that the changes are color-coded:
700 # black - unchanged lines
701 # blue - changed lines
702 # bold blue - new lines
703 # brown - deleted lines
705 # Blank lines are inserted in each file to keep unchanged lines in sync
706 # (side-by-side). This format is familiar to users of sdiff(1) or
707 # Teamware's filemerge tool.
711 diff -b $1 $2 > /tmp
/$$.diffs
718 # Now we have the diffs, generate the HTML for the old file.
722 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
723 printf "function removed() "
724 printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
725 printf "function changed() "
726 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
727 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
734 split($1, a, /[cad]/) ;
735 if (index($1, "a")) {
737 n = split(a[2], r, /,/);
739 printf "BEGIN\t\t{sp(1)}\n"
741 printf "BEGIN\t\t{sp(%d)}\n",\
746 printf "NR==%s\t\t{", a[1]
747 n = split(a[2], r, /,/);
750 printf "bl();printf \"\\n\"; next}\n"
753 printf "bl();sp(%d);next}\n",\
758 if (index($1, "d")) {
759 n = split(a[1], r, /,/);
763 printf "NR==%s\t\t{removed(); next}\n" , n1
765 printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
768 if (index($1, "c")) {
769 n = split(a[1], r, /,/);
775 printf "NR==%s\t\t{changed();" , n1
778 printf "NR==%s,NR==%s\t{changed();" , n1, n2
780 m = split(a[2], r, /,/);
786 if (n > 1) printf "if (NR==%d)", final
787 printf "sp(%d);", d2 - d1
796 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
797 ' /tmp
/$$.diffs
> /tmp
/$$.file1
800 # Now generate the HTML for the new file
804 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
805 printf "function new() "
806 printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
807 printf "function changed() "
808 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
809 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
817 split($1, a, /[cad]/) ;
818 if (index($1, "d")) {
820 n = split(a[1], r, /,/);
822 printf "BEGIN\t\t{sp(1)}\n"
824 printf "BEGIN\t\t{sp(%d)}\n",\
829 printf "NR==%s\t\t{", a[2]
830 n = split(a[1], r, /,/);
833 printf "bl();printf \"\\n\"; next}\n"
836 printf "bl();sp(%d);next}\n",\
841 if (index($1, "a")) {
842 n = split(a[2], r, /,/);
846 printf "NR==%s\t\t{new() ; next}\n" , n1
848 printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
851 if (index($1, "c")) {
852 n = split(a[2], r, /,/);
859 printf "NR==%s\t\t{changed();" , n1
862 printf "NR==%s,NR==%s\t{changed();" , n1, n2
864 m = split(a[1], r, /,/);
870 if (n > 1) printf "if (NR==%d)", final
871 printf "sp(%d);", d1 - d2
878 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
879 ' /tmp
/$$.diffs
> /tmp
/$$.file2
882 # Post-process the HTML files by running them back through $AWK
884 html_quote
< $1 |
$AWK -f /tmp
/$$.file1
> /tmp
/$$.file1.html
886 html_quote
< $2 |
$AWK -f /tmp
/$$.file2
> /tmp
/$$.file2.html
889 # Now combine into a valid HTML file and side-by-side into a table
891 print
"$HTML<head>$STDHEAD"
892 print
"<title>$WNAME Sdiff $TPATH/$TNAME</title>"
893 print
"</head><body id=\"SUNWwebrev\">"
894 print
"<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
895 print
"<pre>$COMMENT</pre>\n"
896 print
"<table><tr valign=\"top\">"
899 strip_unchanged
/tmp
/$$.file1.html
901 print
"</pre></td><td><pre>"
903 strip_unchanged
/tmp
/$$.file2.html
906 print
"</tr></table>"
907 print
"</body></html>"
909 framed_sdiff
$TNAME $TPATH /tmp
/$$.file1.html
/tmp
/$$.file2.html \
915 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
917 # Expects lefthand and righthand side html files created by sdiff_to_html.
918 # We use insert_anchors() to augment those with HTML navigation anchors,
919 # and then emit the main frame. Content is placed into:
921 # $WDIR/DIR/$TNAME.lhs.html
922 # $WDIR/DIR/$TNAME.rhs.html
923 # $WDIR/DIR/$TNAME.frames.html
925 # NOTE: We rely on standard usage of $WDIR and $DIR.
927 function framed_sdiff
936 # Enable html files to access WDIR via a relative path.
937 RTOP
=$
(relative_dir
$TPATH $WDIR)
939 # Make the rhs/lhs files and output the frameset file.
940 print
"$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
942 cat >> $WDIR/$DIR/$TNAME.lhs.html
<<-EOF
943 <script type="text/javascript" src="${RTOP}ancnav.js"></script>
945 <body id="SUNWwebrev" onkeypress="keypress(event);">
947 <pre>$comments</pre><hr></hr>
950 cp $WDIR/$DIR/$TNAME.lhs.html
$WDIR/$DIR/$TNAME.rhs.html
952 insert_anchors
$lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
953 insert_anchors
$rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
955 close
='</body></html>'
957 print
$close >> $WDIR/$DIR/$TNAME.lhs.html
958 print
$close >> $WDIR/$DIR/$TNAME.rhs.html
960 print
"$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
961 print
"<title>$WNAME Framed-Sdiff " \
962 "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
963 cat >> $WDIR/$DIR/$TNAME.frames.html
<<-EOF
964 <frameset rows="*,60">
965 <frameset cols="50%,50%">
966 <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
967 <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
969 <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0"
970 marginheight="0" name="nav"></frame>
972 <body id="SUNWwebrev">
973 Alas 'frames' webrev requires that your browser supports frames
974 and has the feature enabled.
986 # Merge codereview output files to a single conforming postscript file, by:
987 # - removing all extraneous headers/trailers
988 # - making the page numbers right
989 # - removing pages devoid of contents which confuse some
990 # postscript readers.
994 function fix_postscript
998 cat > /tmp
/$$.crmerge.pl
<< \EOF
1000 print scalar
(<>); # %!PS-Adobe---
1001 print
"%%Orientation: Landscape\n";
1009 next
if (/^
%%Pages
:\s
*\d
+/);
1012 if ($pno == 0 ||
$page =~
/\
)S
/) {
1013 # Header or single page containing text
1014 print
"%%Page: ? $pno\n" if ($pno > 0);
1018 # Empty page, skip it.
1025 # Skip from %%Trailer of one document to Endprolog
1026 # %%Page of the next
1027 $doprint = 0 if (/^
%%Trailer
/);
1028 $page .
= $_ if ($doprint);
1031 if ($page =~
/\
)S
/) {
1032 print
"%%Page: ? $pno\n";
1037 print
"%%Trailer\n%%Pages: $pno\n";
1040 $PERL /tmp
/$$.crmerge.pl
< $infile
1045 # input_cmd | insert_anchors | output_cmd
1047 # Flag blocks of difference with sequentially numbered invisible
1048 # anchors. These are used to drive the frames version of the
1051 # NOTE: Anchor zero flags the top of the file irrespective of changes,
1052 # an additional anchor is also appended to flag the bottom.
1054 # The script detects changed lines as any line that has a "<span
1055 # class=" string embedded (unchanged lines have no class set and are
1056 # not part of a <span>. Blank lines (without a sequence number)
1057 # are also detected since they flag lines that have been inserted or
1060 function insert_anchors
1064 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1072 NF == 0 || /^<span class=/ {
1087 printf "<b style=\"font-size: large; color: red\">";
1088 printf "--- EOF ---</b>"
1089 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1091 printf "<form name=\"eof\">";
1092 printf "<input name=\"value\" value=\"%d\" " \
1093 "type=\"hidden\"></input>", anc - 1;
1103 # Print a relative return path from $1 to $2. For example if
1104 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1105 # this function would print "../../../../".
1107 # In the event that $1 is not in $2 a warning is printed to stderr,
1108 # and $2 is returned-- the result of this is that the resulting webrev
1109 # is not relocatable.
1111 function relative_dir
1113 typeset cur
="${1##$2?(/)}"
1116 # If the first path was specified absolutely, and it does
1117 # not start with the second path, it's an error.
1119 if [[ "$cur" = "/${1#/}" ]]; then
1120 # Should never happen.
1121 print
-u2 "\nWARNING: relative_dir: \"$1\" not relative "
1122 print
-u2 "to \"$2\". Check input paths. Framed webrev "
1123 print
-u2 "will not be relocatable!"
1129 # This is kind of ugly. The sed script will do the following:
1131 # 1. Strip off a leading "." or "./": this is important to get
1132 # the correct arcnav links for files in $WDIR.
1133 # 2. Strip off a trailing "/": this is not strictly necessary,
1134 # but is kind of nice, since it doesn't end up in "//" at
1135 # the end of a relative path.
1136 # 3. Replace all remaining sequences of non-"/" with "..": the
1137 # assumption here is that each dirname represents another
1138 # level of relative separation.
1139 # 4. Append a trailing "/" only for non-empty paths: this way
1140 # the caller doesn't need to duplicate this logic, and does
1141 # not end up using $RTOP/file for files in $WDIR.
1143 print
$cur |
$SED -e '{
1154 # Emit javascript for frame navigation
1156 function frame_nav_js
1164 function scrollByPix
() {
1169 parent.lhs.scrollBy
(0,sfactor
);
1170 parent.rhs.scrollBy
(0,sfactor
);
1174 function scrollToAnc
(num
) {
1176 // Update the value of the anchor
in the form
which we use as
1177 // storage
for this value. setAncValue
() will take care of
1178 // correcting
for overflow and underflow of the value and
return
1179 // us the new value.
1180 num
= setAncValue
(num
);
1182 // Set location and scroll back a little to expose previous
1185 // Note that this could be improved
: it is possible although
1186 // complex to compute the x and y position of an anchor
, and to
1187 // scroll to that location directly.
1189 parent.lhs.location.replace
(parent.lhs.location.pathname
+ "#" + num
);
1190 parent.rhs.location.replace
(parent.rhs.location.pathname
+ "#" + num
);
1192 parent.lhs.scrollBy
(0,-30);
1193 parent.rhs.scrollBy
(0,-30);
1196 function getAncValue
()
1198 return (parseInt
(parent.nav.document.
diff.real.value
));
1201 function setAncValue
(val
)
1205 parent.nav.document.
diff.real.value
= val
;
1206 parent.nav.document.
diff.display.value
= "BOF";
1211 // The way we compute the max anchor value is to stash it
1212 // inline
in the left and right hand side pages-- it
's the same
1213 // on each side, so we pluck from the left.
1215 maxval = parent.lhs.document.eof.value.value;
1217 parent.nav.document.diff.real.value = val;
1218 parent.nav.document.diff.display.value = val.toString();
1222 // this must be: val >= maxval
1224 parent.nav.document.diff.real.value = val;
1225 parent.nav.document.diff.display.value = "EOF";
1229 function stopScroll() {
1231 clearInterval(myInt);
1236 function startScroll() {
1239 myInt=setInterval("scrollByPix()",10);
1242 function handlePress(b) {
1249 scrollToAnc(getAncValue() - 1);
1260 scrollToAnc(getAncValue() + 1);
1263 scrollToAnc(999999);
1268 function handleRelease(b) {
1272 function keypress(ev) {
1276 if (window.event) { // IE
1277 keynum = ev.keyCode;
1278 } else if (ev.which) { // non-IE
1282 keychar = String.fromCharCode(keynum);
1284 if (keychar == "k") {
1287 } else if (keychar == "j" || keychar == " ") {
1294 function ValidateDiffNum(){
1295 val = parent.nav.document.diff.display.value;
1297 scrollToAnc(999999);
1308 parent.nav.document.diff.display.value = getAncValue();
1321 # Output anchor navigation file for framed sdiffs.
1323 function frame_navigation
1325 print "$HTML<head>$STDHEAD"
1328 <title>Anchor Navigation</title>
1329 <meta http-equiv="Content-Script-Type" content="text/javascript">
1330 <meta http-equiv="Content-Type" content="text/html">
1332 <style type="text/css">
1333 div.button td { padding-left: 5px; padding-right: 5px;
1334 background-color: #eee; text-align: center;
1335 border: 1px #444 outset; cursor: pointer; }
1336 div.button a { font-weight: bold; color: black }
1337 div.button td:hover { background: #ffcc99; }
1341 print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1345 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1346 onkeypress="keypress(event);">
1347 <noscript lang="javascript">
1349 <p><big>Framed Navigation controls require Javascript</big><br></br>
1350 Either this browser is incompatable or javascript is not enabled</p>
1353 <table width="100%" border="0" align="center">
1355 <td valign="middle" width="25%">Diff navigation:
1356 Use 'j
' and 'k
' for next and previous diffs; or use buttons
1358 <td align="center" valign="top" width="50%">
1359 <div class="button">
1360 <table border="0" align="center">
1363 <a onMouseDown="handlePress(1);return true;"
1364 onMouseUp="handleRelease(1);return true;"
1365 onMouseOut="handleRelease(1);return true;"
1366 onClick="return false;"
1367 title="Go to Beginning Of file">BOF</a></td>
1369 <a onMouseDown="handlePress(3);return true;"
1370 onMouseUp="handleRelease(3);return true;"
1371 onMouseOut="handleRelease(3);return true;"
1372 title="Scroll Up: Press and Hold to accelerate"
1373 onClick="return false;">Scroll Up</a></td>
1375 <a onMouseDown="handlePress(2);return true;"
1376 onMouseUp="handleRelease(2);return true;"
1377 onMouseOut="handleRelease(2);return true;"
1378 title="Go to previous Diff"
1379 onClick="return false;">Prev Diff</a>
1384 <a onMouseDown="handlePress(6);return true;"
1385 onMouseUp="handleRelease(6);return true;"
1386 onMouseOut="handleRelease(6);return true;"
1387 onClick="return false;"
1388 title="Go to End Of File">EOF</a></td>
1390 <a onMouseDown="handlePress(4);return true;"
1391 onMouseUp="handleRelease(4);return true;"
1392 onMouseOut="handleRelease(4);return true;"
1393 title="Scroll Down: Press and Hold to accelerate"
1394 onClick="return false;">Scroll Down</a></td>
1396 <a onMouseDown="handlePress(5);return true;"
1397 onMouseUp="handleRelease(5);return true;"
1398 onMouseOut="handleRelease(5);return true;"
1399 title="Go to next Diff"
1400 onClick="return false;">Next Diff</a></td>
1405 <th valign="middle" width="25%">
1406 <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1407 <input name="display" value="BOF" size="8" type="text"></input>
1408 <input name="real" value="0" size="8" type="hidden"></input>
1421 # diff_to_html <filename> <filepath> { U | C } <comment>
1423 # Processes the output of diff to produce an HTML file representing either
1424 # context or unified diffs.
1433 print "$HTML<head>$STDHEAD"
1434 print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1436 if [[ $DIFFTYPE == "U" ]]; then
1442 <body id="SUNWwebrev">
1443 <a class="print" href="javascript:print()">Print this page</a>
1450 /^\
+\
+\
+ new
/ { next
}
1452 /^\
*\
*\
* old
/ { next
}
1453 /^\
*\
*\
*\
*/ { next
}
1454 /^
-------/ { printf "<center><h1>%s</h1></center>\n", $0; next
}
1455 /^\@\@.
*\@\@$
/ { printf "</pre><hr></hr><pre>\n";
1456 printf "<span class=\"newmarker\">%s</span>\n", $0;
1459 /^\
*\
*\
*/ { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1461 /^
---/ { printf "<span class=\"newmarker\">%s</span>\n", $0;
1463 /^\
+/ {printf "<span class=\"new\">%s</span>\n", $0; next
}
1464 /^
!/ {printf "<span class=\"changed\">%s</span>\n", $0; next
}
1465 /^
-/ {printf "<span class=\"removed\">%s</span>\n", $0; next
}
1466 {printf "%s\n", $0; next
}
1469 print "</pre></body></html>\n"
1474 # source_to_html { new | old } <filename>
1476 # Process a plain vanilla source file to transform it into an HTML file.
1483 print "$HTML<head>$STDHEAD"
1484 print "<title>$WNAME $WHICH $TNAME</title>"
1485 print "<body id=\"SUNWwebrev\">"
1487 html_quote | $AWK '{line
+= 1 ; printf "%4d %s\n", line
, $0 }'
1488 print "</pre></body></html>"
1492 # comments_from_wx {text|html} filepath
1494 # Given the pathname of a file, find its location in a "wx" active
1495 # file list and print the following comment. Output is either text or
1496 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1497 # are turned into URLs.
1499 # This is also used with Mercurial and the file list provided by hg-active.
1508 do getline
; while (NF
> 0)
1510 while (NF
> 0) { print
; getline
}
1514 if [[ -z $comm ]]; then
1515 comm="*** NO COMMENTS ***"
1518 if [[ $fmt == "text" ]]; then
1523 print -- "$comm" | html_quote | its2url
1528 # getcomments {text|html} filepath parentpath
1530 # Fetch the comments depending on what SCM mode we're
in.
1538 if [[ -n $Nflag ]]; then
1542 # Mercurial support uses a file list in wx format, so this
1543 # will be used there, too
1545 if [[ -n $wxfile ]]; then
1546 comments_from_wx
$fmt $p
1551 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1553 # Print out Code Inspection figures similar to sccs-prt(1) format.
1557 integer tot
=$1 ins
=$2 del
=$3 mod
=$4 unc
=$5
1559 if (( tot
== 1 )); then
1564 printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1565 $tot $str $ins $del $mod $unc
1570 # difflines <oldfile> <newfile>
1572 # Calculate and emit number of added, removed, modified and unchanged lines,
1573 # and total lines changed, the sum of added + removed + modified.
1577 integer tot mod del ins unc err
1580 eval $
( diff -e $1 $2 |
$AWK '
1581 # Change range of lines: N,Nc
1582 /^[0-9]*,[0-9]*c$/ {
1583 n=split(substr($1,1,length($1)-1), counts, ",");
1589 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1590 # following would be 5 - 3 = 2! Hence +1 for correction.
1592 r=(counts[2]-counts[1])+1;
1595 # Now count replacement lines: each represents a change instead
1596 # of a delete, so increment c and decrement r.
1598 while (getline != /^\.$/) {
1603 # If there were more replacement lines than original lines,
1604 # then r will be negative; in this case there are no deletions,
1605 # but there are r changes that should be counted as adds, and
1606 # since r is negative, subtract it from a and add it to c.
1614 # If there were more original lines than replacement lines, then
1615 # r will be positive; in this case, increment d by that much.
1625 # The first line is a replacement; any more are additions.
1626 if (getline != /^\.$/) {
1628 while (getline != /^\.$/) a++;
1633 # Add lines: both Na and N,Na
1635 while (getline != /^\.$/) a++;
1639 # Delete range of lines: N,Nd
1640 /^[0-9]*,[0-9]*d$/ {
1641 n=split(substr($1,1,length($1)-1), counts, ",");
1647 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1648 # following would be 5 - 3 = 2! Hence +1 for correction.
1650 r=(counts[2]-counts[1])+1;
1655 # Delete line: Nd. For example 10d says line 10 is deleted.
1656 /^[0-9]*d$/ {d++; next}
1658 # Should not get here!
1664 # Finish off - print results
1666 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1667 (c+d+a), c, d, a, error);
1670 # End of $AWK, Check to see if any trouble occurred.
1671 if (( $?
> 0 || err
> 0 )); then
1672 print
"Unexpected Error occurred reading" \
1673 "\`diff -e $1 $2\`: \$?=$?, err=" $err
1682 # Calculate unchanged lines
1684 if (( unc
> 0 )); then
1685 (( unc
-= del
+ mod
))
1689 print
"<span class=\"lineschanged\">"
1690 printCI
$tot $ins $del $mod $unc
1698 # Sets up webrev to source its information from a wx-formatted file.
1699 # Sets the global 'wxfile' variable.
1701 function flist_from_wx
1704 if [[ -n ${argfile%%/*} ]]; then
1706 # If the wx file pathname is relative then make it absolute
1707 # because the webrev does a "cd" later on.
1709 wxfile
=$PWD/$argfile
1714 $AWK '{ c = 1; print;
1716 if (NF == 0) { c = -c; continue }
1725 # Call hg-active to get the active list output in the wx active list format
1727 function hg_active_wxfile
1732 TMPFLIST
=/tmp
/$$.active
1733 $HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1738 # flist_from_mercurial
1739 # Call hg-active to get a wx-style active list, and hand it off to
1742 function flist_from_mercurial
1747 print
" File list from: hg-active -p $parent ...\c"
1748 if [[ ! -x $HG_ACTIVE ]]; then
1749 print
# Blank line for the \c above
1750 print
-u2 "Error: hg-active tool not found. Exiting"
1753 hg_active_wxfile
$child $parent
1755 # flist_from_wx prints the Done, so we don't have to.
1756 flist_from_wx
$TMPFLIST
1760 # Transform a specified 'git log' output format into a wx-like active list.
1767 TMPFLIST
=/tmp
/$$.active
1768 $PERL -e 'my (%files, %realfiles, $msg);
1769 my $parent = $ARGV[0];
1770 my $child = $ARGV[1];
1772 open(F, "git diff -M --name-status $parent..$child |");
1775 if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
1776 if ($1 >= 75) { # Probably worth treating as a rename
1777 $realfiles{$3} = $2;
1779 $realfiles{$3} = $3;
1780 $realfiles{$2} = $2;
1783 my $f = (split /\s+/, $_)[1];
1784 $realfiles{$f} = $f;
1789 my $state = 1; # 0|comments, 1|files
1790 open(F, "git whatchanged --pretty=format:%B $parent..$child |");
1794 my $fname = (split /\t/, $_)[1];
1795 next if !defined($realfiles{$fname}); # No real change
1798 $files{$fname} .= $msg;
1802 $msg = /^\n/ ? "" : "\n";
1804 $msg .= "$_\n" if ($_);
1809 for (sort keys %files) {
1810 if ($realfiles{$_} ne $_) {
1811 print "$_ $realfiles{$_}\n$files{$_}\n\n";
1813 print "$_\n$files{$_}\n\n"
1815 }' ${parent} ${child} > $TMPFLIST
1822 # Build a wx-style active list, and hand it off to flist_from_wx
1824 function flist_from_git
1829 print
" File list from: git ...\c"
1830 git_wxfile
"$child" "$parent";
1832 # flist_from_wx prints the Done, so we don't have to.
1833 flist_from_wx
$TMPFLIST
1837 # flist_from_subversion
1839 # Generate the file list by extracting file names from svn status.
1841 function flist_from_subversion
1847 print
-u2 " File list from: svn status ... \c"
1848 svn status |
$AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1853 function env_from_flist
1855 [[ -r $FLIST ]] ||
return
1858 # Use "eval" to set env variables that are listed in the file
1859 # list. Then copy those into our local versions of those
1860 # variables if they have not been set already.
1862 eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1864 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1865 codemgr_ws
=$CODEMGR_WS
1870 # Check to see if CODEMGR_PARENT is set in the flist file.
1872 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1873 codemgr_parent
=$CODEMGR_PARENT
1874 export CODEMGR_PARENT
1878 function look_for_prog
1885 ppath
=$ppath:/usr
/sfw
/bin
:/usr
/bin
:/usr
/sbin
1886 ppath
=$ppath:/opt
/onbld
/bin
1887 ppath
=$ppath:/opt
/onbld
/bin
/`uname -p`
1889 PATH
=$ppath prog
=`whence $progname`
1890 if [[ -n $prog ]]; then
1895 function get_file_mode
1898 if (@stat = stat($ARGV[0])) {
1899 $mode = $stat[2] & 0777;
1900 printf "%03o\n", $mode;
1908 function build_old_new_mercurial
1917 # Get old file mode, from the parent revision manifest entry.
1918 # Mercurial only stores a "file is executable" flag, but the
1919 # manifest will display an octal mode "644" or "755".
1921 if [[ "$PDIR" == "." ]]; then
1926 file=`echo $file | $SED 's#/#\\\/#g'`
1927 # match the exact filename, and return only the permission digits
1928 old_mode
=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
1929 < $HG_PARENT_MANIFEST`
1932 # Get new file mode, directly from the filesystem.
1933 # Normalize the mode to match Mercurial's behavior.
1935 new_mode
=`get_file_mode $CWS/$DIR/$F`
1936 if [[ -n "$new_mode" ]]; then
1937 if [[ "$new_mode" = *[1357]* ]]; then
1945 # new version of the file.
1947 rm -rf $newdir/$DIR/$F
1948 if [[ -e $CWS/$DIR/$F ]]; then
1949 cp $CWS/$DIR/$F $newdir/$DIR/$F
1950 if [[ -n $new_mode ]]; then
1951 chmod $new_mode $newdir/$DIR/$F
1953 # should never happen
1954 print
-u2 "ERROR: set mode of $newdir/$DIR/$F"
1959 # parent's version of the file
1961 # Note that we get this from the last version common to both
1962 # ourselves and the parent. References are via $CWS since we have no
1963 # guarantee that the parent workspace is reachable via the filesystem.
1965 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
1966 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1967 elif [[ -n $HG_PARENT ]]; then
1968 hg
cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
1969 $olddir/$PDIR/$PF 2>/dev
/null
1971 if (( $?
!= 0 )); then
1972 rm -f $olddir/$PDIR/$PF
1974 if [[ -n $old_mode ]]; then
1975 chmod $old_mode $olddir/$PDIR/$PF
1977 # should never happen
1978 print
-u2 "ERROR: set mode of $olddir/$PDIR/$PF"
1984 function build_old_new_git
1999 # Get old file and its mode from the git object tree
2001 if [[ "$PDIR" == "." ]]; then
2007 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2008 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2010 $GIT ls-tree
$GIT_PARENT $file |
read o_mode
type o_object junk
2011 $GIT cat-file
$type $o_object > $olddir/$file 2>/dev
/null
2013 if (( $?
!= 0 )); then
2015 elif [[ -n $o_mode ]]; then
2016 # Strip the first 3 digits, to get a regular octal mode
2017 o_mode
=${o_mode/???/}
2018 chmod $o_mode $olddir/$file
2020 # should never happen
2021 print
-u2 "ERROR: set mode of $olddir/$file"
2026 # new version of the file.
2028 if [[ "$DIR" == "." ]]; then
2033 rm -rf $newdir/$file
2035 if [[ -e $CWS/$DIR/$F ]]; then
2036 cp $CWS/$DIR/$F $newdir/$DIR/$F
2037 chmod $
(get_file_mode
$CWS/$DIR/$F) $newdir/$DIR/$F
2042 function build_old_new_subversion
2047 # Snag new version of file.
2048 rm -f $newdir/$DIR/$F
2049 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2051 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2052 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2054 # Get the parent's version of the file.
2055 svn status
$CWS/$DIR/$F |
read stat
file
2056 if [[ $stat != "A" ]]; then
2057 svn
cat -r BASE
$CWS/$DIR/$F > $olddir/$PDIR/$PF
2062 function build_old_new_unknown
2068 # Snag new version of file.
2070 rm -f $newdir/$DIR/$F
2071 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2074 # Snag the parent's version of the file.
2076 if [[ -f $PWS/$PDIR/$PF ]]; then
2077 rm -f $olddir/$PDIR/$PF
2078 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2082 function build_old_new
2092 typeset olddir
="$WDIR/raw_files/old"
2093 typeset newdir
="$WDIR/raw_files/new"
2095 mkdir
-p $olddir/$PDIR
2096 mkdir
-p $newdir/$DIR
2098 if [[ $SCM_MODE == "mercurial" ]]; then
2099 build_old_new_mercurial
"$olddir" "$newdir"
2100 elif [[ $SCM_MODE == "git" ]]; then
2101 build_old_new_git
"$olddir" "$newdir"
2102 elif [[ $SCM_MODE == "subversion" ]]; then
2103 build_old_new_subversion
"$olddir" "$newdir"
2104 elif [[ $SCM_MODE == "unknown" ]]; then
2105 build_old_new_unknown
"$olddir" "$newdir"
2108 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2109 print
"*** Error: file not in parent or child"
2121 print
'Usage:\twebrev [common-options]
2122 webrev [common-options] ( <file> | - )
2123 webrev [common-options] -w <wx file>
2126 -c <revision>: generate webrev for single revision (git only)
2127 -C <filename>: Use <filename> for the information tracking configuration.
2128 -D: delete remote webrev
2129 -h <revision>: specify "head" revision for comparison (git only)
2130 -i <filename>: Include <filename> in the index.html file.
2131 -I <filename>: Use <filename> for the information tracking registry.
2132 -n: do not generate the webrev (useful with -U)
2133 -O: Print bugids/arc cases suitable for OpenSolaris.
2134 -o <outdir>: Output webrev to specified directory.
2135 -p <compare-against>: Use specified parent wkspc or basis for comparison
2136 -t <remote_target>: Specify remote destination for webrev upload
2137 -U: upload the webrev to remote destination
2138 -w <wxfile>: Use specified wx active file.
2141 WDIR: Control the output directory.
2142 WEBREV_TRASH_DIR: Set directory for webrev delete.
2145 CODEMGR_WS: Workspace location.
2146 CODEMGR_PARENT: Parent workspace location.
2154 # Main program starts here
2158 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2162 PATH
=$
(/bin
/dirname "$(whence $0)"):$PATH
2164 [[ -z $WDIFF ]] && WDIFF
=`look_for_prog wdiff`
2165 [[ -z $WX ]] && WX
=`look_for_prog wx`
2166 [[ -z $HG_ACTIVE ]] && HG_ACTIVE
=`look_for_prog hg-active`
2167 [[ -z $GIT ]] && GIT
=`look_for_prog git`
2168 [[ -z $WHICH_SCM ]] && WHICH_SCM
=`look_for_prog which_scm`
2169 [[ -z $CODEREVIEW ]] && CODEREVIEW
=`look_for_prog codereview`
2170 [[ -z $PS2PDF ]] && PS2PDF
=`look_for_prog ps2pdf`
2171 [[ -z $PERL ]] && PERL
=`look_for_prog perl`
2172 [[ -z $RSYNC ]] && RSYNC
=`look_for_prog rsync`
2173 [[ -z $SCCS ]] && SCCS
=`look_for_prog sccs`
2174 [[ -z $AWK ]] && AWK
=`look_for_prog nawk`
2175 [[ -z $AWK ]] && AWK
=`look_for_prog gawk`
2176 [[ -z $AWK ]] && AWK
=`look_for_prog awk`
2177 [[ -z $SCP ]] && SCP
=`look_for_prog scp`
2178 [[ -z $SED ]] && SED
=`look_for_prog sed`
2179 [[ -z $SFTP ]] && SFTP
=`look_for_prog sftp`
2180 [[ -z $SORT ]] && SORT
=`look_for_prog sort`
2181 [[ -z $MKTEMP ]] && MKTEMP
=`look_for_prog mktemp`
2182 [[ -z $GREP ]] && GREP
=`look_for_prog grep`
2183 [[ -z $FIND ]] && FIND
=`look_for_prog find`
2184 [[ -z $MANDOC ]] && MANDOC
=`look_for_prog mandoc`
2185 [[ -z $COL ]] && COL
=`look_for_prog col`
2187 # set name of trash directory for remote webrev deletion
2189 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR
=$WEBREV_TRASH_DIR
2191 if [[ ! -x $PERL ]]; then
2192 print
-u2 "Error: No perl interpreter found. Exiting."
2196 if [[ ! -x $WHICH_SCM ]]; then
2197 print
-u2 "Error: Could not find which_scm. Exiting."
2202 # These aren't fatal, but we want to note them to the user.
2203 # We don't warn on the absence of 'wx' until later when we've
2204 # determined that we actually need to try to invoke it.
2206 [[ ! -x $CODEREVIEW ]] && print
-u2 "WARNING: codereview(1) not found."
2207 [[ ! -x $PS2PDF ]] && print
-u2 "WARNING: ps2pdf(1) not found."
2208 [[ ! -x $WDIFF ]] && print
-u2 "WARNING: wdiff not found."
2210 # Declare global total counters.
2211 integer TOTL TINS TDEL TMOD TUNC
2213 # default remote host for upload/delete
2214 typeset
-r DEFAULT_REMOTE_HOST
="cr.opensolaris.org"
2215 # prefixes for upload targets
2216 typeset
-r rsync_prefix
="rsync://"
2217 typeset
-r ssh_prefix
="ssh://"
2240 # NOTE: when adding/removing options it is necessary to sync the list
2241 # with usr/src/tools/onbld/hgext/cdm.py
2243 while getopts "c:C:Dh:i:I:lnNo:Op:t:Uw" opt
2247 codemgr_head
=$OPTARG
2248 codemgr_parent
=$OPTARG~
1;;
2256 codemgr_head
=$OPTARG;;
2259 INCLUDE_FILE
=$OPTARG;;
2271 # Strip the trailing slash to correctly form remote target.
2275 codemgr_parent
=$OPTARG;;
2278 remote_target
=$OPTARG;;
2290 if [[ -n $wflag && -n $lflag ]]; then
2294 # more sanity checking
2295 if [[ -n $nflag && -z $Uflag ]]; then
2296 print
"it does not make sense to skip webrev generation" \
2301 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2302 echo "remote target has to be used only for upload or delete"
2307 # For the invocation "webrev -n -U" with no other options, webrev will assume
2308 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2309 # $(basename ${CWS}). So we need to get CWS set before we skip any remaining
2312 $WHICH_SCM |
read SCM_MODE junk ||
exit 1
2313 if [[ $SCM_MODE == "mercurial" ]]; then
2315 # Mercurial priorities:
2316 # 1. hg root from CODEMGR_WS environment variable
2317 # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under
2318 # usr/closed when we run webrev
2319 # 2. hg root from directory of invocation
2321 if [[ ${PWD} =~
"usr/closed" ]]; then
2322 testparent
=${CODEMGR_WS}/usr
/closed
2323 # If we're in OpenSolaris mode, we enforce a minor policy:
2324 # help to make sure the reviewer doesn't accidentally publish
2325 # source which is under usr/closed
2326 if [[ -n "$Oflag" ]]; then
2327 print
-u2 "OpenSolaris output not permitted with" \
2328 "usr/closed changes"
2332 testparent
=${CODEMGR_WS}
2334 [[ -z $codemgr_ws && -n $testparent ]] && \
2335 codemgr_ws
=$
(hg root
-R $testparent 2>/dev
/null
)
2336 [[ -z $codemgr_ws ]] && codemgr_ws
=$
(hg root
2>/dev
/null
)
2338 elif [[ $SCM_MODE == "git" ]]; then
2341 # 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2342 # 2. git rev-parse --git-dir from directory of invocation
2344 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2345 codemgr_ws
=$
($GIT --git-dir=$CODEMGR_WS/.git rev-parse
--git-dir \
2347 [[ -z $codemgr_ws ]] && \
2348 codemgr_ws
=$
($GIT rev-parse
--git-dir 2>/dev
/null
)
2350 if [[ "$codemgr_ws" == ".git" ]]; then
2351 codemgr_ws
="${PWD}/${codemgr_ws}"
2354 codemgr_ws
=$
(dirname $codemgr_ws) # Lose the '/.git'
2356 elif [[ $SCM_MODE == "subversion" ]]; then
2358 # Subversion priorities:
2359 # 1. CODEMGR_WS from environment
2360 # 2. Relative path from current directory to SVN repository root
2362 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn
]]; then
2365 svn info |
while read line
; do
2366 if [[ $line == "URL: "* ]]; then
2368 elif [[ $line == "Repository Root: "* ]]; then
2369 repo
=${line#Repository Root: }
2379 # If no SCM has been determined, take either the environment setting
2380 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2382 if [[ -z ${CWS} ]]; then
2383 CWS
=${CODEMGR_WS:-.}
2387 # If the command line options indicate no webrev generation, either
2388 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2389 # ton of logic we can skip.
2391 # Instead of increasing indentation, we intentionally leave this loop
2392 # body open here, and exit via break from multiple points within.
2393 # Search for DO_EVERYTHING below to find the break points and closure.
2395 for do_everything
in 1; do
2397 # DO_EVERYTHING: break point
2398 if [[ -n $nflag ||
( -z $Uflag && -n $Dflag ) ]]; then
2403 # If this manually set as the parent, and it appears to be an earlier webrev,
2404 # then note that fact and set the parent to the raw_files/new subdirectory.
2406 if [[ -n $pflag && -d $codemgr_parent/raw_files
/new
]]; then
2407 parent_webrev
=$
(readlink
-f "$codemgr_parent")
2408 codemgr_parent
=$
(readlink
-f "$codemgr_parent/raw_files/new")
2411 if [[ -z $wflag && -z $lflag ]]; then
2412 shift $
(($OPTIND - 1))
2414 if [[ $1 == "-" ]]; then
2419 elif [[ -n $1 ]]; then
2420 if [[ ! -r $1 ]]; then
2421 print
-u2 "$1: no such file or not readable"
2435 # Before we go on to further consider -l and -w, work out which SCM we think
2439 mercurial|git|subversion
)
2442 if [[ $flist_mode == "auto" ]]; then
2443 print
-u2 "Unable to determine SCM in use and file list not specified"
2444 print
-u2 "See which_scm(1) for SCM detection information."
2449 if [[ $flist_mode == "auto" ]]; then
2450 print
-u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2456 print
-u2 " SCM detected: $SCM_MODE"
2458 if [[ -n $wflag ]]; then
2460 # If the -w is given then assume the file list is in Bonwick's "wx"
2461 # command format, i.e. pathname lines alternating with SCCS comment
2462 # lines with blank lines as separators. Use the SCCS comments later
2463 # in building the index.html file.
2465 shift $
(($OPTIND - 1))
2467 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2468 if [[ -r $CODEMGR_WS/wx
/active
]]; then
2469 wxfile
=$CODEMGR_WS/wx
/active
2473 [[ -z $wxfile ]] && print
-u2 "wx file not specified, and could not " \
2474 "be auto-detected (check \$CODEMGR_WS)" && exit 1
2476 if [[ ! -r $wxfile ]]; then
2477 print
-u2 "$wxfile: no such file or not readable"
2481 print
-u2 " File list from: wx 'active' file '$wxfile' ... \c"
2482 flist_from_wx
$wxfile
2484 if [[ -n "$*" ]]; then
2487 elif [[ $flist_mode == "stdin" ]]; then
2488 print
-u2 " File list from: standard input"
2489 elif [[ $flist_mode == "file" ]]; then
2490 print
-u2 " File list from: $flist_file"
2493 if [[ $# -gt 0 ]]; then
2494 print
-u2 "WARNING: unused arguments: $*"
2498 # Before we entered the DO_EVERYTHING loop, we should have already set CWS
2499 # and CODEMGR_WS as needed. Here, we set the parent workspace.
2501 if [[ $SCM_MODE == "mercurial" ]]; then
2503 # Parent can either be specified with -p
2504 # Specified with CODEMGR_PARENT in the environment
2505 # or taken from hg's default path.
2508 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2509 codemgr_parent
=$CODEMGR_PARENT
2512 if [[ -z $codemgr_parent ]]; then
2513 codemgr_parent
=`hg path -R $codemgr_ws default 2>/dev/null`
2519 # If the parent is a webrev, we want to do some things against
2520 # the natural workspace parent (file list, comments, etc)
2522 if [[ -n $parent_webrev ]]; then
2523 real_parent
=$
(hg path
-R $codemgr_ws default
2>/dev
/null
)
2529 # If hg-active exists, then we run it. In the case of no explicit
2530 # flist given, we'll use it for our comments. In the case of an
2531 # explicit flist given we'll try to use it for comments for any
2532 # files mentioned in the flist.
2534 if [[ -z $flist_done ]]; then
2535 flist_from_mercurial
$CWS $real_parent
2540 # If we have a file list now, pull out any variables set
2541 # therein. We do this now (rather than when we possibly use
2542 # hg-active to find comments) to avoid stomping specifications
2543 # in the user-specified flist.
2545 if [[ -n $flist_done ]]; then
2550 # Only call hg-active if we don't have a wx formatted file already
2552 if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2553 print
" Comments from: hg-active -p $real_parent ...\c"
2554 hg_active_wxfile
$CWS $real_parent
2559 # At this point we must have a wx flist either from hg-active,
2560 # or in general. Use it to try and find our parent revision,
2561 # if we don't have one.
2563 if [[ -z $HG_PARENT ]]; then
2564 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2568 # If we still don't have a parent, we must have been given a
2569 # wx-style active list with no HG_PARENT specification, run
2570 # hg-active and pull an HG_PARENT out of it, ignore the rest.
2572 if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2573 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2574 eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2575 elif [[ -z $HG_PARENT ]]; then
2576 print
-u2 "Error: Cannot discover parent revision"
2580 pnode
=$
(trim_digest
$HG_PARENT)
2581 PRETTY_PWS
="${PWS} (at ${pnode})"
2582 cnode
=$
(hg parent
-R $codemgr_ws --template '{node|short}' \
2584 PRETTY_CWS
="${CWS} (at ${cnode})"}
2585 elif [[ $SCM_MODE == "git
" ]]; then
2586 # Check that "head" revision specified with -c or -h is sane
2587 if [[ -n $cflag || -n $hflag ]]; then
2588 head_rev=$($GIT rev-parse --verify --quiet "$codemgr_head")
2589 if [[ -z $head_rev ]]; then
2590 print -u2 "Error
: bad revision
${codemgr_head}"
2595 if [[ -z $codemgr_head ]]; then
2596 codemgr_head="HEAD
";
2599 # Parent can either be specified with -p, or specified with
2600 # CODEMGR_PARENT in the environment.
2601 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2602 codemgr_parent=$CODEMGR_PARENT
2605 # Try to figure out the parent based on the branch the current
2606 # branch is tracking, if we fail, use origin/master
2607 this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2608 par_branch="origin
/master
"
2610 # If we're not on a branch there's nothing we can do
2611 if [[ $this_branch != "(no branch
)" ]]; then
2613 --format='%(refname:short) %(upstream:short)' refs/heads/ | \
2614 while read local remote; do \
2615 [[ "$local" == "$this_branch" ]] && par_branch="$remote"; \
2619 if [[ -z $codemgr_parent ]]; then
2620 codemgr_parent=$par_branch
2625 # If the parent is a webrev, we want to do some things against
2626 # the natural workspace parent (file list, comments, etc)
2628 if [[ -n $parent_webrev ]]; then
2629 real_parent=$par_branch
2634 if [[ -z $flist_done ]]; then
2635 flist_from_git "$codemgr_head" "$real_parent"
2640 # If we have a file list now, pull out any variables set
2643 if [[ -n $flist_done ]]; then
2648 # If we don't have a wx-format file list, build one we can pull change
2651 if [[ -z $wxfile ]]; then
2652 print " Comments from
: git...\c
"
2653 git_wxfile "$codemgr_head" "$real_parent"
2657 if [[ -z $GIT_PARENT ]]; then
2658 GIT_PARENT=$($GIT merge-base "$real_parent" "$codemgr_head")
2660 if [[ -z $GIT_PARENT ]]; then
2661 print -u2 "Error
: Cannot discover parent revision
"
2665 pnode=$(trim_digest $GIT_PARENT)
2667 if [[ -n $cflag ]]; then
2668 PRETTY_PWS="previous revision
(at ${pnode})"
2669 elif [[ $real_parent == */* ]]; then
2670 origin=$(echo $real_parent | cut -d/ -f1)
2671 origin=$($GIT remote -v | \
2672 $AWK '$1 == "'$origin'" { print $2; exit }')
2673 PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2674 elif [[ -n $pflag && -z $parent_webrev ]]; then
2675 PRETTY_PWS="${CWS} (explicit revision
${pnode})"
2677 PRETTY_PWS="${PWS} (at ${pnode})"
2680 cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 \
2681 ${codemgr_head} 2>/dev/null)
2683 if [[ -n $cflag || -n $hflag ]]; then
2684 PRETTY_CWS="${CWS} (explicit
head at ${cnode})"
2686 PRETTY_CWS="${CWS} (at ${cnode})"
2688 elif [[ $SCM_MODE == "subversion
" ]]; then
2691 # We only will have a real parent workspace in the case one
2692 # was specified (be it an older webrev, or another checkout).
2694 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2696 if [[ -z $flist_done && $flist_mode == "auto
" ]]; then
2697 flist_from_subversion $CWS $OLDPWD
2700 if [[ $SCM_MODE == "unknown
" ]]; then
2701 print -u2 " Unknown
type of SCM
in use
"
2703 print -u2 " Unsupported SCM
in use
: $SCM_MODE"
2708 if [[ -z $CODEMGR_WS ]]; then
2709 print -u2 "SCM not detected
/supported and CODEMGR_WS not specified
"
2713 if [[ -z $CODEMGR_PARENT ]]; then
2714 print -u2 "SCM not detected
/supported and CODEMGR_PARENT not specified
"
2723 # If the user didn't specify a -i option, check to see if there is a
2724 # webrev-info file in the workspace directory.
2726 if [[ -z $iflag && -r "$CWS/webrev-info
" ]]; then
2728 INCLUDE_FILE="$CWS/webrev-info
"
2731 if [[ -n $iflag ]]; then
2732 if [[ ! -r $INCLUDE_FILE ]]; then
2733 print -u2 "include
file '$INCLUDE_FILE' does not exist or is
" \
2738 # $INCLUDE_FILE may be a relative path, and the script alters
2739 # PWD, so we just stash a copy in /tmp.
2741 cp $INCLUDE_FILE /tmp/$$.include
2745 # DO_EVERYTHING: break point
2746 if [[ -n $Nflag ]]; then
2751 typeset -r its_sed_script=/tmp/$$.its_sed
2753 if [[ -z $nflag ]]; then
2754 DEFREGFILE="$
(/bin
/dirname "$(whence $0)")/..
/etc
/its.reg
"
2755 if [[ -n $Iflag ]]; then
2757 elif [[ -r $HOME/.its.reg ]]; then
2758 REGFILE=$HOME/.its.reg
2762 if [[ ! -r $REGFILE ]]; then
2763 print "ERROR
: Unable to
read database registry
file $REGFILE"
2765 elif [[ $REGFILE != $DEFREGFILE ]]; then
2766 print " its.reg from
: $REGFILE"
2769 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do
2774 if [[ $name == PREFIX ]]; then
2776 valid_prefixes="${p} ${valid_prefixes}"
2778 itsinfo["${p}_${name}"]="${value}"
2783 DEFCONFFILE="$
(/bin
/dirname "$(whence $0)")/..
/etc
/its.conf
"
2784 CONFFILES=$DEFCONFFILE
2785 if [[ -r $HOME/.its.conf ]]; then
2786 CONFFILES="${CONFFILES} $HOME/.its.conf
"
2788 if [[ -n $Cflag ]]; then
2789 CONFFILES="${CONFFILES} ${ITSCONF}"
2793 for cf in ${CONFFILES}; do
2794 if [[ ! -r $cf ]]; then
2795 print "ERROR
: Unable to
read database configuration
file $cf"
2797 elif [[ $cf != $DEFCONFFILE ]]; then
2798 print " its.conf
: reading
$cf"
2800 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do
2806 # If an information tracking system is explicitly identified by prefix,
2807 # we want to disregard the specified priorities and resolve it accordingly.
2809 # To that end, we'll build a sed script to do each valid prefix in turn.
2811 for p in ${valid_prefixes}; do
2813 # When an informational URL was provided, translate it to a
2814 # hyperlink. When omitted, simply use the prefix text.
2816 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2817 itsinfo["${p}_INFO
"]=${p}
2819 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a
>"
2823 # Assume that, for this invocation of webrev, all references
2824 # to this information tracking system should resolve through
2827 # If the caller specified -O, then always use EXTERNAL_URL.
2829 # Otherwise, look in the list of domains for a matching
2832 [[ -z $Oflag ]] && for d in ${its_domain}; do
2833 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2834 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2838 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2839 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2843 # Turn the destination URL into a hyperlink
2845 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a
>"
2847 # The character class below contains a literal tab
2848 print "/^
${p}[: ]/ {
2849 s
;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2850 s
;^
${p};${itsinfo[${p}_INFO]};
2851 }" >> ${its_sed_script}
2855 # The previous loop took care of explicit specification. Now use
2856 # the configured priorities to attempt implicit translations.
2858 for p in ${its_priority}; do
2859 print "/^
${itsinfo[${p}_REGEX]}[ ]/ {
2860 s
;^
${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2861 }" >> ${its_sed_script}
2866 # Search for DO_EVERYTHING above for matching "for" statement
2867 # and explanation of this terminator.
2874 WDIR=${WDIR:-$CWS/webrev}
2877 # Name of the webrev, derived from the workspace name or output directory;
2878 # in the future this could potentially be an option.
2880 if [[ -n $oflag ]]; then
2886 # Make sure remote target is well formed for remote upload/delete.
2887 if [[ -n $Dflag || -n $Uflag ]]; then
2889 # If remote target is not specified, build it from scratch using
2890 # the default values.
2892 if [[ -z $tflag ]]; then
2893 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2896 # Check upload target prefix first.
2898 if [[ "${remote_target}" != ${rsync_prefix}* &&
2899 "${remote_target}" != ${ssh_prefix}* ]]; then
2900 print "ERROR
: invalid prefix of upload URI
" \
2905 # If destination specification is not in the form of
2906 # host_spec:remote_dir then assume it is just remote hostname
2907 # and append a colon and destination directory formed from
2908 # local webrev directory name.
2910 typeset target_no_prefix=${remote_target##*://}
2911 if [[ ${target_no_prefix} == *:* ]]; then
2912 if [[ "${remote_target}" == *: ]]; then
2913 remote_target=${remote_target}${WNAME}
2916 if [[ ${target_no_prefix} == */* ]]; then
2917 print "ERROR
: badly formed upload URI
" \
2921 remote_target=${remote_target}:${WNAME}
2927 # Strip trailing slash. Each upload method will deal with directory
2928 # specification separately.
2930 remote_target=${remote_target%/}
2934 # Option -D by itself (option -U not present) implies no webrev generation.
2936 if [[ -z $Uflag && -n $Dflag ]]; then
2942 # Do not generate the webrev, just upload it or delete it.
2944 if [[ -n $nflag ]]; then
2945 if [[ -n $Dflag ]]; then
2947 (( $? == 0 )) || exit $?
2949 if [[ -n $Uflag ]]; then
2955 if [ "${WDIR%%/*}" ]; then
2959 if [[ ! -d $WDIR ]]; then
2961 (( $? != 0 )) && exit 1
2965 # Summarize what we're going to do.
2967 print " Workspace
: ${PRETTY_CWS:-$CWS}"
2968 if [[ -n $parent_webrev ]]; then
2969 print "Compare against
: webrev
at $parent_webrev"
2971 print "Compare against
: ${PRETTY_PWS:-$PWS}"
2974 [[ -n $INCLUDE_FILE ]] && print " Including
: $INCLUDE_FILE"
2975 print " Output to
: $WDIR"
2978 # Save the file list in the webrev dir
2980 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
2982 rm -f $WDIR/$WNAME.patch
2983 rm -f $WDIR/$WNAME.ps
2984 rm -f $WDIR/$WNAME.pdf
2986 touch $WDIR/$WNAME.patch
2988 print " Output Files
:"
2991 # Clean up the file list: Remove comments, blank lines and env variables.
2993 $SED -e "s
/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean
2994 FLIST
=/tmp
/$$.flist.clean
2997 # For Mercurial, create a cache of manifest entries.
2999 if [[ $SCM_MODE == "mercurial" ]]; then
3001 # Transform the FLIST into a temporary sed script that matches
3002 # relevant entries in the Mercurial manifest as follows:
3003 # 1) The script will be used against the parent revision manifest,
3004 # so for FLIST lines that have two filenames (a renamed file)
3005 # keep only the old name.
3006 # 2) Escape all forward slashes the filename.
3007 # 3) Change the filename into another sed command that matches
3008 # that file in "hg manifest -v" output: start of line, three
3009 # octal digits for file permissions, space, a file type flag
3010 # character, space, the filename, end of line.
3011 # 4) Eliminate any duplicate entries. (This can occur if a
3012 # file has been used as the source of an hg cp and it's
3013 # also been modified in the same changeset.)
3015 SEDFILE
=/tmp
/$$.manifest.
sed
3019 s#^.*$#/^... . &$/p#
3020 ' < $FLIST |
$SORT -u > $SEDFILE
3023 # Apply the generated script to the output of "hg manifest -v"
3024 # to get the relevant subset for this webrev.
3026 HG_PARENT_MANIFEST
=/tmp
/$$.manifest
3027 hg
-R $CWS manifest
-v -r $HG_PARENT |
3028 $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
3032 # First pass through the files: generate the per-file webrev HTML-files.
3034 cat $FLIST |
while read LINE
3040 # Normally, each line in the file list is just a pathname of a
3041 # file that has been modified or created in the child. A file
3042 # that is renamed in the child workspace has two names on the
3043 # line: new name followed by the old name.
3048 if [[ $# -eq 2 ]]; then
3049 PP
=$2 # old filename
3050 if [[ -f $PP ]]; then
3051 oldname
=" (copied from $PP)"
3053 oldname
=" (renamed from $PP)"
3058 if [[ $PDIR == $PP ]]; then
3059 PDIR
="." # File at root of workspace
3065 if [[ $DIR == $P ]]; then
3066 DIR
="." # File at root of workspace
3073 if [[ "$DIR" == "$P" ]]; then
3074 DIR
="." # File at root of workspace
3084 COMM
=`getcomments html $P $PP`
3086 print
"\t$P$oldname\n\t\t\c"
3088 # Make the webrev mirror directory if necessary
3092 # We stash old and new files into parallel directories in $WDIR
3093 # and do our diffs there. This makes it possible to generate
3094 # clean looking diffs which don't have absolute paths present.
3097 build_old_new
"$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3101 # Keep the old PWD around, so we can safely switch back after
3102 # diff generation, such that build_old_new runs in a
3103 # consistent environment.
3111 cmp $ofile $nfile > /dev
/null
2>&1
3112 if [[ $?
== 0 && $rename == 1 ]]; then
3117 # If we have old and new versions of the file then run the appropriate
3118 # diffs. This is complicated by a couple of factors:
3120 # - renames must be handled specially: we emit a 'remove'
3121 # diff and an 'add' diff
3122 # - new files and deleted files must be handled specially
3123 # - GNU patch doesn't interpret the output of illumos diff
3124 # properly when it comes to adds and deletes. We need to
3125 # do some "cleansing" transformations:
3126 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@
3127 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@
3129 cleanse_rmfile
="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3130 cleanse_newfile
="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3132 rm -f $WDIR/$DIR/$F.
patch
3133 if [[ -z $rename ]]; then
3134 if [ ! -f "$ofile" ]; then
3135 diff -u /dev
/null
$nfile | sh
-c "$cleanse_newfile" \
3136 > $WDIR/$DIR/$F.
patch
3137 elif [ ! -f "$nfile" ]; then
3138 diff -u $ofile /dev
/null | sh
-c "$cleanse_rmfile" \
3139 > $WDIR/$DIR/$F.
patch
3141 diff -u $ofile $nfile > $WDIR/$DIR/$F.
patch
3144 diff -u $ofile /dev
/null | sh
-c "$cleanse_rmfile" \
3145 > $WDIR/$DIR/$F.
patch
3147 diff -u /dev
/null
$nfile | sh
-c "$cleanse_newfile" \
3148 >> $WDIR/$DIR/$F.
patch
3152 # Tack the patch we just made onto the accumulated patch for the
3155 cat $WDIR/$DIR/$F.
patch >> $WDIR/$WNAME.
patch
3158 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3159 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3160 diff_to_html
$F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3161 > $WDIR/$DIR/$F.cdiff.html
3164 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3165 diff_to_html
$F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3166 > $WDIR/$DIR/$F.udiff.html
3169 if [[ -x $WDIFF ]]; then
3171 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3172 $WDIR/$DIR/$F.wdiff.html
2>/dev
/null
3173 if [[ $?
-eq 0 ]]; then
3176 print
" wdiffs[fail]\c"
3180 sdiff_to_html
$ofile $nfile $F $DIR "$COMM" \
3181 > $WDIR/$DIR/$F.
sdiff.html
3185 rm -f $WDIR/$DIR/$F.cdiff
$WDIR/$DIR/$F.udiff
3186 difflines
$ofile $nfile > $WDIR/$DIR/$F.count
3187 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3188 # renamed file: may also have differences
3189 difflines
$ofile $nfile > $WDIR/$DIR/$F.count
3190 elif [[ -f $nfile ]]; then
3191 # new file: count added lines
3192 difflines
/dev
/null
$nfile > $WDIR/$DIR/$F.count
3193 elif [[ -f $ofile ]]; then
3194 # old file: count deleted lines
3195 difflines
$ofile /dev
/null
> $WDIR/$DIR/$F.count
3199 # Check if it's man page, and create plain text, html and raw (ascii)
3200 # output for the new version, as well as diffs against old version.
3202 if [[ -f "$nfile" && "$nfile" = *.
+([0-9])*([a-zA-Z
]) && \
3203 -x $MANDOC && -x $COL ]]; then
3204 $MANDOC -Tascii $nfile |
$COL -b > $nfile.man.txt
3205 source_to_html txt
< $nfile.man.txt
> $nfile.man.txt.html
3207 print
"$MANCSS" > $WDIR/raw_files
/new
/$DIR/man.css
3208 $MANDOC -Thtml -Ostyle=man.css
$nfile > $nfile.man.html
3210 $MANDOC -Tascii $nfile > $nfile.man.raw
3212 if [[ -f "$ofile" && -z $mv_but_nodiff ]]; then
3213 $MANDOC -Tascii $ofile |
$COL -b > $ofile.man.txt
3214 ${CDIFFCMD:-diff -bt -C 5} $ofile.man.txt \
3215 $nfile.man.txt
> $WDIR/$DIR/$F.man.cdiff
3216 diff_to_html
$F $DIR/$F "C" "$COMM" < \
3217 $WDIR/$DIR/$F.man.cdiff
> \
3218 $WDIR/$DIR/$F.man.cdiff.html
3219 print
" man-cdiffs\c"
3220 ${UDIFFCMD:-diff -bt -U 5} $ofile.man.txt \
3221 $nfile.man.txt
> $WDIR/$DIR/$F.man.udiff
3222 diff_to_html
$F $DIR/$F "U" "$COMM" < \
3223 $WDIR/$DIR/$F.man.udiff
> \
3224 $WDIR/$DIR/$F.man.udiff.html
3225 print
" man-udiffs\c"
3226 if [[ -x $WDIFF ]]; then
3227 $WDIFF -c "$COMM" -t "$WNAME Wdiff $DIR/$F" \
3228 $ofile.man.txt
$nfile.man.txt
> \
3229 $WDIR/$DIR/$F.man.wdiff.html
2>/dev
/null
3230 if [[ $?
-eq 0 ]]; then
3231 print
" man-wdiffs\c"
3233 print
" man-wdiffs[fail]\c"
3236 sdiff_to_html
$ofile.man.txt
$nfile.man.txt
$F.man
$DIR \
3237 "$COMM" > $WDIR/$DIR/$F.man.
sdiff.html
3238 print
" man-sdiffs\c"
3239 print
" man-frames\c"
3241 rm -f $ofile.man.txt
$nfile.man.txt
3242 rm -f $WDIR/$DIR/$F.man.cdiff
$WDIR/$DIR/$F.man.udiff
3246 # Now we generate the postscript for this file. We generate diffs
3247 # only in the event that there is delta, or the file is new (it seems
3248 # tree-killing to print out the contents of deleted files).
3250 if [[ -f $nfile ]]; then
3252 [[ ! -f $ofile ]] && ocr
=/dev
/null
3254 if [[ -z $mv_but_nodiff ]]; then
3255 textcomm
=`getcomments text $P $PP`
3256 if [[ -x $CODEREVIEW ]]; then
3257 $CODEREVIEW -y "$textcomm" \
3259 > /tmp
/$$.psfile
2>/dev
/null
&&
3260 cat /tmp
/$$.psfile
>> $WDIR/$WNAME.ps
3261 if [[ $?
-eq 0 ]]; then
3270 if [[ -f $ofile ]]; then
3271 source_to_html Old
$PP < $ofile > $WDIR/$DIR/$F-.html
3275 if [[ -f $nfile ]]; then
3276 source_to_html New
$P < $nfile > $WDIR/$DIR/$F.html
3285 frame_nav_js
> $WDIR/ancnav.js
3286 frame_navigation
> $WDIR/ancnav.html
3288 if [[ ! -f $WDIR/$WNAME.ps
]]; then
3289 print
" Generating PDF: Skipped: no output available"
3290 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3291 print
" Generating PDF: \c"
3292 fix_postscript
$WDIR/$WNAME.ps |
$PS2PDF - > $WDIR/$WNAME.pdf
3295 print
" Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3298 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3299 # delete it - prevent accidental publishing of closed source
3301 if [[ -n "$Oflag" ]]; then
3302 $FIND $WDIR -type d
-name closed
-exec /bin
/rm -rf {} \
;
3305 # Now build the index.html file that contains
3306 # links to the source files and their diffs.
3310 # Save total changed lines for Code Inspection.
3311 print
"$TOTL" > $WDIR/TotalChangedLines
3313 print
" index.html: \c"
3314 INDEXFILE
=$WDIR/index.html
3315 exec 3<&1 # duplicate stdout to FD3.
3316 exec 1<&- # Close stdout.
3317 exec > $INDEXFILE # Open stdout to index file.
3319 print
"$HTML<head>$STDHEAD"
3320 print
"<title>$WNAME</title>"
3322 print
"<body id=\"SUNWwebrev\">"
3323 print
"<div class=\"summary\">"
3324 print
"<h2>Code Review for $WNAME</h2>"
3329 # Get the preparer's name:
3331 # If the SCM detected is Mercurial, and the configuration property
3332 # ui.username is available, use that, but be careful to properly escape
3333 # angle brackets (HTML syntax characters) in the email address.
3335 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3336 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3339 if [[ "$SCM_MODE" == mercurial
]]; then
3340 preparer
=`hg showconfig ui.username 2>/dev/null`
3341 if [[ -n "$preparer" ]]; then
3342 preparer
="$(echo "$preparer" | html_quote)"
3345 if [[ -z "$preparer" ]]; then
3348 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3350 $gcos =~ s/\&/ucfirst($login)/e;
3351 printf "%s (%s)\n", $gcos, $login;
3353 printf "(unknown)\n";
3358 PREPDATE
=$
(LC_ALL
=C
/usr
/bin
/date +%Y-
%b-
%d\
%R\
%z\
%Z
)
3359 print
"<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3360 print
"<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3362 print
"<tr><th>Compare against:</th><td>"
3363 if [[ -n $parent_webrev ]]; then
3364 print
"webrev at $parent_webrev"
3366 print
"${PRETTY_PWS:-$PWS}"
3369 print
"<tr><th>Summary of changes:</th><td>"
3370 printCI
$TOTL $TINS $TDEL $TMOD $TUNC
3373 if [[ -f $WDIR/$WNAME.
patch ]]; then
3374 wpatch_url
="$(print $WNAME.patch | url_encode)"
3375 print
"<tr><th>Patch of changes:</th><td>"
3376 print
"<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3378 if [[ -f $WDIR/$WNAME.pdf
]]; then
3379 wpdf_url
="$(print $WNAME.pdf | url_encode)"
3380 print
"<tr><th>Printable review:</th><td>"
3381 print
"<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3384 if [[ -n "$iflag" ]]; then
3385 print
"<tr><th>Author comments:</th><td><div>"
3387 print
"</div></td></tr>"
3393 # Second pass through the files: generate the rest of the index file
3395 cat $FLIST |
while read LINE
3400 if [[ $# == 2 ]]; then
3409 cmp $WDIR/raw_files
/old
/$PP $WDIR/raw_files
/new
/$P > /dev
/null
2>&1
3410 if [[ $?
== 0 && -n "$oldname" ]]; then
3415 if [[ $DIR == $P ]]; then
3416 DIR
="." # File at root of workspace
3419 # Avoid processing the same file twice.
3420 # It's possible for renamed files to
3421 # appear twice in the file list
3427 # If there's a diffs file, make diffs links
3429 if [[ -f $F.cdiff.html
]]; then
3430 cdiff_url
="$(print $P.cdiff.html | url_encode)"
3431 udiff_url
="$(print $P.udiff.html | url_encode)"
3432 sdiff_url
="$(print $P.sdiff.html | url_encode)"
3433 frames_url
="$(print $P.frames.html | url_encode)"
3434 print
"<a href=\"$cdiff_url\">Cdiffs</a>"
3435 print
"<a href=\"$udiff_url\">Udiffs</a>"
3436 if [[ -f $F.wdiff.html
&& -x $WDIFF ]]; then
3437 wdiff_url
="$(print $P.wdiff.html | url_encode)"
3438 print
"<a href=\"$wdiff_url\">Wdiffs</a>"
3440 print
"<a href=\"$sdiff_url\">Sdiffs</a>"
3441 print
"<a href=\"$frames_url\">Frames</a>"
3443 print
" ------ ------"
3444 if [[ -x $WDIFF ]]; then
3447 print
" ------ ------"
3450 # If there's an old file, make the link
3452 if [[ -f $F-.html
]]; then
3453 oldfile_url
="$(print $P-.html | url_encode)"
3454 print
"<a href=\"$oldfile_url\">Old</a>"
3459 # If there's an new file, make the link
3461 if [[ -f $F.html
]]; then
3462 newfile_url
="$(print $P.html | url_encode)"
3463 print
"<a href=\"$newfile_url\">New</a>"
3468 if [[ -f $F.
patch ]]; then
3469 patch_url
="$(print $P.patch | url_encode)"
3470 print
"<a href=\"$patch_url\">Patch</a>"
3475 if [[ -f $WDIR/raw_files
/new
/$P ]]; then
3476 rawfiles_url
="$(print raw_files/new/$P | url_encode)"
3477 print
"<a href=\"$rawfiles_url\">Raw</a>"
3484 # For renamed files, clearly state whether or not they are modified
3485 if [[ -f "$oldname" ]]; then
3486 if [[ -n "$mv_but_nodiff" ]]; then
3487 print
"<i>(copied from $oldname)</i>"
3489 print
"<i>(copied and modified from $oldname)</i>"
3491 elif [[ -n "$oldname" ]]; then
3492 if [[ -n "$mv_but_nodiff" ]]; then
3493 print
"<i>(renamed from $oldname)</i>"
3495 print
"<i>(renamed and modified from $oldname)</i>"
3499 # If there's an old file, but no new file, the file was deleted
3500 if [[ -f $F-.html
&& ! -f $F.html
]]; then
3501 print
" <i>(deleted)</i>"
3504 # Check for usr/closed and deleted_files/usr/closed
3505 if [ ! -z "$Oflag" ]; then
3506 if [[ $P == usr
/closed
/* || \
3507 $P == deleted_files
/usr
/closed
/* ]]; then
3508 print
" <i>Closed source: omitted from" \
3514 if [[ -f $F.man.cdiff.html || \
3515 -f $WDIR/raw_files
/new
/$P.man.txt.html
]]; then
3520 if [[ -f $F.man.cdiff.html
]]; then
3521 mancdiff_url
="$(print $P.man.cdiff.html | url_encode)"
3522 manudiff_url
="$(print $P.man.udiff.html | url_encode)"
3523 mansdiff_url
="$(print $P.man.sdiff.html | url_encode)"
3524 manframes_url
="$(print $P.man.frames.html | url_encode)"
3525 print
"<a href=\"$mancdiff_url\">Cdiffs</a>"
3526 print
"<a href=\"$manudiff_url\">Udiffs</a>"
3527 if [[ -f $F.man.wdiff.html
&& -x $WDIFF ]]; then
3528 manwdiff_url
="$(print $P.man.wdiff.html | url_encode)"
3529 print
"<a href=\"$manwdiff_url\">Wdiffs</a>"
3531 print
"<a href=\"$mansdiff_url\">Sdiffs</a>"
3532 print
"<a href=\"$manframes_url\">Frames</a>"
3533 elif [[ -n $manpage ]]; then
3534 print
" ------ ------"
3535 if [[ -x $WDIFF ]]; then
3538 print
" ------ ------"
3541 if [[ -f $WDIR/raw_files
/new
/$P.man.txt.html
]]; then
3542 mantxt_url
="$(print raw_files/new/$P.man.txt.html | url_encode)"
3543 print
"<a href=\"$mantxt_url\">TXT</a>"
3544 manhtml_url
="$(print raw_files/new/$P.man.html | url_encode)"
3545 print
"<a href=\"$manhtml_url\">HTML</a>"
3546 manraw_url
="$(print raw_files/new/$P.man.raw | url_encode)"
3547 print
"<a href=\"$manraw_url\">Raw</a>"
3548 elif [[ -n $manpage ]]; then
3549 print
" --- ---- ---"
3554 # Insert delta comments
3555 print
"<blockquote><pre>"
3556 getcomments html
$P $PP
3559 # Add additional comments comment
3560 print
"<!-- Add comments to explain changes in $P here -->"
3562 # Add count of changes.
3563 if [[ -f $F.count
]]; then
3568 if [[ $SCM_MODE == "mercurial" ||
3569 $SCM_MODE == "unknown" ]]; then
3570 # Include warnings for important file mode situations:
3571 # 1) New executable files
3572 # 2) Permission changes of any kind
3573 # 3) Existing executable files
3575 if [[ -f $WDIR/raw_files
/old
/$PP ]]; then
3576 old_mode
=`get_file_mode $WDIR/raw_files/old/$PP`
3580 if [[ -f $WDIR/raw_files
/new
/$P ]]; then
3581 new_mode
=`get_file_mode $WDIR/raw_files/new/$P`
3584 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3585 print
"<span class=\"chmod\">"
3586 print
"<p>new executable file: mode $new_mode</p>"
3588 elif [[ -n "$old_mode" && -n "$new_mode" &&
3589 "$old_mode" != "$new_mode" ]]; then
3590 print
"<span class=\"chmod\">"
3591 print
"<p>mode change: $old_mode to $new_mode</p>"
3593 elif [[ "$new_mode" = *[1357]* ]]; then
3594 print
"<span class=\"chmod\">"
3595 print
"<p>executable file: mode $new_mode</p>"
3600 print
"</blockquote>"
3606 print
"<p style=\"font-size: small\">"
3607 print
"This code review page was prepared using <b>$0</b>."
3608 print
"Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3609 print
"illumos</a> project. The latest version may be obtained"
3610 print
"<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3614 exec 1<&- # Close FD 1.
3615 exec 1<&3 # dup FD 3 to restore stdout.
3616 exec 3<&- # close FD 3.
3621 # If remote deletion was specified and fails do not continue.
3623 if [[ -n $Dflag ]]; then
3625 (( $?
== 0 )) ||
exit $?
3628 if [[ -n $Uflag ]]; then