xref: /illumos-gate/usr/src/tools/scripts/webrev.sh (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
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 2019 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# input_cmd | insert_anchors | output_cmd
1284#
1285# Flag blocks of difference with sequentially numbered invisible
1286# anchors.  These are used to drive the frames version of the
1287# sdiffs output.
1288#
1289# NOTE: Anchor zero flags the top of the file irrespective of changes,
1290# an additional anchor is also appended to flag the bottom.
1291#
1292# The script detects changed lines as any line that has a "<span
1293# class=" string embedded (unchanged lines have no class set and are
1294# not part of a <span>.  Blank lines (without a sequence number)
1295# are also detected since they flag lines that have been inserted or
1296# deleted.
1297#
1298function insert_anchors
1299{
1300	$AWK '
1301	function ia() {
1302		printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1303	}
1304
1305	BEGIN {
1306		anc=1;
1307		inblock=1;
1308		printf "<pre>\n";
1309	}
1310	NF == 0 || /^<span class=/ {
1311		if (inblock == 0) {
1312			ia();
1313			inblock=1;
1314		}
1315		print;
1316		next;
1317	}
1318	{
1319		inblock=0;
1320		print;
1321	}
1322	END {
1323		ia();
1324
1325		printf "<b style=\"font-size: large; color: red\">";
1326		printf "--- EOF ---</b>"
1327		for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1328		printf "</pre>"
1329		printf "<form name=\"eof\">";
1330		printf "<input name=\"value\" value=\"%d\" " \
1331		    "type=\"hidden\"></input>", anc - 1;
1332		printf "</form>";
1333	}
1334	' $1
1335}
1336
1337
1338#
1339# relative_dir
1340#
1341# Print a relative return path from $1 to $2.  For example if
1342# $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1343# this function would print "../../../../".
1344#
1345# In the event that $1 is not in $2 a warning is printed to stderr,
1346# and $2 is returned-- the result of this is that the resulting webrev
1347# is not relocatable.
1348#
1349function relative_dir
1350{
1351	typeset cur="${1##$2?(/)}"
1352
1353	#
1354	# If the first path was specified absolutely, and it does
1355	# not start with the second path, it's an error.
1356	#
1357	if [[ "$cur" = "/${1#/}" ]]; then
1358		# Should never happen.
1359		print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1360		print -u2 "to \"$2\".  Check input paths.  Framed webrev "
1361		print -u2 "will not be relocatable!"
1362		print $2
1363		return
1364	fi
1365
1366	#
1367	# This is kind of ugly.  The sed script will do the following:
1368	#
1369	# 1. Strip off a leading "." or "./": this is important to get
1370	#    the correct arcnav links for files in $WDIR.
1371	# 2. Strip off a trailing "/": this is not strictly necessary,
1372	#    but is kind of nice, since it doesn't end up in "//" at
1373	#    the end of a relative path.
1374	# 3. Replace all remaining sequences of non-"/" with "..": the
1375	#    assumption here is that each dirname represents another
1376	#    level of relative separation.
1377	# 4. Append a trailing "/" only for non-empty paths: this way
1378	#    the caller doesn't need to duplicate this logic, and does
1379	#    not end up using $RTOP/file for files in $WDIR.
1380	#
1381	print $cur | $SED -e '{
1382		s:^\./*::
1383		s:/$::
1384		s:[^/][^/]*:..:g
1385		s:^\(..*\)$:\1/:
1386	}'
1387}
1388
1389#
1390# frame_nav_js
1391#
1392# Emit javascript for frame navigation
1393#
1394function frame_nav_js
1395{
1396cat << \EOF
1397var myInt;
1398var scrolling = 0;
1399var sfactor = 3;
1400var scount = 10;
1401
1402function scrollByPix()
1403{
1404	if (scount <= 0) {
1405		sfactor *= 1.2;
1406		scount = 10;
1407	}
1408	parent.lhs.scrollBy(0, sfactor);
1409	parent.rhs.scrollBy(0, sfactor);
1410	scount--;
1411}
1412
1413function scrollToAnc(num)
1414{
1415	// Update the value of the anchor in the form which we use as
1416	// storage for this value.  setAncValue() will take care of
1417	// correcting for overflow and underflow of the value and return
1418	// us the new value.
1419	num = setAncValue(num);
1420
1421	// Set location and scroll back a little to expose previous
1422	// lines.
1423	//
1424	// Note that this could be improved: it is possible although
1425	// complex to compute the x and y position of an anchor, and to
1426	// scroll to that location directly.
1427	//
1428	parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1429	parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1430
1431	parent.lhs.scrollBy(0, -30);
1432	parent.rhs.scrollBy(0, -30);
1433}
1434
1435function getAncValue()
1436{
1437	return (parseInt(parent.nav.document.diff.real.value));
1438}
1439
1440function setAncValue(val)
1441{
1442	if (val <= 0) {
1443		val = 0;
1444		parent.nav.document.diff.real.value = val;
1445		parent.nav.document.diff.display.value = "BOF";
1446		return (val);
1447	}
1448
1449	//
1450	// The way we compute the max anchor value is to stash it
1451	// inline in the left and right hand side pages-- it's the same
1452	// on each side, so we pluck from the left.
1453	//
1454	maxval = parent.lhs.document.eof.value.value;
1455	if (val < maxval) {
1456		parent.nav.document.diff.real.value = val;
1457		parent.nav.document.diff.display.value = val.toString();
1458		return (val);
1459	}
1460
1461	// this must be: val >= maxval
1462	val = maxval;
1463	parent.nav.document.diff.real.value = val;
1464	parent.nav.document.diff.display.value = "EOF";
1465	return (val);
1466}
1467
1468function stopScroll()
1469{
1470	if (scrolling == 1) {
1471		clearInterval(myInt);
1472		scrolling = 0;
1473	}
1474}
1475
1476function startScroll()
1477{
1478	stopScroll();
1479	scrolling = 1;
1480	myInt = setInterval("scrollByPix()", 10);
1481}
1482
1483function handlePress(b)
1484{
1485	switch (b) {
1486	case 1:
1487		scrollToAnc(-1);
1488		break;
1489	case 2:
1490		scrollToAnc(getAncValue() - 1);
1491		break;
1492	case 3:
1493		sfactor = -3;
1494		startScroll();
1495		break;
1496	case 4:
1497		sfactor = 3;
1498		startScroll();
1499		break;
1500	case 5:
1501		scrollToAnc(getAncValue() + 1);
1502		break;
1503	case 6:
1504		scrollToAnc(999999);
1505		break;
1506	}
1507}
1508
1509function handleRelease(b)
1510{
1511	stopScroll();
1512}
1513
1514function keypress(ev)
1515{
1516	var keynum;
1517	var keychar;
1518
1519	if (window.event) { // IE
1520		keynum = ev.keyCode;
1521	} else if (ev.which) { // non-IE
1522		keynum = ev.which;
1523	}
1524
1525	keychar = String.fromCharCode(keynum);
1526
1527	if (keychar == "k") {
1528		handlePress(2);
1529		return (0);
1530	} else if (keychar == "j" || keychar == " ") {
1531		handlePress(5);
1532		return (0);
1533	}
1534
1535	return (1);
1536}
1537
1538function ValidateDiffNum()
1539{
1540	var val;
1541	var i;
1542
1543	val = parent.nav.document.diff.display.value;
1544	if (val == "EOF") {
1545		scrollToAnc(999999);
1546		return;
1547	}
1548
1549	if (val == "BOF") {
1550		scrollToAnc(0);
1551		return;
1552	}
1553
1554	i = parseInt(val);
1555	if (isNaN(i)) {
1556		parent.nav.document.diff.display.value = getAncValue();
1557	} else {
1558		scrollToAnc(i);
1559	}
1560
1561	return (false);
1562}
1563EOF
1564}
1565
1566#
1567# frame_navigation
1568#
1569# Output anchor navigation file for framed sdiffs.
1570#
1571function frame_navigation
1572{
1573	print "$HTML<head>$STDHEAD"
1574
1575	cat << \EOF
1576<title>Anchor Navigation</title>
1577<meta http-equiv="Content-Script-Type" content="text/javascript">
1578<meta http-equiv="Content-Type" content="text/html">
1579
1580<style type="text/css">
1581    div.button td { padding-left: 5px; padding-right: 5px;
1582		    background-color: #eee; text-align: center;
1583		    border: 1px #444 outset; cursor: pointer; }
1584    div.button a { font-weight: bold; color: black }
1585    div.button td:hover { background: #ffcc99; }
1586</style>
1587EOF
1588
1589	print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1590
1591	cat << \EOF
1592</head>
1593<body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1594	onkeypress="keypress(event);">
1595    <noscript lang="javascript">
1596      <center>
1597	<p><big>Framed Navigation controls require Javascript</big><br></br>
1598	Either this browser is incompatable or javascript is not enabled</p>
1599      </center>
1600    </noscript>
1601    <table width="100%" border="0" align="center">
1602	<tr>
1603          <td valign="middle" width="25%">Diff navigation:
1604          Use 'j' and 'k' for next and previous diffs; or use buttons
1605          at right</td>
1606	  <td align="center" valign="top" width="50%">
1607	    <div class="button">
1608	      <table border="0" align="center">
1609                  <tr>
1610		    <td>
1611		      <a onMouseDown="handlePress(1);return true;"
1612			 onMouseUp="handleRelease(1);return true;"
1613			 onMouseOut="handleRelease(1);return true;"
1614			 onClick="return false;"
1615			 title="Go to Beginning Of file">BOF</a></td>
1616		    <td>
1617		      <a onMouseDown="handlePress(3);return true;"
1618			 onMouseUp="handleRelease(3);return true;"
1619			 onMouseOut="handleRelease(3);return true;"
1620			 title="Scroll Up: Press and Hold to accelerate"
1621			 onClick="return false;">Scroll Up</a></td>
1622		    <td>
1623		      <a onMouseDown="handlePress(2);return true;"
1624			 onMouseUp="handleRelease(2);return true;"
1625			 onMouseOut="handleRelease(2);return true;"
1626			 title="Go to previous Diff"
1627			 onClick="return false;">Prev Diff</a>
1628		    </td></tr>
1629
1630		  <tr>
1631		    <td>
1632		      <a onMouseDown="handlePress(6);return true;"
1633			 onMouseUp="handleRelease(6);return true;"
1634			 onMouseOut="handleRelease(6);return true;"
1635			 onClick="return false;"
1636			 title="Go to End Of File">EOF</a></td>
1637		    <td>
1638		      <a onMouseDown="handlePress(4);return true;"
1639			 onMouseUp="handleRelease(4);return true;"
1640			 onMouseOut="handleRelease(4);return true;"
1641			 title="Scroll Down: Press and Hold to accelerate"
1642			 onClick="return false;">Scroll Down</a></td>
1643		    <td>
1644		      <a onMouseDown="handlePress(5);return true;"
1645			 onMouseUp="handleRelease(5);return true;"
1646			 onMouseOut="handleRelease(5);return true;"
1647			 title="Go to next Diff"
1648			 onClick="return false;">Next Diff</a></td>
1649		  </tr>
1650              </table>
1651	    </div>
1652	  </td>
1653	  <th valign="middle" width="25%">
1654	    <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1655		<input name="display" value="BOF" size="8" type="text"></input>
1656		<input name="real" value="0" size="8" type="hidden"></input>
1657	    </form>
1658	  </th>
1659	</tr>
1660    </table>
1661  </body>
1662</html>
1663EOF
1664}
1665
1666
1667
1668#
1669# diff_to_html <filename> <filepath> { U | C } <comment>
1670#
1671# Processes the output of diff to produce an HTML file representing either
1672# context or unified diffs.
1673#
1674diff_to_html()
1675{
1676	TNAME=$1
1677	TPATH=$2
1678	DIFFTYPE=$3
1679	COMMENT=$4
1680
1681	print "$HTML<head>$STDHEAD"
1682	print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1683
1684	if [[ $DIFFTYPE == "U" ]]; then
1685		print "$UDIFFCSS"
1686	fi
1687
1688	cat <<-EOF
1689	</head>
1690	<body id="SUNWwebrev">
1691        <a class="print" href="javascript:print()">Print this page</a>
1692	<pre>$COMMENT</pre>
1693        <pre>
1694	EOF
1695
1696	html_quote | $AWK '
1697	/^--- new/	{ next }
1698	/^\+\+\+ new/	{ next }
1699	/^--- old/	{ next }
1700	/^\*\*\* old/	{ next }
1701	/^\*\*\*\*/	{ next }
1702	/^-------/	{ printf "<center><h1>%s</h1></center>\n", $0; next }
1703	/^\@\@.*\@\@$/	{ printf "</pre><hr></hr><pre>\n";
1704			  printf "<span class=\"newmarker\">%s</span>\n", $0;
1705			  next}
1706
1707	/^\*\*\*/	{ printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1708			  next}
1709	/^---/		{ printf "<span class=\"newmarker\">%s</span>\n", $0;
1710			  next}
1711	/^\+/		{printf "<span class=\"new\">%s</span>\n", $0; next}
1712	/^!/		{printf "<span class=\"changed\">%s</span>\n", $0; next}
1713	/^-/		{printf "<span class=\"removed\">%s</span>\n", $0; next}
1714			{printf "%s\n", $0; next}
1715	'
1716
1717	print "</pre></body></html>\n"
1718}
1719
1720
1721#
1722# source_to_html { new | old } <filename>
1723#
1724# Process a plain vanilla source file to transform it into an HTML file.
1725#
1726source_to_html()
1727{
1728	WHICH=$1
1729	TNAME=$2
1730
1731	print "$HTML<head>$STDHEAD"
1732	print "<title>$WNAME $WHICH $TNAME</title>"
1733	print "<body id=\"SUNWwebrev\">"
1734	print "<pre>"
1735	html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1736	print "</pre></body></html>"
1737}
1738
1739#
1740# comments_from_wx {text|html} filepath
1741#
1742# Given the pathname of a file, find its location in a "wx" active
1743# file list and print the following comment.  Output is either text or
1744# HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1745# are turned into URLs.
1746#
1747comments_from_wx()
1748{
1749	typeset fmt=$1
1750	typeset p=$2
1751
1752	comm=`$AWK '
1753	$1 == "'$p'" {
1754		do getline ; while (NF > 0)
1755		getline
1756		while (NF > 0) { print ; getline }
1757		exit
1758	}' < $wxfile`
1759
1760	if [[ -z $comm ]]; then
1761		comm="*** NO COMMENTS ***"
1762	fi
1763
1764	if [[ $fmt == "text" ]]; then
1765		print -- "$comm"
1766		return
1767	fi
1768
1769	print -- "$comm" | html_quote | its2url
1770
1771}
1772
1773#
1774# getcomments {text|html} filepath parentpath
1775#
1776# Fetch the comments depending on what SCM mode we're in.
1777#
1778getcomments()
1779{
1780	typeset fmt=$1
1781	typeset p=$2
1782	typeset pp=$3
1783
1784	if [[ -n $Nflag ]]; then
1785		return
1786	fi
1787
1788	if [[ -n $wxfile ]]; then
1789		comments_from_wx $fmt $p
1790	fi
1791}
1792
1793#
1794# printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
1795#
1796# Print out Code Inspection figures similar to sccs-prt(1) format.
1797#
1798function printCI
1799{
1800	integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
1801	typeset str
1802	if (( tot == 1 )); then
1803		str="line"
1804	else
1805		str="lines"
1806	fi
1807	printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
1808	    $tot $str $ins $del $mod $unc
1809}
1810
1811
1812#
1813# difflines <oldfile> <newfile>
1814#
1815# Calculate and emit number of added, removed, modified and unchanged lines,
1816# and total lines changed, the sum of added + removed + modified.
1817#
1818function difflines
1819{
1820	integer tot mod del ins unc err
1821	typeset filename
1822
1823	eval $( diff -e $1 $2 | $AWK '
1824	# Change range of lines: N,Nc
1825	/^[0-9]*,[0-9]*c$/ {
1826		n=split(substr($1,1,length($1)-1), counts, ",");
1827		if (n != 2) {
1828			error=2
1829			exit;
1830		}
1831		#
1832		# 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
1833		# following would be 5 - 3 = 2! Hence +1 for correction.
1834		#
1835		r=(counts[2]-counts[1])+1;
1836
1837		#
1838		# Now count replacement lines: each represents a change instead
1839		# of a delete, so increment c and decrement r.
1840		#
1841		while (getline != /^\.$/) {
1842			c++;
1843			r--;
1844		}
1845		#
1846		# If there were more replacement lines than original lines,
1847		# then r will be negative; in this case there are no deletions,
1848		# but there are r changes that should be counted as adds, and
1849		# since r is negative, subtract it from a and add it to c.
1850		#
1851		if (r < 0) {
1852			a-=r;
1853			c+=r;
1854		}
1855
1856		#
1857		# If there were more original lines than replacement lines, then
1858		# r will be positive; in this case, increment d by that much.
1859		#
1860		if (r > 0) {
1861			d+=r;
1862		}
1863		next;
1864	}
1865
1866	# Change lines: Nc
1867	/^[0-9].*c$/ {
1868		# The first line is a replacement; any more are additions.
1869		if (getline != /^\.$/) {
1870			c++;
1871			while (getline != /^\.$/) a++;
1872		}
1873		next;
1874	}
1875
1876	# Add lines: both Na and N,Na
1877	/^[0-9].*a$/ {
1878		while (getline != /^\.$/) a++;
1879		next;
1880	}
1881
1882	# Delete range of lines: N,Nd
1883	/^[0-9]*,[0-9]*d$/ {
1884		n=split(substr($1,1,length($1)-1), counts, ",");
1885		if (n != 2) {
1886			error=2
1887			exit;
1888		}
1889		#
1890		# 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
1891		# following would be 5 - 3 = 2! Hence +1 for correction.
1892		#
1893		r=(counts[2]-counts[1])+1;
1894		d+=r;
1895		next;
1896	}
1897
1898	# Delete line: Nd.   For example 10d says line 10 is deleted.
1899	/^[0-9]*d$/ {d++; next}
1900
1901	# Should not get here!
1902	{
1903		error=1;
1904		exit;
1905	}
1906
1907	# Finish off - print results
1908	END {
1909		printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
1910		    (c+d+a), c, d, a, error);
1911	}' )
1912
1913	# End of $AWK, Check to see if any trouble occurred.
1914	if (( $? > 0 || err > 0 )); then
1915		print "Unexpected Error occurred reading" \
1916		    "\`diff -e $1 $2\`: \$?=$?, err=" $err
1917		return
1918	fi
1919
1920	# Accumulate totals
1921	(( TOTL += tot ))
1922	(( TMOD += mod ))
1923	(( TDEL += del ))
1924	(( TINS += ins ))
1925	# Calculate unchanged lines
1926	unc=`wc -l < $1`
1927	if (( unc > 0 )); then
1928		(( unc -= del + mod ))
1929		(( TUNC += unc ))
1930	fi
1931	# print summary
1932	print "<span class=\"lineschanged\">"
1933	printCI $tot $ins $del $mod $unc
1934	print "</span>"
1935}
1936
1937
1938#
1939# flist_from_wx
1940#
1941# Sets up webrev to source its information from a wx-formatted file.
1942# Sets the global 'wxfile' variable.
1943#
1944function flist_from_wx
1945{
1946	typeset argfile=$1
1947	if [[ -n ${argfile%%/*} ]]; then
1948		#
1949		# If the wx file pathname is relative then make it absolute
1950		# because the webrev does a "cd" later on.
1951		#
1952		wxfile=$PWD/$argfile
1953	else
1954		wxfile=$argfile
1955	fi
1956
1957	$AWK '{ c = 1; print;
1958	  while (getline) {
1959		if (NF == 0) { c = -c; continue }
1960		if (c > 0) print
1961	  }
1962	}' $wxfile > $FLIST
1963
1964	print " Done."
1965}
1966
1967#
1968# Transform a specified 'git log' output format into a wx-like active list.
1969#
1970function git_wxfile
1971{
1972	typeset child="$1"
1973	typeset parent="$2"
1974
1975	TMPFLIST=/tmp/$$.active
1976	$PERL -e 'my (%files, %realfiles, $msg);
1977	my $parent = $ARGV[0];
1978	my $child = $ARGV[1];
1979
1980	open(F, "git diff -M --name-status $parent..$child |");
1981	while (<F>) {
1982	    chomp;
1983	    if (/^R(\d+)\s+([^ ]+)\s+([^ ]+)/) { # rename
1984		if ($1 >= 75) {		 # Probably worth treating as a rename
1985		    $realfiles{$3} = $2;
1986		} else {
1987		    $realfiles{$3} = $3;
1988		    $realfiles{$2} = $2;
1989		}
1990	    } else {
1991		my $f = (split /\s+/, $_)[1];
1992		$realfiles{$f} = $f;
1993	    }
1994	}
1995	close(F);
1996
1997	my $state = 1;		    # 0|comments, 1|files
1998	open(F, "git whatchanged --pretty=format:%B $parent..$child |");
1999	while (<F>) {
2000	    chomp;
2001	    if (/^:[0-9]{6}/) {
2002		my ($unused, $fname, $fname2) = split(/\t/, $_);
2003		$fname = $fname2 if defined($fname2);
2004		next if !defined($realfiles{$fname}); # No real change
2005		$state = 1;
2006		chomp $msg;
2007		$files{$fname} .= $msg;
2008	    } else {
2009		if ($state == 1) {
2010		    $state = 0;
2011		    $msg = /^\n/ ? "" : "\n";
2012		}
2013		$msg .= "$_\n" if ($_);
2014	    }
2015	}
2016	close(F);
2017
2018	for (sort keys %files) {
2019	    if ($realfiles{$_} ne $_) {
2020		print "$_ $realfiles{$_}\n$files{$_}\n\n";
2021	    } else {
2022		print "$_\n$files{$_}\n\n"
2023	    }
2024	}' ${parent} ${child} > $TMPFLIST
2025
2026	wxfile=$TMPFLIST
2027}
2028
2029#
2030# flist_from_git
2031# Build a wx-style active list, and hand it off to flist_from_wx
2032#
2033function flist_from_git
2034{
2035	typeset child=$1
2036	typeset parent=$2
2037
2038	print " File list from: git ...\c"
2039	git_wxfile "$child" "$parent";
2040
2041	# flist_from_wx prints the Done, so we don't have to.
2042	flist_from_wx $TMPFLIST
2043}
2044
2045#
2046# flist_from_subversion
2047#
2048# Generate the file list by extracting file names from svn status.
2049#
2050function flist_from_subversion
2051{
2052	CWS=$1
2053	OLDPWD=$2
2054
2055	cd $CWS
2056	print -u2 " File list from: svn status ... \c"
2057	svn status | $AWK '/^[ACDMR]/ { print $NF }' > $FLIST
2058	print -u2 " Done."
2059	cd $OLDPWD
2060}
2061
2062function env_from_flist
2063{
2064	[[ -r $FLIST ]] || return
2065
2066	#
2067	# Use "eval" to set env variables that are listed in the file
2068	# list.  Then copy those into our local versions of those
2069	# variables if they have not been set already.
2070	#
2071	eval `$SED -e "s/#.*$//" $FLIST | $GREP = `
2072
2073	if [[ -z $codemgr_ws && -n $CODEMGR_WS ]]; then
2074		codemgr_ws=$CODEMGR_WS
2075		export CODEMGR_WS
2076	fi
2077
2078	#
2079	# Check to see if CODEMGR_PARENT is set in the flist file.
2080	#
2081	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2082		codemgr_parent=$CODEMGR_PARENT
2083		export CODEMGR_PARENT
2084	fi
2085}
2086
2087function look_for_prog
2088{
2089	typeset path
2090	typeset ppath
2091	typeset progname=$1
2092
2093	ppath=$PATH
2094	ppath=$ppath:/usr/sfw/bin:/usr/bin:/usr/sbin
2095	ppath=$ppath:/opt/onbld/bin
2096	ppath=$ppath:/opt/onbld/bin/`uname -p`
2097
2098	PATH=$ppath prog=`whence $progname`
2099	if [[ -n $prog ]]; then
2100		print $prog
2101	fi
2102}
2103
2104function get_file_mode
2105{
2106	$PERL -e '
2107		if (@stat = stat($ARGV[0])) {
2108			$mode = $stat[2] & 0777;
2109			printf "%03o\n", $mode;
2110			exit 0;
2111		} else {
2112			exit 1;
2113		}
2114	    ' $1
2115}
2116
2117function build_old_new_git
2118{
2119	typeset olddir="$1"
2120	typeset newdir="$2"
2121	typeset o_mode=
2122	typeset n_mode=
2123	typeset o_object=
2124	typeset n_object=
2125	typeset OWD=$PWD
2126	typeset file
2127	typeset type
2128
2129	cd $CWS
2130
2131	#
2132	# Get old file and its mode from the git object tree
2133	#
2134	if [[ "$PDIR" == "." ]]; then
2135		file="$PF"
2136	else
2137		file="$PDIR/$PF"
2138	fi
2139
2140	if [[ -n $parent_webrev && -e $PWS/$PDIR/$PF ]]; then
2141		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2142	else
2143		$GIT ls-tree $GIT_PARENT $file | read o_mode type o_object junk
2144		$GIT cat-file $type $o_object > $olddir/$file 2>/dev/null
2145
2146		if (( $? != 0 )); then
2147			rm -f $olddir/$file
2148		elif [[ -n $o_mode ]]; then
2149			# Strip the first 3 digits, to get a regular octal mode
2150			o_mode=${o_mode/???/}
2151			chmod $o_mode $olddir/$file
2152		else
2153			# should never happen
2154			print -u2 "ERROR: set mode of $olddir/$file"
2155		fi
2156	fi
2157
2158	#
2159	# new version of the file.
2160	#
2161	if [[ "$DIR" == "." ]]; then
2162		file="$F"
2163	else
2164		file="$DIR/$F"
2165	fi
2166	rm -rf $newdir/$file
2167
2168        if [[ -e $CWS/$DIR/$F ]]; then
2169		cp $CWS/$DIR/$F $newdir/$DIR/$F
2170		chmod $(get_file_mode $CWS/$DIR/$F) $newdir/$DIR/$F
2171        fi
2172	cd $OWD
2173}
2174
2175function build_old_new_subversion
2176{
2177	typeset olddir="$1"
2178	typeset newdir="$2"
2179
2180	# Snag new version of file.
2181	rm -f $newdir/$DIR/$F
2182	[[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2183
2184	if [[ -n $PWS && -e $PWS/$PDIR/$PF ]]; then
2185		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2186	else
2187		# Get the parent's version of the file.
2188		svn status $CWS/$DIR/$F | read stat file
2189		if [[ $stat != "A" ]]; then
2190			svn cat -r BASE $CWS/$DIR/$F > $olddir/$PDIR/$PF
2191		fi
2192	fi
2193}
2194
2195function build_old_new_unknown
2196{
2197	typeset olddir="$1"
2198	typeset newdir="$2"
2199
2200	#
2201	# Snag new version of file.
2202	#
2203	rm -f $newdir/$DIR/$F
2204	[[ -e $CWS/$DIR/$F ]] && cp $CWS/$DIR/$F $newdir/$DIR/$F
2205
2206	#
2207	# Snag the parent's version of the file.
2208	#
2209	if [[ -f $PWS/$PDIR/$PF ]]; then
2210		rm -f $olddir/$PDIR/$PF
2211		cp $PWS/$PDIR/$PF $olddir/$PDIR/$PF
2212	fi
2213}
2214
2215function build_old_new
2216{
2217	typeset WDIR=$1
2218	typeset PWS=$2
2219	typeset PDIR=$3
2220	typeset PF=$4
2221	typeset CWS=$5
2222	typeset DIR=$6
2223	typeset F=$7
2224
2225	typeset olddir="$WDIR/raw_files/old"
2226	typeset newdir="$WDIR/raw_files/new"
2227
2228	mkdir -p $olddir/$PDIR
2229	mkdir -p $newdir/$DIR
2230
2231	if [[ $SCM_MODE == "git" ]]; then
2232		build_old_new_git "$olddir" "$newdir"
2233	elif [[ $SCM_MODE == "subversion" ]]; then
2234		build_old_new_subversion "$olddir" "$newdir"
2235	elif [[ $SCM_MODE == "unknown" ]]; then
2236		build_old_new_unknown "$olddir" "$newdir"
2237	fi
2238
2239	if [[ ! -f $olddir/$PDIR/$PF && ! -f $newdir/$DIR/$F ]]; then
2240		print "*** Error: file not in parent or child"
2241		return 1
2242	fi
2243	return 0
2244}
2245
2246
2247#
2248# Usage message.
2249#
2250function usage
2251{
2252	print 'Usage:\twebrev [common-options]
2253	webrev [common-options] ( <file> | - )
2254	webrev [common-options] -w <wx file>
2255
2256Options:
2257	-c <revision>: generate webrev for single revision (git only)
2258	-C <filename>: Use <filename> for the information tracking configuration.
2259	-D: delete remote webrev
2260	-h <revision>: specify "head" revision for comparison (git only)
2261	-i <filename>: Include <filename> in the index.html file.
2262	-I <filename>: Use <filename> for the information tracking registry.
2263	-n: do not generate the webrev (useful with -U)
2264	-O: Print bugids/arc cases suitable for OpenSolaris.
2265	-o <outdir>: Output webrev to specified directory.
2266	-p <compare-against>: Use specified parent wkspc or basis for comparison
2267	-t <remote_target>: Specify remote destination for webrev upload
2268	-U: upload the webrev to remote destination
2269	-w <wxfile>: Use specified wx active file.
2270
2271Environment:
2272	WDIR: Control the output directory.
2273	WEBREV_TRASH_DIR: Set directory for webrev delete.
2274
2275SCM Environment:
2276	CODEMGR_WS: Workspace location.
2277	CODEMGR_PARENT: Parent workspace location.
2278'
2279
2280	exit 2
2281}
2282
2283#
2284#
2285# Main program starts here
2286#
2287#
2288
2289trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15
2290
2291set +o noclobber
2292
2293PATH=$(/bin/dirname "$(whence $0)"):$PATH
2294
2295[[ -z $WDIFF ]] && WDIFF=`look_for_prog wdiff`
2296[[ -z $WX ]] && WX=`look_for_prog wx`
2297[[ -z $GIT ]] && GIT=`look_for_prog git`
2298[[ -z $WHICH_SCM ]] && WHICH_SCM=`look_for_prog which_scm`
2299[[ -z $PERL ]] && PERL=`look_for_prog perl`
2300[[ -z $RSYNC ]] && RSYNC=`look_for_prog rsync`
2301[[ -z $SCCS ]] && SCCS=`look_for_prog sccs`
2302[[ -z $AWK ]] && AWK=`look_for_prog nawk`
2303[[ -z $AWK ]] && AWK=`look_for_prog gawk`
2304[[ -z $AWK ]] && AWK=`look_for_prog awk`
2305[[ -z $SCP ]] && SCP=`look_for_prog scp`
2306[[ -z $SED ]] && SED=`look_for_prog sed`
2307[[ -z $SFTP ]] && SFTP=`look_for_prog sftp`
2308[[ -z $SORT ]] && SORT=`look_for_prog sort`
2309[[ -z $MKTEMP ]] && MKTEMP=`look_for_prog mktemp`
2310[[ -z $GREP ]] && GREP=`look_for_prog grep`
2311[[ -z $FIND ]] && FIND=`look_for_prog find`
2312[[ -z $MANDOC ]] && MANDOC=`look_for_prog mandoc`
2313[[ -z $COL ]] && COL=`look_for_prog col`
2314
2315# set name of trash directory for remote webrev deletion
2316TRASH_DIR=".trash"
2317[[ -n $WEBREV_TRASH_DIR ]] && TRASH_DIR=$WEBREV_TRASH_DIR
2318
2319if [[ ! -x $PERL ]]; then
2320	print -u2 "Error: No perl interpreter found.  Exiting."
2321	exit 1
2322fi
2323
2324if [[ ! -x $WHICH_SCM ]]; then
2325	print -u2 "Error: Could not find which_scm.  Exiting."
2326	exit 1
2327fi
2328
2329[[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found."
2330
2331# Declare global total counters.
2332integer TOTL TINS TDEL TMOD TUNC
2333
2334# default remote host for upload/delete
2335typeset -r DEFAULT_REMOTE_HOST="cr.opensolaris.org"
2336# prefixes for upload targets
2337typeset -r rsync_prefix="rsync://"
2338typeset -r ssh_prefix="ssh://"
2339
2340cflag=
2341Cflag=
2342Dflag=
2343flist_mode=
2344flist_file=
2345hflag=
2346iflag=
2347Iflag=
2348lflag=
2349Nflag=
2350nflag=
2351Oflag=
2352oflag=
2353pflag=
2354tflag=
2355uflag=
2356Uflag=
2357wflag=
2358remote_target=
2359
2360while getopts "c:C:Dh:i:I:lnNo:Op:t:Uw" opt
2361do
2362	case $opt in
2363	c)	cflag=1
2364		codemgr_head=$OPTARG
2365		codemgr_parent=$OPTARG~1;;
2366
2367	C)	Cflag=1
2368		ITSCONF=$OPTARG;;
2369
2370	D)	Dflag=1;;
2371
2372	h)	hflag=1
2373		codemgr_head=$OPTARG;;
2374
2375	i)	iflag=1
2376		INCLUDE_FILE=$OPTARG;;
2377
2378	I)	Iflag=1
2379		ITSREG=$OPTARG;;
2380
2381	N)	Nflag=1;;
2382
2383	n)	nflag=1;;
2384
2385	O)	Oflag=1;;
2386
2387	o)	oflag=1
2388		# Strip the trailing slash to correctly form remote target.
2389		WDIR=${OPTARG%/};;
2390
2391	p)	pflag=1
2392		codemgr_parent=$OPTARG;;
2393
2394	t)	tflag=1
2395		remote_target=$OPTARG;;
2396
2397	U)	Uflag=1;;
2398
2399	w)	wflag=1;;
2400
2401	?)	usage;;
2402	esac
2403done
2404
2405FLIST=/tmp/$$.flist
2406
2407if [[ -n $wflag && -n $lflag ]]; then
2408	usage
2409fi
2410
2411# more sanity checking
2412if [[ -n $nflag && -z $Uflag ]]; then
2413	print "it does not make sense to skip webrev generation" \
2414	    "without -U"
2415	exit 1
2416fi
2417
2418if [[ -n $tflag && -z $Uflag && -z $Dflag ]]; then
2419	echo "remote target has to be used only for upload or delete"
2420	exit 1
2421fi
2422
2423#
2424# For the invocation "webrev -n -U" with no other options, webrev will assume
2425# that the webrev exists in ${CWS}/webrev, but will upload it using the name
2426# $(basename ${CWS}).  So we need to get CWS set before we skip any remaining
2427# logic.
2428#
2429$WHICH_SCM | read SCM_MODE junk || exit 1
2430
2431if [[ $SCM_MODE == "git" ]]; then
2432	#
2433	# Git priorities:
2434	# 1. git rev-parse --git-dir from CODEMGR_WS environment variable
2435	# 2. git rev-parse --git-dir from directory of invocation
2436	#
2437	[[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2438	    codemgr_ws=$($GIT --git-dir=$CODEMGR_WS/.git rev-parse --git-dir \
2439		2>/dev/null)
2440	[[ -z $codemgr_ws ]] && \
2441	    codemgr_ws=$($GIT rev-parse --git-dir 2>/dev/null)
2442
2443	if [[ "$codemgr_ws" == ".git" ]]; then
2444		codemgr_ws="${PWD}/${codemgr_ws}"
2445	fi
2446
2447	if [[ "$codemgr_ws" = *"/.git" ]]; then
2448		codemgr_ws=$(dirname $codemgr_ws) # Lose the '/.git'
2449	fi
2450	CWS="$codemgr_ws"
2451elif [[ $SCM_MODE == "subversion" ]]; then
2452	#
2453	# Subversion priorities:
2454	# 1. CODEMGR_WS from environment
2455	# 2. Relative path from current directory to SVN repository root
2456	#
2457	if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2458		CWS=$CODEMGR_WS
2459	else
2460		svn info | while read line; do
2461			if [[ $line == "URL: "* ]]; then
2462				url=${line#URL: }
2463			elif [[ $line == "Repository Root: "* ]]; then
2464				repo=${line#Repository Root: }
2465			fi
2466		done
2467
2468		rel=${url#$repo}
2469		CWS=${PWD%$rel}
2470	fi
2471fi
2472
2473#
2474# If no SCM has been determined, take either the environment setting
2475# setting for CODEMGR_WS, or the current directory if that wasn't set.
2476#
2477if [[ -z ${CWS} ]]; then
2478	CWS=${CODEMGR_WS:-.}
2479fi
2480
2481#
2482# If the command line options indicate no webrev generation, either
2483# explicitly (-n) or implicitly (-D but not -U), then there's a whole
2484# ton of logic we can skip.
2485#
2486# Instead of increasing indentation, we intentionally leave this loop
2487# body open here, and exit via break from multiple points within.
2488# Search for DO_EVERYTHING below to find the break points and closure.
2489#
2490for do_everything in 1; do
2491
2492# DO_EVERYTHING: break point
2493if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2494	break
2495fi
2496
2497#
2498# If this manually set as the parent, and it appears to be an earlier webrev,
2499# then note that fact and set the parent to the raw_files/new subdirectory.
2500#
2501if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2502	parent_webrev=$(readlink -f "$codemgr_parent")
2503	codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new")
2504fi
2505
2506if [[ -z $wflag && -z $lflag ]]; then
2507	shift $(($OPTIND - 1))
2508
2509	if [[ $1 == "-" ]]; then
2510		cat > $FLIST
2511		flist_mode="stdin"
2512		flist_done=1
2513		shift
2514	elif [[ -n $1 ]]; then
2515		if [[ ! -r $1 ]]; then
2516			print -u2 "$1: no such file or not readable"
2517			usage
2518		fi
2519		cat $1 > $FLIST
2520		flist_mode="file"
2521		flist_file=$1
2522		flist_done=1
2523		shift
2524	else
2525		flist_mode="auto"
2526	fi
2527fi
2528
2529#
2530# Before we go on to further consider -l and -w, work out which SCM we think
2531# is in use.
2532#
2533case "$SCM_MODE" in
2534git|subversion)
2535	;;
2536unknown)
2537	if [[ $flist_mode == "auto" ]]; then
2538		print -u2 "Unable to determine SCM in use and file list not specified"
2539		print -u2 "See which_scm(1) for SCM detection information."
2540		exit 1
2541	fi
2542	;;
2543*)
2544	if [[ $flist_mode == "auto" ]]; then
2545		print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2546		exit 1
2547	fi
2548	;;
2549esac
2550
2551print -u2 "   SCM detected: $SCM_MODE"
2552
2553if [[ -n $wflag ]]; then
2554	#
2555	# If the -w is given then assume the file list is in Bonwick's "wx"
2556	# command format, i.e.  pathname lines alternating with SCCS comment
2557	# lines with blank lines as separators.  Use the SCCS comments later
2558	# in building the index.html file.
2559	#
2560	shift $(($OPTIND - 1))
2561	wxfile=$1
2562	if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2563		if [[ -r $CODEMGR_WS/wx/active ]]; then
2564			wxfile=$CODEMGR_WS/wx/active
2565		fi
2566	fi
2567
2568	[[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2569	    "be auto-detected (check \$CODEMGR_WS)" && exit 1
2570
2571	if [[ ! -r $wxfile ]]; then
2572		print -u2 "$wxfile: no such file or not readable"
2573		usage
2574	fi
2575
2576	print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2577	flist_from_wx $wxfile
2578	flist_done=1
2579	if [[ -n "$*" ]]; then
2580		shift
2581	fi
2582elif [[ $flist_mode == "stdin" ]]; then
2583	print -u2 " File list from: standard input"
2584elif [[ $flist_mode == "file" ]]; then
2585	print -u2 " File list from: $flist_file"
2586fi
2587
2588if [[ $# -gt 0 ]]; then
2589	print -u2 "WARNING: unused arguments: $*"
2590fi
2591
2592
2593if [[ $SCM_MODE == "git" ]]; then
2594	# Check that "head" revision specified with -c or -h is sane
2595	if [[ -n $cflag || -n $hflag ]]; then
2596		head_rev=$($GIT rev-parse --verify --quiet "$codemgr_head")
2597		if [[ -z $head_rev ]]; then
2598			print -u2 "Error: bad revision ${codemgr_head}"
2599			exit 1
2600		fi
2601	fi
2602
2603	if [[ -z $codemgr_head ]]; then
2604		codemgr_head="HEAD";
2605	fi
2606
2607	# Parent can either be specified with -p, or specified with
2608	# CODEMGR_PARENT in the environment.
2609	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2610		codemgr_parent=$CODEMGR_PARENT
2611	fi
2612
2613	# Try to figure out the parent based on the branch the current
2614	# branch is tracking, if we fail, use origin/master
2615	this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2616	par_branch="origin/master"
2617
2618	# If we're not on a branch there's nothing we can do
2619	if [[ $this_branch != "(no branch)" ]]; then
2620		$GIT for-each-ref					\
2621		    --format='%(refname:short) %(upstream:short)'	\
2622		    refs/heads/ |					\
2623		    while read local remote; do
2624			if [[ "$local" == "$this_branch" ]]; then
2625				par_branch="$remote"
2626			fi
2627		done
2628	fi
2629
2630	if [[ -z $codemgr_parent ]]; then
2631		codemgr_parent=$par_branch
2632	fi
2633	PWS=$codemgr_parent
2634
2635	#
2636	# If the parent is a webrev, we want to do some things against
2637	# the natural workspace parent (file list, comments, etc)
2638	#
2639	if [[ -n $parent_webrev ]]; then
2640		real_parent=$par_branch
2641	else
2642		real_parent=$PWS
2643	fi
2644
2645	if [[ -z $flist_done ]]; then
2646		flist_from_git "$codemgr_head" "$real_parent"
2647		flist_done=1
2648	fi
2649
2650	#
2651	# If we have a file list now, pull out any variables set
2652	# therein.
2653	#
2654	if [[ -n $flist_done ]]; then
2655		env_from_flist
2656	fi
2657
2658	#
2659	# If we don't have a wx-format file list, build one we can pull change
2660	# comments from.
2661	#
2662	if [[ -z $wxfile ]]; then
2663		print "  Comments from: git...\c"
2664		git_wxfile "$codemgr_head" "$real_parent"
2665		print " Done."
2666	fi
2667
2668	if [[ -z $GIT_PARENT ]]; then
2669		GIT_PARENT=$($GIT merge-base "$real_parent" "$codemgr_head")
2670	fi
2671	if [[ -z $GIT_PARENT ]]; then
2672		print -u2 "Error: Cannot discover parent revision"
2673		exit 1
2674	fi
2675
2676	pnode=$(trim_digest $GIT_PARENT)
2677
2678	if [[ -n $cflag ]]; then
2679		PRETTY_PWS="previous revision (at ${pnode})"
2680	elif [[ $real_parent == */* ]]; then
2681		origin=$(echo $real_parent | cut -d/ -f1)
2682		origin=$($GIT remote -v | \
2683		    $AWK '$1 == "'$origin'" { print $2; exit }')
2684		PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2685	elif [[ -n $pflag && -z $parent_webrev ]]; then
2686		PRETTY_PWS="${CWS} (explicit revision ${pnode})"
2687	else
2688		PRETTY_PWS="${PWS} (at ${pnode})"
2689	fi
2690
2691	cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 \
2692	    ${codemgr_head} 2>/dev/null)
2693
2694	if [[ -n $cflag || -n $hflag ]]; then
2695		PRETTY_CWS="${CWS} (explicit head at ${cnode})"
2696	else
2697		PRETTY_CWS="${CWS} (at ${cnode})"
2698	fi
2699elif [[ $SCM_MODE == "subversion" ]]; then
2700
2701	#
2702	# We only will have a real parent workspace in the case one
2703	# was specified (be it an older webrev, or another checkout).
2704	#
2705	[[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2706
2707	if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2708		flist_from_subversion $CWS $OLDPWD
2709	fi
2710else
2711	if [[ $SCM_MODE == "unknown" ]]; then
2712		print -u2 "    Unknown type of SCM in use"
2713	else
2714		print -u2 "    Unsupported SCM in use: $SCM_MODE"
2715	fi
2716
2717	env_from_flist
2718
2719	if [[ -z $CODEMGR_WS ]]; then
2720		print -u2 "SCM not detected/supported and " \
2721		    "CODEMGR_WS not specified"
2722		exit 1
2723		fi
2724
2725	if [[ -z $CODEMGR_PARENT ]]; then
2726		print -u2 "SCM not detected/supported and " \
2727		    "CODEMGR_PARENT not specified"
2728		exit 1
2729	fi
2730
2731	CWS=$CODEMGR_WS
2732	PWS=$CODEMGR_PARENT
2733fi
2734
2735#
2736# If the user didn't specify a -i option, check to see if there is a
2737# webrev-info file in the workspace directory.
2738#
2739if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2740	iflag=1
2741	INCLUDE_FILE="$CWS/webrev-info"
2742fi
2743
2744if [[ -n $iflag ]]; then
2745	if [[ ! -r $INCLUDE_FILE ]]; then
2746		print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2747		    "not readable."
2748		exit 1
2749	else
2750		#
2751		# $INCLUDE_FILE may be a relative path, and the script alters
2752		# PWD, so we just stash a copy in /tmp.
2753		#
2754		cp $INCLUDE_FILE /tmp/$$.include
2755	fi
2756fi
2757
2758# DO_EVERYTHING: break point
2759if [[ -n $Nflag ]]; then
2760	break
2761fi
2762
2763typeset -A itsinfo
2764typeset -r its_sed_script=/tmp/$$.its_sed
2765valid_prefixes=
2766if [[ -z $nflag ]]; then
2767	DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg"
2768	if [[ -n $Iflag ]]; then
2769		REGFILE=$ITSREG
2770	elif [[ -r $HOME/.its.reg ]]; then
2771		REGFILE=$HOME/.its.reg
2772	else
2773		REGFILE=$DEFREGFILE
2774	fi
2775	if [[ ! -r $REGFILE ]]; then
2776		print "ERROR: Unable to read database registry file $REGFILE"
2777		exit 1
2778	elif [[ $REGFILE != $DEFREGFILE ]]; then
2779		print "   its.reg from: $REGFILE"
2780	fi
2781
2782	$SED -e '/^#/d' -e '/^[ 	]*$/d' $REGFILE | while read LINE; do
2783
2784		name=${LINE%%=*}
2785		value="${LINE#*=}"
2786
2787		if [[ $name == PREFIX ]]; then
2788			p=${value}
2789			valid_prefixes="${p} ${valid_prefixes}"
2790		else
2791			itsinfo["${p}_${name}"]="${value}"
2792		fi
2793	done
2794
2795
2796	DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf"
2797	CONFFILES=$DEFCONFFILE
2798	if [[ -r $HOME/.its.conf ]]; then
2799		CONFFILES="${CONFFILES} $HOME/.its.conf"
2800	fi
2801	if [[ -n $Cflag ]]; then
2802		CONFFILES="${CONFFILES} ${ITSCONF}"
2803	fi
2804	its_domain=
2805	its_priority=
2806	for cf in ${CONFFILES}; do
2807		if [[ ! -r $cf ]]; then
2808			print "ERROR: Unable to read database configuration file $cf"
2809			exit 1
2810		elif [[ $cf != $DEFCONFFILE ]]; then
2811			print "       its.conf: reading $cf"
2812		fi
2813		$SED -e '/^#/d' -e '/^[ 	]*$/d' $cf | while read LINE; do
2814		    eval "${LINE}"
2815		done
2816	done
2817
2818	#
2819	# If an information tracking system is explicitly identified by prefix,
2820	# we want to disregard the specified priorities and resolve it accordingly.
2821	#
2822	# To that end, we'll build a sed script to do each valid prefix in turn.
2823	#
2824	for p in ${valid_prefixes}; do
2825		#
2826		# When an informational URL was provided, translate it to a
2827		# hyperlink.  When omitted, simply use the prefix text.
2828		#
2829		if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2830			itsinfo["${p}_INFO"]=${p}
2831		else
2832			itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2833		fi
2834
2835		#
2836		# Assume that, for this invocation of webrev, all references
2837		# to this information tracking system should resolve through
2838		# the same URL.
2839		#
2840		# If the caller specified -O, then always use EXTERNAL_URL.
2841		#
2842		# Otherwise, look in the list of domains for a matching
2843		# INTERNAL_URL.
2844		#
2845		[[ -z $Oflag ]] && for d in ${its_domain}; do
2846			if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2847				itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2848				break
2849			fi
2850		done
2851		if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2852			itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2853		fi
2854
2855		#
2856		# Turn the destination URL into a hyperlink
2857		#
2858		itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2859
2860		# The character class below contains a literal tab
2861		print "/^${p}[: 	]/ {
2862				s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2863				s;^${p};${itsinfo[${p}_INFO]};
2864			}" >> ${its_sed_script}
2865	done
2866
2867	#
2868	# The previous loop took care of explicit specification.  Now use
2869	# the configured priorities to attempt implicit translations.
2870	#
2871	for p in ${its_priority}; do
2872		print "/^${itsinfo[${p}_REGEX]}[ 	]/ {
2873				s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2874			}" >> ${its_sed_script}
2875	done
2876fi
2877
2878#
2879# Search for DO_EVERYTHING above for matching "for" statement
2880# and explanation of this terminator.
2881#
2882done
2883
2884#
2885# Output directory.
2886#
2887WDIR=${WDIR:-$CWS/webrev}
2888
2889#
2890# Name of the webrev, derived from the workspace name or output directory;
2891# in the future this could potentially be an option.
2892#
2893if [[ -n $oflag ]]; then
2894	WNAME=${WDIR##*/}
2895else
2896	WNAME=${CWS##*/}
2897fi
2898
2899# Make sure remote target is well formed for remote upload/delete.
2900if [[ -n $Dflag || -n $Uflag ]]; then
2901	#
2902	# If remote target is not specified, build it from scratch using
2903	# the default values.
2904	#
2905	if [[ -z $tflag ]]; then
2906		remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2907	else
2908		#
2909		# Check upload target prefix first.
2910		#
2911		if [[ "${remote_target}" != ${rsync_prefix}* &&
2912		    "${remote_target}" != ${ssh_prefix}* ]]; then
2913			print "ERROR: invalid prefix of upload URI" \
2914			    "($remote_target)"
2915			exit 1
2916		fi
2917		#
2918		# If destination specification is not in the form of
2919		# host_spec:remote_dir then assume it is just remote hostname
2920		# and append a colon and destination directory formed from
2921		# local webrev directory name.
2922		#
2923		typeset target_no_prefix=${remote_target##*://}
2924		if [[ ${target_no_prefix} == *:* ]]; then
2925			if [[ "${remote_target}" == *: ]]; then
2926				remote_target=${remote_target}${WNAME}
2927			fi
2928		else
2929			if [[ ${target_no_prefix} == */* ]]; then
2930				print "ERROR: badly formed upload URI" \
2931					"($remote_target)"
2932				exit 1
2933			else
2934				remote_target=${remote_target}:${WNAME}
2935			fi
2936		fi
2937	fi
2938
2939	#
2940	# Strip trailing slash. Each upload method will deal with directory
2941	# specification separately.
2942	#
2943	remote_target=${remote_target%/}
2944fi
2945
2946#
2947# Option -D by itself (option -U not present) implies no webrev generation.
2948#
2949if [[ -z $Uflag && -n $Dflag ]]; then
2950	delete_webrev 1 1
2951	exit $?
2952fi
2953
2954#
2955# Do not generate the webrev, just upload it or delete it.
2956#
2957if [[ -n $nflag ]]; then
2958	if [[ -n $Dflag ]]; then
2959		delete_webrev 1 1
2960		(( $? == 0 )) || exit $?
2961	fi
2962	if [[ -n $Uflag ]]; then
2963		upload_webrev
2964		exit $?
2965	fi
2966fi
2967
2968if [ "${WDIR%%/*}" ]; then
2969	WDIR=$PWD/$WDIR
2970fi
2971
2972if [[ ! -d $WDIR ]]; then
2973	mkdir -p $WDIR
2974	(( $? != 0 )) && exit 1
2975fi
2976
2977#
2978# Summarize what we're going to do.
2979#
2980print "      Workspace: ${PRETTY_CWS:-$CWS}"
2981if [[ -n $parent_webrev ]]; then
2982	print "Compare against: webrev at $parent_webrev"
2983else
2984	print "Compare against: ${PRETTY_PWS:-$PWS}"
2985fi
2986
2987[[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
2988print "      Output to: $WDIR"
2989
2990#
2991# Save the file list in the webrev dir
2992#
2993[[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
2994
2995rm -f $WDIR/$WNAME.patch
2996
2997touch $WDIR/$WNAME.patch
2998
2999print "   Output Files:"
3000
3001#
3002# Clean up the file list: Remove comments, blank lines and env variables.
3003#
3004$SED -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
3005FLIST=/tmp/$$.flist.clean
3006
3007#
3008# First pass through the files: generate the per-file webrev HTML-files.
3009#
3010cat $FLIST | while read LINE
3011do
3012	set - $LINE
3013	P=$1
3014
3015	#
3016	# Normally, each line in the file list is just a pathname of a
3017	# file that has been modified or created in the child.  A file
3018	# that is renamed in the child workspace has two names on the
3019	# line: new name followed by the old name.
3020	#
3021	oldname=""
3022	oldpath=""
3023	rename=
3024	if [[ $# -eq 2 ]]; then
3025		PP=$2			# old filename
3026		if [[ -f $PP ]]; then
3027			oldname=" (copied from $PP)"
3028		else
3029			oldname=" (renamed from $PP)"
3030		fi
3031		oldpath="$PP"
3032		rename=1
3033		PDIR=${PP%/*}
3034		if [[ $PDIR == $PP ]]; then
3035			PDIR="."   # File at root of workspace
3036		fi
3037
3038		PF=${PP##*/}
3039
3040		DIR=${P%/*}
3041		if [[ $DIR == $P ]]; then
3042			DIR="."   # File at root of workspace
3043		fi
3044
3045		F=${P##*/}
3046
3047	else
3048		DIR=${P%/*}
3049		if [[ "$DIR" == "$P" ]]; then
3050			DIR="."   # File at root of workspace
3051		fi
3052
3053		F=${P##*/}
3054
3055		PP=$P
3056		PDIR=$DIR
3057		PF=$F
3058	fi
3059
3060	COMM=`getcomments html $P $PP`
3061
3062	print "\t$P$oldname\n\t\t\c"
3063
3064	# Make the webrev mirror directory if necessary
3065	mkdir -p $WDIR/$DIR
3066
3067	#
3068	# We stash old and new files into parallel directories in $WDIR
3069	# and do our diffs there.  This makes it possible to generate
3070	# clean looking diffs which don't have absolute paths present.
3071	#
3072
3073	build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3074	    continue
3075
3076	#
3077	# Keep the old PWD around, so we can safely switch back after
3078	# diff generation, such that build_old_new runs in a
3079	# consistent environment.
3080	#
3081	OWD=$PWD
3082	cd $WDIR/raw_files
3083
3084	#
3085	# The "git apply" command does not tolerate the spurious
3086	# "./" that we otherwise insert; be careful not to include
3087	# it in the paths that we pass to diff(1).
3088	#
3089	if [[ $PDIR == "." ]]; then
3090		ofile=old/$PF
3091	else
3092		ofile=old/$PDIR/$PF
3093	fi
3094	if [[ $DIR == "." ]]; then
3095		nfile=new/$F
3096	else
3097		nfile=new/$DIR/$F
3098	fi
3099
3100	mv_but_nodiff=
3101	cmp $ofile $nfile > /dev/null 2>&1
3102	if [[ $? == 0 && $rename == 1 ]]; then
3103		mv_but_nodiff=1
3104	fi
3105
3106	#
3107	# If we have old and new versions of the file then run the appropriate
3108	# diffs.  This is complicated by a couple of factors:
3109	#
3110	#	- renames must be handled specially: we emit a 'remove'
3111	#	  diff and an 'add' diff
3112	#	- new files and deleted files must be handled specially
3113	#	- GNU patch doesn't interpret the output of illumos diff
3114	#	  properly when it comes to adds and deletes.  We need to
3115	#	  do some "cleansing" transformations:
3116	#	    [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
3117	#	    [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
3118	#
3119	cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3120	cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3121
3122	rm -f $WDIR/$DIR/$F.patch
3123	if [[ -z $rename ]]; then
3124		if [ ! -f "$ofile" ]; then
3125			diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3126			    > $WDIR/$DIR/$F.patch
3127		elif [ ! -f "$nfile" ]; then
3128			diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3129			    > $WDIR/$DIR/$F.patch
3130		else
3131			diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3132		fi
3133	else
3134		diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3135		    > $WDIR/$DIR/$F.patch
3136
3137		diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3138		    >> $WDIR/$DIR/$F.patch
3139	fi
3140
3141	#
3142	# Tack the patch we just made onto the accumulated patch for the
3143	# whole wad.
3144	#
3145	cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3146	print " patch\c"
3147
3148	if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3149		${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3150		diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3151		    > $WDIR/$DIR/$F.cdiff.html
3152		print " cdiffs\c"
3153
3154		${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3155		diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3156		    > $WDIR/$DIR/$F.udiff.html
3157		print " udiffs\c"
3158
3159		if [[ -x $WDIFF ]]; then
3160			$WDIFF -c "$COMM" \
3161			    -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3162			    $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3163			if [[ $? -eq 0 ]]; then
3164				print " wdiffs\c"
3165			else
3166				print " wdiffs[fail]\c"
3167			fi
3168		fi
3169
3170		sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3171		    > $WDIR/$DIR/$F.sdiff.html
3172		print " sdiffs\c"
3173		print " frames\c"
3174
3175		rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3176		difflines $ofile $nfile > $WDIR/$DIR/$F.count
3177	elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3178		# renamed file: may also have differences
3179		difflines $ofile $nfile > $WDIR/$DIR/$F.count
3180	elif [[ -f $nfile ]]; then
3181		# new file: count added lines
3182		difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3183	elif [[ -f $ofile ]]; then
3184		# old file: count deleted lines
3185		difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3186	fi
3187
3188	#
3189	# Check if it's man page, and create plain text, html and raw (ascii)
3190	# output for the new version, as well as diffs against old version.
3191	#
3192	if [[ -f "$nfile" && "$nfile" = *.+([0-9])*([a-zA-Z]) && \
3193	    -x $MANDOC && -x $COL ]]; then
3194		$MANDOC -Tascii $nfile | $COL -b > $nfile.man.txt
3195		source_to_html txt < $nfile.man.txt > $nfile.man.txt.html
3196		print " man-txt\c"
3197		print "$MANCSS" > $WDIR/raw_files/new/$DIR/man.css
3198		$MANDOC -Thtml -Ostyle=man.css $nfile > $nfile.man.html
3199		print " man-html\c"
3200		$MANDOC -Tascii $nfile > $nfile.man.raw
3201		print " man-raw\c"
3202		if [[ -f "$ofile" && -z $mv_but_nodiff ]]; then
3203			$MANDOC -Tascii $ofile | $COL -b > $ofile.man.txt
3204			${CDIFFCMD:-diff -bt -C 5} $ofile.man.txt \
3205			    $nfile.man.txt > $WDIR/$DIR/$F.man.cdiff
3206			diff_to_html $F $DIR/$F "C" "$COMM" < \
3207			    $WDIR/$DIR/$F.man.cdiff > \
3208			    $WDIR/$DIR/$F.man.cdiff.html
3209			print " man-cdiffs\c"
3210			${UDIFFCMD:-diff -bt -U 5} $ofile.man.txt \
3211			    $nfile.man.txt > $WDIR/$DIR/$F.man.udiff
3212			diff_to_html $F $DIR/$F "U" "$COMM" < \
3213			    $WDIR/$DIR/$F.man.udiff > \
3214			    $WDIR/$DIR/$F.man.udiff.html
3215			print " man-udiffs\c"
3216			if [[ -x $WDIFF ]]; then
3217				$WDIFF -c "$COMM" -t "$WNAME Wdiff $DIR/$F" \
3218				    $ofile.man.txt $nfile.man.txt > \
3219				    $WDIR/$DIR/$F.man.wdiff.html 2>/dev/null
3220				if [[ $? -eq 0 ]]; then
3221					print " man-wdiffs\c"
3222				else
3223					print " man-wdiffs[fail]\c"
3224				fi
3225			fi
3226			sdiff_to_html $ofile.man.txt $nfile.man.txt $F.man $DIR \
3227			    "$COMM" > $WDIR/$DIR/$F.man.sdiff.html
3228			print " man-sdiffs\c"
3229			print " man-frames\c"
3230		fi
3231		rm -f $ofile.man.txt $nfile.man.txt
3232		rm -f $WDIR/$DIR/$F.man.cdiff $WDIR/$DIR/$F.man.udiff
3233	fi
3234
3235	if [[ -f $ofile ]]; then
3236		source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3237		print " old\c"
3238	fi
3239
3240	if [[ -f $nfile ]]; then
3241		source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3242		print " new\c"
3243	fi
3244
3245	cd $OWD
3246
3247	print
3248done
3249
3250frame_nav_js > $WDIR/ancnav.js
3251frame_navigation > $WDIR/ancnav.html
3252
3253# If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3254# delete it - prevent accidental publishing of closed source
3255
3256if [[ -n "$Oflag" ]]; then
3257	$FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3258fi
3259
3260# Now build the index.html file that contains
3261# links to the source files and their diffs.
3262
3263cd $CWS
3264
3265# Save total changed lines for Code Inspection.
3266print "$TOTL" > $WDIR/TotalChangedLines
3267
3268print "     index.html: \c"
3269INDEXFILE=$WDIR/index.html
3270exec 3<&1			# duplicate stdout to FD3.
3271exec 1<&-			# Close stdout.
3272exec > $INDEXFILE		# Open stdout to index file.
3273
3274print "$HTML<head>$STDHEAD"
3275print "<title>$WNAME</title>"
3276print "</head>"
3277print "<body id=\"SUNWwebrev\">"
3278print "<div class=\"summary\">"
3279print "<h2>Code Review for $WNAME</h2>"
3280
3281print "<table>"
3282
3283#
3284# Get the preparer's name:
3285#
3286# If the SCM detected is Git, and the configuration property user.name is
3287# available, use that, but be careful to properly escape angle brackets (HTML
3288# syntax characters) in the email address.
3289#
3290# Otherwise, use the current userid in the form "John Doe (jdoe)", but
3291# to maintain compatibility with passwd(5), we must support '&' substitutions.
3292#
3293preparer=
3294if [[ "$SCM_MODE" == git ]]; then
3295	preparer=$(git config user.name 2>/dev/null)
3296	if [[ -n "$preparer" ]]; then
3297		preparer="$(echo "$preparer" | html_quote)"
3298	fi
3299fi
3300if [[ -z "$preparer" ]]; then
3301	preparer=$(
3302	    $PERL -e '
3303	        ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3304	        if ($login) {
3305	            $gcos =~ s/\&/ucfirst($login)/e;
3306	            printf "%s (%s)\n", $gcos, $login;
3307	        } else {
3308	            printf "(unknown)\n";
3309	        }
3310	')
3311fi
3312
3313PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3314print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3315print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3316print "</td></tr>"
3317print "<tr><th>Compare against:</th><td>"
3318if [[ -n $parent_webrev ]]; then
3319	print "webrev at $parent_webrev"
3320else
3321	print "${PRETTY_PWS:-$PWS}"
3322fi
3323print "</td></tr>"
3324print "<tr><th>Summary of changes:</th><td>"
3325printCI $TOTL $TINS $TDEL $TMOD $TUNC
3326print "</td></tr>"
3327
3328if [[ -f $WDIR/$WNAME.patch ]]; then
3329	wpatch_url="$(print $WNAME.patch | url_encode)"
3330	print "<tr><th>Patch of changes:</th><td>"
3331	print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3332fi
3333
3334if [[ -n "$iflag" ]]; then
3335	print "<tr><th>Author comments:</th><td><div>"
3336	cat /tmp/$$.include
3337	print "</div></td></tr>"
3338fi
3339print "</table>"
3340print "</div>"
3341
3342#
3343# Second pass through the files: generate the rest of the index file
3344#
3345cat $FLIST | while read LINE
3346do
3347	set - $LINE
3348	P=$1
3349
3350	if [[ $# == 2 ]]; then
3351		PP=$2
3352		oldname="$PP"
3353	else
3354		PP=$P
3355		oldname=""
3356	fi
3357
3358	mv_but_nodiff=
3359	cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3360	if [[ $? == 0 && -n "$oldname" ]]; then
3361		mv_but_nodiff=1
3362	fi
3363
3364	DIR=${P%/*}
3365	if [[ $DIR == $P ]]; then
3366		DIR="."   # File at root of workspace
3367	fi
3368
3369	# Avoid processing the same file twice.
3370	# It's possible for renamed files to
3371	# appear twice in the file list
3372
3373	F=$WDIR/$P
3374
3375	print "<p>"
3376
3377	# If there's a diffs file, make diffs links
3378
3379	if [[ -f $F.cdiff.html ]]; then
3380		cdiff_url="$(print $P.cdiff.html | url_encode)"
3381		udiff_url="$(print $P.udiff.html | url_encode)"
3382		sdiff_url="$(print $P.sdiff.html | url_encode)"
3383		frames_url="$(print $P.frames.html | url_encode)"
3384		print "<a href=\"$cdiff_url\">Cdiffs</a>"
3385		print "<a href=\"$udiff_url\">Udiffs</a>"
3386		if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3387			wdiff_url="$(print $P.wdiff.html | url_encode)"
3388			print "<a href=\"$wdiff_url\">Wdiffs</a>"
3389		fi
3390		print "<a href=\"$sdiff_url\">Sdiffs</a>"
3391		print "<a href=\"$frames_url\">Frames</a>"
3392	else
3393		print " ------ ------"
3394		if [[ -x $WDIFF ]]; then
3395			print " ------"
3396		fi
3397		print " ------ ------"
3398	fi
3399
3400	# If there's an old file, make the link
3401
3402	if [[ -f $F-.html ]]; then
3403		oldfile_url="$(print $P-.html | url_encode)"
3404		print "<a href=\"$oldfile_url\">Old</a>"
3405	else
3406		print " ---"
3407	fi
3408
3409	# If there's an new file, make the link
3410
3411	if [[ -f $F.html ]]; then
3412		newfile_url="$(print $P.html | url_encode)"
3413		print "<a href=\"$newfile_url\">New</a>"
3414	else
3415		print " ---"
3416	fi
3417
3418	if [[ -f $F.patch ]]; then
3419		patch_url="$(print $P.patch | url_encode)"
3420		print "<a href=\"$patch_url\">Patch</a>"
3421	else
3422		print " -----"
3423	fi
3424
3425	if [[ -f $WDIR/raw_files/new/$P ]]; then
3426		rawfiles_url="$(print raw_files/new/$P | url_encode)"
3427		print "<a href=\"$rawfiles_url\">Raw</a>"
3428	else
3429		print " ---"
3430	fi
3431
3432	print "<b>$P</b>"
3433
3434	# For renamed files, clearly state whether or not they are modified
3435	if [[ -f "$oldname" ]]; then
3436		if [[ -n "$mv_but_nodiff" ]]; then
3437			print "<i>(copied from $oldname)</i>"
3438		else
3439			print "<i>(copied and modified from $oldname)</i>"
3440		fi
3441	elif [[ -n "$oldname" ]]; then
3442		if [[ -n "$mv_but_nodiff" ]]; then
3443			print "<i>(renamed from $oldname)</i>"
3444		else
3445			print "<i>(renamed and modified from $oldname)</i>"
3446		fi
3447	fi
3448
3449	# If there's an old file, but no new file, the file was deleted
3450	if [[ -f $F-.html && ! -f $F.html ]]; then
3451		print " <i>(deleted)</i>"
3452	fi
3453
3454	# Check for usr/closed and deleted_files/usr/closed
3455	if [ ! -z "$Oflag" ]; then
3456		if [[ $P == usr/closed/* || \
3457		    $P == deleted_files/usr/closed/* ]]; then
3458			print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3459			    "this review</i>"
3460		fi
3461	fi
3462
3463	manpage=
3464	if [[ -f $F.man.cdiff.html || \
3465	    -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3466		manpage=1
3467		print "<br/>man:"
3468	fi
3469
3470	if [[ -f $F.man.cdiff.html ]]; then
3471		mancdiff_url="$(print $P.man.cdiff.html | url_encode)"
3472		manudiff_url="$(print $P.man.udiff.html | url_encode)"
3473		mansdiff_url="$(print $P.man.sdiff.html | url_encode)"
3474		manframes_url="$(print $P.man.frames.html | url_encode)"
3475		print "<a href=\"$mancdiff_url\">Cdiffs</a>"
3476		print "<a href=\"$manudiff_url\">Udiffs</a>"
3477		if [[ -f $F.man.wdiff.html && -x $WDIFF ]]; then
3478			manwdiff_url="$(print $P.man.wdiff.html | url_encode)"
3479			print "<a href=\"$manwdiff_url\">Wdiffs</a>"
3480		fi
3481		print "<a href=\"$mansdiff_url\">Sdiffs</a>"
3482		print "<a href=\"$manframes_url\">Frames</a>"
3483	elif [[ -n $manpage ]]; then
3484		print " ------ ------"
3485		if [[ -x $WDIFF ]]; then
3486			print " ------"
3487		fi
3488		print " ------ ------"
3489	fi
3490
3491	if [[ -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3492		mantxt_url="$(print raw_files/new/$P.man.txt.html | url_encode)"
3493		print "<a href=\"$mantxt_url\">TXT</a>"
3494		manhtml_url="$(print raw_files/new/$P.man.html | url_encode)"
3495		print "<a href=\"$manhtml_url\">HTML</a>"
3496		manraw_url="$(print raw_files/new/$P.man.raw | url_encode)"
3497		print "<a href=\"$manraw_url\">Raw</a>"
3498	elif [[ -n $manpage ]]; then
3499		print " --- ---- ---"
3500	fi
3501
3502	print "</p>"
3503
3504	# Insert delta comments
3505	print "<blockquote><pre>"
3506	getcomments html $P $PP
3507	print "</pre>"
3508
3509	# Add additional comments comment
3510	print "<!-- Add comments to explain changes in $P here -->"
3511
3512	# Add count of changes.
3513	if [[ -f $F.count ]]; then
3514	    cat $F.count
3515	    rm $F.count
3516	fi
3517
3518	if [[ $SCM_MODE == "unknown" ]]; then
3519		# Include warnings for important file mode situations:
3520		# 1) New executable files
3521		# 2) Permission changes of any kind
3522		# 3) Existing executable files
3523		old_mode=
3524		if [[ -f $WDIR/raw_files/old/$PP ]]; then
3525			old_mode=`get_file_mode $WDIR/raw_files/old/$PP`
3526		fi
3527
3528		new_mode=
3529		if [[ -f $WDIR/raw_files/new/$P ]]; then
3530			new_mode=`get_file_mode $WDIR/raw_files/new/$P`
3531		fi
3532
3533		if [[ -z "$old_mode" && "$new_mode" = *[1357]* ]]; then
3534			print "<span class=\"chmod\">"
3535			print "<p>new executable file: mode $new_mode</p>"
3536			print "</span>"
3537		elif [[ -n "$old_mode" && -n "$new_mode" &&
3538		    "$old_mode" != "$new_mode" ]]; then
3539			print "<span class=\"chmod\">"
3540			print "<p>mode change: $old_mode to $new_mode</p>"
3541			print "</span>"
3542		elif [[ "$new_mode" = *[1357]* ]]; then
3543			print "<span class=\"chmod\">"
3544			print "<p>executable file: mode $new_mode</p>"
3545			print "</span>"
3546		fi
3547	fi
3548
3549	print "</blockquote>"
3550done
3551
3552print
3553print
3554print "<hr></hr>"
3555print "<p style=\"font-size: small\">"
3556print "This code review page was prepared using <b>$0</b>."
3557print "Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3558print "illumos</a> project.  The latest version may be obtained"
3559print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3560print "</body>"
3561print "</html>"
3562
3563exec 1<&-			# Close FD 1.
3564exec 1<&3			# dup FD 3 to restore stdout.
3565exec 3<&-			# close FD 3.
3566
3567print "Done."
3568
3569#
3570# If remote deletion was specified and fails do not continue.
3571#
3572if [[ -n $Dflag ]]; then
3573	delete_webrev 1 1
3574	(( $? == 0 )) || exit $?
3575fi
3576
3577if [[ -n $Uflag ]]; then
3578	upload_webrev
3579	exit $?
3580fi
3581