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