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 2017 Nexenta Systems, Inc.
29 # Copyright 2016 Joyent, Inc.
30 # Copyright 2016 RackTop Systems.
34 # This script takes a file list and a workspace and builds a set of html files
35 # suitable for doing a code review of source changes via a web page.
36 # Documentation is available via the manual page, webrev.1, or just
39 # Acknowledgements to contributors to webrev are listed in the webrev(1)
47 HTML
='<?xml version="1.0"?>
48 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
49 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
50 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
52 FRAMEHTML
='<?xml version="1.0"?>
53 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
54 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
55 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
57 STDHEAD
='<meta http-equiv="cache-control" content="no-cache"></meta>
58 <meta http-equiv="Content-Type" content="text/xhtml;charset=utf-8"></meta>
59 <meta http-equiv="Pragma" content="no-cache"></meta>
60 <meta http-equiv="Expires" content="-1"></meta>
62 Note to customizers: the body of the webrev is IDed as SUNWwebrev
63 to allow easy overriding by users of webrev via the userContent.css
64 mechanism available in some browsers.
66 For example, to have all "removed" information be red instead of
67 brown, set a rule in your userContent.css file like:
69 body#SUNWwebrev span.removed { color: red ! important; }
71 <style type="text/css" media="screen">
73 background-color: #eeeeee;
77 border-top: 1px solid #aaa;
82 border-bottom: 1px solid #aaa;
89 div.summary table th {
121 a.print { font-size: x-small; }
122 a:hover { background-color: #ffcc99; }
125 <style type="text/css" media="print">
126 pre { font-size: 0.8em; font-family: courier, monospace; }
127 span.removed { color: #444; font-style: italic }
128 span.changed { font-weight: bold; }
129 span.new { font-weight: bold; }
130 span.newmarker { font-size: 1.2em; font-weight: bold; }
131 span.oldmarker { font-size: 1.2em; font-weight: bold; }
132 a.print {display: none}
133 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
138 # UDiffs need a slightly different CSS rule for 'new' items (we don't
139 # want them to be bolded as we do in cdiffs or sdiffs).
142 <style type="text/css" media="screen">
151 # CSS for the HTML version of the man pages.
154 html { max-width: 880px; margin-left: 1em; }
155 body { font-size: smaller; font-family: Helvetica,Arial,sans-serif; }
156 h1 { margin-bottom: 1ex; font-size: 110%; margin-left: -4ex; }
157 h2 { margin-bottom: 1ex; font-size: 105%; margin-left: -2ex; }
158 table { width: 100%; margin-top: 0ex; margin-bottom: 0ex; }
159 td { vertical-align: top; }
160 blockquote { margin-left: 5ex; margin-top: 0ex; margin-bottom: 0ex; }
161 div.section { margin-bottom: 2ex; margin-left: 5ex; }
162 table.foot { font-size: smaller; margin-top: 1em;
163 border-top: 1px dotted #dddddd; }
164 td.foot-date { width: 50%; }
165 td.foot-os { width: 50%; text-align: right; }
166 table.head { font-size: smaller; margin-bottom: 1em;
167 border-bottom: 1px dotted #dddddd; }
168 td.head-ltitle { width: 10%; }
169 td.head-vol { width: 80%; text-align: center; }
170 td.head-rtitle { width: 10%; text-align: right; }
171 .emph { font-style: italic; font-weight: normal; }
172 .symb { font-style: normal; font-weight: bold; }
173 .lit { font-style: normal; font-weight: normal; font-family: monospace; }
174 i.addr { font-weight: normal; }
175 i.arg { font-weight: normal; }
176 b.cmd { font-style: normal; }
177 b.config { font-style: normal; }
178 b.diag { font-style: normal; }
179 i.farg { font-weight: normal; }
180 i.file { font-weight: normal; }
181 b.flag { font-style: normal; }
182 b.fname { font-style: normal; }
183 i.ftype { font-weight: normal; }
184 b.includes { font-style: normal; }
185 i.link-sec { font-weight: normal; }
186 b.macro { font-style: normal; }
187 b.name { font-style: normal; }
188 i.ref-book { font-weight: normal; }
189 i.ref-issue { font-weight: normal; }
190 i.ref-jrnl { font-weight: normal; }
191 span.ref-title { text-decoration: underline; }
192 span.type { font-style: italic; font-weight: normal; }
193 b.utility { font-style: normal; }
194 b.var { font-style: normal; }
195 dd.list-ohang { margin-left: 0ex; }
196 ul.list-bul { list-style-type: disc; padding-left: 1em; }
197 ul.list-dash { list-style-type: none; padding-left: 0em; }
198 li.list-dash:before { content: "\2014 "; }
199 ul.list-hyph { list-style-type: none; padding-left: 0em; }
200 li.list-hyph:before { content: "\2013 "; }
201 ul.list-item { list-style-type: none; padding-left: 0em; }
202 ol.list-enum { padding-left: 2em; }
206 # Display remote target with prefix and trailing slash.
208 function print_upload_header
211 typeset display_target
213 if [[ -z $tflag ]]; then
214 display_target
=${prefix}${remote_target}
216 display_target
=${remote_target}
219 if [[ ${display_target} != */ ]]; then
220 display_target
=${display_target}/
223 print
" Upload to: ${display_target}\n" \
228 # Upload the webrev via rsync. Return 0 on success, 1 on error.
230 function rsync_upload
232 if (( $# != 2 )); then
233 print
"\nERROR: rsync_upload: wrong usage ($#)"
238 integer
-r print_err_msg
=$2
240 print_upload_header
${rsync_prefix}
242 typeset
-r err_msg
=$
( $MKTEMP /tmp
/rsync_err.XXXXXX
)
243 if [[ -z $err_msg ]]; then
244 print
"\nERROR: rsync_upload: cannot create temporary file"
248 # The source directory must end with a slash in order to copy just
249 # directory contents, not the whole directory.
251 typeset src_dir
=$WDIR
252 if [[ ${src_dir} != */ ]]; then
255 $RSYNC -r -q ${src_dir} $dst 2>$err_msg
256 if (( $?
!= 0 )); then
257 if (( ${print_err_msg} > 0 )); then
258 print
"Failed.\nERROR: rsync failed"
259 print
"src dir: '${src_dir}'\ndst dir: '$dst'"
260 print
"error messages:"
261 $SED 's/^/> /' $err_msg
273 # Create directories on remote host using SFTP. Return 0 on success,
276 function remote_mkdirs
278 typeset
-r dir_spec
=$1
279 typeset
-r host_spec
=$2
282 # If the supplied path is absolute we assume all directories are
283 # created, otherwise try to create all directories in the path
284 # except the last one which will be created by scp.
286 if [[ "${dir_spec}" == */* && "${dir_spec}" != /* ]]; then
289 # Remove the last directory from directory specification.
291 typeset
-r dirs_mk
=${dir_spec%/*}
292 typeset
-r batch_file_mkdir
=$
( $MKTEMP \
293 /tmp
/webrev_mkdir.XXXXXX
)
294 if [[ -z $batch_file_mkdir ]]; then
295 print
"\nERROR: remote_mkdirs:" \
296 "cannot create temporary file for batch file"
302 for dir
in ${dirs_mk}; do
304 # Use the '-' prefix to ignore mkdir errors in order
305 # to avoid an error in case the directory already
306 # exists. We check the directory with chdir to be sure
309 print
-- "-mkdir ${dir}" >> ${batch_file_mkdir}
310 print
"chdir ${dir}" >> ${batch_file_mkdir}
313 typeset
-r sftp_err_msg
=$
( $MKTEMP /tmp
/webrev_scp_err.XXXXXX
)
314 if [[ -z ${sftp_err_msg} ]]; then
315 print
"\nERROR: remote_mkdirs:" \
316 "cannot create temporary file for error messages"
319 $SFTP -b ${batch_file_mkdir} ${host_spec} 2>${sftp_err_msg} 1>&2
320 if (( $?
!= 0 )); then
321 print
"\nERROR: failed to create remote directories"
322 print
"error messages:"
323 $SED 's/^/> /' ${sftp_err_msg}
324 rm -f ${sftp_err_msg} ${batch_file_mkdir}
327 rm -f ${sftp_err_msg} ${batch_file_mkdir}
334 # Upload the webrev via SSH. Return 0 on success, 1 on error.
338 if (( $# != 1 )); then
339 print
"\nERROR: ssh_upload: wrong number of arguments"
344 typeset
-r host_spec
=${dst%%:*}
345 typeset
-r dir_spec
=${dst#*:}
348 # Display the upload information before calling delete_webrev
349 # because it will also print its progress.
351 print_upload_header
${ssh_prefix}
354 # If the deletion was explicitly requested there is no need
355 # to perform it again.
357 if [[ -z $Dflag ]]; then
359 # We do not care about return value because this might be
360 # the first time this directory is uploaded.
366 # Create remote directories. Any error reporting will be done
367 # in remote_mkdirs function.
369 remote_mkdirs
${dir_spec} ${host_spec}
370 if (( $?
!= 0 )); then
374 print
"upload ... \c"
375 typeset
-r scp_err_msg
=$
( $MKTEMP /tmp
/scp_err.XXXXXX
)
376 if [[ -z ${scp_err_msg} ]]; then
377 print
"\nERROR: ssh_upload:" \
378 "cannot create temporary file for error messages"
381 $SCP -q -C -B -o PreferredAuthentications
=publickey
-r \
382 $WDIR $dst 2>${scp_err_msg}
383 if (( $?
!= 0 )); then
384 print
"Failed.\nERROR: scp failed"
385 print
"src dir: '$WDIR'\ndst dir: '$dst'"
386 print
"error messages:"
387 $SED 's/^/> /' ${scp_err_msg}
398 # Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp
399 # on failure. If first argument is 1 then perform the check of sftp return
400 # value otherwise ignore it. If second argument is present it means this run
401 # only performs deletion.
403 function delete_webrev
405 if (( $# < 1 )); then
406 print
"delete_webrev: wrong number of arguments"
411 integer delete_only
=0
412 if (( $# == 2 )); then
417 # Strip the transport specification part of remote target first.
419 typeset
-r stripped_target
=${remote_target##*://}
420 typeset
-r host_spec
=${stripped_target%%:*}
421 typeset
-r dir_spec
=${stripped_target#*:}
425 # Do not accept an absolute path.
427 if [[ ${dir_spec} == /* ]]; then
432 # Strip the ending slash.
434 if [[ ${dir_spec} == */ ]]; then
435 dir_rm
=${dir_spec%%/}
440 if (( ${delete_only} > 0 )); then
441 print
" Removing: \c"
445 if [[ -z "$dir_rm" ]]; then
446 print
"\nERROR: empty directory for removal"
451 # Prepare batch file.
453 typeset
-r batch_file_rm
=$
( $MKTEMP /tmp
/webrev_remove.XXXXXX
)
454 if [[ -z $batch_file_rm ]]; then
455 print
"\nERROR: delete_webrev: cannot create temporary file"
458 print
"rename $dir_rm $TRASH_DIR/removed.$$" > $batch_file_rm
461 # Perform remote deletion and remove the batch file.
463 typeset
-r sftp_err_msg
=$
( $MKTEMP /tmp
/webrev_scp_err.XXXXXX
)
464 if [[ -z ${sftp_err_msg} ]]; then
465 print
"\nERROR: delete_webrev:" \
466 "cannot create temporary file for error messages"
469 $SFTP -b $batch_file_rm $host_spec 2>${sftp_err_msg} 1>&2
472 if (( $ret != 0 && $check > 0 )); then
473 print
"Failed.\nERROR: failed to remove remote directories"
474 print
"error messages:"
475 $SED 's/^/> /' ${sftp_err_msg}
476 rm -f ${sftp_err_msg}
479 rm -f ${sftp_err_msg}
480 if (( ${delete_only} > 0 )); then
488 # Upload webrev to remote site
490 function upload_webrev
494 if [[ ! -d "$WDIR" ]]; then
495 print
"\nERROR: webrev directory '$WDIR' does not exist"
500 # Perform a late check to make sure we do not upload closed source
501 # to remote target when -n is used. If the user used custom remote
502 # target he probably knows what he is doing.
504 if [[ -n $nflag && -z $tflag ]]; then
505 $FIND $WDIR -type d
-name closed \
506 |
$GREP closed
>/dev
/null
507 if (( $?
== 0 )); then
508 print
"\nERROR: directory '$WDIR' contains" \
509 "\"closed\" directory"
516 # We have the URI for remote destination now so let's start the upload.
518 if [[ -n $tflag ]]; then
519 if [[ "${remote_target}" == ${rsync_prefix}?
* ]]; then
520 rsync_upload
${remote_target##$rsync_prefix} 1
523 elif [[ "${remote_target}" == ${ssh_prefix}?
* ]]; then
524 ssh_upload
${remote_target##$ssh_prefix}
530 # Try rsync first and fallback to SSH in case it fails.
532 rsync_upload
${remote_target} 0
534 if (( $ret != 0 )); then
535 print
"Failed. (falling back to SSH)"
536 ssh_upload
${remote_target}
544 # input_cmd | url_encode | output_cmd
546 # URL-encode (percent-encode) reserved characters as defined in RFC 3986.
548 # Reserved characters are: :/?#[]@!$&'()*+,;=
550 # While not a reserved character itself, percent '%' is reserved by definition
551 # so encode it first to avoid recursive transformation, and skip '/' which is
554 # The quotation character is deliberately not escaped in order to make
555 # the substitution work with GNU sed.
559 $SED -e "s|%|%25|g" -e "s|:|%3A|g" -e "s|\&|%26|g" \
560 -e "s|?|%3F|g" -e "s|#|%23|g" -e "s|\[|%5B|g" \
561 -e "s|*|%2A|g" -e "s|@|%40|g" -e "s|\!|%21|g" \
562 -e "s|=|%3D|g" -e "s|;|%3B|g" -e "s|\]|%5D|g" \
563 -e "s|(|%28|g" -e "s|)|%29|g" -e "s|'|%27|g" \
564 -e "s|+|%2B|g" -e "s|\,|%2C|g" -e "s|\\\$|%24|g"
568 # input_cmd | html_quote | output_cmd
570 # html_quote filename | output_cmd
572 # Make a piece of source code safe for display in an HTML <pre> block.
576 $SED -e "s/&/\&/g" -e "s/</\</g" -e "s/>/\>/g" "$@" |
expand
580 # Trim a digest-style revision to a conventionally readable yet useful length
586 echo $digest |
$SED -e 's/\([0-9a-f]\{12\}\).*/\1/'
590 # input_cmd | its2url | output_cmd
592 # Scan for information tracking system references and insert <a> links to the
593 # relevant databases.
597 $SED -f ${its_sed_script}
601 # strip_unchanged <infile> | output_cmd
603 # Removes chunks of sdiff documents that have not changed. This makes it
604 # easier for a code reviewer to find the bits that have changed.
606 # Deleted lines of text are replaced by a horizontal rule. Some
607 # identical lines are retained before and after the changed lines to
608 # provide some context. The number of these lines is controlled by the
609 # variable C in the $AWK script below.
611 # The script detects changed lines as any line that has a "<span class="
612 # string embedded (unchanged lines have no particular class and are not
613 # part of a <span>). Blank lines (without a sequence number) are also
614 # detected since they flag lines that have been inserted or deleted.
620 NF == 0 || /<span class="/ {
625 print "\n</pre><hr></hr><pre>"
630 for (i = 0; i < c; i++)
631 print ln[(inx + i) % C]
645 END { if (c > (C * 2)) print "\n</pre><hr></hr>" }
653 # This function takes two files as arguments, obtains their diff, and
654 # processes the diff output to present the files as an HTML document with
655 # the files displayed side-by-side, differences shown in color. It also
656 # takes a delta comment, rendered as an HTML snippet, as the third
657 # argument. The function takes two files as arguments, then the name of
658 # file, the path, and the comment. The HTML will be delivered on stdout,
661 # $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
662 # new/usr/src/tools/scripts/webrev.sh \
663 # webrev.sh usr/src/tools/scripts \
664 # '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
665 # 1234567</a> my bugid' > <file>.html
667 # framed_sdiff() is then called which creates $2.frames.html
668 # in the webrev tree.
670 # FYI: This function is rather unusual in its use of awk. The initial
671 # diff run produces conventional diff output showing changed lines mixed
672 # with editing codes. The changed lines are ignored - we're interested in
673 # the editing codes, e.g.
682 # These editing codes are parsed by the awk script and used to generate
683 # another awk script that generates HTML, e.g the above lines would turn
684 # into something like this:
686 # BEGIN { printf "<pre>\n" }
687 # function sp(n) {for (i=0;i<n;i++)printf "\n"}
688 # function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
689 # NR==8 {wl("#7A7ADD");next}
690 # NR==54 {wl("#7A7ADD");sp(3);next}
691 # NR==56 {wl("#7A7ADD");next}
692 # NR==57 {wl("black");printf "\n"; next}
695 # This script is then run on the original source file to generate the
696 # HTML that corresponds to the source file.
698 # The two HTML files are then combined into a single piece of HTML that
699 # uses an HTML table construct to present the files side by side. You'll
700 # notice that the changes are color-coded:
702 # black - unchanged lines
703 # blue - changed lines
704 # bold blue - new lines
705 # brown - deleted lines
707 # Blank lines are inserted in each file to keep unchanged lines in sync
708 # (side-by-side). This format is familiar to users of sdiff(1) or
709 # Teamware's filemerge tool.
713 diff -b $1 $2 > /tmp
/$$.diffs
720 # Now we have the diffs, generate the HTML for the old file.
724 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
725 printf "function removed() "
726 printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
727 printf "function changed() "
728 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
729 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
736 split($1, a, /[cad]/) ;
737 if (index($1, "a")) {
739 n = split(a[2], r, /,/);
741 printf "BEGIN\t\t{sp(1)}\n"
743 printf "BEGIN\t\t{sp(%d)}\n",\
748 printf "NR==%s\t\t{", a[1]
749 n = split(a[2], r, /,/);
752 printf "bl();printf \"\\n\"; next}\n"
755 printf "bl();sp(%d);next}\n",\
760 if (index($1, "d")) {
761 n = split(a[1], r, /,/);
765 printf "NR==%s\t\t{removed(); next}\n" , n1
767 printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
770 if (index($1, "c")) {
771 n = split(a[1], r, /,/);
777 printf "NR==%s\t\t{changed();" , n1
780 printf "NR==%s,NR==%s\t{changed();" , n1, n2
782 m = split(a[2], r, /,/);
788 if (n > 1) printf "if (NR==%d)", final
789 printf "sp(%d);", d2 - d1
798 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
799 ' /tmp
/$$.diffs
> /tmp
/$$.file1
802 # Now generate the HTML for the new file
806 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
807 printf "function new() "
808 printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
809 printf "function changed() "
810 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
811 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
819 split($1, a, /[cad]/) ;
820 if (index($1, "d")) {
822 n = split(a[1], r, /,/);
824 printf "BEGIN\t\t{sp(1)}\n"
826 printf "BEGIN\t\t{sp(%d)}\n",\
831 printf "NR==%s\t\t{", a[2]
832 n = split(a[1], r, /,/);
835 printf "bl();printf \"\\n\"; next}\n"
838 printf "bl();sp(%d);next}\n",\
843 if (index($1, "a")) {
844 n = split(a[2], r, /,/);
848 printf "NR==%s\t\t{new() ; next}\n" , n1
850 printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
853 if (index($1, "c")) {
854 n = split(a[2], r, /,/);
861 printf "NR==%s\t\t{changed();" , n1
864 printf "NR==%s,NR==%s\t{changed();" , n1, n2
866 m = split(a[1], r, /,/);
872 if (n > 1) printf "if (NR==%d)", final
873 printf "sp(%d);", d1 - d2
880 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
881 ' /tmp
/$$.diffs
> /tmp
/$$.file2
884 # Post-process the HTML files by running them back through $AWK
886 html_quote
< $1 |
$AWK -f /tmp
/$$.file1
> /tmp
/$$.file1.html
888 html_quote
< $2 |
$AWK -f /tmp
/$$.file2
> /tmp
/$$.file2.html
891 # Now combine into a valid HTML file and side-by-side into a table
893 print
"$HTML<head>$STDHEAD"
894 print
"<title>$WNAME Sdiff $TPATH/$TNAME</title>"
895 print
"</head><body id=\"SUNWwebrev\">"
896 print
"<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
897 print
"<pre>$COMMENT</pre>\n"
898 print
"<table><tr valign=\"top\">"
901 strip_unchanged
/tmp
/$$.file1.html
903 print
"</pre></td><td><pre>"
905 strip_unchanged
/tmp
/$$.file2.html
908 print
"</tr></table>"
909 print
"</body></html>"
911 framed_sdiff
$TNAME $TPATH /tmp
/$$.file1.html
/tmp
/$$.file2.html \
917 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
919 # Expects lefthand and righthand side html files created by sdiff_to_html.
920 # We use insert_anchors() to augment those with HTML navigation anchors,
921 # and then emit the main frame. Content is placed into:
923 # $WDIR/DIR/$TNAME.lhs.html
924 # $WDIR/DIR/$TNAME.rhs.html
925 # $WDIR/DIR/$TNAME.frames.html
927 # NOTE: We rely on standard usage of $WDIR and $DIR.
929 function framed_sdiff
938 # Enable html files to access WDIR via a relative path.
939 RTOP
=$
(relative_dir
$TPATH $WDIR)
941 # Make the rhs/lhs files and output the frameset file.
942 print
"$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
944 cat >> $WDIR/$DIR/$TNAME.lhs.html
<<-EOF
945 <script type="text/javascript" src="${RTOP}ancnav.js"></script>
947 <body id="SUNWwebrev" onkeypress="keypress(event);">
949 <pre>$comments</pre><hr></hr>
952 cp $WDIR/$DIR/$TNAME.lhs.html
$WDIR/$DIR/$TNAME.rhs.html
954 insert_anchors
$lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
955 insert_anchors
$rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
957 close
='</body></html>'
959 print
$close >> $WDIR/$DIR/$TNAME.lhs.html
960 print
$close >> $WDIR/$DIR/$TNAME.rhs.html
962 print
"$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
963 print
"<title>$WNAME Framed-Sdiff " \
964 "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
965 cat >> $WDIR/$DIR/$TNAME.frames.html
<<-EOF
966 <frameset rows="*,60">
967 <frameset cols="50%,50%">
968 <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
969 <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
971 <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0"
972 marginheight="0" name="nav"></frame>
974 <body id="SUNWwebrev">
975 Alas 'frames' webrev requires that your browser supports frames
976 and has the feature enabled.
988 # Merge codereview output files to a single conforming postscript file, by:
989 # - removing all extraneous headers/trailers
990 # - making the page numbers right
991 # - removing pages devoid of contents which confuse some
992 # postscript readers.
996 function fix_postscript
1000 cat > /tmp
/$$.crmerge.pl
<< \EOF
1002 print scalar
(<>); # %!PS-Adobe---
1003 print
"%%Orientation: Landscape\n";
1011 next
if (/^
%%Pages
:\s
*\d
+/);
1014 if ($pno == 0 ||
$page =~
/\
)S
/) {
1015 # Header or single page containing text
1016 print
"%%Page: ? $pno\n" if ($pno > 0);
1020 # Empty page, skip it.
1027 # Skip from %%Trailer of one document to Endprolog
1028 # %%Page of the next
1029 $doprint = 0 if (/^
%%Trailer
/);
1030 $page .
= $_ if ($doprint);
1033 if ($page =~
/\
)S
/) {
1034 print
"%%Page: ? $pno\n";
1039 print
"%%Trailer\n%%Pages: $pno\n";
1042 $PERL /tmp
/$$.crmerge.pl
< $infile
1047 # input_cmd | insert_anchors | output_cmd
1049 # Flag blocks of difference with sequentially numbered invisible
1050 # anchors. These are used to drive the frames version of the
1053 # NOTE: Anchor zero flags the top of the file irrespective of changes,
1054 # an additional anchor is also appended to flag the bottom.
1056 # The script detects changed lines as any line that has a "<span
1057 # class=" string embedded (unchanged lines have no class set and are
1058 # not part of a <span>. Blank lines (without a sequence number)
1059 # are also detected since they flag lines that have been inserted or
1062 function insert_anchors
1066 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1074 NF == 0 || /^<span class=/ {
1089 printf "<b style=\"font-size: large; color: red\">";
1090 printf "--- EOF ---</b>"
1091 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1093 printf "<form name=\"eof\">";
1094 printf "<input name=\"value\" value=\"%d\" " \
1095 "type=\"hidden\"></input>", anc - 1;
1105 # Print a relative return path from $1 to $2. For example if
1106 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1107 # this function would print "../../../../".
1109 # In the event that $1 is not in $2 a warning is printed to stderr,
1110 # and $2 is returned-- the result of this is that the resulting webrev
1111 # is not relocatable.
1113 function relative_dir
1115 typeset cur
="${1##$2?(/)}"
1118 # If the first path was specified absolutely, and it does
1119 # not start with the second path, it's an error.
1121 if [[ "$cur" = "/${1#/}" ]]; then
1122 # Should never happen.
1123 print
-u2 "\nWARNING: relative_dir: \"$1\" not relative "
1124 print
-u2 "to \"$2\". Check input paths. Framed webrev "
1125 print
-u2 "will not be relocatable!"
1131 # This is kind of ugly. The sed script will do the following:
1133 # 1. Strip off a leading "." or "./": this is important to get
1134 # the correct arcnav links for files in $WDIR.
1135 # 2. Strip off a trailing "/": this is not strictly necessary,
1136 # but is kind of nice, since it doesn't end up in "//" at
1137 # the end of a relative path.
1138 # 3. Replace all remaining sequences of non-"/" with "..": the
1139 # assumption here is that each dirname represents another
1140 # level of relative separation.
1141 # 4. Append a trailing "/" only for non-empty paths: this way
1142 # the caller doesn't need to duplicate this logic, and does
1143 # not end up using $RTOP/file for files in $WDIR.
1145 print
$cur |
$SED -e '{
1156 # Emit javascript for frame navigation
1158 function frame_nav_js
1166 function scrollByPix
()
1172 parent.lhs.scrollBy
(0, sfactor
);
1173 parent.rhs.scrollBy
(0, sfactor
);
1177 function scrollToAnc
(num
)
1179 // Update the value of the anchor
in the form
which we use as
1180 // storage
for this value. setAncValue
() will take care of
1181 // correcting
for overflow and underflow of the value and
return
1182 // us the new value.
1183 num
= setAncValue
(num
);
1185 // Set location and scroll back a little to expose previous
1188 // Note that this could be improved
: it is possible although
1189 // complex to compute the x and y position of an anchor
, and to
1190 // scroll to that location directly.
1192 parent.lhs.location.replace
(parent.lhs.location.pathname
+ "#" + num
);
1193 parent.rhs.location.replace
(parent.rhs.location.pathname
+ "#" + num
);
1195 parent.lhs.scrollBy
(0, -30);
1196 parent.rhs.scrollBy
(0, -30);
1199 function getAncValue
()
1201 return (parseInt
(parent.nav.document.
diff.real.value
));
1204 function setAncValue
(val
)
1208 parent.nav.document.
diff.real.value
= val
;
1209 parent.nav.document.
diff.display.value
= "BOF";
1214 // The way we compute the max anchor value is to stash it
1215 // inline
in the left and right hand side pages-- it
's the same
1216 // on each side, so we pluck from the left.
1218 maxval = parent.lhs.document.eof.value.value;
1220 parent.nav.document.diff.real.value = val;
1221 parent.nav.document.diff.display.value = val.toString();
1225 // this must be: val >= maxval
1227 parent.nav.document.diff.real.value = val;
1228 parent.nav.document.diff.display.value = "EOF";
1232 function stopScroll()
1234 if (scrolling == 1) {
1235 clearInterval(myInt);
1240 function startScroll()
1244 myInt = setInterval("scrollByPix()", 10);
1247 function handlePress(b)
1254 scrollToAnc(getAncValue() - 1);
1265 scrollToAnc(getAncValue() + 1);
1268 scrollToAnc(999999);
1273 function handleRelease(b)
1278 function keypress(ev)
1283 if (window.event) { // IE
1284 keynum = ev.keyCode;
1285 } else if (ev.which) { // non-IE
1289 keychar = String.fromCharCode(keynum);
1291 if (keychar == "k") {
1294 } else if (keychar == "j" || keychar == " ") {
1302 function ValidateDiffNum()
1307 val = parent.nav.document.diff.display.value;
1309 scrollToAnc(999999);
1320 parent.nav.document.diff.display.value = getAncValue();
1333 # Output anchor navigation file for framed sdiffs.
1335 function frame_navigation
1337 print "$HTML<head>$STDHEAD"
1340 <title>Anchor Navigation</title>
1341 <meta http-equiv="Content-Script-Type" content="text/javascript">
1342 <meta http-equiv="Content-Type" content="text/html">
1344 <style type="text/css">
1345 div.button td { padding-left: 5px; padding-right: 5px;
1346 background-color: #eee; text-align: center;
1347 border: 1px #444 outset; cursor: pointer; }
1348 div.button a { font-weight: bold; color: black }
1349 div.button td:hover { background: #ffcc99; }
1353 print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1357 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1358 onkeypress="keypress(event);">
1359 <noscript lang="javascript">
1361 <p><big>Framed Navigation controls require Javascript</big><br></br>
1362 Either this browser is incompatable or javascript is not enabled</p>
1365 <table width="100%" border="0" align="center">
1367 <td valign="middle" width="25%">Diff navigation:
1368 Use 'j
' and 'k
' for next and previous diffs; or use buttons
1370 <td align="center" valign="top" width="50%">
1371 <div class="button">
1372 <table border="0" align="center">
1375 <a onMouseDown="handlePress(1);return true;"
1376 onMouseUp="handleRelease(1);return true;"
1377 onMouseOut="handleRelease(1);return true;"
1378 onClick="return false;"
1379 title="Go to Beginning Of file">BOF</a></td>
1381 <a onMouseDown="handlePress(3);return true;"
1382 onMouseUp="handleRelease(3);return true;"
1383 onMouseOut="handleRelease(3);return true;"
1384 title="Scroll Up: Press and Hold to accelerate"
1385 onClick="return false;">Scroll Up</a></td>
1387 <a onMouseDown="handlePress(2);return true;"
1388 onMouseUp="handleRelease(2);return true;"
1389 onMouseOut="handleRelease(2);return true;"
1390 title="Go to previous Diff"
1391 onClick="return false;">Prev Diff</a>
1396 <a onMouseDown="handlePress(6);return true;"
1397 onMouseUp="handleRelease(6);return true;"
1398 onMouseOut="handleRelease(6);return true;"
1399 onClick="return false;"
1400 title="Go to End Of File">EOF</a></td>
1402 <a onMouseDown="handlePress(4);return true;"
1403 onMouseUp="handleRelease(4);return true;"
1404 onMouseOut="handleRelease(4);return true;"
1405 title="Scroll Down: Press and Hold to accelerate"
1406 onClick="return false;">Scroll Down</a></td>
1408 <a onMouseDown="handlePress(5);return true;"
1409 onMouseUp="handleRelease(5);return true;"
1410 onMouseOut="handleRelease(5);return true;"
1411 title="Go to next Diff"
1412 onClick="return false;">Next Diff</a></td>
1417 <th valign="middle" width="25%">
1418 <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1419 <input name="display" value="BOF" size="8" type="text"></input>
1420 <input name="real" value="0" size="8" type="hidden"></input>
1433 # diff_to_html <filename> <filepath> { U | C } <comment>
1435 # Processes the output of diff to produce an HTML file representing either
1436 # context or unified diffs.
1445 print "$HTML<head>$STDHEAD"
1446 print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1448 if [[ $DIFFTYPE == "U" ]]; then
1454 <body id="SUNWwebrev">
1455 <a class="print" href="javascript:print()">Print this page</a>
1462 /^\
+\
+\
+ new
/ { next
}
1464 /^\
*\
*\
* old
/ { next
}
1465 /^\
*\
*\
*\
*/ { next
}
1466 /^
-------/ { printf "<center><h1>%s</h1></center>\n", $0; next
}
1467 /^\@\@.
*\@\@$
/ { printf "</pre><hr></hr><pre>\n";
1468 printf "<span class=\"newmarker\">%s</span>\n", $0;
1471 /^\
*\
*\
*/ { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1473 /^
---/ { printf "<span class=\"newmarker\">%s</span>\n", $0;
1475 /^\
+/ {printf "<span class=\"new\">%s</span>\n", $0; next
}
1476 /^
!/ {printf "<span class=\"changed\">%s</span>\n", $0; next
}
1477 /^
-/ {printf "<span class=\"removed\">%s</span>\n", $0; next
}
1478 {printf "%s\n", $0; next
}
1481 print "</pre></body></html>\n"
1486 # source_to_html { new | old } <filename>
1488 # Process a plain vanilla source file to transform it into an HTML file.
1495 print "$HTML<head>$STDHEAD"
1496 print "<title>$WNAME $WHICH $TNAME</title>"
1497 print "<body id=\"SUNWwebrev\">"
1499 html_quote | $AWK '{line
+= 1 ; printf "%4d %s\n", line
, $0 }'
1500 print "</pre></body></html>"
1504 # comments_from_wx {text|html} filepath
1506 # Given the pathname of a file, find its location in a "wx" active
1507 # file list and print the following comment. Output is either text or
1508 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1509 # are turned into URLs.
1511 # This is also used with Mercurial and the file list provided by hg-active.
1520 do getline
; while (NF
> 0)
1522 while (NF
> 0) { print
; getline
}
1526 if [[ -z $comm ]]; then
1527 comm="*** NO COMMENTS ***"
1530 if [[ $fmt == "text" ]]; then
1535 print -- "$comm" | html_quote | its2url
1540 # getcomments {text|html} filepath parentpath
1542 # Fetch the comments depending on what SCM mode we're
in.
1550 if [[ -n $Nflag ]]; then
1554 # Mercurial support uses a file list in wx format, so this
1555 # will be used there, too
1557 if [[ -n $wxfile ]]; then
1558 comments_from_wx
$fmt $p
1563 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1565 # Print out Code Inspection figures similar to sccs-prt(1) format.
1569 integer tot
=$1 ins
=$2 del
=$3 mod
=$4 unc
=$5
1571 if (( tot
== 1 )); then
1576 printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1577 $tot $str $ins $del $mod $unc
1582 # difflines <oldfile> <newfile>
1584 # Calculate and emit number of added, removed, modified and unchanged lines,
1585 # and total lines changed, the sum of added + removed + modified.
1589 integer tot mod del ins unc err
1592 eval $
( diff -e $1 $2 |
$AWK '
1593 # Change range of lines: N,Nc
1594 /^[0-9]*,[0-9]*c$/ {
1595 n=split(substr($1,1,length($1)-1), counts, ",");
1601 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1602 # following would be 5 - 3 = 2! Hence +1 for correction.
1604 r=(counts[2]-counts[1])+1;
1607 # Now count replacement lines: each represents a change instead
1608 # of a delete, so increment c and decrement r.
1610 while (getline != /^\.$/) {
1615 # If there were more replacement lines than original lines,
1616 # then r will be negative; in this case there are no deletions,
1617 # but there are r changes that should be counted as adds, and
1618 # since r is negative, subtract it from a and add it to c.
1626 # If there were more original lines than replacement lines, then
1627 # r will be positive; in this case, increment d by that much.
1637 # The first line is a replacement; any more are additions.
1638 if (getline != /^\.$/) {
1640 while (getline != /^\.$/) a++;
1645 # Add lines: both Na and N,Na
1647 while (getline != /^\.$/) a++;
1651 # Delete range of lines: N,Nd
1652 /^[0-9]*,[0-9]*d$/ {
1653 n=split(substr($1,1,length($1)-1), counts, ",");
1659 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1660 # following would be 5 - 3 = 2! Hence +1 for correction.
1662 r=(counts[2]-counts[1])+1;
1667 # Delete line: Nd. For example 10d says line 10 is deleted.
1668 /^[0-9]*d$/ {d++; next}
1670 # Should not get here!
1676 # Finish off - print results
1678 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1679 (c+d+a), c, d, a, error);
1682 # End of $AWK, Check to see if any trouble occurred.
1683 if (( $?
> 0 || err
> 0 )); then
1684 print
"Unexpected Error occurred reading" \
1685 "\`diff -e $1 $2\`: \$?=$?, err=" $err
1694 # Calculate unchanged lines
1696 if (( unc
> 0 )); then
1697 (( unc
-= del
+ mod
))
1701 print
"<span class=\"lineschanged\">"
1702 printCI
$tot $ins $del $mod $unc
1710 # Sets up webrev to source its information from a wx-formatted file.
1711 # Sets the global 'wxfile' variable.
1713 function flist_from_wx
1716 if [[ -n ${argfile%%/*} ]]; then
1718 # If the wx file pathname is relative then make it absolute
1719 # because the webrev does a "cd" later on.
1721 wxfile
=$PWD/$argfile
1726 $AWK '{ c = 1; print;
1728 if (NF == 0) { c = -c; continue }
1737 # Call hg-active to get the active list output in the wx active list format
1739 function hg_active_wxfile
1744 TMPFLIST
=/tmp
/$$.active
1745 $HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1750 # flist_from_mercurial
1751 # Call hg-active to get a wx-style active list, and hand it off to
1754 function flist_from_mercurial
1759 print
" File list from: hg-active -p $parent ...\c"
1760 if [[ ! -x $HG_ACTIVE ]]; then
1761 print
# Blank line for the \c above
1762 print
-u2 "Error: hg-active tool not found. Exiting"
1765 hg_active_wxfile
$child $parent
1767 # flist_from_wx prints the Done, so we don't have to.
1768 flist_from_wx
$TMPFLIST
1772 # Transform a specified 'git log' output format into a wx-like active list.
1779 TMPFLIST
=/tmp
/$$.active
1780 $PERL -e 'my (%files, %realfiles, $msg);
1781 my $parent = $ARGV[0];
1782 my $child = $ARGV[1];
1784 open(F, "git diff -M --name-status $parent..$child |");
1787 if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
1788 if ($1 >= 75) { # Probably worth treating as a rename
1789 $realfiles{$3} = $2;
1791 $realfiles{$3} = $3;
1792 $realfiles{$2} = $2;
1795 my $f = (split /\s+/, $_)[1];
1796 $realfiles{$f} = $f;
1801 my $state = 1; # 0|comments, 1|files
1802 open(F, "git whatchanged --pretty=format:%B $parent..$child |");
1806 my ($unused, $fname, $fname2) = split(/\t/, $_);
1807 $fname = $fname2 if defined($fname2);
1808 next if !defined($realfiles{$fname}); # No real change
1811 $files{$fname} .= $msg;
1815 $msg = /^\n/ ? "" : "\n";
1817 $msg .= "$_\n" if ($_);
1822 for (sort keys %files) {
1823 if ($realfiles{$_} ne $_) {
1824 print "$_ $realfiles{$_}\n$files{$_}\n\n";
1826 print "$_\n$files{$_}\n\n"
1828 }' ${parent} ${child} > $TMPFLIST
1835 # Build a wx-style active list, and hand it off to flist_from_wx
1837 function flist_from_git
1842 print
" File list from: git ...\c"
1843 git_wxfile
"$child" "$parent";
1845 # flist_from_wx prints the Done, so we don't have to.
1846 flist_from_wx
$TMPFLIST
1850 # flist_from_subversion
1852 # Generate the file list by extracting file names from svn status.
1854 function flist_from_subversion
1860 print
-u2 " File list from: svn status ... \c"
1861 svn status |
$AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1866 function env_from_flist
1868 [[ -r $FLIST ]] ||
return
1871 # Use "eval" to set env variables that are listed in the file
1872 # list. Then copy those into our local versions of those
1873 # variables if they have not been set already.
1875 eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1877 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1878 codemgr_ws
=$CODEMGR_WS
1883 # Check to see if CODEMGR_PARENT is set in the flist file.
1885 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1886 codemgr_parent
=$CODEMGR_PARENT
1887 export CODEMGR_PARENT
1891 function look_for_prog
1898 ppath
=$ppath:/usr
/sfw
/bin
:/usr
/bin
:/usr
/sbin
1899 ppath
=$ppath:/opt
/onbld
/bin
1900 ppath
=$ppath:/opt
/onbld
/bin
/`uname -p`
1902 PATH
=$ppath prog
=`whence $progname`
1903 if [[ -n $prog ]]; then
1908 function get_file_mode
1911 if (@stat = stat($ARGV[0])) {
1912 $mode = $stat[2] & 0777;
1913 printf "%03o\n", $mode;
1921 function build_old_new_mercurial
1930 # Get old file mode, from the parent revision manifest entry.
1931 # Mercurial only stores a "file is executable" flag, but the
1932 # manifest will display an octal mode "644" or "755".
1934 if [[ "$PDIR" == "." ]]; then
1939 file=`echo $file | $SED 's#/#\\\/#g'`
1940 # match the exact filename, and return only the permission digits
1941 old_mode
=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
1942 < $HG_PARENT_MANIFEST`
1945 # Get new file mode, directly from the filesystem.
1946 # Normalize the mode to match Mercurial's behavior.
1948 new_mode
=`get_file_mode $CWS/$DIR/$F`
1949 if [[ -n "$new_mode" ]]; then
1950 if [[ "$new_mode" = *[1357]* ]]; then
1958 # new version of the file.
1960 rm -rf $newdir/$DIR/$F
1961 if [[ -e $CWS/$DIR/$F ]]; then
1962 cp $CWS/$DIR/$F $newdir/$DIR/$F
1963 if [[ -n $new_mode ]]; then
1964 chmod $new_mode $newdir/$DIR/$F
1966 # should never happen
1967 print
-u2 "ERROR: set mode of $newdir/$DIR/$F"
1972 # parent's version of the file
1974 # Note that we get this from the last version common to both
1975 # ourselves and the parent. References are via $CWS since we have no
1976 # guarantee that the parent workspace is reachable via the filesystem.
1978 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
1979 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1980 elif [[ -n $HG_PARENT ]]; then
1981 hg
cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
1982 $olddir/$PDIR/$PF 2>/dev
/null
1984 if (( $?
!= 0 )); then
1985 rm -f $olddir/$PDIR/$PF
1987 if [[ -n $old_mode ]]; then
1988 chmod $old_mode $olddir/$PDIR/$PF
1990 # should never happen
1991 print
-u2 "ERROR: set mode of $olddir/$PDIR/$PF"
1997 function build_old_new_git
2012 # Get old file and its mode from the git object tree
2014 if [[ "$PDIR" == "." ]]; then
2020 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2021 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2023 $GIT ls-tree
$GIT_PARENT $file |
read o_mode
type o_object junk
2024 $GIT cat-file
$type $o_object > $olddir/$file 2>/dev
/null
2026 if (( $?
!= 0 )); then
2028 elif [[ -n $o_mode ]]; then
2029 # Strip the first 3 digits, to get a regular octal mode
2030 o_mode
=${o_mode/???/}
2031 chmod $o_mode $olddir/$file
2033 # should never happen
2034 print
-u2 "ERROR: set mode of $olddir/$file"
2039 # new version of the file.
2041 if [[ "$DIR" == "." ]]; then
2046 rm -rf $newdir/$file
2048 if [[ -e $CWS/$DIR/$F ]]; then
2049 cp $CWS/$DIR/$F $newdir/$DIR/$F
2050 chmod $
(get_file_mode
$CWS/$DIR/$F) $newdir/$DIR/$F
2055 function build_old_new_subversion
2060 # Snag new version of file.
2061 rm -f $newdir/$DIR/$F
2062 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2064 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2065 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2067 # Get the parent's version of the file.
2068 svn status
$CWS/$DIR/$F |
read stat
file
2069 if [[ $stat != "A" ]]; then
2070 svn
cat -r BASE
$CWS/$DIR/$F > $olddir/$PDIR/$PF
2075 function build_old_new_unknown
2081 # Snag new version of file.
2083 rm -f $newdir/$DIR/$F
2084 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2087 # Snag the parent's version of the file.
2089 if [[ -f $PWS/$PDIR/$PF ]]; then
2090 rm -f $olddir/$PDIR/$PF
2091 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2095 function build_old_new
2105 typeset olddir
="$WDIR/raw_files/old"
2106 typeset newdir
="$WDIR/raw_files/new"
2108 mkdir
-p $olddir/$PDIR
2109 mkdir
-p $newdir/$DIR
2111 if [[ $SCM_MODE == "mercurial" ]]; then
2112 build_old_new_mercurial
"$olddir" "$newdir"
2113 elif [[ $SCM_MODE == "git" ]]; then
2114 build_old_new_git
"$olddir" "$newdir"
2115 elif [[ $SCM_MODE == "subversion" ]]; then
2116 build_old_new_subversion
"$olddir" "$newdir"
2117 elif [[ $SCM_MODE == "unknown" ]]; then
2118 build_old_new_unknown
"$olddir" "$newdir"
2121 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2122 print
"*** Error: file not in parent or child"
2134 print
'Usage:\twebrev [common-options]
2135 webrev [common-options] ( <file> | - )
2136 webrev [common-options] -w <wx file>
2139 -c <revision>: generate webrev for single revision (git only)
2140 -C <filename>: Use <filename> for the information tracking configuration.
2141 -D: delete remote webrev
2142 -h <revision>: specify "head" revision for comparison (git only)
2143 -i <filename>: Include <filename> in the index.html file.
2144 -I <filename>: Use <filename> for the information tracking registry.
2145 -n: do not generate the webrev (useful with -U)
2146 -O: Print bugids/arc cases suitable for OpenSolaris.
2147 -o <outdir>: Output webrev to specified directory.
2148 -p <compare-against>: Use specified parent wkspc or basis for comparison
2149 -t <remote_target>: Specify remote destination for webrev upload
2150 -U: upload the webrev to remote destination
2151 -w <wxfile>: Use specified wx active file.
2154 WDIR: Control the output directory.
2155 WEBREV_TRASH_DIR: Set directory for webrev delete.
2158 CODEMGR_WS: Workspace location.
2159 CODEMGR_PARENT: Parent workspace location.
2167 # Main program starts here
2171 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2175 PATH
=$
(/bin
/dirname "$(whence $0)"):$PATH
2177 [[ -z $WDIFF ]] && WDIFF
=`look_for_prog wdiff`
2178 [[ -z $WX ]] && WX
=`look_for_prog wx`
2179 [[ -z $HG_ACTIVE ]] && HG_ACTIVE
=`look_for_prog hg-active`
2180 [[ -z $GIT ]] && GIT
=`look_for_prog git`
2181 [[ -z $WHICH_SCM ]] && WHICH_SCM
=`look_for_prog which_scm`
2182 [[ -z $CODEREVIEW ]] && CODEREVIEW
=`look_for_prog codereview`
2183 [[ -z $PS2PDF ]] && PS2PDF
=`look_for_prog ps2pdf`
2184 [[ -z $PERL ]] && PERL
=`look_for_prog perl`
2185 [[ -z $RSYNC ]] && RSYNC
=`look_for_prog rsync`
2186 [[ -z $SCCS ]] && SCCS
=`look_for_prog sccs`
2187 [[ -z $AWK ]] && AWK
=`look_for_prog nawk`
2188 [[ -z $AWK ]] && AWK
=`look_for_prog gawk`
2189 [[ -z $AWK ]] && AWK
=`look_for_prog awk`
2190 [[ -z $SCP ]] && SCP
=`look_for_prog scp`
2191 [[ -z $SED ]] && SED
=`look_for_prog sed`
2192 [[ -z $SFTP ]] && SFTP
=`look_for_prog sftp`
2193 [[ -z $SORT ]] && SORT
=`look_for_prog sort`
2194 [[ -z $MKTEMP ]] && MKTEMP
=`look_for_prog mktemp`
2195 [[ -z $GREP ]] && GREP
=`look_for_prog grep`
2196 [[ -z $FIND ]] && FIND
=`look_for_prog find`
2197 [[ -z $MANDOC ]] && MANDOC
=`look_for_prog mandoc`
2198 [[ -z $COL ]] && COL
=`look_for_prog col`
2200 # set name of trash directory for remote webrev deletion
2202 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR
=$WEBREV_TRASH_DIR
2204 if [[ ! -x $PERL ]]; then
2205 print
-u2 "Error: No perl interpreter found. Exiting."
2209 if [[ ! -x $WHICH_SCM ]]; then
2210 print
-u2 "Error: Could not find which_scm. Exiting."
2215 # These aren't fatal, but we want to note them to the user.
2216 # We don't warn on the absence of 'wx' until later when we've
2217 # determined that we actually need to try to invoke it.
2219 [[ ! -x $CODEREVIEW ]] && print
-u2 "WARNING: codereview(1) not found."
2220 [[ ! -x $PS2PDF ]] && print
-u2 "WARNING: ps2pdf(1) not found."
2221 [[ ! -x $WDIFF ]] && print
-u2 "WARNING: wdiff not found."
2223 # Declare global total counters.
2224 integer TOTL TINS TDEL TMOD TUNC
2226 # default remote host for upload/delete
2227 typeset
-r DEFAULT_REMOTE_HOST
="cr.opensolaris.org"
2228 # prefixes for upload targets
2229 typeset
-r rsync_prefix
="rsync://"
2230 typeset
-r ssh_prefix
="ssh://"
2253 # NOTE: when adding/removing options it is necessary to sync the list
2254 # with usr/src/tools/onbld/hgext/cdm.py
2256 while getopts "c:C:Dh:i:I:lnNo:Op:t:Uw" opt
2260 codemgr_head
=$OPTARG
2261 codemgr_parent
=$OPTARG~
1;;
2269 codemgr_head
=$OPTARG;;
2272 INCLUDE_FILE
=$OPTARG;;
2284 # Strip the trailing slash to correctly form remote target.
2288 codemgr_parent
=$OPTARG;;
2291 remote_target
=$OPTARG;;
2303 if [[ -n $wflag && -n $lflag ]]; then
2307 # more sanity checking
2308 if [[ -n $nflag && -z $Uflag ]]; then
2309 print
"it does not make sense to skip webrev generation" \
2314 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2315 echo "remote target has to be used only for upload or delete"
2320 # For the invocation "webrev -n -U" with no other options, webrev will assume
2321 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2322 # $(basename ${CWS}). So we need to get CWS set before we skip any remaining
2325 $WHICH_SCM |
read SCM_MODE junk ||
exit 1
2326 if [[ $SCM_MODE == "mercurial" ]]; then
2328 # Mercurial priorities:
2329 # 1. hg root from CODEMGR_WS environment variable
2330 # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under
2331 # usr/closed when we run webrev
2332 # 2. hg root from directory of invocation
2334 if [[ ${PWD} =~
"usr/closed" ]]; then
2335 testparent
=${CODEMGR_WS}/usr
/closed
2336 # If we're in OpenSolaris mode, we enforce a minor policy:
2337 # help to make sure the reviewer doesn't accidentally publish
2338 # source which is under usr/closed
2339 if [[ -n "$Oflag" ]]; then
2340 print
-u2 "OpenSolaris output not permitted with" \
2341 "usr/closed changes"
2345 testparent
=${CODEMGR_WS}
2347 [[ -z $codemgr_ws && -n $testparent ]] && \
2348 codemgr_ws
=$
(hg root
-R $testparent 2>/dev
/null
)
2349 [[ -z $codemgr_ws ]] && codemgr_ws
=$
(hg root
2>/dev
/null
)
2351 elif [[ $SCM_MODE == "git" ]]; then
2354 # 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2355 # 2. git rev-parse --git-dir from directory of invocation
2357 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2358 codemgr_ws
=$
($GIT --git-dir=$CODEMGR_WS/.git rev-parse
--git-dir \
2360 [[ -z $codemgr_ws ]] && \
2361 codemgr_ws
=$
($GIT rev-parse
--git-dir 2>/dev
/null
)
2363 if [[ "$codemgr_ws" == ".git" ]]; then
2364 codemgr_ws
="${PWD}/${codemgr_ws}"
2367 if [[ "$codemgr_ws" = *"/.git" ]]; then
2368 codemgr_ws
=$
(dirname $codemgr_ws) # Lose the '/.git'
2371 elif [[ $SCM_MODE == "subversion" ]]; then
2373 # Subversion priorities:
2374 # 1. CODEMGR_WS from environment
2375 # 2. Relative path from current directory to SVN repository root
2377 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn
]]; then
2380 svn info |
while read line
; do
2381 if [[ $line == "URL: "* ]]; then
2383 elif [[ $line == "Repository Root: "* ]]; then
2384 repo
=${line#Repository Root: }
2394 # If no SCM has been determined, take either the environment setting
2395 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2397 if [[ -z ${CWS} ]]; then
2398 CWS
=${CODEMGR_WS:-.}
2402 # If the command line options indicate no webrev generation, either
2403 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2404 # ton of logic we can skip.
2406 # Instead of increasing indentation, we intentionally leave this loop
2407 # body open here, and exit via break from multiple points within.
2408 # Search for DO_EVERYTHING below to find the break points and closure.
2410 for do_everything
in 1; do
2412 # DO_EVERYTHING: break point
2413 if [[ -n $nflag ||
( -z $Uflag && -n $Dflag ) ]]; then
2418 # If this manually set as the parent, and it appears to be an earlier webrev,
2419 # then note that fact and set the parent to the raw_files/new subdirectory.
2421 if [[ -n $pflag && -d $codemgr_parent/raw_files
/new
]]; then
2422 parent_webrev
=$
(readlink
-f "$codemgr_parent")
2423 codemgr_parent
=$
(readlink
-f "$codemgr_parent/raw_files/new")
2426 if [[ -z $wflag && -z $lflag ]]; then
2427 shift $
(($OPTIND - 1))
2429 if [[ $1 == "-" ]]; then
2434 elif [[ -n $1 ]]; then
2435 if [[ ! -r $1 ]]; then
2436 print
-u2 "$1: no such file or not readable"
2450 # Before we go on to further consider -l and -w, work out which SCM we think
2454 mercurial|git|subversion
)
2457 if [[ $flist_mode == "auto" ]]; then
2458 print
-u2 "Unable to determine SCM in use and file list not specified"
2459 print
-u2 "See which_scm(1) for SCM detection information."
2464 if [[ $flist_mode == "auto" ]]; then
2465 print
-u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2471 print
-u2 " SCM detected: $SCM_MODE"
2473 if [[ -n $wflag ]]; then
2475 # If the -w is given then assume the file list is in Bonwick's "wx"
2476 # command format, i.e. pathname lines alternating with SCCS comment
2477 # lines with blank lines as separators. Use the SCCS comments later
2478 # in building the index.html file.
2480 shift $
(($OPTIND - 1))
2482 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2483 if [[ -r $CODEMGR_WS/wx
/active
]]; then
2484 wxfile
=$CODEMGR_WS/wx
/active
2488 [[ -z $wxfile ]] && print
-u2 "wx file not specified, and could not " \
2489 "be auto-detected (check \$CODEMGR_WS)" && exit 1
2491 if [[ ! -r $wxfile ]]; then
2492 print
-u2 "$wxfile: no such file or not readable"
2496 print
-u2 " File list from: wx 'active' file '$wxfile' ... \c"
2497 flist_from_wx
$wxfile
2499 if [[ -n "$*" ]]; then
2502 elif [[ $flist_mode == "stdin" ]]; then
2503 print
-u2 " File list from: standard input"
2504 elif [[ $flist_mode == "file" ]]; then
2505 print
-u2 " File list from: $flist_file"
2508 if [[ $# -gt 0 ]]; then
2509 print
-u2 "WARNING: unused arguments: $*"
2513 # Before we entered the DO_EVERYTHING loop, we should have already set CWS
2514 # and CODEMGR_WS as needed. Here, we set the parent workspace.
2516 if [[ $SCM_MODE == "mercurial" ]]; then
2518 # Parent can either be specified with -p
2519 # Specified with CODEMGR_PARENT in the environment
2520 # or taken from hg's default path.
2523 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2524 codemgr_parent
=$CODEMGR_PARENT
2527 if [[ -z $codemgr_parent ]]; then
2528 codemgr_parent
=`hg path -R $codemgr_ws default 2>/dev/null`
2534 # If the parent is a webrev, we want to do some things against
2535 # the natural workspace parent (file list, comments, etc)
2537 if [[ -n $parent_webrev ]]; then
2538 real_parent
=$
(hg path
-R $codemgr_ws default
2>/dev
/null
)
2544 # If hg-active exists, then we run it. In the case of no explicit
2545 # flist given, we'll use it for our comments. In the case of an
2546 # explicit flist given we'll try to use it for comments for any
2547 # files mentioned in the flist.
2549 if [[ -z $flist_done ]]; then
2550 flist_from_mercurial
$CWS $real_parent
2555 # If we have a file list now, pull out any variables set
2556 # therein. We do this now (rather than when we possibly use
2557 # hg-active to find comments) to avoid stomping specifications
2558 # in the user-specified flist.
2560 if [[ -n $flist_done ]]; then
2565 # Only call hg-active if we don't have a wx formatted file already
2567 if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2568 print
" Comments from: hg-active -p $real_parent ...\c"
2569 hg_active_wxfile
$CWS $real_parent
2574 # At this point we must have a wx flist either from hg-active,
2575 # or in general. Use it to try and find our parent revision,
2576 # if we don't have one.
2578 if [[ -z $HG_PARENT ]]; then
2579 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2583 # If we still don't have a parent, we must have been given a
2584 # wx-style active list with no HG_PARENT specification, run
2585 # hg-active and pull an HG_PARENT out of it, ignore the rest.
2587 if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2588 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2589 eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2590 elif [[ -z $HG_PARENT ]]; then
2591 print
-u2 "Error: Cannot discover parent revision"
2595 pnode
=$
(trim_digest
$HG_PARENT)
2596 PRETTY_PWS
="${PWS} (at ${pnode})"
2597 cnode
=$
(hg parent
-R $codemgr_ws --template '{node|short}' \
2599 PRETTY_CWS
="${CWS} (at ${cnode})"}
2600 elif [[ $SCM_MODE == "git
" ]]; then
2601 # Check that "head" revision specified with -c or -h is sane
2602 if [[ -n $cflag || -n $hflag ]]; then
2603 head_rev=$($GIT rev-parse --verify --quiet "$codemgr_head")
2604 if [[ -z $head_rev ]]; then
2605 print -u2 "Error
: bad revision
${codemgr_head}"
2610 if [[ -z $codemgr_head ]]; then
2611 codemgr_head="HEAD
";
2614 # Parent can either be specified with -p, or specified with
2615 # CODEMGR_PARENT in the environment.
2616 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2617 codemgr_parent=$CODEMGR_PARENT
2620 # Try to figure out the parent based on the branch the current
2621 # branch is tracking, if we fail, use origin/master
2622 this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2623 par_branch="origin
/master
"
2625 # If we're not on a branch there's nothing we can do
2626 if [[ $this_branch != "(no branch
)" ]]; then
2628 --format='%(refname:short) %(upstream:short)' \
2630 while read local remote; do
2631 if [[ "$local" == "$this_branch" ]]; then
2632 par_branch="$remote"
2637 if [[ -z $codemgr_parent ]]; then
2638 codemgr_parent=$par_branch
2643 # If the parent is a webrev, we want to do some things against
2644 # the natural workspace parent (file list, comments, etc)
2646 if [[ -n $parent_webrev ]]; then
2647 real_parent=$par_branch
2652 if [[ -z $flist_done ]]; then
2653 flist_from_git "$codemgr_head" "$real_parent"
2658 # If we have a file list now, pull out any variables set
2661 if [[ -n $flist_done ]]; then
2666 # If we don't have a wx-format file list, build one we can pull change
2669 if [[ -z $wxfile ]]; then
2670 print " Comments from
: git...\c
"
2671 git_wxfile "$codemgr_head" "$real_parent"
2675 if [[ -z $GIT_PARENT ]]; then
2676 GIT_PARENT=$($GIT merge-base "$real_parent" "$codemgr_head")
2678 if [[ -z $GIT_PARENT ]]; then
2679 print -u2 "Error
: Cannot discover parent revision
"
2683 pnode=$(trim_digest $GIT_PARENT)
2685 if [[ -n $cflag ]]; then
2686 PRETTY_PWS="previous revision
(at ${pnode})"
2687 elif [[ $real_parent == */* ]]; then
2688 origin=$(echo $real_parent | cut -d/ -f1)
2689 origin=$($GIT remote -v | \
2690 $AWK '$1 == "'$origin'" { print $2; exit }')
2691 PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2692 elif [[ -n $pflag && -z $parent_webrev ]]; then
2693 PRETTY_PWS="${CWS} (explicit revision
${pnode})"
2695 PRETTY_PWS="${PWS} (at ${pnode})"
2698 cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 \
2699 ${codemgr_head} 2>/dev/null)
2701 if [[ -n $cflag || -n $hflag ]]; then
2702 PRETTY_CWS="${CWS} (explicit
head at ${cnode})"
2704 PRETTY_CWS="${CWS} (at ${cnode})"
2706 elif [[ $SCM_MODE == "subversion
" ]]; then
2709 # We only will have a real parent workspace in the case one
2710 # was specified (be it an older webrev, or another checkout).
2712 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2714 if [[ -z $flist_done && $flist_mode == "auto
" ]]; then
2715 flist_from_subversion $CWS $OLDPWD
2718 if [[ $SCM_MODE == "unknown
" ]]; then
2719 print -u2 " Unknown
type of SCM
in use
"
2721 print -u2 " Unsupported SCM
in use
: $SCM_MODE"
2726 if [[ -z $CODEMGR_WS ]]; then
2727 print -u2 "SCM not detected
/supported and
" \
2728 "CODEMGR_WS not specified
"
2732 if [[ -z $CODEMGR_PARENT ]]; then
2733 print -u2 "SCM not detected
/supported and
" \
2734 "CODEMGR_PARENT not specified
"
2743 # If the user didn't specify a -i option, check to see if there is a
2744 # webrev-info file in the workspace directory.
2746 if [[ -z $iflag && -r "$CWS/webrev-info
" ]]; then
2748 INCLUDE_FILE="$CWS/webrev-info
"
2751 if [[ -n $iflag ]]; then
2752 if [[ ! -r $INCLUDE_FILE ]]; then
2753 print -u2 "include
file '$INCLUDE_FILE' does not exist or is
" \
2758 # $INCLUDE_FILE may be a relative path, and the script alters
2759 # PWD, so we just stash a copy in /tmp.
2761 cp $INCLUDE_FILE /tmp/$$.include
2765 # DO_EVERYTHING: break point
2766 if [[ -n $Nflag ]]; then
2771 typeset -r its_sed_script=/tmp/$$.its_sed
2773 if [[ -z $nflag ]]; then
2774 DEFREGFILE="$
(/bin
/dirname "$(whence $0)")/..
/etc
/its.reg
"
2775 if [[ -n $Iflag ]]; then
2777 elif [[ -r $HOME/.its.reg ]]; then
2778 REGFILE=$HOME/.its.reg
2782 if [[ ! -r $REGFILE ]]; then
2783 print "ERROR
: Unable to
read database registry
file $REGFILE"
2785 elif [[ $REGFILE != $DEFREGFILE ]]; then
2786 print " its.reg from
: $REGFILE"
2789 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do
2794 if [[ $name == PREFIX ]]; then
2796 valid_prefixes="${p} ${valid_prefixes}"
2798 itsinfo["${p}_${name}"]="${value}"
2803 DEFCONFFILE="$
(/bin
/dirname "$(whence $0)")/..
/etc
/its.conf
"
2804 CONFFILES=$DEFCONFFILE
2805 if [[ -r $HOME/.its.conf ]]; then
2806 CONFFILES="${CONFFILES} $HOME/.its.conf
"
2808 if [[ -n $Cflag ]]; then
2809 CONFFILES="${CONFFILES} ${ITSCONF}"
2813 for cf in ${CONFFILES}; do
2814 if [[ ! -r $cf ]]; then
2815 print "ERROR
: Unable to
read database configuration
file $cf"
2817 elif [[ $cf != $DEFCONFFILE ]]; then
2818 print " its.conf
: reading
$cf"
2820 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do
2826 # If an information tracking system is explicitly identified by prefix,
2827 # we want to disregard the specified priorities and resolve it accordingly.
2829 # To that end, we'll build a sed script to do each valid prefix in turn.
2831 for p in ${valid_prefixes}; do
2833 # When an informational URL was provided, translate it to a
2834 # hyperlink. When omitted, simply use the prefix text.
2836 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2837 itsinfo["${p}_INFO
"]=${p}
2839 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a
>"
2843 # Assume that, for this invocation of webrev, all references
2844 # to this information tracking system should resolve through
2847 # If the caller specified -O, then always use EXTERNAL_URL.
2849 # Otherwise, look in the list of domains for a matching
2852 [[ -z $Oflag ]] && for d in ${its_domain}; do
2853 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2854 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2858 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2859 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2863 # Turn the destination URL into a hyperlink
2865 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a
>"
2867 # The character class below contains a literal tab
2868 print "/^
${p}[: ]/ {
2869 s
;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2870 s
;^
${p};${itsinfo[${p}_INFO]};
2871 }" >> ${its_sed_script}
2875 # The previous loop took care of explicit specification. Now use
2876 # the configured priorities to attempt implicit translations.
2878 for p in ${its_priority}; do
2879 print "/^
${itsinfo[${p}_REGEX]}[ ]/ {
2880 s
;^
${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2881 }" >> ${its_sed_script}
2886 # Search for DO_EVERYTHING above for matching "for" statement
2887 # and explanation of this terminator.
2894 WDIR=${WDIR:-$CWS/webrev}
2897 # Name of the webrev, derived from the workspace name or output directory;
2898 # in the future this could potentially be an option.
2900 if [[ -n $oflag ]]; then
2906 # Make sure remote target is well formed for remote upload/delete.
2907 if [[ -n $Dflag || -n $Uflag ]]; then
2909 # If remote target is not specified, build it from scratch using
2910 # the default values.
2912 if [[ -z $tflag ]]; then
2913 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2916 # Check upload target prefix first.
2918 if [[ "${remote_target}" != ${rsync_prefix}* &&
2919 "${remote_target}" != ${ssh_prefix}* ]]; then
2920 print "ERROR
: invalid prefix of upload URI
" \
2925 # If destination specification is not in the form of
2926 # host_spec:remote_dir then assume it is just remote hostname
2927 # and append a colon and destination directory formed from
2928 # local webrev directory name.
2930 typeset target_no_prefix=${remote_target##*://}
2931 if [[ ${target_no_prefix} == *:* ]]; then
2932 if [[ "${remote_target}" == *: ]]; then
2933 remote_target=${remote_target}${WNAME}
2936 if [[ ${target_no_prefix} == */* ]]; then
2937 print "ERROR
: badly formed upload URI
" \
2941 remote_target=${remote_target}:${WNAME}
2947 # Strip trailing slash. Each upload method will deal with directory
2948 # specification separately.
2950 remote_target=${remote_target%/}
2954 # Option -D by itself (option -U not present) implies no webrev generation.
2956 if [[ -z $Uflag && -n $Dflag ]]; then
2962 # Do not generate the webrev, just upload it or delete it.
2964 if [[ -n $nflag ]]; then
2965 if [[ -n $Dflag ]]; then
2967 (( $? == 0 )) || exit $?
2969 if [[ -n $Uflag ]]; then
2975 if [ "${WDIR%%/*}" ]; then
2979 if [[ ! -d $WDIR ]]; then
2981 (( $? != 0 )) && exit 1
2985 # Summarize what we're going to do.
2987 print " Workspace
: ${PRETTY_CWS:-$CWS}"
2988 if [[ -n $parent_webrev ]]; then
2989 print "Compare against
: webrev
at $parent_webrev"
2991 print "Compare against
: ${PRETTY_PWS:-$PWS}"
2994 [[ -n $INCLUDE_FILE ]] && print " Including
: $INCLUDE_FILE"
2995 print " Output to
: $WDIR"
2998 # Save the file list in the webrev dir
3000 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
3002 rm -f $WDIR/$WNAME.patch
3003 rm -f $WDIR/$WNAME.ps
3004 rm -f $WDIR/$WNAME.pdf
3006 touch $WDIR/$WNAME.patch
3008 print " Output Files
:"
3011 # Clean up the file list: Remove comments, blank lines and env variables.
3013 $SED -e "s
/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean
3014 FLIST
=/tmp
/$$.flist.clean
3017 # For Mercurial, create a cache of manifest entries.
3019 if [[ $SCM_MODE == "mercurial" ]]; then
3021 # Transform the FLIST into a temporary sed script that matches
3022 # relevant entries in the Mercurial manifest as follows:
3023 # 1) The script will be used against the parent revision manifest,
3024 # so for FLIST lines that have two filenames (a renamed file)
3025 # keep only the old name.
3026 # 2) Escape all forward slashes the filename.
3027 # 3) Change the filename into another sed command that matches
3028 # that file in "hg manifest -v" output: start of line, three
3029 # octal digits for file permissions, space, a file type flag
3030 # character, space, the filename, end of line.
3031 # 4) Eliminate any duplicate entries. (This can occur if a
3032 # file has been used as the source of an hg cp and it's
3033 # also been modified in the same changeset.)
3035 SEDFILE
=/tmp
/$$.manifest.
sed
3039 s#^.*$#/^... . &$/p#
3040 ' < $FLIST |
$SORT -u > $SEDFILE
3043 # Apply the generated script to the output of "hg manifest -v"
3044 # to get the relevant subset for this webrev.
3046 HG_PARENT_MANIFEST
=/tmp
/$$.manifest
3047 hg
-R $CWS manifest
-v -r $HG_PARENT |
3048 $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
3052 # First pass through the files: generate the per-file webrev HTML-files.
3054 cat $FLIST |
while read LINE
3060 # Normally, each line in the file list is just a pathname of a
3061 # file that has been modified or created in the child. A file
3062 # that is renamed in the child workspace has two names on the
3063 # line: new name followed by the old name.
3068 if [[ $# -eq 2 ]]; then
3069 PP
=$2 # old filename
3070 if [[ -f $PP ]]; then
3071 oldname
=" (copied from $PP)"
3073 oldname
=" (renamed from $PP)"
3078 if [[ $PDIR == $PP ]]; then
3079 PDIR
="." # File at root of workspace
3085 if [[ $DIR == $P ]]; then
3086 DIR
="." # File at root of workspace
3093 if [[ "$DIR" == "$P" ]]; then
3094 DIR
="." # File at root of workspace
3104 COMM
=`getcomments html $P $PP`
3106 print
"\t$P$oldname\n\t\t\c"
3108 # Make the webrev mirror directory if necessary
3112 # We stash old and new files into parallel directories in $WDIR
3113 # and do our diffs there. This makes it possible to generate
3114 # clean looking diffs which don't have absolute paths present.
3117 build_old_new
"$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3121 # Keep the old PWD around, so we can safely switch back after
3122 # diff generation, such that build_old_new runs in a
3123 # consistent environment.
3129 # The "git apply" command does not tolerate the spurious
3130 # "./" that we otherwise insert; be careful not to include
3131 # it in the paths that we pass to diff(1).
3133 if [[ $PDIR == "." ]]; then
3138 if [[ $DIR == "." ]]; then
3145 cmp $ofile $nfile > /dev
/null
2>&1
3146 if [[ $?
== 0 && $rename == 1 ]]; then
3151 # If we have old and new versions of the file then run the appropriate
3152 # diffs. This is complicated by a couple of factors:
3154 # - renames must be handled specially: we emit a 'remove'
3155 # diff and an 'add' diff
3156 # - new files and deleted files must be handled specially
3157 # - GNU patch doesn't interpret the output of illumos diff
3158 # properly when it comes to adds and deletes. We need to
3159 # do some "cleansing" transformations:
3160 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@
3161 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@
3163 cleanse_rmfile
="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3164 cleanse_newfile
="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3166 rm -f $WDIR/$DIR/$F.
patch
3167 if [[ -z $rename ]]; then
3168 if [ ! -f "$ofile" ]; then
3169 diff -u /dev
/null
$nfile | sh
-c "$cleanse_newfile" \
3170 > $WDIR/$DIR/$F.
patch
3171 elif [ ! -f "$nfile" ]; then
3172 diff -u $ofile /dev
/null | sh
-c "$cleanse_rmfile" \
3173 > $WDIR/$DIR/$F.
patch
3175 diff -u $ofile $nfile > $WDIR/$DIR/$F.
patch
3178 diff -u $ofile /dev
/null | sh
-c "$cleanse_rmfile" \
3179 > $WDIR/$DIR/$F.
patch
3181 diff -u /dev
/null
$nfile | sh
-c "$cleanse_newfile" \
3182 >> $WDIR/$DIR/$F.
patch
3186 # Tack the patch we just made onto the accumulated patch for the
3189 cat $WDIR/$DIR/$F.
patch >> $WDIR/$WNAME.
patch
3192 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3193 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3194 diff_to_html
$F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3195 > $WDIR/$DIR/$F.cdiff.html
3198 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3199 diff_to_html
$F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3200 > $WDIR/$DIR/$F.udiff.html
3203 if [[ -x $WDIFF ]]; then
3205 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3206 $WDIR/$DIR/$F.wdiff.html
2>/dev
/null
3207 if [[ $?
-eq 0 ]]; then
3210 print
" wdiffs[fail]\c"
3214 sdiff_to_html
$ofile $nfile $F $DIR "$COMM" \
3215 > $WDIR/$DIR/$F.
sdiff.html
3219 rm -f $WDIR/$DIR/$F.cdiff
$WDIR/$DIR/$F.udiff
3220 difflines
$ofile $nfile > $WDIR/$DIR/$F.count
3221 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3222 # renamed file: may also have differences
3223 difflines
$ofile $nfile > $WDIR/$DIR/$F.count
3224 elif [[ -f $nfile ]]; then
3225 # new file: count added lines
3226 difflines
/dev
/null
$nfile > $WDIR/$DIR/$F.count
3227 elif [[ -f $ofile ]]; then
3228 # old file: count deleted lines
3229 difflines
$ofile /dev
/null
> $WDIR/$DIR/$F.count
3233 # Check if it's man page, and create plain text, html and raw (ascii)
3234 # output for the new version, as well as diffs against old version.
3236 if [[ -f "$nfile" && "$nfile" = *.
+([0-9])*([a-zA-Z
]) && \
3237 -x $MANDOC && -x $COL ]]; then
3238 $MANDOC -Tascii $nfile |
$COL -b > $nfile.man.txt
3239 source_to_html txt
< $nfile.man.txt
> $nfile.man.txt.html
3241 print
"$MANCSS" > $WDIR/raw_files
/new
/$DIR/man.css
3242 $MANDOC -Thtml -Ostyle=man.css
$nfile > $nfile.man.html
3244 $MANDOC -Tascii $nfile > $nfile.man.raw
3246 if [[ -f "$ofile" && -z $mv_but_nodiff ]]; then
3247 $MANDOC -Tascii $ofile |
$COL -b > $ofile.man.txt
3248 ${CDIFFCMD:-diff -bt -C 5} $ofile.man.txt \
3249 $nfile.man.txt
> $WDIR/$DIR/$F.man.cdiff
3250 diff_to_html
$F $DIR/$F "C" "$COMM" < \
3251 $WDIR/$DIR/$F.man.cdiff
> \
3252 $WDIR/$DIR/$F.man.cdiff.html
3253 print
" man-cdiffs\c"
3254 ${UDIFFCMD:-diff -bt -U 5} $ofile.man.txt \
3255 $nfile.man.txt
> $WDIR/$DIR/$F.man.udiff
3256 diff_to_html
$F $DIR/$F "U" "$COMM" < \
3257 $WDIR/$DIR/$F.man.udiff
> \
3258 $WDIR/$DIR/$F.man.udiff.html
3259 print
" man-udiffs\c"
3260 if [[ -x $WDIFF ]]; then
3261 $WDIFF -c "$COMM" -t "$WNAME Wdiff $DIR/$F" \
3262 $ofile.man.txt
$nfile.man.txt
> \
3263 $WDIR/$DIR/$F.man.wdiff.html
2>/dev
/null
3264 if [[ $?
-eq 0 ]]; then
3265 print
" man-wdiffs\c"
3267 print
" man-wdiffs[fail]\c"
3270 sdiff_to_html
$ofile.man.txt
$nfile.man.txt
$F.man
$DIR \
3271 "$COMM" > $WDIR/$DIR/$F.man.
sdiff.html
3272 print
" man-sdiffs\c"
3273 print
" man-frames\c"
3275 rm -f $ofile.man.txt
$nfile.man.txt
3276 rm -f $WDIR/$DIR/$F.man.cdiff
$WDIR/$DIR/$F.man.udiff
3280 # Now we generate the postscript for this file. We generate diffs
3281 # only in the event that there is delta, or the file is new (it seems
3282 # tree-killing to print out the contents of deleted files).
3284 if [[ -f $nfile ]]; then
3286 [[ ! -f $ofile ]] && ocr
=/dev
/null
3288 if [[ -z $mv_but_nodiff ]]; then
3289 textcomm
=`getcomments text $P $PP`
3290 if [[ -x $CODEREVIEW ]]; then
3291 $CODEREVIEW -y "$textcomm" \
3293 > /tmp
/$$.psfile
2>/dev
/null
&&
3294 cat /tmp
/$$.psfile
>> $WDIR/$WNAME.ps
3295 if [[ $?
-eq 0 ]]; then
3304 if [[ -f $ofile ]]; then
3305 source_to_html Old
$PP < $ofile > $WDIR/$DIR/$F-.html
3309 if [[ -f $nfile ]]; then
3310 source_to_html New
$P < $nfile > $WDIR/$DIR/$F.html
3319 frame_nav_js
> $WDIR/ancnav.js
3320 frame_navigation
> $WDIR/ancnav.html
3322 if [[ ! -f $WDIR/$WNAME.ps
]]; then
3323 print
" Generating PDF: Skipped: no output available"
3324 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3325 print
" Generating PDF: \c"
3326 fix_postscript
$WDIR/$WNAME.ps |
$PS2PDF - > $WDIR/$WNAME.pdf
3329 print
" Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3332 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3333 # delete it - prevent accidental publishing of closed source
3335 if [[ -n "$Oflag" ]]; then
3336 $FIND $WDIR -type d
-name closed
-exec /bin
/rm -rf {} \
;
3339 # Now build the index.html file that contains
3340 # links to the source files and their diffs.
3344 # Save total changed lines for Code Inspection.
3345 print
"$TOTL" > $WDIR/TotalChangedLines
3347 print
" index.html: \c"
3348 INDEXFILE
=$WDIR/index.html
3349 exec 3<&1 # duplicate stdout to FD3.
3350 exec 1<&- # Close stdout.
3351 exec > $INDEXFILE # Open stdout to index file.
3353 print
"$HTML<head>$STDHEAD"
3354 print
"<title>$WNAME</title>"
3356 print
"<body id=\"SUNWwebrev\">"
3357 print
"<div class=\"summary\">"
3358 print
"<h2>Code Review for $WNAME</h2>"
3363 # Get the preparer's name:
3365 # If the SCM detected is Mercurial, and the configuration property
3366 # ui.username is available, use that, but be careful to properly escape
3367 # angle brackets (HTML syntax characters) in the email address.
3369 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3370 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3373 if [[ "$SCM_MODE" == mercurial
]]; then
3374 preparer
=`hg showconfig ui.username 2>/dev/null`
3375 if [[ -n "$preparer" ]]; then
3376 preparer
="$(echo "$preparer" | html_quote)"
3379 if [[ -z "$preparer" ]]; then
3382 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3384 $gcos =~ s/\&/ucfirst($login)/e;
3385 printf "%s (%s)\n", $gcos, $login;
3387 printf "(unknown)\n";
3392 PREPDATE
=$
(LC_ALL
=C
/usr
/bin
/date +%Y-
%b-
%d\
%R\
%z\
%Z
)
3393 print
"<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3394 print
"<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3396 print
"<tr><th>Compare against:</th><td>"
3397 if [[ -n $parent_webrev ]]; then
3398 print
"webrev at $parent_webrev"
3400 print
"${PRETTY_PWS:-$PWS}"
3403 print
"<tr><th>Summary of changes:</th><td>"
3404 printCI
$TOTL $TINS $TDEL $TMOD $TUNC
3407 if [[ -f $WDIR/$WNAME.
patch ]]; then
3408 wpatch_url
="$(print $WNAME.patch | url_encode)"
3409 print
"<tr><th>Patch of changes:</th><td>"
3410 print
"<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3412 if [[ -f $WDIR/$WNAME.pdf
]]; then
3413 wpdf_url
="$(print $WNAME.pdf | url_encode)"
3414 print
"<tr><th>Printable review:</th><td>"
3415 print
"<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3418 if [[ -n "$iflag" ]]; then
3419 print
"<tr><th>Author comments:</th><td><div>"
3421 print
"</div></td></tr>"
3427 # Second pass through the files: generate the rest of the index file
3429 cat $FLIST |
while read LINE
3434 if [[ $# == 2 ]]; then
3443 cmp $WDIR/raw_files
/old
/$PP $WDIR/raw_files
/new
/$P > /dev
/null
2>&1
3444 if [[ $?
== 0 && -n "$oldname" ]]; then
3449 if [[ $DIR == $P ]]; then
3450 DIR
="." # File at root of workspace
3453 # Avoid processing the same file twice.
3454 # It's possible for renamed files to
3455 # appear twice in the file list
3461 # If there's a diffs file, make diffs links
3463 if [[ -f $F.cdiff.html
]]; then
3464 cdiff_url
="$(print $P.cdiff.html | url_encode)"
3465 udiff_url
="$(print $P.udiff.html | url_encode)"
3466 sdiff_url
="$(print $P.sdiff.html | url_encode)"
3467 frames_url
="$(print $P.frames.html | url_encode)"
3468 print
"<a href=\"$cdiff_url\">Cdiffs</a>"
3469 print
"<a href=\"$udiff_url\">Udiffs</a>"
3470 if [[ -f $F.wdiff.html
&& -x $WDIFF ]]; then
3471 wdiff_url
="$(print $P.wdiff.html | url_encode)"
3472 print
"<a href=\"$wdiff_url\">Wdiffs</a>"
3474 print
"<a href=\"$sdiff_url\">Sdiffs</a>"
3475 print
"<a href=\"$frames_url\">Frames</a>"
3477 print
" ------ ------"
3478 if [[ -x $WDIFF ]]; then
3481 print
" ------ ------"
3484 # If there's an old file, make the link
3486 if [[ -f $F-.html
]]; then
3487 oldfile_url
="$(print $P-.html | url_encode)"
3488 print
"<a href=\"$oldfile_url\">Old</a>"
3493 # If there's an new file, make the link
3495 if [[ -f $F.html
]]; then
3496 newfile_url
="$(print $P.html | url_encode)"
3497 print
"<a href=\"$newfile_url\">New</a>"
3502 if [[ -f $F.
patch ]]; then
3503 patch_url
="$(print $P.patch | url_encode)"
3504 print
"<a href=\"$patch_url\">Patch</a>"
3509 if [[ -f $WDIR/raw_files
/new
/$P ]]; then
3510 rawfiles_url
="$(print raw_files/new/$P | url_encode)"
3511 print
"<a href=\"$rawfiles_url\">Raw</a>"
3518 # For renamed files, clearly state whether or not they are modified
3519 if [[ -f "$oldname" ]]; then
3520 if [[ -n "$mv_but_nodiff" ]]; then
3521 print
"<i>(copied from $oldname)</i>"
3523 print
"<i>(copied and modified from $oldname)</i>"
3525 elif [[ -n "$oldname" ]]; then
3526 if [[ -n "$mv_but_nodiff" ]]; then
3527 print
"<i>(renamed from $oldname)</i>"
3529 print
"<i>(renamed and modified from $oldname)</i>"
3533 # If there's an old file, but no new file, the file was deleted
3534 if [[ -f $F-.html
&& ! -f $F.html
]]; then
3535 print
" <i>(deleted)</i>"
3538 # Check for usr/closed and deleted_files/usr/closed
3539 if [ ! -z "$Oflag" ]; then
3540 if [[ $P == usr
/closed
/* || \
3541 $P == deleted_files
/usr
/closed
/* ]]; then
3542 print
" <i>Closed source: omitted from" \
3548 if [[ -f $F.man.cdiff.html || \
3549 -f $WDIR/raw_files
/new
/$P.man.txt.html
]]; then
3554 if [[ -f $F.man.cdiff.html
]]; then
3555 mancdiff_url
="$(print $P.man.cdiff.html | url_encode)"
3556 manudiff_url
="$(print $P.man.udiff.html | url_encode)"
3557 mansdiff_url
="$(print $P.man.sdiff.html | url_encode)"
3558 manframes_url
="$(print $P.man.frames.html | url_encode)"
3559 print
"<a href=\"$mancdiff_url\">Cdiffs</a>"
3560 print
"<a href=\"$manudiff_url\">Udiffs</a>"
3561 if [[ -f $F.man.wdiff.html
&& -x $WDIFF ]]; then
3562 manwdiff_url
="$(print $P.man.wdiff.html | url_encode)"
3563 print
"<a href=\"$manwdiff_url\">Wdiffs</a>"
3565 print
"<a href=\"$mansdiff_url\">Sdiffs</a>"
3566 print
"<a href=\"$manframes_url\">Frames</a>"
3567 elif [[ -n $manpage ]]; then
3568 print
" ------ ------"
3569 if [[ -x $WDIFF ]]; then
3572 print
" ------ ------"
3575 if [[ -f $WDIR/raw_files
/new
/$P.man.txt.html
]]; then
3576 mantxt_url
="$(print raw_files/new/$P.man.txt.html | url_encode)"
3577 print
"<a href=\"$mantxt_url\">TXT</a>"
3578 manhtml_url
="$(print raw_files/new/$P.man.html | url_encode)"
3579 print
"<a href=\"$manhtml_url\">HTML</a>"
3580 manraw_url
="$(print raw_files/new/$P.man.raw | url_encode)"
3581 print
"<a href=\"$manraw_url\">Raw</a>"
3582 elif [[ -n $manpage ]]; then
3583 print
" --- ---- ---"
3588 # Insert delta comments
3589 print
"<blockquote><pre>"
3590 getcomments html
$P $PP
3593 # Add additional comments comment
3594 print
"<!-- Add comments to explain changes in $P here -->"
3596 # Add count of changes.
3597 if [[ -f $F.count
]]; then
3602 if [[ $SCM_MODE == "mercurial" ||
3603 $SCM_MODE == "unknown" ]]; then
3604 # Include warnings for important file mode situations:
3605 # 1) New executable files
3606 # 2) Permission changes of any kind
3607 # 3) Existing executable files
3609 if [[ -f $WDIR/raw_files
/old
/$PP ]]; then
3610 old_mode
=`get_file_mode $WDIR/raw_files/old/$PP`
3614 if [[ -f $WDIR/raw_files
/new
/$P ]]; then
3615 new_mode
=`get_file_mode $WDIR/raw_files/new/$P`
3618 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3619 print
"<span class=\"chmod\">"
3620 print
"<p>new executable file: mode $new_mode</p>"
3622 elif [[ -n "$old_mode" && -n "$new_mode" &&
3623 "$old_mode" != "$new_mode" ]]; then
3624 print
"<span class=\"chmod\">"
3625 print
"<p>mode change: $old_mode to $new_mode</p>"
3627 elif [[ "$new_mode" = *[1357]* ]]; then
3628 print
"<span class=\"chmod\">"
3629 print
"<p>executable file: mode $new_mode</p>"
3634 print
"</blockquote>"
3640 print
"<p style=\"font-size: small\">"
3641 print
"This code review page was prepared using <b>$0</b>."
3642 print
"Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3643 print
"illumos</a> project. The latest version may be obtained"
3644 print
"<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3648 exec 1<&- # Close FD 1.
3649 exec 1<&3 # dup FD 3 to restore stdout.
3650 exec 3<&- # close FD 3.
3655 # If remote deletion was specified and fails do not continue.
3657 if [[ -n $Dflag ]]; then
3659 (( $?
== 0 )) ||
exit $?
3662 if [[ -n $Uflag ]]; then