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