1# $NetBSD: tzdata2netbsd,v 1.7 2015/08/11 18:10:13 apb 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. Edit OLDVER and NEWVER below.
8# 2. Run this script.  You will be prompted for confirmation before
9#    anything major (such as a cvs operation).
10# 3. If something fails, abort the script and fix it.
11# 4. Re-run this script until you are happy.  It's designed to
12#    be re-run over and over, and later runs will try not to
13#    redo non-trivial work done by earlier runs.
14#
15
16OLDVER=2015e
17NEWVER=2015f
18
19# Uppercase variants of OLDVER and NEWVER
20OLDVER_UC="$( echo "${OLDVER}" | tr '[a-z]' '[A-Z]' )"
21NEWVER_UC="$( echo "${NEWVER}" | tr '[a-z]' '[A-Z]' )"
22
23# Tags for use with version control systems
24CVSOLDTAG="TZDATA${OLDVER_UC}"
25CVSNEWTAG="TZDATA${NEWVER_UC}"
26CVSBRANCHTAG="TZDATA"
27GITHUBTAG="${NEWVER}"
28
29# URLs for fetching distribution files, etc.
30DISTURL="ftp://ftp.iana.org/tz/releases/tzdata${NEWVER}.tar.gz"
31SIGURL="${DISTURL}.asc"
32NEWSURL="https://github.com/eggert/tz/raw/${GITHUBTAG}/NEWS"
33
34# Directories
35REPODIR="src/external/public-domain/tz/dist" # relative to the NetBSD CVS repo
36TZDISTDIR="$(pwd)/dist" # should be .../external/public-domain/tz/dist
37WORKDIR="$(pwd)/update-work/${NEWVER}"
38EXTRACTDIR="${WORKDIR}/extract"
39
40# Files in the work directory
41DISTFILE="${WORKDIR}/${DISTURL##*/}"
42SIGFILE="${DISTFILE}.sig"
43PGPVERIFYLOG="${WORKDIR}/pgpverify.log"
44NEWSFILE="${WORKDIR}/NEWS"
45NEWSTRIMFILE="${WORKDIR}/NEWS.trimmed"
46IMPORTMSGFILE="${WORKDIR}/import.msg"
47IMPORTDONEFILE="${WORKDIR}/import.done"
48MERGSMSGFILE="${WORKDIR}/merge.msg"
49MERGEDONEFILE="${WORKDIR}/merge.done"
50COMMITMERGEDONEFILE="${WORKDIR}/commitmerge.done"
51
52DOIT()
53{
54	local really_do_it=false
55	local reply
56
57	echo "In directory $(pwd)"
58	echo "ABOUT TO DO:" "$(shell_quote "$@")"
59	read -p "Really do it? [yes/no/quit] " reply
60	case "${reply}" in
61	[yY]*)	really_do_it=true ;;
62	[nN]*)	really_do_it=false ;;
63	[qQ]*)
64		echo "Aborting"
65		return 1
66		;;
67	esac
68	if $really_do_it; then
69		echo "REALLY DOING IT NOW..."
70		"$@"
71	else
72		echo "NOT REALLY DOING THE ABOVE COMMAND"
73	fi
74}
75
76# Quote args to make them safe in the shell.
77# Usage: quotedlist="$(shell_quote args...)"
78#
79# After building up a quoted list, use it by evaling it inside
80# double quotes, like this:
81#    eval "set -- $quotedlist"
82# or like this:
83#    eval "\$command $quotedlist \$filename"
84#
85shell_quote()
86{(
87	local result=''
88	local arg qarg
89	LC_COLLATE=C ; export LC_COLLATE # so [a-zA-Z0-9] works in ASCII
90	for arg in "$@" ; do
91		case "${arg}" in
92		'')
93			qarg="''"
94			;;
95		*[!-./a-zA-Z0-9]*)
96			# Convert each embedded ' to '\'',
97			# then insert ' at the beginning of the first line,
98			# and append ' at the end of the last line.
99			# Finally, elide unnecessary '' pairs at the
100			# beginning and end of the result and as part of
101			# '\'''\'' sequences that result from multiple
102			# adjacent quotes in he input.
103			qarg="$(printf "%s\n" "$arg" | \
104			    ${SED:-sed} -e "s/'/'\\\\''/g" \
105				-e "1s/^/'/" -e "\$s/\$/'/" \
106				-e "1s/^''//" -e "\$s/''\$//" \
107				-e "s/'''/'/g"
108				)"
109			;;
110		*)
111			# Arg is not the empty string, and does not contain
112			# any unsafe characters.  Leave it unchanged for
113			# readability.
114			qarg="${arg}"
115			;;
116		esac
117		result="${result}${result:+ }${qarg}"
118	done
119	printf "%s\n" "$result"
120)}
121
122findcvsroot()
123{
124	[ -n "${CVSROOT}" ] && return 0
125	CVSROOT="$( cat ./CVS/Root )"
126	[ -n "${CVSROOT}" ] && return 0
127	echo >&2 "Failed to set CVSROOT value"
128	return 1
129}
130
131mkworkdir()
132{
133	mkdir -p "${WORKDIR}"
134}
135
136fetch()
137{
138	[ -f "${DISTFILE}" ] || ftp -o "${DISTFILE}" "${DISTURL}"
139	[ -f "${SIGFILE}" ] || ftp -o "${SIGFILE}" "${SIGURL}"
140	[ -f "${NEWSFILE}" ] || ftp -o "${NEWSFILE}" "${NEWSURL}"
141}
142
143checksig()
144{
145	{ gpg --verify "${SIGFILE}" "${DISTFILE}"
146	  echo gpg exit status $?
147	} 2>&1 | tee "${PGPVERIFYLOG}"
148
149	# The output should contain lines that match all the following regexps
150	#
151	while read line; do
152		if ! grep -q -e "^${line}\$" "${PGPVERIFYLOG}"; then
153			echo >&2 "Failed to verify signature: ${line}"
154			return 1
155		fi
156	done <<'EOF'
157gpg: Signature made .* using RSA key ID 62AA7E34
158gpg: Good signature from "Paul Eggert <eggert@cs.ucla.edu>"
159Primary key fingerprint: 7E37 92A9 D8AC F7D6 33BC  1588 ED97 E90E 62AA 7E34
160gpg exit status 0
161EOF
162}
163
164extract()
165{
166	[ -f "${EXTRACTDIR}/zone.tab" ] && return
167	mkdir -p "${EXTRACTDIR}"
168	tar -z -xf "${DISTFILE}" -C "${EXTRACTDIR}"
169}
170
171addnews()
172{
173	[ -f "${EXTRACTDIR}/NEWS" ] && return
174	cp -p "${NEWSFILE}" "${EXTRACTDIR}"/NEWS
175}
176
177# Find the relevant part of the NEWS file for all releases between
178# OLDVER and NEWVER, and save them to NEWSTRIMFILE.
179#
180trimnews()
181{
182	[ -s "${NEWSTRIMFILE}" ] && return
183	awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \
184	    '
185		BEGIN {inrange = 0}
186		/^Release [0-9]+[a-z]+ - .*/ {
187			# "Release <version> - <date>"
188			inrange = ($2 > oldver && $2 <= newver)
189		}
190		// { if (inrange) print; }
191		' \
192		<"${NEWSFILE}" >"${NEWSTRIMFILE}"
193}
194
195# Create IMPORTMSGFILE from NEWSTRIMFILE, by ignoring some sections,
196# keeping only the first sentence from paragraphs in other sections,
197# and changing the format.
198#
199# The result should be edited by hand before performing a cvs commit.
200# A message to that effect is inserted at the beginning of the file.
201#
202mkimportmsg()
203{
204	[ -s "${IMPORTMSGFILE}" ] && return
205	{ cat <<EOF
206EDIT ME: Edit this file and then delete the lines marked "EDIT ME".
207EDIT ME: This file will be used as a log message for the "cvs commit" that
208EDIT ME: imports tzdata${NEWVER}.  The initial contents of this file were
209EDIT ME: generated from ${NEWSFILE}.
210EDIT ME:
211EOF
212	awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \
213	    -v disturl="${DISTURL}" -v newsurl="${NEWSURL}" \
214	    '
215		BEGIN {
216			bullet = "  * ";
217			indent = "    ";
218			blankline = 0;
219			goodsection = 0;
220			havesentence = 0;
221			print "Import tzdata"newver" from "disturl;
222			#print "and NEWS file from "newsurl;
223		}
224		/^Release/ {
225			# "Release <version> - <date>"
226			ver = $2;
227			date = gensub(".* - ", "", 1, $0);
228			print "";
229			print "Summary of changes in tzdata"ver \
230				" ("date"):";
231		}
232		/^$/ { blankline = 1; havesentence = 0; }
233		/^  Changes affecting/ { goodsection = 0; }
234		/^  Changes affecting.*time/ { goodsection = 1; }
235		/^  Changes affecting.*data/ { goodsection = 1; }
236		/^  Changes affecting.*documentation/ || \
237		/^  Changes affecting.*commentary/ {
238			t = gensub("^ *", "", 1, $0);
239			t = gensub("\\.*$", ".", 1, t);
240			print bullet t;
241			goodsection = 0;
242		}
243		/^    .*/ && goodsection {
244			# In a paragraph in a "good" section.
245			# Ignore leading spaces, and ignore anything
246			# after the first sentence.
247			# First line of paragraph gets a bullet.
248			t = gensub("^ *", "", 1, $0);
249			t = gensub("\\. .*", ".", 1, t);
250			if (blankline) print bullet t;
251			else if (! havesentence) print indent t;
252			havesentence = (havesentence || (t ~ "\\.$"));
253		}
254		/./ { blankline = 0; }
255		' \
256		<"${NEWSTRIMFILE}"
257	} >"${IMPORTMSGFILE}"
258}
259
260editimportmsg()
261{
262	if [ -s "${IMPORTMSGFILE}" ] \
263	&& ! grep -q '^EDIT' "${IMPORTMSGFILE}"
264	then
265		return 0 # file has already been edited
266	fi
267	# Pass both IMPORTMSGFILE and NEWSFILE to the editor, so that the
268	# user can easily consult NEWSFILE while editing IMPORTMSGFILE.
269	vi "${IMPORTMSGFILE}" "${NEWSFILE}"
270}
271
272cvsimport()
273{
274	if [ -e "${IMPORTDONEFILE}" ]; then
275		cat >&2 <<EOF
276The CVS import has already been performed.
277EOF
278		return 0
279	fi
280	if ! [ -s "${IMPORTMSGFILE}" ] \
281	|| grep -q '^EDIT' "${IMPORTMSGFILE}"
282	then
283		cat >&2 <<EOF
284The message file ${IMPORTMSGFILE}
285has not been properly edited.
286Not performing cvs import.
287EOF
288		return 1
289	fi
290	( cd "${EXTRACTDIR}" &&
291	  DOIT cvs -d "${CVSROOT}" import -m "$(cat "${IMPORTMSGFILE}")" \
292		"${REPODIR}" "${CVSBRANCHTAG}" "${CVSNEWTAG}"
293	) && touch "${IMPORTDONEFILE}"
294}
295
296cvsmerge()
297{
298
299	cd "${TZDISTDIR}" || exit 1
300	if [ -e "${MERGEDONEFILE}" ]; then
301		cat >&2 <<EOF
302The CVS merge has already been performed.
303EOF
304		return 0
305	fi
306	DOIT cvs -d "${CVSROOT}" update -j"${CVSOLDTAG}" -j"${CVSNEWTAG}" \
307	&& touch "${MERGEDONEFILE}"
308}
309
310resolveconflicts()
311{
312	cd "${TZDISTDIR}" || exit 1
313	if grep -l '^[<=>][<=>][<=>]' *
314	then
315		cat <<EOF
316There appear to be conflicts in the files listed above.
317Resolve conflicts, then re-run this script.
318EOF
319		return 1
320	fi
321}
322
323cvscommitmerge()
324{
325	cd "${TZDISTDIR}" || exit 1
326	if grep -l '^[<=>][<=>][<=>]' *
327	then
328		cat >&2 <<EOF
329There still appear to be conflicts in the files listed above.
330Not performing cvs commit.
331EOF
332		return 1
333	fi
334	if [ -e "${COMMITMERGEDONEFILE}" ]; then
335		cat >&2 <<EOF
336The CVS commmit (of the merge result) has already been performed.
337EOF
338		return 0
339	fi
340	DOIT cvs -d "${CVSROOT}" commit -m "Merge tzdata${NEWVER}" \
341	&& touch "${COMMITMERGEDONEFILE}"
342}
343
344extra()
345{
346	cat <<EOF
347Also do the following:
348 * Edit src/doc/3RDPARTY
349 * Edit src/doc/CHANGES
350 * Edit src/distrib/sets/base/mi if the set of installed files has changed.
351 * Submit pullup requests for all active release branches.
352 * rm -rf ${WORKDIR}
353EOF
354}
355
356main()
357{
358	set -e
359	findcvsroot
360	mkworkdir
361	fetch
362	checksig
363	extract
364	addnews
365	trimnews
366	mkimportmsg
367	editimportmsg
368	cvsimport
369	cvsmerge
370	resolveconflicts
371	cvscommitmerge
372	extra
373}
374
375main "$@"
376