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).
650 #
651 sdiff_to_html()
652 {
653 diff -b $1 $2 > /tmp/$$.diffs
654
655 TNAME=$3
656 TPATH=$4
657 COMMENT=$5
658
659 #
660 # Now we have the diffs, generate the HTML for the old file.
661 #
662 $AWK '
663 BEGIN {
664 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
665 printf "function removed() "
666 printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
667 printf "function changed() "
668 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
669 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
670 }
671 /^</ {next}
672 /^>/ {next}
673 /^---/ {next}
674
675 {
676 split($1, a, /[cad]/) ;
677 if (index($1, "a")) {
678 if (a[1] == 0) {
679 n = split(a[2], r, /,/);
680 if (n == 1)
681 printf "BEGIN\t\t{sp(1)}\n"
682 else
683 printf "BEGIN\t\t{sp(%d)}\n",\
684 (r[2] - r[1]) + 1
685 next
686 }
687
688 printf "NR==%s\t\t{", a[1]
689 n = split(a[2], r, /,/);
690 s = r[1];
691 if (n == 1)
692 printf "bl();printf \"\\n\"; next}\n"
693 else {
694 n = r[2] - r[1]
695 printf "bl();sp(%d);next}\n",\
696 (r[2] - r[1]) + 1
697 }
698 next
699 }
700 if (index($1, "d")) {
701 n = split(a[1], r, /,/);
702 n1 = r[1]
703 n2 = r[2]
704 if (n == 1)
705 printf "NR==%s\t\t{removed(); next}\n" , n1
706 else
707 printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
708 next
709 }
710 if (index($1, "c")) {
711 n = split(a[1], r, /,/);
712 n1 = r[1]
713 n2 = r[2]
714 final = n2
715 d1 = 0
716 if (n == 1)
717 printf "NR==%s\t\t{changed();" , n1
718 else {
719 d1 = n2 - n1
720 printf "NR==%s,NR==%s\t{changed();" , n1, n2
721 }
722 m = split(a[2], r, /,/);
723 n1 = r[1]
724 n2 = r[2]
725 if (m > 1) {
726 d2 = n2 - n1
727 if (d2 > d1) {
728 if (n > 1) printf "if (NR==%d)", final
729 printf "sp(%d);", d2 - d1
730 }
731 }
732 printf "next}\n" ;
733
734 next
735 }
736 }
737
738 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
739 ' /tmp/$$.diffs > /tmp/$$.file1
740
741 #
742 # Now generate the HTML for the new file
743 #
744 $AWK '
745 BEGIN {
746 printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
747 printf "function new() "
748 printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
749 printf "function changed() "
750 printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
751 printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
752 }
753
754 /^</ {next}
755 /^>/ {next}
756 /^---/ {next}
757
758 {
759 split($1, a, /[cad]/) ;
760 if (index($1, "d")) {
761 if (a[2] == 0) {
762 n = split(a[1], r, /,/);
763 if (n == 1)
764 printf "BEGIN\t\t{sp(1)}\n"
765 else
766 printf "BEGIN\t\t{sp(%d)}\n",\
767 (r[2] - r[1]) + 1
768 next
769 }
770
771 printf "NR==%s\t\t{", a[2]
772 n = split(a[1], r, /,/);
773 s = r[1];
774 if (n == 1)
775 printf "bl();printf \"\\n\"; next}\n"
776 else {
777 n = r[2] - r[1]
778 printf "bl();sp(%d);next}\n",\
779 (r[2] - r[1]) + 1
780 }
781 next
782 }
783 if (index($1, "a")) {
784 n = split(a[2], r, /,/);
785 n1 = r[1]
786 n2 = r[2]
787 if (n == 1)
788 printf "NR==%s\t\t{new() ; next}\n" , n1
789 else
790 printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
791 next
792 }
793 if (index($1, "c")) {
794 n = split(a[2], r, /,/);
795 n1 = r[1]
796 n2 = r[2]
797 final = n2
798 d2 = 0;
799 if (n == 1) {
800 final = n1
801 printf "NR==%s\t\t{changed();" , n1
802 } else {
803 d2 = n2 - n1
804 printf "NR==%s,NR==%s\t{changed();" , n1, n2
805 }
806 m = split(a[1], r, /,/);
807 n1 = r[1]
808 n2 = r[2]
809 if (m > 1) {
810 d1 = n2 - n1
811 if (d1 > d2) {
812 if (n > 1) printf "if (NR==%d)", final
813 printf "sp(%d);", d1 - d2
814 }
815 }
816 printf "next}\n" ;
817 next
818 }
819 }
820 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
821 ' /tmp/$$.diffs > /tmp/$$.file2
822
823 #
824 # Post-process the HTML files by running them back through $AWK
825 #
826 html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html
827
828 html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html
829
830 #
831 # Now combine into a valid HTML file and side-by-side into a table
832 #
833 print "$HTML<head>$STDHEAD"
834 print "<title>$WNAME Sdiff $TPATH/$TNAME</title>"
835 print "</head><body id=\"SUNWwebrev\">"
836 print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
837 print "<pre>$COMMENT</pre>\n"
838 print "<table><tr valign=\"top\">"
839 print "<td><pre>"
840
841 strip_unchanged /tmp/$$.file1.html
842
843 print "</pre></td><td><pre>"
844
845 strip_unchanged /tmp/$$.file2.html
846
847 print "</pre></td>"
848 print "</tr></table>"
849 print "</body></html>"
850
851 framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
852 "$COMMENT"
853 }
854
855
856 #
857 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
858 #
859 # Expects lefthand and righthand side html files created by sdiff_to_html.
860 # We use insert_anchors() to augment those with HTML navigation anchors,
861 # and then emit the main frame. Content is placed into:
862 #
863 # $WDIR/DIR/$TNAME.lhs.html
864 # $WDIR/DIR/$TNAME.rhs.html
865 # $WDIR/DIR/$TNAME.frames.html
866 #
867 # NOTE: We rely on standard usage of $WDIR and $DIR.
868 #
869 function framed_sdiff
870 {
871 typeset TNAME=$1
872 typeset TPATH=$2
873 typeset lhsfile=$3
874 typeset rhsfile=$4
875 typeset comments=$5
876 typeset RTOP
877
878 # Enable html files to access WDIR via a relative path.
879 RTOP=$(relative_dir $TPATH $WDIR)
880
881 # Make the rhs/lhs files and output the frameset file.
882 print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
883
884 cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
885 <script type="text/javascript" src="${RTOP}ancnav.js"></script>
886 </head>
887 <body id="SUNWwebrev" onkeypress="keypress(event);">
888 <a name="0"></a>
889 <pre>$comments</pre><hr></hr>
890 EOF
891
892 cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
893
894 insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
895 insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
896
897 close='</body></html>'
898
899 print $close >> $WDIR/$DIR/$TNAME.lhs.html
900 print $close >> $WDIR/$DIR/$TNAME.rhs.html
901
902 print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
903 print "<title>$WNAME Framed-Sdiff " \
904 "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
905 cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
906 <frameset rows="*,60">
907 <frameset cols="50%,50%">
908 <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
909 <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
910 </frameset>
911 <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0"
912 marginheight="0" name="nav"></frame>
913 <noframes>
914 <body id="SUNWwebrev">
915 Alas 'frames' webrev requires that your browser supports frames
916 and has the feature enabled.
917 </body>
918 </noframes>
919 </frameset>
920 </html>
921 EOF
922 }
923
924
925 #
926 # fix_postscript
927 #
928 # Merge codereview output files to a single conforming postscript file, by:
929 # - removing all extraneous headers/trailers
930 # - making the page numbers right
931 # - removing pages devoid of contents which confuse some
932 # postscript readers.
933 #
934 # From Casper.
935 #
936 function fix_postscript
937 {
938 infile=$1
939
940 cat > /tmp/$$.crmerge.pl << \EOF
941
942 print scalar(<>); # %!PS-Adobe---
943 print "%%Orientation: Landscape\n";
944
945 $pno = 0;
946 $doprint = 1;
947
948 $page = "";
949
950 while (<>) {
951 next if (/^%%Pages:\s*\d+/);
952
953 if (/^%%Page:/) {
954 if ($pno == 0 || $page =~ /\)S/) {
955 # Header or single page containing text
956 print "%%Page: ? $pno\n" if ($pno > 0);
957 print $page;
958 $pno++;
959 } else {
960 # Empty page, skip it.
961 }
962 $page = "";
963 $doprint = 1;
964 next;
965 }
966
967 # Skip from %%Trailer of one document to Endprolog
968 # %%Page of the next
969 $doprint = 0 if (/^%%Trailer/);
970 $page .= $_ if ($doprint);
971 }
972
973 if ($page =~ /\)S/) {
974 print "%%Page: ? $pno\n";
975 print $page;
976 } else {
977 $pno--;
978 }
979 print "%%Trailer\n%%Pages: $pno\n";
980 EOF
981
982 $PERL /tmp/$$.crmerge.pl < $infile
983 }
984
985
986 #
987 # input_cmd | insert_anchors | output_cmd
988 #
989 # Flag blocks of difference with sequentially numbered invisible
990 # anchors. These are used to drive the frames version of the
991 # sdiffs output.
992 #
993 # NOTE: Anchor zero flags the top of the file irrespective of changes,
994 # an additional anchor is also appended to flag the bottom.
995 #
996 # The script detects changed lines as any line that has a "<span
997 # class=" string embedded (unchanged lines have no class set and are
998 # not part of a <span>. Blank lines (without a sequence number)
999 # are also detected since they flag lines that have been inserted or
1000 # deleted.
1001 #
1002 function insert_anchors
1003 {
1004 $AWK '
1005 function ia() {
1006 printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1007 }
1008
1009 BEGIN {
1010 anc=1;
1011 inblock=1;
1012 printf "<pre>\n";
1013 }
1014 NF == 0 || /^<span class=/ {
1015 if (inblock == 0) {
1016 ia();
1017 inblock=1;
1018 }
1019 print;
1020 next;
1021 }
1022 {
1023 inblock=0;
1024 print;
1025 }
1026 END {
1027 ia();
1028
1029 printf "<b style=\"font-size: large; color: red\">";
1030 printf "--- EOF ---</b>"
1031 for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1032 printf "</pre>"
1033 printf "<form name=\"eof\">";
1034 printf "<input name=\"value\" value=\"%d\" " \
1035 "type=\"hidden\"></input>", anc - 1;
1036 printf "</form>";
1037 }
1038 ' $1
1039 }
1040
1041
1042 #
1043 # relative_dir
1044 #
1045 # Print a relative return path from $1 to $2. For example if
1046 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1047 # this function would print "../../../../".
1048 #
1049 # In the event that $1 is not in $2 a warning is printed to stderr,
1050 # and $2 is returned-- the result of this is that the resulting webrev
1051 # is not relocatable.
1052 #
1053 function relative_dir
1054 {
1055 typeset cur="${1##$2?(/)}"
1056
1057 #
1058 # If the first path was specified absolutely, and it does
1059 # not start with the second path, it's an error.
1060 #
1061 if [[ "$cur" = "/${1#/}" ]]; then
1062 # Should never happen.
1063 print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1064 print -u2 "to \"$2\". Check input paths. Framed webrev "
1065 print -u2 "will not be relocatable!"
1066 print $2
1067 return
1068 fi
1069
1070 #
1071 # This is kind of ugly. The sed script will do the following:
1072 #
1073 # 1. Strip off a leading "." or "./": this is important to get
1074 # the correct arcnav links for files in $WDIR.
1075 # 2. Strip off a trailing "/": this is not strictly necessary,
1076 # but is kind of nice, since it doesn't end up in "//" at
1077 # the end of a relative path.
1078 # 3. Replace all remaining sequences of non-"/" with "..": the
1079 # assumption here is that each dirname represents another
1080 # level of relative separation.
1081 # 4. Append a trailing "/" only for non-empty paths: this way
1082 # the caller doesn't need to duplicate this logic, and does
1083 # not end up using $RTOP/file for files in $WDIR.
1084 #
1085 print $cur | $SED -e '{
1086 s:^\./*::
1087 s:/$::
1088 s:[^/][^/]*:..:g
1089 s:^\(..*\)$:\1/:
1090 }'
1091 }
1092
1093 #
1094 # frame_nav_js
1095 #
1096 # Emit javascript for frame navigation
1097 #
1098 function frame_nav_js
1099 {
1100 cat << \EOF
1101 var myInt;
1102 var scrolling=0;
1103 var sfactor = 3;
1104 var scount=10;
1105
1106 function scrollByPix() {
1107 if (scount<=0) {
1108 sfactor*=1.2;
1109 scount=10;
1110 }
1111 parent.lhs.scrollBy(0,sfactor);
1112 parent.rhs.scrollBy(0,sfactor);
1113 scount--;
1114 }
1115
1116 function scrollToAnc(num) {
1117
1118 // Update the value of the anchor in the form which we use as
1119 // storage for this value. setAncValue() will take care of
1120 // correcting for overflow and underflow of the value and return
1121 // us the new value.
1122 num = setAncValue(num);
1123
1124 // Set location and scroll back a little to expose previous
1125 // lines.
1126 //
1127 // Note that this could be improved: it is possible although
1128 // complex to compute the x and y position of an anchor, and to
1129 // scroll to that location directly.
1130 //
1131 parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1132 parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1133
1134 parent.lhs.scrollBy(0,-30);
1135 parent.rhs.scrollBy(0,-30);
1136 }
1137
1138 function getAncValue()
1139 {
1140 return (parseInt(parent.nav.document.diff.real.value));
1141 }
1142
1143 function setAncValue(val)
1144 {
1145 if (val <= 0) {
1146 val = 0;
1147 parent.nav.document.diff.real.value = val;
1148 parent.nav.document.diff.display.value = "BOF";
1149 return (val);
1150 }
1151
1152 //
1153 // The way we compute the max anchor value is to stash it
1154 // inline in the left and right hand side pages-- it's the same
1155 // on each side, so we pluck from the left.
1156 //
1157 maxval = parent.lhs.document.eof.value.value;
1158 if (val < maxval) {
1159 parent.nav.document.diff.real.value = val;
1160 parent.nav.document.diff.display.value = val.toString();
1161 return (val);
1162 }
1163
1164 // this must be: val >= maxval
1165 val = maxval;
1166 parent.nav.document.diff.real.value = val;
1167 parent.nav.document.diff.display.value = "EOF";
1168 return (val);
1169 }
1170
1171 function stopScroll() {
1172 if (scrolling==1) {
1173 clearInterval(myInt);
1174 scrolling=0;
1175 }
1176 }
1177
1178 function startScroll() {
1179 stopScroll();
1180 scrolling=1;
1181 myInt=setInterval("scrollByPix()",10);
1182 }
1183
1184 function handlePress(b) {
1185
1186 switch (b) {
1187 case 1 :
1188 scrollToAnc(-1);
1189 break;
1190 case 2 :
1191 scrollToAnc(getAncValue() - 1);
1192 break;
1193 case 3 :
1194 sfactor=-3;
1195 startScroll();
1196 break;
1197 case 4 :
1198 sfactor=3;
1199 startScroll();
1200 break;
1201 case 5 :
1202 scrollToAnc(getAncValue() + 1);
1203 break;
1204 case 6 :
1205 scrollToAnc(999999);
1206 break;
1207 }
1208 }
1209
1210 function handleRelease(b) {
1211 stopScroll();
1212 }
1213
1214 function keypress(ev) {
1215 var keynum;
1216 var keychar;
1217
1218 if (window.event) { // IE
1219 keynum = ev.keyCode;
1220 } else if (ev.which) { // non-IE
1221 keynum = ev.which;
1222 }
1223
1224 keychar = String.fromCharCode(keynum);
1225
1226 if (keychar == "k") {
1227 handlePress(2);
1228 return (0);
1229 } else if (keychar == "j" || keychar == " ") {
1230 handlePress(5);
1231 return (0);
1232 }
1233 return (1);
1234 }
1235
1236 function ValidateDiffNum(){
1237 val = parent.nav.document.diff.display.value;
1238 if (val == "EOF") {
1239 scrollToAnc(999999);
1240 return;
1241 }
1242
1243 if (val == "BOF") {
1244 scrollToAnc(0);
1245 return;
1246 }
1247
1248 i=parseInt(val);
1249 if (isNaN(i)) {
1250 parent.nav.document.diff.display.value = getAncValue();
1251 } else {
1252 scrollToAnc(i);
1253 }
1254 return false;
1255 }
1256
1257 EOF
1258 }
1259
1260 #
1261 # frame_navigation
1262 #
1263 # Output anchor navigation file for framed sdiffs.
1264 #
1265 function frame_navigation
1266 {
1267 print "$HTML<head>$STDHEAD"
1268
1269 cat << \EOF
1270 <title>Anchor Navigation</title>
1271 <meta http-equiv="Content-Script-Type" content="text/javascript">
1272 <meta http-equiv="Content-Type" content="text/html">
1273
1274 <style type="text/css">
1275 div.button td { padding-left: 5px; padding-right: 5px;
1276 background-color: #eee; text-align: center;
1277 border: 1px #444 outset; cursor: pointer; }
1278 div.button a { font-weight: bold; color: black }
1279 div.button td:hover { background: #ffcc99; }
1280 </style>
1281 EOF
1282
1283 print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1284
1285 cat << \EOF
1286 </head>
1287 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1288 onkeypress="keypress(event);">
1289 <noscript lang="javascript">
1290 <center>
1291 <p><big>Framed Navigation controls require Javascript</big><br></br>
1292 Either this browser is incompatable or javascript is not enabled</p>
1293 </center>
1294 </noscript>
1295 <table width="100%" border="0" align="center">
1296 <tr>
1297 <td valign="middle" width="25%">Diff navigation:
1298 Use 'j' and 'k' for next and previous diffs; or use buttons
1299 at right</td>
1300 <td align="center" valign="top" width="50%">
1301 <div class="button">
1302 <table border="0" align="center">
1303 <tr>
1304 <td>
1305 <a onMouseDown="handlePress(1);return true;"
1306 onMouseUp="handleRelease(1);return true;"
1307 onMouseOut="handleRelease(1);return true;"
1308 onClick="return false;"
1309 title="Go to Beginning Of file">BOF</a></td>
1310 <td>
1311 <a onMouseDown="handlePress(3);return true;"
1312 onMouseUp="handleRelease(3);return true;"
1313 onMouseOut="handleRelease(3);return true;"
1314 title="Scroll Up: Press and Hold to accelerate"
1315 onClick="return false;">Scroll Up</a></td>
1316 <td>
1317 <a onMouseDown="handlePress(2);return true;"
1318 onMouseUp="handleRelease(2);return true;"
1319 onMouseOut="handleRelease(2);return true;"
1320 title="Go to previous Diff"
1321 onClick="return false;">Prev Diff</a>
1322 </td></tr>
1323
1324 <tr>
1325 <td>
1326 <a onMouseDown="handlePress(6);return true;"
1327 onMouseUp="handleRelease(6);return true;"
1328 onMouseOut="handleRelease(6);return true;"
1329 onClick="return false;"
1330 title="Go to End Of File">EOF</a></td>
1331 <td>
1332 <a onMouseDown="handlePress(4);return true;"
1333 onMouseUp="handleRelease(4);return true;"
1334 onMouseOut="handleRelease(4);return true;"
1335 title="Scroll Down: Press and Hold to accelerate"
1336 onClick="return false;">Scroll Down</a></td>
1337 <td>
1338 <a onMouseDown="handlePress(5);return true;"
1339 onMouseUp="handleRelease(5);return true;"
1340 onMouseOut="handleRelease(5);return true;"
1341 title="Go to next Diff"
1342 onClick="return false;">Next Diff</a></td>
1343 </tr>
1344 </table>
1345 </div>
1346 </td>
1347 <th valign="middle" width="25%">
1348 <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1349 <input name="display" value="BOF" size="8" type="text"></input>
1350 <input name="real" value="0" size="8" type="hidden"></input>
1351 </form>
1352 </th>
1353 </tr>
1354 </table>
1355 </body>
1356 </html>
1357 EOF
1358 }
1359
1360
1361
1362 #
1363 # diff_to_html <filename> <filepath> { U | C } <comment>
1364 #
1365 # Processes the output of diff to produce an HTML file representing either
1366 # context or unified diffs.
1367 #
1368 diff_to_html()
1369 {
1370 TNAME=$1
1371 TPATH=$2
1372 DIFFTYPE=$3
1373 COMMENT=$4
1374
1375 print "$HTML<head>$STDHEAD"
1376 print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1377
1378 if [[ $DIFFTYPE == "U" ]]; then
1379 print "$UDIFFCSS"
1380 fi
1381
1382 cat <<-EOF
1383 </head>
1384 <body id="SUNWwebrev">
1385 <a class="print" href="javascript:print()">Print this page</a>
1386 <pre>$COMMENT</pre>
1387 <pre>
1388 EOF
1389
1390 html_quote | $AWK '
1391 /^--- new/ { next }
1392 /^\+\+\+ new/ { next }
1393 /^--- old/ { next }
1394 /^\*\*\* old/ { next }
1395 /^\*\*\*\*/ { next }
1396 /^-------/ { printf "<center><h1>%s</h1></center>\n", $0; next }
1397 /^\@\@.*\@\@$/ { printf "</pre><hr></hr><pre>\n";
1398 printf "<span class=\"newmarker\">%s</span>\n", $0;
1399 next}
1400
1401 /^\*\*\*/ { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1402 next}
1403 /^---/ { printf "<span class=\"newmarker\">%s</span>\n", $0;
1404 next}
1405 /^\+/ {printf "<span class=\"new\">%s</span>\n", $0; next}
1406 /^!/ {printf "<span class=\"changed\">%s</span>\n", $0; next}
1407 /^-/ {printf "<span class=\"removed\">%s</span>\n", $0; next}
1408 {printf "%s\n", $0; next}
1409 '
1410
1411 print "</pre></body></html>\n"
1412 }
1413
1414
1415 #
1416 # source_to_html { new | old } <filename>
1417 #
1418 # Process a plain vanilla source file to transform it into an HTML file.
1419 #
1420 source_to_html()
1421 {
1422 WHICH=$1
1423 TNAME=$2
1424
1425 print "$HTML<head>$STDHEAD"
1426 print "<title>$WNAME $WHICH $TNAME</title>"
1427 print "<body id=\"SUNWwebrev\">"
1428 print "<pre>"
1429 html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1430 print "</pre></body></html>"
1431 }
1432
1433 #
1434 # comments_from_wx {text|html} filepath
1435 #
1436 # Given the pathname of a file, find its location in a "wx" active
1437 # file list and print the following comment. Output is either text or
1438 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1439 # are turned into URLs.
1440 #
1441 # This is also used with Mercurial and the file list provided by hg-active.
1442 #
1443 comments_from_wx()
1444 {
1445 typeset fmt=$1
1446 typeset p=$2
1447
1448 comm=`$AWK '
1449 $1 == "'$p'" {
1450 do getline ; while (NF > 0)
1451 getline
1452 while (NF > 0) { print ; getline }
1453 exit
1454 }' < $wxfile`
1455
1456 if [[ -z $comm ]]; then
1457 comm="*** NO COMMENTS ***"
1458 fi
1459
1460 if [[ $fmt == "text" ]]; then
1461 print -- "$comm"
1462 return
1463 fi
1464
1465 print -- "$comm" | html_quote | its2url
1466
1467 }
1468
1469 #
1470 # getcomments {text|html} filepath parentpath
1471 #
1472 # Fetch the comments depending on what SCM mode we're in.
1473 #
1474 getcomments()
1475 {
1476 typeset fmt=$1
1477 typeset p=$2
1478 typeset pp=$3
1479
1480 if [[ -n $Nflag ]]; then
1481 return
1482 fi
1483 #
1484 # Mercurial support uses a file list in wx format, so this
1485 # will be used there, too
1486 #
1487 if [[ -n $wxfile ]]; then
1488 comments_from_wx $fmt $p
1489 fi
1490 }
1491
1492 #
1493 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1494 #
1495 # Print out Code Inspection figures similar to sccs-prt(1) format.
1496 #
1497 function printCI
1498 {
1499 integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1500 typeset str
1501 if (( tot == 1 )); then
1502 str="line"
1503 else
1504 str="lines"
1505 fi
1506 printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1507 $tot $str $ins $del $mod $unc
1508 }
1509
1510
1511 #
1512 # difflines <oldfile> <newfile>
1513 #
1514 # Calculate and emit number of added, removed, modified and unchanged lines,
1515 # and total lines changed, the sum of added + removed + modified.
1516 #
1517 function difflines
1518 {
1519 integer tot mod del ins unc err
1520 typeset filename
1521
1522 eval $( diff -e $1 $2 | $AWK '
1523 # Change range of lines: N,Nc
1524 /^[0-9]*,[0-9]*c$/ {
1525 n=split(substr($1,1,length($1)-1), counts, ",");
1526 if (n != 2) {
1527 error=2
1528 exit;
1529 }
1530 #
1531 # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1532 # following would be 5 - 3 = 2! Hence +1 for correction.
1533 #
1534 r=(counts[2]-counts[1])+1;
1535
1536 #
1537 # Now count replacement lines: each represents a change instead
1538 # of a delete, so increment c and decrement r.
1539 #
1540 while (getline != /^\.$/) {
1541 c++;
1542 r--;
1543 }
1544 #
1545 # If there were more replacement lines than original lines,
1546 # then r will be negative; in this case there are no deletions,
1547 # but there are r changes that should be counted as adds, and
1548 # since r is negative, subtract it from a and add it to c.
1549 #
1550 if (r < 0) {
1551 a-=r;
1552 c+=r;
1553 }
1554
1555 #
1556 # If there were more original lines than replacement lines, then
1557 # r will be positive; in this case, increment d by that much.
1558 #
1559 if (r > 0) {
1560 d+=r;
1561 }
1562 next;
1563 }
1564
1565 # Change lines: Nc
1566 /^[0-9].*c$/ {
1567 # The first line is a replacement; any more are additions.
1568 if (getline != /^\.$/) {
1569 c++;
1570 while (getline != /^\.$/) a++;
1571 }
1572 next;
1573 }
1574
1575 # Add lines: both Na and N,Na
1576 /^[0-9].*a$/ {
1577 while (getline != /^\.$/) a++;
1578 next;
1579 }
1580
1581 # Delete range of lines: N,Nd
1582 /^[0-9]*,[0-9]*d$/ {
1583 n=split(substr($1,1,length($1)-1), counts, ",");
1584 if (n != 2) {
1585 error=2
1586 exit;
1587 }
1588 #
1589 # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1590 # following would be 5 - 3 = 2! Hence +1 for correction.
1591 #
1592 r=(counts[2]-counts[1])+1;
1593 d+=r;
1594 next;
1595 }
1596
1597 # Delete line: Nd. For example 10d says line 10 is deleted.
1598 /^[0-9]*d$/ {d++; next}
1599
1600 # Should not get here!
1601 {
1602 error=1;
1603 exit;
1604 }
1605
1606 # Finish off - print results
1607 END {
1608 printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1609 (c+d+a), c, d, a, error);
1610 }' )
1611
1612 # End of $AWK, Check to see if any trouble occurred.
1613 if (( $? > 0 || err > 0 )); then
1614 print "Unexpected Error occurred reading" \
1615 "\`diff -e $1 $2\`: \$?=$?, err=" $err
1616 return
1617 fi
1618
1619 # Accumulate totals
1620 (( TOTL += tot ))
1621 (( TMOD += mod ))
1622 (( TDEL += del ))
1623 (( TINS += ins ))
1624 # Calculate unchanged lines
1625 unc=`wc -l < $1`
1626 if (( unc > 0 )); then
1627 (( unc -= del + mod ))
1628 (( TUNC += unc ))
1629 fi
1630 # print summary
1631 print "<span class=\"lineschanged\">"
1632 printCI $tot $ins $del $mod $unc
1633 print "</span>"
1634 }
1635
1636
1637 #
1638 # flist_from_wx
1639 #
1640 # Sets up webrev to source its information from a wx-formatted file.
1641 # Sets the global 'wxfile' variable.
1642 #
1643 function flist_from_wx
1644 {
1645 typeset argfile=$1
1646 if [[ -n ${argfile%%/*} ]]; then
1647 #
1648 # If the wx file pathname is relative then make it absolute
1649 # because the webrev does a "cd" later on.
1650 #
1651 wxfile=$PWD/$argfile
1652 else
1653 wxfile=$argfile
1654 fi
1655
1656 $AWK '{ c = 1; print;
1657 while (getline) {
1658 if (NF == 0) { c = -c; continue }
1659 if (c > 0) print
1660 }
1661 }' $wxfile > $FLIST
1662
1663 print " Done."
1664 }
1665
1666 #
1667 # Call hg-active to get the active list output in the wx active list format
1668 #
1669 function hg_active_wxfile
1670 {
1671 typeset child=$1
1672 typeset parent=$2
1673
1674 TMPFLIST=/tmp/$$.active
1675 $HG_ACTIVE -w $child -p $parent -o $TMPFLIST
1676 wxfile=$TMPFLIST
1677 }
1678
1679 #
1680 # flist_from_mercurial
1681 # Call hg-active to get a wx-style active list, and hand it off to
1682 # flist_from_wx
1683 #
1684 function flist_from_mercurial
1685 {
1686 typeset child=$1
1687 typeset parent=$2
1688
1689 print " File list from: hg-active -p $parent ...\c"
1690 if [[ ! -x $HG_ACTIVE ]]; then
1691 print # Blank line for the \c above
1692 print -u2 "Error: hg-active tool not found. Exiting"
1693 exit 1
1694 fi
1695 hg_active_wxfile $child $parent
1696
1697 # flist_from_wx prints the Done, so we don't have to.
1698 flist_from_wx $TMPFLIST
1699 }
1700
1701 #
1702 # Transform a specified 'git log' output format into a wx-like active list.
1703 #
1704 function git_wxfile
1705 {
1706 typeset child="$1"
1707 typeset parent="$2"
1708
1709 TMPFLIST=/tmp/$$.active
1710 $PERL -e 'my (%files, %realfiles, $msg);
1711 my $branch = $ARGV[0];
1712
1713 open(F, "git diff -M --name-status $branch |");
1714 while (<F>) {
1715 chomp;
1716 if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
1717 if ($1 >= 75) { # Probably worth treating as a rename
1718 $realfiles{$3} = $2;
1719 } else {
1720 $realfiles{$3} = $3;
1721 $realfiles{$2} = $2;
1722 }
1723 } else {
1724 my $f = (split /\s+/, $_)[1];
1725 $realfiles{$f} = $f;
1726 }
1727 }
1728 close(F);
1729
1730 my $state = 1; # 0|comments, 1|files
1731 open(F, "git whatchanged --pretty=format:%B $branch.. |");
1732 while (<F>) {
1733 chomp;
1734 if (/^:[0-9]{6}/) {
1735 my $fname = (split /\t/, $_)[1];
1736 next if !defined($realfiles{$fname}); # No real change
1737 $state = 1;
1738 chomp $msg;
1739 $files{$fname} .= $msg;
1740 } else {
1741 if ($state == 1) {
1742 $state = 0;
1743 $msg = /^\n/ ? "" : "\n";
1744 }
1745 $msg .= "$_\n" if ($_);
1746 }
1747 }
1748 close(F);
1749
1750 for (sort keys %files) {
1751 if ($realfiles{$_} ne $_) {
1752 print "$_ $realfiles{$_}\n$files{$_}\n\n";
1753 } else {
1754 print "$_\n$files{$_}\n\n"
1755 }
1756 }' ${parent} > $TMPFLIST
1757
1758 wxfile=$TMPFLIST
1759 }
1760
1761 #
1762 # flist_from_git
1763 # Build a wx-style active list, and hand it off to flist_from_wx
1764 #
1765 function flist_from_git
1766 {
1767 typeset child=$1
1768 typeset parent=$2
1769
1770 print " File list from: git ...\c"
1771 git_wxfile "$child" "$parent";
1772
1773 # flist_from_wx prints the Done, so we don't have to.
1774 flist_from_wx $TMPFLIST
1775 }
1776
1777 #
1778 # flist_from_subversion
1779 #
1780 # Generate the file list by extracting file names from svn status.
1781 #
1782 function flist_from_subversion
1783 {
1784 CWS=$1
1785 OLDPWD=$2
1786
1787 cd $CWS
1788 print -u2 " File list from: svn status ... \c"
1789 svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
1790 print -u2 " Done."
1791 cd $OLDPWD
1792 }
1793
1794 function env_from_flist
1795 {
1796 [[ -r $FLIST ]] || return
1797
1798 #
1799 # Use "eval" to set env variables that are listed in the file
1800 # list. Then copy those into our local versions of those
1801 # variables if they have not been set already.
1802 #
1803 eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
1804
1805 if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
1806 codemgr_ws=$CODEMGR_WS
1807 export CODEMGR_WS
1808 fi
1809
1810 #
1811 # Check to see if CODEMGR_PARENT is set in the flist file.
1812 #
1813 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
1814 codemgr_parent=$CODEMGR_PARENT
1815 export CODEMGR_PARENT
1816 fi
1817 }
1818
1819 function look_for_prog
1820 {
1821 typeset path
1822 typeset ppath
1823 typeset progname=$1
1824
1825 ppath=$PATH
1826 ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
1827 ppath=$ppath:/opt/onbld/bin:/opt/onbld/bin/`uname -p`
1828
1829 PATH=$ppath prog=`whence $progname`
1830 if [[ -n $prog ]]; then
1831 print $prog
1832 fi
1833 }
1834
1835 function get_file_mode
1836 {
1837 $PERL -e '
1838 if (@stat = stat($ARGV[0])) {
1839 $mode = $stat[2] & 0777;
1840 printf "%03o\n", $mode;
1841 exit 0;
1842 } else {
1843 exit 1;
1844 }
1845 ' $1
1846 }
1847
1848 function build_old_new_mercurial
1849 {
1850 typeset olddir="$1"
1851 typeset newdir="$2"
1852 typeset old_mode=
1853 typeset new_mode=
1854 typeset file
1855
1856 #
1857 # Get old file mode, from the parent revision manifest entry.
1858 # Mercurial only stores a "file is executable" flag, but the
1859 # manifest will display an octal mode "644" or "755".
1860 #
1861 if [[ "$PDIR" == "." ]]; then
1862 file="$PF"
1863 else
1864 file="$PDIR/$PF"
1865 fi
1866 file=`echo $file | $SED 's#/#\\\/#g'`
1867 # match the exact filename, and return only the permission digits
1868 old_mode=`$SED -n -e "/^\\(...\\) . ${file}$/s//\\1/p" \
1869 < $HG_PARENT_MANIFEST`
1870
1871 #
1872 # Get new file mode, directly from the filesystem.
1873 # Normalize the mode to match Mercurial's behavior.
1874 #
1875 new_mode=`get_file_mode $CWS/$DIR/$F`
1876 if [[ -n "$new_mode" ]]; then
1877 if [[ "$new_mode" = *[1357]* ]]; then
1878 new_mode=755
1879 else
1880 new_mode=644
1881 fi
1882 fi
1883
1884 #
1885 # new version of the file.
1886 #
1887 rm -rf $newdir/$DIR/$F
1888 if [[ -e $CWS/$DIR/$F ]]; then
1889 cp $CWS/$DIR/$F $newdir/$DIR/$F
1890 if [[ -n $new_mode ]]; then
1891 chmod $new_mode $newdir/$DIR/$F
1892 else
1893 # should never happen
1894 print -u2 "ERROR: set mode of $newdir/$DIR/$F"
1895 fi
1896 fi
1897
1898 #
1899 # parent's version of the file
1900 #
1901 # Note that we get this from the last version common to both
1902 # ourselves and the parent. References are via $CWS since we have no
1903 # guarantee that the parent workspace is reachable via the filesystem.
1904 #
1905 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
1906 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1907 elif [[ -n $HG_PARENT ]]; then
1908 hg cat -R $CWS -r $HG_PARENT $CWS/$PDIR/$PF > \
1909 $olddir/$PDIR/$PF 2>/dev/null
1910
1911 if (( $? != 0 )); then
1912 rm -f $olddir/$PDIR/$PF
1913 else
1914 if [[ -n $old_mode ]]; then
1915 chmod $old_mode $olddir/$PDIR/$PF
1916 else
1917 # should never happen
1918 print -u2 "ERROR: set mode of $olddir/$PDIR/$PF"
1919 fi
1920 fi
1921 fi
1922 }
1923
1924 function build_old_new_git
1925 {
1926 typeset olddir="$1"
1927 typeset newdir="$2"
1928 typeset o_mode=
1929 typeset n_mode=
1930 typeset o_object=
1931 typeset n_object=
1932 typeset OWD=$PWD
1933 typeset file
1934 typeset type
1935
1936 cd $CWS
1937
1938 #
1939 # Get old file and its mode from the git object tree
1940 #
1941 if [[ "$PDIR" == "." ]]; then
1942 file="$PF"
1943 else
1944 file="$PDIR/$PF"
1945 fi
1946
1947 if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
1948 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1949 else
1950 $GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk
1951 $GIT cat-file $type $o_object > $olddir/$file 2>/dev/null
1952
1953 if (( $? != 0 )); then
1954 rm -f $olddir/$file
1955 elif [[ -n $o_mode ]]; then
1956 # Strip the first 3 digits, to get a regular octal mode
1957 o_mode=${o_mode/???/}
1958 chmod $o_mode $olddir/$file
1959 else
1960 # should never happen
1961 print -u2 "ERROR: set mode of $olddir/$file"
1962 fi
1963 fi
1964
1965 #
1966 # new version of the file.
1967 #
1968 if [[ "$DIR" == "." ]]; then
1969 file="$F"
1970 else
1971 file="$DIR/$F"
1972 fi
1973 rm -rf $newdir/$file
1974
1975 if [[ -e $CWS/$DIR/$F ]]; then
1976 cp $CWS/$DIR/$F $newdir/$DIR/$F
1977 chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F
1978 fi
1979 cd $OWD
1980 }
1981
1982 function build_old_new_subversion
1983 {
1984 typeset olddir="$1"
1985 typeset newdir="$2"
1986
1987 # Snag new version of file.
1988 rm -f $newdir/$DIR/$F
1989 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
1990
1991 if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
1992 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
1993 else
1994 # Get the parent's version of the file.
1995 svn status $CWS/$DIR/$F | read stat file
1996 if [[ $stat != "A" ]]; then
1997 svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
1998 fi
1999 fi
2000 }
2001
2002 function build_old_new_unknown
2003 {
2004 typeset olddir="$1"
2005 typeset newdir="$2"
2006
2007 #
2008 # Snag new version of file.
2009 #
2010 rm -f $newdir/$DIR/$F
2011 [[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2012
2013 #
2014 # Snag the parent's version of the file.
2015 #
2016 if [[ -f $PWS/$PDIR/$PF ]]; then
2017 rm -f $olddir/$PDIR/$PF
2018 cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2019 fi
2020 }
2021
2022 function build_old_new
2023 {
2024 typeset WDIR=$1
2025 typeset PWS=$2
2026 typeset PDIR=$3
2027 typeset PF=$4
2028 typeset CWS=$5
2029 typeset DIR=$6
2030 typeset F=$7
2031
2032 typeset olddir="$WDIR/raw_files/old"
2033 typeset newdir="$WDIR/raw_files/new"
2034
2035 mkdir -p $olddir/$PDIR
2036 mkdir -p $newdir/$DIR
2037
2038 if [[ $SCM_MODE == "mercurial" ]]; then
2039 build_old_new_mercurial "$olddir" "$newdir"
2040 elif [[ $SCM_MODE == "git" ]]; then
2041 build_old_new_git "$olddir" "$newdir"
2042 elif [[ $SCM_MODE == "subversion" ]]; then
2043 build_old_new_subversion "$olddir" "$newdir"
2044 elif [[ $SCM_MODE == "unknown" ]]; then
2045 build_old_new_unknown "$olddir" "$newdir"
2046 fi
2047
2048 if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2049 print "*** Error: file not in parent or child"
2050 return 1
2051 fi
2052 return 0
2053 }
2054
2055
2056 #
2057 # Usage message.
2058 #
2059 function usage
2060 {
2061 print 'Usage:\twebrev [common-options]
2062 webrev [common-options] ( <file> | - )
2063 webrev [common-options] -w <wx file>
2064
2065 Options:
2066 -C <filename>: Use <filename> for the information tracking configuration.
2067 -D: delete remote webrev
2068 -i <filename>: Include <filename> in the index.html file.
2069 -I <filename>: Use <filename> for the information tracking registry.
2070 -n: do not generate the webrev (useful with -U)
2071 -O: Print bugids/arc cases suitable for OpenSolaris.
2072 -o <outdir>: Output webrev to specified directory.
2073 -p <compare-against>: Use specified parent wkspc or basis for comparison
2074 -t <remote_target>: Specify remote destination for webrev upload
2075 -U: upload the webrev to remote destination
2076 -w <wxfile>: Use specified wx active file.
2077
2078 Environment:
2079 WDIR: Control the output directory.
2080 WEBREV_TRASH_DIR: Set directory for webrev delete.
2081
2082 SCM Environment:
2083 CODEMGR_WS: Workspace location.
2084 CODEMGR_PARENT: Parent workspace location.
2085 '
2086 exit 2
2087 }
2088
2089 #
2090 #
2091 # Main program starts here
2092 #
2093 #
2094
2095 trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2096
2097 set +o noclobber
2098
2099 PATH=$(/bin/dirname "$(whence $0)"):$PATH
2100
2101 [[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2102 [[ -z $WX ]] && WX=`look_for_prog wx`
2103 [[ -z $HG_ACTIVE ]] && HG_ACTIVE=`look_for_prog hg-active`
2104 [[ -z $GIT ]] && GIT=`look_for_prog git`
2105 [[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2106 [[ -z $CODEREVIEW ]] && CODEREVIEW=`look_for_prog codereview`
2107 [[ -z $PS2PDF ]] && PS2PDF=`look_for_prog ps2pdf`
2108 [[ -z $PERL ]] && PERL=`look_for_prog perl`
2109 [[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2110 [[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2111 [[ -z $AWK ]] && AWK=`look_for_prog nawk`
2112 [[ -z $AWK ]] && AWK=`look_for_prog gawk`
2113 [[ -z $AWK ]] && AWK=`look_for_prog awk`
2114 [[ -z $SCP ]] && SCP=`look_for_prog scp`
2115 [[ -z $SED ]] && SED=`look_for_prog sed`
2116 [[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2117 [[ -z $SORT ]] && SORT=`look_for_prog sort`
2118 [[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2119 [[ -z $GREP ]] && GREP=`look_for_prog grep`
2120 [[ -z $FIND ]] && FIND=`look_for_prog find`
2121
2122 # set name of trash directory for remote webrev deletion
2123 TRASH_DIR=".trash"
2124 [[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2125
2126 if [[ ! -x $PERL ]]; then
2127 print -u2 "Error: No perl interpreter found. Exiting."
2128 exit 1
2129 fi
2130
2131 if [[ ! -x $WHICH_SCM ]]; then
2132 print -u2 "Error: Could not find which_scm. Exiting."
2133 exit 1
2134 fi
2135
2136 #
2137 # These aren't fatal, but we want to note them to the user.
2138 # We don't warn on the absence of 'wx' until later when we've
2139 # determined that we actually need to try to invoke it.
2140 #
2141 [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found."
2142 [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found."
2143 [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2144
2145 # Declare global total counters.
2146 integer TOTL TINS TDEL TMOD TUNC
2147
2148 # default remote host for upload/delete
2149 typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2150 # prefixes for upload targets
2151 typeset -r rsync_prefix="rsync://"
2152 typeset -r ssh_prefix="ssh://"
2153
2154 Cflag=
2155 Dflag=
2156 flist_mode=
2157 flist_file=
2158 iflag=
2159 Iflag=
2160 lflag=
2161 Nflag=
2162 nflag=
2163 Oflag=
2164 oflag=
2165 pflag=
2166 tflag=
2167 uflag=
2168 Uflag=
2169 wflag=
2170 remote_target=
2171
2172 #
2173 # NOTE: when adding/removing options it is necessary to sync the list
2174 # with usr/src/tools/onbld/hgext/cdm.py
2175 #
2176 while getopts "C:Di:I:lnNo:Op:t:Uw" opt
2177 do
2178 case $opt in
2179 C) Cflag=1
2180 ITSCONF=$OPTARG;;
2181
2182 D) Dflag=1;;
2183
2184 i) iflag=1
2185 INCLUDE_FILE=$OPTARG;;
2186
2187 I) Iflag=1
2188 ITSREG=$OPTARG;;
2189
2190 #
2191 # If -l has been specified, we need to abort further options
2192 # processing, because subsequent arguments are going to be
2193 # arguments to 'putback -n'.
2194 #
2195 l) lflag=1
2196 break;;
2197
2198 N) Nflag=1;;
2199
2200 n) nflag=1;;
2201
2202 O) Oflag=1;;
2203
2204 o) oflag=1
2205 # Strip the trailing slash to correctly form remote target.
2206 WDIR=${OPTARG%/};;
2207
2208 p) pflag=1
2209 codemgr_parent=$OPTARG;;
2210
2211 t) tflag=1
2212 remote_target=$OPTARG;;
2213
2214 U) Uflag=1;;
2215
2216 w) wflag=1;;
2217
2218 ?) usage;;
2219 esac
2220 done
2221
2222 FLIST=/tmp/$$.flist
2223
2224 if [[ -n $wflag && -n $lflag ]]; then
2225 usage
2226 fi
2227
2228 # more sanity checking
2229 if [[ -n $nflag && -z $Uflag ]]; then
2230 print "it does not make sense to skip webrev generation" \
2231 "without -U"
2232 exit 1
2233 fi
2234
2235 if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2236 echo "remote target has to be used only for upload or delete"
2237 exit 1
2238 fi
2239
2240 #
2241 # For the invocation "webrev -n -U" with no other options, webrev will assume
2242 # that the webrev exists in ${CWS}/webrev, but will upload it using the name
2243 # $(basename ${CWS}). So we need to get CWS set before we skip any remaining
2244 # logic.
2245 #
2246 $WHICH_SCM | read SCM_MODE junk || exit 1
2247 if [[ $SCM_MODE == "mercurial" ]]; then
2248 #
2249 # Mercurial priorities:
2250 # 1. hg root from CODEMGR_WS environment variable
2251 # 1a. hg root from CODEMGR_WS/usr/closed if we're somewhere under
2252 # usr/closed when we run webrev
2253 # 2. hg root from directory of invocation
2254 #
2255 if [[ ${PWD} =~ "usr/closed" ]]; then
2256 testparent=${CODEMGR_WS}/usr/closed
2257 # If we're in OpenSolaris mode, we enforce a minor policy:
2258 # help to make sure the reviewer doesn't accidentally publish
2259 # source which is under usr/closed
2260 if [[ -n "$Oflag" ]]; then
2261 print -u2 "OpenSolaris output not permitted with" \
2262 "usr/closed changes"
2263 exit 1
2264 fi
2265 else
2266 testparent=${CODEMGR_WS}
2267 fi
2268 [[ -z $codemgr_ws && -n $testparent ]] && \
2269 codemgr_ws=$(hg root -R $testparent 2>/dev/null)
2270 [[ -z $codemgr_ws ]] && codemgr_ws=$(hg root 2>/dev/null)
2271 CWS=$codemgr_ws
2272 elif [[ $SCM_MODE == "git" ]]; then
2273 #
2274 # Git priorities:
2275 # 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2276 # 2. git rev-parse --git-dir from directory of invocation
2277 #
2278 [[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2279 codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \
2280 2>/dev/null)
2281 [[ -z $codemgr_ws ]] && \
2282 codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null)
2283
2284 if [[ "$codemgr_ws" == ".git" ]]; then
2285 codemgr_ws="${PWD}/${codemgr_ws}"
2286 fi
2287
2288 codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git'
2289 CWS="$codemgr_ws"
2290 elif [[ $SCM_MODE == "subversion" ]]; then
2291 #
2292 # Subversion priorities:
2293 # 1. CODEMGR_WS from environment
2294 # 2. Relative path from current directory to SVN repository root
2295 #
2296 if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2297 CWS=$CODEMGR_WS
2298 else
2299 svn info | while read line; do
2300 if [[ $line == "URL: "* ]]; then
2301 url=${line#URL: }
2302 elif [[ $line == "Repository Root: "* ]]; then
2303 repo=${line#Repository Root: }
2304 fi
2305 done
2306
2307 rel=${url#$repo}
2308 CWS=${PWD%$rel}
2309 fi
2310 fi
2311
2312 #
2313 # If no SCM has been determined, take either the environment setting
2314 # setting for CODEMGR_WS, or the current directory if that wasn't set.
2315 #
2316 if [[ -z ${CWS} ]]; then
2317 CWS=${CODEMGR_WS:-.}
2318 fi
2319
2320 #
2321 # If the command line options indicate no webrev generation, either
2322 # explicitly (-n) or implicitly (-D but not -U), then there's a whole
2323 # ton of logic we can skip.
2324 #
2325 # Instead of increasing indentation, we intentionally leave this loop
2326 # body open here, and exit via break from multiple points within.
2327 # Search for DO_EVERYTHING below to find the break points and closure.
2328 #
2329 for do_everything in 1; do
2330
2331 # DO_EVERYTHING: break point
2332 if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2333 break
2334 fi
2335
2336 #
2337 # If this manually set as the parent, and it appears to be an earlier webrev,
2338 # then note that fact and set the parent to the raw_files/new subdirectory.
2339 #
2340 if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2341 parent_webrev=$(readlink -f "$codemgr_parent")
2342 codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new")
2343 fi
2344
2345 if [[ -z $wflag && -z $lflag ]]; then
2346 shift $(($OPTIND - 1))
2347
2348 if [[ $1 == "-" ]]; then
2349 cat > $FLIST
2350 flist_mode="stdin"
2351 flist_done=1
2352 shift
2353 elif [[ -n $1 ]]; then
2354 if [[ ! -r $1 ]]; then
2355 print -u2 "$1: no such file or not readable"
2356 usage
2357 fi
2358 cat $1 > $FLIST
2359 flist_mode="file"
2360 flist_file=$1
2361 flist_done=1
2362 shift
2363 else
2364 flist_mode="auto"
2365 fi
2366 fi
2367
2368 #
2369 # Before we go on to further consider -l and -w, work out which SCM we think
2370 # is in use.
2371 #
2372 case "$SCM_MODE" in
2373 mercurial|git|subversion)
2374 ;;
2375 unknown)
2376 if [[ $flist_mode == "auto" ]]; then
2377 print -u2 "Unable to determine SCM in use and file list not specified"
2378 print -u2 "See which_scm(1) for SCM detection information."
2379 exit 1
2380 fi
2381 ;;
2382 *)
2383 if [[ $flist_mode == "auto" ]]; then
2384 print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2385 exit 1
2386 fi
2387 ;;
2388 esac
2389
2390 print -u2 " SCM detected: $SCM_MODE"
2391
2392 if [[ -n $wflag ]]; then
2393 #
2394 # If the -w is given then assume the file list is in Bonwick's "wx"
2395 # command format, i.e. pathname lines alternating with SCCS comment
2396 # lines with blank lines as separators. Use the SCCS comments later
2397 # in building the index.html file.
2398 #
2399 shift $(($OPTIND - 1))
2400 wxfile=$1
2401 if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2402 if [[ -r $CODEMGR_WS/wx/active ]]; then
2403 wxfile=$CODEMGR_WS/wx/active
2404 fi
2405 fi
2406
2407 [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2408 "be auto-detected (check \$CODEMGR_WS)" && exit 1
2409
2410 if [[ ! -r $wxfile ]]; then
2411 print -u2 "$wxfile: no such file or not readable"
2412 usage
2413 fi
2414
2415 print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2416 flist_from_wx $wxfile
2417 flist_done=1
2418 if [[ -n "$*" ]]; then
2419 shift
2420 fi
2421 elif [[ $flist_mode == "stdin" ]]; then
2422 print -u2 " File list from: standard input"
2423 elif [[ $flist_mode == "file" ]]; then
2424 print -u2 " File list from: $flist_file"
2425 fi
2426
2427 if [[ $# -gt 0 ]]; then
2428 print -u2 "WARNING: unused arguments: $*"
2429 fi
2430
2431 #
2432 # Before we entered the DO_EVERYTHING loop, we should have already set CWS
2433 # and CODEMGR_WS as needed. Here, we set the parent workspace.
2434 #
2435
2436 if [[ $SCM_MODE == "mercurial" ]]; then
2437 #
2438 # Parent can either be specified with -p
2439 # Specified with CODEMGR_PARENT in the environment
2440 # or taken from hg's default path.
2441 #
2442
2443 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2444 codemgr_parent=$CODEMGR_PARENT
2445 fi
2446
2447 if [[ -z $codemgr_parent ]]; then
2448 codemgr_parent=`hg path -R $codemgr_ws default 2>/dev/null`
2449 fi
2450
2451 PWS=$codemgr_parent
2452
2453 #
2454 # If the parent is a webrev, we want to do some things against
2455 # the natural workspace parent (file list, comments, etc)
2456 #
2457 if [[ -n $parent_webrev ]]; then
2458 real_parent=$(hg path -R $codemgr_ws default 2>/dev/null)
2459 else
2460 real_parent=$PWS
2461 fi
2462
2463 #
2464 # If hg-active exists, then we run it. In the case of no explicit
2465 # flist given, we'll use it for our comments. In the case of an
2466 # explicit flist given we'll try to use it for comments for any
2467 # files mentioned in the flist.
2468 #
2469 if [[ -z $flist_done ]]; then
2470 flist_from_mercurial $CWS $real_parent
2471 flist_done=1
2472 fi
2473
2474 #
2475 # If we have a file list now, pull out any variables set
2476 # therein. We do this now (rather than when we possibly use
2477 # hg-active to find comments) to avoid stomping specifications
2478 # in the user-specified flist.
2479 #
2480 if [[ -n $flist_done ]]; then
2481 env_from_flist
2482 fi
2483
2484 #
2485 # Only call hg-active if we don't have a wx formatted file already
2486 #
2487 if [[ -x $HG_ACTIVE && -z $wxfile ]]; then
2488 print " Comments from: hg-active -p $real_parent ...\c"
2489 hg_active_wxfile $CWS $real_parent
2490 print " Done."
2491 fi
2492
2493 #
2494 # At this point we must have a wx flist either from hg-active,
2495 # or in general. Use it to try and find our parent revision,
2496 # if we don't have one.
2497 #
2498 if [[ -z $HG_PARENT ]]; then
2499 eval `$SED -e "s/#.*$//" $wxfile | $GREP HG_PARENT=`
2500 fi
2501
2502 #
2503 # If we still don't have a parent, we must have been given a
2504 # wx-style active list with no HG_PARENT specification, run
2505 # hg-active and pull an HG_PARENT out of it, ignore the rest.
2506 #
2507 if [[ -z $HG_PARENT && -x $HG_ACTIVE ]]; then
2508 $HG_ACTIVE -w $codemgr_ws -p $real_parent | \
2509 eval `$SED -e "s/#.*$//" | $GREP HG_PARENT=`
2510 elif [[ -z $HG_PARENT ]]; then
2511 print -u2 "Error: Cannot discover parent revision"
2512 exit 1
2513 fi
2514
2515 pnode=$(trim_digest $HG_PARENT)
2516 PRETTY_PWS="${PWS} (at ${pnode})"
2517 cnode=$(hg parent -R $codemgr_ws --template '{node|short}' \
2518 2>/dev/null)
2519 PRETTY_CWS="${CWS} (at ${cnode})"}
2520 elif [[ $SCM_MODE == "git" ]]; then
2521 #
2522 # Parent can either be specified with -p, or specified with
2523 # CODEMGR_PARENT in the environment.
2524 #
2525
2526 if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2527 codemgr_parent=$CODEMGR_PARENT
2528 fi
2529
2530 # Try to figure out the parent based on the branch the current
2531 # branch is tracking, if we fail, use origin/master
2532 this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2533 par_branch="origin/master"
2534
2535 # If we're not on a branch there's nothing we can do
2536 if [[ $this_branch != "(no branch)" ]]; then
2537 $GIT for-each-ref \
2538 --format='%(refname:short) %(upstream:short)' refs/heads/ | \
2539 while read local remote; do \
2540 [[ "$local" == "$this_branch" ]] && par_branch="$remote"; \
2541 done
2542 fi
2543
2544 if [[ -z $codemgr_parent ]]; then
2545 codemgr_parent=$par_branch
2546 fi
2547 PWS=$codemgr_parent
2548
2549 #
2550 # If the parent is a webrev, we want to do some things against
2551 # the natural workspace parent (file list, comments, etc)
2552 #
2553 if [[ -n $parent_webrev ]]; then
2554 real_parent=$par_branch
2555 else
2556 real_parent=$PWS
2557 fi
2558
2559 if [[ -z $flist_done ]]; then
2560 flist_from_git "$CWS" "$real_parent"
2561 flist_done=1
2562 fi
2563
2564 #
2565 # If we have a file list now, pull out any variables set
2566 # therein.
2567 #
2568 if [[ -n $flist_done ]]; then
2569 env_from_flist
2570 fi
2571
2572 #
2573 # If we don't have a wx-format file list, build one we can pull change
2574 # comments from.
2575 #
2576 if [[ -z $wxfile ]]; then
2577 print " Comments from: git...\c"
2578 git_wxfile "$CWS" "$real_parent"
2579 print " Done."
2580 fi
2581
2582 if [[ -z $GIT_PARENT ]]; then
2583 GIT_PARENT=$($GIT merge-base "$real_parent" HEAD)
2584 fi
2585 if [[ -z $GIT_PARENT ]]; then
2586 print -u2 "Error: Cannot discover parent revision"
2587 exit 1
2588 fi
2589
2590 pnode=$(trim_digest $GIT_PARENT)
2591
2592 if [[ $real_parent == */* ]]; then
2593 origin=$(echo $real_parent | cut -d/ -f1)
2594 origin=$($GIT remote -v | \
2595 $AWK '$1 == "'$origin'" { print $2; exit }')
2596 PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2597 else
2598 PRETTY_PWS="${PWS} (at ${pnode})"
2599 fi
2600
2601 cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 HEAD \
2602 2>/dev/null)
2603 PRETTY_CWS="${CWS} (at ${cnode})"
2604 elif [[ $SCM_MODE == "subversion" ]]; then
2605
2606 #
2607 # We only will have a real parent workspace in the case one
2608 # was specified (be it an older webrev, or another checkout).
2609 #
2610 [[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2611
2612 if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2613 flist_from_subversion $CWS $OLDPWD
2614 fi
2615 else
2616 if [[ $SCM_MODE == "unknown" ]]; then
2617 print -u2 " Unknown type of SCM in use"
2618 else
2619 print -u2 " Unsupported SCM in use: $SCM_MODE"
2620 fi
2621
2622 env_from_flist
2623
2624 if [[ -z $CODEMGR_WS ]]; then
2625 print -u2 "SCM not detected/supported and CODEMGR_WS not specified"
2626 exit 1
2627 fi
2628
2629 if [[ -z $CODEMGR_PARENT ]]; then
2630 print -u2 "SCM not detected/supported and CODEMGR_PARENT not specified"
2631 exit 1
2632 fi
2633
2634 CWS=$CODEMGR_WS
2635 PWS=$CODEMGR_PARENT
2636 fi
2637
2638 #
2639 # If the user didn't specify a -i option, check to see if there is a
2640 # webrev-info file in the workspace directory.
2641 #
2642 if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2643 iflag=1
2644 INCLUDE_FILE="$CWS/webrev-info"
2645 fi
2646
2647 if [[ -n $iflag ]]; then
2648 if [[ ! -r $INCLUDE_FILE ]]; then
2649 print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2650 "not readable."
2651 exit 1
2652 else
2653 #
2654 # $INCLUDE_FILE may be a relative path, and the script alters
2655 # PWD, so we just stash a copy in /tmp.
2656 #
2657 cp $INCLUDE_FILE /tmp/$$.include
2658 fi
2659 fi
2660
2661 # DO_EVERYTHING: break point
2662 if [[ -n $Nflag ]]; then
2663 break
2664 fi
2665
2666 typeset -A itsinfo
2667 typeset -r its_sed_script=/tmp/$$.its_sed
2668 valid_prefixes=
2669 if [[ -z $nflag ]]; then
2670 DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg"
2671 if [[ -n $Iflag ]]; then
2672 REGFILE=$ITSREG
2673 elif [[ -r $HOME/.its.reg ]]; then
2674 REGFILE=$HOME/.its.reg
2675 else
2676 REGFILE=$DEFREGFILE
2677 fi
2678 if [[ ! -r $REGFILE ]]; then
2679 print "ERROR: Unable to read database registry file $REGFILE"
2680 exit 1
2681 elif [[ $REGFILE != $DEFREGFILE ]]; then
2682 print " its.reg from: $REGFILE"
2683 fi
2684
2685 $SED -e '/^#/d' -e '/^[ ]*$/d' $REGFILE | while read LINE; do
2686
2687 name=${LINE%%=*}
2688 value="${LINE#*=}"
2689
2690 if [[ $name == PREFIX ]]; then
2691 p=${value}
2692 valid_prefixes="${p} ${valid_prefixes}"
2693 else
2694 itsinfo["${p}_${name}"]="${value}"
2695 fi
2696 done
2697
2698
2699 DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf"
2700 CONFFILES=$DEFCONFFILE
2701 if [[ -r $HOME/.its.conf ]]; then
2702 CONFFILES="${CONFFILES} $HOME/.its.conf"
2703 fi
2704 if [[ -n $Cflag ]]; then
2705 CONFFILES="${CONFFILES} ${ITSCONF}"
2706 fi
2707 its_domain=
2708 its_priority=
2709 for cf in ${CONFFILES}; do
2710 if [[ ! -r $cf ]]; then
2711 print "ERROR: Unable to read database configuration file $cf"
2712 exit 1
2713 elif [[ $cf != $DEFCONFFILE ]]; then
2714 print " its.conf: reading $cf"
2715 fi
2716 $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do
2717 eval "${LINE}"
2718 done
2719 done
2720
2721 #
2722 # If an information tracking system is explicitly identified by prefix,
2723 # we want to disregard the specified priorities and resolve it accordingly.
2724 #
2725 # To that end, we'll build a sed script to do each valid prefix in turn.
2726 #
2727 for p in ${valid_prefixes}; do
2728 #
2729 # When an informational URL was provided, translate it to a
2730 # hyperlink. When omitted, simply use the prefix text.
2731 #
2732 if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2733 itsinfo["${p}_INFO"]=${p}
2734 else
2735 itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2736 fi
2737
2738 #
2739 # Assume that, for this invocation of webrev, all references
2740 # to this information tracking system should resolve through
2741 # the same URL.
2742 #
2743 # If the caller specified -O, then always use EXTERNAL_URL.
2744 #
2745 # Otherwise, look in the list of domains for a matching
2746 # INTERNAL_URL.
2747 #
2748 [[ -z $Oflag ]] && for d in ${its_domain}; do
2749 if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2750 itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2751 break
2752 fi
2753 done
2754 if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2755 itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2756 fi
2757
2758 #
2759 # Turn the destination URL into a hyperlink
2760 #
2761 itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2762
2763 # The character class below contains a literal tab
2764 print "/^${p}[: ]/ {
2765 s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2766 s;^${p};${itsinfo[${p}_INFO]};
2767 }" >> ${its_sed_script}
2768 done
2769
2770 #
2771 # The previous loop took care of explicit specification. Now use
2772 # the configured priorities to attempt implicit translations.
2773 #
2774 for p in ${its_priority}; do
2775 print "/^${itsinfo[${p}_REGEX]}[ ]/ {
2776 s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2777 }" >> ${its_sed_script}
2778 done
2779 fi
2780
2781 #
2782 # Search for DO_EVERYTHING above for matching "for" statement
2783 # and explanation of this terminator.
2784 #
2785 done
2786
2787 #
2788 # Output directory.
2789 #
2790 WDIR=${WDIR:-$CWS/webrev}
2791
2792 #
2793 # Name of the webrev, derived from the workspace name or output directory;
2794 # in the future this could potentially be an option.
2795 #
2796 if [[ -n $oflag ]]; then
2797 WNAME=${WDIR##*/}
2798 else
2799 WNAME=${CWS##*/}
2800 fi
2801
2802 # Make sure remote target is well formed for remote upload/delete.
2803 if [[ -n $Dflag || -n $Uflag ]]; then
2804 #
2805 # If remote target is not specified, build it from scratch using
2806 # the default values.
2807 #
2808 if [[ -z $tflag ]]; then
2809 remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2810 else
2811 #
2812 # Check upload target prefix first.
2813 #
2814 if [[ "${remote_target}" != ${rsync_prefix}* &&
2815 "${remote_target}" != ${ssh_prefix}* ]]; then
2816 print "ERROR: invalid prefix of upload URI" \
2817 "($remote_target)"
2818 exit 1
2819 fi
2820 #
2821 # If destination specification is not in the form of
2822 # host_spec:remote_dir then assume it is just remote hostname
2823 # and append a colon and destination directory formed from
2824 # local webrev directory name.
2825 #
2826 typeset target_no_prefix=${remote_target##*://}
2827 if [[ ${target_no_prefix} == *:* ]]; then
2828 if [[ "${remote_target}" == *: ]]; then
2829 remote_target=${remote_target}${WNAME}
2830 fi
2831 else
2832 if [[ ${target_no_prefix} == */* ]]; then
2833 print "ERROR: badly formed upload URI" \
2834 "($remote_target)"
2835 exit 1
2836 else
2837 remote_target=${remote_target}:${WNAME}
2838 fi
2839 fi
2840 fi
2841
2842 #
2843 # Strip trailing slash. Each upload method will deal with directory
2844 # specification separately.
2845 #
2846 remote_target=${remote_target%/}
2847 fi
2848
2849 #
2850 # Option -D by itself (option -U not present) implies no webrev generation.
2851 #
2852 if [[ -z $Uflag && -n $Dflag ]]; then
2853 delete_webrev 1 1
2854 exit $?
2855 fi
2856
2857 #
2858 # Do not generate the webrev, just upload it or delete it.
2859 #
2860 if [[ -n $nflag ]]; then
2861 if [[ -n $Dflag ]]; then
2862 delete_webrev 1 1
2863 (( $? == 0 )) || exit $?
2864 fi
2865 if [[ -n $Uflag ]]; then
2866 upload_webrev
2867 exit $?
2868 fi
2869 fi
2870
2871 if [ "${WDIR%%/*}" ]; then
2872 WDIR=$PWD/$WDIR
2873 fi
2874
2875 if [[ ! -d $WDIR ]]; then
2876 mkdir -p $WDIR
2877 (( $? != 0 )) && exit 1
2878 fi
2879
2880 #
2881 # Summarize what we're going to do.
2882 #
2883 print " Workspace: ${PRETTY_CWS:-$CWS}"
2884 if [[ -n $parent_webrev ]]; then
2885 print "Compare against: webrev at $parent_webrev"
2886 else
2887 print "Compare against: ${PRETTY_PWS:-$PWS}"
2888 fi
2889
2890 [[ -n $INCLUDE_FILE ]] && print " Including: $INCLUDE_FILE"
2891 print " Output to: $WDIR"
2892
2893 #
2894 # Save the file list in the webrev dir
2895 #
2896 [[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
2897
2898 rm -f $WDIR/$WNAME.patch
2899 rm -f $WDIR/$WNAME.ps
2900 rm -f $WDIR/$WNAME.pdf
2901
2902 touch $WDIR/$WNAME.patch
2903
2904 print " Output Files:"
2905
2906 #
2907 # Clean up the file list: Remove comments, blank lines and env variables.
2908 #
2909 $SED -e "s/#.*$//" -e "/=/d" -e "/^[ ]*$/d" $FLIST > /tmp/$$.flist.clean
2910 FLIST=/tmp/$$.flist.clean
2911
2912 #
2913 # For Mercurial, create a cache of manifest entries.
2914 #
2915 if [[ $SCM_MODE == "mercurial" ]]; then
2916 #
2917 # Transform the FLIST into a temporary sed script that matches
2918 # relevant entries in the Mercurial manifest as follows:
2919 # 1) The script will be used against the parent revision manifest,
2920 # so for FLIST lines that have two filenames (a renamed file)
2921 # keep only the old name.
2922 # 2) Escape all forward slashes the filename.
2923 # 3) Change the filename into another sed command that matches
2924 # that file in "hg manifest -v" output: start of line, three
2925 # octal digits for file permissions, space, a file type flag
2926 # character, space, the filename, end of line.
2927 # 4) Eliminate any duplicate entries. (This can occur if a
2928 # file has been used as the source of an hg cp and it's
2929 # also been modified in the same changeset.)
2930 #
2931 SEDFILE=/tmp/$$.manifest.sed
2932 $SED '
2933 s#^[^ ]* ##
2934 s#/#\\\/#g
2935 s#^.*$#/^... . &$/p#
2936 ' < $FLIST | $SORT -u > $SEDFILE
2937
2938 #
2939 # Apply the generated script to the output of "hg manifest -v"
2940 # to get the relevant subset for this webrev.
2941 #
2942 HG_PARENT_MANIFEST=/tmp/$$.manifest
2943 hg -R $CWS manifest -v -r $HG_PARENT |
2944 $SED -n -f $SEDFILE > $HG_PARENT_MANIFEST
2945 fi
2946
2947 #
2948 # First pass through the files: generate the per-file webrev HTML-files.
2949 #
2950 cat $FLIST | while read LINE
2951 do
2952 set - $LINE
2953 P=$1
2954
2955 #
2956 # Normally, each line in the file list is just a pathname of a
2957 # file that has been modified or created in the child. A file
2958 # that is renamed in the child workspace has two names on the
2959 # line: new name followed by the old name.
2960 #
2961 oldname=""
2962 oldpath=""
2963 rename=
2964 if [[ $# -eq 2 ]]; then
2965 PP=$2 # old filename
2966 if [[ -f $PP ]]; then
2967 oldname=" (copied from $PP)"
2968 else
2969 oldname=" (renamed from $PP)"
2970 fi
2971 oldpath="$PP"
2972 rename=1
2973 PDIR=${PP%/*}
2974 if [[ $PDIR == $PP ]]; then
2975 PDIR="." # File at root of workspace
2976 fi
2977
2978 PF=${PP##*/}
2979
2980 DIR=${P%/*}
2981 if [[ $DIR == $P ]]; then
2982 DIR="." # File at root of workspace
2983 fi
2984
2985 F=${P##*/}
2986
2987 else
2988 DIR=${P%/*}
2989 if [[ "$DIR" == "$P" ]]; then
2990 DIR="." # File at root of workspace
2991 fi
2992
2993 F=${P##*/}
2994
2995 PP=$P
2996 PDIR=$DIR
2997 PF=$F
2998 fi
2999
3000 COMM=`getcomments html $P $PP`
3001
3002 print "\t$P$oldname\n\t\t\c"
3003
3004 # Make the webrev mirror directory if necessary
3005 mkdir -p $WDIR/$DIR
3006
3007 #
3008 # We stash old and new files into parallel directories in $WDIR
3009 # and do our diffs there. This makes it possible to generate
3010 # clean looking diffs which don't have absolute paths present.
3011 #
3012
3013 build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3014 continue
3015
3016 #
3017 # Keep the old PWD around, so we can safely switch back after
3018 # diff generation, such that build_old_new runs in a
3019 # consistent environment.
3020 #
3021 OWD=$PWD
3022 cd $WDIR/raw_files
3023 ofile=old/$PDIR/$PF
3024 nfile=new/$DIR/$F
3025
3026 mv_but_nodiff=
3027 cmp $ofile $nfile > /dev/null 2>&1
3028 if [[ $? == 0 && $rename == 1 ]]; then
3029 mv_but_nodiff=1
3030 fi
3031
3032 #
3033 # If we have old and new versions of the file then run the appropriate
3034 # diffs. This is complicated by a couple of factors:
3035 #
3036 # - renames must be handled specially: we emit a 'remove'
3037 # diff and an 'add' diff
3038 # - new files and deleted files must be handled specially
3039 # - Solaris patch(1m) can't cope with file creation
3040 # (and hence renames) as of this writing.
3041 # - To make matters worse, gnu patch doesn't interpret the
3042 # output of Solaris diff properly when it comes to
3043 # adds and deletes. We need to do some "cleansing"
3044 # transformations:
3045 # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@
3046 # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@
3047 #
3048 cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3049 cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3050
3051 rm -f $WDIR/$DIR/$F.patch
3052 if [[ -z $rename ]]; then
3053 if [ ! -f "$ofile" ]; then
3054 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3055 > $WDIR/$DIR/$F.patch
3056 elif [ ! -f "$nfile" ]; then
3057 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3058 > $WDIR/$DIR/$F.patch
3059 else
3060 diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3061 fi
3062 else
3063 diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3064 > $WDIR/$DIR/$F.patch
3065
3066 diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3067 >> $WDIR/$DIR/$F.patch
3068 fi
3069
3070 #
3071 # Tack the patch we just made onto the accumulated patch for the
3072 # whole wad.
3073 #
3074 cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3075
3076 print " patch\c"
3077
3078 if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3079
3080 ${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3081 diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3082 > $WDIR/$DIR/$F.cdiff.html
3083 print " cdiffs\c"
3084
3085 ${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3086 diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3087 > $WDIR/$DIR/$F.udiff.html
3088
3089 print " udiffs\c"
3090
3091 if [[ -x $WDIFF ]]; then
3092 $WDIFF -c "$COMM" \
3093 -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3094 $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3095 if [[ $? -eq 0 ]]; then
3096 print " wdiffs\c"
3097 else
3098 print " wdiffs[fail]\c"
3099 fi
3100 fi
3101
3102 sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3103 > $WDIR/$DIR/$F.sdiff.html
3104 print " sdiffs\c"
3105
3106 print " frames\c"
3107
3108 rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3109
3110 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3111
3112 elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3113 # renamed file: may also have differences
3114 difflines $ofile $nfile > $WDIR/$DIR/$F.count
3115 elif [[ -f $nfile ]]; then
3116 # new file: count added lines
3117 difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3118 elif [[ -f $ofile ]]; then
3119 # old file: count deleted lines
3120 difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3121 fi
3122
3123 #
3124 # Now we generate the postscript for this file. We generate diffs
3125 # only in the event that there is delta, or the file is new (it seems
3126 # tree-killing to print out the contents of deleted files).
3127 #
3128 if [[ -f $nfile ]]; then
3129 ocr=$ofile
3130 [[ ! -f $ofile ]] && ocr=/dev/null
3131
3132 if [[ -z $mv_but_nodiff ]]; then
3133 textcomm=`getcomments text $P $PP`
3134 if [[ -x $CODEREVIEW ]]; then
3135 $CODEREVIEW -y "$textcomm" \
3136 -e $ocr $nfile \
3137 > /tmp/$$.psfile 2>/dev/null &&
3138 cat /tmp/$$.psfile >> $WDIR/$WNAME.ps
3139 if [[ $? -eq 0 ]]; then
3140 print " ps\c"
3141 else
3142 print " ps[fail]\c"
3143 fi
3144 fi
3145 fi
3146 fi
3147
3148 if [[ -f $ofile ]]; then
3149 source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3150 print " old\c"
3151 fi
3152
3153 if [[ -f $nfile ]]; then
3154 source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3155 print " new\c"
3156 fi
3157
3158 cd $OWD
3159
3160 print
3161 done
3162
3163 frame_nav_js > $WDIR/ancnav.js
3164 frame_navigation > $WDIR/ancnav.html
3165
3166 if [[ ! -f $WDIR/$WNAME.ps ]]; then
3167 print " Generating PDF: Skipped: no output available"
3168 elif [[ -x $CODEREVIEW && -x $PS2PDF ]]; then
3169 print " Generating PDF: \c"
3170 fix_postscript $WDIR/$WNAME.ps | $PS2PDF - > $WDIR/$WNAME.pdf
3171 print "Done."
3172 else
3173 print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'"
3174 fi
3175
3176 # If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3177 # delete it - prevent accidental publishing of closed source
3178
3179 if [[ -n "$Oflag" ]]; then
3180 $FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3181 fi
3182
3183 # Now build the index.html file that contains
3184 # links to the source files and their diffs.
3185
3186 cd $CWS
3187
3188 # Save total changed lines for Code Inspection.
3189 print "$TOTL" > $WDIR/TotalChangedLines
3190
3191 print " index.html: \c"
3192 INDEXFILE=$WDIR/index.html
3193 exec 3<&1 # duplicate stdout to FD3.
3194 exec 1<&- # Close stdout.
3195 exec > $INDEXFILE # Open stdout to index file.
3196
3197 print "$HTML<head>$STDHEAD"
3198 print "<title>$WNAME</title>"
3199 print "</head>"
3200 print "<body id=\"SUNWwebrev\">"
3201 print "<div class=\"summary\">"
3202 print "<h2>Code Review for $WNAME</h2>"
3203
3204 print "<table>"
3205
3206 #
3207 # Get the preparer's name:
3208 #
3209 # If the SCM detected is Mercurial, and the configuration property
3210 # ui.username is available, use that, but be careful to properly escape
3211 # angle brackets (HTML syntax characters) in the email address.
3212 #
3213 # Otherwise, use the current userid in the form "John Doe (jdoe)", but
3214 # to maintain compatibility with passwd(4), we must support '&' substitutions.
3215 #
3216 preparer=
3217 if [[ "$SCM_MODE" == mercurial ]]; then
3218 preparer=`hg showconfig ui.username 2>/dev/null`
3219 if [[ -n "$preparer" ]]; then
3220 preparer="$(echo "$preparer" | html_quote)"
3221 fi
3222 fi
3223 if [[ -z "$preparer" ]]; then
3224 preparer=$(
3225 $PERL -e '
3226 ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3227 if ($login) {
3228 $gcos =~ s/\&/ucfirst($login)/e;
3229 printf "%s (%s)\n", $gcos, $login;
3230 } else {
3231 printf "(unknown)\n";
3232 }
3233 ')
3234 fi
3235
3236 PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3237 print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3238 print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3239 print "</td></tr>"
3240 print "<tr><th>Compare against:</th><td>"
3241 if [[ -n $parent_webrev ]]; then
3242 print "webrev at $parent_webrev"
3243 else
3244 print "${PRETTY_PWS:-$PWS}"
3245 fi
3246 print "</td></tr>"
3247 print "<tr><th>Summary of changes:</th><td>"
3248 printCI $TOTL $TINS $TDEL $TMOD $TUNC
3249 print "</td></tr>"
3250
3251 if [[ -f $WDIR/$WNAME.patch ]]; then
3252 wpatch_url="$(print $WNAME.patch | url_encode)"
3253 print "<tr><th>Patch of changes:</th><td>"
3254 print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3255 fi
3256 if [[ -f $WDIR/$WNAME.pdf ]]; then
3257 wpdf_url="$(print $WNAME.pdf | url_encode)"
3258 print "<tr><th>Printable review:</th><td>"
3259 print "<a href=\"$wpdf_url\">$WNAME.pdf</a></td></tr>"
3260 fi
3261
3262 if [[ -n "$iflag" ]]; then
3263 print "<tr><th>Author comments:</th><td><div>"
3264 cat /tmp/$$.include
3265 print "</div></td></tr>"
3266 fi
3267 print "</table>"
3268 print "</div>"
3269
3270 #
3271 # Second pass through the files: generate the rest of the index file
3272 #
3273 cat $FLIST | while read LINE
3274 do
3275 set - $LINE
3276 P=$1
3277
3278 if [[ $# == 2 ]]; then
3279 PP=$2
3280 oldname="$PP"
3281 else
3282 PP=$P
3283 oldname=""
3284 fi
3285
3286 mv_but_nodiff=
3287 cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3288 if [[ $? == 0 && -n "$oldname" ]]; then
3289 mv_but_nodiff=1
3290 fi
3291
3292 DIR=${P%/*}
3293 if [[ $DIR == $P ]]; then
3294 DIR="." # File at root of workspace
3295 fi
3296
3297 # Avoid processing the same file twice.
3298 # It's possible for renamed files to
3299 # appear twice in the file list
3300
3301 F=$WDIR/$P
3302
3303 print "<p>"
3304
3305 # If there's a diffs file, make diffs links
3306
3307 if [[ -f $F.cdiff.html ]]; then
3308 cdiff_url="$(print $P.cdiff.html | url_encode)"
3309 udiff_url="$(print $P.udiff.html | url_encode)"
3310 print "<a href=\"$cdiff_url\">Cdiffs</a>"
3311 print "<a href=\"$udiff_url\">Udiffs</a>"
3312
3313 if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3314 wdiff_url="$(print $P.wdiff.html | url_encode)"
3315 print "<a href=\"$wdiff_url\">Wdiffs</a>"
3316 fi
3317
3318 sdiff_url="$(print $P.sdiff.html | url_encode)"
3319 print "<a href=\"$sdiff_url\">Sdiffs</a>"
3320
3321 frames_url="$(print $P.frames.html | url_encode)"
3322 print "<a href=\"$frames_url\">Frames</a>"
3323 else
3324 print " ------ ------ ------"
3325
3326 if [[ -x $WDIFF ]]; then
3327 print " ------"
3328 fi
3329
3330 print " ------"
3331 fi
3332
3333 # If there's an old file, make the link
3334
3335 if [[ -f $F-.html ]]; then
3336 oldfile_url="$(print $P-.html | url_encode)"
3337 print "<a href=\"$oldfile_url\">Old</a>"
3338 else
3339 print " ---"
3340 fi
3341
3342 # If there's an new file, make the link
3343
3344 if [[ -f $F.html ]]; then
3345 newfile_url="$(print $P.html | url_encode)"
3346 print "<a href=\"$newfile_url\">New</a>"
3347 else
3348 print " ---"
3349 fi
3350
3351 if [[ -f $F.patch ]]; then
3352 patch_url="$(print $P.patch | url_encode)"
3353 print "<a href=\"$patch_url\">Patch</a>"
3354 else
3355 print " -----"
3356 fi
3357
3358 if [[ -f $WDIR/raw_files/new/$P ]]; then
3359 rawfiles_url="$(print raw_files/new/$P | url_encode)"
3360 print "<a href=\"$rawfiles_url\">Raw</a>"
3361 else
3362 print " ---"
3363 fi
3364
3365 print "<b>$P</b>"
3366
3367 # For renamed files, clearly state whether or not they are modified
3368 if [[ -f "$oldname" ]]; then
3369 if [[ -n "$mv_but_nodiff" ]]; then
3370 print "<i>(copied from $oldname)</i>"
3371 else
3372 print "<i>(copied and modified from $oldname)</i>"
3373 fi
3374 elif [[ -n "$oldname" ]]; then
3375 if [[ -n "$mv_but_nodiff" ]]; then
3376 print "<i>(renamed from $oldname)</i>"
3377 else
3378 print "<i>(renamed and modified from $oldname)</i>"
3379 fi
3380 fi
3381
3382 # If there's an old file, but no new file, the file was deleted
3383 if [[ -f $F-.html && ! -f $F.html ]]; then
3384 print " <i>(deleted)</i>"
3385 fi
3386
3387 #
3388 # Check for usr/closed and deleted_files/usr/closed
3389 #
3390 if [ ! -z "$Oflag" ]; then
3391 if [[ $P == usr/closed/* || \
3392 $P == deleted_files/usr/closed/* ]]; then
3393 print " <i>Closed source: omitted from" \
3394 "this review</i>"
3395 fi
3396 fi
3397
3398 print "</p>"
3399 # Insert delta comments
3400
3401 print "<blockquote><pre>"
3402 getcomments html $P $PP
3403 print "</pre>"
3404
3405 # Add additional comments comment
3406
3407 print "<!-- Add comments to explain changes in $P here -->"
3408
3409 # Add count of changes.
3410
3411 if [[ -f $F.count ]]; then
3412 cat $F.count
3413 rm $F.count
3414 fi
3415
3416 if [[ $SCM_MODE == "mercurial" ||
3417 $SCM_MODE == "unknown" ]]; then
3418
3419 # Include warnings for important file mode situations:
3420 # 1) New executable files
3421 # 2) Permission changes of any kind
3422 # 3) Existing executable files
3423
3424 old_mode=
3425 if [[ -f $WDIR/raw_files/old/$PP ]]; then
3426 old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3427 fi
3428
3429 new_mode=
3430 if [[ -f $WDIR/raw_files/new/$P ]]; then
3431 new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3432 fi
3433
3434 if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3435 print "<span class=\"chmod\">"
3436 print "<p>new executable file: mode $new_mode</p>"
3437 print "</span>"
3438 elif [[ -n "$old_mode" && -n "$new_mode" &&
3439 "$old_mode" != "$new_mode" ]]; then
3440 print "<span class=\"chmod\">"
3441 print "<p>mode change: $old_mode to $new_mode</p>"
3442 print "</span>"
3443 elif [[ "$new_mode" = *[1357]* ]]; then
3444 print "<span class=\"chmod\">"
3445 print "<p>executable file: mode $new_mode</p>"
3446 print "</span>"
3447 fi
3448 fi
3449
3450 print "</blockquote>"
3451 done
3452
3453 print
3454 print
3455 print "<hr></hr>"
3456 print "<p style=\"font-size: small\">"
3457 print "This code review page was prepared using <b>$0</b>."
3458 print "Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3459 print "illumos</a> project. The latest version may be obtained"
3460 print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3461 print "</body>"
3462 print "</html>"
3463
3464 exec 1<&- # Close FD 1.
3465 exec 1<&3 # dup FD 3 to restore stdout.
3466 exec 3<&- # close FD 3.
3467
3468 print "Done."
3469
3470 #
3471 # If remote deletion was specified and fails do not continue.
3472 #
3473 if [[ -n $Dflag ]]; then
3474 delete_webrev 1 1
3475 (( $? == 0 )) || exit $?
3476 fi
3477
3478 if [[ -n $Uflag ]]; then
3479 upload_webrev
3480 exit $?
3481 fi