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