1 #!/usr/bin/ksh93 -p
2 #
3 # CDDL HEADER START
4 #
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.
8 #
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.
13 #
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]
19 #
20 # CDDL HEADER END
21 #
22
23 #
24 # Copyright 2009 Sun Microsystems, Inc. All rights reserved.
25 # Use is subject to license terms.
26 #
27
28 #
29 # This script takes a file list and a workspace and builds a set of html files
30 # suitable for doing a code review of source changes via a web page.
31 # Documentation is available via the manual page, webrev.1, or just
32 # type 'webrev -h'.
33 #
34 # Acknowledgements to contributors to webrev are listed in the webrev(1)
35 # man page.
36 #
37
38 REMOVED_COLOR=brown
39 CHANGED_COLOR=blue
40 NEW_COLOR=blue
41
42 HTML='<?xml version="1.0"?>
43 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
44 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
45 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
46
47 FRAMEHTML='<?xml version="1.0"?>
48 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
49 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
50 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
51
52 STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta>
53 <meta http-equiv="Pragma" content="no-cache"></meta>
54 <meta http-equiv="Expires" content="-1"></meta>
55 <!--
56 Note to customizers: the body of the webrev is IDed as SUNWwebrev
57 to allow easy overriding by users of webrev via the userContent.css
58 mechanism available in some browsers.
59
60 For example, to have all "removed" information be red instead of
61 brown, set a rule in your userContent.css file like:
62
63 body#SUNWwebrev span.removed { color: red ! important; }
64 -->
65 <style type="text/css" media="screen">
66 body {
67 background-color: #eeeeee;
68 }
69 hr {
70 border: none 0;
71 border-top: 1px solid #aaa;
72 height: 1px;
73 }
74 div.summary {
75 font-size: .8em;
76 border-bottom: 1px solid #aaa;
77 padding-left: 1em;
78 padding-right: 1em;
79 }
80 div.summary h2 {
81 margin-bottom: 0.3em;
82 }
83 div.summary table th {
84 text-align: right;
85 vertical-align: top;
86 white-space: nowrap;
87 }
88 span.lineschanged {
89 font-size: 0.7em;
90 }
91 span.oldmarker {
92 color: red;
93 font-size: large;
94 font-weight: bold;
95 }
96 span.newmarker {
97 color: green;
98 font-size: large;
99 font-weight: bold;
100 }
101 span.removed {
102 color: brown;
103 }
104 span.changed {
105 color: blue;
106 }
107 span.new {
108 color: blue;
109 font-weight: bold;
110 }
111 span.chmod {
112 font-size: 0.7em;
113 color: #db7800;
114 }
115 a.print { font-size: x-small; }
116 a:hover { background-color: #ffcc99; }
117 </style>
118
119 <style type="text/css" media="print">
120 pre { font-size: 0.8em; font-family: courier, monospace; }
121 span.removed { color: #444; font-style: italic }
122 span.changed { font-weight: bold; }
123 span.new { font-weight: bold; }
124 span.newmarker { font-size: 1.2em; font-weight: bold; }
125 span.oldmarker { font-size: 1.2em; font-weight: bold; }
126 a.print {display: none}
127 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
128 </style>
129 '
130
131 #
132 # UDiffs need a slightly different CSS rule for 'new' items (we don't
133 # want them to be bolded as we do in cdiffs or sdiffs).
134 #
135 UDIFFCSS='
136 <style type="text/css" media="screen">
137 span.new {
138 color: blue;
139 font-weight: normal;
140 }
141 </style>
142 '
143
144 #
145 # Display remote target with prefix and trailing slash.
146 #
147 function print_upload_header
148 {
149 typeset -r prefix=$1
150 typeset display_target
151
152 if [[ -z $tflag ]]; then
153 display_target=${prefix}${remote_target}
154 else
155 display_target=${remote_target}
156 fi
157
158 if [[ ${display_target} != */ ]]; then
159 display_target=${display_target}/
160 fi
161
162 print " Upload to: ${display_target}\n" \
163 " Uploading: \c"
164 }
165
166 #
167 # Upload the webrev via rsync. Return 0 on success, 1 on error.
168 #
169 function rsync_upload
170 {
171 if (( $# != 2 )); then
172 print "\nERROR: rsync_upload: wrong usage ($#)"
173 exit 1
174 fi
175
176 typeset -r dst=$1
177 integer -r print_err_msg=$2
178
179 print_upload_header ${rsync_prefix}
180 print "rsync ... \c"
181 typeset -r err_msg=$( $MKTEMP /tmp/rsync_err.XXXXXX )
182 if [[ -z $err_msg ]]; then
183 print "\nERROR: rsync_upload: cannot create temporary file"
184 return 1
185 fi
186 #
187 # The source directory must end with a slash in order to copy just
188 # directory contents, not the whole directory.
189 #
190 typeset src_dir=$WDIR
191 if [[ ${src_dir} != */ ]]; then
192 src_dir=${src_dir}/
193 fi
194 $RSYNC -r -q ${src_dir} $dst 2>$err_msg
195 if (( $? != 0 )); then
196 if (( ${print_err_msg} > 0 )); then
197 print "Failed.\nERROR: rsync failed"
198 print "src dir: '${src_dir}'\ndst dir: '$dst'"
199 print "error messages:"
200 $SED 's/^/> /' $err_msg
201 rm -f $err_msg
202 fi
203 return 1
204 fi
205
206 rm -f $err_msg
207 print "Done."
208 return 0
209 }
210
211 #
212 # Create directories on remote host using SFTP. Return 0 on success,
213 # 1 on failure.
214 #
215 function remote_mkdirs
216 {
217 typeset -r dir_spec=$1
218
219 #
220 # If the supplied path is absolute we assume all directories are
221 # created, otherwise try to create all directories in the path
222 # except the last one which will be created by scp.
223 #
224 if [[ "${dir_spec}" == */* && "${dir_spec}" != /* ]]; then
225 print "mkdirs \c"
226 #
227 # Remove the last directory from directory specification.
228 #
229 typeset -r dirs_mk=${dir_spec%/*}
230 typeset -r batch_file_mkdir=$( $MKTEMP \
231 /tmp/webrev_mkdir.XXXXXX )
232 if [[ -z $batch_file_mkdir ]]; then
233 print "\nERROR: remote_mkdirs:" \
234 "cannot create temporary file for batch file"
235 return 1
236 fi
237 OLDIFS=$IFS
238 IFS=/
239 typeset dir
240 for dir in ${dirs_mk}; do
241 #
242 # Use the '-' prefix to ignore mkdir errors in order
243 # to avoid an error in case the directory already
244 # exists. We check the directory with chdir to be sure
245 # there is one.
246 #
247 print -- "-mkdir ${dir}" >> ${batch_file_mkdir}
248 print "chdir ${dir}" >> ${batch_file_mkdir}
249 done
250 IFS=$OLDIFS
251 typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
252 if [[ -z ${sftp_err_msg} ]]; then
253 print "\nERROR: remote_mkdirs:" \
254 "cannot create temporary file for error messages"
255 return 1
256 fi
257 $SFTP -b ${batch_file_mkdir} ${host_spec} 2>${sftp_err_msg} 1>&2
258 if (( $? != 0 )); then
259 print "\nERROR: failed to create remote directories"
260 print "error messages:"
261 $SED 's/^/> /' ${sftp_err_msg}
262 rm -f ${sftp_err_msg} ${batch_file_mkdir}
263 return 1
264 fi
265 rm -f ${sftp_err_msg} ${batch_file_mkdir}
266 fi
267
268 return 0
269 }
270
271 #
272 # Upload the webrev via SSH. Return 0 on success, 1 on error.
273 #
274 function ssh_upload
275 {
276 if (( $# != 1 )); then
277 print "\nERROR: ssh_upload: wrong number of arguments"
278 exit 1
279 fi
280
281 typeset dst=$1
282 typeset -r host_spec=${dst%%:*}
283 typeset -r dir_spec=${dst#*:}
284
285 #
286 # Display the upload information before calling delete_webrev
287 # because it will also print its progress.
288 #
289 print_upload_header ${ssh_prefix}
290
291 #
292 # If the deletion was explicitly requested there is no need
293 # to perform it again.
294 #
295 if [[ -z $Dflag ]]; then
296 #
297 # We do not care about return value because this might be
298 # the first time this directory is uploaded.
299 #
300 delete_webrev 0
301 fi
302
303 #
304 # Create remote directories. Any error reporting will be done
305 # in remote_mkdirs function.
306 #
307 remote_mkdirs ${dir_spec}
308 if (( $? != 0 )); then
309 return 1
310 fi
311
312 print "upload ... \c"
313 typeset -r scp_err_msg=$( $MKTEMP /tmp/scp_err.XXXXXX )
314 if [[ -z ${scp_err_msg} ]]; then
315 print "\nERROR: ssh_upload:" \
316 "cannot create temporary file for error messages"
317 return 1
318 fi
319 $SCP -q -C -B -o PreferredAuthentications=publickey -r \
320 $WDIR $dst 2>${scp_err_msg}
321 if (( $? != 0 )); then
322 print "Failed.\nERROR: scp failed"
323 print "src dir: '$WDIR'\ndst dir: '$dst'"
324 print "error messages:"
325 $SED 's/^/> /' ${scp_err_msg}
326 rm -f ${scp_err_msg}
327 return 1
328 fi
329
330 rm -f ${scp_err_msg}
331 print "Done."
332 return 0
333 }
334
335 #
336 # Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp
337 # on failure. If first argument is 1 then perform the check of sftp return
338 # value otherwise ignore it. If second argument is present it means this run
339 # only performs deletion.
340 #
341 function delete_webrev
342 {
343 if (( $# < 1 )); then
344 print "delete_webrev: wrong number of arguments"
345 exit 1
346 fi
347
348 integer -r check=$1
349 integer delete_only=0
350 if (( $# == 2 )); then
351 delete_only=1
352 fi
353
354 #
355 # Strip the transport specification part of remote target first.
356 #
357 typeset -r stripped_target=${remote_target##*://}
358 typeset -r host_spec=${stripped_target%%:*}
359 typeset -r dir_spec=${stripped_target#*:}
360 typeset dir_rm
361
362 #
363 # Do not accept an absolute path.
364 #
365 if [[ ${dir_spec} == /* ]]; then
366 return 1
367 fi
368
369 #
370 # Strip the ending slash.
371 #
372 if [[ ${dir_spec} == */ ]]; then
373 dir_rm=${dir_spec%%/}
374 else
375 dir_rm=${dir_spec}
376 fi
377
378 if (( ${delete_only} > 0 )); then
379 print " Removing: \c"
380 else
381 print "rmdir \c"
382 fi
383 if [[ -z "$dir_rm" ]]; then
384 print "\nERROR: empty directory for removal"
385 return 1
386 fi
387
388 #
389 # Prepare batch file.
390 #
391 typeset -r batch_file_rm=$( $MKTEMP /tmp/webrev_remove.XXXXXX )
392 if [[ -z $batch_file_rm ]]; then
393 print "\nERROR: delete_webrev: cannot create temporary file"
394 return 1
395 fi
396 print "rename $dir_rm $TRASH_DIR/removed.$$" > $batch_file_rm
397
398 #
399 # Perform remote deletion and remove the batch file.
400 #
401 typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
402 if [[ -z ${sftp_err_msg} ]]; then
403 print "\nERROR: delete_webrev:" \
404 "cannot create temporary file for error messages"
405 return 1
406 fi
407 $SFTP -b $batch_file_rm $host_spec 2>${sftp_err_msg} 1>&2
408 integer -r ret=$?
409 rm -f $batch_file_rm
410 if (( $ret != 0 && $check > 0 )); then
411 print "Failed.\nERROR: failed to remove remote directories"
412 print "error messages:"
413 $SED 's/^/> /' ${sftp_err_msg}
414 rm -f ${sftp_err_msg}
415 return $ret
416 fi
417 rm -f ${sftp_err_msg}
418 if (( ${delete_only} > 0 )); then
419 print "Done."
420 fi
421
422 return 0
423 }
424
425 #
426 # Upload webrev to remote site
427 #
428 function upload_webrev
429 {
430 integer ret
431
432 if [[ ! -d "$WDIR" ]]; then
433 print "\nERROR: webrev directory '$WDIR' does not exist"
434 return 1
435 fi
436
437 #
438 # Perform a late check to make sure we do not upload closed source
439 # to remote target when -n is used. If the user used custom remote
440 # target he probably knows what he is doing.
441 #
442 if [[ -n $nflag && -z $tflag ]]; then
443 $FIND $WDIR -type d -name closed \
444 | $GREP closed >/dev/null
445 if (( $? == 0 )); then
446 print "\nERROR: directory '$WDIR' contains" \
447 "\"closed\" directory"
448 return 1
449 fi
450 fi
451
452
453 #
454 # We have the URI for remote destination now so let's start the upload.
455 #
456 if [[ -n $tflag ]]; then
457 if [[ "${remote_target}" == ${rsync_prefix}?* ]]; then
458 rsync_upload ${remote_target##$rsync_prefix} 1
459 ret=$?
460 return $ret
461 elif [[ "${remote_target}" == ${ssh_prefix}?* ]]; then
462 ssh_upload ${remote_target##$ssh_prefix}
463 ret=$?
464 return $ret
465 fi
466 else
467 #
468 # Try rsync first and fallback to SSH in case it fails.
469 #
470 rsync_upload ${remote_target} 0
471 ret=$?
472 if (( $ret != 0 )); then
473 print "Failed. (falling back to SSH)"
474 ssh_upload ${remote_target}
475 ret=$?
476 fi
477 return $ret
478 fi
479 }
480
481 #
482 # input_cmd | url_encode | output_cmd
483 #
484 # URL-encode (percent-encode) reserved characters as defined in RFC 3986.
485 #
486 # Reserved characters are: :/?#[]@!$&'()*+,;=
487 #
488 # While not a reserved character itself, percent '%' is reserved by definition
489 # so encode it first to avoid recursive transformation, and skip '/' which is
490 # a path delimiter.
491 #
492 # The quotation character is deliberately not escaped in order to make
493 # the substitution work with GNU sed.
494 #
495 function url_encode
496 {
497 $SED -e "s|%|%25|g" -e "s|:|%3A|g" -e "s|\&|%26|g" \
498 -e "s|?|%3F|g" -e "s|#|%23|g" -e "s|\[|%5B|g" \
499 -e "s|*|%2A|g" -e "s|@|%40|g" -e "s|\!|%21|g" \
500 -e "s|=|%3D|g" -e "s|;|%3B|g" -e "s|\]|%5D|g" \
501 -e "s|(|%28|g" -e "s|)|%29|g" -e "s|'|%27|g" \
502 -e "s|+|%2B|g" -e "s|\,|%2C|g" -e "s|\\\$|%24|g"
503 }
504
505 #
506 # input_cmd | html_quote | output_cmd
507 # or
508 # html_quote filename | output_cmd
509 #
510 # Make a piece of source code safe for display in an HTML <pre> block.
511 #
512 html_quote()
513 {
514 $SED -e "s/&/\&/g" -e "s/</\</g" -e "s/>/\>/g" "$@" | expand
515 }
516
517 #
518 # input_cmd | its2url | output_cmd
519 #
520 # Scan for information tracking system references and insert <a> links to the
521 # relevant databases.
522 #
523 its2url()
524 {
525 $SED -f ${its_sed_script}
526 }
527
528 #
529 # strip_unchanged <infile> | output_cmd
530 #
531 # Removes chunks of sdiff documents that have not changed. This makes it
532 # easier for a code reviewer to find the bits that have changed.
533 #
534 # Deleted lines of text are replaced by a horizontal rule. Some
535 # identical lines are retained before and after the changed lines to
536 # provide some context. The number of these lines is controlled by the
537 # variable C in the $AWK script below.
538 #
539 # The script detects changed lines as any line that has a "<span class="
540 # string embedded (unchanged lines have no particular class and are not
541 # part of a <span>). Blank lines (without a sequence number) are also
542 # detected since they flag lines that have been inserted or deleted.
543 #
544 strip_unchanged()
545 {
546 $AWK '
547 BEGIN { C = c = 20 }
548 NF == 0 || /<span class="/ {
549 if (c > C) {
550 c -= C
551 inx = 0
552 if (c > C) {
553 print "\n</pre><hr></hr><pre>"
554 inx = c % C
555 c = C
556 }
557
558 for (i = 0; i < c; i++)
559 print ln[(inx + i) % C]
560 }
561 c = 0;
562 print
563 next
564 }
565 { if (c >= C) {
566 ln[c % C] = $0
567 c++;
568 next;
569 }
570 c++;
571 print
572 }
573 END { if (c > (C * 2)) print "\n</pre><hr></hr>" }
574
575 ' $1
576 }
577
578 #
579 # sdiff_to_html
580 #
581 # This function takes two files as arguments, obtains their diff, and
582 # processes the diff output to present the files as an HTML document with
583 # the files displayed side-by-side, differences shown in color. It also
584 # takes a delta comment, rendered as an HTML snippet, as the third
585 # argument. The function takes two files as arguments, then the name of
586 # file, the path, and the comment. The HTML will be delivered on stdout,
587 # e.g.
588 #
589 # $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
590 # new/usr/src/tools/scripts/webrev.sh \
591 # webrev.sh usr/src/tools/scripts \
592 # '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
593 # 1234567</a> my bugid' > <file>.html
594 #
595 # framed_sdiff() is then called which creates $2.frames.html
596 # in the webrev tree.
597 #
598 # FYI: This function is rather unusual in its use of awk. The initial
599 # diff run produces conventional diff output showing changed lines mixed
600 # with editing codes. The changed lines are ignored - we're interested in
601 # the editing codes, e.g.
602 #
603 # 8c8
604 # 57a61
605 # 63c66,76
606 # 68,93d80
607 # 106d90
608 # 108,110d91
609 #
610 # These editing codes are parsed by the awk script and used to generate
611 # another awk script that generates HTML, e.g the above lines would turn
612 # into something like this:
613 #
614 # BEGIN { printf "<pre>\n" }
615 # function sp(n) {for (i=0;i<n;i++)printf "\n"}
616 # function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
617 # NR==8 {wl("#7A7ADD");next}
618 # NR==54 {wl("#7A7ADD");sp(3);next}
619 # NR==56 {wl("#7A7ADD");next}
620 # NR==57 {wl("black");printf "\n"; next}
621 # : :
622 #
623 # This script is then run on the original source file to generate the
624 # HTML that corresponds to the source file.
625 #
626 # The two HTML files are then combined into a single piece of HTML that
627 # uses an HTML table construct to present the files side by side. You'll
628 # notice that the changes are color-coded:
629 #
630 # black - unchanged lines
631 # blue - changed lines
632 # bold blue - new lines
633 # brown - deleted lines
634 #
635 # Blank lines are inserted in each file to keep unchanged lines in sync
636 # (side-by-side). This format is familiar to users of sdiff(1) or
637 # Teamware's filemerge tool.
638 #
639 sdiff_to_html()
640 {
641 diff -b $1 $2 > /tmp/$$.diffs
642
643 TNAME=$3
644 TPATH=$4
645 COMMENT=$5
646
647 #
648 # Now we have the diffs, generate the HTML for the old file.
649 #
650 $AWK '
651 BEGIN {
652 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
653 printf "function removed() "
654 printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
655 printf "function changed() "
656 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
657 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
658 }
659 /^</ {next}
660 /^>/ {next}
661 /^---/ {next}
662
663 {
664 split($1, a, /[cad]/) ;
665 if (index($1, "a")) {
666 if (a[1] == 0) {
667 n = split(a[2], r, /,/);
668 if (n == 1)
669 printf "BEGIN\t\t{sp(1)}\n"
670 else
671 printf "BEGIN\t\t{sp(%d)}\n",\
672 (r[2] - r[1]) + 1
673 next
674 }
675
676 printf "NR==%s\t\t{", a[1]
677 n = split(a[2], r, /,/);
678 s = r[1];
679 if (n == 1)
680 printf "bl();printf \"\\n\"; next}\n"
681 else {
682 n = r[2] - r[1]
683 printf "bl();sp(%d);next}\n",\
684 (r[2] - r[1]) + 1
685 }
686 next
687 }
688 if (index($1, "d")) {
689 n = split(a[1], r, /,/);
690 n1 = r[1]
691 n2 = r[2]
692 if (n == 1)
693 printf "NR==%s\t\t{removed(); next}\n" , n1
694 else
695 printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
696 next
697 }
698 if (index($1, "c")) {
699 n = split(a[1], r, /,/);
700 n1 = r[1]
701 n2 = r[2]
702 final = n2
703 d1 = 0
704 if (n == 1)
705 printf "NR==%s\t\t{changed();" , n1
706 else {
707 d1 = n2 - n1
708 printf "NR==%s,NR==%s\t{changed();" , n1, n2
709 }
710 m = split(a[2], r, /,/);
711 n1 = r[1]
712 n2 = r[2]
713 if (m > 1) {
714 d2 = n2 - n1
715 if (d2 > d1) {
716 if (n > 1) printf "if (NR==%d)", final
717 printf "sp(%d);", d2 - d1
718 }
719 }
720 printf "next}\n" ;
721
722 next
723 }
724 }
725
726 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
727 ' /tmp/$$.diffs > /tmp/$$.file1
728
729 #
730 # Now generate the HTML for the new file
731 #
732 $AWK '
733 BEGIN {
734 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
735 printf "function new() "
736 printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
737 printf "function changed() "
738 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
739 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
740 }
741
742 /^</ {next}
743 /^>/ {next}
744 /^---/ {next}
745
746 {
747 split($1, a, /[cad]/) ;
748 if (index($1, "d")) {
749 if (a[2] == 0) {
750 n = split(a[1], r, /,/);
751 if (n == 1)
752 printf "BEGIN\t\t{sp(1)}\n"
753 else
754 printf "BEGIN\t\t{sp(%d)}\n",\
755 (r[2] - r[1]) + 1
756 next
757 }
758
759 printf "NR==%s\t\t{", a[2]
760 n = split(a[1], r, /,/);
761 s = r[1];
762 if (n == 1)
763 printf "bl();printf \"\\n\"; next}\n"
764 else {
765 n = r[2] - r[1]
766 printf "bl();sp(%d);next}\n",\
767 (r[2] - r[1]) + 1
768 }
769 next
770 }
771 if (index($1, "a")) {
772 n = split(a[2], r, /,/);
773 n1 = r[1]
774 n2 = r[2]
775 if (n == 1)
776 printf "NR==%s\t\t{new() ; next}\n" , n1
777 else
778 printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
779 next
780 }
781 if (index($1, "c")) {
782 n = split(a[2], r, /,/);
783 n1 = r[1]
784 n2 = r[2]
785 final = n2
786 d2 = 0;
787 if (n == 1) {
788 final = n1
789 printf "NR==%s\t\t{changed();" , n1
790 } else {
791 d2 = n2 - n1
792 printf "NR==%s,NR==%s\t{changed();" , n1, n2
793 }
794 m = split(a[1], r, /,/);
795 n1 = r[1]
796 n2 = r[2]
797 if (m > 1) {
798 d1 = n2 - n1
799 if (d1 > d2) {
800 if (n > 1) printf "if (NR==%d)", final
801 printf "sp(%d);", d1 - d2
802 }
803 }
804 printf "next}\n" ;
805 next
806 }
807 }
808 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
809 ' /tmp/$$.diffs > /tmp/$$.file2
810
811 #
812 # Post-process the HTML files by running them back through $AWK
813 #
814 html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html
815
816 html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html
817
818 #
819 # Now combine into a valid HTML file and side-by-side into a table
820 #
821 print "$HTML<head>$STDHEAD"
822 print "<title>$WNAME Sdiff $TPATH/$TNAME</title>"
823 print "</head><body id=\"SUNWwebrev\">"
824 print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
825 print "<pre>$COMMENT</pre>\n"
826 print "<table><tr valign=\"top\">"
827 print "<td><pre>"
828
829 strip_unchanged /tmp/$$.file1.html
830
831 print "</pre></td><td><pre>"
832
833 strip_unchanged /tmp/$$.file2.html
834
835 print "</pre></td>"
836 print "</tr></table>"
837 print "</body></html>"
838
839 framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
840 "$COMMENT"
841 }
842
843
844 #
845 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
846 #
847 # Expects lefthand and righthand side html files created by sdiff_to_html.
848 # We use insert_anchors() to augment those with HTML navigation anchors,
849 # and then emit the main frame. Content is placed into:
850 #
851 # $WDIR/DIR/$TNAME.lhs.html
852 # $WDIR/DIR/$TNAME.rhs.html
853 # $WDIR/DIR/$TNAME.frames.html
854 #
855 # NOTE: We rely on standard usage of $WDIR and $DIR.
856 #
857 function framed_sdiff
858 {
859 typeset TNAME=$1
860 typeset TPATH=$2
861 typeset lhsfile=$3
862 typeset rhsfile=$4
863 typeset comments=$5
864 typeset RTOP
865
866 # Enable html files to access WDIR via a relative path.
867 RTOP=$(relative_dir $TPATH $WDIR)
868
869 # Make the rhs/lhs files and output the frameset file.
870 print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
871
872 cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
873 <script type="text/javascript" src="${RTOP}ancnav.js"></script>
874 </head>
875 <body id="SUNWwebrev" onkeypress="keypress(event);">
876 <a name="0"></a>
877 <pre>$comments</pre><hr></hr>
878 EOF
879
880 cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
881
882 insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
883 insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
884
885 close='</body></html>'
886
887 print $close >> $WDIR/$DIR/$TNAME.lhs.html
888 print $close >> $WDIR/$DIR/$TNAME.rhs.html
889
890 print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
891 print "<title>$WNAME Framed-Sdiff " \
892 "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
893 cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
894 <frameset rows="*,60">
895 <frameset cols="50%,50%">
896 <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
897 <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
898 </frameset>
899 <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0"
900 marginheight="0" name="nav"></frame>
901 <noframes>
902 <body id="SUNWwebrev">
903 Alas 'frames' webrev requires that your browser supports frames
904 and has the feature enabled.
905 </body>
906 </noframes>
907 </frameset>
908 </html>
909 EOF
910 }
911
912
913 #
914 # fix_postscript
915 #
916 # Merge codereview output files to a single conforming postscript file, by:
917 # - removing all extraneous headers/trailers
918 # - making the page numbers right
919 # - removing pages devoid of contents which confuse some
920 # postscript readers.
921 #
922 # From Casper.
923 #
924 function fix_postscript
925 {
926 infile=$1
927
928 cat > /tmp/$$.crmerge.pl << \EOF
929
930 print scalar(<>); # %!PS-Adobe---
931 print "%%Orientation: Landscape\n";
932
933 $pno = 0;
934 $doprint = 1;
935
936 $page = "";
937
938 while (<>) {
939 next if (/^%%Pages:\s*\d+/);
940
941 if (/^%%Page:/) {
942 if ($pno == 0 || $page =~ /\)S/) {
943 # Header or single page containing text
944 print "%%Page: ? $pno\n" if ($pno > 0);
945 print $page;
946 $pno++;
947 } else {
948 # Empty page, skip it.
949 }
950 $page = "";
951 $doprint = 1;
952 next;
953 }
954
955 # Skip from %%Trailer of one document to Endprolog
956 # %%Page of the next
957 $doprint = 0 if (/^%%Trailer/);
958 $page .= $_ if ($doprint);
959 }
960
961 if ($page =~ /\)S/) {
962 print "%%Page: ? $pno\n";
963 print $page;
964 } else {
965 $pno--;
966 }
967 print "%%Trailer\n%%Pages: $pno\n";
968 EOF
969
970 $PERL /tmp/$$.crmerge.pl < $infile
971 }
972
973
974 #
975 # input_cmd | insert_anchors | output_cmd
976 #
977 # Flag blocks of difference with sequentially numbered invisible
978 # anchors. These are used to drive the frames version of the
979 # sdiffs output.
980 #
981 # NOTE: Anchor zero flags the top of the file irrespective of changes,
982 # an additional anchor is also appended to flag the bottom.
983 #
984 # The script detects changed lines as any line that has a "<span
985 # class=" string embedded (unchanged lines have no class set and are
986 # not part of a <span>. Blank lines (without a sequence number)
987 # are also detected since they flag lines that have been inserted or
988 # deleted.
989 #
990 function insert_anchors
991 {
992 $AWK '
993 function ia() {
994 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
995 }
996
997 BEGIN {
998 anc=1;
999 inblock=1;
1000 printf "<pre>\n";
1001 }
1002 NF == 0 || /^<span class=/ {
1003 if (inblock == 0) {
1004 ia();
1005 inblock=1;
1006 }
1007 print;
1008 next;
1009 }
1010 {
1011 inblock=0;
1012 print;
1013 }
1014 END {
1015 ia();
1016
1017 printf "<b style=\"font-size: large; color: red\">";
1018 printf "--- EOF ---</b>"
1019 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1020 printf "</pre>"
1021 printf "<form name=\"eof\">";
1022 printf "<input name=\"value\" value=\"%d\" " \
1023 "type=\"hidden\"></input>", anc - 1;
1024 printf "</form>";
1025 }
1026 ' $1
1027 }
1028
1029
1030 #
1031 # relative_dir
1032 #
1033 # Print a relative return path from $1 to $2. For example if
1034 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1035 # this function would print "../../../../".
1036 #
1037 # In the event that $1 is not in $2 a warning is printed to stderr,
1038 # and $2 is returned-- the result of this is that the resulting webrev
1039 # is not relocatable.
1040 #
1041 function relative_dir
1042 {
1043 typeset cur="${1##$2?(/)}"
1044
1045 #
1046 # If the first path was specified absolutely, and it does
1047 # not start with the second path, it's an error.
1048 #
1049 if [[ "$cur" = "/${1#/}" ]]; then
1050 # Should never happen.
1051 print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1052 print -u2 "to \"$2\". Check input paths. Framed webrev "
1053 print -u2 "will not be relocatable!"
1054 print $2
1055 return
1056 fi
1057
1058 #
1059 # This is kind of ugly. The sed script will do the following:
1060 #
1061 # 1. Strip off a leading "." or "./": this is important to get
1062 # the correct arcnav links for files in $WDIR.
1063 # 2. Strip off a trailing "/": this is not strictly necessary,
1064 # but is kind of nice, since it doesn't end up in "//" at
1065 # the end of a relative path.
1066 # 3. Replace all remaining sequences of non-"/" with "..": the
1067 # assumption here is that each dirname represents another
1068 # level of relative separation.
1069 # 4. Append a trailing "/" only for non-empty paths: this way
1070 # the caller doesn't need to duplicate this logic, and does
1071 # not end up using $RTOP/file for files in $WDIR.
1072 #
1073 print $cur | $SED -e '{
1074 s:^\./*::
1075 s:/$::
1076 s:[^/][^/]*:..:g
1077 s:^\(..*\)$:\1/:
1078 }'
1079 }
1080
1081 #
1082 # frame_nav_js
1083 #
1084 # Emit javascript for frame navigation
1085 #
1086 function frame_nav_js
1087 {
1088 cat << \EOF
1089 var myInt;
1090 var scrolling=0;
1091 var sfactor = 3;
1092 var scount=10;
1093
1094 function scrollByPix() {
1095 if (scount<=0) {
1096 sfactor*=1.2;
1097 scount=10;
1098 }
1099 parent.lhs.scrollBy(0,sfactor);
1100 parent.rhs.scrollBy(0,sfactor);
1101 scount--;
1102 }
1103
1104 function scrollToAnc(num) {
1105
1106 // Update the value of the anchor in the form which we use as
1107 // storage for this value. setAncValue() will take care of
1108 // correcting for overflow and underflow of the value and return
1109 // us the new value.
1110 num = setAncValue(num);
1111
1112 // Set location and scroll back a little to expose previous
1113 // lines.
1114 //
1115 // Note that this could be improved: it is possible although
1116 // complex to compute the x and y position of an anchor, and to
1117 // scroll to that location directly.
1118 //
1119 parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1120 parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1121
1122 parent.lhs.scrollBy(0,-30);
1123 parent.rhs.scrollBy(0,-30);
1124 }
1125
1126 function getAncValue()
1127 {
1128 return (parseInt(parent.nav.document.diff.real.value));
1129 }
1130
1131 function setAncValue(val)
1132 {
1133 if (val <= 0) {
1134 val = 0;
1135 parent.nav.document.diff.real.value = val;
1136 parent.nav.document.diff.display.value = "BOF";
1137 return (val);
1138 }
1139
1140 //
1141 // The way we compute the max anchor value is to stash it
1142 // inline in the left and right hand side pages-- it's the same
1143 // on each side, so we pluck from the left.
1144 //
1145 maxval = parent.lhs.document.eof.value.value;
1146 if (val < maxval) {
1147 parent.nav.document.diff.real.value = val;
1148 parent.nav.document.diff.display.value = val.toString();
1149 return (val);
1150 }
1151
1152 // this must be: val >= maxval
1153 val = maxval;
1154 parent.nav.document.diff.real.value = val;
1155 parent.nav.document.diff.display.value = "EOF";
1156 return (val);
1157 }
1158
1159 function stopScroll() {
1160 if (scrolling==1) {
1161 clearInterval(myInt);
1162 scrolling=0;
1163 }
1164 }
1165
1166 function startScroll() {
1167 stopScroll();
1168 scrolling=1;
1169 myInt=setInterval("scrollByPix()",10);
1170 }
1171
1172 function handlePress(b) {
1173
1174 switch (b) {
1175 case 1 :
1176 scrollToAnc(-1);
1177 break;
1178 case 2 :
1179 scrollToAnc(getAncValue() - 1);
1180 break;
1181 case 3 :
1182 sfactor=-3;
1183 startScroll();
1184 break;
1185 case 4 :
1186 sfactor=3;
1187 startScroll();
1188 break;
1189 case 5 :
1190 scrollToAnc(getAncValue() + 1);
1191 break;
1192 case 6 :
1193 scrollToAnc(999999);
1194 break;
1195 }
1196 }
1197
1198 function handleRelease(b) {
1199 stopScroll();
1200 }
1201
1202 function keypress(ev) {
1203 var keynum;
1204 var keychar;
1205
1206 if (window.event) { // IE
1207 keynum = ev.keyCode;
1208 } else if (ev.which) { // non-IE
1209 keynum = ev.which;
1210 }
1211
1212 keychar = String.fromCharCode(keynum);
1213
1214 if (keychar == "k") {
1215 handlePress(2);
1216 return (0);
1217 } else if (keychar == "j" || keychar == " ") {
1218 handlePress(5);
1219 return (0);
1220 }
1221 return (1);
1222 }
1223
1224 function ValidateDiffNum(){
1225 val = parent.nav.document.diff.display.value;
1226 if (val == "EOF") {
1227 scrollToAnc(999999);
1228 return;
1229 }
1230
1231 if (val == "BOF") {
1232 scrollToAnc(0);
1233 return;
1234 }
1235
1236 i=parseInt(val);
1237 if (isNaN(i)) {
1238 parent.nav.document.diff.display.value = getAncValue();
1239 } else {
1240 scrollToAnc(i);
1241 }
1242 return false;
1243 }
1244
1245 EOF
1246 }
1247
1248 #
1249 # frame_navigation
1250 #
1251 # Output anchor navigation file for framed sdiffs.
1252 #
1253 function frame_navigation
1254 {
1255 print "$HTML<head>$STDHEAD"
1256
1257 cat << \EOF
1258 <title>Anchor Navigation</title>
1259 <meta http-equiv="Content-Script-Type" content="text/javascript">
1260 <meta http-equiv="Content-Type" content="text/html">
1261
1262 <style type="text/css">
1263 div.button td { padding-left: 5px; padding-right: 5px;
1264 background-color: #eee; text-align: center;
1265 border: 1px #444 outset; cursor: pointer; }
1266 div.button a { font-weight: bold; color: black }
1267 div.button td:hover { background: #ffcc99; }
1268 </style>
1269 EOF
1270
1271 print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1272
1273 cat << \EOF
1274 </head>
1275 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1276 onkeypress="keypress(event);">
1277 <noscript lang="javascript">
1278 <center>
1279 <p><big>Framed Navigation controls require Javascript</big><br></br>
1280 Either this browser is incompatable or javascript is not enabled</p>
1281 </center>
1282 </noscript>
1283 <table width="100%" border="0" align="center">
1284 <tr>
1285 <td valign="middle" width="25%">Diff navigation:
1286 Use 'j' and 'k' for next and previous diffs; or use buttons
1287 at right</td>
1288 <td align="center" valign="top" width="50%">
1289 <div class="button">
1290 <table border="0" align="center">
1291 <tr>
1292 <td>
1293 <a onMouseDown="handlePress(1);return true;"
1294 onMouseUp="handleRelease(1);return true;"
1295 onMouseOut="handleRelease(1);return true;"
1296 onClick="return false;"
1297 title="Go to Beginning Of file">BOF</a></td>
1298 <td>
1299 <a onMouseDown="handlePress(3);return true;"
1300 onMouseUp="handleRelease(3);return true;"
1301 onMouseOut="handleRelease(3);return true;"
1302 title="Scroll Up: Press and Hold to accelerate"
1303 onClick="return false;">Scroll Up</a></td>
1304 <td>
1305 <a onMouseDown="handlePress(2);return true;"
1306 onMouseUp="handleRelease(2);return true;"
1307 onMouseOut="handleRelease(2);return true;"
1308 title="Go to previous Diff"
1309 onClick="return false;">Prev Diff</a>
1310 </td></tr>
1311
1312 <tr>
1313 <td>
1314 <a onMouseDown="handlePress(6);return true;"
1315 onMouseUp="handleRelease(6);return true;"
1316 onMouseOut="handleRelease(6);return true;"
1317 onClick="return false;"
1318 title="Go to End Of File">EOF</a></td>
1319 <td>
1320 <a onMouseDown="handlePress(4);return true;"
1321 onMouseUp="handleRelease(4);return true;"
1322 onMouseOut="handleRelease(4);return true;"
1323 title="Scroll Down: Press and Hold to accelerate"
1324 onClick="return false;">Scroll Down</a></td>
1325 <td>
1326 <a onMouseDown="handlePress(5);return true;"
1327 onMouseUp="handleRelease(5);return true;"
1328 onMouseOut="handleRelease(5);return true;"
1329 title="Go to next Diff"
1330 onClick="return false;">Next Diff</a></td>
1331 </tr>
1332 </table>
1333 </div>
1334 </td>
1335 <th valign="middle" width="25%">
1336 <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1337 <input name="display" value="BOF" size="8" type="text"></input>
1338 <input name="real" value="0" size="8" type="hidden"></input>
1339 </form>
1340 </th>
1341 </tr>
1342 </table>
1343 </body>
1344 </html>
1345 EOF
1346 }
1347
1348
1349
1350 #
1351 # diff_to_html <filename> <filepath> { U | C } <comment>
1352 #
1353 # Processes the output of diff to produce an HTML file representing either
1354 # context or unified diffs.
1355 #
1356 diff_to_html()
1357 {
1358 TNAME=$1
1359 TPATH=$2
1360 DIFFTYPE=$3
1361 COMMENT=$4
1362
1363 print "$HTML<head>$STDHEAD"
1364 print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1365
1366 if [[ $DIFFTYPE == "U" ]]; then
1367 print "$UDIFFCSS"
1368 fi
1369
1370 cat <<-EOF
1371 </head>
1372 <body id="SUNWwebrev">
1373 <a class="print" href="javascript:print()">Print this page</a>
1374 <pre>$COMMENT</pre>
1375 <pre>
1376 EOF
1377
1378 html_quote | $AWK '
1379 /^--- new/ { next }
1380 /^\+\+\+ new/ { next }
1381 /^--- old/ { next }
1382 /^\*\*\* old/ { next }
1383 /^\*\*\*\*/ { next }
1384 /^-------/ { printf "<center><h1>%s</h1></center>\n", $0; next }
1385 /^\@\@.*\@\@$/ { printf "</pre><hr></hr><pre>\n";
1386 printf "<span class=\"newmarker\">%s</span>\n", $0;
1387 next}
1388
1389 /^\*\*\*/ { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1390 next}
1391 /^---/ { printf "<span class=\"newmarker\">%s</span>\n", $0;
1392 next}
1393 /^\+/ {printf "<span class=\"new\">%s</span>\n", $0; next}
1394 /^!/ {printf "<span class=\"changed\">%s</span>\n", $0; next}
1395 /^-/ {printf "<span class=\"removed\">%s</span>\n", $0; next}
1396 {printf "%s\n", $0; next}
1397 '
1398
1399 print "</pre></body></html>\n"
1400 }
1401
1402
1403 #
1404 # source_to_html { new | old } <filename>
1405 #
1406 # Process a plain vanilla source file to transform it into an HTML file.
1407 #
1408 source_to_html()
1409 {
1410 WHICH=$1
1411 TNAME=$2
1412
1413 print "$HTML<head>$STDHEAD"
1414 print "<title>$WNAME $WHICH $TNAME</title>"
1415 print "<body id=\"SUNWwebrev\">"
1416 print "<pre>"
1417 html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1418 print "</pre></body></html>"
1419 }
1420
1421 #
1422 # comments_from_teamware {text|html} parent-file child-file
1423 #
1424 # Find the first delta in the child that's not in the parent. Get the
1425 # newest delta from the parent, get all deltas from the child starting
1426 # with that delta, and then get all info starting with the second oldest
1427 # delta in that list (the first delta unique to the child).
1428 #
1429 # This code adapted from Bill Shannon's "spc" script
1430 #
1431 comments_from_teamware()
1432 {
1433 fmt=$1
1434 pfile=$PWS/$2
1435 cfile=$CWS/$3
1436
1437 if [[ ! -f $PWS/${2%/*}/SCCS/s.${2##*/} && -n $RWS ]]; then
1438 pfile=$RWS/$2
1439 fi
1440
1441 if [[ -f $pfile ]]; then
1442 psid=$($SCCS prs -d:I: $pfile 2>/dev/null)
1443 else
1444 psid=1.1
1445 fi
1446
1447 set -A sids $($SCCS prs -l -r$psid -d:I: $cfile 2>/dev/null)
1448 N=${#sids[@]}
1449
1450 nawkprg='
1451 /^COMMENTS:/ {p=1; continue}
1452 /^D [0-9]+\.[0-9]+/ {printf "--- %s ---\n", $2; p=0; }
1453 NF == 0u { continue }
1454 {if (p==0) continue; print $0 }'
1455
1456 if [[ $N -ge 2 ]]; then
1457 sid1=${sids[$((N-2))]} # Gets 2nd to last sid
1458
1459 if [[ $fmt == "text" ]]; then
1460 $SCCS prs -l -r$sid1 $cfile 2>/dev/null | \
1461 $AWK "$nawkprg"
1462 return
1463 fi
1464
1465 $SCCS prs -l -r$sid1 $cfile 2>/dev/null | \
1466 html_quote | its2url | $AWK "$nawkprg"
1467 fi
1468 }
1469
1470 #
1471 # comments_from_wx {text|html} filepath
1472 #
1473 # Given the pathname of a file, find its location in a "wx" active
1474 # file list and print the following comment. Output is either text or
1475 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1476 # are turned into URLs.
1477 #
1478 # This is also used with Mercurial and the file list provided by hg-active.
1479 #
1480 comments_from_wx()
1481 {
1482 typeset fmt=$1
1483 typeset p=$2
1484
1485 comm=`$AWK '
1486 $1 == "'$p'" {
1487 do getline ; while (NF > 0)
1488 getline
1489 while (NF > 0) { print ; getline }
1490 exit
1491 }' < $wxfile`
1492
1493 if [[ -z $comm ]]; then
1494 comm="*** NO COMMENTS ***"
1495 fi
1496
1497 if [[ $fmt == "text" ]]; then
1498 print -- "$comm"
1499 return
1500 fi
1501
1502 print -- "$comm" | html_quote | its2url
1503
1504 }
1505
1506 #
1507 # getcomments {text|html} filepath parentpath
1508 #
1509 # Fetch the comments depending on what SCM mode we're in.
1510 #
1511 getcomments()
1512 {
1513 typeset fmt=$1
1514 typeset p=$2
1515 typeset pp=$3
1516
1517 if [[ -n $Nflag ]]; then
1518 return
1519 fi
1520 #
1521 # Mercurial support uses a file list in wx format, so this
1522 # will be used there, too
1523 #
1524 if [[ -n $wxfile ]]; then
1525 comments_from_wx $fmt $p
1526 else
1527 if [[ $SCM_MODE == "teamware" ]]; then
1528 comments_from_teamware $fmt $pp $p
1529 fi
1530 fi
1531 }
1532
1533 #
1534 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1535 #
1536 # Print out Code Inspection figures similar to sccs-prt(1) format.
1537 #
1538 function printCI
1539 {
1540 integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1541 typeset str
1542 if (( tot == 1 )); then
1543 str="line"
1544 else
1545 str="lines"
1546 fi
1547 printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1548 $tot $str $ins $del $mod $unc
1549 }
1550
1551
1552 #
1553 # difflines <oldfile> <newfile>
1554 #
1555 # Calculate and emit number of added, removed, modified and unchanged lines,
1556 # and total lines changed, the sum of added + removed + modified.
1557 #
1558 function difflines
1559 {
1560 integer tot mod del ins unc err
1561 typeset filename
1562
1563 eval $( diff -e $1 $2 | $AWK '
1564 # Change range of lines: N,Nc
1565 /^[0-9]*,[0-9]*c$/ {
1566 n=split(substr($1,1,length($1)-1), counts, ",");
1567 if (n != 2) {
1568 error=2
1569 exit;
1570 }
1571 #
1572 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1573 # following would be 5 - 3 = 2! Hence +1 for correction.
1574 #
1575 r=(counts[2]-counts[1])+1;
1576
1577 #
1578 # Now count replacement lines: each represents a change instead
1579 # of a delete, so increment c and decrement r.
1580 #
1581 while (getline != /^\.$/) {
1582 c++;
1583 r--;
1584 }
1585 #
1586 # If there were more replacement lines than original lines,
1587 # then r will be negative; in this case there are no deletions,
1588 # but there are r changes that should be counted as adds, and
1589 # since r is negative, subtract it from a and add it to c.
1590 #
1591 if (r < 0) {
1592 a-=r;
1593 c+=r;
1594 }
1595
1596 #
1597 # If there were more original lines than replacement lines, then
1598 # r will be positive; in this case, increment d by that much.
1599 #
1600 if (r > 0) {
1601 d+=r;
1602 }
1603 next;
1604 }
1605
1606 # Change lines: Nc
1607 /^[0-9].*c$/ {
1608 # The first line is a replacement; any more are additions.
1609 if (getline != /^\.$/) {
1610 c++;
1611 while (getline != /^\.$/) a++;
1612 }
1613 next;
1614 }
1615
1616 # Add lines: both Na and N,Na
1617 /^[0-9].*a$/ {
1618 while (getline != /^\.$/) a++;
1619 next;
1620 }
1621
1622 # Delete range of lines: N,Nd
1623 /^[0-9]*,[0-9]*d$/ {
1624 n=split(substr($1,1,length($1)-1), counts, ",");
1625 if (n != 2) {
1626 error=2
1627 exit;
1628 }
1629 #
1630 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1631 # following would be 5 - 3 = 2! Hence +1 for correction.
1632 #
1633 r=(counts[2]-counts[1])+1;
1634 d+=r;
1635 next;
1636 }
1637
1638 # Delete line: Nd. For example 10d says line 10 is deleted.
1639 /^[0-9]*d$/ {d++; next}
1640
1641 # Should not get here!
1642 {
1643 error=1;
1644 exit;
1645 }
1646
1647 # Finish off - print results
1648 END {
1649 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1650 (c+d+a), c, d, a, error);
1651 }' )
1652
1653 # End of $AWK, Check to see if any trouble occurred.
1654 if (( $? > 0 || err > 0 )); then
1655 print "Unexpected Error occurred reading" \
1656 "\`diff -e $1 $2\`: \$?=$?, err=" $err
1657 return
1658 fi
1659
1660 # Accumulate totals
1661 (( TOTL += tot ))
1662 (( TMOD += mod ))
1663 (( TDEL += del ))
1664 (( TINS += ins ))
1665 # Calculate unchanged lines
1666 unc=`wc -l < $1`
1667 if (( unc > 0 )); then
1668 (( unc -= del + mod ))
1669 (( TUNC += unc ))
1670 fi
1671 # print summary
1672 print "<span class=\"lineschanged\">"
1673 printCI $tot $ins $del $mod $unc
1674 print "</span>"
1675 }
1676
1677
1678 #
1679 # flist_from_wx
1680 #
1681 # Sets up webrev to source its information from a wx-formatted file.
1682 # Sets the global 'wxfile' variable.
1683 #
1684 function flist_from_wx
1685 {
1686 typeset argfile=$1
1687 if [[ -n ${argfile%%/*} ]]; then
1688 #
1689 # If the wx file pathname is relative then make it absolute
1690 # because the webrev does a "cd" later on.
1691 #
1692 wxfile=$PWD/$argfile
1693 else
1694 wxfile=$argfile
1695 fi
1696
1697 $AWK '{ c = 1; print;
1698 while (getline) {
1699 if (NF == 0) { c = -c; continue }
1700 if (c > 0) print
1701 }
1702 }' $wxfile > $FLIST
1703
1704 print " Done."
1705 }
1706
1707 #
1708 # flist_from_teamware [ <args-to-putback-n> ]
1709 #
1710 # Generate the file list by extracting file names from a putback -n. Some
1711 # names may come from the "update/create" messages and others from the
1712 # "currently checked out" warning. Renames are detected here too. Extract
1713 # values for CODEMGR_WS and CODEMGR_PARENT from the output of the putback
1714 # -n as well, but remove them if they are already defined.
1715 #
1716 function flist_from_teamware
1717 {
1718 if [[ -n $codemgr_parent && -z $parent_webrev ]]; then
1719 if [[ ! -d $codemgr_parent/Codemgr_wsdata ]]; then
1720 print -u2 "parent $codemgr_parent doesn't look like a" \
1721 "valid teamware workspace"
1722 exit 1
1723 fi
1724 parent_args="-p $codemgr_parent"
1725 fi
1726
1727 print " File list from: 'putback -n $parent_args $*' ... \c"
1728
1729 putback -n $parent_args $* 2>&1 |
1730 $AWK '
1731 /^update:|^create:/ {print $2}
1732 /^Parent workspace:/ {printf("CODEMGR_PARENT=%s\n",$3)}
1733 /^Child workspace:/ {printf("CODEMGR_WS=%s\n",$3)}
1734 /^The following files are currently checked out/ {p = 1; continue}
1735 NF == 0 {p=0 ; continue}
1736 /^rename/ {old=$3}
1737 $1 == "to:" {print $2, old}
1738 /^"/ {continue}
1739 p == 1 {print $1}' |
1740 sort -r -k 1,1 -u | sort > $FLIST
1741
1742 print " Done."
1743 }
1744
1745 #
1746 # Call hg-active to get the active list output in the wx active list format
1747 #
1748 function hg_active_wxfile
1749 {
1750 typeset child=$1
1751 typeset parent=$2
1752
1753 TMPFLIST=/tmp/$$.active
1754 $HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1755 wxfile=$TMPFLIST
1756 }
1757
1758 #
1759 # flist_from_mercurial
1760 # Call hg-active to get a wx-style active list, and hand it off to
1761 # flist_from_wx
1762 #
1763 function flist_from_mercurial
1764 {
1765 typeset child=$1
1766 typeset parent=$2
1767
1768 print " File list from: hg-active -p $parent ...\c"
1769
1770 if [[ ! -x $HG_ACTIVE ]]; then
1771 print # Blank line for the \c above
1772 print -u2 "Error: hg-active tool not found. Exiting"
1773 exit 1
1774 fi
1775 hg_active_wxfile $child $parent
1776
1777 # flist_from_wx prints the Done, so we don't have to.
1778 flist_from_wx $TMPFLIST
1779 }
1780
1781 #
1782 # Call git-active to get the active list output in the wx active list format
1783 #
1784 function git_active_wxfile
1785 {
1786 typeset child=$1
1787 typeset parent=$2
1788
1789 TMPFLIST=/tmp/$$.active
1790 $GIT_ACTIVE -w $child -p $parent -o $TMPFLIST
1791 wxfile=$TMPFLIST
1792 }
1793
1794 #
1795 # flist_from_git
1796 # Call git-active to get a wx-style active list, and hand it off to
1797 # flist_from_wx
1798 #
1799 function flist_from_git
1800 {
1801 typeset child=$1
1802 typeset parent=$2
1803
1804 print " File list from: git-active -p $parent ...\c"
1805
1806 if [[ ! -x $GIT_ACTIVE ]]; then
1807 print # Blank line for the \c above
1808 print -u2 "Error: git-active tool not found. Exiting"
1809 exit 1
1810 fi
1811 git_active_wxfile $child $parent
1812
1813 # flist_from_wx prints the Done, so we don't have to.
1814 flist_from_wx $TMPFLIST
1815 }
1816
1817 #
1818 # flist_from_subversion
1819 #
1820 # Generate the file list by extracting file names from svn status.
1821 #
1822 function flist_from_subversion
1823 {
1824 CWS=$1
1825 OLDPWD=$2
1826
1827 cd $CWS
1828 print -u2 " File list from: svn status ... \c"
1829 svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1830 print -u2 " Done."
1831 cd $OLDPWD
1832 }
1833
1834 function env_from_flist
1835 {
1836 [[ -r $FLIST ]] || return
1837
1838 #
1839 # Use "eval" to set env variables that are listed in the file
1840 # list. Then copy those into our local versions of those
1841 # variables if they have not been set already.
1842 #
1843 eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1844
1845 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1846 codemgr_ws=$CODEMGR_WS
1847 export CODEMGR_WS
1848 fi
1849
1850 #
1851 # Check to see if CODEMGR_PARENT is set in the flist file.
1852 #
1853 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1854 codemgr_parent=$CODEMGR_PARENT
1855 export CODEMGR_PARENT
1856 fi
1857 }
1858
1859 function look_for_prog
1860 {
1861 typeset path
1862 typeset ppath
1863 typeset progname=$1
1864
1865 ppath=$PATH
1866 ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1867 ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin
1868 ppath=$ppath:/opt/onbld/bin/`uname -p`
1869
1870 PATH=$ppath prog=`whence $progname`
1871 if [[ -n $prog ]]; then
1872 print $prog
1873 fi
1874 }
1875
1876 function get_file_mode
1877 {
1878 $PERL -e '
1879 if (@stat = stat($ARGV[0])) {
1880 $mode = $stat[2] & 0777;
1881 printf "%03o\n", $mode;
1882 exit 0;
1883 } else {
1884 exit 1;
1885 }
1886 ' $1
1887 }
1888
1889 function build_old_new_teamware
1890 {
1891 typeset olddir="$1"
1892 typeset newdir="$2"
1893
1894 # If the child's version doesn't exist then
1895 # get a readonly copy.
1896
1897 if [[ ! -f $CWS/$DIR/$F && -f $CWS/$DIR/SCCS/s.$F ]]; then
1898 $SCCS get -s -p $CWS/$DIR/$F > $CWS/$DIR/$F
1899 fi
1900
1901 # The following two sections propagate file permissions the
1902 # same way SCCS does. If the file is already under version
1903 # control, always use permissions from the SCCS/s.file. If
1904 # the file is not under SCCS control, use permissions from the
1905 # working copy. In all cases, the file copied to the webrev
1906 # is set to read only, and group/other permissions are set to
1907 # match those of the file owner. This way, even if the file
1908 # is currently checked out, the webrev will display the final
1909 # permissions that would result after check in.
1910
1911 #
1912 # Snag new version of file.
1913 #
1914 rm -f $newdir/$DIR/$F
1915 cp $CWS/$DIR/$F $newdir/$DIR/$F
1916 if [[ -f $CWS/$DIR/SCCS/s.$F ]]; then
1917 chmod `get_file_mode $CWS/$DIR/SCCS/s.$F` \
1918 $newdir/$DIR/$F
1919 fi
1920 chmod u-w,go=u $newdir/$DIR/$F
1921
1922 #
1923 # Get the parent's version of the file. First see whether the
1924 # child's version is checked out and get the parent's version
1925 # with keywords expanded or unexpanded as appropriate.
1926 #
1927 if [[ -f $PWS/$PDIR/$PF && ! -f $PWS/$PDIR/SCCS/s.$PF && \
1928 ! -f $PWS/$PDIR/SCCS/p.$PF ]]; then
1929 # Parent is not a real workspace, but just a raw
1930 # directory tree - use the file that's there as
1931 # the old file.
1932
1933 rm -f $olddir/$PDIR/$PF
1934 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1935 else
1936 if [[ -f $PWS/$PDIR/SCCS/s.$PF ]]; then
1937 real_parent=$PWS
1938 else
1939 real_parent=$RWS
1940 fi
1941
1942 rm -f $olddir/$PDIR/$PF
1943
1944 if [[ -f $real_parent/$PDIR/$PF ]]; then
1945 if [ -f $CWS/$DIR/SCCS/p.$F ]; then
1946 $SCCS get -s -p -k $real_parent/$PDIR/$PF > \
1947 $olddir/$PDIR/$PF
1948 else
1949 $SCCS get -s -p $real_parent/$PDIR/$PF > \
1950 $olddir/$PDIR/$PF
1951 fi
1952 chmod `get_file_mode $real_parent/$PDIR/SCCS/s.$PF` \
1953 $olddir/$PDIR/$PF
1954 fi
1955 fi
1956 if [[ -f $olddir/$PDIR/$PF ]]; then
1957 chmod u-w,go=u $olddir/$PDIR/$PF
1958 fi
1959 }
1960
1961 function build_old_new_mercurial
1962 {
1963 typeset olddir="$1"
1964 typeset newdir="$2"
1965 typeset old_mode=
1966 typeset new_mode=
1967 typeset file
1968
1969 #
1970 # Get old file mode, from the parent revision manifest entry.
1971 # Mercurial only stores a "file is executable" flag, but the
1972 # manifest will display an octal mode "644" or "755".
1973 #
1974 if [[ "$PDIR" == "." ]]; then
1975 file="$PF"
1976 else
1977 file="$PDIR/$PF"
1978 fi
1979 file=`echo $file | $SED 's#/#\\\/#g'`
1980 # match the exact filename, and return only the permission digits
1981 old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
1982 < $HG_PARENT_MANIFEST`
1983
1984 #
1985 # Get new file mode, directly from the filesystem.
1986 # Normalize the mode to match Mercurial's behavior.
1987 #
1988 new_mode=`get_file_mode $CWS/$DIR/$F`
1989 if [[ -n "$new_mode" ]]; then
1990 if [[ "$new_mode" = *[1357]* ]]; then
1991 new_mode=755
1992 else
1993 new_mode=644
1994 fi
1995 fi
1996
1997 #
1998 # new version of the file.
1999 #
2000 rm -rf $newdir/$DIR/$F
2001 if [[ -e $CWS/$DIR/$F ]]; then
2002 cp $CWS/$DIR/$F $newdir/$DIR/$F
2003 if [[ -n $new_mode ]]; then
2004 chmod $new_mode $newdir/$DIR/$F
2005 else
2006 # should never happen
2007 print -u2 "ERROR: set mode of $newdir/$DIR/$F"
2008 fi
2009 fi
2010
2011 #
2012 # parent's version of the file
2013 #
2014 # Note that we get this from the last version common to both
2015 # ourselves and the parent. References are via $CWS since we have no
2016 # guarantee that the parent workspace is reachable via the filesystem.
2017 #
2018 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2019 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2020 elif [[ -n $HG_PARENT ]]; then
2021 hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
2022 $olddir/$PDIR/$PF 2>/dev/null
2023
2024 if (( $? != 0 )); then
2025 rm -f $olddir/$PDIR/$PF
2026 else
2027 if [[ -n $old_mode ]]; then
2028 chmod $old_mode $olddir/$PDIR/$PF
2029 else
2030 # should never happen
2031 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
2032 fi
2033 fi
2034 fi
2035 }
2036
2037 function build_old_new_git
2038 {
2039 typeset olddir="$1"
2040 typeset newdir="$2"
2041 typeset old_mode=
2042 typeset new_mode=
2043 typeset old_object=
2044 typeset new_object=
2045 typeset file
2046 typeset type
2047
2048 #
2049 # Get old file and its mode from the git object tree
2050 #
2051 if [[ "$PDIR" == "." ]]; then
2052 file="$PF"
2053 else
2054 file="$PDIR/$PF"
2055 fi
2056 git ls-tree $GIT_PARENT $file | read old_mode type old_object junk
2057 git cat-file $type $old_object > $olddir/$file 2>/dev/null
2058
2059 if (( $? != 0 )); then
2060 rm -f $olddir/$PDIR/$PF
2061 else
2062 if [[ -n $old_mode ]]; then
2063 chmod $old_mode $olddir/$PDIR/$PF
2064 else
2065 # should never happen
2066 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
2067 fi
2068 fi
2069
2070 #
2071 # new version of the file.
2072 #
2073 rm -rf $newdir/$DIR/$F
2074 if [[ -e $CWS/../$DIR/$F ]]; then
2075 cp $CWS/../$DIR/$F $newdir/$DIR/$F
2076 # Temporary new_node = old_mode
2077 new_mode=$old_mode
2078 if [[ -n $new_mode ]]; then
2079 chmod $new_mode $newdir/$DIR/$F
2080 else
2081 # should never happen
2082 print -u2 "ERROR: set mode of $newdir/$DIR/$F"
2083 fi
2084 fi
2085
2086 }
2087
2088 function build_old_new_subversion
2089 {
2090 typeset olddir="$1"
2091 typeset newdir="$2"
2092
2093 # Snag new version of file.
2094 rm -f $newdir/$DIR/$F
2095 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2096
2097 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2098 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2099 else
2100 # Get the parent's version of the file.
2101 svn status $CWS/$DIR/$F | read stat file
2102 if [[ $stat != "A" ]]; then
2103 svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2104 fi
2105 fi
2106 }
2107
2108 function build_old_new_unknown
2109 {
2110 typeset olddir="$1"
2111 typeset newdir="$2"
2112
2113 #
2114 # Snag new version of file.
2115 #
2116 rm -f $newdir/$DIR/$F
2117 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2118
2119 #
2120 # Snag the parent's version of the file.
2121 #
2122 if [[ -f $PWS/$PDIR/$PF ]]; then
2123 rm -f $olddir/$PDIR/$PF
2124 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2125 fi
2126 }
2127
2128 function build_old_new
2129 {
2130 typeset WDIR=$1
2131 typeset PWS=$2
2132 typeset PDIR=$3
2133 typeset PF=$4
2134 typeset CWS=$5
2135 typeset DIR=$6
2136 typeset F=$7
2137
2138 typeset olddir="$WDIR/raw_files/old"
2139 typeset newdir="$WDIR/raw_files/new"
2140
2141 mkdir -p $olddir/$PDIR
2142 mkdir -p $newdir/$DIR
2143
2144 if [[ $SCM_MODE == "teamware" ]]; then
2145 build_old_new_teamware "$olddir" "$newdir"
2146 elif [[ $SCM_MODE == "mercurial" ]]; then
2147 build_old_new_mercurial "$olddir" "$newdir"
2148 elif [[ $SCM_MODE == "git" ]]; then
2149 build_old_new_git "$olddir" "$newdir"
2150 elif [[ $SCM_MODE == "subversion" ]]; then
2151 build_old_new_subversion "$olddir" "$newdir"
2152 elif [[ $SCM_MODE == "unknown" ]]; then
2153 build_old_new_unknown "$olddir" "$newdir"
2154 fi
2155
2156 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2157 print "*** Error: file not in parent or child"
2158 return 1
2159 fi
2160 return 0
2161 }
2162
2163
2164 #
2165 # Usage message.
2166 #
2167 function usage
2168 {
2169 print 'Usage:\twebrev [common-options]
2170 webrev [common-options] ( <file> | - )
2171 webrev [common-options] -w <wx file>
2172
2173 Options:
2174 -C <filename>: Use <filename> for the information tracking configuration.
2175 -D: delete remote webrev
2176 -i <filename>: Include <filename> in the index.html file.
2177 -I <filename>: Use <filename> for the information tracking registry.
2178 -n: do not generate the webrev (useful with -U)
2179 -O: Print bugids/arc cases suitable for OpenSolaris.
2180 -o <outdir>: Output webrev to specified directory.
2181 -p <compare-against>: Use specified parent wkspc or basis for comparison
2182 -t <remote_target>: Specify remote destination for webrev upload
2183 -U: upload the webrev to remote destination
2184 -w <wxfile>: Use specified wx active file.
2185
2186 Environment:
2187 WDIR: Control the output directory.
2188 WEBREV_TRASH_DIR: Set directory for webrev delete.
2189
2190 SCM Specific Options:
2191 TeamWare: webrev [common-options] -l [arguments to 'putback']
2192
2193 SCM Environment:
2194 CODEMGR_WS: Workspace location.
2195 CODEMGR_PARENT: Parent workspace location.
2196 '
2197
2198 exit 2
2199 }
2200
2201 #
2202 #
2203 # Main program starts here
2204 #
2205 #
2206
2207 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2208
2209 set +o noclobber
2210
2211 PATH=$(dirname $(whence $0)):$PATH
2212
2213 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2214 [[ -z $WX ]] && WX=`look_for_prog wx`
2215 [[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
2216 [[ -z $GIT_ACTIVE ]] && GIT_ACTIVE=`look_for_prog git-active`
2217 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2218 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2219 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2220 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2221 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2222 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2223 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2224 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2225 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2226 [[ -z $SCP ]] && SCP=`look_for_prog scp`
2227 [[ -z $SED ]] && SED=`look_for_prog sed`
2228 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2229 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2230 [[ -z $GREP ]] && GREP=`look_for_prog grep`
2231 [[ -z $FIND ]] && FIND=`look_for_prog find`
2232
2233 # set name of trash directory for remote webrev deletion
2234 TRASH_DIR=".trash"
2235 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2236
2237 if [[ ! -x $PERL ]]; then
2238 print -u2 "Error: No perl interpreter found. Exiting."
2239 exit 1
2240 fi
2241
2242 if [[ ! -x $WHICH_SCM ]]; then
2243 print -u2 "Error: Could not find which_scm. Exiting."
2244 exit 1
2245 fi
2246
2247 #
2248 # These aren't fatal, but we want to note them to the user.
2249 # We don't warn on the absence of 'wx' until later when we've
2250 # determined that we actually need to try to invoke it.
2251 #
2252 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2253 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2254 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2255
2256 # Declare global total counters.
2257 integer TOTL TINS TDEL TMOD TUNC
2258
2259 # default remote host for upload/delete
2260 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2261 # prefixes for upload targets
2262 typeset -r rsync_prefix="rsync://"
2263 typeset -r ssh_prefix="ssh://"
2264
2265 Cflag=
2266 Dflag=
2267 flist_mode=
2268 flist_file=
2269 iflag=
2270 Iflag=
2271 lflag=
2272 Nflag=
2273 nflag=
2274 Oflag=
2275 oflag=
2276 pflag=
2277 tflag=
2278 uflag=
2279 Uflag=
2280 wflag=
2281 remote_target=
2282
2283 #
2284 # NOTE: when adding/removing options it is necessary to sync the list
2285 # with usr/src/tools/onbld/hgext/cdm.py
2286 #
2287 while getopts "C:Di:I:lnNo:Op:t:Uw" opt
2288 do
2289 case $opt in
2290 C) Cflag=1
2291 ITSCONF=$OPTARG;;
2292
2293 D) Dflag=1;;
2294
2295 i) iflag=1
2296 INCLUDE_FILE=$OPTARG;;
2297
2298 I) Iflag=1
2299 ITSREG=$OPTARG;;
2300
2301 #
2302 # If -l has been specified, we need to abort further options
2303 # processing, because subsequent arguments are going to be
2304 # arguments to 'putback -n'.
2305 #
2306 l) lflag=1
2307 break;;
2308
2309 N) Nflag=1;;
2310
2311 n) nflag=1;;
2312
2313 O) Oflag=1;;
2314
2315 o) oflag=1
2316 WDIR=$OPTARG;;
2317
2318 p) pflag=1
2319 codemgr_parent=$OPTARG;;
2320
2321 t) tflag=1
2322 remote_target=$OPTARG;;
2323
2324 U) Uflag=1;;
2325
2326 w) wflag=1;;
2327
2328 ?) usage;;
2329 esac
2330 done
2331
2332 FLIST=/tmp/$$.flist
2333
2334 if [[ -n $wflag && -n $lflag ]]; then
2335 usage
2336 fi
2337
2338 # more sanity checking
2339 if [[ -n $nflag && -z $Uflag ]]; then
2340 print "it does not make sense to skip webrev generation" \
2341 "without -U"
2342 exit 1
2343 fi
2344
2345 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2346 echo "remote target has to be used only for upload or delete"
2347 exit 1
2348 fi
2349
2350 #
2351 # For the invocation "webrev -n -U" with no other options, webrev will assume
2352 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2353 # $(basename ${CWS}). So we need to get CWS set before we skip any remaining
2354 # logic.
2355 #
2356 $WHICH_SCM | read SCM_MODE junk || exit 1
2357 if [[ $SCM_MODE == "teamware" ]]; then
2358 #
2359 # Teamware priorities:
2360 # 1. CODEMGR_WS from the environment
2361 # 2. workspace name
2362 #
2363 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
2364 if [[ -n $codemgr_ws && ! -d $codemgr_ws ]]; then
2365 print -u2 "$codemgr_ws: no such workspace"
2366 exit 1
2367 fi
2368 [[ -z $codemgr_ws ]] && codemgr_ws=$(workspace name)
2369 codemgr_ws=$(cd $codemgr_ws;print $PWD)
2370 CODEMGR_WS=$codemgr_ws
2371 CWS=$codemgr_ws
2372 elif [[ $SCM_MODE == "mercurial" ]]; then
2373 #
2374 # Mercurial priorities:
2375 # 1. hg root from CODEMGR_WS environment variable
2376 # 2. hg root from directory of invocation
2377 #
2378 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2379 codemgr_ws=$(hg root -R $CODEMGR_WS 2>/dev/null)
2380 [[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null)
2381 CWS=$codemgr_ws
2382 elif [[ $SCM_MODE == "git" ]]; then
2383 #
2384 # Git priorities:
2385 # 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2386 # 2. git rev-parse --git-dir from directory of invocation
2387 #
2388 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2389 codemgr_ws=$(git --git-dir=$CODEMGR_WS rev-parse --git-dir \
2390 2>/dev/null)
2391 [[ -z $codemgr_ws ]] && \
2392 codemgr_ws=$(git rev-parse --git-dir 2>/dev/null)
2393 CWS=$codemgr_ws
2394 elif [[ $SCM_MODE == "subversion" ]]; then
2395 #
2396 # Subversion priorities:
2397 # 1. CODEMGR_WS from environment
2398 # 2. Relative path from current directory to SVN repository root
2399 #
2400 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2401 CWS=$CODEMGR_WS
2402 else
2403 svn info | while read line; do
2404 if [[ $line == "URL: "* ]]; then
2405 url=${line#URL: }
2406 elif [[ $line == "Repository Root: "* ]]; then
2407 repo=${line#Repository Root: }
2408 fi
2409 done
2410
2411 rel=${url#$repo}
2412 CWS=${PWD%$rel}
2413 fi
2414 fi
2415
2416 #
2417 # If no SCM has been determined, take either the environment setting
2418 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2419 #
2420 if [[ -z ${CWS} ]]; then
2421 CWS=${CODEMGR_WS:-.}
2422 fi
2423
2424
2425
2426 #
2427 # If the command line options indicate no webrev generation, either
2428 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2429 # ton of logic we can skip.
2430 #
2431 # Instead of increasing indentation, we intentionally leave this loop
2432 # body open here, and exit via break from multiple points within.
2433 # Search for DO_EVERYTHING below to find the break points and closure.
2434 #
2435 for do_everything in 1; do
2436
2437 # DO_EVERYTHING: break point
2438 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2439 break
2440 fi
2441
2442 #
2443 # If this manually set as the parent, and it appears to be an earlier webrev,
2444 # then note that fact and set the parent to the raw_files/new subdirectory.
2445 #
2446 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2447 parent_webrev="$codemgr_parent"
2448 codemgr_parent="$codemgr_parent/raw_files/new"
2449 fi
2450
2451 if [[ -z $wflag && -z $lflag ]]; then
2452 shift $(($OPTIND - 1))
2453
2454 if [[ $1 == "-" ]]; then
2455 cat > $FLIST
2456 flist_mode="stdin"
2457 flist_done=1
2458 shift
2459 elif [[ -n $1 ]]; then
2460 if [[ ! -r $1 ]]; then
2461 print -u2 "$1: no such file or not readable"
2462 usage
2463 fi
2464 cat $1 > $FLIST
2465 flist_mode="file"
2466 flist_file=$1
2467 flist_done=1
2468 shift
2469 else
2470 flist_mode="auto"
2471 fi
2472 fi
2473
2474 #
2475 # Before we go on to further consider -l and -w, work out which SCM we think
2476 # is in use.
2477 #
2478 case "$SCM_MODE" in
2479 teamware|mercurial|git|subversion)
2480 ;;
2481 unknown)
2482 if [[ $flist_mode == "auto" ]]; then
2483 print -u2 "Unable to determine SCM in use and file list not specified"
2484 print -u2 "See which_scm(1) for SCM detection information."
2485 exit 1
2486 fi
2487 ;;
2488 *)
2489 if [[ $flist_mode == "auto" ]]; then
2490 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2491 exit 1
2492 fi
2493 ;;
2494 esac
2495
2496 print -u2 " SCM detected: $SCM_MODE"
2497
2498 if [[ -n $lflag ]]; then
2499 #
2500 # If the -l flag is given instead of the name of a file list,
2501 # then generate the file list by extracting file names from a
2502 # putback -n.
2503 #
2504 shift $(($OPTIND - 1))
2505 if [[ $SCM_MODE == "teamware" ]]; then
2506 flist_from_teamware "$*"
2507 else
2508 print -u2 -- "Error: -l option only applies to TeamWare"
2509 exit 1
2510 fi
2511 flist_done=1
2512 shift $#
2513 elif [[ -n $wflag ]]; then
2514 #
2515 # If the -w is given then assume the file list is in Bonwick's "wx"
2516 # command format, i.e. pathname lines alternating with SCCS comment
2517 # lines with blank lines as separators. Use the SCCS comments later
2518 # in building the index.html file.
2519 #
2520 shift $(($OPTIND - 1))
2521 wxfile=$1
2522 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2523 if [[ -r $CODEMGR_WS/wx/active ]]; then
2524 wxfile=$CODEMGR_WS/wx/active
2525 fi
2526 fi
2527
2528 [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2529 "be auto-detected (check \$CODEMGR_WS)" && exit 1
2530
2531 if [[ ! -r $wxfile ]]; then
2532 print -u2 "$wxfile: no such file or not readable"
2533 usage
2534 fi
2535
2536 print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2537 flist_from_wx $wxfile
2538 flist_done=1
2539 if [[ -n "$*" ]]; then
2540 shift
2541 fi
2542 elif [[ $flist_mode == "stdin" ]]; then
2543 print -u2 " File list from: standard input"
2544 elif [[ $flist_mode == "file" ]]; then
2545 print -u2 " File list from: $flist_file"
2546 fi
2547
2548 if [[ $# -gt 0 ]]; then
2549 print -u2 "WARNING: unused arguments: $*"
2550 fi
2551
2552 #
2553 # Before we entered the DO_EVERYTHING loop, we should have already set CWS
2554 # and CODEMGR_WS as needed. Here, we set the parent workspace.
2555 #
2556
2557 if [[ $SCM_MODE == "teamware" ]]; then
2558
2559 #
2560 # Teamware priorities:
2561 #
2562 # 1) via -p command line option
2563 # 2) in the user environment
2564 # 3) in the flist
2565 # 4) automatically based on the workspace
2566 #
2567
2568 #
2569 # For 1, codemgr_parent will already be set. Here's 2:
2570 #
2571 [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
2572 codemgr_parent=$CODEMGR_PARENT
2573 if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then
2574 print -u2 "$codemgr_parent: no such directory"
2575 exit 1
2576 fi
2577
2578 #
2579 # If we're in auto-detect mode and we haven't already gotten the file
2580 # list, then see if we can get it by probing for wx.
2581 #
2582 if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then
2583 if [[ ! -x $WX ]]; then
2584 print -u2 "WARNING: wx not found!"
2585 fi
2586
2587 #
2588 # We need to use wx list -w so that we get renamed files, etc.
2589 # but only if a wx active file exists-- otherwise wx will
2590 # hang asking us to initialize our wx information.
2591 #
2592 if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then
2593 print -u2 " File list from: 'wx list -w' ... \c"
2594 $WX list -w > $FLIST
2595 $WX comments > /tmp/$$.wx_comments
2596 wxfile=/tmp/$$.wx_comments
2597 print -u2 "done"
2598 flist_done=1
2599 fi
2600 fi
2601
2602 #
2603 # If by hook or by crook we've gotten a file list by now (perhaps
2604 # from the command line), eval it to extract environment variables from
2605 # it: This is method 3 for finding the parent.
2606 #
2607 if [[ -z $flist_done ]]; then
2608 flist_from_teamware
2609 fi
2610 env_from_flist
2611
2612 #
2613 # (4) If we still don't have a value for codemgr_parent, get it
2614 # from workspace.
2615 #
2616 [[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent`
2617 if [[ ! -d $codemgr_parent ]]; then
2618 print -u2 "$CODEMGR_PARENT: no such parent workspace"
2619 exit 1
2620 fi
2621
2622 PWS=$codemgr_parent
2623 \
2624
2625 [[ -n $parent_webrev ]] && RWS=$(workspace parent $CWS)
2626
2627 elif [[ $SCM_MODE == "mercurial" ]]; then
2628 #
2629 # Parent can either be specified with -p
2630 # Specified with CODEMGR_PARENT in the environment
2631 # or taken from hg's default path.
2632 #
2633
2634 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2635 codemgr_parent=$CODEMGR_PARENT
2636 fi
2637
2638 if [[ -z $codemgr_parent ]]; then
2639 codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
2640 fi
2641
2642 CWS_REV=`hg parent -R $codemgr_ws --template '{node|short}' 2>/dev/null`
2643 PWS=$codemgr_parent
2644
2645 #
2646 # If the parent is a webrev, we want to do some things against
2647 # the natural workspace parent (file list, comments, etc)
2648 #
2649 if [[ -n $parent_webrev ]]; then
2650 real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
2651 else
2652 real_parent=$PWS
2653 fi
2654
2655 #
2656 # If hg-active exists, then we run it. In the case of no explicit
2657 # flist given, we'll use it for our comments. In the case of an
2658 # explicit flist given we'll try to use it for comments for any
2659 # files mentioned in the flist.
2660 #
2661 if [[ -z $flist_done ]]; then
2662 flist_from_mercurial $CWS $real_parent
2663 flist_done=1
2664 fi
2665
2666 #
2667 # If we have a file list now, pull out any variables set
2668 # therein. We do this now (rather than when we possibly use
2669 # hg-active to find comments) to avoid stomping specifications
2670 # in the user-specified flist.
2671 #
2672 if [[ -n $flist_done ]]; then
2673 env_from_flist
2674 fi
2675
2676 #
2677 # Only call hg-active if we don't have a wx formatted file already
2678 #
2679 if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2680 print " Comments from: hg-active -p $real_parent ...\c"
2681 hg_active_wxfile $CWS $real_parent
2682 print " Done."
2683 fi
2684
2685 #
2686 # At this point we must have a wx flist either from hg-active,
2687 # or in general. Use it to try and find our parent revision,
2688 # if we don't have one.
2689 #
2690 if [[ -z $HG_PARENT ]]; then
2691 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2692 fi
2693
2694 #
2695 # If we still don't have a parent, we must have been given a
2696 # wx-style active list with no HG_PARENT specification, run
2697 # hg-active and pull an HG_PARENT out of it, ignore the rest.
2698 #
2699 if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2700 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2701 eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2702 elif [[ -z $HG_PARENT ]]; then
2703 print -u2 "Error: Cannot discover parent revision"
2704 exit 1
2705 fi
2706 elif [[ $SCM_MODE == "git" ]]; then
2707 #
2708 # Parent can either be specified with -p
2709 # Specified with CODEMGR_PARENT in the environment
2710 # or taken from git config.
2711 #
2712
2713 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2714 codemgr_parent=$CODEMGR_PARENT
2715 fi
2716
2717 if [[ -z $codemgr_parent ]]; then
2718 codemgr_parent=$(git --git-dir=$codemgr_ws config \
2719 remote.origin.url 2>/dev/null)
2720 fi
2721
2722 CWS_REV=$(git --git-dir=$codemgr_ws rev-parse HEAD 2>/dev/null)
2723 PWS=$codemgr_parent
2724
2725 #
2726 # If the parent is a webrev, we want to do some things against
2727 # the natural workspace parent (file list, comments, etc)
2728 #
2729 if [[ -n $parent_webrev ]]; then
2730 real_parent=$(git --git-dir $codemgr_ws config \
2731 remote.origin.url 2>/dev/null)
2732 else
2733 real_parent=$PWS
2734 fi
2735
2736 #
2737 # If git-active exists, then we run it. In the case of no explicit
2738 # flist given, we'll use it for our comments. In the case of an
2739 # explicit flist given we'll try to use it for comments for any
2740 # files mentioned in the flist.
2741 #
2742 if [[ -z $flist_done ]]; then
2743 flist_from_git $CWS $real_parent
2744 flist_done=1
2745 fi
2746
2747 #
2748 # If we have a file list now, pull out any variables set
2749 # therein. We do this now (rather than when we possibly use
2750 # git-active to find comments) to avoid stomping specifications
2751 # in the user-specified flist.
2752 #
2753 if [[ -n $flist_done ]]; then
2754 env_from_flist
2755 fi
2756
2757 #
2758 # Only call git-active if we don't have a wx formatted file already
2759 #
2760 if [[ -x $GIT_ACTIVE && -z $wxfile ]]; then
2761 print " Comments from: git-active -p $real_parent ...\c"
2762 git_active_wxfile $CWS $real_parent
2763 print " Done."
2764 fi
2765
2766 #
2767 # At this point we must have a wx flist either from git-active,
2768 # or in general. Use it to try and find our parent revision,
2769 # if we don't have one.
2770 #
2771 if [[ -z $GIT_PARENT ]]; then
2772 eval `$SED -e "s/#.*$//" $wxfile | $GREP GIT_PARENT=`
2773 fi
2774
2775 #
2776 # If we still don't have a parent, we must have been given a
2777 # wx-style active list with no GIT_PARENT specification, run
2778 # git-active and pull an GIT_PARENT out of it, ignore the rest.
2779 #
2780 if [[ -z $GIT_PARENT && -x $GIT_ACTIVE ]]; then
2781 $GIT_ACTIVE -w $codemgr_ws -p $real_parent | \
2782 eval `$SED -e "s/#.*$//" | $GREP GIT_PARENT=`
2783 elif [[ -z $GIT_PARENT ]]; then
2784 print -u2 "Error: Cannot discover parent revision"
2785 exit 1
2786 fi
2787 WDIR=${WDIR:-$CWS/../webrev}
2788 elif [[ $SCM_MODE == "subversion" ]]; then
2789
2790 #
2791 # We only will have a real parent workspace in the case one
2792 # was specified (be it an older webrev, or another checkout).
2793 #
2794 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2795
2796 if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2797 flist_from_subversion $CWS $OLDPWD
2798 fi
2799 else
2800 if [[ $SCM_MODE == "unknown" ]]; then
2801 print -u2 " Unknown type of SCM in use"
2802 else
2803 print -u2 " Unsupported SCM in use: $SCM_MODE"
2804 fi
2805
2806 env_from_flist
2807
2808 if [[ -z $CODEMGR_WS ]]; then
2809 print -u2 "SCM not detected/supported and CODEMGR_WS not specified"
2810 exit 1
2811 fi
2812
2813 if [[ -z $CODEMGR_PARENT ]]; then
2814 print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified"
2815 exit 1
2816 fi
2817
2818 CWS=$CODEMGR_WS
2819 PWS=$CODEMGR_PARENT
2820 fi
2821
2822 #
2823 # If the user didn't specify a -i option, check to see if there is a
2824 # webrev-info file in the workspace directory.
2825 #
2826 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2827 iflag=1
2828 INCLUDE_FILE="$CWS/webrev-info"
2829 fi
2830
2831 if [[ -n $iflag ]]; then
2832 if [[ ! -r $INCLUDE_FILE ]]; then
2833 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2834 "not readable."
2835 exit 1
2836 else
2837 #
2838 # $INCLUDE_FILE may be a relative path, and the script alters
2839 # PWD, so we just stash a copy in /tmp.
2840 #
2841 cp $INCLUDE_FILE /tmp/$$.include
2842 fi
2843 fi
2844
2845 # DO_EVERYTHING: break point
2846 if [[ -n $Nflag ]]; then
2847 break
2848 fi
2849
2850 typeset -A itsinfo
2851 typeset -r its_sed_script=/tmp/$$.its_sed
2852 valid_prefixes=
2853 if [[ -z $nflag ]]; then
2854 DEFREGFILE="$(dirname $(whence $0))/../etc/its.reg"
2855 if [[ -n $Iflag ]]; then
2856 REGFILE=$ITSREG
2857 elif [[ -r $HOME/.its.reg ]]; then
2858 REGFILE=$HOME/.its.reg
2859 else
2860 REGFILE=$DEFREGFILE
2861 fi
2862 if [[ ! -r $REGFILE ]]; then
2863 print "ERROR: Unable to read database registry file $REGFILE"
2864 exit 1
2865 elif [[ $REGFILE != $DEFREGFILE ]]; then
2866 print " its.reg from: $REGFILE"
2867 fi
2868
2869 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do
2870
2871 name=${LINE%%=*}
2872 value="${LINE#*=}"
2873
2874 if [[ $name == PREFIX ]]; then
2875 p=${value}
2876 valid_prefixes="${p} ${valid_prefixes}"
2877 else
2878 itsinfo["${p}_${name}"]="${value}"
2879 fi
2880 done
2881
2882
2883 DEFCONFFILE="$(dirname $(whence $0))/../etc/its.conf"
2884 CONFFILES=$DEFCONFFILE
2885 if [[ -r $HOME/.its.conf ]]; then
2886 CONFFILES="${CONFFILES} $HOME/.its.conf"
2887 fi
2888 if [[ -n $Cflag ]]; then
2889 CONFFILES="${CONFFILES} ${ITSCONF}"
2890 fi
2891 its_domain=
2892 its_priority=
2893 for cf in ${CONFFILES}; do
2894 if [[ ! -r $cf ]]; then
2895 print "ERROR: Unable to read database configuration file $cf"
2896 exit 1
2897 elif [[ $cf != $DEFCONFFILE ]]; then
2898 print " its.conf: reading $cf"
2899 fi
2900 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do
2901 eval "${LINE}"
2902 done
2903 done
2904
2905 #
2906 # If an information tracking system is explicitly identified by prefix,
2907 # we want to disregard the specified priorities and resolve it accordingly.
2908 #
2909 # To that end, we'll build a sed script to do each valid prefix in turn.
2910 #
2911 for p in ${valid_prefixes}; do
2912 #
2913 # When an informational URL was provided, translate it to a
2914 # hyperlink. When omitted, simply use the prefix text.
2915 #
2916 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2917 itsinfo["${p}_INFO"]=${p}
2918 else
2919 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2920 fi
2921
2922 #
2923 # Assume that, for this invocation of webrev, all references
2924 # to this information tracking system should resolve through
2925 # the same URL.
2926 #
2927 # If the caller specified -O, then always use EXTERNAL_URL.
2928 #
2929 # Otherwise, look in the list of domains for a matching
2930 # INTERNAL_URL.
2931 #
2932 [[ -z $Oflag ]] && for d in ${its_domain}; do
2933 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2934 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2935 break
2936 fi
2937 done
2938 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2939 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2940 fi
2941
2942 #
2943 # Turn the destination URL into a hyperlink
2944 #
2945 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2946
2947 print "/^${p}[ ]/ {
2948 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2949 s;^${p};${itsinfo[${p}_INFO]};
2950 }" >> ${its_sed_script}
2951 done
2952
2953 #
2954 # The previous loop took care of explicit specification. Now use
2955 # the configured priorities to attempt implicit translations.
2956 #
2957 for p in ${its_priority}; do
2958 print "/^${itsinfo[${p}_REGEX]}[ ]/ {
2959 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2960 }" >> ${its_sed_script}
2961 done
2962 fi
2963
2964 #
2965 # Search for DO_EVERYTHING above for matching "for" statement
2966 # and explanation of this terminator.
2967 #
2968 done
2969
2970 #
2971 # Output directory.
2972 #
2973 WDIR=${WDIR:-$CWS/webrev}
2974
2975 #
2976 # Name of the webrev, derived from the workspace name or output directory;
2977 # in the future this could potentially be an option.
2978 #
2979 if [[ -n $oflag ]]; then
2980 WNAME=${WDIR##*/}
2981 else
2982 WNAME=${CWS##*/}
2983 fi
2984
2985 # Make sure remote target is well formed for remote upload/delete.
2986 if [[ -n $Dflag || -n $Uflag ]]; then
2987 #
2988 # If remote target is not specified, build it from scratch using
2989 # the default values.
2990 #
2991 if [[ -z $tflag ]]; then
2992 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2993 else
2994 #
2995 # Check upload target prefix first.
2996 #
2997 if [[ "${remote_target}" != ${rsync_prefix}* &&
2998 "${remote_target}" != ${ssh_prefix}* ]]; then
2999 print "ERROR: invalid prefix of upload URI" \
3000 "($remote_target)"
3001 exit 1
3002 fi
3003 #
3004 # If destination specification is not in the form of
3005 # host_spec:remote_dir then assume it is just remote hostname
3006 # and append a colon and destination directory formed from
3007 # local webrev directory name.
3008 #
3009 typeset target_no_prefix=${remote_target##*://}
3010 if [[ ${target_no_prefix} == *:* ]]; then
3011 if [[ "${remote_target}" == *: ]]; then
3012 remote_target=${remote_target}${WNAME}
3013 fi
3014 else
3015 if [[ ${target_no_prefix} == */* ]]; then
3016 print "ERROR: badly formed upload URI" \
3017 "($remote_target)"
3018 exit 1
3019 else
3020 remote_target=${remote_target}:${WNAME}
3021 fi
3022 fi
3023 fi
3024
3025 #
3026 # Strip trailing slash. Each upload method will deal with directory
3027 # specification separately.
3028 #
3029 remote_target=${remote_target%/}
3030 fi
3031
3032 #
3033 # Option -D by itself (option -U not present) implies no webrev generation.
3034 #
3035 if [[ -z $Uflag && -n $Dflag ]]; then
3036 delete_webrev 1 1
3037 exit $?
3038 fi
3039
3040 #
3041 # Do not generate the webrev, just upload it or delete it.
3042 #
3043 if [[ -n $nflag ]]; then
3044 if [[ -n $Dflag ]]; then
3045 delete_webrev 1 1
3046 (( $? == 0 )) || exit $?
3047 fi
3048 if [[ -n $Uflag ]]; then
3049 upload_webrev
3050 exit $?
3051 fi
3052 fi
3053
3054 if [ "${WDIR%%/*}" ]; then
3055 WDIR=$PWD/$WDIR
3056 fi
3057
3058 if [[ ! -d $WDIR ]]; then
3059 mkdir -p $WDIR
3060 (( $? != 0 )) && exit 1
3061 fi
3062
3063 #
3064 # Summarize what we're going to do.
3065 #
3066 if [[ -n $CWS_REV ]]; then
3067 print " Workspace: $CWS (at $CWS_REV)"
3068 else
3069 print " Workspace: $CWS"
3070 fi
3071 if [[ -n $parent_webrev ]]; then
3072 print "Compare against: webrev at $parent_webrev"
3073 else
3074 if [[ -n $HG_PARENT ]]; then
3075 hg_parent_short=`echo $HG_PARENT \
3076 | $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'`
3077 print "Compare against: $PWS (at $hg_parent_short)"
3078 else
3079 print "Compare against: $PWS"
3080 fi
3081 fi
3082
3083 [[ -n $INCLUDE_FILE ]] && print " Including: $INCLUDE_FILE"
3084 print " Output to: $WDIR"
3085
3086 #
3087 # Save the file list in the webrev dir
3088 #
3089 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
3090
3091 rm -f $WDIR/$WNAME.patch
3092 rm -f $WDIR/$WNAME.ps
3093 rm -f $WDIR/$WNAME.pdf
3094
3095 touch $WDIR/$WNAME.patch
3096
3097 print " Output Files:"
3098
3099 #
3100 # Clean up the file list: Remove comments, blank lines and env variables.
3101 #
3102 $SED -e "s/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean
3103 FLIST=/tmp/$$.flist.clean
3104
3105 #
3106 # For Mercurial, create a cache of manifest entries.
3107 #
3108 if [[ $SCM_MODE == "mercurial" ]]; then
3109 #
3110 # Transform the FLIST into a temporary sed script that matches
3111 # relevant entries in the Mercurial manifest as follows:
3112 # 1) The script will be used against the parent revision manifest,
3113 # so for FLIST lines that have two filenames (a renamed file)
3114 # keep only the old name.
3115 # 2) Escape all forward slashes the filename.
3116 # 3) Change the filename into another sed command that matches
3117 # that file in "hg manifest -v" output: start of line, three
3118 # octal digits for file permissions, space, a file type flag
3119 # character, space, the filename, end of line.
3120 #
3121 SEDFILE=/tmp/$$.manifest.sed
3122 $SED '
3123 s#^[^ ]* ##
3124 s#/#\\\/#g
3125 s#^.*$#/^... . &$/p#
3126 ' < $FLIST > $SEDFILE
3127
3128 #
3129 # Apply the generated script to the output of "hg manifest -v"
3130 # to get the relevant subset for this webrev.
3131 #
3132 HG_PARENT_MANIFEST=/tmp/$$.manifest
3133 hg -R $CWS manifest -v -r $HG_PARENT |
3134 $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
3135 fi
3136
3137 #
3138 # First pass through the files: generate the per-file webrev HTML-files.
3139 #
3140 cat $FLIST | while read LINE
3141 do
3142 set - $LINE
3143 P=$1
3144
3145 #
3146 # Normally, each line in the file list is just a pathname of a
3147 # file that has been modified or created in the child. A file
3148 # that is renamed in the child workspace has two names on the
3149 # line: new name followed by the old name.
3150 #
3151 oldname=""
3152 oldpath=""
3153 rename=
3154 if [[ $# -eq 2 ]]; then
3155 PP=$2 # old filename
3156 oldname=" (was $PP)"
3157 oldpath="$PP"
3158 rename=1
3159 PDIR=${PP%/*}
3160 if [[ $PDIR == $PP ]]; then
3161 PDIR="." # File at root of workspace
3162 fi
3163
3164 PF=${PP##*/}
3165
3166 DIR=${P%/*}
3167 if [[ $DIR == $P ]]; then
3168 DIR="." # File at root of workspace
3169 fi
3170
3171 F=${P##*/}
3172
3173 else
3174 DIR=${P%/*}
3175 if [[ "$DIR" == "$P" ]]; then
3176 DIR="." # File at root of workspace
3177 fi
3178
3179 F=${P##*/}
3180
3181 PP=$P
3182 PDIR=$DIR
3183 PF=$F
3184 fi
3185
3186 COMM=`getcomments html $P $PP`
3187
3188 print "\t$P$oldname\n\t\t\c"
3189
3190 # Make the webrev mirror directory if necessary
3191 mkdir -p $WDIR/$DIR
3192
3193 #
3194 # If we're in OpenSolaris mode, we enforce a minor policy:
3195 # help to make sure the reviewer doesn't accidentally publish
3196 # source which is in usr/closed/* or deleted_files/usr/closed/*
3197 #
3198 if [[ -n "$Oflag" ]]; then
3199 pclosed=${P##usr/closed/}
3200 pdeleted=${P##deleted_files/usr/closed/}
3201 if [[ "$pclosed" != "$P" || "$pdeleted" != "$P" ]]; then
3202 print "*** Omitting closed source for OpenSolaris" \
3203 "mode review"
3204 continue
3205 fi
3206 fi
3207
3208 #
3209 # We stash old and new files into parallel directories in $WDIR
3210 # and do our diffs there. This makes it possible to generate
3211 # clean looking diffs which don't have absolute paths present.
3212 #
3213
3214 build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3215 continue
3216
3217 #
3218 # Keep the old PWD around, so we can safely switch back after
3219 # diff generation, such that build_old_new runs in a
3220 # consistent environment.
3221 #
3222 OWD=$PWD
3223 cd $WDIR/raw_files
3224 ofile=old/$PDIR/$PF
3225 nfile=new/$DIR/$F
3226
3227 mv_but_nodiff=
3228 cmp $ofile $nfile > /dev/null 2>&1
3229 if [[ $? == 0 && $rename == 1 ]]; then
3230 mv_but_nodiff=1
3231 fi
3232
3233 #
3234 # If we have old and new versions of the file then run the appropriate
3235 # diffs. This is complicated by a couple of factors:
3236 #
3237 # - renames must be handled specially: we emit a 'remove'
3238 # diff and an 'add' diff
3239 # - new files and deleted files must be handled specially
3240 # - Solaris patch(1m) can't cope with file creation
3241 # (and hence renames) as of this writing.
3242 # - To make matters worse, gnu patch doesn't interpret the
3243 # output of Solaris diff properly when it comes to
3244 # adds and deletes. We need to do some "cleansing"
3245 # transformations:
3246 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@
3247 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@
3248 #
3249 cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3250 cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3251
3252 rm -f $WDIR/$DIR/$F.patch
3253 if [[ -z $rename ]]; then
3254 if [ ! -f "$ofile" ]; then
3255 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3256 > $WDIR/$DIR/$F.patch
3257 elif [ ! -f "$nfile" ]; then
3258 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3259 > $WDIR/$DIR/$F.patch
3260 else
3261 diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3262 fi
3263 else
3264 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3265 > $WDIR/$DIR/$F.patch
3266
3267 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3268 >> $WDIR/$DIR/$F.patch
3269
3270 fi
3271
3272 #
3273 # Tack the patch we just made onto the accumulated patch for the
3274 # whole wad.
3275 #
3276 cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3277
3278 print " patch\c"
3279
3280 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3281
3282 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3283 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3284 > $WDIR/$DIR/$F.cdiff.html
3285 print " cdiffs\c"
3286
3287 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3288 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3289 > $WDIR/$DIR/$F.udiff.html
3290
3291 print " udiffs\c"
3292
3293 if [[ -x $WDIFF ]]; then
3294 $WDIFF -c "$COMM" \
3295 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3296 $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3297 if [[ $? -eq 0 ]]; then
3298 print " wdiffs\c"
3299 else
3300 print " wdiffs[fail]\c"
3301 fi
3302 fi
3303
3304 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3305 > $WDIR/$DIR/$F.sdiff.html
3306 print " sdiffs\c"
3307
3308 print " frames\c"
3309
3310 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3311
3312 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3313
3314 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3315 # renamed file: may also have differences
3316 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3317 elif [[ -f $nfile ]]; then
3318 # new file: count added lines
3319 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3320 elif [[ -f $ofile ]]; then
3321 # old file: count deleted lines
3322 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3323 fi
3324
3325 #
3326 # Now we generate the postscript for this file. We generate diffs
3327 # only in the event that there is delta, or the file is new (it seems
3328 # tree-killing to print out the contents of deleted files).
3329 #
3330 if [[ -f $nfile ]]; then
3331 ocr=$ofile
3332 [[ ! -f $ofile ]] && ocr=/dev/null
3333
3334 if [[ -z $mv_but_nodiff ]]; then
3335 textcomm=`getcomments text $P $PP`
3336 if [[ -x $CODEREVIEW ]]; then
3337 $CODEREVIEW -y "$textcomm" \
3338 -e $ocr $nfile \
3339 > /tmp/$$.psfile 2>/dev/null &&
3340 cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3341 if [[ $? -eq 0 ]]; then
3342 print " ps\c"
3343 else
3344 print " ps[fail]\c"
3345 fi
3346 fi
3347 fi
3348 fi
3349
3350 if [[ -f $ofile ]]; then
3351 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3352 print " old\c"
3353 fi
3354
3355 if [[ -f $nfile ]]; then
3356 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3357 print " new\c"
3358 fi
3359
3360 cd $OWD
3361
3362 print
3363 done
3364
3365 frame_nav_js > $WDIR/ancnav.js
3366 frame_navigation > $WDIR/ancnav.html
3367
3368 if [[ ! -f $WDIR/$WNAME.ps ]]; then
3369 print " Generating PDF: Skipped: no output available"
3370 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3371 print " Generating PDF: \c"
3372 fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3373 print "Done."
3374 else
3375 print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3376 fi
3377
3378 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3379 # delete it - prevent accidental publishing of closed source
3380
3381 if [[ -n "$Oflag" ]]; then
3382 $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3383 fi
3384
3385 # Now build the index.html file that contains
3386 # links to the source files and their diffs.
3387
3388 cd $CWS
3389
3390 # Save total changed lines for Code Inspection.
3391 print "$TOTL" > $WDIR/TotalChangedLines
3392
3393 print " index.html: \c"
3394 INDEXFILE=$WDIR/index.html
3395 exec 3<&1 # duplicate stdout to FD3.
3396 exec 1<&- # Close stdout.
3397 exec > $INDEXFILE # Open stdout to index file.
3398
3399 print "$HTML<head>$STDHEAD"
3400 print "<title>$WNAME</title>"
3401 print "</head>"
3402 print "<body id=\"SUNWwebrev\">"
3403 print "<div class=\"summary\">"
3404 print "<h2>Code Review for $WNAME</h2>"
3405
3406 print "<table>"
3407
3408 #
3409 # Get the preparer's name:
3410 #
3411 # If the SCM detected is Mercurial, and the configuration property
3412 # ui.username is available, use that, but be careful to properly escape
3413 # angle brackets (HTML syntax characters) in the email address.
3414 #
3415 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3416 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3417 #
3418 preparer=
3419 if [[ "$SCM_MODE" == mercurial ]]; then
3420 preparer=`hg showconfig ui.username 2>/dev/null`
3421 if [[ -n "$preparer" ]]; then
3422 preparer="$(echo "$preparer" | html_quote)"
3423 fi
3424 fi
3425 if [[ -z "$preparer" ]]; then
3426 preparer=$(
3427 $PERL -e '
3428 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3429 if ($login) {
3430 $gcos =~ s/\&/ucfirst($login)/e;
3431 printf "%s (%s)\n", $gcos, $login;
3432 } else {
3433 printf "(unknown)\n";
3434 }
3435 ')
3436 fi
3437
3438 print "<tr><th>Prepared by:</th><td>$preparer on `date`</td></tr>"
3439 print "<tr><th>Workspace:</th><td>$CWS"
3440 if [[ -n $CWS_REV ]]; then
3441 print "(at $CWS_REV)"
3442 fi
3443 print "</td></tr>"
3444 print "<tr><th>Compare against:</th><td>"
3445 if [[ -n $parent_webrev ]]; then
3446 print "webrev at $parent_webrev"
3447 else
3448 print "$PWS"
3449 if [[ -n $hg_parent_short ]]; then
3450 print "(at $hg_parent_short)"
3451 fi
3452 fi
3453 print "</td></tr>"
3454 print "<tr><th>Summary of changes:</th><td>"
3455 printCI $TOTL $TINS $TDEL $TMOD $TUNC
3456 print "</td></tr>"
3457
3458 if [[ -f $WDIR/$WNAME.patch ]]; then
3459 wpatch_url="$(print $WNAME.patch | url_encode)"
3460 print "<tr><th>Patch of changes:</th><td>"
3461 print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3462 fi
3463 if [[ -f $WDIR/$WNAME.pdf ]]; then
3464 wpdf_url="$(print $WNAME.pdf | url_encode)"
3465 print "<tr><th>Printable review:</th><td>"
3466 print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3467 fi
3468
3469 if [[ -n "$iflag" ]]; then
3470 print "<tr><th>Author comments:</th><td><div>"
3471 cat /tmp/$$.include
3472 print "</div></td></tr>"
3473 fi
3474 print "</table>"
3475 print "</div>"
3476
3477 #
3478 # Second pass through the files: generate the rest of the index file
3479 #
3480 cat $FLIST | while read LINE
3481 do
3482 set - $LINE
3483 P=$1
3484
3485 if [[ $# == 2 ]]; then
3486 PP=$2
3487 oldname="$PP"
3488 else
3489 PP=$P
3490 oldname=""
3491 fi
3492
3493 mv_but_nodiff=
3494 cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3495 if [[ $? == 0 && -n "$oldname" ]]; then
3496 mv_but_nodiff=1
3497 fi
3498
3499 DIR=${P%/*}
3500 if [[ $DIR == $P ]]; then
3501 DIR="." # File at root of workspace
3502 fi
3503
3504 # Avoid processing the same file twice.
3505 # It's possible for renamed files to
3506 # appear twice in the file list
3507
3508 F=$WDIR/$P
3509
3510 print "<p>"
3511
3512 # If there's a diffs file, make diffs links
3513
3514 if [[ -f $F.cdiff.html ]]; then
3515 cdiff_url="$(print $P.cdiff.html | url_encode)"
3516 udiff_url="$(print $P.udiff.html | url_encode)"
3517 print "<a href=\"$cdiff_url\">Cdiffs</a>"
3518 print "<a href=\"$udiff_url\">Udiffs</a>"
3519
3520 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3521 wdiff_url="$(print $P.wdiff.html | url_encode)"
3522 print "<a href=\"$wdiff_url\">Wdiffs</a>"
3523 fi
3524
3525 sdiff_url="$(print $P.sdiff.html | url_encode)"
3526 print "<a href=\"$sdiff_url\">Sdiffs</a>"
3527
3528 frames_url="$(print $P.frames.html | url_encode)"
3529 print "<a href=\"$frames_url\">Frames</a>"
3530 else
3531 print " ------ ------ ------"
3532
3533 if [[ -x $WDIFF ]]; then
3534 print " ------"
3535 fi
3536
3537 print " ------"
3538 fi
3539
3540 # If there's an old file, make the link
3541
3542 if [[ -f $F-.html ]]; then
3543 oldfile_url="$(print $P-.html | url_encode)"
3544 print "<a href=\"$oldfile_url\">Old</a>"
3545 else
3546 print " ---"
3547 fi
3548
3549 # If there's an new file, make the link
3550
3551 if [[ -f $F.html ]]; then
3552 newfile_url="$(print $P.html | url_encode)"
3553 print "<a href=\"$newfile_url\">New</a>"
3554 else
3555 print " ---"
3556 fi
3557
3558 if [[ -f $F.patch ]]; then
3559 patch_url="$(print $P.patch | url_encode)"
3560 print "<a href=\"$patch_url\">Patch</a>"
3561 else
3562 print " -----"
3563 fi
3564
3565 if [[ -f $WDIR/raw_files/new/$P ]]; then
3566 rawfiles_url="$(print raw_files/new/$P | url_encode)"
3567 print "<a href=\"$rawfiles_url\">Raw</a>"
3568 else
3569 print " ---"
3570 fi
3571
3572 print "<b>$P</b>"
3573
3574 # For renamed files, clearly state whether or not they are modified
3575 if [[ -n "$oldname" ]]; then
3576 if [[ -n "$mv_but_nodiff" ]]; then
3577 print "<i>(renamed only, was $oldname)</i>"
3578 else
3579 print "<i>(modified and renamed, was $oldname)</i>"
3580 fi
3581 fi
3582
3583 # If there's an old file, but no new file, the file was deleted
3584 if [[ -f $F-.html && ! -f $F.html ]]; then
3585 print " <i>(deleted)</i>"
3586 fi
3587
3588 #
3589 # Check for usr/closed and deleted_files/usr/closed
3590 #
3591 if [ ! -z "$Oflag" ]; then
3592 if [[ $P == usr/closed/* || \
3593 $P == deleted_files/usr/closed/* ]]; then
3594 print " <i>Closed source: omitted from" \
3595 "this review</i>"
3596 fi
3597 fi
3598
3599 print "</p>"
3600 # Insert delta comments
3601
3602 print "<blockquote><pre>"
3603 getcomments html $P $PP
3604 print "</pre>"
3605
3606 # Add additional comments comment
3607
3608 print "<!-- Add comments to explain changes in $P here -->"
3609
3610 # Add count of changes.
3611
3612 if [[ -f $F.count ]]; then
3613 cat $F.count
3614 rm $F.count
3615 fi
3616
3617 if [[ $SCM_MODE == "teamware" ||
3618 $SCM_MODE == "mercurial" ||
3619 $SCM_MODE == "unknown" ]]; then
3620
3621 # Include warnings for important file mode situations:
3622 # 1) New executable files
3623 # 2) Permission changes of any kind
3624 # 3) Existing executable files
3625
3626 old_mode=
3627 if [[ -f $WDIR/raw_files/old/$PP ]]; then
3628 old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3629 fi
3630
3631 new_mode=
3632 if [[ -f $WDIR/raw_files/new/$P ]]; then
3633 new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3634 fi
3635
3636 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3637 print "<span class=\"chmod\">"
3638 print "<p>new executable file: mode $new_mode</p>"
3639 print "</span>"
3640 elif [[ -n "$old_mode" && -n "$new_mode" &&
3641 "$old_mode" != "$new_mode" ]]; then
3642 print "<span class=\"chmod\">"
3643 print "<p>mode change: $old_mode to $new_mode</p>"
3644 print "</span>"
3645 elif [[ "$new_mode" = *[1357]* ]]; then
3646 print "<span class=\"chmod\">"
3647 print "<p>executable file: mode $new_mode</p>"
3648 print "</span>"
3649 fi
3650 fi
3651
3652 print "</blockquote>"
3653 done
3654
3655 print
3656 print
3657 print "<hr></hr>"
3658 print "<p style=\"font-size: small\">"
3659 print "This code review page was prepared using <b>$0</b>."
3660 print "Webrev is maintained by the <a href=\"http://www.opensolaris.org\">"
3661 print "OpenSolaris</a> project. The latest version may be obtained"
3662 print "<a href=\"http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3663 print "</body>"
3664 print "</html>"
3665
3666 exec 1<&- # Close FD 1.
3667 exec 1<&3 # dup FD 3 to restore stdout.
3668 exec 3<&- # close FD 3.
3669
3670 print "Done."
3671
3672 #
3673 # If remote deletion was specified and fails do not continue.
3674 #
3675 if [[ -n $Dflag ]]; then
3676 delete_webrev 1 1
3677 (( $? == 0 )) || exit $?
3678 fi
3679
3680 if [[ -n $Uflag ]]; then
3681 upload_webrev
3682 exit $?
3683 fi