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