10063 basic support for smatch
[unleashed.git] / usr / src / tools / scripts / webrev.sh
blob7466bdcb13214fe9b6026d7a6666e3347d3f65be
1 #!/usr/bin/ksh93 -p
3 # CDDL HEADER START
5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
20 # CDDL HEADER END
24 # Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
25 # Copyright 2008, 2010, Richard Lowe
26 # Copyright 2012 Marcel Telka <marcel@telka.sk>
27 # Copyright 2014 Bart Coddens <bart.coddens@gmail.com>
28 # Copyright 2017 Nexenta Systems, Inc.
29 # Copyright 2016 Joyent, Inc.
30 # Copyright 2016 RackTop Systems.
34 # This script takes a file list and a workspace and builds a set of html files
35 # suitable for doing a code review of source changes via a web page.
36 # Documentation is available via the manual page, webrev.1, or just
37 # type 'webrev -h'.
39 # Acknowledgements to contributors to webrev are listed in the webrev(1)
40 # man page.
43 REMOVED_COLOR=brown
44 CHANGED_COLOR=blue
45 NEW_COLOR=blue
47 HTML='<?xml version="1.0"?>
48 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
49 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
50 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
52 FRAMEHTML='<?xml version="1.0"?>
53 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
54 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
55 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
57 STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta>
58 <meta http-equiv="Content-Type" content="text/xhtml;charset=utf-8"></meta>
59 <meta http-equiv="Pragma" content="no-cache"></meta>
60 <meta http-equiv="Expires" content="-1"></meta>
61 <!--
62 Note to customizers: the body of the webrev is IDed as SUNWwebrev
63 to allow easy overriding by users of webrev via the userContent.css
64 mechanism available in some browsers.
66 For example, to have all "removed" information be red instead of
67 brown, set a rule in your userContent.css file like:
69 body#SUNWwebrev span.removed { color: red ! important; }
70 -->
71 <style type="text/css" media="screen">
72 body {
73 background-color: #eeeeee;
75 hr {
76 border: none 0;
77 border-top: 1px solid #aaa;
78 height: 1px;
80 div.summary {
81 font-size: .8em;
82 border-bottom: 1px solid #aaa;
83 padding-left: 1em;
84 padding-right: 1em;
86 div.summary h2 {
87 margin-bottom: 0.3em;
89 div.summary table th {
90 text-align: right;
91 vertical-align: top;
92 white-space: nowrap;
94 span.lineschanged {
95 font-size: 0.7em;
97 span.oldmarker {
98 color: red;
99 font-size: large;
100 font-weight: bold;
102 span.newmarker {
103 color: green;
104 font-size: large;
105 font-weight: bold;
107 span.removed {
108 color: brown;
110 span.changed {
111 color: blue;
113 span.new {
114 color: blue;
115 font-weight: bold;
117 span.chmod {
118 font-size: 0.7em;
119 color: #db7800;
121 a.print { font-size: x-small; }
122 a:hover { background-color: #ffcc99; }
123 </style>
125 <style type="text/css" media="print">
126 pre { font-size: 0.8em; font-family: courier, monospace; }
127 span.removed { color: #444; font-style: italic }
128 span.changed { font-weight: bold; }
129 span.new { font-weight: bold; }
130 span.newmarker { font-size: 1.2em; font-weight: bold; }
131 span.oldmarker { font-size: 1.2em; font-weight: bold; }
132 a.print {display: none}
133 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
134 </style>
138 # UDiffs need a slightly different CSS rule for 'new' items (we don't
139 # want them to be bolded as we do in cdiffs or sdiffs).
141 UDIFFCSS='
142 <style type="text/css" media="screen">
143 span.new {
144 color: blue;
145 font-weight: normal;
147 </style>
150 # CSS for the HTML version of the man pages.
151 # Current version is from mandoc 1.14.4.
152 MANCSS='
153 /* $Id: mandoc.css,v 1.36 2018/07/23 22:51:26 schwarze Exp $ */
155 * Standard style sheet for mandoc(1) -Thtml and man.cgi(8).
158 /* Global defaults. */
160 html { max-width: 65em; }
161 body { font-family: Helvetica,Arial,sans-serif; }
162 table { margin-top: 0em;
163 margin-bottom: 0em; }
164 td { vertical-align: top; }
165 ul, ol, dl { margin-top: 0em;
166 margin-bottom: 0em; }
167 li, dt { margin-top: 1em; }
169 .permalink { border-bottom: thin dotted;
170 color: inherit;
171 font: inherit;
172 text-decoration: inherit; }
173 * { clear: both }
175 /* Search form and search results. */
177 fieldset { border: thin solid silver;
178 border-radius: 1em;
179 text-align: center; }
180 input[name=expr] {
181 width: 25%; }
183 table.results { margin-top: 1em;
184 margin-left: 2em;
185 font-size: smaller; }
187 /* Header and footer lines. */
189 table.head { width: 100%;
190 border-bottom: 1px dotted #808080;
191 margin-bottom: 1em;
192 font-size: smaller; }
193 td.head-vol { text-align: center; }
194 td.head-rtitle {
195 text-align: right; }
197 table.foot { width: 100%;
198 border-top: 1px dotted #808080;
199 margin-top: 1em;
200 font-size: smaller; }
201 td.foot-os { text-align: right; }
203 /* Sections and paragraphs. */
205 .manual-text {
206 margin-left: 3.8em; }
207 .Nd { display: inline; }
208 .Sh { margin-top: 1.2em;
209 margin-bottom: 0.6em;
210 margin-left: -3.2em;
211 font-size: 110%; }
212 .Ss { margin-top: 1.2em;
213 margin-bottom: 0.6em;
214 margin-left: -1.2em;
215 font-size: 105%; }
216 .Pp { margin: 0.6em 0em; }
217 .Sx { }
218 .Xr { }
220 /* Displays and lists. */
222 .Bd { }
223 .Bd-indent { margin-left: 3.8em; }
225 .Bl-bullet { list-style-type: disc;
226 padding-left: 1em; }
227 .Bl-bullet > li { }
228 .Bl-dash { list-style-type: none;
229 padding-left: 0em; }
230 .Bl-dash > li:before {
231 content: "\2014 "; }
232 .Bl-item { list-style-type: none;
233 padding-left: 0em; }
234 .Bl-item > li { }
235 .Bl-compact > li {
236 margin-top: 0em; }
238 .Bl-enum { padding-left: 2em; }
239 .Bl-enum > li { }
240 .Bl-compact > li {
241 margin-top: 0em; }
243 .Bl-diag { }
244 .Bl-diag > dt {
245 font-style: normal;
246 font-weight: bold; }
247 .Bl-diag > dd {
248 margin-left: 0em; }
249 .Bl-hang { }
250 .Bl-hang > dt { }
251 .Bl-hang > dd {
252 margin-left: 5.5em; }
253 .Bl-inset { }
254 .Bl-inset > dt { }
255 .Bl-inset > dd {
256 margin-left: 0em; }
257 .Bl-ohang { }
258 .Bl-ohang > dt { }
259 .Bl-ohang > dd {
260 margin-left: 0em; }
261 .Bl-tag { margin-left: 5.5em; }
262 .Bl-tag > dt {
263 float: left;
264 margin-top: 0em;
265 margin-left: -5.5em;
266 padding-right: 1.2em;
267 vertical-align: top; }
268 .Bl-tag > dd {
269 clear: right;
270 width: 100%;
271 margin-top: 0em;
272 margin-left: 0em;
273 vertical-align: top;
274 overflow: auto; }
275 .Bl-compact > dt {
276 margin-top: 0em; }
278 .Bl-column { }
279 .Bl-column > tbody > tr { }
280 .Bl-column > tbody > tr > td {
281 margin-top: 1em; }
282 .Bl-compact > tbody > tr > td {
283 margin-top: 0em; }
285 .Rs { font-style: normal;
286 font-weight: normal; }
287 .RsA { }
288 .RsB { font-style: italic;
289 font-weight: normal; }
290 .RsC { }
291 .RsD { }
292 .RsI { font-style: italic;
293 font-weight: normal; }
294 .RsJ { font-style: italic;
295 font-weight: normal; }
296 .RsN { }
297 .RsO { }
298 .RsP { }
299 .RsQ { }
300 .RsR { }
301 .RsT { text-decoration: underline; }
302 .RsU { }
303 .RsV { }
305 .eqn { }
306 .tbl { }
308 .HP { margin-left: 3.8em;
309 text-indent: -3.8em; }
311 /* Semantic markup for command line utilities. */
313 table.Nm { }
314 code.Nm { font-style: normal;
315 font-weight: bold;
316 font-family: inherit; }
317 .Fl { font-style: normal;
318 font-weight: bold;
319 font-family: inherit; }
320 .Cm { font-style: normal;
321 font-weight: bold;
322 font-family: inherit; }
323 .Ar { font-style: italic;
324 font-weight: normal; }
325 .Op { display: inline; }
326 .Ic { font-style: normal;
327 font-weight: bold;
328 font-family: inherit; }
329 .Ev { font-style: normal;
330 font-weight: normal;
331 font-family: monospace; }
332 .Pa { font-style: italic;
333 font-weight: normal; }
335 /* Semantic markup for function libraries. */
337 .Lb { }
338 code.In { font-style: normal;
339 font-weight: bold;
340 font-family: inherit; }
341 a.In { }
342 .Fd { font-style: normal;
343 font-weight: bold;
344 font-family: inherit; }
345 .Ft { font-style: italic;
346 font-weight: normal; }
347 .Fn { font-style: normal;
348 font-weight: bold;
349 font-family: inherit; }
350 .Fa { font-style: italic;
351 font-weight: normal; }
352 .Vt { font-style: italic;
353 font-weight: normal; }
354 .Va { font-style: italic;
355 font-weight: normal; }
356 .Dv { font-style: normal;
357 font-weight: normal;
358 font-family: monospace; }
359 .Er { font-style: normal;
360 font-weight: normal;
361 font-family: monospace; }
363 /* Various semantic markup. */
365 .An { }
366 .Lk { }
367 .Mt { }
368 .Cd { font-style: normal;
369 font-weight: bold;
370 font-family: inherit; }
371 .Ad { font-style: italic;
372 font-weight: normal; }
373 .Ms { font-style: normal;
374 font-weight: bold; }
375 .St { }
376 .Ux { }
378 /* Physical markup. */
380 .Bf { display: inline; }
381 .No { font-style: normal;
382 font-weight: normal; }
383 .Em { font-style: italic;
384 font-weight: normal; }
385 .Sy { font-style: normal;
386 font-weight: bold; }
387 .Li { font-style: normal;
388 font-weight: normal;
389 font-family: monospace; }
391 /* Overrides to avoid excessive margins on small devices. */
393 @media (max-width: 37.5em) {
394 .manual-text {
395 margin-left: 0.5em; }
396 .Sh, .Ss { margin-left: 0em; }
397 .Bd-indent { margin-left: 2em; }
398 .Bl-hang > dd {
399 margin-left: 2em; }
400 .Bl-tag { margin-left: 2em; }
401 .Bl-tag > dt {
402 margin-left: -2em; }
403 .HP { margin-left: 2em;
404 text-indent: -2em; }
409 # Display remote target with prefix and trailing slash.
411 function print_upload_header
413 typeset -r prefix=$1
414 typeset display_target
416 if [[ -z $tflag ]]; then
417 display_target=${prefix}${remote_target}
418 else
419 display_target=${remote_target}
422 if [[ ${display_target} != */ ]]; then
423 display_target=${display_target}/
426 print " Upload to: ${display_target}\n" \
427 " Uploading: \c"
431 # Upload the webrev via rsync. Return 0 on success, 1 on error.
433 function rsync_upload
435 if (( $# != 2 )); then
436 print "\nERROR: rsync_upload: wrong usage ($#)"
437 exit 1
440 typeset -r dst=$1
441 integer -r print_err_msg=$2
443 print_upload_header ${rsync_prefix}
444 print "rsync ... \c"
445 typeset -r err_msg=$( $MKTEMP /tmp/rsync_err.XXXXXX )
446 if [[ -z $err_msg ]]; then
447 print "\nERROR: rsync_upload: cannot create temporary file"
448 return 1
451 # The source directory must end with a slash in order to copy just
452 # directory contents, not the whole directory.
454 typeset src_dir=$WDIR
455 if [[ ${src_dir} != */ ]]; then
456 src_dir=${src_dir}/
458 $RSYNC -r -q ${src_dir} $dst 2>$err_msg
459 if (( $? != 0 )); then
460 if (( ${print_err_msg} > 0 )); then
461 print "Failed.\nERROR: rsync failed"
462 print "src dir: '${src_dir}'\ndst dir: '$dst'"
463 print "error messages:"
464 $SED 's/^/> /' $err_msg
465 rm -f $err_msg
467 return 1
470 rm -f $err_msg
471 print "Done."
472 return 0
476 # Create directories on remote host using SFTP. Return 0 on success,
477 # 1 on failure.
479 function remote_mkdirs
481 typeset -r dir_spec=$1
482 typeset -r host_spec=$2
485 # If the supplied path is absolute we assume all directories are
486 # created, otherwise try to create all directories in the path
487 # except the last one which will be created by scp.
489 if [[ "${dir_spec}" == */* && "${dir_spec}" != /* ]]; then
490 print "mkdirs \c"
492 # Remove the last directory from directory specification.
494 typeset -r dirs_mk=${dir_spec%/*}
495 typeset -r batch_file_mkdir=$( $MKTEMP \
496 /tmp/webrev_mkdir.XXXXXX )
497 if [[ -z $batch_file_mkdir ]]; then
498 print "\nERROR: remote_mkdirs:" \
499 "cannot create temporary file for batch file"
500 return 1
502 OLDIFS=$IFS
503 IFS=/
504 typeset dir
505 for dir in ${dirs_mk}; do
507 # Use the '-' prefix to ignore mkdir errors in order
508 # to avoid an error in case the directory already
509 # exists. We check the directory with chdir to be sure
510 # there is one.
512 print -- "-mkdir ${dir}" >> ${batch_file_mkdir}
513 print "chdir ${dir}" >> ${batch_file_mkdir}
514 done
515 IFS=$OLDIFS
516 typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
517 if [[ -z ${sftp_err_msg} ]]; then
518 print "\nERROR: remote_mkdirs:" \
519 "cannot create temporary file for error messages"
520 return 1
522 $SFTP -b ${batch_file_mkdir} ${host_spec} 2>${sftp_err_msg} 1>&2
523 if (( $? != 0 )); then
524 print "\nERROR: failed to create remote directories"
525 print "error messages:"
526 $SED 's/^/> /' ${sftp_err_msg}
527 rm -f ${sftp_err_msg} ${batch_file_mkdir}
528 return 1
530 rm -f ${sftp_err_msg} ${batch_file_mkdir}
533 return 0
537 # Upload the webrev via SSH. Return 0 on success, 1 on error.
539 function ssh_upload
541 if (( $# != 1 )); then
542 print "\nERROR: ssh_upload: wrong number of arguments"
543 exit 1
546 typeset dst=$1
547 typeset -r host_spec=${dst%%:*}
548 typeset -r dir_spec=${dst#*:}
551 # Display the upload information before calling delete_webrev
552 # because it will also print its progress.
554 print_upload_header ${ssh_prefix}
557 # If the deletion was explicitly requested there is no need
558 # to perform it again.
560 if [[ -z $Dflag ]]; then
562 # We do not care about return value because this might be
563 # the first time this directory is uploaded.
565 delete_webrev 0
569 # Create remote directories. Any error reporting will be done
570 # in remote_mkdirs function.
572 remote_mkdirs ${dir_spec} ${host_spec}
573 if (( $? != 0 )); then
574 return 1
577 print "upload ... \c"
578 typeset -r scp_err_msg=$( $MKTEMP /tmp/scp_err.XXXXXX )
579 if [[ -z ${scp_err_msg} ]]; then
580 print "\nERROR: ssh_upload:" \
581 "cannot create temporary file for error messages"
582 return 1
584 $SCP -q -C -B -o PreferredAuthentications=publickey -r \
585 $WDIR $dst 2>${scp_err_msg}
586 if (( $? != 0 )); then
587 print "Failed.\nERROR: scp failed"
588 print "src dir: '$WDIR'\ndst dir: '$dst'"
589 print "error messages:"
590 $SED 's/^/> /' ${scp_err_msg}
591 rm -f ${scp_err_msg}
592 return 1
595 rm -f ${scp_err_msg}
596 print "Done."
597 return 0
601 # Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp
602 # on failure. If first argument is 1 then perform the check of sftp return
603 # value otherwise ignore it. If second argument is present it means this run
604 # only performs deletion.
606 function delete_webrev
608 if (( $# < 1 )); then
609 print "delete_webrev: wrong number of arguments"
610 exit 1
613 integer -r check=$1
614 integer delete_only=0
615 if (( $# == 2 )); then
616 delete_only=1
620 # Strip the transport specification part of remote target first.
622 typeset -r stripped_target=${remote_target##*://}
623 typeset -r host_spec=${stripped_target%%:*}
624 typeset -r dir_spec=${stripped_target#*:}
625 typeset dir_rm
628 # Do not accept an absolute path.
630 if [[ ${dir_spec} == /* ]]; then
631 return 1
635 # Strip the ending slash.
637 if [[ ${dir_spec} == */ ]]; then
638 dir_rm=${dir_spec%%/}
639 else
640 dir_rm=${dir_spec}
643 if (( ${delete_only} > 0 )); then
644 print " Removing: \c"
645 else
646 print "rmdir \c"
648 if [[ -z "$dir_rm" ]]; then
649 print "\nERROR: empty directory for removal"
650 return 1
654 # Prepare batch file.
656 typeset -r batch_file_rm=$( $MKTEMP /tmp/webrev_remove.XXXXXX )
657 if [[ -z $batch_file_rm ]]; then
658 print "\nERROR: delete_webrev: cannot create temporary file"
659 return 1
661 print "rename $dir_rm $TRASH_DIR/removed.$$" > $batch_file_rm
664 # Perform remote deletion and remove the batch file.
666 typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
667 if [[ -z ${sftp_err_msg} ]]; then
668 print "\nERROR: delete_webrev:" \
669 "cannot create temporary file for error messages"
670 return 1
672 $SFTP -b $batch_file_rm $host_spec 2>${sftp_err_msg} 1>&2
673 integer -r ret=$?
674 rm -f $batch_file_rm
675 if (( $ret != 0 && $check > 0 )); then
676 print "Failed.\nERROR: failed to remove remote directories"
677 print "error messages:"
678 $SED 's/^/> /' ${sftp_err_msg}
679 rm -f ${sftp_err_msg}
680 return $ret
682 rm -f ${sftp_err_msg}
683 if (( ${delete_only} > 0 )); then
684 print "Done."
687 return 0
691 # Upload webrev to remote site
693 function upload_webrev
695 integer ret
697 if [[ ! -d "$WDIR" ]]; then
698 print "\nERROR: webrev directory '$WDIR' does not exist"
699 return 1
703 # Perform a late check to make sure we do not upload closed source
704 # to remote target when -n is used. If the user used custom remote
705 # target he probably knows what he is doing.
707 if [[ -n $nflag && -z $tflag ]]; then
708 $FIND $WDIR -type d -name closed \
709 | $GREP closed >/dev/null
710 if (( $? == 0 )); then
711 print "\nERROR: directory '$WDIR' contains" \
712 "\"closed\" directory"
713 return 1
719 # We have the URI for remote destination now so let's start the upload.
721 if [[ -n $tflag ]]; then
722 if [[ "${remote_target}" == ${rsync_prefix}?* ]]; then
723 rsync_upload ${remote_target##$rsync_prefix} 1
724 ret=$?
725 return $ret
726 elif [[ "${remote_target}" == ${ssh_prefix}?* ]]; then
727 ssh_upload ${remote_target##$ssh_prefix}
728 ret=$?
729 return $ret
731 else
733 # Try rsync first and fallback to SSH in case it fails.
735 rsync_upload ${remote_target} 0
736 ret=$?
737 if (( $ret != 0 )); then
738 print "Failed. (falling back to SSH)"
739 ssh_upload ${remote_target}
740 ret=$?
742 return $ret
747 # input_cmd | url_encode | output_cmd
749 # URL-encode (percent-encode) reserved characters as defined in RFC 3986.
751 # Reserved characters are: :/?#[]@!$&'()*+,;=
753 # While not a reserved character itself, percent '%' is reserved by definition
754 # so encode it first to avoid recursive transformation, and skip '/' which is
755 # a path delimiter.
757 # The quotation character is deliberately not escaped in order to make
758 # the substitution work with GNU sed.
760 function url_encode
762 $SED -e "s|%|%25|g" -e "s|:|%3A|g" -e "s|\&|%26|g" \
763 -e "s|?|%3F|g" -e "s|#|%23|g" -e "s|\[|%5B|g" \
764 -e "s|*|%2A|g" -e "s|@|%40|g" -e "s|\!|%21|g" \
765 -e "s|=|%3D|g" -e "s|;|%3B|g" -e "s|\]|%5D|g" \
766 -e "s|(|%28|g" -e "s|)|%29|g" -e "s|'|%27|g" \
767 -e "s|+|%2B|g" -e "s|\,|%2C|g" -e "s|\\\$|%24|g"
771 # input_cmd | html_quote | output_cmd
772 # or
773 # html_quote filename | output_cmd
775 # Make a piece of source code safe for display in an HTML <pre> block.
777 html_quote()
779 $SED -e "s/&/\&amp;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g" "$@" | expand
783 # Trim a digest-style revision to a conventionally readable yet useful length
785 trim_digest()
787 typeset digest=$1
789 echo $digest | $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'
793 # input_cmd | its2url | output_cmd
795 # Scan for information tracking system references and insert <a> links to the
796 # relevant databases.
798 its2url()
800 $SED -f ${its_sed_script}
804 # strip_unchanged <infile> | output_cmd
806 # Removes chunks of sdiff documents that have not changed. This makes it
807 # easier for a code reviewer to find the bits that have changed.
809 # Deleted lines of text are replaced by a horizontal rule. Some
810 # identical lines are retained before and after the changed lines to
811 # provide some context. The number of these lines is controlled by the
812 # variable C in the $AWK script below.
814 # The script detects changed lines as any line that has a "<span class="
815 # string embedded (unchanged lines have no particular class and are not
816 # part of a <span>). Blank lines (without a sequence number) are also
817 # detected since they flag lines that have been inserted or deleted.
819 strip_unchanged()
821 $AWK '
822 BEGIN { C = c = 20 }
823 NF == 0 || /<span class="/ {
824 if (c > C) {
825 c -= C
826 inx = 0
827 if (c > C) {
828 print "\n</pre><hr></hr><pre>"
829 inx = c % C
830 c = C
833 for (i = 0; i < c; i++)
834 print ln[(inx + i) % C]
836 c = 0;
837 print
838 next
840 { if (c >= C) {
841 ln[c % C] = $0
842 c++;
843 next;
845 c++;
846 print
848 END { if (c > (C * 2)) print "\n</pre><hr></hr>" }
850 ' $1
854 # sdiff_to_html
856 # This function takes two files as arguments, obtains their diff, and
857 # processes the diff output to present the files as an HTML document with
858 # the files displayed side-by-side, differences shown in color. It also
859 # takes a delta comment, rendered as an HTML snippet, as the third
860 # argument. The function takes two files as arguments, then the name of
861 # file, the path, and the comment. The HTML will be delivered on stdout,
862 # e.g.
864 # $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
865 # new/usr/src/tools/scripts/webrev.sh \
866 # webrev.sh usr/src/tools/scripts \
867 # '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
868 # 1234567</a> my bugid' > <file>.html
870 # framed_sdiff() is then called which creates $2.frames.html
871 # in the webrev tree.
873 # FYI: This function is rather unusual in its use of awk. The initial
874 # diff run produces conventional diff output showing changed lines mixed
875 # with editing codes. The changed lines are ignored - we're interested in
876 # the editing codes, e.g.
878 # 8c8
879 # 57a61
880 # 63c66,76
881 # 68,93d80
882 # 106d90
883 # 108,110d91
885 # These editing codes are parsed by the awk script and used to generate
886 # another awk script that generates HTML, e.g the above lines would turn
887 # into something like this:
889 # BEGIN { printf "<pre>\n" }
890 # function sp(n) {for (i=0;i<n;i++)printf "\n"}
891 # function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
892 # NR==8 {wl("#7A7ADD");next}
893 # NR==54 {wl("#7A7ADD");sp(3);next}
894 # NR==56 {wl("#7A7ADD");next}
895 # NR==57 {wl("black");printf "\n"; next}
896 # : :
898 # This script is then run on the original source file to generate the
899 # HTML that corresponds to the source file.
901 # The two HTML files are then combined into a single piece of HTML that
902 # uses an HTML table construct to present the files side by side. You'll
903 # notice that the changes are color-coded:
905 # black - unchanged lines
906 # blue - changed lines
907 # bold blue - new lines
908 # brown - deleted lines
910 # Blank lines are inserted in each file to keep unchanged lines in sync
911 # (side-by-side). This format is familiar to users of sdiff(1) or
912 # Teamware's filemerge tool.
914 sdiff_to_html()
916 diff -b $1 $2 > /tmp/$$.diffs
918 TNAME=$3
919 TPATH=$4
920 COMMENT=$5
923 # Now we have the diffs, generate the HTML for the old file.
925 $AWK '
926 BEGIN {
927 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
928 printf "function removed() "
929 printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
930 printf "function changed() "
931 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
932 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
934 /^</ {next}
935 /^>/ {next}
936 /^---/ {next}
939 split($1, a, /[cad]/) ;
940 if (index($1, "a")) {
941 if (a[1] == 0) {
942 n = split(a[2], r, /,/);
943 if (n == 1)
944 printf "BEGIN\t\t{sp(1)}\n"
945 else
946 printf "BEGIN\t\t{sp(%d)}\n",\
947 (r[2] - r[1]) + 1
948 next
951 printf "NR==%s\t\t{", a[1]
952 n = split(a[2], r, /,/);
953 s = r[1];
954 if (n == 1)
955 printf "bl();printf \"\\n\"; next}\n"
956 else {
957 n = r[2] - r[1]
958 printf "bl();sp(%d);next}\n",\
959 (r[2] - r[1]) + 1
961 next
963 if (index($1, "d")) {
964 n = split(a[1], r, /,/);
965 n1 = r[1]
966 n2 = r[2]
967 if (n == 1)
968 printf "NR==%s\t\t{removed(); next}\n" , n1
969 else
970 printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
971 next
973 if (index($1, "c")) {
974 n = split(a[1], r, /,/);
975 n1 = r[1]
976 n2 = r[2]
977 final = n2
978 d1 = 0
979 if (n == 1)
980 printf "NR==%s\t\t{changed();" , n1
981 else {
982 d1 = n2 - n1
983 printf "NR==%s,NR==%s\t{changed();" , n1, n2
985 m = split(a[2], r, /,/);
986 n1 = r[1]
987 n2 = r[2]
988 if (m > 1) {
989 d2 = n2 - n1
990 if (d2 > d1) {
991 if (n > 1) printf "if (NR==%d)", final
992 printf "sp(%d);", d2 - d1
995 printf "next}\n" ;
997 next
1001 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
1002 ' /tmp/$$.diffs > /tmp/$$.file1
1005 # Now generate the HTML for the new file
1007 $AWK '
1008 BEGIN {
1009 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
1010 printf "function new() "
1011 printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
1012 printf "function changed() "
1013 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
1014 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
1017 /^</ {next}
1018 /^>/ {next}
1019 /^---/ {next}
1022 split($1, a, /[cad]/) ;
1023 if (index($1, "d")) {
1024 if (a[2] == 0) {
1025 n = split(a[1], r, /,/);
1026 if (n == 1)
1027 printf "BEGIN\t\t{sp(1)}\n"
1028 else
1029 printf "BEGIN\t\t{sp(%d)}\n",\
1030 (r[2] - r[1]) + 1
1031 next
1034 printf "NR==%s\t\t{", a[2]
1035 n = split(a[1], r, /,/);
1036 s = r[1];
1037 if (n == 1)
1038 printf "bl();printf \"\\n\"; next}\n"
1039 else {
1040 n = r[2] - r[1]
1041 printf "bl();sp(%d);next}\n",\
1042 (r[2] - r[1]) + 1
1044 next
1046 if (index($1, "a")) {
1047 n = split(a[2], r, /,/);
1048 n1 = r[1]
1049 n2 = r[2]
1050 if (n == 1)
1051 printf "NR==%s\t\t{new() ; next}\n" , n1
1052 else
1053 printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
1054 next
1056 if (index($1, "c")) {
1057 n = split(a[2], r, /,/);
1058 n1 = r[1]
1059 n2 = r[2]
1060 final = n2
1061 d2 = 0;
1062 if (n == 1) {
1063 final = n1
1064 printf "NR==%s\t\t{changed();" , n1
1065 } else {
1066 d2 = n2 - n1
1067 printf "NR==%s,NR==%s\t{changed();" , n1, n2
1069 m = split(a[1], r, /,/);
1070 n1 = r[1]
1071 n2 = r[2]
1072 if (m > 1) {
1073 d1 = n2 - n1
1074 if (d1 > d2) {
1075 if (n > 1) printf "if (NR==%d)", final
1076 printf "sp(%d);", d1 - d2
1079 printf "next}\n" ;
1080 next
1083 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
1084 ' /tmp/$$.diffs > /tmp/$$.file2
1087 # Post-process the HTML files by running them back through $AWK
1089 html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html
1091 html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html
1094 # Now combine into a valid HTML file and side-by-side into a table
1096 print "$HTML<head>$STDHEAD"
1097 print "<title>$WNAME Sdiff $TPATH/$TNAME</title>"
1098 print "</head><body id=\"SUNWwebrev\">"
1099 print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
1100 print "<pre>$COMMENT</pre>\n"
1101 print "<table><tr valign=\"top\">"
1102 print "<td><pre>"
1104 strip_unchanged /tmp/$$.file1.html
1106 print "</pre></td><td><pre>"
1108 strip_unchanged /tmp/$$.file2.html
1110 print "</pre></td>"
1111 print "</tr></table>"
1112 print "</body></html>"
1114 framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
1115 "$COMMENT"
1120 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
1122 # Expects lefthand and righthand side html files created by sdiff_to_html.
1123 # We use insert_anchors() to augment those with HTML navigation anchors,
1124 # and then emit the main frame. Content is placed into:
1126 # $WDIR/DIR/$TNAME.lhs.html
1127 # $WDIR/DIR/$TNAME.rhs.html
1128 # $WDIR/DIR/$TNAME.frames.html
1130 # NOTE: We rely on standard usage of $WDIR and $DIR.
1132 function framed_sdiff
1134 typeset TNAME=$1
1135 typeset TPATH=$2
1136 typeset lhsfile=$3
1137 typeset rhsfile=$4
1138 typeset comments=$5
1139 typeset RTOP
1141 # Enable html files to access WDIR via a relative path.
1142 RTOP=$(relative_dir $TPATH $WDIR)
1144 # Make the rhs/lhs files and output the frameset file.
1145 print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
1147 cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
1148 <script type="text/javascript" src="${RTOP}ancnav.js"></script>
1149 </head>
1150 <body id="SUNWwebrev" onkeypress="keypress(event);">
1151 <a name="0"></a>
1152 <pre>$comments</pre><hr></hr>
1155 cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
1157 insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
1158 insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
1160 close='</body></html>'
1162 print $close >> $WDIR/$DIR/$TNAME.lhs.html
1163 print $close >> $WDIR/$DIR/$TNAME.rhs.html
1165 print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
1166 print "<title>$WNAME Framed-Sdiff " \
1167 "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
1168 cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
1169 <frameset rows="*,60">
1170 <frameset cols="50%,50%">
1171 <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
1172 <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
1173 </frameset>
1174 <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0"
1175 marginheight="0" name="nav"></frame>
1176 <noframes>
1177 <body id="SUNWwebrev">
1178 Alas 'frames' webrev requires that your browser supports frames
1179 and has the feature enabled.
1180 </body>
1181 </noframes>
1182 </frameset>
1183 </html>
1189 # fix_postscript
1191 # Merge codereview output files to a single conforming postscript file, by:
1192 # - removing all extraneous headers/trailers
1193 # - making the page numbers right
1194 # - removing pages devoid of contents which confuse some
1195 # postscript readers.
1197 # From Casper.
1199 function fix_postscript
1201 infile=$1
1203 cat > /tmp/$$.crmerge.pl << \EOF
1205 print scalar(<>); # %!PS-Adobe---
1206 print "%%Orientation: Landscape\n";
1208 $pno = 0;
1209 $doprint = 1;
1211 $page = "";
1213 while (<>) {
1214 next if (/^%%Pages:\s*\d+/);
1216 if (/^%%Page:/) {
1217 if ($pno == 0 || $page =~ /\)S/) {
1218 # Header or single page containing text
1219 print "%%Page: ? $pno\n" if ($pno > 0);
1220 print $page;
1221 $pno++;
1222 } else {
1223 # Empty page, skip it.
1225 $page = "";
1226 $doprint = 1;
1227 next;
1230 # Skip from %%Trailer of one document to Endprolog
1231 # %%Page of the next
1232 $doprint = 0 if (/^%%Trailer/);
1233 $page .= $_ if ($doprint);
1236 if ($page =~ /\)S/) {
1237 print "%%Page: ? $pno\n";
1238 print $page;
1239 } else {
1240 $pno--;
1242 print "%%Trailer\n%%Pages: $pno\n";
1245 $PERL /tmp/$$.crmerge.pl < $infile
1250 # input_cmd | insert_anchors | output_cmd
1252 # Flag blocks of difference with sequentially numbered invisible
1253 # anchors. These are used to drive the frames version of the
1254 # sdiffs output.
1256 # NOTE: Anchor zero flags the top of the file irrespective of changes,
1257 # an additional anchor is also appended to flag the bottom.
1259 # The script detects changed lines as any line that has a "<span
1260 # class=" string embedded (unchanged lines have no class set and are
1261 # not part of a <span>. Blank lines (without a sequence number)
1262 # are also detected since they flag lines that have been inserted or
1263 # deleted.
1265 function insert_anchors
1267 $AWK '
1268 function ia() {
1269 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1272 BEGIN {
1273 anc=1;
1274 inblock=1;
1275 printf "<pre>\n";
1277 NF == 0 || /^<span class=/ {
1278 if (inblock == 0) {
1279 ia();
1280 inblock=1;
1282 print;
1283 next;
1286 inblock=0;
1287 print;
1289 END {
1290 ia();
1292 printf "<b style=\"font-size: large; color: red\">";
1293 printf "--- EOF ---</b>"
1294 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1295 printf "</pre>"
1296 printf "<form name=\"eof\">";
1297 printf "<input name=\"value\" value=\"%d\" " \
1298 "type=\"hidden\"></input>", anc - 1;
1299 printf "</form>";
1301 ' $1
1306 # relative_dir
1308 # Print a relative return path from $1 to $2. For example if
1309 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1310 # this function would print "../../../../".
1312 # In the event that $1 is not in $2 a warning is printed to stderr,
1313 # and $2 is returned-- the result of this is that the resulting webrev
1314 # is not relocatable.
1316 function relative_dir
1318 typeset cur="${1##$2?(/)}"
1321 # If the first path was specified absolutely, and it does
1322 # not start with the second path, it's an error.
1324 if [[ "$cur" = "/${1#/}" ]]; then
1325 # Should never happen.
1326 print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1327 print -u2 "to \"$2\". Check input paths. Framed webrev "
1328 print -u2 "will not be relocatable!"
1329 print $2
1330 return
1334 # This is kind of ugly. The sed script will do the following:
1336 # 1. Strip off a leading "." or "./": this is important to get
1337 # the correct arcnav links for files in $WDIR.
1338 # 2. Strip off a trailing "/": this is not strictly necessary,
1339 # but is kind of nice, since it doesn't end up in "//" at
1340 # the end of a relative path.
1341 # 3. Replace all remaining sequences of non-"/" with "..": the
1342 # assumption here is that each dirname represents another
1343 # level of relative separation.
1344 # 4. Append a trailing "/" only for non-empty paths: this way
1345 # the caller doesn't need to duplicate this logic, and does
1346 # not end up using $RTOP/file for files in $WDIR.
1348 print $cur | $SED -e '{
1349 s:^\./*::
1350 s:/$::
1351 s:[^/][^/]*:..:g
1352 s:^\(..*\)$:\1/:
1357 # frame_nav_js
1359 # Emit javascript for frame navigation
1361 function frame_nav_js
1363 cat << \EOF
1364 var myInt;
1365 var scrolling = 0;
1366 var sfactor = 3;
1367 var scount = 10;
1369 function scrollByPix()
1371 if (scount <= 0) {
1372 sfactor *= 1.2;
1373 scount = 10;
1375 parent.lhs.scrollBy(0, sfactor);
1376 parent.rhs.scrollBy(0, sfactor);
1377 scount--;
1380 function scrollToAnc(num)
1382 // Update the value of the anchor in the form which we use as
1383 // storage for this value. setAncValue() will take care of
1384 // correcting for overflow and underflow of the value and return
1385 // us the new value.
1386 num = setAncValue(num);
1388 // Set location and scroll back a little to expose previous
1389 // lines.
1391 // Note that this could be improved: it is possible although
1392 // complex to compute the x and y position of an anchor, and to
1393 // scroll to that location directly.
1395 parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1396 parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1398 parent.lhs.scrollBy(0, -30);
1399 parent.rhs.scrollBy(0, -30);
1402 function getAncValue()
1404 return (parseInt(parent.nav.document.diff.real.value));
1407 function setAncValue(val)
1409 if (val <= 0) {
1410 val = 0;
1411 parent.nav.document.diff.real.value = val;
1412 parent.nav.document.diff.display.value = "BOF";
1413 return (val);
1417 // The way we compute the max anchor value is to stash it
1418 // inline in the left and right hand side pages-- it's the same
1419 // on each side, so we pluck from the left.
1421 maxval = parent.lhs.document.eof.value.value;
1422 if (val < maxval) {
1423 parent.nav.document.diff.real.value = val;
1424 parent.nav.document.diff.display.value = val.toString();
1425 return (val);
1428 // this must be: val >= maxval
1429 val = maxval;
1430 parent.nav.document.diff.real.value = val;
1431 parent.nav.document.diff.display.value = "EOF";
1432 return (val);
1435 function stopScroll()
1437 if (scrolling == 1) {
1438 clearInterval(myInt);
1439 scrolling = 0;
1443 function startScroll()
1445 stopScroll();
1446 scrolling = 1;
1447 myInt = setInterval("scrollByPix()", 10);
1450 function handlePress(b)
1452 switch (b) {
1453 case 1:
1454 scrollToAnc(-1);
1455 break;
1456 case 2:
1457 scrollToAnc(getAncValue() - 1);
1458 break;
1459 case 3:
1460 sfactor = -3;
1461 startScroll();
1462 break;
1463 case 4:
1464 sfactor = 3;
1465 startScroll();
1466 break;
1467 case 5:
1468 scrollToAnc(getAncValue() + 1);
1469 break;
1470 case 6:
1471 scrollToAnc(999999);
1472 break;
1476 function handleRelease(b)
1478 stopScroll();
1481 function keypress(ev)
1483 var keynum;
1484 var keychar;
1486 if (window.event) { // IE
1487 keynum = ev.keyCode;
1488 } else if (ev.which) { // non-IE
1489 keynum = ev.which;
1492 keychar = String.fromCharCode(keynum);
1494 if (keychar == "k") {
1495 handlePress(2);
1496 return (0);
1497 } else if (keychar == "j" || keychar == " ") {
1498 handlePress(5);
1499 return (0);
1502 return (1);
1505 function ValidateDiffNum()
1507 var val;
1508 var i;
1510 val = parent.nav.document.diff.display.value;
1511 if (val == "EOF") {
1512 scrollToAnc(999999);
1513 return;
1516 if (val == "BOF") {
1517 scrollToAnc(0);
1518 return;
1521 i = parseInt(val);
1522 if (isNaN(i)) {
1523 parent.nav.document.diff.display.value = getAncValue();
1524 } else {
1525 scrollToAnc(i);
1528 return (false);
1534 # frame_navigation
1536 # Output anchor navigation file for framed sdiffs.
1538 function frame_navigation
1540 print "$HTML<head>$STDHEAD"
1542 cat << \EOF
1543 <title>Anchor Navigation</title>
1544 <meta http-equiv="Content-Script-Type" content="text/javascript">
1545 <meta http-equiv="Content-Type" content="text/html">
1547 <style type="text/css">
1548 div.button td { padding-left: 5px; padding-right: 5px;
1549 background-color: #eee; text-align: center;
1550 border: 1px #444 outset; cursor: pointer; }
1551 div.button a { font-weight: bold; color: black }
1552 div.button td:hover { background: #ffcc99; }
1553 </style>
1556 print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1558 cat << \EOF
1559 </head>
1560 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1561 onkeypress="keypress(event);">
1562 <noscript lang="javascript">
1563 <center>
1564 <p><big>Framed Navigation controls require Javascript</big><br></br>
1565 Either this browser is incompatable or javascript is not enabled</p>
1566 </center>
1567 </noscript>
1568 <table width="100%" border="0" align="center">
1569 <tr>
1570 <td valign="middle" width="25%">Diff navigation:
1571 Use 'j' and 'k' for next and previous diffs; or use buttons
1572 at right</td>
1573 <td align="center" valign="top" width="50%">
1574 <div class="button">
1575 <table border="0" align="center">
1576 <tr>
1577 <td>
1578 <a onMouseDown="handlePress(1);return true;"
1579 onMouseUp="handleRelease(1);return true;"
1580 onMouseOut="handleRelease(1);return true;"
1581 onClick="return false;"
1582 title="Go to Beginning Of file">BOF</a></td>
1583 <td>
1584 <a onMouseDown="handlePress(3);return true;"
1585 onMouseUp="handleRelease(3);return true;"
1586 onMouseOut="handleRelease(3);return true;"
1587 title="Scroll Up: Press and Hold to accelerate"
1588 onClick="return false;">Scroll Up</a></td>
1589 <td>
1590 <a onMouseDown="handlePress(2);return true;"
1591 onMouseUp="handleRelease(2);return true;"
1592 onMouseOut="handleRelease(2);return true;"
1593 title="Go to previous Diff"
1594 onClick="return false;">Prev Diff</a>
1595 </td></tr>
1597 <tr>
1598 <td>
1599 <a onMouseDown="handlePress(6);return true;"
1600 onMouseUp="handleRelease(6);return true;"
1601 onMouseOut="handleRelease(6);return true;"
1602 onClick="return false;"
1603 title="Go to End Of File">EOF</a></td>
1604 <td>
1605 <a onMouseDown="handlePress(4);return true;"
1606 onMouseUp="handleRelease(4);return true;"
1607 onMouseOut="handleRelease(4);return true;"
1608 title="Scroll Down: Press and Hold to accelerate"
1609 onClick="return false;">Scroll Down</a></td>
1610 <td>
1611 <a onMouseDown="handlePress(5);return true;"
1612 onMouseUp="handleRelease(5);return true;"
1613 onMouseOut="handleRelease(5);return true;"
1614 title="Go to next Diff"
1615 onClick="return false;">Next Diff</a></td>
1616 </tr>
1617 </table>
1618 </div>
1619 </td>
1620 <th valign="middle" width="25%">
1621 <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1622 <input name="display" value="BOF" size="8" type="text"></input>
1623 <input name="real" value="0" size="8" type="hidden"></input>
1624 </form>
1625 </th>
1626 </tr>
1627 </table>
1628 </body>
1629 </html>
1636 # diff_to_html <filename> <filepath> { U | C } <comment>
1638 # Processes the output of diff to produce an HTML file representing either
1639 # context or unified diffs.
1641 diff_to_html()
1643 TNAME=$1
1644 TPATH=$2
1645 DIFFTYPE=$3
1646 COMMENT=$4
1648 print "$HTML<head>$STDHEAD"
1649 print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1651 if [[ $DIFFTYPE == "U" ]]; then
1652 print "$UDIFFCSS"
1655 cat <<-EOF
1656 </head>
1657 <body id="SUNWwebrev">
1658 <a class="print" href="javascript:print()">Print this page</a>
1659 <pre>$COMMENT</pre>
1660 <pre>
1663 html_quote | $AWK '
1664 /^--- new/ { next }
1665 /^\+\+\+ new/ { next }
1666 /^--- old/ { next }
1667 /^\*\*\* old/ { next }
1668 /^\*\*\*\*/ { next }
1669 /^-------/ { printf "<center><h1>%s</h1></center>\n", $0; next }
1670 /^\@\@.*\@\@$/ { printf "</pre><hr></hr><pre>\n";
1671 printf "<span class=\"newmarker\">%s</span>\n", $0;
1672 next}
1674 /^\*\*\*/ { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1675 next}
1676 /^---/ { printf "<span class=\"newmarker\">%s</span>\n", $0;
1677 next}
1678 /^\+/ {printf "<span class=\"new\">%s</span>\n", $0; next}
1679 /^!/ {printf "<span class=\"changed\">%s</span>\n", $0; next}
1680 /^-/ {printf "<span class=\"removed\">%s</span>\n", $0; next}
1681 {printf "%s\n", $0; next}
1684 print "</pre></body></html>\n"
1689 # source_to_html { new | old } <filename>
1691 # Process a plain vanilla source file to transform it into an HTML file.
1693 source_to_html()
1695 WHICH=$1
1696 TNAME=$2
1698 print "$HTML<head>$STDHEAD"
1699 print "<title>$WNAME $WHICH $TNAME</title>"
1700 print "<body id=\"SUNWwebrev\">"
1701 print "<pre>"
1702 html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1703 print "</pre></body></html>"
1707 # comments_from_wx {text|html} filepath
1709 # Given the pathname of a file, find its location in a "wx" active
1710 # file list and print the following comment. Output is either text or
1711 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1712 # are turned into URLs.
1714 comments_from_wx()
1716 typeset fmt=$1
1717 typeset p=$2
1719 comm=`$AWK '
1720 $1 == "'$p'" {
1721 do getline ; while (NF > 0)
1722 getline
1723 while (NF > 0) { print ; getline }
1724 exit
1725 }' < $wxfile`
1727 if [[ -z $comm ]]; then
1728 comm="*** NO COMMENTS ***"
1731 if [[ $fmt == "text" ]]; then
1732 print -- "$comm"
1733 return
1736 print -- "$comm" | html_quote | its2url
1741 # getcomments {text|html} filepath parentpath
1743 # Fetch the comments depending on what SCM mode we're in.
1745 getcomments()
1747 typeset fmt=$1
1748 typeset p=$2
1749 typeset pp=$3
1751 if [[ -n $Nflag ]]; then
1752 return
1755 if [[ -n $wxfile ]]; then
1756 comments_from_wx $fmt $p
1761 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1763 # Print out Code Inspection figures similar to sccs-prt(1) format.
1765 function printCI
1767 integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1768 typeset str
1769 if (( tot == 1 )); then
1770 str="line"
1771 else
1772 str="lines"
1774 printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1775 $tot $str $ins $del $mod $unc
1780 # difflines <oldfile> <newfile>
1782 # Calculate and emit number of added, removed, modified and unchanged lines,
1783 # and total lines changed, the sum of added + removed + modified.
1785 function difflines
1787 integer tot mod del ins unc err
1788 typeset filename
1790 eval $( diff -e $1 $2 | $AWK '
1791 # Change range of lines: N,Nc
1792 /^[0-9]*,[0-9]*c$/ {
1793 n=split(substr($1,1,length($1)-1), counts, ",");
1794 if (n != 2) {
1795 error=2
1796 exit;
1799 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1800 # following would be 5 - 3 = 2! Hence +1 for correction.
1802 r=(counts[2]-counts[1])+1;
1805 # Now count replacement lines: each represents a change instead
1806 # of a delete, so increment c and decrement r.
1808 while (getline != /^\.$/) {
1809 c++;
1810 r--;
1813 # If there were more replacement lines than original lines,
1814 # then r will be negative; in this case there are no deletions,
1815 # but there are r changes that should be counted as adds, and
1816 # since r is negative, subtract it from a and add it to c.
1818 if (r < 0) {
1819 a-=r;
1820 c+=r;
1824 # If there were more original lines than replacement lines, then
1825 # r will be positive; in this case, increment d by that much.
1827 if (r > 0) {
1828 d+=r;
1830 next;
1833 # Change lines: Nc
1834 /^[0-9].*c$/ {
1835 # The first line is a replacement; any more are additions.
1836 if (getline != /^\.$/) {
1837 c++;
1838 while (getline != /^\.$/) a++;
1840 next;
1843 # Add lines: both Na and N,Na
1844 /^[0-9].*a$/ {
1845 while (getline != /^\.$/) a++;
1846 next;
1849 # Delete range of lines: N,Nd
1850 /^[0-9]*,[0-9]*d$/ {
1851 n=split(substr($1,1,length($1)-1), counts, ",");
1852 if (n != 2) {
1853 error=2
1854 exit;
1857 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1858 # following would be 5 - 3 = 2! Hence +1 for correction.
1860 r=(counts[2]-counts[1])+1;
1861 d+=r;
1862 next;
1865 # Delete line: Nd. For example 10d says line 10 is deleted.
1866 /^[0-9]*d$/ {d++; next}
1868 # Should not get here!
1870 error=1;
1871 exit;
1874 # Finish off - print results
1875 END {
1876 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1877 (c+d+a), c, d, a, error);
1878 }' )
1880 # End of $AWK, Check to see if any trouble occurred.
1881 if (( $? > 0 || err > 0 )); then
1882 print "Unexpected Error occurred reading" \
1883 "\`diff -e $1 $2\`: \$?=$?, err=" $err
1884 return
1887 # Accumulate totals
1888 (( TOTL += tot ))
1889 (( TMOD += mod ))
1890 (( TDEL += del ))
1891 (( TINS += ins ))
1892 # Calculate unchanged lines
1893 unc=`wc -l < $1`
1894 if (( unc > 0 )); then
1895 (( unc -= del + mod ))
1896 (( TUNC += unc ))
1898 # print summary
1899 print "<span class=\"lineschanged\">"
1900 printCI $tot $ins $del $mod $unc
1901 print "</span>"
1906 # flist_from_wx
1908 # Sets up webrev to source its information from a wx-formatted file.
1909 # Sets the global 'wxfile' variable.
1911 function flist_from_wx
1913 typeset argfile=$1
1914 if [[ -n ${argfile%%/*} ]]; then
1916 # If the wx file pathname is relative then make it absolute
1917 # because the webrev does a "cd" later on.
1919 wxfile=$PWD/$argfile
1920 else
1921 wxfile=$argfile
1924 $AWK '{ c = 1; print;
1925 while (getline) {
1926 if (NF == 0) { c = -c; continue }
1927 if (c > 0) print
1929 }' $wxfile > $FLIST
1931 print " Done."
1935 # Transform a specified 'git log' output format into a wx-like active list.
1937 function git_wxfile
1939 typeset child="$1"
1940 typeset parent="$2"
1942 TMPFLIST=/tmp/$$.active
1943 $PERL -e 'my (%files, %realfiles, $msg);
1944 my $parent = $ARGV[0];
1945 my $child = $ARGV[1];
1947 open(F, "git diff -M --name-status $parent..$child |");
1948 while (<F>) {
1949 chomp;
1950 if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
1951 if ($1 >= 75) { # Probably worth treating as a rename
1952 $realfiles{$3} = $2;
1953 } else {
1954 $realfiles{$3} = $3;
1955 $realfiles{$2} = $2;
1957 } else {
1958 my $f = (split /\s+/, $_)[1];
1959 $realfiles{$f} = $f;
1962 close(F);
1964 my $state = 1; # 0|comments, 1|files
1965 open(F, "git whatchanged --pretty=format:%B $parent..$child |");
1966 while (<F>) {
1967 chomp;
1968 if (/^:[0-9]{6}/) {
1969 my ($unused, $fname, $fname2) = split(/\t/, $_);
1970 $fname = $fname2 if defined($fname2);
1971 next if !defined($realfiles{$fname}); # No real change
1972 $state = 1;
1973 chomp $msg;
1974 $files{$fname} .= $msg;
1975 } else {
1976 if ($state == 1) {
1977 $state = 0;
1978 $msg = /^\n/ ? "" : "\n";
1980 $msg .= "$_\n" if ($_);
1983 close(F);
1985 for (sort keys %files) {
1986 if ($realfiles{$_} ne $_) {
1987 print "$_ $realfiles{$_}\n$files{$_}\n\n";
1988 } else {
1989 print "$_\n$files{$_}\n\n"
1991 }' ${parent} ${child} > $TMPFLIST
1993 wxfile=$TMPFLIST
1997 # flist_from_git
1998 # Build a wx-style active list, and hand it off to flist_from_wx
2000 function flist_from_git
2002 typeset child=$1
2003 typeset parent=$2
2005 print " File list from: git ...\c"
2006 git_wxfile "$child" "$parent";
2008 # flist_from_wx prints the Done, so we don't have to.
2009 flist_from_wx $TMPFLIST
2013 # flist_from_subversion
2015 # Generate the file list by extracting file names from svn status.
2017 function flist_from_subversion
2019 CWS=$1
2020 OLDPWD=$2
2022 cd $CWS
2023 print -u2 " File list from: svn status ... \c"
2024 svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
2025 print -u2 " Done."
2026 cd $OLDPWD
2029 function env_from_flist
2031 [[ -r $FLIST ]] || return
2034 # Use "eval" to set env variables that are listed in the file
2035 # list. Then copy those into our local versions of those
2036 # variables if they have not been set already.
2038 eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
2040 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
2041 codemgr_ws=$CODEMGR_WS
2042 export CODEMGR_WS
2046 # Check to see if CODEMGR_PARENT is set in the flist file.
2048 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2049 codemgr_parent=$CODEMGR_PARENT
2050 export CODEMGR_PARENT
2054 function look_for_prog
2056 typeset path
2057 typeset ppath
2058 typeset progname=$1
2060 ppath=$PATH
2061 ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
2062 ppath=$ppath:/opt/onbld/bin
2063 ppath=$ppath:/opt/onbld/bin/`uname -p`
2065 PATH=$ppath prog=`whence $progname`
2066 if [[ -n $prog ]]; then
2067 print $prog
2071 function get_file_mode
2073 $PERL -e '
2074 if (@stat = stat($ARGV[0])) {
2075 $mode = $stat[2] & 0777;
2076 printf "%03o\n", $mode;
2077 exit 0;
2078 } else {
2079 exit 1;
2081 ' $1
2084 function build_old_new_git
2086 typeset olddir="$1"
2087 typeset newdir="$2"
2088 typeset o_mode=
2089 typeset n_mode=
2090 typeset o_object=
2091 typeset n_object=
2092 typeset OWD=$PWD
2093 typeset file
2094 typeset type
2096 cd $CWS
2099 # Get old file and its mode from the git object tree
2101 if [[ "$PDIR" == "." ]]; then
2102 file="$PF"
2103 else
2104 file="$PDIR/$PF"
2107 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2108 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2109 else
2110 $GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk
2111 $GIT cat-file $type $o_object > $olddir/$file 2>/dev/null
2113 if (( $? != 0 )); then
2114 rm -f $olddir/$file
2115 elif [[ -n $o_mode ]]; then
2116 # Strip the first 3 digits, to get a regular octal mode
2117 o_mode=${o_mode/???/}
2118 chmod $o_mode $olddir/$file
2119 else
2120 # should never happen
2121 print -u2 "ERROR: set mode of $olddir/$file"
2126 # new version of the file.
2128 if [[ "$DIR" == "." ]]; then
2129 file="$F"
2130 else
2131 file="$DIR/$F"
2133 rm -rf $newdir/$file
2135 if [[ -e $CWS/$DIR/$F ]]; then
2136 cp $CWS/$DIR/$F $newdir/$DIR/$F
2137 chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F
2139 cd $OWD
2142 function build_old_new_subversion
2144 typeset olddir="$1"
2145 typeset newdir="$2"
2147 # Snag new version of file.
2148 rm -f $newdir/$DIR/$F
2149 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2151 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2152 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2153 else
2154 # Get the parent's version of the file.
2155 svn status $CWS/$DIR/$F | read stat file
2156 if [[ $stat != "A" ]]; then
2157 svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2162 function build_old_new_unknown
2164 typeset olddir="$1"
2165 typeset newdir="$2"
2168 # Snag new version of file.
2170 rm -f $newdir/$DIR/$F
2171 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2174 # Snag the parent's version of the file.
2176 if [[ -f $PWS/$PDIR/$PF ]]; then
2177 rm -f $olddir/$PDIR/$PF
2178 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2182 function build_old_new
2184 typeset WDIR=$1
2185 typeset PWS=$2
2186 typeset PDIR=$3
2187 typeset PF=$4
2188 typeset CWS=$5
2189 typeset DIR=$6
2190 typeset F=$7
2192 typeset olddir="$WDIR/raw_files/old"
2193 typeset newdir="$WDIR/raw_files/new"
2195 mkdir -p $olddir/$PDIR
2196 mkdir -p $newdir/$DIR
2198 if [[ $SCM_MODE == "git" ]]; then
2199 build_old_new_git "$olddir" "$newdir"
2200 elif [[ $SCM_MODE == "subversion" ]]; then
2201 build_old_new_subversion "$olddir" "$newdir"
2202 elif [[ $SCM_MODE == "unknown" ]]; then
2203 build_old_new_unknown "$olddir" "$newdir"
2206 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2207 print "*** Error: file not in parent or child"
2208 return 1
2210 return 0
2215 # Usage message.
2217 function usage
2219 print 'Usage:\twebrev [common-options]
2220 webrev [common-options] ( <file> | - )
2221 webrev [common-options] -w <wx file>
2223 Options:
2224 -c <revision>: generate webrev for single revision (git only)
2225 -C <filename>: Use <filename> for the information tracking configuration.
2226 -D: delete remote webrev
2227 -h <revision>: specify "head" revision for comparison (git only)
2228 -i <filename>: Include <filename> in the index.html file.
2229 -I <filename>: Use <filename> for the information tracking registry.
2230 -n: do not generate the webrev (useful with -U)
2231 -O: Print bugids/arc cases suitable for OpenSolaris.
2232 -o <outdir>: Output webrev to specified directory.
2233 -p <compare-against>: Use specified parent wkspc or basis for comparison
2234 -t <remote_target>: Specify remote destination for webrev upload
2235 -U: upload the webrev to remote destination
2236 -w <wxfile>: Use specified wx active file.
2238 Environment:
2239 WDIR: Control the output directory.
2240 WEBREV_TRASH_DIR: Set directory for webrev delete.
2242 SCM Environment:
2243 CODEMGR_WS: Workspace location.
2244 CODEMGR_PARENT: Parent workspace location.
2247 exit 2
2252 # Main program starts here
2256 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2258 set +o noclobber
2260 PATH=$(/bin/dirname "$(whence $0)"):$PATH
2262 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2263 [[ -z $WX ]] && WX=`look_for_prog wx`
2264 [[ -z $GIT ]] && GIT=`look_for_prog git`
2265 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2266 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2267 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2268 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2269 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2270 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2271 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2272 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2273 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2274 [[ -z $SCP ]] && SCP=`look_for_prog scp`
2275 [[ -z $SED ]] && SED=`look_for_prog sed`
2276 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2277 [[ -z $SORT ]] && SORT=`look_for_prog sort`
2278 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2279 [[ -z $GREP ]] && GREP=`look_for_prog grep`
2280 [[ -z $FIND ]] && FIND=`look_for_prog find`
2281 [[ -z $MANDOC ]] && MANDOC=`look_for_prog mandoc`
2282 [[ -z $COL ]] && COL=`look_for_prog col`
2284 # set name of trash directory for remote webrev deletion
2285 TRASH_DIR=".trash"
2286 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2288 if [[ ! -x $PERL ]]; then
2289 print -u2 "Error: No perl interpreter found. Exiting."
2290 exit 1
2293 if [[ ! -x $WHICH_SCM ]]; then
2294 print -u2 "Error: Could not find which_scm. Exiting."
2295 exit 1
2299 # These aren't fatal, but we want to note them to the user.
2300 # We don't warn on the absence of 'wx' until later when we've
2301 # determined that we actually need to try to invoke it.
2303 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2304 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2305 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2307 # Declare global total counters.
2308 integer TOTL TINS TDEL TMOD TUNC
2310 # default remote host for upload/delete
2311 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2312 # prefixes for upload targets
2313 typeset -r rsync_prefix="rsync://"
2314 typeset -r ssh_prefix="ssh://"
2316 cflag=
2317 Cflag=
2318 Dflag=
2319 flist_mode=
2320 flist_file=
2321 hflag=
2322 iflag=
2323 Iflag=
2324 lflag=
2325 Nflag=
2326 nflag=
2327 Oflag=
2328 oflag=
2329 pflag=
2330 tflag=
2331 uflag=
2332 Uflag=
2333 wflag=
2334 remote_target=
2336 while getopts "c:C:Dh:i:I:lnNo:Op:t:Uw" opt
2338 case $opt in
2339 c) cflag=1
2340 codemgr_head=$OPTARG
2341 codemgr_parent=$OPTARG~1;;
2343 C) Cflag=1
2344 ITSCONF=$OPTARG;;
2346 D) Dflag=1;;
2348 h) hflag=1
2349 codemgr_head=$OPTARG;;
2351 i) iflag=1
2352 INCLUDE_FILE=$OPTARG;;
2354 I) Iflag=1
2355 ITSREG=$OPTARG;;
2357 N) Nflag=1;;
2359 n) nflag=1;;
2361 O) Oflag=1;;
2363 o) oflag=1
2364 # Strip the trailing slash to correctly form remote target.
2365 WDIR=${OPTARG%/};;
2367 p) pflag=1
2368 codemgr_parent=$OPTARG;;
2370 t) tflag=1
2371 remote_target=$OPTARG;;
2373 U) Uflag=1;;
2375 w) wflag=1;;
2377 ?) usage;;
2378 esac
2379 done
2381 FLIST=/tmp/$$.flist
2383 if [[ -n $wflag && -n $lflag ]]; then
2384 usage
2387 # more sanity checking
2388 if [[ -n $nflag && -z $Uflag ]]; then
2389 print "it does not make sense to skip webrev generation" \
2390 "without -U"
2391 exit 1
2394 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2395 echo "remote target has to be used only for upload or delete"
2396 exit 1
2400 # For the invocation "webrev -n -U" with no other options, webrev will assume
2401 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2402 # $(basename ${CWS}). So we need to get CWS set before we skip any remaining
2403 # logic.
2405 $WHICH_SCM | read SCM_MODE junk || exit 1
2407 if [[ $SCM_MODE == "git" ]]; then
2409 # Git priorities:
2410 # 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2411 # 2. git rev-parse --git-dir from directory of invocation
2413 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2414 codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \
2415 2>/dev/null)
2416 [[ -z $codemgr_ws ]] && \
2417 codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null)
2419 if [[ "$codemgr_ws" == ".git" ]]; then
2420 codemgr_ws="${PWD}/${codemgr_ws}"
2423 if [[ "$codemgr_ws" = *"/.git" ]]; then
2424 codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git'
2426 CWS="$codemgr_ws"
2427 elif [[ $SCM_MODE == "subversion" ]]; then
2429 # Subversion priorities:
2430 # 1. CODEMGR_WS from environment
2431 # 2. Relative path from current directory to SVN repository root
2433 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2434 CWS=$CODEMGR_WS
2435 else
2436 svn info | while read line; do
2437 if [[ $line == "URL: "* ]]; then
2438 url=${line#URL: }
2439 elif [[ $line == "Repository Root: "* ]]; then
2440 repo=${line#Repository Root: }
2442 done
2444 rel=${url#$repo}
2445 CWS=${PWD%$rel}
2450 # If no SCM has been determined, take either the environment setting
2451 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2453 if [[ -z ${CWS} ]]; then
2454 CWS=${CODEMGR_WS:-.}
2458 # If the command line options indicate no webrev generation, either
2459 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2460 # ton of logic we can skip.
2462 # Instead of increasing indentation, we intentionally leave this loop
2463 # body open here, and exit via break from multiple points within.
2464 # Search for DO_EVERYTHING below to find the break points and closure.
2466 for do_everything in 1; do
2468 # DO_EVERYTHING: break point
2469 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2470 break
2474 # If this manually set as the parent, and it appears to be an earlier webrev,
2475 # then note that fact and set the parent to the raw_files/new subdirectory.
2477 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2478 parent_webrev=$(readlink -f "$codemgr_parent")
2479 codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new")
2482 if [[ -z $wflag && -z $lflag ]]; then
2483 shift $(($OPTIND - 1))
2485 if [[ $1 == "-" ]]; then
2486 cat > $FLIST
2487 flist_mode="stdin"
2488 flist_done=1
2489 shift
2490 elif [[ -n $1 ]]; then
2491 if [[ ! -r $1 ]]; then
2492 print -u2 "$1: no such file or not readable"
2493 usage
2495 cat $1 > $FLIST
2496 flist_mode="file"
2497 flist_file=$1
2498 flist_done=1
2499 shift
2500 else
2501 flist_mode="auto"
2506 # Before we go on to further consider -l and -w, work out which SCM we think
2507 # is in use.
2509 case "$SCM_MODE" in
2510 git|subversion)
2512 unknown)
2513 if [[ $flist_mode == "auto" ]]; then
2514 print -u2 "Unable to determine SCM in use and file list not specified"
2515 print -u2 "See which_scm(1) for SCM detection information."
2516 exit 1
2520 if [[ $flist_mode == "auto" ]]; then
2521 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2522 exit 1
2525 esac
2527 print -u2 " SCM detected: $SCM_MODE"
2529 if [[ -n $wflag ]]; then
2531 # If the -w is given then assume the file list is in Bonwick's "wx"
2532 # command format, i.e. pathname lines alternating with SCCS comment
2533 # lines with blank lines as separators. Use the SCCS comments later
2534 # in building the index.html file.
2536 shift $(($OPTIND - 1))
2537 wxfile=$1
2538 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2539 if [[ -r $CODEMGR_WS/wx/active ]]; then
2540 wxfile=$CODEMGR_WS/wx/active
2544 [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2545 "be auto-detected (check \$CODEMGR_WS)" && exit 1
2547 if [[ ! -r $wxfile ]]; then
2548 print -u2 "$wxfile: no such file or not readable"
2549 usage
2552 print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2553 flist_from_wx $wxfile
2554 flist_done=1
2555 if [[ -n "$*" ]]; then
2556 shift
2558 elif [[ $flist_mode == "stdin" ]]; then
2559 print -u2 " File list from: standard input"
2560 elif [[ $flist_mode == "file" ]]; then
2561 print -u2 " File list from: $flist_file"
2564 if [[ $# -gt 0 ]]; then
2565 print -u2 "WARNING: unused arguments: $*"
2569 if [[ $SCM_MODE == "git" ]]; then
2570 # Check that "head" revision specified with -c or -h is sane
2571 if [[ -n $cflag || -n $hflag ]]; then
2572 head_rev=$($GIT rev-parse --verify --quiet "$codemgr_head")
2573 if [[ -z $head_rev ]]; then
2574 print -u2 "Error: bad revision ${codemgr_head}"
2575 exit 1
2579 if [[ -z $codemgr_head ]]; then
2580 codemgr_head="HEAD";
2583 # Parent can either be specified with -p, or specified with
2584 # CODEMGR_PARENT in the environment.
2585 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2586 codemgr_parent=$CODEMGR_PARENT
2589 # Try to figure out the parent based on the branch the current
2590 # branch is tracking, if we fail, use origin/master
2591 this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2592 par_branch="origin/master"
2594 # If we're not on a branch there's nothing we can do
2595 if [[ $this_branch != "(no branch)" ]]; then
2596 $GIT for-each-ref \
2597 --format='%(refname:short) %(upstream:short)' \
2598 refs/heads/ | \
2599 while read local remote; do
2600 if [[ "$local" == "$this_branch" ]]; then
2601 par_branch="$remote"
2603 done
2606 if [[ -z $codemgr_parent ]]; then
2607 codemgr_parent=$par_branch
2609 PWS=$codemgr_parent
2612 # If the parent is a webrev, we want to do some things against
2613 # the natural workspace parent (file list, comments, etc)
2615 if [[ -n $parent_webrev ]]; then
2616 real_parent=$par_branch
2617 else
2618 real_parent=$PWS
2621 if [[ -z $flist_done ]]; then
2622 flist_from_git "$codemgr_head" "$real_parent"
2623 flist_done=1
2627 # If we have a file list now, pull out any variables set
2628 # therein.
2630 if [[ -n $flist_done ]]; then
2631 env_from_flist
2635 # If we don't have a wx-format file list, build one we can pull change
2636 # comments from.
2638 if [[ -z $wxfile ]]; then
2639 print " Comments from: git...\c"
2640 git_wxfile "$codemgr_head" "$real_parent"
2641 print " Done."
2644 if [[ -z $GIT_PARENT ]]; then
2645 GIT_PARENT=$($GIT merge-base "$real_parent" "$codemgr_head")
2647 if [[ -z $GIT_PARENT ]]; then
2648 print -u2 "Error: Cannot discover parent revision"
2649 exit 1
2652 pnode=$(trim_digest $GIT_PARENT)
2654 if [[ -n $cflag ]]; then
2655 PRETTY_PWS="previous revision (at ${pnode})"
2656 elif [[ $real_parent == */* ]]; then
2657 origin=$(echo $real_parent | cut -d/ -f1)
2658 origin=$($GIT remote -v | \
2659 $AWK '$1 == "'$origin'" { print $2; exit }')
2660 PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2661 elif [[ -n $pflag && -z $parent_webrev ]]; then
2662 PRETTY_PWS="${CWS} (explicit revision ${pnode})"
2663 else
2664 PRETTY_PWS="${PWS} (at ${pnode})"
2667 cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 \
2668 ${codemgr_head} 2>/dev/null)
2670 if [[ -n $cflag || -n $hflag ]]; then
2671 PRETTY_CWS="${CWS} (explicit head at ${cnode})"
2672 else
2673 PRETTY_CWS="${CWS} (at ${cnode})"
2675 elif [[ $SCM_MODE == "subversion" ]]; then
2678 # We only will have a real parent workspace in the case one
2679 # was specified (be it an older webrev, or another checkout).
2681 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2683 if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2684 flist_from_subversion $CWS $OLDPWD
2686 else
2687 if [[ $SCM_MODE == "unknown" ]]; then
2688 print -u2 " Unknown type of SCM in use"
2689 else
2690 print -u2 " Unsupported SCM in use: $SCM_MODE"
2693 env_from_flist
2695 if [[ -z $CODEMGR_WS ]]; then
2696 print -u2 "SCM not detected/supported and " \
2697 "CODEMGR_WS not specified"
2698 exit 1
2701 if [[ -z $CODEMGR_PARENT ]]; then
2702 print -u2 "SCM not detected/supported and " \
2703 "CODEMGR_PARENT not specified"
2704 exit 1
2707 CWS=$CODEMGR_WS
2708 PWS=$CODEMGR_PARENT
2712 # If the user didn't specify a -i option, check to see if there is a
2713 # webrev-info file in the workspace directory.
2715 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2716 iflag=1
2717 INCLUDE_FILE="$CWS/webrev-info"
2720 if [[ -n $iflag ]]; then
2721 if [[ ! -r $INCLUDE_FILE ]]; then
2722 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2723 "not readable."
2724 exit 1
2725 else
2727 # $INCLUDE_FILE may be a relative path, and the script alters
2728 # PWD, so we just stash a copy in /tmp.
2730 cp $INCLUDE_FILE /tmp/$$.include
2734 # DO_EVERYTHING: break point
2735 if [[ -n $Nflag ]]; then
2736 break
2739 typeset -A itsinfo
2740 typeset -r its_sed_script=/tmp/$$.its_sed
2741 valid_prefixes=
2742 if [[ -z $nflag ]]; then
2743 DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg"
2744 if [[ -n $Iflag ]]; then
2745 REGFILE=$ITSREG
2746 elif [[ -r $HOME/.its.reg ]]; then
2747 REGFILE=$HOME/.its.reg
2748 else
2749 REGFILE=$DEFREGFILE
2751 if [[ ! -r $REGFILE ]]; then
2752 print "ERROR: Unable to read database registry file $REGFILE"
2753 exit 1
2754 elif [[ $REGFILE != $DEFREGFILE ]]; then
2755 print " its.reg from: $REGFILE"
2758 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do
2760 name=${LINE%%=*}
2761 value="${LINE#*=}"
2763 if [[ $name == PREFIX ]]; then
2764 p=${value}
2765 valid_prefixes="${p} ${valid_prefixes}"
2766 else
2767 itsinfo["${p}_${name}"]="${value}"
2769 done
2772 DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf"
2773 CONFFILES=$DEFCONFFILE
2774 if [[ -r $HOME/.its.conf ]]; then
2775 CONFFILES="${CONFFILES} $HOME/.its.conf"
2777 if [[ -n $Cflag ]]; then
2778 CONFFILES="${CONFFILES} ${ITSCONF}"
2780 its_domain=
2781 its_priority=
2782 for cf in ${CONFFILES}; do
2783 if [[ ! -r $cf ]]; then
2784 print "ERROR: Unable to read database configuration file $cf"
2785 exit 1
2786 elif [[ $cf != $DEFCONFFILE ]]; then
2787 print " its.conf: reading $cf"
2789 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do
2790 eval "${LINE}"
2791 done
2792 done
2795 # If an information tracking system is explicitly identified by prefix,
2796 # we want to disregard the specified priorities and resolve it accordingly.
2798 # To that end, we'll build a sed script to do each valid prefix in turn.
2800 for p in ${valid_prefixes}; do
2802 # When an informational URL was provided, translate it to a
2803 # hyperlink. When omitted, simply use the prefix text.
2805 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2806 itsinfo["${p}_INFO"]=${p}
2807 else
2808 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2812 # Assume that, for this invocation of webrev, all references
2813 # to this information tracking system should resolve through
2814 # the same URL.
2816 # If the caller specified -O, then always use EXTERNAL_URL.
2818 # Otherwise, look in the list of domains for a matching
2819 # INTERNAL_URL.
2821 [[ -z $Oflag ]] && for d in ${its_domain}; do
2822 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2823 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2824 break
2826 done
2827 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2828 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2832 # Turn the destination URL into a hyperlink
2834 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2836 # The character class below contains a literal tab
2837 print "/^${p}[: ]/ {
2838 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2839 s;^${p};${itsinfo[${p}_INFO]};
2840 }" >> ${its_sed_script}
2841 done
2844 # The previous loop took care of explicit specification. Now use
2845 # the configured priorities to attempt implicit translations.
2847 for p in ${its_priority}; do
2848 print "/^${itsinfo[${p}_REGEX]}[ ]/ {
2849 s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2850 }" >> ${its_sed_script}
2851 done
2855 # Search for DO_EVERYTHING above for matching "for" statement
2856 # and explanation of this terminator.
2858 done
2861 # Output directory.
2863 WDIR=${WDIR:-$CWS/webrev}
2866 # Name of the webrev, derived from the workspace name or output directory;
2867 # in the future this could potentially be an option.
2869 if [[ -n $oflag ]]; then
2870 WNAME=${WDIR##*/}
2871 else
2872 WNAME=${CWS##*/}
2875 # Make sure remote target is well formed for remote upload/delete.
2876 if [[ -n $Dflag || -n $Uflag ]]; then
2878 # If remote target is not specified, build it from scratch using
2879 # the default values.
2881 if [[ -z $tflag ]]; then
2882 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2883 else
2885 # Check upload target prefix first.
2887 if [[ "${remote_target}" != ${rsync_prefix}* &&
2888 "${remote_target}" != ${ssh_prefix}* ]]; then
2889 print "ERROR: invalid prefix of upload URI" \
2890 "($remote_target)"
2891 exit 1
2894 # If destination specification is not in the form of
2895 # host_spec:remote_dir then assume it is just remote hostname
2896 # and append a colon and destination directory formed from
2897 # local webrev directory name.
2899 typeset target_no_prefix=${remote_target##*://}
2900 if [[ ${target_no_prefix} == *:* ]]; then
2901 if [[ "${remote_target}" == *: ]]; then
2902 remote_target=${remote_target}${WNAME}
2904 else
2905 if [[ ${target_no_prefix} == */* ]]; then
2906 print "ERROR: badly formed upload URI" \
2907 "($remote_target)"
2908 exit 1
2909 else
2910 remote_target=${remote_target}:${WNAME}
2916 # Strip trailing slash. Each upload method will deal with directory
2917 # specification separately.
2919 remote_target=${remote_target%/}
2923 # Option -D by itself (option -U not present) implies no webrev generation.
2925 if [[ -z $Uflag && -n $Dflag ]]; then
2926 delete_webrev 1 1
2927 exit $?
2931 # Do not generate the webrev, just upload it or delete it.
2933 if [[ -n $nflag ]]; then
2934 if [[ -n $Dflag ]]; then
2935 delete_webrev 1 1
2936 (( $? == 0 )) || exit $?
2938 if [[ -n $Uflag ]]; then
2939 upload_webrev
2940 exit $?
2944 if [ "${WDIR%%/*}" ]; then
2945 WDIR=$PWD/$WDIR
2948 if [[ ! -d $WDIR ]]; then
2949 mkdir -p $WDIR
2950 (( $? != 0 )) && exit 1
2954 # Summarize what we're going to do.
2956 print " Workspace: ${PRETTY_CWS:-$CWS}"
2957 if [[ -n $parent_webrev ]]; then
2958 print "Compare against: webrev at $parent_webrev"
2959 else
2960 print "Compare against: ${PRETTY_PWS:-$PWS}"
2963 [[ -n $INCLUDE_FILE ]] && print " Including: $INCLUDE_FILE"
2964 print " Output to: $WDIR"
2967 # Save the file list in the webrev dir
2969 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
2971 rm -f $WDIR/$WNAME.patch
2972 rm -f $WDIR/$WNAME.ps
2973 rm -f $WDIR/$WNAME.pdf
2975 touch $WDIR/$WNAME.patch
2977 print " Output Files:"
2980 # Clean up the file list: Remove comments, blank lines and env variables.
2982 $SED -e "s/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean
2983 FLIST=/tmp/$$.flist.clean
2986 # First pass through the files: generate the per-file webrev HTML-files.
2988 cat $FLIST | while read LINE
2990 set - $LINE
2991 P=$1
2994 # Normally, each line in the file list is just a pathname of a
2995 # file that has been modified or created in the child. A file
2996 # that is renamed in the child workspace has two names on the
2997 # line: new name followed by the old name.
2999 oldname=""
3000 oldpath=""
3001 rename=
3002 if [[ $# -eq 2 ]]; then
3003 PP=$2 # old filename
3004 if [[ -f $PP ]]; then
3005 oldname=" (copied from $PP)"
3006 else
3007 oldname=" (renamed from $PP)"
3009 oldpath="$PP"
3010 rename=1
3011 PDIR=${PP%/*}
3012 if [[ $PDIR == $PP ]]; then
3013 PDIR="." # File at root of workspace
3016 PF=${PP##*/}
3018 DIR=${P%/*}
3019 if [[ $DIR == $P ]]; then
3020 DIR="." # File at root of workspace
3023 F=${P##*/}
3025 else
3026 DIR=${P%/*}
3027 if [[ "$DIR" == "$P" ]]; then
3028 DIR="." # File at root of workspace
3031 F=${P##*/}
3033 PP=$P
3034 PDIR=$DIR
3035 PF=$F
3038 COMM=`getcomments html $P $PP`
3040 print "\t$P$oldname\n\t\t\c"
3042 # Make the webrev mirror directory if necessary
3043 mkdir -p $WDIR/$DIR
3046 # We stash old and new files into parallel directories in $WDIR
3047 # and do our diffs there. This makes it possible to generate
3048 # clean looking diffs which don't have absolute paths present.
3051 build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3052 continue
3055 # Keep the old PWD around, so we can safely switch back after
3056 # diff generation, such that build_old_new runs in a
3057 # consistent environment.
3059 OWD=$PWD
3060 cd $WDIR/raw_files
3063 # The "git apply" command does not tolerate the spurious
3064 # "./" that we otherwise insert; be careful not to include
3065 # it in the paths that we pass to diff(1).
3067 if [[ $PDIR == "." ]]; then
3068 ofile=old/$PF
3069 else
3070 ofile=old/$PDIR/$PF
3072 if [[ $DIR == "." ]]; then
3073 nfile=new/$F
3074 else
3075 nfile=new/$DIR/$F
3078 mv_but_nodiff=
3079 cmp $ofile $nfile > /dev/null 2>&1
3080 if [[ $? == 0 && $rename == 1 ]]; then
3081 mv_but_nodiff=1
3085 # If we have old and new versions of the file then run the appropriate
3086 # diffs. This is complicated by a couple of factors:
3088 # - renames must be handled specially: we emit a 'remove'
3089 # diff and an 'add' diff
3090 # - new files and deleted files must be handled specially
3091 # - GNU patch doesn't interpret the output of illumos diff
3092 # properly when it comes to adds and deletes. We need to
3093 # do some "cleansing" transformations:
3094 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@
3095 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@
3097 cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3098 cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3100 rm -f $WDIR/$DIR/$F.patch
3101 if [[ -z $rename ]]; then
3102 if [ ! -f "$ofile" ]; then
3103 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3104 > $WDIR/$DIR/$F.patch
3105 elif [ ! -f "$nfile" ]; then
3106 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3107 > $WDIR/$DIR/$F.patch
3108 else
3109 diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3111 else
3112 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3113 > $WDIR/$DIR/$F.patch
3115 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3116 >> $WDIR/$DIR/$F.patch
3120 # Tack the patch we just made onto the accumulated patch for the
3121 # whole wad.
3123 cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3124 print " patch\c"
3126 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3127 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3128 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3129 > $WDIR/$DIR/$F.cdiff.html
3130 print " cdiffs\c"
3132 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3133 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3134 > $WDIR/$DIR/$F.udiff.html
3135 print " udiffs\c"
3137 if [[ -x $WDIFF ]]; then
3138 $WDIFF -c "$COMM" \
3139 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3140 $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3141 if [[ $? -eq 0 ]]; then
3142 print " wdiffs\c"
3143 else
3144 print " wdiffs[fail]\c"
3148 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3149 > $WDIR/$DIR/$F.sdiff.html
3150 print " sdiffs\c"
3151 print " frames\c"
3153 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3154 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3155 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3156 # renamed file: may also have differences
3157 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3158 elif [[ -f $nfile ]]; then
3159 # new file: count added lines
3160 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3161 elif [[ -f $ofile ]]; then
3162 # old file: count deleted lines
3163 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3167 # Check if it's man page, and create plain text, html and raw (ascii)
3168 # output for the new version, as well as diffs against old version.
3170 if [[ -f "$nfile" && "$nfile" = *.+([0-9])*([a-zA-Z]) && \
3171 -x $MANDOC && -x $COL ]]; then
3172 $MANDOC -Tascii $nfile | $COL -b > $nfile.man.txt
3173 source_to_html txt < $nfile.man.txt > $nfile.man.txt.html
3174 print " man-txt\c"
3175 print "$MANCSS" > $WDIR/raw_files/new/$DIR/man.css
3176 $MANDOC -Thtml -Ostyle=man.css $nfile > $nfile.man.html
3177 print " man-html\c"
3178 $MANDOC -Tascii $nfile > $nfile.man.raw
3179 print " man-raw\c"
3180 if [[ -f "$ofile" && -z $mv_but_nodiff ]]; then
3181 $MANDOC -Tascii $ofile | $COL -b > $ofile.man.txt
3182 ${CDIFFCMD:-diff -bt -C 5} $ofile.man.txt \
3183 $nfile.man.txt > $WDIR/$DIR/$F.man.cdiff
3184 diff_to_html $F $DIR/$F "C" "$COMM" < \
3185 $WDIR/$DIR/$F.man.cdiff > \
3186 $WDIR/$DIR/$F.man.cdiff.html
3187 print " man-cdiffs\c"
3188 ${UDIFFCMD:-diff -bt -U 5} $ofile.man.txt \
3189 $nfile.man.txt > $WDIR/$DIR/$F.man.udiff
3190 diff_to_html $F $DIR/$F "U" "$COMM" < \
3191 $WDIR/$DIR/$F.man.udiff > \
3192 $WDIR/$DIR/$F.man.udiff.html
3193 print " man-udiffs\c"
3194 if [[ -x $WDIFF ]]; then
3195 $WDIFF -c "$COMM" -t "$WNAME Wdiff $DIR/$F" \
3196 $ofile.man.txt $nfile.man.txt > \
3197 $WDIR/$DIR/$F.man.wdiff.html 2>/dev/null
3198 if [[ $? -eq 0 ]]; then
3199 print " man-wdiffs\c"
3200 else
3201 print " man-wdiffs[fail]\c"
3204 sdiff_to_html $ofile.man.txt $nfile.man.txt $F.man $DIR \
3205 "$COMM" > $WDIR/$DIR/$F.man.sdiff.html
3206 print " man-sdiffs\c"
3207 print " man-frames\c"
3209 rm -f $ofile.man.txt $nfile.man.txt
3210 rm -f $WDIR/$DIR/$F.man.cdiff $WDIR/$DIR/$F.man.udiff
3214 # Now we generate the postscript for this file. We generate diffs
3215 # only in the event that there is delta, or the file is new (it seems
3216 # tree-killing to print out the contents of deleted files).
3218 if [[ -f $nfile ]]; then
3219 ocr=$ofile
3220 [[ ! -f $ofile ]] && ocr=/dev/null
3222 if [[ -z $mv_but_nodiff ]]; then
3223 textcomm=`getcomments text $P $PP`
3224 if [[ -x $CODEREVIEW ]]; then
3225 $CODEREVIEW -y "$textcomm" \
3226 -e $ocr $nfile \
3227 > /tmp/$$.psfile 2>/dev/null &&
3228 cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3229 if [[ $? -eq 0 ]]; then
3230 print " ps\c"
3231 else
3232 print " ps[fail]\c"
3238 if [[ -f $ofile ]]; then
3239 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3240 print " old\c"
3243 if [[ -f $nfile ]]; then
3244 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3245 print " new\c"
3248 cd $OWD
3250 print
3251 done
3253 frame_nav_js > $WDIR/ancnav.js
3254 frame_navigation > $WDIR/ancnav.html
3256 if [[ ! -f $WDIR/$WNAME.ps ]]; then
3257 print " Generating PDF: Skipped: no output available"
3258 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3259 print " Generating PDF: \c"
3260 fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3261 print "Done."
3262 else
3263 print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3266 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3267 # delete it - prevent accidental publishing of closed source
3269 if [[ -n "$Oflag" ]]; then
3270 $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3273 # Now build the index.html file that contains
3274 # links to the source files and their diffs.
3276 cd $CWS
3278 # Save total changed lines for Code Inspection.
3279 print "$TOTL" > $WDIR/TotalChangedLines
3281 print " index.html: \c"
3282 INDEXFILE=$WDIR/index.html
3283 exec 3<&1 # duplicate stdout to FD3.
3284 exec 1<&- # Close stdout.
3285 exec > $INDEXFILE # Open stdout to index file.
3287 print "$HTML<head>$STDHEAD"
3288 print "<title>$WNAME</title>"
3289 print "</head>"
3290 print "<body id=\"SUNWwebrev\">"
3291 print "<div class=\"summary\">"
3292 print "<h2>Code Review for $WNAME</h2>"
3294 print "<table>"
3297 # Get the preparer's name:
3299 # If the SCM detected is Git, and the configuration property user.name is
3300 # available, use that, but be careful to properly escape angle brackets (HTML
3301 # syntax characters) in the email address.
3303 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3304 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3306 preparer=
3307 if [[ "$SCM_MODE" == git ]]; then
3308 preparer=$(git config user.name 2>/dev/null)
3309 if [[ -n "$preparer" ]]; then
3310 preparer="$(echo "$preparer" | html_quote)"
3313 if [[ -z "$preparer" ]]; then
3314 preparer=$(
3315 $PERL -e '
3316 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3317 if ($login) {
3318 $gcos =~ s/\&/ucfirst($login)/e;
3319 printf "%s (%s)\n", $gcos, $login;
3320 } else {
3321 printf "(unknown)\n";
3326 PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3327 print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3328 print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3329 print "</td></tr>"
3330 print "<tr><th>Compare against:</th><td>"
3331 if [[ -n $parent_webrev ]]; then
3332 print "webrev at $parent_webrev"
3333 else
3334 print "${PRETTY_PWS:-$PWS}"
3336 print "</td></tr>"
3337 print "<tr><th>Summary of changes:</th><td>"
3338 printCI $TOTL $TINS $TDEL $TMOD $TUNC
3339 print "</td></tr>"
3341 if [[ -f $WDIR/$WNAME.patch ]]; then
3342 wpatch_url="$(print $WNAME.patch | url_encode)"
3343 print "<tr><th>Patch of changes:</th><td>"
3344 print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3346 if [[ -f $WDIR/$WNAME.pdf ]]; then
3347 wpdf_url="$(print $WNAME.pdf | url_encode)"
3348 print "<tr><th>Printable review:</th><td>"
3349 print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3352 if [[ -n "$iflag" ]]; then
3353 print "<tr><th>Author comments:</th><td><div>"
3354 cat /tmp/$$.include
3355 print "</div></td></tr>"
3357 print "</table>"
3358 print "</div>"
3361 # Second pass through the files: generate the rest of the index file
3363 cat $FLIST | while read LINE
3365 set - $LINE
3366 P=$1
3368 if [[ $# == 2 ]]; then
3369 PP=$2
3370 oldname="$PP"
3371 else
3372 PP=$P
3373 oldname=""
3376 mv_but_nodiff=
3377 cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3378 if [[ $? == 0 && -n "$oldname" ]]; then
3379 mv_but_nodiff=1
3382 DIR=${P%/*}
3383 if [[ $DIR == $P ]]; then
3384 DIR="." # File at root of workspace
3387 # Avoid processing the same file twice.
3388 # It's possible for renamed files to
3389 # appear twice in the file list
3391 F=$WDIR/$P
3393 print "<p>"
3395 # If there's a diffs file, make diffs links
3397 if [[ -f $F.cdiff.html ]]; then
3398 cdiff_url="$(print $P.cdiff.html | url_encode)"
3399 udiff_url="$(print $P.udiff.html | url_encode)"
3400 sdiff_url="$(print $P.sdiff.html | url_encode)"
3401 frames_url="$(print $P.frames.html | url_encode)"
3402 print "<a href=\"$cdiff_url\">Cdiffs</a>"
3403 print "<a href=\"$udiff_url\">Udiffs</a>"
3404 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3405 wdiff_url="$(print $P.wdiff.html | url_encode)"
3406 print "<a href=\"$wdiff_url\">Wdiffs</a>"
3408 print "<a href=\"$sdiff_url\">Sdiffs</a>"
3409 print "<a href=\"$frames_url\">Frames</a>"
3410 else
3411 print " ------ ------"
3412 if [[ -x $WDIFF ]]; then
3413 print " ------"
3415 print " ------ ------"
3418 # If there's an old file, make the link
3420 if [[ -f $F-.html ]]; then
3421 oldfile_url="$(print $P-.html | url_encode)"
3422 print "<a href=\"$oldfile_url\">Old</a>"
3423 else
3424 print " ---"
3427 # If there's an new file, make the link
3429 if [[ -f $F.html ]]; then
3430 newfile_url="$(print $P.html | url_encode)"
3431 print "<a href=\"$newfile_url\">New</a>"
3432 else
3433 print " ---"
3436 if [[ -f $F.patch ]]; then
3437 patch_url="$(print $P.patch | url_encode)"
3438 print "<a href=\"$patch_url\">Patch</a>"
3439 else
3440 print " -----"
3443 if [[ -f $WDIR/raw_files/new/$P ]]; then
3444 rawfiles_url="$(print raw_files/new/$P | url_encode)"
3445 print "<a href=\"$rawfiles_url\">Raw</a>"
3446 else
3447 print " ---"
3450 print "<b>$P</b>"
3452 # For renamed files, clearly state whether or not they are modified
3453 if [[ -f "$oldname" ]]; then
3454 if [[ -n "$mv_but_nodiff" ]]; then
3455 print "<i>(copied from $oldname)</i>"
3456 else
3457 print "<i>(copied and modified from $oldname)</i>"
3459 elif [[ -n "$oldname" ]]; then
3460 if [[ -n "$mv_but_nodiff" ]]; then
3461 print "<i>(renamed from $oldname)</i>"
3462 else
3463 print "<i>(renamed and modified from $oldname)</i>"
3467 # If there's an old file, but no new file, the file was deleted
3468 if [[ -f $F-.html && ! -f $F.html ]]; then
3469 print " <i>(deleted)</i>"
3472 # Check for usr/closed and deleted_files/usr/closed
3473 if [ ! -z "$Oflag" ]; then
3474 if [[ $P == usr/closed/* || \
3475 $P == deleted_files/usr/closed/* ]]; then
3476 print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3477 "this review</i>"
3481 manpage=
3482 if [[ -f $F.man.cdiff.html || \
3483 -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3484 manpage=1
3485 print "<br/>man:"
3488 if [[ -f $F.man.cdiff.html ]]; then
3489 mancdiff_url="$(print $P.man.cdiff.html | url_encode)"
3490 manudiff_url="$(print $P.man.udiff.html | url_encode)"
3491 mansdiff_url="$(print $P.man.sdiff.html | url_encode)"
3492 manframes_url="$(print $P.man.frames.html | url_encode)"
3493 print "<a href=\"$mancdiff_url\">Cdiffs</a>"
3494 print "<a href=\"$manudiff_url\">Udiffs</a>"
3495 if [[ -f $F.man.wdiff.html && -x $WDIFF ]]; then
3496 manwdiff_url="$(print $P.man.wdiff.html | url_encode)"
3497 print "<a href=\"$manwdiff_url\">Wdiffs</a>"
3499 print "<a href=\"$mansdiff_url\">Sdiffs</a>"
3500 print "<a href=\"$manframes_url\">Frames</a>"
3501 elif [[ -n $manpage ]]; then
3502 print " ------ ------"
3503 if [[ -x $WDIFF ]]; then
3504 print " ------"
3506 print " ------ ------"
3509 if [[ -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3510 mantxt_url="$(print raw_files/new/$P.man.txt.html | url_encode)"
3511 print "<a href=\"$mantxt_url\">TXT</a>"
3512 manhtml_url="$(print raw_files/new/$P.man.html | url_encode)"
3513 print "<a href=\"$manhtml_url\">HTML</a>"
3514 manraw_url="$(print raw_files/new/$P.man.raw | url_encode)"
3515 print "<a href=\"$manraw_url\">Raw</a>"
3516 elif [[ -n $manpage ]]; then
3517 print " --- ---- ---"
3520 print "</p>"
3522 # Insert delta comments
3523 print "<blockquote><pre>"
3524 getcomments html $P $PP
3525 print "</pre>"
3527 # Add additional comments comment
3528 print "<!-- Add comments to explain changes in $P here -->"
3530 # Add count of changes.
3531 if [[ -f $F.count ]]; then
3532 cat $F.count
3533 rm $F.count
3536 if [[ $SCM_MODE == "unknown" ]]; then
3537 # Include warnings for important file mode situations:
3538 # 1) New executable files
3539 # 2) Permission changes of any kind
3540 # 3) Existing executable files
3541 old_mode=
3542 if [[ -f $WDIR/raw_files/old/$PP ]]; then
3543 old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3546 new_mode=
3547 if [[ -f $WDIR/raw_files/new/$P ]]; then
3548 new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3551 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3552 print "<span class=\"chmod\">"
3553 print "<p>new executable file: mode $new_mode</p>"
3554 print "</span>"
3555 elif [[ -n "$old_mode" && -n "$new_mode" &&
3556 "$old_mode" != "$new_mode" ]]; then
3557 print "<span class=\"chmod\">"
3558 print "<p>mode change: $old_mode to $new_mode</p>"
3559 print "</span>"
3560 elif [[ "$new_mode" = *[1357]* ]]; then
3561 print "<span class=\"chmod\">"
3562 print "<p>executable file: mode $new_mode</p>"
3563 print "</span>"
3567 print "</blockquote>"
3568 done
3570 print
3571 print
3572 print "<hr></hr>"
3573 print "<p style=\"font-size: small\">"
3574 print "This code review page was prepared using <b>$0</b>."
3575 print "Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3576 print "illumos</a> project. The latest version may be obtained"
3577 print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3578 print "</body>"
3579 print "</html>"
3581 exec 1<&- # Close FD 1.
3582 exec 1<&3 # dup FD 3 to restore stdout.
3583 exec 3<&- # close FD 3.
3585 print "Done."
3588 # If remote deletion was specified and fails do not continue.
3590 if [[ -n $Dflag ]]; then
3591 delete_webrev 1 1
3592 (( $? == 0 )) || exit $?
3595 if [[ -n $Uflag ]]; then
3596 upload_webrev
3597 exit $?