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