Home | History | Annotate | Line # | Download | only in tz
tzdata2netbsd revision 1.13
      1 # $NetBSD: tzdata2netbsd,v 1.13 2022/08/16 13:19:41 kre Exp $
      2 
      3 # For use by NetBSD developers when updating to new versions of tzdata.
      4 #
      5 # 0. Be in an up-to-date checkout of src/external/public-domain/tz
      6 #    from NetBSD-current.
      7 # 1. Make sure that you have Paul Eggert's 4K RSA public key in your
      8 #    keyring (62AA7E34, eggert (a] cs.ucla.edu)  It is not required that it be trusted.
      9 # 2. Run this script.  You will be prompted for confirmation before
     10 #    anything major (such as a cvs operation).  The tz versions can be
     11 #    specified as args (new version first, and the previous second) if
     12 #    needed to override the calculated values
     13 # 3. If something fails, abort the script and fix it.
     14 # 4. Re-run this script until you are happy.  It's designed to
     15 #    be re-run over and over, and later runs will try not to
     16 #    redo non-trivial work done by earlier runs.
     17 #
     18 
     19 VERS_PATTERN='2[0-9][0-9][0-9][a-z]'
     20 # This needs to be updated twice every millennium to allow for the
     21 # new millenium's years.
     22 # First in the late xx90's sometime, allow the new one by changing the leading
     23 # digit from a specific value to the class containing the current and
     24 # following values (eg: in 2098 or so, change '2' to be '[23]').
     25 # Tnen in the following early xx00's sometime, delete the class, and insert
     26 # leave only the current value as valid (eg: in 3001 or 3002,
     27 # change '[23]' to be just '3'
     28 # Doing it this way helps guard against invalid specifications.
     29 # We could automate this, but it is (IMO) not worth the cost, to avoid a
     30 # twice a millenium edit requirement.
     31 # A more significant (and harder) change will be needed in the late 9990's
     32 # If this script lasts until then, send me a postcard, I'll be waiting for it!
     33 # Things get easier again after that until the late 99990's (etc.)
     34 
     35 # Note the pattern is used on both the old and new version specifiers,
     36 # so it must be able to cope with the shift from one form (eg 2999g)
     37 # to the new one (eg: 3000a) without failing (or the code that uses it
     38 # below needs to be updated).
     39 
     40 # Also note that the option of having a second alpha (1997aa or something)
     41 # to handle years with much activity is handled below, the pattern does not
     42 # need to match those.
     43 # If that convention changes (as of date of writing, it has never been
     44 # exercised) then code changes below will be required.
     45 
     46 DIST_HOST=ftp.iana.org
     47 DIST_PATH=tz
     48 DIST_FILES=releases
     49 
     50 EDITOR=${EDITOR:-vi}
     51 WORK_PFX=$(pwd)/update-work || fail "Cannot obtain PWD"
     52 UPDATE_FROM=${WORK_PFX}/updating.from.version
     53 
     54 usage()
     55 {
     56 	printf >&2 '%s\n'						\
     57 		"Usage: $0 [new-version-id [old-version-id]]"		\
     58 		"     where a version-id is of the form YYYYx (eg: 2018c)" \
     59 		"     or '' for new-version-id (to specify only the old)"
     60 	exit 2
     61 }
     62 
     63 fail()
     64 {
     65 	local IFS=' '
     66 
     67 	printf >&2 '%s\n'	"Error detected:" "   $*" "Aborting."
     68 	exit 1
     69 }
     70 
     71 valid_vers()
     72 {
     73 	case "$2" in
     74 	${VERS_PATTERN} | ${VERS_PATTERN}[a-z] )
     75 		;;
     76 	*)	printf >&2 '%s: %s\n' \
     77 		    "Bad form for $1 version specifier '$2'" \
     78 		    "should (usually) be 'YYYYx'"
     79 		return 1
     80 		;;
     81 	esac
     82 	return 0
     83 }
     84 
     85 get_curvers()
     86 {
     87 	local LF=''
     88 	local LIST=iana-listing
     89 	local SED_SCRIPT='
     90 		/tzdata-latest.*-> /{
     91 			s/^.*-> //
     92 			s/\..*$//
     93 			s;^releases/tzdata;;p
     94 			q
     95 		}
     96 		d'
     97 
     98 	test -d "${WORK_PFX}" &&
     99 		test -s "${WORK_PFX}/${LIST}" &&
    100 		test "${WORK_PFX}/${LIST}" -nt dist/CVS &&
    101 		LF=$(find "${WORK_PFX}" -name "${LIST}" -mtime -1 -print) &&
    102 		test -n "${LF}" &&
    103 		NEWVER=$(sed -n < "${LF}" "${SED_SCRIPT}") &&
    104 		valid_vers new "${NEWVER}"				||
    105 
    106 	ftp >/dev/null 2>&1 -ia "${DIST_HOST}" <<- EOF &&
    107 					dir ${DIST_PATH} ${WORK_PFX}/${LIST}
    108 					quit
    109 					EOF
    110 		test -s "${WORK_PFX}/${LIST}" &&
    111 		NEWVER=$(sed -n < "${WORK_PFX}/${LIST}" "${SED_SCRIPT}") &&
    112 		valid_vers new "${NEWVER}"				||
    113 
    114 	{
    115 		rm -f "${WORK_PFX}/${LIST}"
    116 		fail "Cannot fetch current tzdata version from ${DIST_HOST}"
    117 	}
    118 
    119 	printf '%s\n' "Updating from ${OLDVER} to ${NEWVER}"
    120 }
    121 
    122 argparse()
    123 {
    124 	local OVF
    125 
    126 	if OVF=$(find "${WORK_PFX}" -name "${UPDATE_FROM##*/}" -mtime +2 -print)
    127 	then
    128 		# delete anything old
    129 		test -n "${OVF}" && rm -f "${OVF}"
    130 	fi
    131 
    132 	case "$#" in
    133 	0|1)
    134 		# once we have obtained OLDVER once, never guess it again.
    135 		test -f "${UPDATE_FROM}" && OLDVER=$(cat "${UPDATE_FROM}") ||
    136 
    137 		OLDVER=$(cat dist/version) || {
    138 			printf >&2 '%s\n'  \
    139 			    "Cannot determine current installed version"  \
    140 			    "Specify it on the command line."  \
    141 			    ""
    142 			usage
    143 		}
    144 
    145 		valid_vers old "${OLDVER}" ||
    146 			fail "Calculated bad OLDVER, give as 2nd arg"
    147 		;;
    148 
    149 	2)	valid_vers old "$2" && OLDVER="$2" || usage
    150 		;;
    151 
    152 	*)	usage
    153 		;;
    154 	esac
    155 
    156 	case "$#:$1" in
    157 	0: | 1: | 2: )
    158 		;;
    159 	1:?*|2:?*)	
    160 		valid_vers new "$1" && NEWVER="$1" || usage
    161 		;;
    162 	*)	usage
    163 		;;
    164 	esac
    165 
    166 	test -z "${NEWVER}" && get_curvers
    167 
    168 	test "${NEWVER}" = "${OLDVER}" && {
    169 		printf '%s\n' \
    170 		     "New and old versions both ${NEWVER}: nothing to do"
    171 		exit 0
    172 
    173 	}
    174 
    175 	printf '%s\n' "${OLDVER}" > "${UPDATE_FROM}" ||
    176 	    fail "Unable to preserve old version ${OLDVER} in ${UPDATE_FROM}"
    177 
    178 	test "${#NEWVER}" -gt "${#OLDVER}" ||
    179 		test "${NEWVER}" '>' "${OLDVER}" ||
    180 		{
    181 			local reply
    182 
    183 			printf '%s\n' \
    184 			    "Update would revert ${OLDVER} to ${NEWVER}"
    185 			read -p "Is reversion intended? " reply
    186 			case "${reply}" in
    187 			[Yy]*)	;;
    188 			*)	printf '%s\n' OK. Aborted.
    189 				rm -f "${UPDATE_FROM}"
    190 				exit 1
    191 				;;
    192 			esac
    193 		}
    194 
    195 	return 0
    196 }
    197 
    198 setup_versions()
    199 {
    200 	# Uppercase variants of OLDVER and NEWVER
    201 	OLDVER_UC="$( echo "${OLDVER}" | tr '[a-z]' '[A-Z]' )"
    202 	NEWVER_UC="$( echo "${NEWVER}" | tr '[a-z]' '[A-Z]' )"
    203 
    204 	# Tags for use with version control systems
    205 	CVSOLDTAG="TZDATA${OLDVER_UC}"
    206 	CVSNEWTAG="TZDATA${NEWVER_UC}"
    207 	CVSBRANCHTAG="TZDATA"
    208 	GITHUBTAG="${NEWVER}"
    209 
    210 	# URLs for fetching distribution files, etc.
    211 	DISTURL="ftp://${DIST_HOST}/${DIST_PATH}/${DIST_FILES}"
    212 	DISTURL="${DISTURL}/tzdata${NEWVER}.tar.gz"
    213 	SIGURL="${DISTURL}.asc"
    214 	NEWSURL="https://github.com/eggert/tz/raw/${GITHUBTAG}/NEWS"
    215 
    216 	# Directories
    217 	REPODIR="src/external/public-domain/tz/dist"
    218 				# relative to the NetBSD CVS repo
    219 	TZDISTDIR="$(pwd)/dist" # should be .../external/public-domain/tz/dist
    220 	WORKDIR="${WORK_PFX}/${NEWVER}"
    221 	EXTRACTDIR="${WORKDIR}/extract"
    222 
    223 	# Files in the work directory
    224 	DISTFILE="${WORKDIR}/${DISTURL##*/}"
    225 	SIGFILE="${DISTFILE}.asc"
    226 	PGPVERIFYLOG="${WORKDIR}/pgpverify.log"
    227 	NEWSFILE="${WORKDIR}/NEWS"
    228 	NEWSTRIMFILE="${WORKDIR}/NEWS.trimmed"
    229 	IMPORTMSGFILE="${WORKDIR}/import.msg"
    230 	IMPORTDONEFILE="${WORKDIR}/import.done"
    231 	MERGSMSGFILE="${WORKDIR}/merge.msg"
    232 	MERGEDONEFILE="${WORKDIR}/merge.done"
    233 	COMMITMERGEDONEFILE="${WORKDIR}/commitmerge.done"
    234 
    235 	printf '%s\n' "${CVSOLDTAG}"  > "${WORK_PFX}/updating_from"
    236 }
    237 
    238 DOIT()
    239 {
    240 	local really_do_it=false
    241 	local reply
    242 
    243 	echo "In directory $(pwd)"
    244 	echo "ABOUT TO DO:" "$(shell_quote "$@")"
    245 	read -p "Really do it? [yes/no/quit] " reply
    246 	case "${reply}" in
    247 	[yY]*)	really_do_it=true ;;
    248 	[nN]*)	really_do_it=false ;;
    249 	[qQ]*)
    250 		echo "Aborting"
    251 		return 1
    252 		;;
    253 	*)	echo "Huh?"
    254 		return 1
    255 		;;
    256 	esac
    257 	if $really_do_it; then
    258 		echo "REALLY DOING IT NOW..."
    259 		"$@"
    260 	else
    261 		echo "NOT REALLY DOING THE ABOVE COMMAND"
    262 	fi
    263 }
    264 
    265 # Quote args to make them safe in the shell.
    266 # Usage: quotedlist="$(shell_quote args...)"
    267 #
    268 # After building up a quoted list, use it by evaling it inside
    269 # double quotes, like this:
    270 #    eval "set -- $quotedlist"
    271 # or like this:
    272 #    eval "\$command $quotedlist \$filename"
    273 #
    274 shell_quote()
    275 (
    276 	local result=''
    277 	local arg qarg
    278 	LC_COLLATE=C ; export LC_COLLATE # so [a-zA-Z0-9] works in ASCII
    279 	for arg in "$@" ; do
    280 		case "${arg}" in
    281 		'')
    282 			qarg="''"
    283 			;;
    284 		*[!-./a-zA-Z0-9]*)
    285 			# Convert each embedded ' to '\'',
    286 			# then insert ' at the beginning of the first line,
    287 			# and append ' at the end of the last line.
    288 			# Finally, elide unnecessary '' pairs at the
    289 			# beginning and end of the result and as part of
    290 			# '\'''\'' sequences that result from multiple
    291 			# adjacent quotes in the input.
    292 			qarg="$(printf '%s\n' "$arg" | \
    293 			    ${SED:-sed} -e "s/'/'\\\\''/g" \
    294 				-e "1s/^/'/" -e "\$s/\$/'/" \
    295 				-e "1s/^''//" -e "\$s/''\$//" \
    296 				-e "s/'''/'/g"
    297 				)"
    298 			;;
    299 		*)
    300 			# Arg is not the empty string, and does not contain
    301 			# any unsafe characters.  Leave it unchanged for
    302 			# readability.
    303 			qarg="${arg}"
    304 			;;
    305 		esac
    306 		result="${result}${result:+ }${qarg}"
    307 	done
    308 	printf '%s\n' "$result"
    309 )
    310 
    311 validate_pwd()
    312 {
    313 	local P="$(pwd)" || return 1
    314 
    315 	test -d "${P}" &&
    316 	    test -d "${P}/CVS" &&
    317 	    test -d "${P}/dist" &&
    318 	    test -f "${P}/dist/zone.tab" &&
    319 	    test -f "${P}/tzdata2netbsd" || {
    320 		printf >&2 '%s\n' "Please change to the correct directory"
    321 		return 1
    322 	}
    323 	
    324 	test -f "${P}/CVS/Tag" && {
    325 
    326 		# Here (for local use only) if needed for private branch work
    327 		# insert tests for the value of $(cat "${P}/CVS/Tag") and
    328 		# allow your private branch tag to pass. Eg:
    329 
    330 		#	case "$(cat "${P}/CVS/Tag")" in
    331 		#	my-branch-name)	return 0;;
    332 		#	esac
    333 
    334 		# Do not commit a version of this script modified that way,
    335 		# (not even on the private branch) - keep it as a local
    336 		# modified file.  (This script will not commit it.)
    337 
    338 		printf >&2 '%s\n' \
    339 		    "This script should be run in a checkout of HEAD only"
    340 		return 1
    341 	}
    342 
    343 	return 0
    344 }
    345 
    346 findcvsroot()
    347 {
    348 	[ -n "${CVSROOT}" ] && return 0
    349 	CVSROOT="$( cat ./CVS/Root )"
    350 	[ -n "${CVSROOT}" ] && return 0
    351 	echo >&2 "Failed to set CVSROOT value"
    352 	return 1
    353 }
    354 
    355 mkworkpfx()
    356 {
    357 	mkdir -p "${WORK_PFX}" || fail "Unable to make missing ${WORK_PFX}"
    358 }
    359 mkworkdir()
    360 {
    361 	mkdir -p "${WORKDIR}" || fail "Unable to make missing ${WORKDIR}"
    362 }
    363 
    364 fetch()
    365 {
    366 	[ -f "${DISTFILE}" ] || ftp -o "${DISTFILE}" "${DISTURL}" ||
    367 		fail "fetch of ${DISTFILE} failed"
    368 	[ -f "${SIGFILE}" ] || ftp -o "${SIGFILE}" "${SIGURL}" ||
    369 		fail "fetch of ${SIGFILE} failed"
    370 	[ -f "${NEWSFILE}" ] || ftp -o "${NEWSFILE}" "${NEWSURL}" ||
    371 		fail "fetch of ${NEWSFILE} failed"
    372 }
    373 
    374 checksig()
    375 {
    376 	{ gpg --verify "${SIGFILE}" "${DISTFILE}"
    377 	  echo gpg exit status $?
    378 	} 2>&1 | tee "${PGPVERIFYLOG}"
    379 
    380 	# The output should contain lines that match all the following regexps
    381 	#
    382 	while read line; do
    383 		if ! grep -E -q -e "^${line}\$" "${PGPVERIFYLOG}"; then
    384 			echo >&2 "Failed to verify signature: ${line}"
    385 			return 1
    386 		fi
    387 	done <<'EOF'
    388 gpg: Signature made .* using RSA key ID (62AA7E34|44AD418C)
    389 gpg: Good signature from "Paul Eggert <eggert (a] cs.ucla.edu>"
    390 gpg exit status 0
    391 EOF
    392 }
    393 
    394 extract()
    395 {
    396 	[ -f "${EXTRACTDIR}/zone.tab" ] && return
    397 	mkdir -p "${EXTRACTDIR}"
    398 	tar -z -xf "${DISTFILE}" -C "${EXTRACTDIR}"
    399 }
    400 
    401 addnews()
    402 {
    403 	[ -f "${EXTRACTDIR}/NEWS" ] && return
    404 	cp -p "${NEWSFILE}" "${EXTRACTDIR}"/NEWS
    405 }
    406 
    407 # Find the relevant part of the NEWS file for all releases between
    408 # OLDVER and NEWVER, and save them to NEWSTRIMFILE.
    409 #
    410 trimnews()
    411 {
    412 	[ -s "${NEWSTRIMFILE}" ] && return
    413 	awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \
    414 	    '
    415 		BEGIN {inrange = 0}
    416 		/^Release [0-9]+[a-z]+ - .*/ {
    417 			# "Release <version> - <date>"
    418 			# Note: must handle transition from 2018z to 2018aa
    419 			# Assumptions: OLDVER and NEWVER have been sanitized,
    420 			# and format of NEWS file does not alter (and
    421 			# contains valid data)
    422 			inrange = ((length($2) > length(oldver) || \
    423 					$2 > oldver) && \
    424 				(length($2) < newver || $2 <= newver))
    425 		}
    426 		// { if (inrange) print; }
    427 	    ' \
    428 		<"${NEWSFILE}" >"${NEWSTRIMFILE}"
    429 	echo "tzdata-${NEWVER}" > ${TZDISTDIR}/TZDATA_VERSION
    430 }
    431 
    432 # Create IMPORTMSGFILE from NEWSTRIMFILE, by ignoring some sections,
    433 # keeping only the first sentence from paragraphs in other sections,
    434 # and changing the format.
    435 #
    436 # The result should be edited by hand before performing a cvs commit.
    437 # A message to that effect is inserted at the beginning of the file.
    438 #
    439 mkimportmsg()
    440 {
    441 	[ -s "${IMPORTMSGFILE}" ] && return
    442 	{ cat <<EOF
    443 EDIT ME: Edit this file and then delete the lines marked "EDIT ME".
    444 EDIT ME: This file will be used as a log message for the "cvs commit" that
    445 EDIT ME: imports tzdata${NEWVER}.  The initial contents of this file were
    446 EDIT ME: generated from ${NEWSFILE}.
    447 EDIT ME: 
    448 EOF
    449 	awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \
    450 	    -v disturl="${DISTURL}" -v newsurl="${NEWSURL}" \
    451 	    '
    452 		BEGIN {
    453 			bullet = "  * ";
    454 			indent = "    ";
    455 			blankline = 0;
    456 			goodsection = 0;
    457 			havesentence = 0;
    458 			print "Import tzdata"newver" from "disturl;
    459 			#print "and NEWS file from "newsurl;
    460 		}
    461 		/^Release/ {
    462 			# "Release <version> - <date>"
    463 			ver = $2;
    464 			date = gensub(".* - ", "", 1, $0);
    465 			print "";
    466 			print "Summary of changes in tzdata"ver \
    467 				" ("date"):";
    468 		}
    469 		/^$/ { blankline = 1; havesentence = 0; }
    470 		/^  Changes affecting/ { goodsection = 0; }
    471 		/^  Changes affecting.*time/ { goodsection = 1; }
    472 		/^  Changes affecting.*data/ { goodsection = 1; }
    473 		/^  Changes affecting.*documentation/ || \
    474 		/^  Changes affecting.*commentary/ {
    475 			t = gensub("^ *", "", 1, $0);
    476 			t = gensub("\\.*$", ".", 1, t);
    477 			print bullet t;
    478 			goodsection = 0;
    479 		}
    480 		/^    .*/ && goodsection {
    481 			# In a paragraph in a "good" section.
    482 			# Ignore leading spaces, and ignore anything
    483 			# after the first sentence.
    484 			# First line of paragraph gets a bullet.
    485 			t = gensub("^ *", "", 1, $0);
    486 			t = gensub("\\. .*", ".", 1, t);
    487 			if (blankline) print bullet t;
    488 			else if (! havesentence) print indent t;
    489 			havesentence = (havesentence || (t ~ "\\.$"));
    490 		}
    491 		/./ { blankline = 0; }
    492 	    ' \
    493 		<"${NEWSTRIMFILE}"
    494 	} >"${IMPORTMSGFILE}"
    495 }
    496 
    497 editimportmsg()
    498 {
    499 	if [ -s "${IMPORTMSGFILE}" ] \
    500 	&& ! grep -q '^EDIT' "${IMPORTMSGFILE}"
    501 	then
    502 		return 0 # file has already been edited
    503 	fi
    504 	# Pass both IMPORTMSGFILE and NEWSFILE to the editor, so that the
    505 	# user can easily consult NEWSFILE while editing IMPORTMSGFILE.
    506 	${EDITOR} "${IMPORTMSGFILE}" "${NEWSFILE}"
    507 }
    508 
    509 cvsimport()
    510 {
    511 	if [ -e "${IMPORTDONEFILE}" ]; then
    512 		cat >&2 <<EOF
    513 The CVS import has already been performed.
    514 EOF
    515 		return 0
    516 	fi
    517 	if ! [ -s "${IMPORTMSGFILE}" ] \
    518 	|| grep -q '^EDIT' "${IMPORTMSGFILE}"
    519 	then
    520 		cat >&2 <<EOF
    521 The message file ${IMPORTMSGFILE}
    522 has not been properly edited.
    523 Not performing cvs import.
    524 EOF
    525 		return 1
    526 	fi
    527 	( cd "${EXTRACTDIR}" &&
    528 	  DOIT cvs -d "${CVSROOT}" import -m "$(cat "${IMPORTMSGFILE}")" \
    529 		"${REPODIR}" "${CVSBRANCHTAG}" "${CVSNEWTAG}"
    530 	) && touch "${IMPORTDONEFILE}"
    531 }
    532 
    533 cvsmerge()
    534 {
    535 
    536 	cd "${TZDISTDIR}" || exit 1
    537 	if [ -e "${MERGEDONEFILE}" ]; then
    538 		cat >&2 <<EOF
    539 The CVS merge has already been performed.
    540 EOF
    541 		return 0
    542 	fi
    543 	DOIT cvs -d "${CVSROOT}" update -j"${CVSOLDTAG}" -j"${CVSNEWTAG}" \
    544 	&& touch "${MERGEDONEFILE}"
    545 }
    546 
    547 resolveconflicts()
    548 {
    549 	cd "${TZDISTDIR}" || exit 1
    550 	if grep -l '^[<=>][<=>][<=>]' *
    551 	then
    552 		cat <<EOF
    553 There appear to be conflicts in the files listed above.
    554 Resolve conflicts, then re-run this script.
    555 EOF
    556 		return 1
    557 	fi
    558 }
    559 
    560 cvscommitmerge()
    561 {
    562 	cd "${TZDISTDIR}" || exit 1
    563 	if grep -l '^[<=>][<=>][<=>]' *
    564 	then
    565 		cat >&2 <<EOF
    566 There still appear to be conflicts in the files listed above.
    567 Not performing cvs commit.
    568 EOF
    569 		return 1
    570 	fi
    571 	if [ -e "${COMMITMERGEDONEFILE}" ]; then
    572 		cat >&2 <<EOF
    573 The CVS commmit (of the merge result) has already been performed.
    574 EOF
    575 		return 0
    576 	fi
    577 	DOIT cvs -d "${CVSROOT}" commit -m "Merge tzdata${NEWVER}" \
    578 	&& touch "${COMMITMERGEDONEFILE}"
    579 }
    580 
    581 extra()
    582 {
    583 	cat <<EOF
    584 Also do the following:
    585  * Edit and commit  src/doc/3RDPARTY
    586  * Edit and commit  src/doc/CHANGES
    587  * Edit and commit  src/distrib/sets/lists/base/mi
    588  *      if the set of installed files altered.
    589  * Submit pullup requests for all active release branches.
    590  * rm -rf ${WORK_PFX}  (optional)
    591  * Verify that
    592   ${UPDATE_FROM}
    593  * no longer exists.
    594 EOF
    595 }
    596 
    597 main()
    598 {
    599 	set -e
    600 	validate_pwd
    601 	findcvsroot
    602 	mkworkpfx
    603 	argparse "$@"
    604 	setup_versions
    605 	mkworkdir
    606 	fetch
    607 	checksig
    608 	extract
    609 	addnews
    610 	trimnews
    611 	mkimportmsg
    612 	editimportmsg
    613 	cvsimport
    614 	cvsmerge
    615 	resolveconflicts
    616 	cvscommitmerge
    617 	rm -f "${UPDATE_FROM}"
    618 	extra
    619 }
    620 
    621 main "$@"
    622