Home | History | Annotate | Line # | Download | only in tz
      1 # $NetBSD: tzdata2netbsd,v 1.18 2026/03/16 11:51:35 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 # Then in the following early xx00's sometime, delete the class, and
     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 # Note it doesn't matter (here) if nnnnz is followed by nnnnaa or nnnnza
     46 
     47 DIST_HOST=ftp.iana.org
     48 DIST_PATH=tz
     49 DIST_FILES=releases
     50 
     51 GTZURL=https://github.com/JodaOrg/global-tz/releases/download
     52 
     53 EDITOR=${EDITOR:-vi}
     54 
     55 TZBASE=$(pwd)	|| fail "Cannot find myself (${PWD})"
     56 cd -P "$TZBASE"	|| fail "Cannot return home: ${TZBASE}"
     57 
     58 if [ "${TZBASE}" != "${PWD}" ]
     59 then
     60 	fail "TZBASE!=PWD ?? TZBASE='${TZBASE}' PWD='${PWD}'"
     61 fi
     62 
     63 WORK_PFX=${TZBASE}/update-work
     64 UPDATE_FROM=${WORK_PFX}/updating.from.version
     65 
     66 usage()
     67 {
     68 	printf >&2 '%s\n' ''						\
     69 		"Usage: $0 [new-version-id [old-version-id]]" ''	\
     70 		"     where a version-id is of the form YYYYx (eg: 2018c)" \
     71 		"     or '' for new-version-id (to specify only the old)"  \
     72 		"     and where new-version-id can have =fetch-version-id" \
     73 		"     appended to specify fetching that version instead" \
     74 		"     where the 'fetch-version-id' can be omitted if it" \
     75 		"     is \${new-version-id}gtz  - and simply using '=' means" \
     76 		"     to work out the new-version-id but then use the gtz fork"
     77 
     78 	printf >&2 '\nUsually use:\n\t\tsh %s =\n' "${0##*/}"
     79 
     80 	exit 2
     81 }
     82 
     83 fail()
     84 {
     85 	local IFS=' '
     86 
     87 	printf >&2 '%s\n'	"Error detected:" "   $*" "Aborting."
     88 	exit 1
     89 }
     90 
     91 find_netbsdsrc()
     92 {
     93 	while ! [ -f UPDATING ] || ! [ -f BUILDING ]
     94 	do
     95 		if [ "${PWD}" = / ]
     96 		then
     97 			printf '%s\n' 'Unable to find NETBSDSRCDIR'
     98 			return 1
     99 		fi
    100 		cd -P ..
    101 	done
    102 	NETBSDSRCDIR=${PWD}
    103 	cd -P "${TZBASE:-/nowhere/that/exists}" ||
    104 		fail 'Unable to return to TZBASE:' "${TZBASE}"
    105 }
    106 
    107 valid_vers()
    108 {
    109 	case "$2" in
    110 	# The IANA (Eggert) standard version names
    111 	( ${VERS_PATTERN} | ${VERS_PATTERN}[a-z] )
    112 		;;
    113 	# The alternate (more rational) fork "global timezone" version
    114 	( ${VERS_PATTERN}gtz | ${VERS_PATTERN}[a-z]gtz )
    115 		;;
    116 	(*)	printf >&2 '%s: %s\n' \
    117 		    "Bad form for $1 version specifier '$2'" \
    118 		    "should (usually) be 'YYYYx'"
    119 		return 1
    120 		;;
    121 	esac
    122 	return 0
    123 }
    124 
    125 get_curvers()
    126 {
    127 	local LF=''
    128 	local LIST=iana-listing
    129 	local SED_SCRIPT='
    130 		/tzdata-latest.*-> /{
    131 			s/^.*-> //
    132 			s/\..*$//
    133 			s;^releases/tzdata;;p
    134 			q
    135 		}
    136 		d'
    137 
    138 	test -d "${WORK_PFX}" &&
    139 		test -s "${WORK_PFX}/${LIST}" &&
    140 		test "${WORK_PFX}/${LIST}" -nt dist/CVS &&
    141 		LF=$(find "${WORK_PFX}" -name "${LIST}" -mtime -1 -print) &&
    142 		test -n "${LF}" &&
    143 		NEWVER=$(sed -n < "${LF}" "${SED_SCRIPT}") &&
    144 		valid_vers new "${NEWVER}"				||
    145 
    146 	ftp >/dev/null 2>&1 -ia "${DIST_HOST}" <<- EOF &&
    147 					dir ${DIST_PATH} ${WORK_PFX}/${LIST}
    148 					quit
    149 					EOF
    150 		test -s "${WORK_PFX}/${LIST}" &&
    151 		NEWVER=$(sed -n < "${WORK_PFX}/${LIST}" "${SED_SCRIPT}") &&
    152 		valid_vers new "${NEWVER}"				||
    153 
    154 	{
    155 		rm -f "${WORK_PFX}/${LIST}"
    156 		fail "Cannot fetch current tzdata version from ${DIST_HOST}"
    157 	}
    158 
    159 	printf 'Updating from %s to %s\n' "${OLDVER}" "${NEWVER}"
    160 }
    161 
    162 argparse()
    163 {
    164 	local OVF OV NV OVy OVs NVy NVs
    165 
    166 	if OVF=$(find "${WORK_PFX}" -name "${UPDATE_FROM##*/}" -mtime +2 -print)
    167 	then
    168 		# delete anything old
    169 		test -n "${OVF}" && rm -f "${OVF}"
    170 	fi
    171 
    172 	case "$#" in
    173 	( 0 | 1 )
    174 		# once we have obtained OLDVER once, never guess it again.
    175 		if  [ -f "${UPDATE_FROM}" ]
    176 		then
    177 			OLDVER=$(cat "${UPDATE_FROM}")
    178 		elif [ -f dist/TZDATA_VERSION ]
    179 		then
    180 			OLDVER=$(cat dist/TZDATA_VERSION)
    181 		elif [ -f dist/version ]
    182 		then
    183 			OLDVER=$(cat dist/version)
    184 		fi
    185 		OLDVER=${OLDVER#tzdata}	# TZDATA_VERS is tzdata-nnnnX
    186 		OLDVER=${OLDVER#-}	# but the '-' is optional
    187 		OLDVERGTZ=${OLDVER}	# This would have been the cvs tag
    188 		OLDVER=${OLDVER%gtz}	# want the base version elsewhere
    189 
    190 		if [ -z "${OLDVER}" ]
    191 		then
    192 			printf >&2 '%s\n'  \
    193 			    'Cannot determine current installed version'  \
    194 			    'Specify it on the command line.'  \
    195 			    ''
    196 			usage
    197 		fi
    198 
    199 		valid_vers old "${OLDVER}" ||
    200 			fail "Calculated bad OLDVER, give as 2nd arg"
    201 		;;
    202 
    203 	( 2 )	valid_vers old "$2" && OLDVER="$2" || usage
    204 		;;
    205 
    206 	( * )	usage
    207 		;;
    208 	esac
    209 
    210 	GLOBAL=false
    211 	case "$#:$1" in
    212 	( 0: | 1: | 2: )
    213 		;;
    214 	( 1:= | 2:= )
    215 		GLOBAL=true;;
    216 	( 1:=?* | 2:=?* )	
    217 		valid_vers fetch "${1#=}" && FETCHVER="${1#=}" || usage
    218 		;;
    219 	( 1:*=?* | 2:*=?* )	
    220 		set -- "{$1%=*}" "${1#*=}"
    221 		valid_vers fetch "$2" && FETCHVER="$2" || usage
    222 		valid_vers new "$1" && NEWVER="$1" || usage
    223 		;;
    224 	( 1:?* | 2:?* )	
    225 		valid_vers new "$1" && NEWVER="$1" || usage
    226 		;;
    227 	( * )	usage
    228 		;;
    229 	esac
    230 
    231 	test -z "${NEWVER}" && get_curvers
    232 
    233 	if [ -z "${FETCHVER}" ]
    234 	then
    235 		if "${GLOBAL}"
    236 		then
    237 			FETCHVER=${NEWVER}gtz
    238 		else
    239 			FETCHVER=${NEWVER}
    240 		fi
    241 	fi
    242 
    243 	case "${FETCHVER}" in
    244 	( *gtz )	GLOBAL=true;;
    245 	( * )		GLOBAL=false;;
    246 	esac
    247 
    248 	if [ "${NEWVER}" = "${OLDVER}" ]
    249 	then
    250 		printf 'New and old versions both %s; nothing to do\n' \
    251 		     "${NEWVER}"
    252 		docupdate
    253 		exit 0
    254 	fi
    255 
    256 	if ! "${GLOBAL}"
    257 	then
    258 		local reply
    259 
    260 		printf 'This will not use the GTZ variant of tzdata.\n'
    261 		read -r -p 'Is that intended? ' reply
    262 		case "${reply}" in
    263 		([Yy]*)	;;
    264 		(*)   printf 'Aborting after doing nothing\n'; exit 1;;
    265 		esac
    266 	fi
    267 
    268 	printf '%s\n' "${OLDVER}" > "${UPDATE_FROM}" ||
    269 	    fail "Unable to preserve old version ${OLDVER} in ${UPDATE_FROM}"
    270 
    271 	# Do version upgrade test using base version names, ignoring
    272 	# the "gtz" in the "global timezone" versions, so we can
    273 	# switch back and forth between use of those as circumstances change
    274 	OV=${OLDVER%gtz}
    275 	NV=${NEWVER%gtz}
    276 
    277 	OVy=${OV%%[!0-9]*}
    278 	OVs=${OV#${OVy}}
    279 	NVy=${NV%%[!0-9]*}
    280 	NVs=${NV#${NVy}}
    281 
    282 	# To get all the permutations correct, we need to separate
    283 	# the year and suffix parts of the version IDs (done just above)
    284 	# and then compare them separately.  The suffix is only relevant
    285 	# to the result when the years are the same.
    286 
    287 	# We compare the length of the suffix separately to the suffix
    288 	# itself, a multi-char suffix has never happened (and is never
    289 	# likely to) - but in the event that prediction is wrong, we don't
    290 	# know (yet) what is to come after 'z' - it might be 'za' 'zb'
    291 	# ... to 'zz" then 'zza' ... or it might be 'aa' 'ab' ... 'az' 'ba'...
    292 	# we need to handle both possibilities.  Two things stand out from
    293 	# those: 1. a longer suffix is always going to be for a newer version
    294 	# than a shorter one;  2. equal length suffixes can be compared as
    295 	# strings
    296 
    297 	if [ "${OVy}" -gt "${NVy}" ]			|| {
    298 		[ "${OVy}" -eq "${NVy}" ]	&& {
    299 			[ "${#OVs}" -gt "${#NVs}" ]		||
    300 			LC_COLLATE=C [ "${OVs}" '>' "${NVs}" ]
    301 		}
    302         } then
    303 		local reply
    304 
    305 		printf '%s\n' "Update would revert ${OLDVER} to ${NEWVER}"
    306 		read -p "Is reversion intended? " reply
    307 		case "${reply}" in
    308 		([Yy]*)	;;
    309 		(*)	printf '%s\n' OK. Aborted.
    310 			rm -f "${UPDATE_FROM}"
    311 			exit 1
    312 			;;
    313 		esac
    314 	fi
    315 
    316 	return 0
    317 }
    318 
    319 setup_versions()
    320 {
    321 	# Uppercase variants of OLDVER and NEWVER
    322 	OLDVER_UC="$( printf '%s\n' "${OLDVERGTZ}" | tr 'a-z' 'A-Z' )"
    323 	NEWVER_UC="$( printf '%s\n' "${NEWVER}" | tr 'a-z' 'A-Z' )"
    324 
    325 	# Tags for use with version control systems
    326 	CVSOLDTAG="TZDATA${OLDVER_UC}"
    327 	CVSNEWTAG="TZDATA${NEWVER_UC}"
    328 	CVSBRANCHTAG="TZDATA"
    329 	GITHUBTAG="${NEWVER}"
    330 
    331 	if "${GLOBAL}" && [ "${CVSNEWTAG%GTZ}" = "${CVSNEWTAG}" ]
    332 	then
    333 		CVSNEWTAG=${CVSNEWTAG}GTZ
    334 	fi
    335 
    336 	# URLs for fetching distribution files, etc.
    337 	if "${GLOBAL}"
    338 	then
    339 		DISTURL=${GTZURL}/${FETCHVER}/tzdata${FETCHVER}.tar.gz
    340 		unset SIGURL
    341 	else
    342 		DISTURL="ftp://${DIST_HOST}/${DIST_PATH}/${DIST_FILES}"
    343 		DISTURL="${DISTURL}/tzdata${NEWVER}.tar.gz"
    344 		SIGURL="${DISTURL}.asc"
    345 	fi
    346 	NEWSURL="https://github.com/eggert/tz/raw/${GITHUBTAG}/NEWS"
    347 
    348 	# Directories
    349 	REPODIR="src/external/public-domain/tz/dist"
    350 				# relative to the NetBSD CVS repo
    351 	TZDISTDIR="$(pwd)/dist" # should be .../external/public-domain/tz/dist
    352 	WORKDIR="${WORK_PFX}/${NEWVER}"
    353 	EXTRACTDIR="${WORKDIR}/extract"
    354 
    355 	# Files in the work directory
    356 	DISTFILE="${WORKDIR}/${DISTURL##*/}"
    357 	SIGFILE="${DISTFILE}.asc"
    358 	PGPVERIFYLOG="${WORKDIR}/pgpverify.log"
    359 	NEWSFILE="${WORKDIR}/NEWS"
    360 	NEWSTRIMFILE="${WORKDIR}/NEWS.trimmed"
    361 	IMPORTMSGFILE="${WORKDIR}/import.msg"
    362 	IMPORTDONEFILE="${WORKDIR}/import.done"
    363 	MERGSMSGFILE="${WORKDIR}/merge.msg"
    364 	MERGEDONEFILE="${WORKDIR}/merge.done"
    365 	FILECONTENTCHECKDONE="${WORKDIR}/filecheck.done"
    366 	COMMITMERGEDONEFILE="${WORKDIR}/commitmerge.done"
    367 
    368 	printf '%s\n' "${CVSOLDTAG}"  > "${WORK_PFX}/updating_from"
    369 }
    370 
    371 DOIT()
    372 {
    373 	local really_do_it=false
    374 	local reply
    375 
    376 	printf 'In directory %s\n' "$(pwd)"
    377 	printf 'ABOUT TO DO: %s\n' "$(shell_quote "$@")"
    378 	read -p "Really do it? [yes/no/quit] " reply
    379 	case "${reply}" in
    380 	([yY]*)	really_do_it=true ;;
    381 	([nN]*)	really_do_it=false ;;
    382 	([qQ]*)
    383 		printf 'Aborting\n'
    384 		return 1
    385 		;;
    386 	(*)	printf 'Huh?\n'
    387 		return 1
    388 		;;
    389 	esac
    390 	if "$really_do_it"; then
    391 		printf 'REALLY DOING IT NOW...\n'
    392 		"$@"
    393 	else
    394 		printf 'NOT DOING THE ABOVE COMMAND\n'
    395 	fi
    396 }
    397 
    398 # Quote args to make them safe in the shell.
    399 # Usage: quotedlist="$(shell_quote args...)"
    400 #
    401 # After building up a quoted list, use it by evaling it inside
    402 # double quotes, like this:
    403 #    eval "set -- $quotedlist"
    404 # or like this:
    405 #    eval "\$command $quotedlist \$filename"
    406 #
    407 shell_quote()
    408 (
    409 	local result=''
    410 	local arg qarg
    411 	LC_COLLATE=C ; export LC_COLLATE # so [a-zA-Z0-9] works in ASCII
    412 	for arg in "$@" ; do
    413 		case "${arg}" in
    414 		('')
    415 			qarg="''"
    416 			;;
    417 		(*[!-./a-zA-Z0-9]*)
    418 			# Convert each embedded ' to '\'',
    419 			# then insert ' at the beginning of the first line,
    420 			# and append ' at the end of the last line.
    421 			# Finally, elide unnecessary '' pairs at the
    422 			# beginning and end of the result and as part of
    423 			# '\'''\'' sequences that result from multiple
    424 			# adjacent quotes in the input.
    425 			qarg="$(printf '%s\n' "$arg" | \
    426 			    ${SED:-sed} -e "s/'/'\\\\''/g" \
    427 				-e "1s/^/'/" -e "\$s/\$/'/" \
    428 				-e "1s/^''//" -e "\$s/''\$//" \
    429 				-e "s/'''/'/g"
    430 				)"
    431 			;;
    432 		(*)
    433 			# Arg is not the empty string, and does not contain
    434 			# any unsafe characters.  Leave it unchanged for
    435 			# readability.
    436 			qarg="${arg}"
    437 			;;
    438 		esac
    439 		result="${result}${result:+ }${qarg}"
    440 	done
    441 	printf '%s\n' "$result"
    442 )
    443 
    444 validate_pwd()
    445 {
    446 	local P="$(pwd)" || return 1
    447 
    448 	test -d "${P}" &&
    449 	    test -d "${P}/dist" &&
    450 	    test -f "${P}/dist/zone.tab" &&
    451 	    test -f "${P}/tzdata2netbsd" || {
    452 		printf >&2 '%s\n' 'Please change to the correct directory' \
    453 		    'It is the one (in src) containing the tzdata2netbsd script, and dist subdir'
    454 		return 1
    455 	}
    456 }
    457 
    458 check_branch()
    459 {
    460 	local P="$(pwd)" || return 1
    461 
    462 	if ${Hg:-false}
    463 	then
    464 		if [ "$(hg branch)" = trunk ]
    465 		then
    466 			return 0
    467 		fi
    468 		printf >&2 '%s\n' \
    469 		    "This script should be run in a checkout of trunk only"
    470 		return 1
    471 
    472 	fi
    473 	test -f "${P}/CVS/Tag" && {
    474 
    475 		# Here (for local use only) if needed for private branch work
    476 		# insert tests for the value of $(cat "${P}/CVS/Tag") and
    477 		# allow your private branch tag to pass. Eg:
    478 
    479 		#	case "$(cat "${P}/CVS/Tag")" in
    480 		#	(my-branch-name)	return 0;;
    481 		#	esac
    482 
    483 		# Do not commit a version of this script modified that way,
    484 		# (not even on the private branch) - keep it as a local
    485 		# modified file.  (This script will not commit it.)
    486 
    487 		printf >&2 '%s\n' \
    488 		    "This script should be run in a checkout of HEAD only"
    489 		return 1
    490 	}
    491 
    492 	return 0
    493 }
    494 
    495 findcvsroot()
    496 {
    497 	Hg=false
    498 	if HGREPO=$(hg paths default 2>/dev/null)
    499 	then
    500 		test -n "${HGREPO}" && Hg=true && return 0
    501 	fi
    502 	[ -n "${CVSROOT}" ] && return 0
    503 	CVSROOT="$( cat ./CVS/Root 2>/dev/null )" || {
    504 		printf 'No CVS/Root or Hg repository in %s\n' "$(pwd)"
    505 		return 1
    506 	}
    507 	[ -n "${CVSROOT}" ] && return 0
    508 	printf >&2 'Failed to set CVSROOT value\n'
    509 	return 1
    510 }
    511 
    512 hginit()
    513 {
    514 	local P=
    515 
    516 	if ! "${Hg:-false}"
    517 	then
    518 		return 0
    519 	fi
    520 
    521 	P=$(cd -P "${NETBSDSRCDIR}/.." && pwd) ||
    522 		fail 'Unable to name parent of NETBSDSRCDIR'
    523 
    524 	TZDATASRC=${P}/tzdatasrc
    525 
    526 	if ! [ -d "${TZDATASRC}" ]
    527 	then
    528 		printf 2>&1 '%s\n' \
    529 		    'Make the tzdatasrc clone of src first.'		\
    530 		    'It should be a sibling of src in the filesys:'	\
    531 		    "	${TZDATASRC}"					\
    532 		    'It should contain just the TZDATA branch.'
    533 
    534 		exit 3
    535 	fi
    536 
    537 	if [ "$( ls "${TZDATASRC}" )" != external ]			  ||
    538 	   [ "$( ls "${TZDATASRC}/external" )" != public-domain ]	  ||
    539 	   [ "$( ls "${TZDATASRC}/external/public-domain" )" != tz ]	  ||
    540 	   [ "$( ls "${TZDATASRC}/external/public-domain/tz" )" != dist ] ||
    541 	 ! [ -d "${TZDATASRC}/external/public-domain/tz/dist" ]
    542 	then
    543 		printf 2>&1 '%s\n' \
    544 			"TZDATASRC (${TZDATASRC}) set up improperly"	\
    545 			'Correct it and restart.'			\
    546 			'It should contain only the TZDATA branch'
    547 		exit 4
    548 	fi
    549 
    550 	WORK_PFX=${P}/tzdata-update-work
    551 	UPDATE_FROM=${WORK_PFX}/updating.from.version
    552 }
    553 
    554 mkworkpfx()
    555 {
    556 	mkdir -p "${WORK_PFX}" || fail "Unable to make missing ${WORK_PFX}"
    557 }
    558 mkworkdir()
    559 {
    560 	mkdir -p "${WORKDIR}" || fail "Unable to make missing ${WORKDIR}"
    561 }
    562 
    563 cvsupdate()
    564 (
    565 	# Make sure our working directory is up to date (and HEAD)
    566 
    567 	if "${Hg:-false}"
    568 	then
    569 		if [ -n "$(hg status | sed 1q)" ]
    570 		then
    571 			printf 'There are modified files in the tree:\n'
    572 			hg status
    573 			printf 'Commit, revert, or shelve any modified files\n'
    574 			return 1
    575 		fi
    576 
    577 		if [ hg outgoing >/dev/null 2>&1 ]
    578 		then
    579 			printf 'There are unpushed changesets in the tree:\n'
    580 			hg outgoing | sed -e 1,2d
    581 			printf \
    582 			  'These changes will be pushed as part of the update\n'
    583 			read -r -p 'Is that OK? [Y/n] ' reply
    584 			case ${reply} in
    585 			[Yy]*)	;;
    586 			*)	printf 'Aborting.  Nothing has changed\n'
    587 				exit 1
    588 				;;
    589 			esac
    590 		fi
    591 		hg pull -u -b trunk "${HGREPO}"
    592 		return 0
    593 	fi
    594 
    595 	(
    596 		cd -P "${TZBASE}/dist"			|| exit 1
    597 		cvs -d "${CVSROOT}" -q update -AdP	|| exit 2
    598 	) || exit $?
    599 )
    600 
    601 fetch()
    602 {
    603 	[ -f "${DISTFILE}" ] || ftp -o "${DISTFILE}" "${DISTURL}" ||
    604 		fail "fetch of ${DISTFILE} failed"
    605 
    606 	if [ -n "${SIGURL}" ]
    607 	then
    608 		[ -f "${SIGFILE}" ] || ftp -o "${SIGFILE}" "${SIGURL}" ||
    609 			fail "fetch of ${SIGFILE} failed"
    610 	fi
    611 
    612 	[ -f "${NEWSFILE}" ] || ftp -o "${NEWSFILE}" "${NEWSURL}" ||
    613 		fail "fetch of ${NEWSFILE} failed"
    614 }
    615 
    616 checksig()
    617 {
    618 	{
    619 		gpg --verify "${SIGFILE}" "${DISTFILE}"
    620 		printf 'gpg exit status %s\n' "$?"
    621 	} 2>&1 |
    622 		tee "${PGPVERIFYLOG}"
    623 
    624 	# The output should contain lines that match all the following regexps
    625 	#
    626 	while read line
    627 	do
    628 		if ! grep -E -q -e "^${line}\$" "${PGPVERIFYLOG}"
    629 		then
    630 			printf >&2 'Failed to verify signature: %s\n' "${line}"
    631 			return 1
    632 		fi
    633 	done <<'EOF'
    634 gpg: Signature made .* using RSA key ID (62AA7E34|44AD418C)
    635 gpg: Good signature from "Paul Eggert <eggert (a] cs.ucla.edu>"
    636 gpg exit status 0
    637 EOF
    638 }
    639 
    640 extract()
    641 {
    642 	[ -f "${EXTRACTDIR}/zone.tab" ] && return
    643 	mkdir -p "${EXTRACTDIR}"
    644 	tar -z -xf "${DISTFILE}" -C "${EXTRACTDIR}"
    645 }
    646 
    647 rflist()
    648 (
    649 	 test "${1}" && cd -P "${1}" && find * -print | sort
    650 )
    651 
    652 zonelists()
    653 {
    654 	[ -f "${WORKDIR}"/addedzones ] && return
    655 
    656 	rm -fr "${WORK_PFX}"/oldzones "${WORK_PFX}"/newzones
    657 
    658 	(
    659 		cd -P "${TZBASE}/share/zoneinfo" || exit 1
    660 
    661 		make	TOOL_ZIC=/usr/sbin/zic			\
    662 			DESTDIR=				\
    663 			TZBUILDDIR="${WORK_PFX}"/oldzones	\
    664 			TZDIR="${WORK_PFX}"/oldzones		\
    665 			TZDISTDIR="${TZBASE}"/dist		\
    666 				posix_only >/dev/null 2>&1
    667 
    668 	) || fail 'Unable to compile old zone data files'
    669 
    670 	(
    671 		cd -P "${TZBASE}/share/zoneinfo" || exit 1
    672 
    673 		make	TOOL_ZIC=/usr/sbin/zic			\
    674 			DESTDIR=				\
    675 			TZBUILDDIR="${WORK_PFX}"/newzones	\
    676 			TZDIR="${WORK_PFX}"/newzones		\
    677 			TZDISTDIR="${EXTRACTDIR}"		\
    678 				posix_only >/dev/null 2>&1
    679 
    680 	) || fail 'Unable to compile new zone data files'
    681 
    682 	rflist "${WORK_PFX}"/oldzones > "${WORKDIR}"/oldzones
    683 	rflist "${WORK_PFX}"/newzones > "${WORKDIR}"/newzones
    684 
    685 	if cmp -s "${WORKDIR}"/oldzones "${WORKDIR}"/newzones >/dev/null
    686 	then
    687 		printf 'No zones added or deleted by this update\n'
    688 		> "${WORKDIR}"/removedzones
    689 		> "${WORKDIR}"/addedzones
    690 		return 0
    691 	fi
    692 
    693 	comm -23 "${WORKDIR}"/oldzones "${WORKDIR}"/newzones \
    694 		> "${WORKDIR}"/removedzones
    695 
    696 	test "${REMOVEOK:-no}" != yes && test -s "${WORKDIR}"/removedzones && {
    697 		printf '%s\n' 'This update wants to remove these zone files:' ''
    698 		sed 's/^/	/' < "${WORKDIR}"/removedzones
    699 		printf '%s\n' '' 'It probably should not' ''
    700 
    701 		printf 'If this is OK, rerun this script with REMOVEOK=yes\n'
    702 		printf 'Otherwise, fix the problem, and then rerun the script\n'
    703 		exit 1
    704 	}
    705 
    706 	comm -13 "${WORKDIR}"/oldzones "${WORKDIR}"/newzones \
    707 		> "${WORKDIR}"/addedzones
    708 
    709 	test -s "${WORKDIR}"/addedzones && {
    710 		printf '%s\n' '' '********************************* NOTE:' \
    711 			'********************************* New Zones Created' \
    712 			''
    713 		sed 's/^/	/' < "${WORKDIR}"/addedzones
    714 		printf '%s\n' '' '*********************************' ''
    715 	}
    716 
    717 	return 0
    718 }
    719 
    720 updatedzones()
    721 {
    722 	[ -f "${WORKDIR}"/.zonesOK ] && return
    723 
    724 	rm -fr "${WORK_PFX}"/updzones
    725 
    726 	(
    727 		cd -P "${TZBASE}/share/zoneinfo" || exit 1
    728 
    729 		make	TOOL_ZIC=/usr/sbin/zic			\
    730 			DESTDIR=				\
    731 			TZBUILDDIR="${WORK_PFX}"/updzones	\
    732 			TZDIR="${WORK_PFX}"/updzones		\
    733 			TZDISTDIR="${TZBASE}"/dist		\
    734 				posix_only >/dev/null 2>&1
    735 
    736 	) || fail 'Unable to compile updated zone data.   HELP'
    737 
    738 	rflist "${WORK_PFX}"/updzones > "${WORKDIR}"/updzones
    739 
    740 	cmp -s "${WORKDIR}"/newzones "${WORKDIR}"/updzones || {
    741 
    742 		printf '%s\n' '' '*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*' \
    743 			'After cvs work, zones created are not as intended' \
    744 			'-------------------------------------------------' \
    745 			'Zones not created but should have been:'
    746 		comm -23 "${WORKDIR}"/newzones "${WORKDIR}"/updzones |
    747 			sed 's/^/	/'
    748 		printf '%s\n' \
    749 			'-------------------------------------------------' \
    750 			'Zones created that should not have been:'
    751 		comm -13 "${WORKDIR}"/newzones "${WORKDIR}"/updzones |
    752 			sed 's/^/	/'
    753 		printf '%s\n' \
    754 			'-------------------------------------------------'
    755 
    756 		fail 'cvs import/merge/update/commit botch'
    757 	}
    758 
    759 	> "${WORKDIR}"/.zonesOK
    760 }
    761 
    762 addnews()
    763 {
    764 	[ -f "${EXTRACTDIR}/NEWS" ] && return
    765 	cp -p "${NEWSFILE}" "${EXTRACTDIR}"/NEWS
    766 }
    767 
    768 # Find the relevant part of the NEWS file for all releases between
    769 # OLDVER and NEWVER, and save them to NEWSTRIMFILE.
    770 #
    771 trimnews()
    772 {
    773 	[ -s "${NEWSTRIMFILE}" ] && return
    774 	awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \
    775 	    '
    776 		BEGIN {inrange = 0}
    777 		/^Release [0-9]+[a-z]+ - .*/ {
    778 			# "Release <version> - <date>"
    779 			# Note: must handle transition from 2018z to 2018aa
    780 			# Assumptions: OLDVER and NEWVER have been sanitized,
    781 			# and format of NEWS file does not alter (and
    782 			# contains valid data)
    783 			inrange = ((length($2) > length(oldver) || \
    784 					$2 > oldver) && \
    785 				(length($2) < newver || $2 <= newver))
    786 		}
    787 		// { if (inrange) print; }
    788 	    ' \
    789 		<"${NEWSFILE}" >"${NEWSTRIMFILE}"
    790 
    791 	if "${GLOBAL}"
    792 	then
    793 		printf '%s\n' "tzdata-${NEWVER}gtz"
    794 	else
    795 		printf '%s\n' "tzdata-${NEWVER}"
    796 	fi > "${TZDISTDIR}/TZDATA_VERSION"
    797 }
    798 
    799 # Create IMPORTMSGFILE from NEWSTRIMFILE, by ignoring some sections,
    800 # keeping only the first sentence from paragraphs in other sections,
    801 # and changing the format.
    802 #
    803 # The result should be edited by hand before performing a cvs commit.
    804 # A message to that effect is inserted at the beginning of the file.
    805 #
    806 mkimportmsg()
    807 {
    808 	[ -s "${IMPORTMSGFILE}" ] && return
    809 	{ cat <<EOF
    810 EDIT ME: Edit this file and then delete the lines marked "EDIT ME".
    811 EDIT ME: This file will be used as a log message for the "cvs commit" that
    812 EDIT ME: imports tzdata${NEWVER}.  The initial contents of this file were
    813 EDIT ME: generated from ${NEWSFILE}.
    814 EDIT ME: 
    815 EOF
    816 	awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \
    817 	    -v disturl="${DISTURL}" -v newsurl="${NEWSURL}" \
    818 	    '
    819 		BEGIN {
    820 			bullet = "  * ";
    821 			indent = "    ";
    822 			blankline = 0;
    823 			goodsection = 0;
    824 			havesentence = 0;
    825 			print "Import tzdata"newver" from "disturl;
    826 			#print "and NEWS file from "newsurl;
    827 		}
    828 		/^Release/ {
    829 			# "Release <version> - <date>"
    830 			ver = $2;
    831 			date = gensub(".* - ", "", 1, $0);
    832 			print "";
    833 			print "Summary of changes in tzdata"ver \
    834 				" ("date"):";
    835 		}
    836 		/^$/ { blankline = 1; havesentence = 0; }
    837 		/^  Changes / { goodsection = 0; }
    838 		/^  Changes to future timestamps/ { goodsection = 1; }
    839 		/^  Changes to past timestamps/ { goodsection = 1; }
    840 		/^  Changes to documentation/ || \
    841 		/^  Changes to commentary/ {
    842 			t = gensub("^ *", "", 1, $0);
    843 			t = gensub("\\.*$", ".", 1, t);
    844 			print bullet t;
    845 			goodsection = 0;
    846 		}
    847 		/^    .*/ && goodsection {
    848 			# In a paragraph in a "good" section.
    849 			# Ignore leading spaces, and ignore anything
    850 			# after the first sentence.
    851 			# First line of paragraph gets a bullet.
    852 			t = gensub("^ *", "", 1, $0);
    853 			t = gensub("\\. .*", ".", 1, t);
    854 			if (blankline) print bullet t;
    855 			else if (! havesentence) print indent t;
    856 			havesentence = (havesentence || (t ~ "\\.$"));
    857 		}
    858 		/./ { blankline = 0; }
    859 	    ' \
    860 		<"${NEWSTRIMFILE}"
    861 	} >"${IMPORTMSGFILE}"
    862 
    863 	if [ -s "${WORKDIR}"/addedzones ]
    864 	then
    865 		printf '%s\n' '' 'Zones added by this update:'
    866 		sed 's/^/	/' < "${WORKDIR}"/addedzones
    867 	fi >> "${IMPORTMSGFILE}"
    868 
    869 	if [ -s "${WORKDIR}"/removedzones ]
    870 	then
    871 		printf '%s\n' '' 'Zones removed by this update:'
    872 		sed 's/^/	/' < "${WORKDIR}"/removedzones
    873 	fi >> "${IMPORTMSGFILE}"
    874 
    875 }
    876 
    877 editimportmsg()
    878 {
    879 	if [ -s "${IMPORTMSGFILE}" ] && ! grep -q '^EDIT' "${IMPORTMSGFILE}"
    880 	then
    881 		return 0 # file has already been edited
    882 	fi
    883 	# Pass both IMPORTMSGFILE and NEWSFILE to the editor, so that the
    884 	# user can easily consult NEWSFILE while editing IMPORTMSGFILE.
    885 	${EDITOR} "${IMPORTMSGFILE}" "${NEWSFILE}"
    886 }
    887 
    888 cvsimport()
    889 {
    890 	if [ -e "${IMPORTDONEFILE}" ]; then
    891 		cat >&2 <<EOF
    892 The CVS import has already been performed.
    893 EOF
    894 		return 0
    895 	fi
    896 	if ! [ -s "${IMPORTMSGFILE}" ] || grep -q '^EDIT' "${IMPORTMSGFILE}"
    897 	then
    898 		cat >&2 <<EOF
    899 The message file ${IMPORTMSGFILE}
    900 has not been properly edited.
    901 Not performing cvs import.
    902 EOF
    903 		return 1
    904 	fi
    905 	( cd -P "${EXTRACTDIR}" &&
    906 	  DOIT cvs -d "${CVSROOT}" import -I ! -m "$(cat "${IMPORTMSGFILE}")" \
    907 		"${REPODIR}" "${CVSBRANCHTAG}" "${CVSNEWTAG}"
    908 	) && touch "${IMPORTDONEFILE}"
    909 }
    910 
    911 hgimport()
    912 (
    913 	set -e		# Just to make sure
    914 
    915 	if [ -e "${IMPORTDONEFILE}" ]; then
    916 		cat >&2 <<EOF
    917 The hg TZDATA branch update has already been performed.
    918 EOF
    919 		return 0
    920 	fi
    921 	if ! [ -s "${IMPORTMSGFILE}" ] || grep -q '^EDIT' "${IMPORTMSGFILE}"
    922 	then
    923 		cat >&2 <<EOF
    924 The message file ${IMPORTMSGFILE}
    925 has not been properly edited.
    926 Not performing hg import.
    927 EOF
    928 		return 1
    929 	fi
    930 
    931 	RELPATH=${TZBASE#${NETBSDSRCDIR}/}
    932 
    933 	cd -P "${TZDATASRC}/${RELPATH}/dist"
    934 
    935 	ls -1 > "${WORKDIR}"/old-dist-files
    936 	ls -1 "${EXTRACTDIR}" >"${WORKDIR}"/new-dist-files
    937 
    938 	rm -f *
    939 	cp -f ${EXTRACTDIR}/* .
    940 
    941 	if cmp "${WORKDIR}"/old-dist-files "${WORKDIR}"/new-dist-files
    942 	then
    943 		DOIT hg commit -l "${IMPORTMSGFILE}" &&
    944 			touch "${IMPORTDONEFILE}"
    945 		return
    946 	fi
    947 
    948 	NEW=$(comm -13 "${WORKDIR}"/old-dist-files "${WORKDIR}"/new-dist-files)
    949 	OLD=$(comm -23 "${WORKDIR}"/old-dist-files "${WORKDIR}"/new-dist-files)
    950 
    951 	if [ -n "${NEW}" ]
    952 	then
    953 		printf '\nThe following files have been added:\n%s\n' "${NEW}"\
    954 			>>"${IMPORTMSGFILE}"
    955 	fi
    956 	if [ -n "${OLD}" ]
    957 	then
    958 		printf '\nThe following files have been removed:\n%s\n' \
    959 			"${OLD}" >>"${IMPORTMSGFILE}"
    960 
    961 		test -n "${NEW}" &&
    962 		printf '\nThe following files have been added:\n%s\n' "${NEW}"
    963 		printf '\nThe following files have been removed:\n%s\n' \
    964 			"${OLD}"
    965 
    966 		read -r -p 'Do you want to do this ? ' reply
    967 		case $reply in
    968 		[Yy]*)	;;
    969 		*)	return 1;;
    970 		esac
    971 	fi
    972 
    973 	DOIT hg addremove -s 90 &&
    974 	DOIT hg commit -l "${IMPORTMSGFILE}" &&
    975 	touch "${IMPORTDONEFILE}"
    976 )
    977 
    978 cvsmerge()
    979 {
    980 
    981 	cd -P "${TZDISTDIR}" || exit 1
    982 	if [ -e "${MERGEDONEFILE}" ]
    983 	then
    984 		cat >&2 <<EOF
    985 The CVS merge has already been performed.
    986 EOF
    987 		return 0
    988 	fi
    989 	DOIT cvs -d "${CVSROOT}" update -j"${CVSOLDTAG}" -j"${CVSNEWTAG}" &&
    990 		touch "${MERGEDONEFILE}"
    991 	printf '%s\n' ================================== \
    992 		'The following differences exist between the merge results' \
    993 		'and the imported files:' '================================='
    994 	diff -ur "${EXTRACTDIR}" . || :
    995 	printf '%s\n' ==================================
    996 }
    997 
    998 hgmerge()
    999 {
   1000 	cd -P "${TZDISTDIR}" || exit 1
   1001 	if [ -e "${MERGEDONEFILE}" ]
   1002 	then
   1003 		cat >&2 <<EOF
   1004 The hg merge has already been performed.
   1005 EOF
   1006 		return 0
   1007 	fi
   1008 	DOIT hg merge TZDATA &&
   1009 		touch "${MERGEDONEFILE}" &&
   1010 		return 0
   1011 
   1012 	if [ -z "$( ht resolve -l | grep -v '^R' >/dev/null 2>&1 )" ]
   1013 	then
   1014 		# All conflicts already resolved, just note that
   1015 		printf 'Auto-resolving merge "conflicts"\n.'
   1016 		hg resolve -m &&
   1017 			touch "${MERGEDONEFILE}"
   1018 		return 0
   1019 	fi
   1020 
   1021 	printf '\n*********************************** CONFLICTS\n'
   1022 	printf 'The following files have conflicts:\n'
   1023 
   1024 	hg resolve -l
   1025 
   1026 	printf '%s\n' \
   1027 	    'Resolve these issues, and run\n\thg resolve -m file...'	\
   1028 	    'as each file listed above is corrected.' ''		\
   1029 	    'Then re-run this script.'
   1030 
   1031 	touch "${MERGEDONEFILE}"
   1032 	exit 1
   1033 }
   1034 
   1035 resolveconflicts()
   1036 {
   1037 	cd -P "${TZDISTDIR}" || exit 1
   1038 	if grep -l '^[<=>][<=>][<=>]' *
   1039 	then
   1040 		cat <<EOF
   1041 There appear to be conflicts in the files listed above.
   1042 Resolve conflicts, then re-run this script.
   1043 EOF
   1044 		return 1
   1045 	fi
   1046 }
   1047 
   1048 checkfilecontents()
   1049 (
   1050 	if [ -e "${FILECONTENTCHECKDONE}" ]
   1051 	then
   1052 		printf 'Repository file contents checked already\n'
   1053 		exit 0
   1054 	fi
   1055 
   1056 	set +e +f
   1057 
   1058 	auto=false
   1059 
   1060 	cd -P "${EXTRACTDIR}" || exit 1
   1061 	set -- *
   1062 
   1063 	if test "$1" = '*'
   1064 	then
   1065 		if [ "$#" -eq 1 ] && ! [ -e "$1" ]
   1066 		then
   1067 			printf 'No files in "%s" !!\nAborting.\n' \
   1068 			    "${EXTRACTDIR}"
   1069 			exit 1
   1070 		fi
   1071 		printf	\
   1072 		    'A file named "*" exists in "%s" !!\nDanger!!\nAborting.\n'\
   1073 		    "${EXTRACTDIR}"
   1074 		exit 1
   1075 	fi
   1076 
   1077 	unset -v LESS MORE
   1078 
   1079 	exit=0
   1080 	cd -P "${TZDISTDIR}" || exit 1
   1081 
   1082 	for file
   1083 	do
   1084 		if ! test -e "${file}"
   1085 		then
   1086 			printf 'In distribution but not in new tree: %s\n' \
   1087 			    "${file}"
   1088 			cp "${EXTRACTDIR}${file}" .
   1089 			if "${Hg:-false}"
   1090 			then
   1091 				DOIT hg add "${file}"
   1092 			else
   1093 				DOIT cvs -d "${CVSROOT}" add \
   1094 				    -m "Added by ${NEWVER}" "${file}"
   1095 			fi
   1096 			continue
   1097 		fi
   1098 
   1099 		cmp -s "${EXTRACTDIR}/${file}" "${file}" && continue
   1100 
   1101 		if "${auto}"
   1102 		then
   1103 			cp -p "${EXTRACTDIR}/${file}" "${file}"
   1104 			continue
   1105 		fi
   1106 
   1107 		printf 'Differences in "%s" (- existing + updated)\n\n' \
   1108 		    "${file}"
   1109 		diff -u "${file}" "${EXTRACTDIR}/${file}" | more
   1110 		read -rp "Update ${file} to new version? (y/n/all)" reply
   1111 		case "${reply}" in
   1112 		([Nn]*) continue;;
   1113 		([Yy]*|[Aa]|[Aa]ll) ;;
   1114 		([Qq]*)	exit 1;;
   1115 		(*)	printf 'Unexpected reply, abort.\n'
   1116 			exit 1;;
   1117 		esac
   1118 
   1119 		cp -p "${EXTRACTDIR}/${file}" "${file}"
   1120 
   1121 		case "${reply}" in
   1122 		([Aa]*)	auto=true;;
   1123 		esac
   1124 	done
   1125 
   1126 	set -- *
   1127 	LIST=
   1128 	for file	# nb: continue if there are no files, $1='*'
   1129 	do
   1130 		if test "${file}" = TZDATA_VERSION	||
   1131 		   test "${file}" = CVS			||
   1132 		   test -e "${EXTRACTDIR}/${file}"
   1133 		then
   1134 			continue
   1135 		fi
   1136 		printf 'File "%s" no longer exists in %s\n' \
   1137 			"${file}" "${NEWVER}"
   1138 		LIST="${LIST} '${file}'"
   1139 	done
   1140 
   1141 	eval set -- ${LIST}
   1142 	while :
   1143 	do
   1144 		case $# in
   1145 		(0)	break;;
   1146 		(1)	M='this file';;
   1147 		(*)	M='these files';;
   1148 		esac
   1149 
   1150 		read -rp "OK to remove $M from the repository? " reply
   1151 		case "${reply}" in
   1152 		([Yy]*)	;;
   1153 		([Nn]*)	break;;
   1154 		(*)	exit 1;;
   1155 		esac
   1156 
   1157 		if "${Hg:-false}"
   1158 		then
   1159 			DOIT hg remove "$@" || continue
   1160 		else
   1161 			rm -f "$@"
   1162 			DOIT cvs -d "${CVSROOT}" remove "${@}" || continue
   1163 		fi
   1164 		break
   1165 	done
   1166 
   1167 	touch "${FILECONTENTCHECKDONE}" || exit 1
   1168 
   1169 	exit "${exit}"
   1170 )
   1171 
   1172 cvscommitmerge()
   1173 {
   1174 	cd -P "${TZDISTDIR}" || exit 1
   1175 	if grep -l '^[<=>][<=>][<=>]' *
   1176 	then
   1177 		cat >&2 <<EOF
   1178 There still appear to be conflicts in the files listed above.
   1179 Not performing cvs commit.
   1180 EOF
   1181 		return 1
   1182 	fi
   1183 	if [ -e "${COMMITMERGEDONEFILE}" ]; then
   1184 		cat >&2 <<EOF
   1185 The CVS commit (of the merge result) has already been performed.
   1186 EOF
   1187 		return 0
   1188 	fi
   1189 	DOIT cvs -d "${CVSROOT}" commit -m "Merge tzdata${NEWVER}" &&
   1190 		touch "${COMMITMERGEDONEFILE}"
   1191 }
   1192 
   1193 hgcommitmerge()
   1194 {
   1195 	cd -P "${TZDISTDIR}" || exit 1
   1196 
   1197 	if ! hg resolve -l
   1198 	then
   1199 		cat >&2 <<EOF
   1200 There still appear to be conflicts in the files listed above.
   1201 Not performing hg commit.   Resolve conflicts first.
   1202 EOF
   1203 		return 1
   1204 	fi
   1205 	if [ -e "${COMMITMERGEDONEFILE}" ]; then
   1206 		cat >&2 <<EOF
   1207 The hg commit (of the merge result) has already been performed.
   1208 EOF
   1209 		return 0
   1210 	fi
   1211 	DOIT hg commit -m "Merge tzdata${NEWVER}" &&
   1212 		touch "${COMMITMERGEDONEFILE}"
   1213 }
   1214 
   1215 setlistupdate()
   1216 {
   1217 	if [ -s "${WORKDIR}"/addedzones ] ||
   1218 	   [ -s "${WORKDIR}"/removedzones ]
   1219 	then	(
   1220 			# Do all the preparatory work first, so
   1221 			# when we get to manipulating the sets list file
   1222 			# it all happens quickly...
   1223 
   1224 			while read file
   1225 			do
   1226 				printf '\\!zoneinfo/%s!{ %s ; %s ; }\n'     \
   1227 					"${file}"			    \
   1228 					's/sys-share	/obsolete	/'  \
   1229 					's/	share$/	obsolete/'
   1230 			done < "${WORKDIR}"/removedzones > "${WORKDIR}/sedcmds"
   1231 
   1232 			while read file
   1233 			do
   1234 				P=./usr/share/zoneinfo/"${file}"
   1235 				T2='		'
   1236 				case "$(( 48 - ${#P} ))" in
   1237 				(-*|0)	T2='	'	T='	'	 ;;
   1238 				([12345678])		T='	'	 ;;
   1239 				(9|1[0123456])		T='		';;
   1240 				(1[789]|2[01234]) T='			';;
   1241 				(2[5-9]|3[012])
   1242 					T='				';;
   1243 				# the following cases can't happen,
   1244 				# but for completeness...
   1245 				(3[3-9])
   1246 				    T='					';;
   1247 				(*)  T='				'
   1248 				    T="${T}		"		;;
   1249 				esac
   1250 
   1251 				if [ -d "${WORKDIR}/newzones/${file}" ]
   1252 				then
   1253 					printf '%s%sbase-sys-share\n' \
   1254 						"${P}" "${T}"
   1255 					continue
   1256 				fi
   1257 
   1258 				printf '%s%sbase-sys-share%sshare\n' \
   1259 					"${P}" "${T}" "${T2}"
   1260 
   1261 				# Deal with possibility that a new file
   1262 				# might have previously existed, and then
   1263 				# been deleted - marked obsolete
   1264 				printf '\\!^%s	.*obsolete!d\n' "${P}" \
   1265 					>> "${WORKDIR}/sedcmds"
   1266 
   1267 			done < "${WORKDIR}"/addedzones > "${WORKDIR}/setadded"
   1268 
   1269 			printf '$r %s\n' "${WORKDIR}/setadded" \
   1270 				>> "${WORKDIR}/sedcmds"
   1271 
   1272 			if ! [ -s "${WORKDIR}/sedcmds" ]	# impossible?
   1273 			then
   1274 				exit 0
   1275 			fi
   1276 
   1277 			MSG=$( 
   1278 				printf 'tzdata update to %s\n' "$NEWVER"
   1279 				if [ -s "${WORKDIR}"/addedzones ]
   1280 				then
   1281 					printf 'Added zoneinfo files:\n'
   1282 					sed 's/^/	/'	\
   1283 						< "${WORKDIR}"/addedzones
   1284 				fi
   1285 				if [ -s "${WORKDIR}"/removedzones ]
   1286 				then
   1287 					printf  'Removed zoneinfo files:\n'
   1288 					sed 's/^/	/'	\
   1289 						< "${WORKDIR}"/removedzones
   1290 				fi
   1291 				printf '\nX'
   1292 			)
   1293 			MSG=${MSG%X}
   1294 
   1295 			# Now is where the changes start happening...
   1296 
   1297 			cd -P "${NETBSDSRCDIR}/distrib/sets" || exit 1
   1298 			cd -P lists/base || exit 2
   1299 			"${Hg:-false}" ||
   1300 			    cvs -d "${CVSROOT}" -q update -A mi || exit 3
   1301 			cp -p mi mi.unsorted || exit 4
   1302 			sh ../../sort-list mi || exit 5
   1303 			cmp -s mi mi.unsorted || {
   1304 				if "${Hg:-false}"
   1305 				then
   1306 					DOIT hg commit -m 'Sort (NFCI)' mi ||
   1307 						exit 6
   1308 				else
   1309 					DOIT cvs -d "${CVSROOT}" -q commit \
   1310 						-m 'Sort (NFCI)' mi || exit 6
   1311 				fi
   1312 			}
   1313 			rm -f mi.unsorted
   1314 			sed -f "${WORKDIR}/sedcmds" < mi > mi.new || exit 7
   1315 			! test -s mi.new || cmp -s mi mi.new && {
   1316 				printf 'Failed to make changes to set lists'
   1317 				exit 8
   1318 			}
   1319 			mv mi.new mi || exit 9
   1320 			sh ../../sort-list mi || exit 10
   1321 			if "${Hg:-false}"
   1322 			then
   1323 				DOIT hg commit -m "${MSG}" mi || exit 11
   1324 			else
   1325 				DOIT cvs -d "${CVSROOT}" commit -m "${MSG}" mi||
   1326 					exit 11
   1327 			fi
   1328 			printf 'Sets list successfully updated'
   1329 			exit 0
   1330 		) || {
   1331 			printf '%s: %d\n%s %s\n' 'Sets list update failed' "$?"\
   1332 				'Update the sets list' \
   1333 				'(src/distrib/sets/lists/base/mi) manually'
   1334 			if [ -s "${WORKDIR}"/removedzones ]
   1335 			then
   1336 				printf 'Removed Zones:'
   1337 				sed 's/^/	/' < "${WORKDIR}"/removedzones
   1338 			fi
   1339 			if [ -s "${WORKDIR}"/addedzones ]
   1340 			then
   1341 				printf 'Added Zones:'
   1342 				sed 's/^/	/' < "${WORKDIR}"/addedzones
   1343 			fi
   1344 		}
   1345 	fi
   1346 }
   1347 
   1348 docupdate()
   1349 {
   1350 	cd -P "${TZBASE}"
   1351 
   1352 	MSG='CHANGES and 3RDPARTY must be updated manually'
   1353 
   1354 	cd -P "${NETBSDSRCDIR:-/nowhere/at/all}" 2>/dev/null || {
   1355 		printf >&2 '%s\n' "${MSG}"
   1356 		return 1
   1357 	}
   1358 
   1359 	if ! [ -d doc ]
   1360 	then
   1361 		printf '%s\n' "No doc directory in ${PWD}" "${MSG}"
   1362 		return 1
   1363 	fi
   1364 	cd -P doc
   1365 	if ! "${Hg:-false}" && ! [ -d CVS ]
   1366 	then
   1367 		printf '%s\n' "No CVS directory in ${PWD}" "${MSG}"
   1368 		return 1
   1369 	fi
   1370 
   1371 	if ! "${Hg:-false}"
   1372 	then
   1373 	    printf 'Making sure doc/CHANGES and doc/3RDPARTY are up to date\n'
   1374 	    cvs -d "${CVSROOT}" -q update -A CHANGES 3RDPARTY
   1375 	fi
   1376 
   1377 	local commit=false
   1378 
   1379 	if grep "tzdata: Updated to ${NEWVER}" CHANGES
   1380 	then
   1381 		printf 'doc/CHANGES has already been updated\n'
   1382 	else
   1383 	    {
   1384 		printf '\ttzdata: Updated to %s' "${NEWVER}"
   1385 		"${GLOBAL}" && printf ' (using %sgtz)' "${NEWVER}"
   1386 		date -u +" [${LOGNAME} %Y%m%d]"
   1387 		commit=true
   1388 	    } >> CHANGES
   1389 	fi
   1390 
   1391 	if grep "^Version:	tzcode[^ 	]* / tzdata${NEWVER}" 3RDPARTY
   1392 	then
   1393 		printf 'doc/3RDPARTY has already been updated\n'
   1394 	else
   1395 		# These just make the sed script below manageable.
   1396 		C=tzcode D=tzdata V=${NEWVER} T=${CVSNEWTAG}
   1397 
   1398 		G=$V
   1399 		"${GLOBAL}" && G=${G}gtz
   1400 
   1401 		sed < 3RDPARTY > 3RDPARTY.new				\
   1402 		    -e '1,/^Package:	tz$/{p;d;}'			\
   1403 		    -e '/^Notes:/,${p;d;}'				\
   1404 		    -e "/^Version:	/s, / ${D}.*, / ${D}${G},"	\
   1405 		    -e "/^Current Vers:/s,tz.*$,${C}${V} / ${D}${V}," && {
   1406 
   1407 			mv 3RDPARTY.new 3RDPARTY
   1408 			commit=true
   1409 		}
   1410 	fi
   1411 
   1412 	if "${commit}"
   1413 	then
   1414 		printf 'These are the changes that will be made:\n\n'
   1415 
   1416 		if "${Hg:-false}"
   1417 		then
   1418 			hg diff CHANGES 3RDPARTY || :  # Ugh -e!
   1419 		else
   1420 			cvs -d "${CVSROOT}" diff -u CHANGES 3RDPARTY || :
   1421 		fi
   1422 
   1423 		read -p $'\nAre those OK? [y/n] ' reply
   1424 
   1425 		
   1426 		if "${GLOBAL}"
   1427 		then
   1428 			MSG="${MSG} (via ${NEWVER}gtz)"
   1429 		fi
   1430 		case "${reply}" in
   1431 		([Yy]*)
   1432 			if "${Hg:-false}"
   1433 			then
   1434 				DOIT hg commit -m "${MSG}" CHANGES 3RDPARTY
   1435 			else
   1436 				DOIT cvs -d "${CVSROOT}" commit -m "${MSG}" \
   1437 					CHANGES 3RDPARTY
   1438 			fi
   1439 			;;
   1440 		(*)	printf '%s\n' \
   1441 			   'OK, correct and commit those files manually' \
   1442 			   'The changes shown above have been made to the files'
   1443 			;;
   1444 		esac
   1445 	fi
   1446 	return 0
   1447 }
   1448 
   1449 extra()
   1450 {
   1451 	cat <<EOF
   1452 Also do the following:
   1453  * Submit pullup requests for all active release branches.
   1454  * rm -rf ${WORK_PFX}  (optional)
   1455  * Verify that
   1456   ${UPDATE_FROM}
   1457  * no longer exists.
   1458 
   1459 And if necessary (if not already done by the script):
   1460  * Edit and commit  src/distrib/sets/lists/base/mi
   1461  * Edit and commit  src/doc/3RDPARTY
   1462  * Edit and commit  src/doc/CHANGES
   1463 EOF
   1464 
   1465 	rm -f "${UPDATE_FROM}"	# just to try to make sure, as we're done!
   1466 }
   1467 
   1468 main()
   1469 {
   1470 	set -e
   1471 	validate_pwd
   1472 
   1473 	find_netbsdsrc
   1474 	findcvsroot
   1475 	hginit
   1476 
   1477 	if ! [ -f "${UPDATE_FROM}" ]
   1478 	then
   1479 		cvsupdate || fail 'working directory (dist) update failed:'" $?"
   1480 	fi
   1481 
   1482 	mkworkpfx
   1483 
   1484 	argparse "$@"
   1485 
   1486 	setup_versions
   1487 	mkworkdir
   1488 	fetch
   1489 	"${GLOBAL}" || checksig
   1490 	extract
   1491 
   1492 	zonelists
   1493 
   1494 	addnews
   1495 	trimnews
   1496 	mkimportmsg
   1497 	editimportmsg
   1498 
   1499 	if "${Hg:-false}"
   1500 	then
   1501 		hgimport
   1502 		hgmerge
   1503 	else
   1504 		cvsimport
   1505 		cvsmerge
   1506 		resolveconflicts
   1507 	fi
   1508 	checkfilecontents
   1509 	if "${Hg:-false}"
   1510 	then
   1511 		hgcommitmerge
   1512 	else
   1513 		cvscommitmerge
   1514 	fi
   1515 	updatedzones
   1516 
   1517 	setlistupdate
   1518 
   1519 	rm -f "${UPDATE_FROM}"
   1520 	rm -fr	"${WORK_PFX}"/oldzones \
   1521 		"${WORK_PFX}"/newzones \
   1522 		"${WORK_PFX}"/updzones
   1523 
   1524 	docupdate
   1525 
   1526 	if "${hg:-false}"
   1527 	then
   1528 		DOIT hg push	# Publish it all!
   1529 	fi
   1530 
   1531 	extra
   1532 }
   1533 
   1534 main "$@"
   1535