xref: /netbsd-src/external/public-domain/tz/tzdata2netbsd (revision 486e07f5ed5caf8bab0db7086665e5109d9ce131)
1# $NetBSD: tzdata2netbsd,v 1.16 2024/02/05 21:52:38 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@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
19VERS_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
47DIST_HOST=ftp.iana.org
48DIST_PATH=tz
49DIST_FILES=releases
50
51GTZURL=https://github.com/JodaOrg/global-tz/releases/download
52
53EDITOR=${EDITOR:-vi}
54
55TZBASE=$(pwd)	|| fail 'Cannot find myself ($PWD)'
56cd -P "$TZBASE"	|| fail "Cannot return home: ${TZBASE}"
57
58WORK_PFX=${TZBASE}/update-work
59UPDATE_FROM=${WORK_PFX}/updating.from.version
60
61usage()
62{
63	printf >&2 '%s\n'						\
64		"Usage: $0 [new-version-id [old-version-id]]"		\
65		"     where a version-id is of the form YYYYx (eg: 2018c)" \
66		"     or '' for new-version-id (to specify only the old)"  \
67		"     and where new-version-id can have =fetch-version-id" \
68		"     appended to specify fetching that version instead" \
69		"     where the 'fetch-version-id' can be omitted if it" \
70		"     is \${new-version-id}gtz  - and simply using '=' means" \
71		"     to work out the new-version-id but then use the gtz fork"
72
73	exit 2
74}
75
76fail()
77{
78	local IFS=' '
79
80	printf >&2 '%s\n'	"Error detected:" "   $*" "Aborting."
81	exit 1
82}
83
84valid_vers()
85{
86	case "$2" in
87	# The IANA (Eggert) standard version names
88	${VERS_PATTERN} | ${VERS_PATTERN}[a-z] )
89		;;
90	# The alternate (more rational) fork "global timezone" version
91	${VERS_PATTERN}gtz | ${VERS_PATTERN}[a-z]gtz )
92		;;
93	*)	printf >&2 '%s: %s\n' \
94		    "Bad form for $1 version specifier '$2'" \
95		    "should (usually) be 'YYYYx'"
96		return 1
97		;;
98	esac
99	return 0
100}
101
102get_curvers()
103{
104	local LF=''
105	local LIST=iana-listing
106	local SED_SCRIPT='
107		/tzdata-latest.*-> /{
108			s/^.*-> //
109			s/\..*$//
110			s;^releases/tzdata;;p
111			q
112		}
113		d'
114
115	test -d "${WORK_PFX}" &&
116		test -s "${WORK_PFX}/${LIST}" &&
117		test "${WORK_PFX}/${LIST}" -nt dist/CVS &&
118		LF=$(find "${WORK_PFX}" -name "${LIST}" -mtime -1 -print) &&
119		test -n "${LF}" &&
120		NEWVER=$(sed -n < "${LF}" "${SED_SCRIPT}") &&
121		valid_vers new "${NEWVER}"				||
122
123	ftp >/dev/null 2>&1 -ia "${DIST_HOST}" <<- EOF &&
124					dir ${DIST_PATH} ${WORK_PFX}/${LIST}
125					quit
126					EOF
127		test -s "${WORK_PFX}/${LIST}" &&
128		NEWVER=$(sed -n < "${WORK_PFX}/${LIST}" "${SED_SCRIPT}") &&
129		valid_vers new "${NEWVER}"				||
130
131	{
132		rm -f "${WORK_PFX}/${LIST}"
133		fail "Cannot fetch current tzdata version from ${DIST_HOST}"
134	}
135
136	printf '%s\n' "Updating from ${OLDVER} to ${NEWVER}"
137}
138
139argparse()
140{
141	local OVF OV NV OVy OVs NVy NVs
142
143	if OVF=$(find "${WORK_PFX}" -name "${UPDATE_FROM##*/}" -mtime +2 -print)
144	then
145		# delete anything old
146		test -n "${OVF}" && rm -f "${OVF}"
147	fi
148
149	case "$#" in
150	0|1)
151		# once we have obtained OLDVER once, never guess it again.
152		if  [ -f "${UPDATE_FROM}" ]
153		then
154			OLDVER=$(cat "${UPDATE_FROM}")
155		elif [ -f dist/TZDATA_VERSION ]
156		then
157			OLDVER=$(cat dist/TZDATA_VERSION)
158		elif [ -f dist/version ]
159		then
160			OLDVER=$(cat dist/version)
161		fi
162		OLDVER=${OLDVER#tzdata}		# TZDATA_VERS is tzdata-nnnnX
163		OLDVER=${OLDVER#-}		# but the '-' is optional
164		OLDVERGTZ=${OLDVER}		# This would have been the cvs tag
165		OLDVER=${OLDVER%gtz}		# want the base version elsewhere
166
167		if [ -z "${OLDVER}" ]
168		then
169			printf >&2 '%s\n'  \
170			    'Cannot determine current installed version'  \
171			    'Specify it on the command line.'  \
172			    ''
173			usage
174		fi
175
176		valid_vers old "${OLDVER}" ||
177			fail "Calculated bad OLDVER, give as 2nd arg"
178		;;
179
180	2)	valid_vers old "$2" && OLDVER="$2" || usage
181		;;
182
183	*)	usage
184		;;
185	esac
186
187	GLOBAL=false
188	case "$#:$1" in
189	0: | 1: | 2: )
190		;;
191	1:=|2:=)
192		GLOBAL=true;;
193	1:=?*|2:=?*)
194		valid_vers fetch "${1#=}" && FETCHVER="${1#=}" || usage
195		;;
196	1:*=?*|2:*=?*)
197		set -- "{$1%=*}" "${1#*=}"
198		valid_vers fetch "$2" && FETCHVER="$2" || usage
199		valid_vers new "$1" && NEWVER="$1" || usage
200		;;
201	1:?*|2:?*)
202		valid_vers new "$1" && NEWVER="$1" || usage
203		;;
204	*)	usage
205		;;
206	esac
207
208	test -z "${NEWVER}" && get_curvers
209
210	if [ -z "${FETCHVER}" ]
211	then
212		if $GLOBAL
213		then
214			FETCHVER=${NEWVER}gtz
215		else
216			FETCHVER=${NEWVER}
217		fi
218	fi
219
220	case "${FETCHVER}" in
221	*gtz)	GLOBAL=true;;
222	*)	GLOBAL=false;;
223	esac
224
225	if [ "${NEWVER}" = "${OLDVER}" ]
226	then
227		printf 'New and old versions both %s; nothing to do\n' \
228		     "${NEWVER}"
229		exit 0
230	fi
231
232	printf '%s\n' "${OLDVER}" > "${UPDATE_FROM}" ||
233	    fail "Unable to preserve old version ${OLDVER} in ${UPDATE_FROM}"
234
235	# Do version upgrade test using base version names, ignoring
236	# the "gtz" in the "global timezone" versions, so we can
237	# switch back and forth between use of those as circumstances change
238	OV=${OLDVER%gtz}
239	NV=${NEWVER%gtz}
240
241	OVy=${OV%%[!0-9]*}
242	OVs=${OV#${OVy}}
243	NVy=${NV%%[!0-9]*}
244	NVs=${NV#${NVy}}
245
246	# To get all the permutations correct, we need to separate
247	# the year and suffix parts of the version IDs (done just above)
248	# and then compare them separately.  The suffix is only relevant
249	# to the result when the years are the same.
250
251	# We compare the length of the suffix separately to the suffix
252	# itself, a multi-char suffix has never happened (and is never
253	# likely to) - but in the event that prediction is wrong, we don't
254	# know (yet) what is to come after 'z' - it might be 'za' 'zb'
255	# ... to 'zz" then 'zza' ... or it might be 'aa' 'ab' ... 'az' 'ba'...
256	# we need to handle both possibilities.  Two things stand out from
257	# those: 1. a longer suffix is always going to be for a newer version
258	# than a shorter one;  2. equal length suffixes can be compared as
259	# strings
260
261	if [ "${OVy}" -gt "${NVy}" ]			|| {
262		[ "${OVy}" -eq "${NVy}" ]	&& {
263			[ "${#OVs}" -gt "${#NVs}" ]		||
264			LC_COLLATE=C [ "${OVs}" '>' "${NVs}" ]
265		}
266        } then
267		local reply
268
269		printf '%s\n' "Update would revert ${OLDVER} to ${NEWVER}"
270		read -p "Is reversion intended? " reply
271		case "${reply}" in
272		[Yy]*)	;;
273		*)	printf '%s\n' OK. Aborted.
274			rm -f "${UPDATE_FROM}"
275			exit 1
276			;;
277		esac
278	fi
279
280	return 0
281}
282
283setup_versions()
284{
285	# Uppercase variants of OLDVER and NEWVER
286	OLDVER_UC="$( echo "${OLDVERGTZ}" | tr '[a-z]' '[A-Z]' )"
287	NEWVER_UC="$( echo "${NEWVER}" | tr '[a-z]' '[A-Z]' )"
288
289	# Tags for use with version control systems
290	CVSOLDTAG="TZDATA${OLDVER_UC}"
291	CVSNEWTAG="TZDATA${NEWVER_UC}"
292	CVSBRANCHTAG="TZDATA"
293	GITHUBTAG="${NEWVER}"
294
295	if $GLOBAL && [ "${CVSNEWTAG%GTZ}" = "${CVSNEWTAG}" ]
296	then
297		CVSNEWTAG=${CVSNEWTAG}GTZ
298	fi
299
300	# URLs for fetching distribution files, etc.
301	if $GLOBAL
302	then
303		DISTURL=${GTZURL}/${FETCHVER}/tzdata${FETCHVER}.tar.gz
304		unset SIGURL
305	else
306		DISTURL="ftp://${DIST_HOST}/${DIST_PATH}/${DIST_FILES}"
307		DISTURL="${DISTURL}/tzdata${NEWVER}.tar.gz"
308		SIGURL="${DISTURL}.asc"
309	fi
310	NEWSURL="https://github.com/eggert/tz/raw/${GITHUBTAG}/NEWS"
311
312	# Directories
313	REPODIR="src/external/public-domain/tz/dist"
314				# relative to the NetBSD CVS repo
315	TZDISTDIR="$(pwd)/dist" # should be .../external/public-domain/tz/dist
316	WORKDIR="${WORK_PFX}/${NEWVER}"
317	EXTRACTDIR="${WORKDIR}/extract"
318
319	# Files in the work directory
320	DISTFILE="${WORKDIR}/${DISTURL##*/}"
321	SIGFILE="${DISTFILE}.asc"
322	PGPVERIFYLOG="${WORKDIR}/pgpverify.log"
323	NEWSFILE="${WORKDIR}/NEWS"
324	NEWSTRIMFILE="${WORKDIR}/NEWS.trimmed"
325	IMPORTMSGFILE="${WORKDIR}/import.msg"
326	IMPORTDONEFILE="${WORKDIR}/import.done"
327	MERGSMSGFILE="${WORKDIR}/merge.msg"
328	MERGEDONEFILE="${WORKDIR}/merge.done"
329	COMMITMERGEDONEFILE="${WORKDIR}/commitmerge.done"
330
331	printf '%s\n' "${CVSOLDTAG}"  > "${WORK_PFX}/updating_from"
332}
333
334DOIT()
335{
336	local really_do_it=false
337	local reply
338
339	echo "In directory $(pwd)"
340	echo "ABOUT TO DO:" "$(shell_quote "$@")"
341	read -p "Really do it? [yes/no/quit] " reply
342	case "${reply}" in
343	[yY]*)	really_do_it=true ;;
344	[nN]*)	really_do_it=false ;;
345	[qQ]*)
346		echo "Aborting"
347		return 1
348		;;
349	*)	echo "Huh?"
350		return 1
351		;;
352	esac
353	if $really_do_it; then
354		echo "REALLY DOING IT NOW..."
355		"$@"
356	else
357		echo "NOT REALLY DOING THE ABOVE COMMAND"
358	fi
359}
360
361# Quote args to make them safe in the shell.
362# Usage: quotedlist="$(shell_quote args...)"
363#
364# After building up a quoted list, use it by evaling it inside
365# double quotes, like this:
366#    eval "set -- $quotedlist"
367# or like this:
368#    eval "\$command $quotedlist \$filename"
369#
370shell_quote()
371(
372	local result=''
373	local arg qarg
374	LC_COLLATE=C ; export LC_COLLATE # so [a-zA-Z0-9] works in ASCII
375	for arg in "$@" ; do
376		case "${arg}" in
377		'')
378			qarg="''"
379			;;
380		*[!-./a-zA-Z0-9]*)
381			# Convert each embedded ' to '\'',
382			# then insert ' at the beginning of the first line,
383			# and append ' at the end of the last line.
384			# Finally, elide unnecessary '' pairs at the
385			# beginning and end of the result and as part of
386			# '\'''\'' sequences that result from multiple
387			# adjacent quotes in the input.
388			qarg="$(printf '%s\n' "$arg" | \
389			    ${SED:-sed} -e "s/'/'\\\\''/g" \
390				-e "1s/^/'/" -e "\$s/\$/'/" \
391				-e "1s/^''//" -e "\$s/''\$//" \
392				-e "s/'''/'/g"
393				)"
394			;;
395		*)
396			# Arg is not the empty string, and does not contain
397			# any unsafe characters.  Leave it unchanged for
398			# readability.
399			qarg="${arg}"
400			;;
401		esac
402		result="${result}${result:+ }${qarg}"
403	done
404	printf '%s\n' "$result"
405)
406
407validate_pwd()
408{
409	local P="$(pwd)" || return 1
410
411	test -d "${P}" &&
412	    test -d "${P}/CVS" &&
413	    test -d "${P}/dist" &&
414	    test -f "${P}/dist/zone.tab" &&
415	    test -f "${P}/tzdata2netbsd" || {
416		printf >&2 '%s\n' "Please change to the correct directory"
417		return 1
418	}
419
420	test -f "${P}/CVS/Tag" && {
421
422		# Here (for local use only) if needed for private branch work
423		# insert tests for the value of $(cat "${P}/CVS/Tag") and
424		# allow your private branch tag to pass. Eg:
425
426		#	case "$(cat "${P}/CVS/Tag")" in
427		#	my-branch-name)	return 0;;
428		#	esac
429
430		# Do not commit a version of this script modified that way,
431		# (not even on the private branch) - keep it as a local
432		# modified file.  (This script will not commit it.)
433
434		printf >&2 '%s\n' \
435		    "This script should be run in a checkout of HEAD only"
436		return 1
437	}
438
439	return 0
440}
441
442findcvsroot()
443{
444	[ -n "${CVSROOT}" ] && return 0
445	CVSROOT="$( cat ./CVS/Root )"
446	[ -n "${CVSROOT}" ] && return 0
447	echo >&2 "Failed to set CVSROOT value"
448	return 1
449}
450
451mkworkpfx()
452{
453	mkdir -p "${WORK_PFX}" || fail "Unable to make missing ${WORK_PFX}"
454}
455mkworkdir()
456{
457	mkdir -p "${WORKDIR}" || fail "Unable to make missing ${WORKDIR}"
458}
459
460cvsupdate()
461(
462	# Make sure our working directory is up to date (and HEAD)
463	cd "${TZBASE}/dist"			|| exit 1
464	cvs -d "${CVSROOT}" -q update -AdP	|| exit 2
465)
466
467fetch()
468{
469	[ -f "${DISTFILE}" ] || ftp -o "${DISTFILE}" "${DISTURL}" ||
470		fail "fetch of ${DISTFILE} failed"
471
472	if [ -n "${SIGURL}" ]
473	then
474		[ -f "${SIGFILE}" ] || ftp -o "${SIGFILE}" "${SIGURL}" ||
475			fail "fetch of ${SIGFILE} failed"
476	fi
477
478	[ -f "${NEWSFILE}" ] || ftp -o "${NEWSFILE}" "${NEWSURL}" ||
479		fail "fetch of ${NEWSFILE} failed"
480}
481
482checksig()
483{
484	{ gpg --verify "${SIGFILE}" "${DISTFILE}"
485	  echo gpg exit status $?
486	} 2>&1 | tee "${PGPVERIFYLOG}"
487
488	# The output should contain lines that match all the following regexps
489	#
490	while read line; do
491		if ! grep -E -q -e "^${line}\$" "${PGPVERIFYLOG}"; then
492			echo >&2 "Failed to verify signature: ${line}"
493			return 1
494		fi
495	done <<'EOF'
496gpg: Signature made .* using RSA key ID (62AA7E34|44AD418C)
497gpg: Good signature from "Paul Eggert <eggert@cs.ucla.edu>"
498gpg exit status 0
499EOF
500}
501
502extract()
503{
504	[ -f "${EXTRACTDIR}/zone.tab" ] && return
505	mkdir -p "${EXTRACTDIR}"
506	tar -z -xf "${DISTFILE}" -C "${EXTRACTDIR}"
507}
508
509rflist()
510(
511	 test "${1}" && cd "${1}" && find * -print | sort
512)
513
514zonelists()
515{
516	[ -f "${WORKDIR}"/addedzones ] && return
517
518	rm -fr "${WORK_PFX}"/oldzones "${WORK_PFX}"/newzones
519
520	(
521		cd "${TZBASE}/share/zoneinfo" || exit 1
522
523		make	TOOL_ZIC=/usr/sbin/zic			\
524			DESTDIR=				\
525			TZBUILDDIR="${WORK_PFX}"/oldzones	\
526			TZDIR="${WORK_PFX}"/oldzones		\
527			TZDISTDIR="${TZBASE}"/dist		\
528				posix_only >/dev/null 2>&1
529
530	) || fail 'Unable to compile old zone data files'
531
532	(
533		cd "${TZBASE}/share/zoneinfo" || exit 1
534
535		make	TOOL_ZIC=/usr/sbin/zic			\
536			DESTDIR=				\
537			TZBUILDDIR="${WORK_PFX}"/newzones	\
538			TZDIR="${WORK_PFX}"/newzones		\
539			TZDISTDIR="${EXTRACTDIR}"		\
540				posix_only >/dev/null 2>&1
541
542	) || fail 'Unable to compile new zone data files'
543
544	rflist "${WORK_PFX}"/oldzones > "${WORKDIR}"/oldzones
545	rflist "${WORK_PFX}"/newzones > "${WORKDIR}"/newzones
546
547	if cmp -s "${WORKDIR}"/oldzones "${WORKDIR}"/newzones >/dev/null
548	then
549		printf "No zones added or deleted by this update"
550		> "${WORKDIR}"/removedzones
551		> "${WORKDIR}"/addedzones
552		return 0
553	fi
554
555	comm -23 "${WORKDIR}"/oldzones "${WORKDIR}"/newzones \
556		> "${WORKDIR}"/removedzones
557
558	test "${REMOVEOK:-no}" != yes && test -s "${WORKDIR}"/removedzones && {
559		printf '%s\n' 'This update wants to remove these zone files:' ''
560		sed 's/^/	/' < "${WORKDIR}"/removedzones
561		printf '%s\n' '' 'It probably should not' ''
562
563		printf 'If this is OK, rerun this script with REMOVEOK=yes\n'
564		printf 'Otherwise, fix the problem, and then rerun the script\n'
565		exit 1
566	}
567
568	comm -13 "${WORKDIR}"/oldzones "${WORKDIR}"/newzones \
569		> "${WORKDIR}"/addedzones
570
571	test -s "${WORKDIR}"/addedzones && {
572		printf '%s\n' '' '********************************* NOTE:' \
573			'********************************* New Zones Created' \
574			''
575		sed 's/^/	/' < "${WORKDIR}"/addedzones
576		printf '%s\n' '' '*********************************' ''
577	}
578
579	return 0
580}
581
582updatedzones()
583{
584	[ -f "${WORKDIR}"/.zonesOK ] && return
585
586	rm -fr "${WORK_PFX}"/updzones
587
588	(
589		cd "${TZBASE}/share/zoneinfo" || exit 1
590
591		make	TOOL_ZIC=/usr/sbin/zic			\
592			DESTDIR=				\
593			TZBUILDDIR="${WORK_PFX}"/updzones	\
594			TZDIR="${WORK_PFX}"/updzones		\
595			TZDISTDIR="${TZBASE}"/dist		\
596				posix_only >/dev/null 2>&1
597
598	) || fail 'Unable to compile updated zone data.   HELP'
599
600	rflist "${WORK_PFX}"/updzones > "${WORKDIR}"/updzones
601
602	cmp -s "${WORKDIR}"/newzones "${WORKDIR}"/updzones || {
603
604		printf '%s\n' '' '*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*' \
605			'After cvs work, zones created are not as intended' \
606			'-------------------------------------------------' \
607			'Zones not created but should have been:'
608		comm -23 "${WORKDIR}"/newzones "${WORKDIR}"/updzones |
609			sed 's/^/	/'
610		printf '%s\n' \
611			'-------------------------------------------------' \
612			'Zones created that should not have been:'
613		comm -13 "${WORKDIR}"/newzones "${WORKDIR}"/updzones |
614			sed 's/^/	/'
615		prinrf '%s\n' \
616			'-------------------------------------------------'
617
618		fail 'cvs import/merge/update/commit botch'
619	}
620
621	> "${WORKDIR}"/.zonesOK
622}
623
624addnews()
625{
626	[ -f "${EXTRACTDIR}/NEWS" ] && return
627	cp -p "${NEWSFILE}" "${EXTRACTDIR}"/NEWS
628}
629
630# Find the relevant part of the NEWS file for all releases between
631# OLDVER and NEWVER, and save them to NEWSTRIMFILE.
632#
633trimnews()
634{
635	[ -s "${NEWSTRIMFILE}" ] && return
636	awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \
637	    '
638		BEGIN {inrange = 0}
639		/^Release [0-9]+[a-z]+ - .*/ {
640			# "Release <version> - <date>"
641			# Note: must handle transition from 2018z to 2018aa
642			# Assumptions: OLDVER and NEWVER have been sanitized,
643			# and format of NEWS file does not alter (and
644			# contains valid data)
645			inrange = ((length($2) > length(oldver) || \
646					$2 > oldver) && \
647				(length($2) < newver || $2 <= newver))
648		}
649		// { if (inrange) print; }
650	    ' \
651		<"${NEWSFILE}" >"${NEWSTRIMFILE}"
652
653	if $GLOBAL
654	then
655		printf '%s\n' "tzdata-${NEWVER}gtz"
656	else
657		printf '%s\n' "tzdata-${NEWVER}"
658	fi > ${TZDISTDIR}/TZDATA_VERSION
659}
660
661# Create IMPORTMSGFILE from NEWSTRIMFILE, by ignoring some sections,
662# keeping only the first sentence from paragraphs in other sections,
663# and changing the format.
664#
665# The result should be edited by hand before performing a cvs commit.
666# A message to that effect is inserted at the beginning of the file.
667#
668mkimportmsg()
669{
670	[ -s "${IMPORTMSGFILE}" ] && return
671	{ cat <<EOF
672EDIT ME: Edit this file and then delete the lines marked "EDIT ME".
673EDIT ME: This file will be used as a log message for the "cvs commit" that
674EDIT ME: imports tzdata${NEWVER}.  The initial contents of this file were
675EDIT ME: generated from ${NEWSFILE}.
676EDIT ME:
677EOF
678	awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \
679	    -v disturl="${DISTURL}" -v newsurl="${NEWSURL}" \
680	    '
681		BEGIN {
682			bullet = "  * ";
683			indent = "    ";
684			blankline = 0;
685			goodsection = 0;
686			havesentence = 0;
687			print "Import tzdata"newver" from "disturl;
688			#print "and NEWS file from "newsurl;
689		}
690		/^Release/ {
691			# "Release <version> - <date>"
692			ver = $2;
693			date = gensub(".* - ", "", 1, $0);
694			print "";
695			print "Summary of changes in tzdata"ver \
696				" ("date"):";
697		}
698		/^$/ { blankline = 1; havesentence = 0; }
699		/^  Changes / { goodsection = 0; }
700		/^  Changes to future timestamps/ { goodsection = 1; }
701		/^  Changes to past timestamps/ { goodsection = 1; }
702		/^  Changes to documentation/ || \
703		/^  Changes to commentary/ {
704			t = gensub("^ *", "", 1, $0);
705			t = gensub("\\.*$", ".", 1, t);
706			print bullet t;
707			goodsection = 0;
708		}
709		/^    .*/ && goodsection {
710			# In a paragraph in a "good" section.
711			# Ignore leading spaces, and ignore anything
712			# after the first sentence.
713			# First line of paragraph gets a bullet.
714			t = gensub("^ *", "", 1, $0);
715			t = gensub("\\. .*", ".", 1, t);
716			if (blankline) print bullet t;
717			else if (! havesentence) print indent t;
718			havesentence = (havesentence || (t ~ "\\.$"));
719		}
720		/./ { blankline = 0; }
721	    ' \
722		<"${NEWSTRIMFILE}"
723	} >"${IMPORTMSGFILE}"
724
725	if [ -s "${WORKDIR}"/addedzones ]
726	then
727		printf '%s\n' '' 'Zones added by this update:'
728		sed 's/^/	/' < "${WORKDIR}"/addedzones
729	fi >> "${IMPORTMSGFILE}"
730
731	if [ -s "${WORKDIR}"/removedzones ]
732	then
733		printf '%s\n' '' 'Zones removed by this update:'
734		sed 's/^/	/' < "${WORKDIR}"/removedzones
735	fi >> "${IMPORTMSGFILE}"
736
737}
738
739editimportmsg()
740{
741	if [ -s "${IMPORTMSGFILE}" ] && ! grep -q '^EDIT' "${IMPORTMSGFILE}"
742	then
743		return 0 # file has already been edited
744	fi
745	# Pass both IMPORTMSGFILE and NEWSFILE to the editor, so that the
746	# user can easily consult NEWSFILE while editing IMPORTMSGFILE.
747	${EDITOR} "${IMPORTMSGFILE}" "${NEWSFILE}"
748}
749
750cvsimport()
751{
752	if [ -e "${IMPORTDONEFILE}" ]; then
753		cat >&2 <<EOF
754The CVS import has already been performed.
755EOF
756		return 0
757	fi
758	if ! [ -s "${IMPORTMSGFILE}" ] || grep -q '^EDIT' "${IMPORTMSGFILE}"
759	then
760		cat >&2 <<EOF
761The message file ${IMPORTMSGFILE}
762has not been properly edited.
763Not performing cvs import.
764EOF
765		return 1
766	fi
767	( cd "${EXTRACTDIR}" &&
768	  DOIT cvs -d "${CVSROOT}" import -I ! -m "$(cat "${IMPORTMSGFILE}")" \
769		"${REPODIR}" "${CVSBRANCHTAG}" "${CVSNEWTAG}"
770	) && touch "${IMPORTDONEFILE}"
771}
772
773cvsmerge()
774{
775
776	cd "${TZDISTDIR}" || exit 1
777	if [ -e "${MERGEDONEFILE}" ]
778	then
779		cat >&2 <<EOF
780The CVS merge has already been performed.
781EOF
782		return 0
783	fi
784	DOIT cvs -d "${CVSROOT}" update -j"${CVSOLDTAG}" -j"${CVSNEWTAG}" &&
785		touch "${MERGEDONEFILE}"
786	printf '%s\n' ================================== \
787		'The following differences exist between the merge results' \
788		'and the imported files:' '================================='
789	diff -ur "${EXTRACTDIR}" .
790	printf '%s\n' ==================================
791}
792
793resolveconflicts()
794{
795	cd "${TZDISTDIR}" || exit 1
796	if grep -l '^[<=>][<=>][<=>]' *
797	then
798		cat <<EOF
799There appear to be conflicts in the files listed above.
800Resolve conflicts, then re-run this script.
801EOF
802		return 1
803	fi
804}
805
806cvscommitmerge()
807{
808	cd "${TZDISTDIR}" || exit 1
809	if grep -l '^[<=>][<=>][<=>]' *
810	then
811		cat >&2 <<EOF
812There still appear to be conflicts in the files listed above.
813Not performing cvs commit.
814EOF
815		return 1
816	fi
817	if [ -e "${COMMITMERGEDONEFILE}" ]; then
818		cat >&2 <<EOF
819The CVS commmit (of the merge result) has already been performed.
820EOF
821		return 0
822	fi
823	DOIT cvs -d "${CVSROOT}" commit -m "Merge tzdata${NEWVER}" &&
824		touch "${COMMITMERGEDONEFILE}"
825}
826
827setlistupdate()
828{
829	if [ -s "${WORKDIR}"/addedzones ] ||
830	   [ -s "${WORKDIR}"/removedzones ]
831	then	(
832			# Do all the preparatory work first, so
833			# when we get to manipulating the sets list file
834			# it all happens quickly...
835
836			while read file
837			do
838				printf '\\!zoneinfo/%s!{ %s ; %s ; }\n'     \
839					"${file}"			    \
840					's/sys-share	/obsolete	/'  \
841					's/	share$/	obsolete/'
842			done < "${WORKDIR}"/removedzones > "${WORKDIR}/sedcmds"
843
844			while read file
845			do
846				P=./usr/share/zoneinfo/"${file}"
847				T2='		'
848				case "$(( 48 - ${#P} ))" in
849				-*|0)	T2='	'	T='	'	 ;;
850				[12345678])		T='	'	 ;;
851				9|1[0123456])		T='		';;
852				1[789]|2[01234]) T='			';;
853				2[5-9]|3[012])
854					T='				';;
855				# the following cases can't happen,
856				# but for completeness...
857				3[3-9])
858				    T='					';;
859				*)  T='				'
860				    T="${T}		"		;;
861				esac
862
863				if [ -d "${WORKDIR}/newzones/${file}" ]
864				then
865					printf '%s%sbase-sys-share\n' \
866						"${P}" "${T}"
867					continue
868				fi
869
870				printf '%s%sbase-sys-share%sshare\n' \
871					"${P}" "${T}" "${T2}"
872
873				# Deal with possibility that a new file
874				# might have previously existed, and then
875				# been deleted - marked obsolete
876				printf '\\!^%s	.*obsolete!d\n' "${P}" \
877					>> "${WORKDIR}/sedcmds"
878
879			done < "${WORKDIR}"/addedzones > "${WORKDIR}/setadded"
880
881			printf '$r %s\n' "${WORKDIR}/setadded" \
882				>> "${WORKDIR}/sedcmds"
883
884			if ! [ -s "${WORKDIR}/sedcmds" ]	# impossible?
885			then
886				exit 0
887			fi
888
889			MSG=$(
890				printf 'tzdata update to %s\n' "$NEWVER"
891				if [ -s "${WORKDIR}"/addedzones ]
892				then
893					printf 'Added zoneinfo files:\n'
894					sed 's/^/	/'	\
895						< "${WORKDIR}"/addedzones
896				fi
897				if [ -s "${WORKDIR}"/removedzones ]
898				then
899					printf  'Removed zoneinfo files:\n'
900					sed 's/^/	/'	\
901						< "${WORKDIR}"/removedzones
902				fi
903				printf '\nX'
904			)
905			MSG=${MSG%X}
906
907			# Now is where the changes start happening...
908
909			cd ${TZBASE}/../../../distrib/sets || exit 1
910			cd lists/base || exit 2
911			DOIT cvs -d "${CVSROOT}" -q update mi || exit 3
912			cp mi mi.unsorted || exit 4
913			sh ../../sort-list mi || exit 5
914			cmp -s mi mi.unsorted ||
915				DOIT cvs -d "${CVSROOT}" -q commit \
916					-m 'Sort (NFCI)' mi || exit 6
917			rm -f mi.unsorted
918			sed -f "${WORKDIR}/sedcmds" < mi > mi.new || exit 7
919			test ! -s mi.new || cmp -s mi mi.new && {
920				printf 'Failed to make changes to set lists'
921				exit 8
922			}
923			mv mi.new mi || exit 9
924			sh ../../sort-list mi || exit 10
925			DOIT cvs -d "${CVSROOT}" commit -m "${MSG}" mi ||
926				exit 11
927			printf 'Sets list successfully updated'
928			exit 0
929		) || {
930			printf '%s: %d\n%s %s\n' 'Sets list update failed' "$?"\
931				'Update the sets list' \
932				'(src/distrib/sets/lists/base/mi) manually'
933			if [ -s "${WORKDIR}"/removedzones ]
934			then
935				printf 'Removed Zones:'
936				sed 's/^/	/' < "${WORKDIR}"/removedzones
937			fi
938			if [ -s "${WORKDIR}"/addedzones ]
939			then
940				printf 'Added Zones:'
941				sed 's/^/	/' < "${WORKDIR}"/addedzones
942			fi
943		}
944	fi
945}
946
947extra()
948{
949	cat <<EOF
950Also do the following:
951 * Edit and commit  src/doc/3RDPARTY
952 * Edit and commit  src/doc/CHANGES
953 * Edit and commit  src/distrib/sets/lists/base/mi
954 *      if the set of installed files altered (if not done by the script).
955 * Submit pullup requests for all active release branches.
956 * rm -rf ${WORK_PFX}  (optional)
957 * Verify that
958  ${UPDATE_FROM}
959 * no longer exists.
960EOF
961}
962
963main()
964{
965	set -e
966	validate_pwd
967	findcvsroot
968	mkworkpfx
969
970	argparse "$@"
971
972	cvsupdate || fail 'working directory (dist) cvs update failed:'" $?"
973
974	setup_versions
975	mkworkdir
976	fetch
977	$GLOBAL || checksig
978	extract
979
980	zonelists
981
982	addnews
983	trimnews
984	mkimportmsg
985	editimportmsg
986	cvsimport
987	cvsmerge
988	resolveconflicts
989	cvscommitmerge
990	updatedzones
991
992	setlistupdate
993
994	rm -f "${UPDATE_FROM}"
995	rm -fr	"${WORK_PFX}"/oldzones \
996		"${WORK_PFX}"/newzones \
997		"${WORK_PFX}"/updzones
998	extra
999}
1000
1001main "$@"
1002