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