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 # flist_from_subversion
1783 #
1784 # Generate the file list by extracting file names from svn status.
1785 #
1786 function flist_from_subversion
1787 {
1788 CWS=$1
1789 OLDPWD=$2
1790
1791 cd $CWS
1792 print -u2 " File list from: svn status ... \c"
1793 svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1794 print -u2 " Done."
1795 cd $OLDPWD
1796 }
1797
1798 function env_from_flist
1799 {
1800 [[ -r $FLIST ]] || return
1801
1802 #
1803 # Use "eval" to set env variables that are listed in the file
1804 # list. Then copy those into our local versions of those
1805 # variables if they have not been set already.
1806 #
1807 eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1808
1809 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1810 codemgr_ws=$CODEMGR_WS
1811 export CODEMGR_WS
1812 fi
1813
1814 #
1815 # Check to see if CODEMGR_PARENT is set in the flist file.
1816 #
1817 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1818 codemgr_parent=$CODEMGR_PARENT
1819 export CODEMGR_PARENT
1820 fi
1821 }
1822
1823 function look_for_prog
1824 {
1825 typeset path
1826 typeset ppath
1827 typeset progname=$1
1828
1829 ppath=$PATH
1830 ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1831 ppath=$ppath:/opt/teamware/bin:/opt/onbld/bin
1832 ppath=$ppath:/opt/onbld/bin/`uname -p`
1833
1834 PATH=$ppath prog=`whence $progname`
1835 if [[ -n $prog ]]; then
1836 print $prog
1837 fi
1838 }
1839
1840 function get_file_mode
1841 {
1842 $PERL -e '
1843 if (@stat = stat($ARGV[0])) {
1844 $mode = $stat[2] & 0777;
1845 printf "%03o\n", $mode;
1846 exit 0;
1847 } else {
1848 exit 1;
1849 }
1850 ' $1
1851 }
1852
1853 function build_old_new_teamware
1854 {
1855 typeset olddir="$1"
1856 typeset newdir="$2"
1857
1858 # If the child's version doesn't exist then
1859 # get a readonly copy.
1860
1861 if [[ ! -f $CWS/$DIR/$F && -f $CWS/$DIR/SCCS/s.$F ]]; then
1862 $SCCS get -s -p $CWS/$DIR/$F > $CWS/$DIR/$F
1863 fi
1864
1865 # The following two sections propagate file permissions the
1866 # same way SCCS does. If the file is already under version
1867 # control, always use permissions from the SCCS/s.file. If
1868 # the file is not under SCCS control, use permissions from the
1869 # working copy. In all cases, the file copied to the webrev
1870 # is set to read only, and group/other permissions are set to
1871 # match those of the file owner. This way, even if the file
1872 # is currently checked out, the webrev will display the final
1873 # permissions that would result after check in.
1874
1875 #
1876 # Snag new version of file.
1877 #
1878 rm -f $newdir/$DIR/$F
1879 cp $CWS/$DIR/$F $newdir/$DIR/$F
1880 if [[ -f $CWS/$DIR/SCCS/s.$F ]]; then
1881 chmod `get_file_mode $CWS/$DIR/SCCS/s.$F` \
1882 $newdir/$DIR/$F
1883 fi
1884 chmod u-w,go=u $newdir/$DIR/$F
1885
1886 #
1887 # Get the parent's version of the file. First see whether the
1888 # child's version is checked out and get the parent's version
1889 # with keywords expanded or unexpanded as appropriate.
1890 #
1891 if [[ -f $PWS/$PDIR/$PF && ! -f $PWS/$PDIR/SCCS/s.$PF && \
1892 ! -f $PWS/$PDIR/SCCS/p.$PF ]]; then
1893 # Parent is not a real workspace, but just a raw
1894 # directory tree - use the file that's there as
1895 # the old file.
1896
1897 rm -f $olddir/$PDIR/$PF
1898 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1899 else
1900 if [[ -f $PWS/$PDIR/SCCS/s.$PF ]]; then
1901 real_parent=$PWS
1902 else
1903 real_parent=$RWS
1904 fi
1905
1906 rm -f $olddir/$PDIR/$PF
1907
1908 if [[ -f $real_parent/$PDIR/$PF ]]; then
1909 if [ -f $CWS/$DIR/SCCS/p.$F ]; then
1910 $SCCS get -s -p -k $real_parent/$PDIR/$PF > \
1911 $olddir/$PDIR/$PF
1912 else
1913 $SCCS get -s -p $real_parent/$PDIR/$PF > \
1914 $olddir/$PDIR/$PF
1915 fi
1916 chmod `get_file_mode $real_parent/$PDIR/SCCS/s.$PF` \
1917 $olddir/$PDIR/$PF
1918 fi
1919 fi
1920 if [[ -f $olddir/$PDIR/$PF ]]; then
1921 chmod u-w,go=u $olddir/$PDIR/$PF
1922 fi
1923 }
1924
1925 function build_old_new_mercurial
1926 {
1927 typeset olddir="$1"
1928 typeset newdir="$2"
1929 typeset old_mode=
1930 typeset new_mode=
1931 typeset file
1932
1933 #
1934 # Get old file mode, from the parent revision manifest entry.
1935 # Mercurial only stores a "file is executable" flag, but the
1936 # manifest will display an octal mode "644" or "755".
1937 #
1938 if [[ "$PDIR" == "." ]]; then
1939 file="$PF"
1940 else
1941 file="$PDIR/$PF"
1942 fi
1943 file=`echo $file | $SED 's#/#\\\/#g'`
1944 # match the exact filename, and return only the permission digits
1945 old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
1946 < $HG_PARENT_MANIFEST`
1947
1948 #
1949 # Get new file mode, directly from the filesystem.
1950 # Normalize the mode to match Mercurial's behavior.
1951 #
1952 new_mode=`get_file_mode $CWS/$DIR/$F`
1953 if [[ -n "$new_mode" ]]; then
1954 if [[ "$new_mode" = *[1357]* ]]; then
1955 new_mode=755
1956 else
1957 new_mode=644
1958 fi
1959 fi
1960
1961 #
1962 # new version of the file.
1963 #
1964 rm -rf $newdir/$DIR/$F
1965 if [[ -e $CWS/$DIR/$F ]]; then
1966 cp $CWS/$DIR/$F $newdir/$DIR/$F
1967 if [[ -n $new_mode ]]; then
1968 chmod $new_mode $newdir/$DIR/$F
1969 else
1970 # should never happen
1971 print -u2 "ERROR: set mode of $newdir/$DIR/$F"
1972 fi
1973 fi
1974
1975 #
1976 # parent's version of the file
1977 #
1978 # Note that we get this from the last version common to both
1979 # ourselves and the parent. References are via $CWS since we have no
1980 # guarantee that the parent workspace is reachable via the filesystem.
1981 #
1982 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
1983 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1984 elif [[ -n $HG_PARENT ]]; then
1985 hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
1986 $olddir/$PDIR/$PF 2>/dev/null
1987
1988 if (( $? != 0 )); then
1989 rm -f $olddir/$PDIR/$PF
1990 else
1991 if [[ -n $old_mode ]]; then
1992 chmod $old_mode $olddir/$PDIR/$PF
1993 else
1994 # should never happen
1995 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
1996 fi
1997 fi
1998 fi
1999 }
2000
2001 function build_old_new_subversion
2002 {
2003 typeset olddir="$1"
2004 typeset newdir="$2"
2005
2006 # Snag new version of file.
2007 rm -f $newdir/$DIR/$F
2008 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2009
2010 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2011 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2012 else
2013 # Get the parent's version of the file.
2014 svn status $CWS/$DIR/$F | read stat file
2015 if [[ $stat != "A" ]]; then
2016 svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2017 fi
2018 fi
2019 }
2020
2021 function build_old_new_unknown
2022 {
2023 typeset olddir="$1"
2024 typeset newdir="$2"
2025
2026 #
2027 # Snag new version of file.
2028 #
2029 rm -f $newdir/$DIR/$F
2030 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2031
2032 #
2033 # Snag the parent's version of the file.
2034 #
2035 if [[ -f $PWS/$PDIR/$PF ]]; then
2036 rm -f $olddir/$PDIR/$PF
2037 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2038 fi
2039 }
2040
2041 function build_old_new
2042 {
2043 typeset WDIR=$1
2044 typeset PWS=$2
2045 typeset PDIR=$3
2046 typeset PF=$4
2047 typeset CWS=$5
2048 typeset DIR=$6
2049 typeset F=$7
2050
2051 typeset olddir="$WDIR/raw_files/old"
2052 typeset newdir="$WDIR/raw_files/new"
2053
2054 mkdir -p $olddir/$PDIR
2055 mkdir -p $newdir/$DIR
2056
2057 if [[ $SCM_MODE == "teamware" ]]; then
2058 build_old_new_teamware "$olddir" "$newdir"
2059 elif [[ $SCM_MODE == "mercurial" ]]; then
2060 build_old_new_mercurial "$olddir" "$newdir"
2061 elif [[ $SCM_MODE == "subversion" ]]; then
2062 build_old_new_subversion "$olddir" "$newdir"
2063 elif [[ $SCM_MODE == "unknown" ]]; then
2064 build_old_new_unknown "$olddir" "$newdir"
2065 fi
2066
2067 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2068 print "*** Error: file not in parent or child"
2069 return 1
2070 fi
2071 return 0
2072 }
2073
2074
2075 #
2076 # Usage message.
2077 #
2078 function usage
2079 {
2080 print 'Usage:\twebrev [common-options]
2081 webrev [common-options] ( <file> | - )
2082 webrev [common-options] -w <wx file>
2083
2084 Options:
2085 -C <filename>: Use <filename> for the information tracking configuration.
2086 -D: delete remote webrev
2087 -i <filename>: Include <filename> in the index.html file.
2088 -I <filename>: Use <filename> for the information tracking registry.
2089 -n: do not generate the webrev (useful with -U)
2090 -O: Print bugids/arc cases suitable for OpenSolaris.
2091 -o <outdir>: Output webrev to specified directory.
2092 -p <compare-against>: Use specified parent wkspc or basis for comparison
2093 -t <remote_target>: Specify remote destination for webrev upload
2094 -U: upload the webrev to remote destination
2095 -w <wxfile>: Use specified wx active file.
2096
2097 Environment:
2098 WDIR: Control the output directory.
2099 WEBREV_TRASH_DIR: Set directory for webrev delete.
2100
2101 SCM Specific Options:
2102 TeamWare: webrev [common-options] -l [arguments to 'putback']
2103
2104 SCM Environment:
2105 CODEMGR_WS: Workspace location.
2106 CODEMGR_PARENT: Parent workspace location.
2107 '
2108
2109 exit 2
2110 }
2111
2112 #
2113 #
2114 # Main program starts here
2115 #
2116 #
2117
2118 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2119
2120 set +o noclobber
2121
2122 PATH=$(dirname $(whence $0)):$PATH
2123
2124 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2125 [[ -z $WX ]] && WX=`look_for_prog wx`
2126 [[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
2127 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2128 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2129 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2130 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2131 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2132 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2133 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2134 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2135 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2136 [[ -z $SCP ]] && SCP=`look_for_prog scp`
2137 [[ -z $SED ]] && SED=`look_for_prog sed`
2138 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2139 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2140 [[ -z $GREP ]] && GREP=`look_for_prog grep`
2141 [[ -z $FIND ]] && FIND=`look_for_prog find`
2142
2143 # set name of trash directory for remote webrev deletion
2144 TRASH_DIR=".trash"
2145 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2146
2147 if [[ ! -x $PERL ]]; then
2148 print -u2 "Error: No perl interpreter found. Exiting."
2149 exit 1
2150 fi
2151
2152 if [[ ! -x $WHICH_SCM ]]; then
2153 print -u2 "Error: Could not find which_scm. Exiting."
2154 exit 1
2155 fi
2156
2157 #
2158 # These aren't fatal, but we want to note them to the user.
2159 # We don't warn on the absence of 'wx' until later when we've
2160 # determined that we actually need to try to invoke it.
2161 #
2162 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2163 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2164 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2165
2166 # Declare global total counters.
2167 integer TOTL TINS TDEL TMOD TUNC
2168
2169 # default remote host for upload/delete
2170 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2171 # prefixes for upload targets
2172 typeset -r rsync_prefix="rsync://"
2173 typeset -r ssh_prefix="ssh://"
2174
2175 Cflag=
2176 Dflag=
2177 flist_mode=
2178 flist_file=
2179 iflag=
2180 Iflag=
2181 lflag=
2182 Nflag=
2183 nflag=
2184 Oflag=
2185 oflag=
2186 pflag=
2187 tflag=
2188 uflag=
2189 Uflag=
2190 wflag=
2191 remote_target=
2192
2193 #
2194 # NOTE: when adding/removing options it is necessary to sync the list
2195 # with usr/src/tools/onbld/hgext/cdm.py
2196 #
2197 while getopts "C:Di:I:lnNo:Op:t:Uw" opt
2198 do
2199 case $opt in
2200 C) Cflag=1
2201 ITSCONF=$OPTARG;;
2202
2203 D) Dflag=1;;
2204
2205 i) iflag=1
2206 INCLUDE_FILE=$OPTARG;;
2207
2208 I) Iflag=1
2209 ITSREG=$OPTARG;;
2210
2211 #
2212 # If -l has been specified, we need to abort further options
2213 # processing, because subsequent arguments are going to be
2214 # arguments to 'putback -n'.
2215 #
2216 l) lflag=1
2217 break;;
2218
2219 N) Nflag=1;;
2220
2221 n) nflag=1;;
2222
2223 O) Oflag=1;;
2224
2225 o) oflag=1
2226 WDIR=$OPTARG;;
2227
2228 p) pflag=1
2229 codemgr_parent=$OPTARG;;
2230
2231 t) tflag=1
2232 remote_target=$OPTARG;;
2233
2234 U) Uflag=1;;
2235
2236 w) wflag=1;;
2237
2238 ?) usage;;
2239 esac
2240 done
2241
2242 FLIST=/tmp/$$.flist
2243
2244 if [[ -n $wflag && -n $lflag ]]; then
2245 usage
2246 fi
2247
2248 # more sanity checking
2249 if [[ -n $nflag && -z $Uflag ]]; then
2250 print "it does not make sense to skip webrev generation" \
2251 "without -U"
2252 exit 1
2253 fi
2254
2255 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2256 echo "remote target has to be used only for upload or delete"
2257 exit 1
2258 fi
2259
2260 #
2261 # For the invocation "webrev -n -U" with no other options, webrev will assume
2262 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2263 # $(basename ${CWS}). So we need to get CWS set before we skip any remaining
2264 # logic.
2265 #
2266 $WHICH_SCM | read SCM_MODE junk || exit 1
2267 if [[ $SCM_MODE == "teamware" ]]; then
2268 #
2269 # Teamware priorities:
2270 # 1. CODEMGR_WS from the environment
2271 # 2. workspace name
2272 #
2273 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && codemgr_ws=$CODEMGR_WS
2274 if [[ -n $codemgr_ws && ! -d $codemgr_ws ]]; then
2275 print -u2 "$codemgr_ws: no such workspace"
2276 exit 1
2277 fi
2278 [[ -z $codemgr_ws ]] && codemgr_ws=$(workspace name)
2279 codemgr_ws=$(cd $codemgr_ws;print $PWD)
2280 CODEMGR_WS=$codemgr_ws
2281 CWS=$codemgr_ws
2282 elif [[ $SCM_MODE == "mercurial" ]]; then
2283 #
2284 # Mercurial priorities:
2285 # 1. hg root from CODEMGR_WS environment variable
2286 # 2. hg root from directory of invocation
2287 #
2288 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2289 codemgr_ws=$(hg root -R $CODEMGR_WS 2>/dev/null)
2290 [[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null)
2291 CWS=$codemgr_ws
2292 elif [[ $SCM_MODE == "subversion" ]]; then
2293 #
2294 # Subversion priorities:
2295 # 1. CODEMGR_WS from environment
2296 # 2. Relative path from current directory to SVN repository root
2297 #
2298 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2299 CWS=$CODEMGR_WS
2300 else
2301 svn info | while read line; do
2302 if [[ $line == "URL: "* ]]; then
2303 url=${line#URL: }
2304 elif [[ $line == "Repository Root: "* ]]; then
2305 repo=${line#Repository Root: }
2306 fi
2307 done
2308
2309 rel=${url#$repo}
2310 CWS=${PWD%$rel}
2311 fi
2312 fi
2313
2314 #
2315 # If no SCM has been determined, take either the environment setting
2316 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2317 #
2318 if [[ -z ${CWS} ]]; then
2319 CWS=${CODEMGR_WS:-.}
2320 fi
2321
2322
2323
2324 #
2325 # If the command line options indicate no webrev generation, either
2326 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2327 # ton of logic we can skip.
2328 #
2329 # Instead of increasing indentation, we intentionally leave this loop
2330 # body open here, and exit via break from multiple points within.
2331 # Search for DO_EVERYTHING below to find the break points and closure.
2332 #
2333 for do_everything in 1; do
2334
2335 # DO_EVERYTHING: break point
2336 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2337 break
2338 fi
2339
2340 #
2341 # If this manually set as the parent, and it appears to be an earlier webrev,
2342 # then note that fact and set the parent to the raw_files/new subdirectory.
2343 #
2344 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2345 parent_webrev="$codemgr_parent"
2346 codemgr_parent="$codemgr_parent/raw_files/new"
2347 fi
2348
2349 if [[ -z $wflag && -z $lflag ]]; then
2350 shift $(($OPTIND - 1))
2351
2352 if [[ $1 == "-" ]]; then
2353 cat > $FLIST
2354 flist_mode="stdin"
2355 flist_done=1
2356 shift
2357 elif [[ -n $1 ]]; then
2358 if [[ ! -r $1 ]]; then
2359 print -u2 "$1: no such file or not readable"
2360 usage
2361 fi
2362 cat $1 > $FLIST
2363 flist_mode="file"
2364 flist_file=$1
2365 flist_done=1
2366 shift
2367 else
2368 flist_mode="auto"
2369 fi
2370 fi
2371
2372 #
2373 # Before we go on to further consider -l and -w, work out which SCM we think
2374 # is in use.
2375 #
2376 case "$SCM_MODE" in
2377 teamware|mercurial|subversion)
2378 ;;
2379 unknown)
2380 if [[ $flist_mode == "auto" ]]; then
2381 print -u2 "Unable to determine SCM in use and file list not specified"
2382 print -u2 "See which_scm(1) for SCM detection information."
2383 exit 1
2384 fi
2385 ;;
2386 *)
2387 if [[ $flist_mode == "auto" ]]; then
2388 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2389 exit 1
2390 fi
2391 ;;
2392 esac
2393
2394 print -u2 " SCM detected: $SCM_MODE"
2395
2396 if [[ -n $lflag ]]; then
2397 #
2398 # If the -l flag is given instead of the name of a file list,
2399 # then generate the file list by extracting file names from a
2400 # putback -n.
2401 #
2402 shift $(($OPTIND - 1))
2403 if [[ $SCM_MODE == "teamware" ]]; then
2404 flist_from_teamware "$*"
2405 else
2406 print -u2 -- "Error: -l option only applies to TeamWare"
2407 exit 1
2408 fi
2409 flist_done=1
2410 shift $#
2411 elif [[ -n $wflag ]]; then
2412 #
2413 # If the -w is given then assume the file list is in Bonwick's "wx"
2414 # command format, i.e. pathname lines alternating with SCCS comment
2415 # lines with blank lines as separators. Use the SCCS comments later
2416 # in building the index.html file.
2417 #
2418 shift $(($OPTIND - 1))
2419 wxfile=$1
2420 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2421 if [[ -r $CODEMGR_WS/wx/active ]]; then
2422 wxfile=$CODEMGR_WS/wx/active
2423 fi
2424 fi
2425
2426 [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2427 "be auto-detected (check \$CODEMGR_WS)" && exit 1
2428
2429 if [[ ! -r $wxfile ]]; then
2430 print -u2 "$wxfile: no such file or not readable"
2431 usage
2432 fi
2433
2434 print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2435 flist_from_wx $wxfile
2436 flist_done=1
2437 if [[ -n "$*" ]]; then
2438 shift
2439 fi
2440 elif [[ $flist_mode == "stdin" ]]; then
2441 print -u2 " File list from: standard input"
2442 elif [[ $flist_mode == "file" ]]; then
2443 print -u2 " File list from: $flist_file"
2444 fi
2445
2446 if [[ $# -gt 0 ]]; then
2447 print -u2 "WARNING: unused arguments: $*"
2448 fi
2449
2450 #
2451 # Before we entered the DO_EVERYTHING loop, we should have already set CWS
2452 # and CODEMGR_WS as needed. Here, we set the parent workspace.
2453 #
2454
2455 if [[ $SCM_MODE == "teamware" ]]; then
2456
2457 #
2458 # Teamware priorities:
2459 #
2460 # 1) via -p command line option
2461 # 2) in the user environment
2462 # 3) in the flist
2463 # 4) automatically based on the workspace
2464 #
2465
2466 #
2467 # For 1, codemgr_parent will already be set. Here's 2:
2468 #
2469 [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]] && \
2470 codemgr_parent=$CODEMGR_PARENT
2471 if [[ -n $codemgr_parent && ! -d $codemgr_parent ]]; then
2472 print -u2 "$codemgr_parent: no such directory"
2473 exit 1
2474 fi
2475
2476 #
2477 # If we're in auto-detect mode and we haven't already gotten the file
2478 # list, then see if we can get it by probing for wx.
2479 #
2480 if [[ -z $flist_done && $flist_mode == "auto" && -n $codemgr_ws ]]; then
2481 if [[ ! -x $WX ]]; then
2482 print -u2 "WARNING: wx not found!"
2483 fi
2484
2485 #
2486 # We need to use wx list -w so that we get renamed files, etc.
2487 # but only if a wx active file exists-- otherwise wx will
2488 # hang asking us to initialize our wx information.
2489 #
2490 if [[ -x $WX && -f $codemgr_ws/wx/active ]]; then
2491 print -u2 " File list from: 'wx list -w' ... \c"
2492 $WX list -w > $FLIST
2493 $WX comments > /tmp/$$.wx_comments
2494 wxfile=/tmp/$$.wx_comments
2495 print -u2 "done"
2496 flist_done=1
2497 fi
2498 fi
2499
2500 #
2501 # If by hook or by crook we've gotten a file list by now (perhaps
2502 # from the command line), eval it to extract environment variables from
2503 # it: This is method 3 for finding the parent.
2504 #
2505 if [[ -z $flist_done ]]; then
2506 flist_from_teamware
2507 fi
2508 env_from_flist
2509
2510 #
2511 # (4) If we still don't have a value for codemgr_parent, get it
2512 # from workspace.
2513 #
2514 [[ -z $codemgr_parent ]] && codemgr_parent=`workspace parent`
2515 if [[ ! -d $codemgr_parent ]]; then
2516 print -u2 "$CODEMGR_PARENT: no such parent workspace"
2517 exit 1
2518 fi
2519
2520 PWS=$codemgr_parent
2521
2522 [[ -n $parent_webrev ]] && RWS=$(workspace parent $CWS)
2523
2524 elif [[ $SCM_MODE == "mercurial" ]]; then
2525 #
2526 # Parent can either be specified with -p
2527 # Specified with CODEMGR_PARENT in the environment
2528 # or taken from hg's default path.
2529 #
2530
2531 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2532 codemgr_parent=$CODEMGR_PARENT
2533 fi
2534
2535 if [[ -z $codemgr_parent ]]; then
2536 codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
2537 fi
2538
2539 CWS_REV=`hg parent -R $codemgr_ws --template '{node|short}' 2>/dev/null`
2540 PWS=$codemgr_parent
2541
2542 #
2543 # If the parent is a webrev, we want to do some things against
2544 # the natural workspace parent (file list, comments, etc)
2545 #
2546 if [[ -n $parent_webrev ]]; then
2547 real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
2548 else
2549 real_parent=$PWS
2550 fi
2551
2552 #
2553 # If hg-active exists, then we run it. In the case of no explicit
2554 # flist given, we'll use it for our comments. In the case of an
2555 # explicit flist given we'll try to use it for comments for any
2556 # files mentioned in the flist.
2557 #
2558 if [[ -z $flist_done ]]; then
2559 flist_from_mercurial $CWS $real_parent
2560 flist_done=1
2561 fi
2562
2563 #
2564 # If we have a file list now, pull out any variables set
2565 # therein. We do this now (rather than when we possibly use
2566 # hg-active to find comments) to avoid stomping specifications
2567 # in the user-specified flist.
2568 #
2569 if [[ -n $flist_done ]]; then
2570 env_from_flist
2571 fi
2572
2573 #
2574 # Only call hg-active if we don't have a wx formatted file already
2575 #
2576 if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2577 print " Comments from: hg-active -p $real_parent ...\c"
2578 hg_active_wxfile $CWS $real_parent
2579 print " Done."
2580 fi
2581
2582 #
2583 # At this point we must have a wx flist either from hg-active,
2584 # or in general. Use it to try and find our parent revision,
2585 # if we don't have one.
2586 #
2587 if [[ -z $HG_PARENT ]]; then
2588 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2589 fi
2590
2591 #
2592 # If we still don't have a parent, we must have been given a
2593 # wx-style active list with no HG_PARENT specification, run
2594 # hg-active and pull an HG_PARENT out of it, ignore the rest.
2595 #
2596 if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2597 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2598 eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2599 elif [[ -z $HG_PARENT ]]; then
2600 print -u2 "Error: Cannot discover parent revision"
2601 exit 1
2602 fi
2603 elif [[ $SCM_MODE == "subversion" ]]; then
2604
2605 #
2606 # We only will have a real parent workspace in the case one
2607 # was specified (be it an older webrev, or another checkout).
2608 #
2609 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2610
2611 if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2612 flist_from_subversion $CWS $OLDPWD
2613 fi
2614 else
2615 if [[ $SCM_MODE == "unknown" ]]; then
2616 print -u2 " Unknown type of SCM in use"
2617 else
2618 print -u2 " Unsupported SCM in use: $SCM_MODE"
2619 fi
2620
2621 env_from_flist
2622
2623 if [[ -z $CODEMGR_WS ]]; then
2624 print -u2 "SCM not detected/supported and CODEMGR_WS not specified"
2625 exit 1
2626 fi
2627
2628 if [[ -z $CODEMGR_PARENT ]]; then
2629 print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified"
2630 exit 1
2631 fi
2632
2633 CWS=$CODEMGR_WS
2634 PWS=$CODEMGR_PARENT
2635 fi
2636
2637 #
2638 # If the user didn't specify a -i option, check to see if there is a
2639 # webrev-info file in the workspace directory.
2640 #
2641 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2642 iflag=1
2643 INCLUDE_FILE="$CWS/webrev-info"
2644 fi
2645
2646 if [[ -n $iflag ]]; then
2647 if [[ ! -r $INCLUDE_FILE ]]; then
2648 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2649 "not readable."
2650 exit 1
2651 else
2652 #
2653 # $INCLUDE_FILE may be a relative path, and the script alters
2654 # PWD, so we just stash a copy in /tmp.
2655 #
2656 cp $INCLUDE_FILE /tmp/$$.include
2657 fi
2658 fi
2659
2660 # DO_EVERYTHING: break point
2661 if [[ -n $Nflag ]]; then
2662 break
2663 fi
2664
2665 typeset -A itsinfo
2666 typeset -r its_sed_script=/tmp/$$.its_sed
2667 valid_prefixes=
2668 if [[ -z $nflag ]]; then
2669 DEFREGFILE="$(dirname $(whence $0))/../etc/its.reg"
2670 if [[ -n $Iflag ]]; then
2671 REGFILE=$ITSREG
2672 elif [[ -r $HOME/.its.reg ]]; then
2673 REGFILE=$HOME/.its.reg
2674 else
2675 REGFILE=$DEFREGFILE
2676 fi
2677 if [[ ! -r $REGFILE ]]; then
2678 print "ERROR: Unable to read database registry file $REGFILE"
2679 exit 1
2680 elif [[ $REGFILE != $DEFREGFILE ]]; then
2681 print " its.reg from: $REGFILE"
2682 fi
2683
2684 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do
2685
2686 name=${LINE%%=*}
2687 value="${LINE#*=}"
2688
2689 if [[ $name == PREFIX ]]; then
2690 p=${value}
2691 valid_prefixes="${p} ${valid_prefixes}"
2692 else
2693 itsinfo["${p}_${name}"]="${value}"
2694 fi
2695 done
2696
2697
2698 DEFCONFFILE="$(dirname $(whence $0))/../etc/its.conf"
2699 CONFFILES=$DEFCONFFILE
2700 if [[ -r $HOME/.its.conf ]]; then
2701 CONFFILES="${CONFFILES} $HOME/.its.conf"
2702 fi
2703 if [[ -n $Cflag ]]; then
2704 CONFFILES="${CONFFILES} ${ITSCONF}"
2705 fi
2706 its_domain=
2707 its_priority=
2708 for cf in ${CONFFILES}; do
2709 if [[ ! -r $cf ]]; then
2710 print "ERROR: Unable to read database configuration file $cf"
2711 exit 1
2712 elif [[ $cf != $DEFCONFFILE ]]; then
2713 print " its.conf: reading $cf"
2714 fi
2715 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do
2716 eval "${LINE}"
2717 done
2718 done
2719
2720 #
2721 # If an information tracking system is explicitly identified by prefix,
2722 # we want to disregard the specified priorities and resolve it accordingly.
2723 #
2724 # To that end, we'll build a sed script to do each valid prefix in turn.
2725 #
2726 for p in ${valid_prefixes}; do
2727 #
2728 # When an informational URL was provided, translate it to a
2729 # hyperlink. When omitted, simply use the prefix text.
2730 #
2731 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2732 itsinfo["${p}_INFO"]=${p}
2733 else
2734 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2735 fi
2736
2737 #
2738 # Assume that, for this invocation of webrev, all references
2739 # to this information tracking system should resolve through
2740 # the same URL.
2741 #
2742 # If the caller specified -O, then always use EXTERNAL_URL.
2743 #
2744 # Otherwise, look in the list of domains for a matching
2745 # INTERNAL_URL.
2746 #
2747 [[ -z $Oflag ]] && for d in ${its_domain}; do
2748 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2749 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2750 break
2751 fi
2752 done
2753 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2754 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2755 fi
2756
2757 #
2758 # Turn the destination URL into a hyperlink
2759 #
2760 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2761
2762 print "/^${p}[ ]/ {
2763 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2764 s;^${p};${itsinfo[${p}_INFO]};
2765 }" >> ${its_sed_script}
2766 done
2767
2768 #
2769 # The previous loop took care of explicit specification. Now use
2770 # the configured priorities to attempt implicit translations.
2771 #
2772 for p in ${its_priority}; do
2773 print "/^${itsinfo[${p}_REGEX]}[ ]/ {
2774 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2775 }" >> ${its_sed_script}
2776 done
2777 fi
2778
2779 #
2780 # Search for DO_EVERYTHING above for matching "for" statement
2781 # and explanation of this terminator.
2782 #
2783 done
2784
2785 #
2786 # Output directory.
2787 #
2788 WDIR=${WDIR:-$CWS/webrev}
2789
2790 #
2791 # Name of the webrev, derived from the workspace name or output directory;
2792 # in the future this could potentially be an option.
2793 #
2794 if [[ -n $oflag ]]; then
2795 WNAME=${WDIR##*/}
2796 else
2797 WNAME=${CWS##*/}
2798 fi
2799
2800 # Make sure remote target is well formed for remote upload/delete.
2801 if [[ -n $Dflag || -n $Uflag ]]; then
2802 #
2803 # If remote target is not specified, build it from scratch using
2804 # the default values.
2805 #
2806 if [[ -z $tflag ]]; then
2807 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2808 else
2809 #
2810 # Check upload target prefix first.
2811 #
2812 if [[ "${remote_target}" != ${rsync_prefix}* &&
2813 "${remote_target}" != ${ssh_prefix}* ]]; then
2814 print "ERROR: invalid prefix of upload URI" \
2815 "($remote_target)"
2816 exit 1
2817 fi
2818 #
2819 # If destination specification is not in the form of
2820 # host_spec:remote_dir then assume it is just remote hostname
2821 # and append a colon and destination directory formed from
2822 # local webrev directory name.
2823 #
2824 typeset target_no_prefix=${remote_target##*://}
2825 if [[ ${target_no_prefix} == *:* ]]; then
2826 if [[ "${remote_target}" == *: ]]; then
2827 remote_target=${remote_target}${WNAME}
2828 fi
2829 else
2830 if [[ ${target_no_prefix} == */* ]]; then
2831 print "ERROR: badly formed upload URI" \
2832 "($remote_target)"
2833 exit 1
2834 else
2835 remote_target=${remote_target}:${WNAME}
2836 fi
2837 fi
2838 fi
2839
2840 #
2841 # Strip trailing slash. Each upload method will deal with directory
2842 # specification separately.
2843 #
2844 remote_target=${remote_target%/}
2845 fi
2846
2847 #
2848 # Option -D by itself (option -U not present) implies no webrev generation.
2849 #
2850 if [[ -z $Uflag && -n $Dflag ]]; then
2851 delete_webrev 1 1
2852 exit $?
2853 fi
2854
2855 #
2856 # Do not generate the webrev, just upload it or delete it.
2857 #
2858 if [[ -n $nflag ]]; then
2859 if [[ -n $Dflag ]]; then
2860 delete_webrev 1 1
2861 (( $? == 0 )) || exit $?
2862 fi
2863 if [[ -n $Uflag ]]; then
2864 upload_webrev
2865 exit $?
2866 fi
2867 fi
2868
2869 if [ "${WDIR%%/*}" ]; then
2870 WDIR=$PWD/$WDIR
2871 fi
2872
2873 if [[ ! -d $WDIR ]]; then
2874 mkdir -p $WDIR
2875 (( $? != 0 )) && exit 1
2876 fi
2877
2878 #
2879 # Summarize what we're going to do.
2880 #
2881 if [[ -n $CWS_REV ]]; then
2882 print " Workspace: $CWS (at $CWS_REV)"
2883 else
2884 print " Workspace: $CWS"
2885 fi
2886 if [[ -n $parent_webrev ]]; then
2887 print "Compare against: webrev at $parent_webrev"
2888 else
2889 if [[ -n $HG_PARENT ]]; then
2890 hg_parent_short=`echo $HG_PARENT \
2891 | $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'`
2892 print "Compare against: $PWS (at $hg_parent_short)"
2893 else
2894 print "Compare against: $PWS"
2895 fi
2896 fi
2897
2898 [[ -n $INCLUDE_FILE ]] && print " Including: $INCLUDE_FILE"
2899 print " Output to: $WDIR"
2900
2901 #
2902 # Save the file list in the webrev dir
2903 #
2904 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
2905
2906 rm -f $WDIR/$WNAME.patch
2907 rm -f $WDIR/$WNAME.ps
2908 rm -f $WDIR/$WNAME.pdf
2909
2910 touch $WDIR/$WNAME.patch
2911
2912 print " Output Files:"
2913
2914 #
2915 # Clean up the file list: Remove comments, blank lines and env variables.
2916 #
2917 $SED -e "s/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean
2918 FLIST=/tmp/$$.flist.clean
2919
2920 #
2921 # For Mercurial, create a cache of manifest entries.
2922 #
2923 if [[ $SCM_MODE == "mercurial" ]]; then
2924 #
2925 # Transform the FLIST into a temporary sed script that matches
2926 # relevant entries in the Mercurial manifest as follows:
2927 # 1) The script will be used against the parent revision manifest,
2928 # so for FLIST lines that have two filenames (a renamed file)
2929 # keep only the old name.
2930 # 2) Escape all forward slashes the filename.
2931 # 3) Change the filename into another sed command that matches
2932 # that file in "hg manifest -v" output: start of line, three
2933 # octal digits for file permissions, space, a file type flag
2934 # character, space, the filename, end of line.
2935 #
2936 SEDFILE=/tmp/$$.manifest.sed
2937 $SED '
2938 s#^[^ ]* ##
2939 s#/#\\\/#g
2940 s#^.*$#/^... . &$/p#
2941 ' < $FLIST > $SEDFILE
2942
2943 #
2944 # Apply the generated script to the output of "hg manifest -v"
2945 # to get the relevant subset for this webrev.
2946 #
2947 HG_PARENT_MANIFEST=/tmp/$$.manifest
2948 hg -R $CWS manifest -v -r $HG_PARENT |
2949 $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
2950 fi
2951
2952 #
2953 # First pass through the files: generate the per-file webrev HTML-files.
2954 #
2955 cat $FLIST | while read LINE
2956 do
2957 set - $LINE
2958 P=$1
2959
2960 #
2961 # Normally, each line in the file list is just a pathname of a
2962 # file that has been modified or created in the child. A file
2963 # that is renamed in the child workspace has two names on the
2964 # line: new name followed by the old name.
2965 #
2966 oldname=""
2967 oldpath=""
2968 rename=
2969 if [[ $# -eq 2 ]]; then
2970 PP=$2 # old filename
2971 oldname=" (was $PP)"
2972 oldpath="$PP"
2973 rename=1
2974 PDIR=${PP%/*}
2975 if [[ $PDIR == $PP ]]; then
2976 PDIR="." # File at root of workspace
2977 fi
2978
2979 PF=${PP##*/}
2980
2981 DIR=${P%/*}
2982 if [[ $DIR == $P ]]; then
2983 DIR="." # File at root of workspace
2984 fi
2985
2986 F=${P##*/}
2987
2988 else
2989 DIR=${P%/*}
2990 if [[ "$DIR" == "$P" ]]; then
2991 DIR="." # File at root of workspace
2992 fi
2993
2994 F=${P##*/}
2995
2996 PP=$P
2997 PDIR=$DIR
2998 PF=$F
2999 fi
3000
3001 COMM=`getcomments html $P $PP`
3002
3003 print "\t$P$oldname\n\t\t\c"
3004
3005 # Make the webrev mirror directory if necessary
3006 mkdir -p $WDIR/$DIR
3007
3008 #
3009 # If we're in OpenSolaris mode, we enforce a minor policy:
3010 # help to make sure the reviewer doesn't accidentally publish
3011 # source which is in usr/closed/* or deleted_files/usr/closed/*
3012 #
3013 if [[ -n "$Oflag" ]]; then
3014 pclosed=${P##usr/closed/}
3015 pdeleted=${P##deleted_files/usr/closed/}
3016 if [[ "$pclosed" != "$P" || "$pdeleted" != "$P" ]]; then
3017 print "*** Omitting closed source for OpenSolaris" \
3018 "mode review"
3019 continue
3020 fi
3021 fi
3022
3023 #
3024 # We stash old and new files into parallel directories in $WDIR
3025 # and do our diffs there. This makes it possible to generate
3026 # clean looking diffs which don't have absolute paths present.
3027 #
3028
3029 build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3030 continue
3031
3032 #
3033 # Keep the old PWD around, so we can safely switch back after
3034 # diff generation, such that build_old_new runs in a
3035 # consistent environment.
3036 #
3037 OWD=$PWD
3038 cd $WDIR/raw_files
3039 ofile=old/$PDIR/$PF
3040 nfile=new/$DIR/$F
3041
3042 mv_but_nodiff=
3043 cmp $ofile $nfile > /dev/null 2>&1
3044 if [[ $? == 0 && $rename == 1 ]]; then
3045 mv_but_nodiff=1
3046 fi
3047
3048 #
3049 # If we have old and new versions of the file then run the appropriate
3050 # diffs. This is complicated by a couple of factors:
3051 #
3052 # - renames must be handled specially: we emit a 'remove'
3053 # diff and an 'add' diff
3054 # - new files and deleted files must be handled specially
3055 # - Solaris patch(1m) can't cope with file creation
3056 # (and hence renames) as of this writing.
3057 # - To make matters worse, gnu patch doesn't interpret the
3058 # output of Solaris diff properly when it comes to
3059 # adds and deletes. We need to do some "cleansing"
3060 # transformations:
3061 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@
3062 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@
3063 #
3064 cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3065 cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3066
3067 rm -f $WDIR/$DIR/$F.patch
3068 if [[ -z $rename ]]; then
3069 if [ ! -f "$ofile" ]; then
3070 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3071 > $WDIR/$DIR/$F.patch
3072 elif [ ! -f "$nfile" ]; then
3073 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3074 > $WDIR/$DIR/$F.patch
3075 else
3076 diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3077 fi
3078 else
3079 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3080 > $WDIR/$DIR/$F.patch
3081
3082 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3083 >> $WDIR/$DIR/$F.patch
3084
3085 fi
3086
3087 #
3088 # Tack the patch we just made onto the accumulated patch for the
3089 # whole wad.
3090 #
3091 cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3092
3093 print " patch\c"
3094
3095 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3096
3097 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3098 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3099 > $WDIR/$DIR/$F.cdiff.html
3100 print " cdiffs\c"
3101
3102 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3103 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3104 > $WDIR/$DIR/$F.udiff.html
3105
3106 print " udiffs\c"
3107
3108 if [[ -x $WDIFF ]]; then
3109 $WDIFF -c "$COMM" \
3110 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3111 $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3112 if [[ $? -eq 0 ]]; then
3113 print " wdiffs\c"
3114 else
3115 print " wdiffs[fail]\c"
3116 fi
3117 fi
3118
3119 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3120 > $WDIR/$DIR/$F.sdiff.html
3121 print " sdiffs\c"
3122
3123 print " frames\c"
3124
3125 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3126
3127 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3128
3129 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3130 # renamed file: may also have differences
3131 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3132 elif [[ -f $nfile ]]; then
3133 # new file: count added lines
3134 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3135 elif [[ -f $ofile ]]; then
3136 # old file: count deleted lines
3137 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3138 fi
3139
3140 #
3141 # Now we generate the postscript for this file. We generate diffs
3142 # only in the event that there is delta, or the file is new (it seems
3143 # tree-killing to print out the contents of deleted files).
3144 #
3145 if [[ -f $nfile ]]; then
3146 ocr=$ofile
3147 [[ ! -f $ofile ]] && ocr=/dev/null
3148
3149 if [[ -z $mv_but_nodiff ]]; then
3150 textcomm=`getcomments text $P $PP`
3151 if [[ -x $CODEREVIEW ]]; then
3152 $CODEREVIEW -y "$textcomm" \
3153 -e $ocr $nfile \
3154 > /tmp/$$.psfile 2>/dev/null &&
3155 cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3156 if [[ $? -eq 0 ]]; then
3157 print " ps\c"
3158 else
3159 print " ps[fail]\c"
3160 fi
3161 fi
3162 fi
3163 fi
3164
3165 if [[ -f $ofile ]]; then
3166 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3167 print " old\c"
3168 fi
3169
3170 if [[ -f $nfile ]]; then
3171 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3172 print " new\c"
3173 fi
3174
3175 cd $OWD
3176
3177 print
3178 done
3179
3180 frame_nav_js > $WDIR/ancnav.js
3181 frame_navigation > $WDIR/ancnav.html
3182
3183 if [[ ! -f $WDIR/$WNAME.ps ]]; then
3184 print " Generating PDF: Skipped: no output available"
3185 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3186 print " Generating PDF: \c"
3187 fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3188 print "Done."
3189 else
3190 print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3191 fi
3192
3193 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3194 # delete it - prevent accidental publishing of closed source
3195
3196 if [[ -n "$Oflag" ]]; then
3197 $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3198 fi
3199
3200 # Now build the index.html file that contains
3201 # links to the source files and their diffs.
3202
3203 cd $CWS
3204
3205 # Save total changed lines for Code Inspection.
3206 print "$TOTL" > $WDIR/TotalChangedLines
3207
3208 print " index.html: \c"
3209 INDEXFILE=$WDIR/index.html
3210 exec 3<&1 # duplicate stdout to FD3.
3211 exec 1<&- # Close stdout.
3212 exec > $INDEXFILE # Open stdout to index file.
3213
3214 print "$HTML<head>$STDHEAD"
3215 print "<title>$WNAME</title>"
3216 print "</head>"
3217 print "<body id=\"SUNWwebrev\">"
3218 print "<div class=\"summary\">"
3219 print "<h2>Code Review for $WNAME</h2>"
3220
3221 print "<table>"
3222
3223 #
3224 # Get the preparer's name:
3225 #
3226 # If the SCM detected is Mercurial, and the configuration property
3227 # ui.username is available, use that, but be careful to properly escape
3228 # angle brackets (HTML syntax characters) in the email address.
3229 #
3230 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3231 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3232 #
3233 preparer=
3234 if [[ "$SCM_MODE" == mercurial ]]; then
3235 preparer=`hg showconfig ui.username 2>/dev/null`
3236 if [[ -n "$preparer" ]]; then
3237 preparer="$(echo "$preparer" | html_quote)"
3238 fi
3239 fi
3240 if [[ -z "$preparer" ]]; then
3241 preparer=$(
3242 $PERL -e '
3243 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3244 if ($login) {
3245 $gcos =~ s/\&/ucfirst($login)/e;
3246 printf "%s (%s)\n", $gcos, $login;
3247 } else {
3248 printf "(unknown)\n";
3249 }
3250 ')
3251 fi
3252
3253 print "<tr><th>Prepared by:</th><td>$preparer on `date`</td></tr>"
3254 print "<tr><th>Workspace:</th><td>$CWS"
3255 if [[ -n $CWS_REV ]]; then
3256 print "(at $CWS_REV)"
3257 fi
3258 print "</td></tr>"
3259 print "<tr><th>Compare against:</th><td>"
3260 if [[ -n $parent_webrev ]]; then
3261 print "webrev at $parent_webrev"
3262 else
3263 print "$PWS"
3264 if [[ -n $hg_parent_short ]]; then
3265 print "(at $hg_parent_short)"
3266 fi
3267 fi
3268 print "</td></tr>"
3269 print "<tr><th>Summary of changes:</th><td>"
3270 printCI $TOTL $TINS $TDEL $TMOD $TUNC
3271 print "</td></tr>"
3272
3273 if [[ -f $WDIR/$WNAME.patch ]]; then
3274 wpatch_url="$(print $WNAME.patch | url_encode)"
3275 print "<tr><th>Patch of changes:</th><td>"
3276 print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3277 fi
3278 if [[ -f $WDIR/$WNAME.pdf ]]; then
3279 wpdf_url="$(print $WNAME.pdf | url_encode)"
3280 print "<tr><th>Printable review:</th><td>"
3281 print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3282 fi
3283
3284 if [[ -n "$iflag" ]]; then
3285 print "<tr><th>Author comments:</th><td><div>"
3286 cat /tmp/$$.include
3287 print "</div></td></tr>"
3288 fi
3289 print "</table>"
3290 print "</div>"
3291
3292 #
3293 # Second pass through the files: generate the rest of the index file
3294 #
3295 cat $FLIST | while read LINE
3296 do
3297 set - $LINE
3298 P=$1
3299
3300 if [[ $# == 2 ]]; then
3301 PP=$2
3302 oldname="$PP"
3303 else
3304 PP=$P
3305 oldname=""
3306 fi
3307
3308 mv_but_nodiff=
3309 cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3310 if [[ $? == 0 && -n "$oldname" ]]; then
3311 mv_but_nodiff=1
3312 fi
3313
3314 DIR=${P%/*}
3315 if [[ $DIR == $P ]]; then
3316 DIR="." # File at root of workspace
3317 fi
3318
3319 # Avoid processing the same file twice.
3320 # It's possible for renamed files to
3321 # appear twice in the file list
3322
3323 F=$WDIR/$P
3324
3325 print "<p>"
3326
3327 # If there's a diffs file, make diffs links
3328
3329 if [[ -f $F.cdiff.html ]]; then
3330 cdiff_url="$(print $P.cdiff.html | url_encode)"
3331 udiff_url="$(print $P.udiff.html | url_encode)"
3332 print "<a href=\"$cdiff_url\">Cdiffs</a>"
3333 print "<a href=\"$udiff_url\">Udiffs</a>"
3334
3335 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3336 wdiff_url="$(print $P.wdiff.html | url_encode)"
3337 print "<a href=\"$wdiff_url\">Wdiffs</a>"
3338 fi
3339
3340 sdiff_url="$(print $P.sdiff.html | url_encode)"
3341 print "<a href=\"$sdiff_url\">Sdiffs</a>"
3342
3343 frames_url="$(print $P.frames.html | url_encode)"
3344 print "<a href=\"$frames_url\">Frames</a>"
3345 else
3346 print " ------ ------ ------"
3347
3348 if [[ -x $WDIFF ]]; then
3349 print " ------"
3350 fi
3351
3352 print " ------"
3353 fi
3354
3355 # If there's an old file, make the link
3356
3357 if [[ -f $F-.html ]]; then
3358 oldfile_url="$(print $P-.html | url_encode)"
3359 print "<a href=\"$oldfile_url\">Old</a>"
3360 else
3361 print " ---"
3362 fi
3363
3364 # If there's an new file, make the link
3365
3366 if [[ -f $F.html ]]; then
3367 newfile_url="$(print $P.html | url_encode)"
3368 print "<a href=\"$newfile_url\">New</a>"
3369 else
3370 print " ---"
3371 fi
3372
3373 if [[ -f $F.patch ]]; then
3374 patch_url="$(print $P.patch | url_encode)"
3375 print "<a href=\"$patch_url\">Patch</a>"
3376 else
3377 print " -----"
3378 fi
3379
3380 if [[ -f $WDIR/raw_files/new/$P ]]; then
3381 rawfiles_url="$(print raw_files/new/$P | url_encode)"
3382 print "<a href=\"$rawfiles_url\">Raw</a>"
3383 else
3384 print " ---"
3385 fi
3386
3387 print "<b>$P</b>"
3388
3389 # For renamed files, clearly state whether or not they are modified
3390 if [[ -n "$oldname" ]]; then
3391 if [[ -n "$mv_but_nodiff" ]]; then
3392 print "<i>(renamed only, was $oldname)</i>"
3393 else
3394 print "<i>(modified and renamed, was $oldname)</i>"
3395 fi
3396 fi
3397
3398 # If there's an old file, but no new file, the file was deleted
3399 if [[ -f $F-.html && ! -f $F.html ]]; then
3400 print " <i>(deleted)</i>"
3401 fi
3402
3403 #
3404 # Check for usr/closed and deleted_files/usr/closed
3405 #
3406 if [ ! -z "$Oflag" ]; then
3407 if [[ $P == usr/closed/* || \
3408 $P == deleted_files/usr/closed/* ]]; then
3409 print " <i>Closed source: omitted from" \
3410 "this review</i>"
3411 fi
3412 fi
3413
3414 print "</p>"
3415 # Insert delta comments
3416
3417 print "<blockquote><pre>"
3418 getcomments html $P $PP
3419 print "</pre>"
3420
3421 # Add additional comments comment
3422
3423 print "<!-- Add comments to explain changes in $P here -->"
3424
3425 # Add count of changes.
3426
3427 if [[ -f $F.count ]]; then
3428 cat $F.count
3429 rm $F.count
3430 fi
3431
3432 if [[ $SCM_MODE == "teamware" ||
3433 $SCM_MODE == "mercurial" ||
3434 $SCM_MODE == "unknown" ]]; then
3435
3436 # Include warnings for important file mode situations:
3437 # 1) New executable files
3438 # 2) Permission changes of any kind
3439 # 3) Existing executable files
3440
3441 old_mode=
3442 if [[ -f $WDIR/raw_files/old/$PP ]]; then
3443 old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3444 fi
3445
3446 new_mode=
3447 if [[ -f $WDIR/raw_files/new/$P ]]; then
3448 new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3449 fi
3450
3451 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3452 print "<span class=\"chmod\">"
3453 print "<p>new executable file: mode $new_mode</p>"
3454 print "</span>"
3455 elif [[ -n "$old_mode" && -n "$new_mode" &&
3456 "$old_mode" != "$new_mode" ]]; then
3457 print "<span class=\"chmod\">"
3458 print "<p>mode change: $old_mode to $new_mode</p>"
3459 print "</span>"
3460 elif [[ "$new_mode" = *[1357]* ]]; then
3461 print "<span class=\"chmod\">"
3462 print "<p>executable file: mode $new_mode</p>"
3463 print "</span>"
3464 fi
3465 fi
3466
3467 print "</blockquote>"
3468 done
3469
3470 print
3471 print
3472 print "<hr></hr>"
3473 print "<p style=\"font-size: small\">"
3474 print "This code review page was prepared using <b>$0</b>."
3475 print "Webrev is maintained by the <a href=\"http://www.opensolaris.org\">"
3476 print "OpenSolaris</a> project. The latest version may be obtained"
3477 print "<a href=\"http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3478 print "</body>"
3479 print "</html>"
3480
3481 exec 1<&- # Close FD 1.
3482 exec 1<&3 # dup FD 3 to restore stdout.
3483 exec 3<&- # close FD 3.
3484
3485 print "Done."
3486
3487 #
3488 # If remote deletion was specified and fails do not continue.
3489 #
3490 if [[ -n $Dflag ]]; then
3491 delete_webrev 1 1
3492 (( $? == 0 )) || exit $?
3493 fi
3494
3495 if [[ -n $Uflag ]]; then
3496 upload_webrev
3497 exit $?
3498 fi