xref: /illumos-gate/usr/src/tools/scripts/webrev.sh (revision 9af2fe54)
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# Copyright 2024 Bill Sommerfeld <sommerfeld@hamachi.org>
32#
33
34#
35# This script takes a file list and a workspace and builds a set of html files
36# suitable for doing a code review of source changes via a web page.
37# Documentation is available via the manual page, webrev.1, or just
38# type 'webrev -h'.
39#
40# Acknowledgements to contributors to webrev are listed in the webrev(1)
41# man page.
42#
43
44REMOVED_COLOR=brown
45CHANGED_COLOR=blue
46NEW_COLOR=blue
47
48HTML='<?xml version="1.0"?>
49<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
50    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
51<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
52
53FRAMEHTML='<?xml version="1.0"?>
54<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
55    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
56<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
57
58STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta>
59<meta http-equiv="Content-Type" content="text/xhtml;charset=utf-8"></meta>
60<meta http-equiv="Pragma" content="no-cache"></meta>
61<meta http-equiv="Expires" content="-1"></meta>
62<!--
63   Note to customizers: the body of the webrev is IDed as SUNWwebrev
64   to allow easy overriding by users of webrev via the userContent.css
65   mechanism available in some browsers.
66
67   For example, to have all "removed" information be red instead of
68   brown, set a rule in your userContent.css file like:
69
70       body#SUNWwebrev span.removed { color: red ! important; }
71-->
72<style type="text/css" media="screen">
73body {
74    background-color: #eeeeee;
75}
76hr {
77    border: none 0;
78    border-top: 1px solid #aaa;
79    height: 1px;
80}
81div.summary {
82    font-size: .8em;
83    border-bottom: 1px solid #aaa;
84    padding-left: 1em;
85    padding-right: 1em;
86}
87div.summary h2 {
88    margin-bottom: 0.3em;
89}
90div.summary table th {
91    text-align: right;
92    vertical-align: top;
93    white-space: nowrap;
94}
95span.lineschanged {
96    font-size: 0.7em;
97}
98span.oldmarker {
99    color: red;
100    font-size: large;
101    font-weight: bold;
102}
103span.newmarker {
104    color: green;
105    font-size: large;
106    font-weight: bold;
107}
108span.removed {
109    color: brown;
110}
111span.changed {
112    color: blue;
113}
114span.new {
115    color: blue;
116    font-weight: bold;
117}
118span.chmod {
119    font-size: 0.7em;
120    color: #db7800;
121}
122a.print { font-size: x-small; }
123a:hover { background-color: #ffcc99; }
124</style>
125
126<style type="text/css" media="print">
127pre { font-size: 0.8em; font-family: courier, monospace; }
128span.removed { color: #444; font-style: italic }
129span.changed { font-weight: bold; }
130span.new { font-weight: bold; }
131span.newmarker { font-size: 1.2em; font-weight: bold; }
132span.oldmarker { font-size: 1.2em; font-weight: bold; }
133a.print {display: none}
134hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
135</style>
136'
137
138#
139# UDiffs need a slightly different CSS rule for 'new' items (we don't
140# want them to be bolded as we do in cdiffs or sdiffs).
141#
142UDIFFCSS='
143<style type="text/css" media="screen">
144span.new {
145    color: blue;
146    font-weight: normal;
147}
148</style>
149'
150
151# CSS for the HTML version of the man pages.
152# Current version is from mandoc 1.14.5.
153MANCSS='
154/* $Id: mandoc.css,v 1.45 2019/03/01 10:57:18 schwarze Exp $ */
155/*
156 * Standard style sheet for mandoc(1) -Thtml and man.cgi(8).
157 *
158 * Written by Ingo Schwarze <schwarze@openbsd.org>.
159 * I place this file into the public domain.
160 * Permission to use, copy, modify, and distribute it for any purpose
161 * with or without fee is hereby granted, without any conditions.
162 */
163
164/* Global defaults. */
165
166html {		max-width: 65em; }
167body {		font-family: Helvetica,Arial,sans-serif; }
168table {		margin-top: 0em;
169		margin-bottom: 0em;
170		border-collapse: collapse; }
171/* Some browsers set border-color in a browser style for tbody,
172 * but not for table, resulting in inconsistent border styling. */
173tbody {		border-color: inherit; }
174tr {		border-color: inherit; }
175td {		vertical-align: top;
176		padding-left: 0.2em;
177		padding-right: 0.2em;
178		border-color: inherit; }
179ul, ol, dl {	margin-top: 0em;
180		margin-bottom: 0em; }
181li, dt {	margin-top: 1em; }
182
183.permalink {	border-bottom: thin dotted;
184		color: inherit;
185		font: inherit;
186		text-decoration: inherit; }
187* {		clear: both }
188
189/* Search form and search results. */
190
191fieldset {	border: thin solid silver;
192		border-radius: 1em;
193		text-align: center; }
194input[name=expr] {
195		width: 25%; }
196
197table.results {	margin-top: 1em;
198		margin-left: 2em;
199		font-size: smaller; }
200
201/* Header and footer lines. */
202
203table.head {	width: 100%;
204		border-bottom: 1px dotted #808080;
205		margin-bottom: 1em;
206		font-size: smaller; }
207td.head-vol {	text-align: center; }
208td.head-rtitle {
209		text-align: right; }
210
211table.foot {	width: 100%;
212		border-top: 1px dotted #808080;
213		margin-top: 1em;
214		font-size: smaller; }
215td.foot-os {	text-align: right; }
216
217/* Sections and paragraphs. */
218
219.manual-text {
220		margin-left: 3.8em; }
221.Nd { }
222section.Sh { }
223h1.Sh {		margin-top: 1.2em;
224		margin-bottom: 0.6em;
225		margin-left: -3.2em;
226		font-size: 110%; }
227section.Ss { }
228h2.Ss {		margin-top: 1.2em;
229		margin-bottom: 0.6em;
230		margin-left: -1.2em;
231		font-size: 105%; }
232.Pp {		margin: 0.6em 0em; }
233.Sx { }
234.Xr { }
235
236/* Displays and lists. */
237
238.Bd { }
239.Bd-indent {	margin-left: 3.8em; }
240
241.Bl-bullet {	list-style-type: disc;
242		padding-left: 1em; }
243.Bl-bullet > li { }
244.Bl-dash {	list-style-type: none;
245		padding-left: 0em; }
246.Bl-dash > li:before {
247		content: "\2014  "; }
248.Bl-item {	list-style-type: none;
249		padding-left: 0em; }
250.Bl-item > li { }
251.Bl-compact > li {
252		margin-top: 0em; }
253
254.Bl-enum {	padding-left: 2em; }
255.Bl-enum > li { }
256.Bl-compact > li {
257		margin-top: 0em; }
258
259.Bl-diag { }
260.Bl-diag > dt {
261		font-style: normal;
262		font-weight: bold; }
263.Bl-diag > dd {
264		margin-left: 0em; }
265.Bl-hang { }
266.Bl-hang > dt { }
267.Bl-hang > dd {
268		margin-left: 5.5em; }
269.Bl-inset { }
270.Bl-inset > dt { }
271.Bl-inset > dd {
272		margin-left: 0em; }
273.Bl-ohang { }
274.Bl-ohang > dt { }
275.Bl-ohang > dd {
276		margin-left: 0em; }
277.Bl-tag {	margin-top: 0.6em;
278		margin-left: 5.5em; }
279.Bl-tag > dt {
280		float: left;
281		margin-top: 0em;
282		margin-left: -5.5em;
283		padding-right: 0.5em;
284		vertical-align: top; }
285.Bl-tag > dd {
286		clear: right;
287		width: 100%;
288		margin-top: 0em;
289		margin-left: 0em;
290		margin-bottom: 0.6em;
291		vertical-align: top;
292		overflow: auto; }
293.Bl-compact {	margin-top: 0em; }
294.Bl-compact > dd {
295		margin-bottom: 0em; }
296.Bl-compact > dt {
297		margin-top: 0em; }
298
299.Bl-column { }
300.Bl-column > tbody > tr { }
301.Bl-column > tbody > tr > td {
302		margin-top: 1em; }
303.Bl-compact > tbody > tr > td {
304		margin-top: 0em; }
305
306.Rs {		font-style: normal;
307		font-weight: normal; }
308.RsA { }
309.RsB {		font-style: italic;
310		font-weight: normal; }
311.RsC { }
312.RsD { }
313.RsI {		font-style: italic;
314		font-weight: normal; }
315.RsJ {		font-style: italic;
316		font-weight: normal; }
317.RsN { }
318.RsO { }
319.RsP { }
320.RsQ { }
321.RsR { }
322.RsT {		text-decoration: underline; }
323.RsU { }
324.RsV { }
325
326.eqn { }
327.tbl td {	vertical-align: middle; }
328
329.HP {		margin-left: 3.8em;
330		text-indent: -3.8em; }
331
332/* Semantic markup for command line utilities. */
333
334table.Nm { }
335code.Nm {	font-style: normal;
336		font-weight: bold;
337		font-family: inherit; }
338.Fl {		font-style: normal;
339		font-weight: bold;
340		font-family: inherit; }
341.Cm {		font-style: normal;
342		font-weight: bold;
343		font-family: inherit; }
344.Ar {		font-style: italic;
345		font-weight: normal; }
346.Op {		display: inline; }
347.Ic {		font-style: normal;
348		font-weight: bold;
349		font-family: inherit; }
350.Ev {		font-style: normal;
351		font-weight: normal;
352		font-family: monospace; }
353.Pa {		font-style: italic;
354		font-weight: normal; }
355
356/* Semantic markup for function libraries. */
357
358.Lb { }
359code.In {	font-style: normal;
360		font-weight: bold;
361		font-family: inherit; }
362a.In { }
363.Fd {		font-style: normal;
364		font-weight: bold;
365		font-family: inherit; }
366.Ft {		font-style: italic;
367		font-weight: normal; }
368.Fn {		font-style: normal;
369		font-weight: bold;
370		font-family: inherit; }
371.Fa {		font-style: italic;
372		font-weight: normal; }
373.Vt {		font-style: italic;
374		font-weight: normal; }
375.Va {		font-style: italic;
376		font-weight: normal; }
377.Dv {		font-style: normal;
378		font-weight: normal;
379		font-family: monospace; }
380.Er {		font-style: normal;
381		font-weight: normal;
382		font-family: monospace; }
383
384/* Various semantic markup. */
385
386.An { }
387.Lk { }
388.Mt { }
389.Cd {		font-style: normal;
390		font-weight: bold;
391		font-family: inherit; }
392.Ad {		font-style: italic;
393		font-weight: normal; }
394.Ms {		font-style: normal;
395		font-weight: bold; }
396.St { }
397.Ux { }
398
399/* Physical markup. */
400
401.Bf {		display: inline; }
402.No {		font-style: normal;
403		font-weight: normal; }
404.Em {		font-style: italic;
405		font-weight: normal; }
406.Sy {		font-style: normal;
407		font-weight: bold; }
408.Li {		font-style: normal;
409		font-weight: normal;
410		font-family: monospace; }
411
412/* Tooltip support. */
413
414h1.Sh, h2.Ss {	position: relative; }
415.An, .Ar, .Cd, .Cm, .Dv, .Em, .Er, .Ev, .Fa, .Fd, .Fl, .Fn, .Ft,
416.Ic, code.In, .Lb, .Lk, .Ms, .Mt, .Nd, code.Nm, .Pa, .Rs,
417.St, .Sx, .Sy, .Va, .Vt, .Xr {
418		display: inline-block;
419		position: relative; }
420
421.An::before {	content: "An"; }
422.Ar::before {	content: "Ar"; }
423.Cd::before {	content: "Cd"; }
424.Cm::before {	content: "Cm"; }
425.Dv::before {	content: "Dv"; }
426.Em::before {	content: "Em"; }
427.Er::before {	content: "Er"; }
428.Ev::before {	content: "Ev"; }
429.Fa::before {	content: "Fa"; }
430.Fd::before {	content: "Fd"; }
431.Fl::before {	content: "Fl"; }
432.Fn::before {	content: "Fn"; }
433.Ft::before {	content: "Ft"; }
434.Ic::before {	content: "Ic"; }
435code.In::before { content: "In"; }
436.Lb::before {	content: "Lb"; }
437.Lk::before {	content: "Lk"; }
438.Ms::before {	content: "Ms"; }
439.Mt::before {	content: "Mt"; }
440.Nd::before {	content: "Nd"; }
441code.Nm::before { content: "Nm"; }
442.Pa::before {	content: "Pa"; }
443.Rs::before {	content: "Rs"; }
444h1.Sh::before {	content: "Sh"; }
445h2.Ss::before {	content: "Ss"; }
446.St::before {	content: "St"; }
447.Sx::before {	content: "Sx"; }
448.Sy::before {	content: "Sy"; }
449.Va::before {	content: "Va"; }
450.Vt::before {	content: "Vt"; }
451.Xr::before {	content: "Xr"; }
452
453.An::before, .Ar::before, .Cd::before, .Cm::before,
454.Dv::before, .Em::before, .Er::before, .Ev::before,
455.Fa::before, .Fd::before, .Fl::before, .Fn::before, .Ft::before,
456.Ic::before, code.In::before, .Lb::before, .Lk::before,
457.Ms::before, .Mt::before, .Nd::before, code.Nm::before,
458.Pa::before, .Rs::before,
459h1.Sh::before, h2.Ss::before, .St::before, .Sx::before, .Sy::before,
460.Va::before, .Vt::before, .Xr::before {
461		opacity: 0;
462		transition: .15s ease opacity;
463		pointer-events: none;
464		position: absolute;
465		bottom: 100%;
466		box-shadow: 0 0 .35em #000;
467		padding: .15em .25em;
468		white-space: nowrap;
469		font-family: Helvetica,Arial,sans-serif;
470		font-style: normal;
471		font-weight: bold;
472		color: black;
473		background: #fff; }
474.An:hover::before, .Ar:hover::before, .Cd:hover::before, .Cm:hover::before,
475.Dv:hover::before, .Em:hover::before, .Er:hover::before, .Ev:hover::before,
476.Fa:hover::before, .Fd:hover::before, .Fl:hover::before, .Fn:hover::before,
477.Ft:hover::before, .Ic:hover::before, code.In:hover::before,
478.Lb:hover::before, .Lk:hover::before, .Ms:hover::before, .Mt:hover::before,
479.Nd:hover::before, code.Nm:hover::before, .Pa:hover::before,
480.Rs:hover::before, h1.Sh:hover::before, h2.Ss:hover::before, .St:hover::before,
481.Sx:hover::before, .Sy:hover::before, .Va:hover::before, .Vt:hover::before,
482.Xr:hover::before {
483		opacity: 1;
484		pointer-events: inherit; }
485
486/* Overrides to avoid excessive margins on small devices. */
487
488@media (max-width: 37.5em) {
489.manual-text {
490		margin-left: 0.5em; }
491h1.Sh, h2.Ss {	margin-left: 0em; }
492.Bd-indent {	margin-left: 2em; }
493.Bl-hang > dd {
494		margin-left: 2em; }
495.Bl-tag {	margin-left: 2em; }
496.Bl-tag > dt {
497		margin-left: -2em; }
498.HP {		margin-left: 2em;
499		text-indent: -2em; }
500}
501'
502
503#
504# Display remote target with prefix and trailing slash.
505#
506function print_upload_header
507{
508	typeset -r prefix=$1
509	typeset display_target
510
511	if [[ -z $tflag ]]; then
512		display_target=${prefix}${remote_target}
513	else
514		display_target=${remote_target}
515	fi
516
517	if [[ ${display_target} != */ ]]; then
518		display_target=${display_target}/
519	fi
520
521	print "      Upload to: ${display_target}\n" \
522	    "     Uploading: \c"
523}
524
525#
526# Upload the webrev via rsync. Return 0 on success, 1 on error.
527#
528function rsync_upload
529{
530	if (( $# != 2 )); then
531		print "\nERROR: rsync_upload: wrong usage ($#)"
532		exit 1
533	fi
534
535	typeset -r dst=$1
536	integer -r print_err_msg=$2
537
538	print_upload_header ${rsync_prefix}
539	print "rsync ... \c"
540	typeset -r err_msg=$( $MKTEMP /tmp/rsync_err.XXXXXX )
541	if [[ -z $err_msg ]]; then
542		print "\nERROR: rsync_upload: cannot create temporary file"
543		return 1
544	fi
545	#
546	# The source directory must end with a slash in order to copy just
547	# directory contents, not the whole directory.
548	#
549	typeset src_dir=$WDIR
550	if [[ ${src_dir} != */ ]]; then
551		src_dir=${src_dir}/
552	fi
553	$RSYNC -r -q ${src_dir} $dst 2>$err_msg
554	if (( $? != 0 )); then
555		if (( print_err_msg > 0 )); then
556			print "Failed.\nERROR: rsync failed"
557			print "src dir: '${src_dir}'\ndst dir: '$dst'"
558			print "error messages:"
559			$SED 's/^/> /' $err_msg
560			rm -f $err_msg
561		fi
562		return 1
563	fi
564
565	rm -f $err_msg
566	print "Done."
567	return 0
568}
569
570#
571# Create directories on remote host using SFTP. Return 0 on success,
572# 1 on failure.
573#
574function remote_mkdirs
575{
576	typeset -r dir_spec=$1
577	typeset -r host_spec=$2
578
579	#
580	# If the supplied path is absolute we assume all directories are
581	# created, otherwise try to create all directories in the path
582	# except the last one which will be created by scp.
583	#
584	if [[ "${dir_spec}" == */* && "${dir_spec}" != /* ]]; then
585		print "mkdirs \c"
586		#
587		# Remove the last directory from directory specification.
588		#
589		typeset -r dirs_mk=${dir_spec%/*}
590		typeset -r batch_file_mkdir=$( $MKTEMP \
591		    /tmp/webrev_mkdir.XXXXXX )
592		if [[ -z $batch_file_mkdir ]]; then
593			print "\nERROR: remote_mkdirs:" \
594			    "cannot create temporary file for batch file"
595			return 1
596		fi
597		OLDIFS=$IFS
598		IFS=/
599		typeset dir
600		for dir in ${dirs_mk}; do
601			#
602			# Use the '-' prefix to ignore mkdir errors in order
603			# to avoid an error in case the directory already
604			# exists. We check the directory with chdir to be sure
605			# there is one.
606			#
607			print -- "-mkdir ${dir}" >> ${batch_file_mkdir}
608			print "chdir ${dir}" >> ${batch_file_mkdir}
609		done
610		IFS=$OLDIFS
611		typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
612		if [[ -z ${sftp_err_msg} ]]; then
613			print "\nERROR: remote_mkdirs:" \
614			    "cannot create temporary file for error messages"
615			return 1
616		fi
617		$SFTP -b ${batch_file_mkdir} ${host_spec} 2>${sftp_err_msg} 1>&2
618		if (( $? != 0 )); then
619			print "\nERROR: failed to create remote directories"
620			print "error messages:"
621			$SED 's/^/> /' ${sftp_err_msg}
622			rm -f ${sftp_err_msg} ${batch_file_mkdir}
623			return 1
624		fi
625		rm -f ${sftp_err_msg} ${batch_file_mkdir}
626	fi
627
628	return 0
629}
630
631#
632# Upload the webrev via SSH. Return 0 on success, 1 on error.
633#
634function ssh_upload
635{
636	if (( $# != 1 )); then
637		print "\nERROR: ssh_upload: wrong number of arguments"
638		exit 1
639	fi
640
641	typeset dst=$1
642	typeset -r host_spec=${dst%%:*}
643	typeset -r dir_spec=${dst#*:}
644
645	#
646	# Display the upload information before calling delete_webrev
647	# because it will also print its progress.
648	#
649	print_upload_header ${ssh_prefix}
650
651	#
652	# If the deletion was explicitly requested there is no need
653	# to perform it again.
654	#
655	if [[ -z $Dflag ]]; then
656		#
657		# We do not care about return value because this might be
658		# the first time this directory is uploaded.
659		#
660		delete_webrev 0
661	fi
662
663	#
664	# Create remote directories. Any error reporting will be done
665	# in remote_mkdirs function.
666	#
667	remote_mkdirs ${dir_spec} ${host_spec}
668	if (( $? != 0 )); then
669		return 1
670	fi
671
672	print "upload ... \c"
673	typeset -r scp_err_msg=$( $MKTEMP /tmp/scp_err.XXXXXX )
674	if [[ -z ${scp_err_msg} ]]; then
675		print "\nERROR: ssh_upload:" \
676		    "cannot create temporary file for error messages"
677		return 1
678	fi
679	$SCP -q -C -B -o PreferredAuthentications=publickey -r \
680		$WDIR $dst 2>${scp_err_msg}
681	if (( $? != 0 )); then
682		print "Failed.\nERROR: scp failed"
683		print "src dir: '$WDIR'\ndst dir: '$dst'"
684		print "error messages:"
685		$SED 's/^/> /' ${scp_err_msg}
686		rm -f ${scp_err_msg}
687		return 1
688	fi
689
690	rm -f ${scp_err_msg}
691	print "Done."
692	return 0
693}
694
695#
696# Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp
697# on failure. If first argument is 1 then perform the check of sftp return
698# value otherwise ignore it. If second argument is present it means this run
699# only performs deletion.
700#
701function delete_webrev
702{
703	if (( $# < 1 )); then
704		print "delete_webrev: wrong number of arguments"
705		exit 1
706	fi
707
708	integer -r check=$1
709	integer delete_only=0
710	if (( $# == 2 )); then
711		delete_only=1
712	fi
713
714	#
715	# Strip the transport specification part of remote target first.
716	#
717	typeset -r stripped_target=${remote_target##*://}
718	typeset -r host_spec=${stripped_target%%:*}
719	typeset -r dir_spec=${stripped_target#*:}
720	typeset dir_rm
721
722	#
723	# Do not accept an absolute path.
724	#
725	if [[ ${dir_spec} == /* ]]; then
726		return 1
727	fi
728
729	#
730	# Strip the ending slash.
731	#
732	if [[ ${dir_spec} == */ ]]; then
733		dir_rm=${dir_spec%%/}
734	else
735		dir_rm=${dir_spec}
736	fi
737
738	if (( delete_only > 0 )); then
739		print "       Removing: \c"
740	else
741		print "rmdir \c"
742	fi
743	if [[ -z "$dir_rm" ]]; then
744		print "\nERROR: empty directory for removal"
745		return 1
746	fi
747
748	#
749	# Prepare batch file.
750	#
751	typeset -r batch_file_rm=$( $MKTEMP /tmp/webrev_remove.XXXXXX )
752	if [[ -z $batch_file_rm ]]; then
753		print "\nERROR: delete_webrev: cannot create temporary file"
754		return 1
755	fi
756	print "rename $dir_rm $TRASH_DIR/removed.$$" > $batch_file_rm
757
758	#
759	# Perform remote deletion and remove the batch file.
760	#
761	typeset -r sftp_err_msg=$( $MKTEMP /tmp/webrev_scp_err.XXXXXX )
762	if [[ -z ${sftp_err_msg} ]]; then
763		print "\nERROR: delete_webrev:" \
764		    "cannot create temporary file for error messages"
765		return 1
766	fi
767	$SFTP -b $batch_file_rm $host_spec 2>${sftp_err_msg} 1>&2
768	integer -r ret=$?
769	rm -f $batch_file_rm
770	if (( ret != 0 && check > 0 )); then
771		print "Failed.\nERROR: failed to remove remote directories"
772		print "error messages:"
773		$SED 's/^/> /' ${sftp_err_msg}
774		rm -f ${sftp_err_msg}
775		return $ret
776	fi
777	rm -f ${sftp_err_msg}
778	if (( delete_only > 0 )); then
779		print "Done."
780	fi
781
782	return 0
783}
784
785#
786# Upload webrev to remote site
787#
788function upload_webrev
789{
790	integer ret
791
792	if [[ ! -d "$WDIR" ]]; then
793		print "\nERROR: webrev directory '$WDIR' does not exist"
794		return 1
795	fi
796
797	#
798	# Perform a late check to make sure we do not upload closed source
799	# to remote target when -n is used. If the user used custom remote
800	# target he probably knows what he is doing.
801	#
802	if [[ -n $nflag && -z $tflag ]]; then
803		$FIND $WDIR -type d -name closed \
804			| $GREP closed >/dev/null
805		if (( $? == 0 )); then
806			print "\nERROR: directory '$WDIR' contains" \
807			    "\"closed\" directory"
808			return 1
809		fi
810	fi
811
812
813	#
814	# We have the URI for remote destination now so let's start the upload.
815	#
816	if [[ -n $tflag ]]; then
817		if [[ "${remote_target}" == ${rsync_prefix}?* ]]; then
818			rsync_upload ${remote_target##$rsync_prefix} 1
819			ret=$?
820			return $ret
821		elif [[ "${remote_target}" == ${ssh_prefix}?* ]]; then
822			ssh_upload ${remote_target##$ssh_prefix}
823			ret=$?
824			return $ret
825		fi
826	else
827		#
828		# Try rsync first and fallback to SSH in case it fails.
829		#
830		rsync_upload ${remote_target} 0
831		ret=$?
832		if (( ret != 0 )); then
833			print "Failed. (falling back to SSH)"
834			ssh_upload ${remote_target}
835			ret=$?
836		fi
837		return $ret
838	fi
839}
840
841#
842# input_cmd | url_encode | output_cmd
843#
844# URL-encode (percent-encode) reserved characters as defined in RFC 3986.
845#
846# Reserved characters are: :/?#[]@!$&'()*+,;=
847#
848# While not a reserved character itself, percent '%' is reserved by definition
849# so encode it first to avoid recursive transformation, and skip '/' which is
850# a path delimiter.
851#
852# The quotation character is deliberately not escaped in order to make
853# the substitution work with GNU sed.
854#
855function url_encode
856{
857	$SED -e "s|%|%25|g" -e "s|:|%3A|g" -e "s|\&|%26|g" \
858	    -e "s|?|%3F|g" -e "s|#|%23|g" -e "s|\[|%5B|g" \
859	    -e "s|*|%2A|g" -e "s|@|%40|g" -e "s|\!|%21|g" \
860	    -e "s|=|%3D|g" -e "s|;|%3B|g" -e "s|\]|%5D|g" \
861	    -e "s|(|%28|g" -e "s|)|%29|g" -e "s|'|%27|g" \
862	    -e "s|+|%2B|g" -e "s|\,|%2C|g" -e "s|\\\$|%24|g"
863}
864
865#
866# input_cmd | html_quote | output_cmd
867# or
868# html_quote filename | output_cmd
869#
870# Make a piece of source code safe for display in an HTML <pre> block.
871#
872html_quote()
873{
874	$SED -e "s/&/\&amp;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g" "$@" | expand
875}
876
877#
878# Trim a digest-style revision to a conventionally readable yet useful length
879#
880trim_digest()
881{
882	typeset digest=$1
883
884	echo $digest | $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'
885}
886
887#
888# input_cmd | its2url | output_cmd
889#
890# Scan for information tracking system references and insert <a> links to the
891# relevant databases.
892#
893its2url()
894{
895	$SED -f ${its_sed_script}
896}
897
898#
899# strip_unchanged <infile> | output_cmd
900#
901# Removes chunks of sdiff documents that have not changed. This makes it
902# easier for a code reviewer to find the bits that have changed.
903#
904# Deleted lines of text are replaced by a horizontal rule. Some
905# identical lines are retained before and after the changed lines to
906# provide some context.  The number of these lines is controlled by the
907# variable C in the $AWK script below.
908#
909# The script detects changed lines as any line that has a "<span class="
910# string embedded (unchanged lines have no particular class and are not
911# part of a <span>).  Blank lines (without a sequence number) are also
912# detected since they flag lines that have been inserted or deleted.
913#
914strip_unchanged()
915{
916	$AWK '
917	BEGIN	{ C = c = 20 }
918	NF == 0 || /<span class="/ {
919		if (c > C) {
920			c -= C
921			inx = 0
922			if (c > C) {
923				print "\n</pre><hr></hr><pre>"
924				inx = c % C
925				c = C
926			}
927
928			for (i = 0; i < c; i++)
929				print ln[(inx + i) % C]
930		}
931		c = 0;
932		print
933		next
934	}
935	{	if (c >= C) {
936			ln[c % C] = $0
937			c++;
938			next;
939		}
940		c++;
941		print
942	}
943	END	{ if (c > (C * 2)) print "\n</pre><hr></hr>" }
944
945	' $1
946}
947
948#
949# sdiff_to_html
950#
951# This function takes two files as arguments, obtains their diff, and
952# processes the diff output to present the files as an HTML document with
953# the files displayed side-by-side, differences shown in color.  It also
954# takes a delta comment, rendered as an HTML snippet, as the third
955# argument.  The function takes two files as arguments, then the name of
956# file, the path, and the comment.  The HTML will be delivered on stdout,
957# e.g.
958#
959#   $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
960#         new/usr/src/tools/scripts/webrev.sh \
961#         webrev.sh usr/src/tools/scripts \
962#         '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
963#          1234567</a> my bugid' > <file>.html
964#
965# framed_sdiff() is then called which creates $2.frames.html
966# in the webrev tree.
967#
968# FYI: This function is rather unusual in its use of awk.  The initial
969# diff run produces conventional diff output showing changed lines mixed
970# with editing codes.  The changed lines are ignored - we're interested in
971# the editing codes, e.g.
972#
973#      8c8
974#      57a61
975#      63c66,76
976#      68,93d80
977#      106d90
978#      108,110d91
979#
980#  These editing codes are parsed by the awk script and used to generate
981#  another awk script that generates HTML, e.g the above lines would turn
982#  into something like this:
983#
984#      BEGIN { printf "<pre>\n" }
985#      function sp(n) {for (i=0;i<n;i++)printf "\n"}
986#      function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
987#      NR==8           {wl("#7A7ADD");next}
988#      NR==54          {wl("#7A7ADD");sp(3);next}
989#      NR==56          {wl("#7A7ADD");next}
990#      NR==57          {wl("black");printf "\n"; next}
991#        :               :
992#
993#  This script is then run on the original source file to generate the
994#  HTML that corresponds to the source file.
995#
996#  The two HTML files are then combined into a single piece of HTML that
997#  uses an HTML table construct to present the files side by side.  You'll
998#  notice that the changes are color-coded:
999#
1000#   black     - unchanged lines
1001#   blue      - changed lines
1002#   bold blue - new lines
1003#   brown     - deleted lines
1004#
1005#  Blank lines are inserted in each file to keep unchanged lines in sync
1006#  (side-by-side).  This format is familiar to users of sdiff(1) or
1007#  Teamware's filemerge tool.
1008#
1009sdiff_to_html()
1010{
1011	diff -b $1 $2 > /tmp/$$.diffs
1012
1013	TNAME=$3
1014	TPATH=$4
1015	COMMENT=$5
1016
1017	#
1018	#  Now we have the diffs, generate the HTML for the old file.
1019	#
1020	$AWK '
1021	BEGIN	{
1022		printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
1023		printf "function removed() "
1024		printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
1025		printf "function changed() "
1026		printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
1027		printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
1028}
1029	/^</	{next}
1030	/^>/	{next}
1031	/^---/	{next}
1032
1033	{
1034	split($1, a, /[cad]/) ;
1035	if (index($1, "a")) {
1036		if (a[1] == 0) {
1037			n = split(a[2], r, /,/);
1038			if (n == 1)
1039				printf "BEGIN\t\t{sp(1)}\n"
1040			else
1041				printf "BEGIN\t\t{sp(%d)}\n",\
1042				(r[2] - r[1]) + 1
1043			next
1044		}
1045
1046		printf "NR==%s\t\t{", a[1]
1047		n = split(a[2], r, /,/);
1048		s = r[1];
1049		if (n == 1)
1050			printf "bl();printf \"\\n\"; next}\n"
1051		else {
1052			n = r[2] - r[1]
1053			printf "bl();sp(%d);next}\n",\
1054			(r[2] - r[1]) + 1
1055		}
1056		next
1057	}
1058	if (index($1, "d")) {
1059		n = split(a[1], r, /,/);
1060		n1 = r[1]
1061		n2 = r[2]
1062		if (n == 1)
1063			printf "NR==%s\t\t{removed(); next}\n" , n1
1064		else
1065			printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
1066		next
1067	}
1068	if (index($1, "c")) {
1069		n = split(a[1], r, /,/);
1070		n1 = r[1]
1071		n2 = r[2]
1072		final = n2
1073		d1 = 0
1074		if (n == 1)
1075			printf "NR==%s\t\t{changed();" , n1
1076		else {
1077			d1 = n2 - n1
1078			printf "NR==%s,NR==%s\t{changed();" , n1, n2
1079		}
1080		m = split(a[2], r, /,/);
1081		n1 = r[1]
1082		n2 = r[2]
1083		if (m > 1) {
1084			d2  = n2 - n1
1085			if (d2 > d1) {
1086				if (n > 1) printf "if (NR==%d)", final
1087				printf "sp(%d);", d2 - d1
1088			}
1089		}
1090		printf "next}\n" ;
1091
1092		next
1093	}
1094	}
1095
1096	END	{ printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
1097	' /tmp/$$.diffs > /tmp/$$.file1
1098
1099	#
1100	#  Now generate the HTML for the new file
1101	#
1102	$AWK '
1103	BEGIN	{
1104		printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
1105		printf "function new() "
1106		printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
1107		printf "function changed() "
1108		printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
1109		printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
1110	}
1111
1112	/^</	{next}
1113	/^>/	{next}
1114	/^---/	{next}
1115
1116	{
1117	split($1, a, /[cad]/) ;
1118	if (index($1, "d")) {
1119		if (a[2] == 0) {
1120			n = split(a[1], r, /,/);
1121			if (n == 1)
1122				printf "BEGIN\t\t{sp(1)}\n"
1123			else
1124				printf "BEGIN\t\t{sp(%d)}\n",\
1125				(r[2] - r[1]) + 1
1126			next
1127		}
1128
1129		printf "NR==%s\t\t{", a[2]
1130		n = split(a[1], r, /,/);
1131		s = r[1];
1132		if (n == 1)
1133			printf "bl();printf \"\\n\"; next}\n"
1134		else {
1135			n = r[2] - r[1]
1136			printf "bl();sp(%d);next}\n",\
1137			(r[2] - r[1]) + 1
1138		}
1139		next
1140	}
1141	if (index($1, "a")) {
1142		n = split(a[2], r, /,/);
1143		n1 = r[1]
1144		n2 = r[2]
1145		if (n == 1)
1146			printf "NR==%s\t\t{new() ; next}\n" , n1
1147		else
1148			printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
1149		next
1150	}
1151	if (index($1, "c")) {
1152		n = split(a[2], r, /,/);
1153		n1 = r[1]
1154		n2 = r[2]
1155		final = n2
1156		d2 = 0;
1157		if (n == 1) {
1158			final = n1
1159			printf "NR==%s\t\t{changed();" , n1
1160		} else {
1161			d2 = n2 - n1
1162			printf "NR==%s,NR==%s\t{changed();" , n1, n2
1163		}
1164		m = split(a[1], r, /,/);
1165		n1 = r[1]
1166		n2 = r[2]
1167		if (m > 1) {
1168			d1  = n2 - n1
1169			if (d1 > d2) {
1170				if (n > 1) printf "if (NR==%d)", final
1171				printf "sp(%d);", d1 - d2
1172			}
1173		}
1174		printf "next}\n" ;
1175		next
1176	}
1177	}
1178	END	{ printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
1179	' /tmp/$$.diffs > /tmp/$$.file2
1180
1181	#
1182	# Post-process the HTML files by running them back through $AWK
1183	#
1184	html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html
1185
1186	html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html
1187
1188	#
1189	# Now combine into a valid HTML file and side-by-side into a table
1190	#
1191	print "$HTML<head>$STDHEAD"
1192	print "<title>$WNAME Sdiff $TPATH/$TNAME</title>"
1193	print "</head><body id=\"SUNWwebrev\">"
1194	print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
1195	print "<pre>$COMMENT</pre>\n"
1196	print "<table><tr valign=\"top\">"
1197	print "<td><pre>"
1198
1199	strip_unchanged /tmp/$$.file1.html
1200
1201	print "</pre></td><td><pre>"
1202
1203	strip_unchanged /tmp/$$.file2.html
1204
1205	print "</pre></td>"
1206	print "</tr></table>"
1207	print "</body></html>"
1208
1209	framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
1210	    "$COMMENT"
1211}
1212
1213
1214#
1215# framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
1216#
1217# Expects lefthand and righthand side html files created by sdiff_to_html.
1218# We use insert_anchors() to augment those with HTML navigation anchors,
1219# and then emit the main frame.  Content is placed into:
1220#
1221#    $WDIR/DIR/$TNAME.lhs.html
1222#    $WDIR/DIR/$TNAME.rhs.html
1223#    $WDIR/DIR/$TNAME.frames.html
1224#
1225# NOTE: We rely on standard usage of $WDIR and $DIR.
1226#
1227function framed_sdiff
1228{
1229	typeset TNAME=$1
1230	typeset TPATH=$2
1231	typeset lhsfile=$3
1232	typeset rhsfile=$4
1233	typeset comments=$5
1234	typeset RTOP
1235
1236	# Enable html files to access WDIR via a relative path.
1237	RTOP=$(relative_dir $TPATH $WDIR)
1238
1239	# Make the rhs/lhs files and output the frameset file.
1240	print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
1241
1242	cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
1243	    <script type="text/javascript" src="${RTOP}ancnav.js"></script>
1244	    </head>
1245	    <body id="SUNWwebrev" onkeypress="keypress(event);">
1246	    <a name="0"></a>
1247	    <pre>$comments</pre><hr></hr>
1248	EOF
1249
1250	cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
1251
1252	insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
1253	insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
1254
1255	close='</body></html>'
1256
1257	print $close >> $WDIR/$DIR/$TNAME.lhs.html
1258	print $close >> $WDIR/$DIR/$TNAME.rhs.html
1259
1260	print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
1261	print "<title>$WNAME Framed-Sdiff " \
1262	    "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
1263	cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
1264	  <frameset rows="*,60">
1265	    <frameset cols="50%,50%">
1266	      <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
1267	      <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
1268	    </frameset>
1269	  <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0"
1270	   marginheight="0" name="nav"></frame>
1271	  <noframes>
1272	    <body id="SUNWwebrev">
1273	      Alas 'frames' webrev requires that your browser supports frames
1274	      and has the feature enabled.
1275	    </body>
1276	  </noframes>
1277	  </frameset>
1278	</html>
1279	EOF
1280}
1281
1282
1283#
1284# input_cmd | insert_anchors | output_cmd
1285#
1286# Flag blocks of difference with sequentially numbered invisible
1287# anchors.  These are used to drive the frames version of the
1288# sdiffs output.
1289#
1290# NOTE: Anchor zero flags the top of the file irrespective of changes,
1291# an additional anchor is also appended to flag the bottom.
1292#
1293# The script detects changed lines as any line that has a "<span
1294# class=" string embedded (unchanged lines have no class set and are
1295# not part of a <span>.  Blank lines (without a sequence number)
1296# are also detected since they flag lines that have been inserted or
1297# deleted.
1298#
1299function insert_anchors
1300{
1301	$AWK '
1302	function ia() {
1303		printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
1304	}
1305
1306	BEGIN {
1307		anc=1;
1308		inblock=1;
1309		printf "<pre>\n";
1310	}
1311	NF == 0 || /^<span class=/ {
1312		if (inblock == 0) {
1313			ia();
1314			inblock=1;
1315		}
1316		print;
1317		next;
1318	}
1319	{
1320		inblock=0;
1321		print;
1322	}
1323	END {
1324		ia();
1325
1326		printf "<b style=\"font-size: large; color: red\">";
1327		printf "--- EOF ---</b>"
1328		for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
1329		printf "</pre>"
1330		printf "<form name=\"eof\">";
1331		printf "<input name=\"value\" value=\"%d\" " \
1332		    "type=\"hidden\"></input>", anc - 1;
1333		printf "</form>";
1334	}
1335	' $1
1336}
1337
1338
1339#
1340# relative_dir
1341#
1342# Print a relative return path from $1 to $2.  For example if
1343# $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
1344# this function would print "../../../../".
1345#
1346# In the event that $1 is not in $2 a warning is printed to stderr,
1347# and $2 is returned-- the result of this is that the resulting webrev
1348# is not relocatable.
1349#
1350function relative_dir
1351{
1352	typeset cur="${1##$2?(/)}"
1353
1354	#
1355	# If the first path was specified absolutely, and it does
1356	# not start with the second path, it's an error.
1357	#
1358	if [[ "$cur" == "/${1#/}" ]]; then
1359		# Should never happen.
1360		print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
1361		print -u2 "to \"$2\".  Check input paths.  Framed webrev "
1362		print -u2 "will not be relocatable!"
1363		print $2
1364		return
1365	fi
1366
1367	#
1368	# This is kind of ugly.  The sed script will do the following:
1369	#
1370	# 1. Strip off a leading "." or "./": this is important to get
1371	#    the correct arcnav links for files in $WDIR.
1372	# 2. Strip off a trailing "/": this is not strictly necessary,
1373	#    but is kind of nice, since it doesn't end up in "//" at
1374	#    the end of a relative path.
1375	# 3. Replace all remaining sequences of non-"/" with "..": the
1376	#    assumption here is that each dirname represents another
1377	#    level of relative separation.
1378	# 4. Append a trailing "/" only for non-empty paths: this way
1379	#    the caller doesn't need to duplicate this logic, and does
1380	#    not end up using $RTOP/file for files in $WDIR.
1381	#
1382	print $cur | $SED -e '{
1383		s:^\./*::
1384		s:/$::
1385		s:[^/][^/]*:..:g
1386		s:^\(..*\)$:\1/:
1387	}'
1388}
1389
1390#
1391# frame_nav_js
1392#
1393# Emit javascript for frame navigation
1394#
1395function frame_nav_js
1396{
1397cat << \EOF
1398var myInt;
1399var scrolling = 0;
1400var sfactor = 3;
1401var scount = 10;
1402
1403function scrollByPix()
1404{
1405	if (scount <= 0) {
1406		sfactor *= 1.2;
1407		scount = 10;
1408	}
1409	parent.lhs.scrollBy(0, sfactor);
1410	parent.rhs.scrollBy(0, sfactor);
1411	scount--;
1412}
1413
1414function scrollToAnc(num)
1415{
1416	// Update the value of the anchor in the form which we use as
1417	// storage for this value.  setAncValue() will take care of
1418	// correcting for overflow and underflow of the value and return
1419	// us the new value.
1420	num = setAncValue(num);
1421
1422	// Set location and scroll back a little to expose previous
1423	// lines.
1424	//
1425	// Note that this could be improved: it is possible although
1426	// complex to compute the x and y position of an anchor, and to
1427	// scroll to that location directly.
1428	//
1429	parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
1430	parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
1431
1432	parent.lhs.scrollBy(0, -30);
1433	parent.rhs.scrollBy(0, -30);
1434}
1435
1436function getAncValue()
1437{
1438	return (parseInt(parent.nav.document.diff.real.value));
1439}
1440
1441function setAncValue(val)
1442{
1443	if (val <= 0) {
1444		val = 0;
1445		parent.nav.document.diff.real.value = val;
1446		parent.nav.document.diff.display.value = "BOF";
1447		return (val);
1448	}
1449
1450	//
1451	// The way we compute the max anchor value is to stash it
1452	// inline in the left and right hand side pages-- it's the same
1453	// on each side, so we pluck from the left.
1454	//
1455	maxval = parent.lhs.document.eof.value.value;
1456	if (val < maxval) {
1457		parent.nav.document.diff.real.value = val;
1458		parent.nav.document.diff.display.value = val.toString();
1459		return (val);
1460	}
1461
1462	// this must be: val >= maxval
1463	val = maxval;
1464	parent.nav.document.diff.real.value = val;
1465	parent.nav.document.diff.display.value = "EOF";
1466	return (val);
1467}
1468
1469function stopScroll()
1470{
1471	if (scrolling == 1) {
1472		clearInterval(myInt);
1473		scrolling = 0;
1474	}
1475}
1476
1477function startScroll()
1478{
1479	stopScroll();
1480	scrolling = 1;
1481	myInt = setInterval("scrollByPix()", 10);
1482}
1483
1484function handlePress(b)
1485{
1486	switch (b) {
1487	case 1:
1488		scrollToAnc(-1);
1489		break;
1490	case 2:
1491		scrollToAnc(getAncValue() - 1);
1492		break;
1493	case 3:
1494		sfactor = -3;
1495		startScroll();
1496		break;
1497	case 4:
1498		sfactor = 3;
1499		startScroll();
1500		break;
1501	case 5:
1502		scrollToAnc(getAncValue() + 1);
1503		break;
1504	case 6:
1505		scrollToAnc(999999);
1506		break;
1507	}
1508}
1509
1510function handleRelease(b)
1511{
1512	stopScroll();
1513}
1514
1515function keypress(ev)
1516{
1517	var keynum;
1518	var keychar;
1519
1520	if (window.event) { // IE
1521		keynum = ev.keyCode;
1522	} else if (ev.which) { // non-IE
1523		keynum = ev.which;
1524	}
1525
1526	keychar = String.fromCharCode(keynum);
1527
1528	if (keychar == "k") {
1529		handlePress(2);
1530		return (0);
1531	} else if (keychar == "j" || keychar == " ") {
1532		handlePress(5);
1533		return (0);
1534	}
1535
1536	return (1);
1537}
1538
1539function ValidateDiffNum()
1540{
1541	var val;
1542	var i;
1543
1544	val = parent.nav.document.diff.display.value;
1545	if (val == "EOF") {
1546		scrollToAnc(999999);
1547		return;
1548	}
1549
1550	if (val == "BOF") {
1551		scrollToAnc(0);
1552		return;
1553	}
1554
1555	i = parseInt(val);
1556	if (isNaN(i)) {
1557		parent.nav.document.diff.display.value = getAncValue();
1558	} else {
1559		scrollToAnc(i);
1560	}
1561
1562	return (false);
1563}
1564EOF
1565}
1566
1567#
1568# frame_navigation
1569#
1570# Output anchor navigation file for framed sdiffs.
1571#
1572function frame_navigation
1573{
1574	print "$HTML<head>$STDHEAD"
1575
1576	cat << \EOF
1577<title>Anchor Navigation</title>
1578<meta http-equiv="Content-Script-Type" content="text/javascript">
1579<meta http-equiv="Content-Type" content="text/html">
1580
1581<style type="text/css">
1582    div.button td { padding-left: 5px; padding-right: 5px;
1583		    background-color: #eee; text-align: center;
1584		    border: 1px #444 outset; cursor: pointer; }
1585    div.button a { font-weight: bold; color: black }
1586    div.button td:hover { background: #ffcc99; }
1587</style>
1588EOF
1589
1590	print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
1591
1592	cat << \EOF
1593</head>
1594<body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
1595	onkeypress="keypress(event);">
1596    <noscript lang="javascript">
1597      <center>
1598	<p><big>Framed Navigation controls require Javascript</big><br></br>
1599	Either this browser is incompatable or javascript is not enabled</p>
1600      </center>
1601    </noscript>
1602    <table width="100%" border="0" align="center">
1603	<tr>
1604          <td valign="middle" width="25%">Diff navigation:
1605          Use 'j' and 'k' for next and previous diffs; or use buttons
1606          at right</td>
1607	  <td align="center" valign="top" width="50%">
1608	    <div class="button">
1609	      <table border="0" align="center">
1610                  <tr>
1611		    <td>
1612		      <a onMouseDown="handlePress(1);return true;"
1613			 onMouseUp="handleRelease(1);return true;"
1614			 onMouseOut="handleRelease(1);return true;"
1615			 onClick="return false;"
1616			 title="Go to Beginning Of file">BOF</a></td>
1617		    <td>
1618		      <a onMouseDown="handlePress(3);return true;"
1619			 onMouseUp="handleRelease(3);return true;"
1620			 onMouseOut="handleRelease(3);return true;"
1621			 title="Scroll Up: Press and Hold to accelerate"
1622			 onClick="return false;">Scroll Up</a></td>
1623		    <td>
1624		      <a onMouseDown="handlePress(2);return true;"
1625			 onMouseUp="handleRelease(2);return true;"
1626			 onMouseOut="handleRelease(2);return true;"
1627			 title="Go to previous Diff"
1628			 onClick="return false;">Prev Diff</a>
1629		    </td></tr>
1630
1631		  <tr>
1632		    <td>
1633		      <a onMouseDown="handlePress(6);return true;"
1634			 onMouseUp="handleRelease(6);return true;"
1635			 onMouseOut="handleRelease(6);return true;"
1636			 onClick="return false;"
1637			 title="Go to End Of File">EOF</a></td>
1638		    <td>
1639		      <a onMouseDown="handlePress(4);return true;"
1640			 onMouseUp="handleRelease(4);return true;"
1641			 onMouseOut="handleRelease(4);return true;"
1642			 title="Scroll Down: Press and Hold to accelerate"
1643			 onClick="return false;">Scroll Down</a></td>
1644		    <td>
1645		      <a onMouseDown="handlePress(5);return true;"
1646			 onMouseUp="handleRelease(5);return true;"
1647			 onMouseOut="handleRelease(5);return true;"
1648			 title="Go to next Diff"
1649			 onClick="return false;">Next Diff</a></td>
1650		  </tr>
1651              </table>
1652	    </div>
1653	  </td>
1654	  <th valign="middle" width="25%">
1655	    <form action="" name="diff" onsubmit="return ValidateDiffNum();">
1656		<input name="display" value="BOF" size="8" type="text"></input>
1657		<input name="real" value="0" size="8" type="hidden"></input>
1658	    </form>
1659	  </th>
1660	</tr>
1661    </table>
1662  </body>
1663</html>
1664EOF
1665}
1666
1667
1668
1669#
1670# diff_to_html <filename> <filepath> { U | C } <comment>
1671#
1672# Processes the output of diff to produce an HTML file representing either
1673# context or unified diffs.
1674#
1675diff_to_html()
1676{
1677	TNAME=$1
1678	TPATH=$2
1679	DIFFTYPE=$3
1680	COMMENT=$4
1681
1682	print "$HTML<head>$STDHEAD"
1683	print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
1684
1685	if [[ $DIFFTYPE == "U" ]]; then
1686		print "$UDIFFCSS"
1687	fi
1688
1689	cat <<-EOF
1690	</head>
1691	<body id="SUNWwebrev">
1692        <a class="print" href="javascript:print()">Print this page</a>
1693	<pre>$COMMENT</pre>
1694        <pre>
1695	EOF
1696
1697	html_quote | $AWK '
1698	/^--- new/	{ next }
1699	/^\+\+\+ new/	{ next }
1700	/^--- old/	{ next }
1701	/^\*\*\* old/	{ next }
1702	/^\*\*\*\*/	{ next }
1703	/^-------/	{ printf "<center><h1>%s</h1></center>\n", $0; next }
1704	/^\@\@.*\@\@$/	{ printf "</pre><hr></hr><pre>\n";
1705			  printf "<span class=\"newmarker\">%s</span>\n", $0;
1706			  next}
1707
1708	/^\*\*\*/	{ printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
1709			  next}
1710	/^---/		{ printf "<span class=\"newmarker\">%s</span>\n", $0;
1711			  next}
1712	/^\+/		{printf "<span class=\"new\">%s</span>\n", $0; next}
1713	/^!/		{printf "<span class=\"changed\">%s</span>\n", $0; next}
1714	/^-/		{printf "<span class=\"removed\">%s</span>\n", $0; next}
1715			{printf "%s\n", $0; next}
1716	'
1717
1718	print "</pre></body></html>\n"
1719}
1720
1721
1722#
1723# source_to_html { new | old } <filename>
1724#
1725# Process a plain vanilla source file to transform it into an HTML file.
1726#
1727source_to_html()
1728{
1729	WHICH=$1
1730	TNAME=$2
1731
1732	print "$HTML<head>$STDHEAD"
1733	print "<title>$WNAME $WHICH $TNAME</title>"
1734	print "<body id=\"SUNWwebrev\">"
1735	print "<pre>"
1736	html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
1737	print "</pre></body></html>"
1738}
1739
1740#
1741# comments_from_wx {text|html} filepath
1742#
1743# Given the pathname of a file, find its location in a "wx" active
1744# file list and print the following comment.  Output is either text or
1745# HTML; if the latter, embedded bugids (sequence of 5 or more digits)
1746# are turned into URLs.
1747#
1748comments_from_wx()
1749{
1750	typeset fmt=$1
1751	typeset p=$2
1752
1753	comm=$($AWK '$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 --show-toplevel from CODEMGR_WS environment variable
2435	# 2. git rev-parse --show-toplevel from directory of invocation
2436	#
2437	[[ -z $codemgr_ws && -n $CODEMGR_WS ]] && \
2438	    codemgr_ws=$($GIT -C $CODEMGR_WS rev-parse --show-toplevel \
2439		2>/dev/null)
2440	[[ -z $codemgr_ws ]] && \
2441	    codemgr_ws=$($GIT rev-parse --show-toplevel 2>/dev/null)
2442
2443	CWS="$codemgr_ws"
2444elif [[ $SCM_MODE == "subversion" ]]; then
2445	#
2446	# Subversion priorities:
2447	# 1. CODEMGR_WS from environment
2448	# 2. Relative path from current directory to SVN repository root
2449	#
2450	if [[ -n $CODEMGR_WS && -d $CODEMGR_WS/.svn ]]; then
2451		CWS=$CODEMGR_WS
2452	else
2453		svn info | while read line; do
2454			if [[ $line == "URL: "* ]]; then
2455				url=${line#URL: }
2456			elif [[ $line == "Repository Root: "* ]]; then
2457				repo=${line#Repository Root: }
2458			fi
2459		done
2460
2461		rel=${url#$repo}
2462		CWS=${PWD%$rel}
2463	fi
2464fi
2465
2466#
2467# If no SCM has been determined, take either the environment setting
2468# setting for CODEMGR_WS, or the current directory if that wasn't set.
2469#
2470if [[ -z ${CWS} ]]; then
2471	CWS=${CODEMGR_WS:-.}
2472fi
2473
2474#
2475# If the command line options indicate no webrev generation, either
2476# explicitly (-n) or implicitly (-D but not -U), then there's a whole
2477# ton of logic we can skip.
2478#
2479# Instead of increasing indentation, we intentionally leave this loop
2480# body open here, and exit via break from multiple points within.
2481# Search for DO_EVERYTHING below to find the break points and closure.
2482#
2483for do_everything in 1; do
2484
2485# DO_EVERYTHING: break point
2486if [[ -n $nflag || ( -z $Uflag && -n $Dflag ) ]]; then
2487	break
2488fi
2489
2490#
2491# If this manually set as the parent, and it appears to be an earlier webrev,
2492# then note that fact and set the parent to the raw_files/new subdirectory.
2493#
2494if [[ -n $pflag && -d $codemgr_parent/raw_files/new ]]; then
2495	parent_webrev=$(readlink -f "$codemgr_parent")
2496	codemgr_parent=$(readlink -f "$codemgr_parent/raw_files/new")
2497fi
2498
2499if [[ -z $wflag && -z $lflag ]]; then
2500	shift $(($OPTIND - 1))
2501
2502	if [[ $1 == "-" ]]; then
2503		cat > $FLIST
2504		flist_mode="stdin"
2505		flist_done=1
2506		shift
2507	elif [[ -n $1 ]]; then
2508		if [[ ! -r $1 ]]; then
2509			print -u2 "$1: no such file or not readable"
2510			usage
2511		fi
2512		cat $1 > $FLIST
2513		flist_mode="file"
2514		flist_file=$1
2515		flist_done=1
2516		shift
2517	else
2518		flist_mode="auto"
2519	fi
2520fi
2521
2522#
2523# Before we go on to further consider -l and -w, work out which SCM we think
2524# is in use.
2525#
2526case "$SCM_MODE" in
2527git|subversion)
2528	;;
2529unknown)
2530	if [[ $flist_mode == "auto" ]]; then
2531		print -u2 "Unable to determine SCM in use and file list not specified"
2532		print -u2 "See which_scm(1) for SCM detection information."
2533		exit 1
2534	fi
2535	;;
2536*)
2537	if [[ $flist_mode == "auto" ]]; then
2538		print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified"
2539		exit 1
2540	fi
2541	;;
2542esac
2543
2544print -u2 "   SCM detected: $SCM_MODE"
2545
2546if [[ -n $wflag ]]; then
2547	#
2548	# If the -w is given then assume the file list is in Bonwick's "wx"
2549	# command format, i.e.  pathname lines alternating with SCCS comment
2550	# lines with blank lines as separators.  Use the SCCS comments later
2551	# in building the index.html file.
2552	#
2553	shift $(($OPTIND - 1))
2554	wxfile=$1
2555	if [[ -z $wxfile && -n $CODEMGR_WS ]]; then
2556		if [[ -r $CODEMGR_WS/wx/active ]]; then
2557			wxfile=$CODEMGR_WS/wx/active
2558		fi
2559	fi
2560
2561	[[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \
2562	    "be auto-detected (check \$CODEMGR_WS)" && exit 1
2563
2564	if [[ ! -r $wxfile ]]; then
2565		print -u2 "$wxfile: no such file or not readable"
2566		usage
2567	fi
2568
2569	print -u2 " File list from: wx 'active' file '$wxfile' ... \c"
2570	flist_from_wx $wxfile
2571	flist_done=1
2572	if [[ -n "$*" ]]; then
2573		shift
2574	fi
2575elif [[ $flist_mode == "stdin" ]]; then
2576	print -u2 " File list from: standard input"
2577elif [[ $flist_mode == "file" ]]; then
2578	print -u2 " File list from: $flist_file"
2579fi
2580
2581if (( $# > 0 )); then
2582	print -u2 "WARNING: unused arguments: $*"
2583fi
2584
2585
2586if [[ $SCM_MODE == "git" ]]; then
2587	# Check that "head" revision specified with -c or -h is sane
2588	if [[ -n $cflag || -n $hflag ]]; then
2589		head_rev=$($GIT rev-parse --verify --quiet "$codemgr_head")
2590		if [[ -z $head_rev ]]; then
2591			print -u2 "Error: bad revision ${codemgr_head}"
2592			exit 1
2593		fi
2594	fi
2595
2596	if [[ -z $codemgr_head ]]; then
2597		codemgr_head="HEAD";
2598	fi
2599
2600	# Parent can either be specified with -p, or specified with
2601	# CODEMGR_PARENT in the environment.
2602	if [[ -z $codemgr_parent && -n $CODEMGR_PARENT ]]; then
2603		codemgr_parent=$CODEMGR_PARENT
2604	fi
2605
2606	# Try to figure out the parent based on the branch the current
2607	# branch is tracking, if we fail, use origin/master
2608	this_branch=$($GIT branch | nawk '$1 == "*" { print $2 }')
2609	par_branch="origin/master"
2610
2611	# If we're not on a branch there's nothing we can do
2612	if [[ $this_branch != "(no branch)" ]]; then
2613		$GIT for-each-ref					\
2614		    --format='%(refname:short) %(upstream:short)'	\
2615		    refs/heads/ |					\
2616		    while read local remote; do
2617			if [[ "$local" == "$this_branch" ]]; then
2618				par_branch="$remote"
2619			fi
2620		done
2621	fi
2622
2623	if [[ -z $codemgr_parent ]]; then
2624		codemgr_parent=$par_branch
2625	fi
2626	PWS=$codemgr_parent
2627
2628	#
2629	# If the parent is a webrev, we want to do some things against
2630	# the natural workspace parent (file list, comments, etc)
2631	#
2632	if [[ -n $parent_webrev ]]; then
2633		real_parent=$par_branch
2634	else
2635		real_parent=$PWS
2636	fi
2637
2638	if [[ -z $flist_done ]]; then
2639		flist_from_git "$codemgr_head" "$real_parent"
2640		flist_done=1
2641	fi
2642
2643	#
2644	# If we have a file list now, pull out any variables set
2645	# therein.
2646	#
2647	if [[ -n $flist_done ]]; then
2648		env_from_flist
2649	fi
2650
2651	#
2652	# If we don't have a wx-format file list, build one we can pull change
2653	# comments from.
2654	#
2655	if [[ -z $wxfile ]]; then
2656		print "  Comments from: git...\c"
2657		git_wxfile "$codemgr_head" "$real_parent"
2658		print " Done."
2659	fi
2660
2661	if [[ -z $GIT_PARENT ]]; then
2662		GIT_PARENT=$($GIT merge-base "$real_parent" "$codemgr_head")
2663	fi
2664	if [[ -z $GIT_PARENT ]]; then
2665		print -u2 "Error: Cannot discover parent revision"
2666		exit 1
2667	fi
2668
2669	pnode=$(trim_digest $GIT_PARENT)
2670
2671	if [[ -n $cflag ]]; then
2672		PRETTY_PWS="previous revision (at ${pnode})"
2673	elif [[ $real_parent == */* ]]; then
2674		origin=$(echo $real_parent | cut -d/ -f1)
2675		origin=$($GIT remote -v | \
2676		    $AWK '$1 == "'$origin'" { print $2; exit }')
2677		PRETTY_PWS="${PWS} (${origin} at ${pnode})"
2678	elif [[ -n $pflag && -z $parent_webrev ]]; then
2679		PRETTY_PWS="${CWS} (explicit revision ${pnode})"
2680	else
2681		PRETTY_PWS="${PWS} (at ${pnode})"
2682	fi
2683
2684	cnode=$($GIT --git-dir=${codemgr_ws}/.git rev-parse --short=12 \
2685	    ${codemgr_head} 2>/dev/null)
2686
2687	if [[ -n $cflag || -n $hflag ]]; then
2688		PRETTY_CWS="${CWS} (explicit head at ${cnode})"
2689	else
2690		PRETTY_CWS="${CWS} (at ${cnode})"
2691	fi
2692elif [[ $SCM_MODE == "subversion" ]]; then
2693
2694	#
2695	# We only will have a real parent workspace in the case one
2696	# was specified (be it an older webrev, or another checkout).
2697	#
2698	[[ -n $codemgr_parent ]] && PWS=$codemgr_parent
2699
2700	if [[ -z $flist_done && $flist_mode == "auto" ]]; then
2701		flist_from_subversion $CWS $OLDPWD
2702	fi
2703else
2704	if [[ $SCM_MODE == "unknown" ]]; then
2705		print -u2 "    Unknown type of SCM in use"
2706	else
2707		print -u2 "    Unsupported SCM in use: $SCM_MODE"
2708	fi
2709
2710	env_from_flist
2711
2712	if [[ -z $CODEMGR_WS ]]; then
2713		print -u2 "SCM not detected/supported and " \
2714		    "CODEMGR_WS not specified"
2715		exit 1
2716		fi
2717
2718	if [[ -z $CODEMGR_PARENT ]]; then
2719		print -u2 "SCM not detected/supported and " \
2720		    "CODEMGR_PARENT not specified"
2721		exit 1
2722	fi
2723
2724	CWS=$CODEMGR_WS
2725	PWS=$CODEMGR_PARENT
2726fi
2727
2728#
2729# If the user didn't specify a -i option, check to see if there is a
2730# webrev-info file in the workspace directory.
2731#
2732if [[ -z $iflag && -r "$CWS/webrev-info" ]]; then
2733	iflag=1
2734	INCLUDE_FILE="$CWS/webrev-info"
2735fi
2736
2737if [[ -n $iflag ]]; then
2738	if [[ ! -r $INCLUDE_FILE ]]; then
2739		print -u2 "include file '$INCLUDE_FILE' does not exist or is" \
2740		    "not readable."
2741		exit 1
2742	else
2743		#
2744		# $INCLUDE_FILE may be a relative path, and the script alters
2745		# PWD, so we just stash a copy in /tmp.
2746		#
2747		cp $INCLUDE_FILE /tmp/$$.include
2748	fi
2749fi
2750
2751# DO_EVERYTHING: break point
2752if [[ -n $Nflag ]]; then
2753	break
2754fi
2755
2756typeset -A itsinfo
2757typeset -r its_sed_script=/tmp/$$.its_sed
2758valid_prefixes=
2759if [[ -z $nflag ]]; then
2760	DEFREGFILE="$(/bin/dirname "$(whence $0)")/../etc/its.reg"
2761	if [[ -n $Iflag ]]; then
2762		REGFILE=$ITSREG
2763	elif [[ -r $HOME/.its.reg ]]; then
2764		REGFILE=$HOME/.its.reg
2765	else
2766		REGFILE=$DEFREGFILE
2767	fi
2768	if [[ ! -r $REGFILE ]]; then
2769		print "ERROR: Unable to read database registry file $REGFILE"
2770		exit 1
2771	elif [[ $REGFILE != $DEFREGFILE ]]; then
2772		print "   its.reg from: $REGFILE"
2773	fi
2774
2775	$SED -e '/^#/d' -e $'/^[ \t]*$/d' $REGFILE | while read LINE; do
2776
2777		name=${LINE%%=*}
2778		value="${LINE#*=}"
2779
2780		if [[ $name == PREFIX ]]; then
2781			p=${value}
2782			valid_prefixes="${p} ${valid_prefixes}"
2783		else
2784			itsinfo["${p}_${name}"]="${value}"
2785		fi
2786	done
2787
2788
2789	DEFCONFFILE="$(/bin/dirname "$(whence $0)")/../etc/its.conf"
2790	CONFFILES=$DEFCONFFILE
2791	if [[ -r $HOME/.its.conf ]]; then
2792		CONFFILES="${CONFFILES} $HOME/.its.conf"
2793	fi
2794	if [[ -n $Cflag ]]; then
2795		CONFFILES="${CONFFILES} ${ITSCONF}"
2796	fi
2797	its_domain=
2798	its_priority=
2799	for cf in ${CONFFILES}; do
2800		if [[ ! -r $cf ]]; then
2801			print "ERROR: Unable to read database configuration file $cf"
2802			exit 1
2803		elif [[ $cf != $DEFCONFFILE ]]; then
2804			print "       its.conf: reading $cf"
2805		fi
2806		$SED -e '/^#/d' -e $'/^[ \t]*$/d' $cf | while read LINE; do
2807		    eval "${LINE}"
2808		done
2809	done
2810
2811	#
2812	# If an information tracking system is explicitly identified by prefix,
2813	# we want to disregard the specified priorities and resolve it accordingly.
2814	#
2815	# To that end, we'll build a sed script to do each valid prefix in turn.
2816	#
2817	for p in ${valid_prefixes}; do
2818		#
2819		# When an informational URL was provided, translate it to a
2820		# hyperlink.  When omitted, simply use the prefix text.
2821		#
2822		if [[ -z ${itsinfo["${p}_INFO"]} ]]; then
2823			itsinfo["${p}_INFO"]=${p}
2824		else
2825			itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>"
2826		fi
2827
2828		#
2829		# Assume that, for this invocation of webrev, all references
2830		# to this information tracking system should resolve through
2831		# the same URL.
2832		#
2833		# If the caller specified -O, then always use EXTERNAL_URL.
2834		#
2835		# Otherwise, look in the list of domains for a matching
2836		# INTERNAL_URL.
2837		#
2838		[[ -z $Oflag ]] && for d in ${its_domain}; do
2839			if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then
2840				itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}"
2841				break
2842			fi
2843		done
2844		if [[ -z ${itsinfo["${p}_URL"]} ]]; then
2845			itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}"
2846		fi
2847
2848		#
2849		# Turn the destination URL into a hyperlink
2850		#
2851		itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>"
2852
2853		# The character class below contains a literal tab
2854		print "/^${p}[: \t]/ {
2855				s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2856				s;^${p};${itsinfo[${p}_INFO]};
2857			}" >> ${its_sed_script}
2858	done
2859
2860	#
2861	# The previous loop took care of explicit specification.  Now use
2862	# the configured priorities to attempt implicit translations.
2863	#
2864	for p in ${its_priority}; do
2865		print "/^${itsinfo[${p}_REGEX]}[ \t]/ {
2866				s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g
2867			}" >> ${its_sed_script}
2868	done
2869fi
2870
2871#
2872# Search for DO_EVERYTHING above for matching "for" statement
2873# and explanation of this terminator.
2874#
2875done
2876
2877#
2878# Output directory.
2879#
2880WDIR=${WDIR:-$CWS/webrev}
2881
2882#
2883# Name of the webrev, derived from the workspace name or output directory;
2884# in the future this could potentially be an option.
2885#
2886if [[ -n $oflag ]]; then
2887	WNAME=${WDIR##*/}
2888else
2889	WNAME=${CWS##*/}
2890fi
2891
2892# Make sure remote target is well formed for remote upload/delete.
2893if [[ -n $Dflag || -n $Uflag ]]; then
2894	#
2895	# If remote target is not specified, build it from scratch using
2896	# the default values.
2897	#
2898	if [[ -z $tflag ]]; then
2899		remote_target=${DEFAULT_REMOTE_HOST}:${WNAME}
2900	else
2901		#
2902		# Check upload target prefix first.
2903		#
2904		if [[ "${remote_target}" != ${rsync_prefix}* &&
2905		    "${remote_target}" != ${ssh_prefix}* ]]; then
2906			print "ERROR: invalid prefix of upload URI" \
2907			    "($remote_target)"
2908			exit 1
2909		fi
2910		#
2911		# If destination specification is not in the form of
2912		# host_spec:remote_dir then assume it is just remote hostname
2913		# and append a colon and destination directory formed from
2914		# local webrev directory name.
2915		#
2916		typeset target_no_prefix=${remote_target##*://}
2917		if [[ ${target_no_prefix} == *:* ]]; then
2918			if [[ "${remote_target}" == *: ]]; then
2919				remote_target=${remote_target}${WNAME}
2920			fi
2921		else
2922			if [[ ${target_no_prefix} == */* ]]; then
2923				print "ERROR: badly formed upload URI" \
2924					"($remote_target)"
2925				exit 1
2926			else
2927				remote_target=${remote_target}:${WNAME}
2928			fi
2929		fi
2930	fi
2931
2932	#
2933	# Strip trailing slash. Each upload method will deal with directory
2934	# specification separately.
2935	#
2936	remote_target=${remote_target%/}
2937fi
2938
2939#
2940# Option -D by itself (option -U not present) implies no webrev generation.
2941#
2942if [[ -z $Uflag && -n $Dflag ]]; then
2943	delete_webrev 1 1
2944	exit $?
2945fi
2946
2947#
2948# Do not generate the webrev, just upload it or delete it.
2949#
2950if [[ -n $nflag ]]; then
2951	if [[ -n $Dflag ]]; then
2952		delete_webrev 1 1
2953		(( $? == 0 )) || exit $?
2954	fi
2955	if [[ -n $Uflag ]]; then
2956		upload_webrev
2957		exit $?
2958	fi
2959fi
2960
2961if [ "${WDIR%%/*}" ]; then
2962	WDIR=$PWD/$WDIR
2963fi
2964
2965if [[ ! -d $WDIR ]]; then
2966	mkdir -p $WDIR
2967	(( $? != 0 )) && exit 1
2968fi
2969
2970#
2971# Summarize what we're going to do.
2972#
2973print "      Workspace: ${PRETTY_CWS:-$CWS}"
2974if [[ -n $parent_webrev ]]; then
2975	print "Compare against: webrev at $parent_webrev"
2976else
2977	print "Compare against: ${PRETTY_PWS:-$PWS}"
2978fi
2979
2980[[ -n $INCLUDE_FILE ]] && print "      Including: $INCLUDE_FILE"
2981print "      Output to: $WDIR"
2982
2983#
2984# Save the file list in the webrev dir
2985#
2986[[ ! $FLIST -ef $WDIR/file.list ]] && cp $FLIST $WDIR/file.list
2987
2988rm -f $WDIR/$WNAME.patch
2989
2990touch $WDIR/$WNAME.patch
2991
2992print "   Output Files:"
2993
2994#
2995# Clean up the file list: Remove comments, blank lines and env variables.
2996#
2997$SED -e "s/#.*$//" -e "/=/d" -e "/^[   ]*$/d" $FLIST > /tmp/$$.flist.clean
2998FLIST=/tmp/$$.flist.clean
2999
3000#
3001# First pass through the files: generate the per-file webrev HTML-files.
3002#
3003cat $FLIST | while read LINE
3004do
3005	set -A FIELDS $LINE
3006	P=${FIELDS[0]}
3007
3008	#
3009	# Normally, each line in the file list is just a pathname of a
3010	# file that has been modified or created in the child.  A file
3011	# that is renamed in the child workspace has two names on the
3012	# line: new name followed by the old name.
3013	#
3014	oldname=""
3015	oldpath=""
3016	rename=
3017	if (( ${#FIELDS[*]} == 2 )); then
3018		PP=${FIELDS[1]}			# old filename
3019		if [[ -f $PP ]]; then
3020			oldname=" (copied from $PP)"
3021		else
3022			oldname=" (renamed from $PP)"
3023		fi
3024		oldpath="$PP"
3025		rename=1
3026		PDIR=${PP%/*}
3027		if [[ $PDIR == $PP ]]; then
3028			PDIR="."   # File at root of workspace
3029		fi
3030
3031		PF=${PP##*/}
3032
3033		DIR=${P%/*}
3034		if [[ $DIR == $P ]]; then
3035			DIR="."   # File at root of workspace
3036		fi
3037
3038		F=${P##*/}
3039
3040	else
3041		DIR=${P%/*}
3042		if [[ "$DIR" == "$P" ]]; then
3043			DIR="."   # File at root of workspace
3044		fi
3045
3046		F=${P##*/}
3047
3048		PP=$P
3049		PDIR=$DIR
3050		PF=$F
3051	fi
3052
3053	COMM=$(getcomments html $P $PP)
3054
3055	print "\t$P$oldname\n\t\t\c"
3056
3057	# Make the webrev mirror directory if necessary
3058	mkdir -p $WDIR/$DIR
3059
3060	#
3061	# We stash old and new files into parallel directories in $WDIR
3062	# and do our diffs there.  This makes it possible to generate
3063	# clean looking diffs which don't have absolute paths present.
3064	#
3065
3066	build_old_new "$WDIR" "$PWS" "$PDIR" "$PF" "$CWS" "$DIR" "$F" || \
3067	    continue
3068
3069	#
3070	# Keep the old PWD around, so we can safely switch back after
3071	# diff generation, such that build_old_new runs in a
3072	# consistent environment.
3073	#
3074	OWD=$PWD
3075	cd $WDIR/raw_files
3076
3077	#
3078	# The "git apply" command does not tolerate the spurious
3079	# "./" that we otherwise insert; be careful not to include
3080	# it in the paths that we pass to diff(1).
3081	#
3082	if [[ $PDIR == "." ]]; then
3083		ofile=old/$PF
3084	else
3085		ofile=old/$PDIR/$PF
3086	fi
3087	if [[ $DIR == "." ]]; then
3088		nfile=new/$F
3089	else
3090		nfile=new/$DIR/$F
3091	fi
3092
3093	mv_but_nodiff=
3094	cmp $ofile $nfile > /dev/null 2>&1
3095	if [[ $? == 0 && $rename == 1 ]]; then
3096		mv_but_nodiff=1
3097	fi
3098
3099	#
3100	# If we have old and new versions of the file then run the appropriate
3101	# diffs.  This is complicated by a couple of factors:
3102	#
3103	#	- renames must be handled specially: we emit a 'remove'
3104	#	  diff and an 'add' diff
3105	#	- new files and deleted files must be handled specially
3106	#	- GNU patch doesn't interpret the output of illumos diff
3107	#	  properly when it comes to adds and deletes.  We need to
3108	#	  do some "cleansing" transformations:
3109	#	    [to add a file] @@ -1,0 +X,Y @@  -->  @@ -0,0 +X,Y @@
3110	#	    [to del a file] @@ -X,Y +1,0 @@  -->  @@ -X,Y +0,0 @@
3111	#
3112	cleanse_rmfile="$SED 's/^\(@@ [0-9+,-]*\) [0-9+,-]* @@$/\1 +0,0 @@/'"
3113	cleanse_newfile="$SED 's/^@@ [0-9+,-]* \([0-9+,-]* @@\)$/@@ -0,0 \1/'"
3114
3115	rm -f $WDIR/$DIR/$F.patch
3116	if [[ -z $rename ]]; then
3117		if [ ! -f "$ofile" ]; then
3118			diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3119			    > $WDIR/$DIR/$F.patch
3120		elif [ ! -f "$nfile" ]; then
3121			diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3122			    > $WDIR/$DIR/$F.patch
3123		else
3124			diff -u $ofile $nfile > $WDIR/$DIR/$F.patch
3125		fi
3126	else
3127		diff -u $ofile /dev/null | sh -c "$cleanse_rmfile" \
3128		    > $WDIR/$DIR/$F.patch
3129
3130		diff -u /dev/null $nfile | sh -c "$cleanse_newfile" \
3131		    >> $WDIR/$DIR/$F.patch
3132	fi
3133
3134	#
3135	# Tack the patch we just made onto the accumulated patch for the
3136	# whole wad.
3137	#
3138	cat $WDIR/$DIR/$F.patch >> $WDIR/$WNAME.patch
3139	print " patch\c"
3140
3141	if [[ -f $ofile && -f $nfile && -z $mv_but_nodiff ]]; then
3142		${CDIFFCMD:-diff -bt -C 5} $ofile $nfile > $WDIR/$DIR/$F.cdiff
3143		diff_to_html $F $DIR/$F "C" "$COMM" < $WDIR/$DIR/$F.cdiff \
3144		    > $WDIR/$DIR/$F.cdiff.html
3145		print " cdiffs\c"
3146
3147		${UDIFFCMD:-diff -bt -U 5} $ofile $nfile > $WDIR/$DIR/$F.udiff
3148		diff_to_html $F $DIR/$F "U" "$COMM" < $WDIR/$DIR/$F.udiff \
3149		    > $WDIR/$DIR/$F.udiff.html
3150		print " udiffs\c"
3151
3152		if [[ -x $WDIFF ]]; then
3153			$WDIFF -c "$COMM" \
3154			    -t "$WNAME Wdiff $DIR/$F" $ofile $nfile > \
3155			    $WDIR/$DIR/$F.wdiff.html 2>/dev/null
3156			if (( $? == 0 )); then
3157				print " wdiffs\c"
3158			else
3159				print " wdiffs[fail]\c"
3160			fi
3161		fi
3162
3163		sdiff_to_html $ofile $nfile $F $DIR "$COMM" \
3164		    > $WDIR/$DIR/$F.sdiff.html
3165		print " sdiffs\c"
3166		print " frames\c"
3167
3168		rm -f $WDIR/$DIR/$F.cdiff $WDIR/$DIR/$F.udiff
3169		difflines $ofile $nfile > $WDIR/$DIR/$F.count
3170	elif [[ -f $ofile && -f $nfile && -n $mv_but_nodiff ]]; then
3171		# renamed file: may also have differences
3172		difflines $ofile $nfile > $WDIR/$DIR/$F.count
3173	elif [[ -f $nfile ]]; then
3174		# new file: count added lines
3175		difflines /dev/null $nfile > $WDIR/$DIR/$F.count
3176	elif [[ -f $ofile ]]; then
3177		# old file: count deleted lines
3178		difflines $ofile /dev/null > $WDIR/$DIR/$F.count
3179	fi
3180
3181	#
3182	# Check if it's man page, and create plain text, html and raw (ascii)
3183	# output for the new version, as well as diffs against old version.
3184	#
3185	if [[ -f "$nfile" && "$nfile" == *.+([0-9])*([a-zA-Z]) && \
3186	    -x $MANDOC && -x $COL ]]; then
3187		$MANDOC -Tascii $nfile | $COL -b > $nfile.man.txt
3188		source_to_html txt < $nfile.man.txt > $nfile.man.txt.html
3189		print " man-txt\c"
3190		print "$MANCSS" > $WDIR/raw_files/new/$DIR/man.css
3191		$MANDOC -Thtml -Ostyle=man.css $nfile > $nfile.man.html
3192		print " man-html\c"
3193		$MANDOC -Tascii $nfile > $nfile.man.raw
3194		print " man-raw\c"
3195		if [[ -f "$ofile" && -z $mv_but_nodiff ]]; then
3196			$MANDOC -Tascii $ofile | $COL -b > $ofile.man.txt
3197			${CDIFFCMD:-diff -bt -C 5} $ofile.man.txt \
3198			    $nfile.man.txt > $WDIR/$DIR/$F.man.cdiff
3199			diff_to_html $F $DIR/$F "C" "$COMM" < \
3200			    $WDIR/$DIR/$F.man.cdiff > \
3201			    $WDIR/$DIR/$F.man.cdiff.html
3202			print " man-cdiffs\c"
3203			${UDIFFCMD:-diff -bt -U 5} $ofile.man.txt \
3204			    $nfile.man.txt > $WDIR/$DIR/$F.man.udiff
3205			diff_to_html $F $DIR/$F "U" "$COMM" < \
3206			    $WDIR/$DIR/$F.man.udiff > \
3207			    $WDIR/$DIR/$F.man.udiff.html
3208			print " man-udiffs\c"
3209			if [[ -x $WDIFF ]]; then
3210				$WDIFF -c "$COMM" -t "$WNAME Wdiff $DIR/$F" \
3211				    $ofile.man.txt $nfile.man.txt > \
3212				    $WDIR/$DIR/$F.man.wdiff.html 2>/dev/null
3213				if (( $? == 0 )); then
3214					print " man-wdiffs\c"
3215				else
3216					print " man-wdiffs[fail]\c"
3217				fi
3218			fi
3219			sdiff_to_html $ofile.man.txt $nfile.man.txt $F.man $DIR \
3220			    "$COMM" > $WDIR/$DIR/$F.man.sdiff.html
3221			print " man-sdiffs\c"
3222			print " man-frames\c"
3223		fi
3224		rm -f $ofile.man.txt $nfile.man.txt
3225		rm -f $WDIR/$DIR/$F.man.cdiff $WDIR/$DIR/$F.man.udiff
3226	fi
3227
3228	if [[ -f $ofile ]]; then
3229		source_to_html Old $PP < $ofile > $WDIR/$DIR/$F-.html
3230		print " old\c"
3231	fi
3232
3233	if [[ -f $nfile ]]; then
3234		source_to_html New $P < $nfile > $WDIR/$DIR/$F.html
3235		print " new\c"
3236	fi
3237
3238	cd $OWD
3239
3240	print
3241done
3242
3243frame_nav_js > $WDIR/ancnav.js
3244frame_navigation > $WDIR/ancnav.html
3245
3246# If we're in OpenSolaris mode and there's a closed dir under $WDIR,
3247# delete it - prevent accidental publishing of closed source
3248
3249if [[ -n "$Oflag" ]]; then
3250	$FIND $WDIR -type d -name closed -exec /bin/rm -rf {} \;
3251fi
3252
3253# Now build the index.html file that contains
3254# links to the source files and their diffs.
3255
3256cd $CWS
3257
3258# Save total changed lines for Code Inspection.
3259print "$TOTL" > $WDIR/TotalChangedLines
3260
3261print "     index.html: \c"
3262INDEXFILE=$WDIR/index.html
3263exec 3<&1			# duplicate stdout to FD3.
3264exec 1<&-			# Close stdout.
3265exec > $INDEXFILE		# Open stdout to index file.
3266
3267print "$HTML<head>$STDHEAD"
3268print "<title>$WNAME</title>"
3269print "</head>"
3270print "<body id=\"SUNWwebrev\">"
3271print "<div class=\"summary\">"
3272print "<h2>Code Review for $WNAME</h2>"
3273
3274print "<table>"
3275
3276#
3277# Get the preparer's name:
3278#
3279# If the SCM detected is Git, and the configuration property user.name is
3280# available, use that, but be careful to properly escape angle brackets (HTML
3281# syntax characters) in the email address.
3282#
3283# Otherwise, use the current userid in the form "John Doe (jdoe)", but
3284# to maintain compatibility with passwd(5), we must support '&' substitutions.
3285#
3286preparer=
3287if [[ "$SCM_MODE" == git ]]; then
3288	preparer=$(git config user.name 2>/dev/null)
3289	if [[ -n "$preparer" ]]; then
3290		preparer="$(echo "$preparer" | html_quote)"
3291	fi
3292fi
3293if [[ -z "$preparer" ]]; then
3294	preparer=$(
3295	    $PERL -e '
3296	        ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<);
3297	        if ($login) {
3298	            $gcos =~ s/\&/ucfirst($login)/e;
3299	            printf "%s (%s)\n", $gcos, $login;
3300	        } else {
3301	            printf "(unknown)\n";
3302	        }
3303	')
3304fi
3305
3306PREPDATE=$(LC_ALL=C /usr/bin/date +%Y-%b-%d\ %R\ %z\ %Z)
3307print "<tr><th>Prepared by:</th><td>$preparer on $PREPDATE</td></tr>"
3308print "<tr><th>Workspace:</th><td>${PRETTY_CWS:-$CWS}"
3309print "</td></tr>"
3310print "<tr><th>Compare against:</th><td>"
3311if [[ -n $parent_webrev ]]; then
3312	print "webrev at $parent_webrev"
3313else
3314	print "${PRETTY_PWS:-$PWS}"
3315fi
3316print "</td></tr>"
3317print "<tr><th>Summary of changes:</th><td>"
3318printCI $TOTL $TINS $TDEL $TMOD $TUNC
3319print "</td></tr>"
3320
3321if [[ -f $WDIR/$WNAME.patch ]]; then
3322	wpatch_url="$(print $WNAME.patch | url_encode)"
3323	print "<tr><th>Patch of changes:</th><td>"
3324	print "<a href=\"$wpatch_url\">$WNAME.patch</a></td></tr>"
3325fi
3326
3327if [[ -n "$iflag" ]]; then
3328	print "<tr><th>Author comments:</th><td><div>"
3329	cat /tmp/$$.include
3330	print "</div></td></tr>"
3331fi
3332print "</table>"
3333print "</div>"
3334
3335#
3336# Second pass through the files: generate the rest of the index file
3337#
3338cat $FLIST | while read LINE
3339do
3340	set -A FIELDS $LINE
3341	P=${FIELDS[0]}
3342
3343	if (( ${#FIELDS[*]} == 2 )); then
3344		PP=${FIELDS[1]}
3345		oldname="$PP"
3346	else
3347		PP=$P
3348		oldname=""
3349	fi
3350
3351	mv_but_nodiff=
3352	cmp $WDIR/raw_files/old/$PP $WDIR/raw_files/new/$P > /dev/null 2>&1
3353	if (( $? == 0 )) && [[ -n "$oldname" ]]; then
3354		mv_but_nodiff=1
3355	fi
3356
3357	DIR=${P%/*}
3358	if [[ $DIR == $P ]]; then
3359		DIR="."   # File at root of workspace
3360	fi
3361
3362	# Avoid processing the same file twice.
3363	# It's possible for renamed files to
3364	# appear twice in the file list
3365
3366	F=$WDIR/$P
3367
3368	print "<p>"
3369
3370	# If there's a diffs file, make diffs links
3371
3372	if [[ -f $F.cdiff.html ]]; then
3373		cdiff_url="$(print $P.cdiff.html | url_encode)"
3374		udiff_url="$(print $P.udiff.html | url_encode)"
3375		sdiff_url="$(print $P.sdiff.html | url_encode)"
3376		frames_url="$(print $P.frames.html | url_encode)"
3377		print "<a href=\"$cdiff_url\">Cdiffs</a>"
3378		print "<a href=\"$udiff_url\">Udiffs</a>"
3379		if [[ -f $F.wdiff.html && -x $WDIFF ]]; then
3380			wdiff_url="$(print $P.wdiff.html | url_encode)"
3381			print "<a href=\"$wdiff_url\">Wdiffs</a>"
3382		fi
3383		print "<a href=\"$sdiff_url\">Sdiffs</a>"
3384		print "<a href=\"$frames_url\">Frames</a>"
3385	else
3386		print " ------ ------"
3387		if [[ -x $WDIFF ]]; then
3388			print " ------"
3389		fi
3390		print " ------ ------"
3391	fi
3392
3393	# If there's an old file, make the link
3394
3395	if [[ -f $F-.html ]]; then
3396		oldfile_url="$(print $P-.html | url_encode)"
3397		print "<a href=\"$oldfile_url\">Old</a>"
3398	else
3399		print " ---"
3400	fi
3401
3402	# If there's an new file, make the link
3403
3404	if [[ -f $F.html ]]; then
3405		newfile_url="$(print $P.html | url_encode)"
3406		print "<a href=\"$newfile_url\">New</a>"
3407	else
3408		print " ---"
3409	fi
3410
3411	if [[ -f $F.patch ]]; then
3412		patch_url="$(print $P.patch | url_encode)"
3413		print "<a href=\"$patch_url\">Patch</a>"
3414	else
3415		print " -----"
3416	fi
3417
3418	if [[ -f $WDIR/raw_files/new/$P ]]; then
3419		rawfiles_url="$(print raw_files/new/$P | url_encode)"
3420		print "<a href=\"$rawfiles_url\">Raw</a>"
3421	else
3422		print " ---"
3423	fi
3424
3425	print "<b>$P</b>"
3426
3427	# For renamed files, clearly state whether or not they are modified
3428	if [[ -f "$oldname" ]]; then
3429		if [[ -n "$mv_but_nodiff" ]]; then
3430			print "<i>(copied from $oldname)</i>"
3431		else
3432			print "<i>(copied and modified from $oldname)</i>"
3433		fi
3434	elif [[ -n "$oldname" ]]; then
3435		if [[ -n "$mv_but_nodiff" ]]; then
3436			print "<i>(renamed from $oldname)</i>"
3437		else
3438			print "<i>(renamed and modified from $oldname)</i>"
3439		fi
3440	fi
3441
3442	# If there's an old file, but no new file, the file was deleted
3443	if [[ -f $F-.html && ! -f $F.html ]]; then
3444		print " <i>(deleted)</i>"
3445	fi
3446
3447	# Check for usr/closed and deleted_files/usr/closed
3448	if [ ! -z "$Oflag" ]; then
3449		if [[ $P == usr/closed/* || \
3450		    $P == deleted_files/usr/closed/* ]]; then
3451			print "&nbsp;&nbsp;<i>Closed source: omitted from" \
3452			    "this review</i>"
3453		fi
3454	fi
3455
3456	manpage=
3457	if [[ -f $F.man.cdiff.html || \
3458	    -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3459		manpage=1
3460		print "<br/>man:"
3461	fi
3462
3463	if [[ -f $F.man.cdiff.html ]]; then
3464		mancdiff_url="$(print $P.man.cdiff.html | url_encode)"
3465		manudiff_url="$(print $P.man.udiff.html | url_encode)"
3466		mansdiff_url="$(print $P.man.sdiff.html | url_encode)"
3467		manframes_url="$(print $P.man.frames.html | url_encode)"
3468		print "<a href=\"$mancdiff_url\">Cdiffs</a>"
3469		print "<a href=\"$manudiff_url\">Udiffs</a>"
3470		if [[ -f $F.man.wdiff.html && -x $WDIFF ]]; then
3471			manwdiff_url="$(print $P.man.wdiff.html | url_encode)"
3472			print "<a href=\"$manwdiff_url\">Wdiffs</a>"
3473		fi
3474		print "<a href=\"$mansdiff_url\">Sdiffs</a>"
3475		print "<a href=\"$manframes_url\">Frames</a>"
3476	elif [[ -n $manpage ]]; then
3477		print " ------ ------"
3478		if [[ -x $WDIFF ]]; then
3479			print " ------"
3480		fi
3481		print " ------ ------"
3482	fi
3483
3484	if [[ -f $WDIR/raw_files/new/$P.man.txt.html ]]; then
3485		mantxt_url="$(print raw_files/new/$P.man.txt.html | url_encode)"
3486		print "<a href=\"$mantxt_url\">TXT</a>"
3487		manhtml_url="$(print raw_files/new/$P.man.html | url_encode)"
3488		print "<a href=\"$manhtml_url\">HTML</a>"
3489		manraw_url="$(print raw_files/new/$P.man.raw | url_encode)"
3490		print "<a href=\"$manraw_url\">Raw</a>"
3491	elif [[ -n $manpage ]]; then
3492		print " --- ---- ---"
3493	fi
3494
3495	print "</p>"
3496
3497	# Insert delta comments
3498	print "<blockquote><pre>"
3499	getcomments html $P $PP
3500	print "</pre>"
3501
3502	# Add additional comments comment
3503	print "<!-- Add comments to explain changes in $P here -->"
3504
3505	# Add count of changes.
3506	if [[ -f $F.count ]]; then
3507	    cat $F.count
3508	    rm $F.count
3509	fi
3510
3511	if [[ $SCM_MODE == "unknown" ]]; then
3512		# Include warnings for important file mode situations:
3513		# 1) New executable files
3514		# 2) Permission changes of any kind
3515		# 3) Existing executable files
3516		old_mode=
3517		if [[ -f $WDIR/raw_files/old/$PP ]]; then
3518			old_mode=$(get_file_mode $WDIR/raw_files/old/$PP)
3519		fi
3520
3521		new_mode=
3522		if [[ -f $WDIR/raw_files/new/$P ]]; then
3523			new_mode=$(get_file_mode $WDIR/raw_files/new/$P)
3524		fi
3525
3526		if [[ -z "$old_mode" && "$new_mode" == *[1357]* ]]; then
3527			print "<span class=\"chmod\">"
3528			print "<p>new executable file: mode $new_mode</p>"
3529			print "</span>"
3530		elif [[ -n "$old_mode" && -n "$new_mode" &&
3531		    "$old_mode" != "$new_mode" ]]; then
3532			print "<span class=\"chmod\">"
3533			print "<p>mode change: $old_mode to $new_mode</p>"
3534			print "</span>"
3535		elif [[ "$new_mode" == *[1357]* ]]; then
3536			print "<span class=\"chmod\">"
3537			print "<p>executable file: mode $new_mode</p>"
3538			print "</span>"
3539		fi
3540	fi
3541
3542	print "</blockquote>"
3543done
3544
3545print
3546print
3547print "<hr></hr>"
3548print "<p style=\"font-size: small\">"
3549print "This code review page was prepared using <b>$0</b>."
3550print "Webrev is maintained by the <a href=\"http://www.illumos.org\">"
3551print "illumos</a> project.  The latest version may be obtained"
3552print "<a href=\"http://src.illumos.org/source/xref/illumos-gate/usr/src/tools/scripts/webrev.sh\">here</a>.</p>"
3553print "</body>"
3554print "</html>"
3555
3556exec 1<&-			# Close FD 1.
3557exec 1<&3			# dup FD 3 to restore stdout.
3558exec 3<&-			# close FD 3.
3559
3560print "Done."
3561
3562#
3563# If remote deletion was specified and fails do not continue.
3564#
3565if [[ -n $Dflag ]]; then
3566	delete_webrev 1 1
3567	(( $? == 0 )) || exit $?
3568fi
3569
3570if [[ -n $Uflag ]]; then
3571	upload_webrev
3572	exit $?
3573fi
3574