# $NetBSD: tzdata2netbsd,v 1.16 2024/02/05 21:52:38 kre Exp $ # For use by NetBSD developers when updating to new versions of tzdata. # # 0. Be in an up-to-date checkout of src/external/public-domain/tz # from NetBSD-current. # 1. Make sure that you have Paul Eggert's 4K RSA public key in your # keyring (62AA7E34, eggert@cs.ucla.edu) It is not required that it be trusted. # 2. Run this script. You will be prompted for confirmation before # anything major (such as a cvs operation). The tz versions can be # specified as args (new version first, and the previous second) if # needed to override the calculated values # 3. If something fails, abort the script and fix it. # 4. Re-run this script until you are happy. It's designed to # be re-run over and over, and later runs will try not to # redo non-trivial work done by earlier runs. # VERS_PATTERN='2[0-9][0-9][0-9][a-z]' # This needs to be updated twice every millennium to allow for the # new millenium's years. # First in the late xx90's sometime, allow the new one by changing the leading # digit from a specific value to the class containing the current and # following values (eg: in 2098 or so, change '2' to be '[23]'). # Then in the following early xx00's sometime, delete the class, and # leave only the current value as valid (eg: in 3001 or 3002, # change '[23]' to be just '3' # Doing it this way helps guard against invalid specifications. # We could automate this, but it is (IMO) not worth the cost, to avoid a # twice a millenium edit requirement. # A more significant (and harder) change will be needed in the late 9990's # If this script lasts until then, send me a postcard, I'll be waiting for it! # Things get easier again after that until the late 99990's (etc.) # Note the pattern is used on both the old and new version specifiers, # so it must be able to cope with the shift from one form (eg 2999g) # to the new one (eg: 3000a) without failing (or the code that uses it # below needs to be updated). # Also note that the option of having a second alpha (1997aa or something) # to handle years with much activity is handled below, the pattern does not # need to match those. # If that convention changes (as of date of writing, it has never been # exercised) then code changes below will be required. # Note it doesn't matter (here) if nnnnz is followed by nnnnaa or nnnnza DIST_HOST=ftp.iana.org DIST_PATH=tz DIST_FILES=releases GTZURL=https://github.com/JodaOrg/global-tz/releases/download EDITOR=${EDITOR:-vi} TZBASE=$(pwd) || fail 'Cannot find myself ($PWD)' cd -P "$TZBASE" || fail "Cannot return home: ${TZBASE}" WORK_PFX=${TZBASE}/update-work UPDATE_FROM=${WORK_PFX}/updating.from.version usage() { printf >&2 '%s\n' \ "Usage: $0 [new-version-id [old-version-id]]" \ " where a version-id is of the form YYYYx (eg: 2018c)" \ " or '' for new-version-id (to specify only the old)" \ " and where new-version-id can have =fetch-version-id" \ " appended to specify fetching that version instead" \ " where the 'fetch-version-id' can be omitted if it" \ " is \${new-version-id}gtz - and simply using '=' means" \ " to work out the new-version-id but then use the gtz fork" exit 2 } fail() { local IFS=' ' printf >&2 '%s\n' "Error detected:" " $*" "Aborting." exit 1 } valid_vers() { case "$2" in # The IANA (Eggert) standard version names ${VERS_PATTERN} | ${VERS_PATTERN}[a-z] ) ;; # The alternate (more rational) fork "global timezone" version ${VERS_PATTERN}gtz | ${VERS_PATTERN}[a-z]gtz ) ;; *) printf >&2 '%s: %s\n' \ "Bad form for $1 version specifier '$2'" \ "should (usually) be 'YYYYx'" return 1 ;; esac return 0 } get_curvers() { local LF='' local LIST=iana-listing local SED_SCRIPT=' /tzdata-latest.*-> /{ s/^.*-> // s/\..*$// s;^releases/tzdata;;p q } d' test -d "${WORK_PFX}" && test -s "${WORK_PFX}/${LIST}" && test "${WORK_PFX}/${LIST}" -nt dist/CVS && LF=$(find "${WORK_PFX}" -name "${LIST}" -mtime -1 -print) && test -n "${LF}" && NEWVER=$(sed -n < "${LF}" "${SED_SCRIPT}") && valid_vers new "${NEWVER}" || ftp >/dev/null 2>&1 -ia "${DIST_HOST}" <<- EOF && dir ${DIST_PATH} ${WORK_PFX}/${LIST} quit EOF test -s "${WORK_PFX}/${LIST}" && NEWVER=$(sed -n < "${WORK_PFX}/${LIST}" "${SED_SCRIPT}") && valid_vers new "${NEWVER}" || { rm -f "${WORK_PFX}/${LIST}" fail "Cannot fetch current tzdata version from ${DIST_HOST}" } printf '%s\n' "Updating from ${OLDVER} to ${NEWVER}" } argparse() { local OVF OV NV OVy OVs NVy NVs if OVF=$(find "${WORK_PFX}" -name "${UPDATE_FROM##*/}" -mtime +2 -print) then # delete anything old test -n "${OVF}" && rm -f "${OVF}" fi case "$#" in 0|1) # once we have obtained OLDVER once, never guess it again. if [ -f "${UPDATE_FROM}" ] then OLDVER=$(cat "${UPDATE_FROM}") elif [ -f dist/TZDATA_VERSION ] then OLDVER=$(cat dist/TZDATA_VERSION) elif [ -f dist/version ] then OLDVER=$(cat dist/version) fi OLDVER=${OLDVER#tzdata} # TZDATA_VERS is tzdata-nnnnX OLDVER=${OLDVER#-} # but the '-' is optional OLDVERGTZ=${OLDVER} # This would have been the cvs tag OLDVER=${OLDVER%gtz} # want the base version elsewhere if [ -z "${OLDVER}" ] then printf >&2 '%s\n' \ 'Cannot determine current installed version' \ 'Specify it on the command line.' \ '' usage fi valid_vers old "${OLDVER}" || fail "Calculated bad OLDVER, give as 2nd arg" ;; 2) valid_vers old "$2" && OLDVER="$2" || usage ;; *) usage ;; esac GLOBAL=false case "$#:$1" in 0: | 1: | 2: ) ;; 1:=|2:=) GLOBAL=true;; 1:=?*|2:=?*) valid_vers fetch "${1#=}" && FETCHVER="${1#=}" || usage ;; 1:*=?*|2:*=?*) set -- "{$1%=*}" "${1#*=}" valid_vers fetch "$2" && FETCHVER="$2" || usage valid_vers new "$1" && NEWVER="$1" || usage ;; 1:?*|2:?*) valid_vers new "$1" && NEWVER="$1" || usage ;; *) usage ;; esac test -z "${NEWVER}" && get_curvers if [ -z "${FETCHVER}" ] then if $GLOBAL then FETCHVER=${NEWVER}gtz else FETCHVER=${NEWVER} fi fi case "${FETCHVER}" in *gtz) GLOBAL=true;; *) GLOBAL=false;; esac if [ "${NEWVER}" = "${OLDVER}" ] then printf 'New and old versions both %s; nothing to do\n' \ "${NEWVER}" exit 0 fi printf '%s\n' "${OLDVER}" > "${UPDATE_FROM}" || fail "Unable to preserve old version ${OLDVER} in ${UPDATE_FROM}" # Do version upgrade test using base version names, ignoring # the "gtz" in the "global timezone" versions, so we can # switch back and forth between use of those as circumstances change OV=${OLDVER%gtz} NV=${NEWVER%gtz} OVy=${OV%%[!0-9]*} OVs=${OV#${OVy}} NVy=${NV%%[!0-9]*} NVs=${NV#${NVy}} # To get all the permutations correct, we need to separate # the year and suffix parts of the version IDs (done just above) # and then compare them separately. The suffix is only relevant # to the result when the years are the same. # We compare the length of the suffix separately to the suffix # itself, a multi-char suffix has never happened (and is never # likely to) - but in the event that prediction is wrong, we don't # know (yet) what is to come after 'z' - it might be 'za' 'zb' # ... to 'zz" then 'zza' ... or it might be 'aa' 'ab' ... 'az' 'ba'... # we need to handle both possibilities. Two things stand out from # those: 1. a longer suffix is always going to be for a newer version # than a shorter one; 2. equal length suffixes can be compared as # strings if [ "${OVy}" -gt "${NVy}" ] || { [ "${OVy}" -eq "${NVy}" ] && { [ "${#OVs}" -gt "${#NVs}" ] || LC_COLLATE=C [ "${OVs}" '>' "${NVs}" ] } } then local reply printf '%s\n' "Update would revert ${OLDVER} to ${NEWVER}" read -p "Is reversion intended? " reply case "${reply}" in [Yy]*) ;; *) printf '%s\n' OK. Aborted. rm -f "${UPDATE_FROM}" exit 1 ;; esac fi return 0 } setup_versions() { # Uppercase variants of OLDVER and NEWVER OLDVER_UC="$( echo "${OLDVERGTZ}" | tr '[a-z]' '[A-Z]' )" NEWVER_UC="$( echo "${NEWVER}" | tr '[a-z]' '[A-Z]' )" # Tags for use with version control systems CVSOLDTAG="TZDATA${OLDVER_UC}" CVSNEWTAG="TZDATA${NEWVER_UC}" CVSBRANCHTAG="TZDATA" GITHUBTAG="${NEWVER}" if $GLOBAL && [ "${CVSNEWTAG%GTZ}" = "${CVSNEWTAG}" ] then CVSNEWTAG=${CVSNEWTAG}GTZ fi # URLs for fetching distribution files, etc. if $GLOBAL then DISTURL=${GTZURL}/${FETCHVER}/tzdata${FETCHVER}.tar.gz unset SIGURL else DISTURL="ftp://${DIST_HOST}/${DIST_PATH}/${DIST_FILES}" DISTURL="${DISTURL}/tzdata${NEWVER}.tar.gz" SIGURL="${DISTURL}.asc" fi NEWSURL="https://github.com/eggert/tz/raw/${GITHUBTAG}/NEWS" # Directories REPODIR="src/external/public-domain/tz/dist" # relative to the NetBSD CVS repo TZDISTDIR="$(pwd)/dist" # should be .../external/public-domain/tz/dist WORKDIR="${WORK_PFX}/${NEWVER}" EXTRACTDIR="${WORKDIR}/extract" # Files in the work directory DISTFILE="${WORKDIR}/${DISTURL##*/}" SIGFILE="${DISTFILE}.asc" PGPVERIFYLOG="${WORKDIR}/pgpverify.log" NEWSFILE="${WORKDIR}/NEWS" NEWSTRIMFILE="${WORKDIR}/NEWS.trimmed" IMPORTMSGFILE="${WORKDIR}/import.msg" IMPORTDONEFILE="${WORKDIR}/import.done" MERGSMSGFILE="${WORKDIR}/merge.msg" MERGEDONEFILE="${WORKDIR}/merge.done" COMMITMERGEDONEFILE="${WORKDIR}/commitmerge.done" printf '%s\n' "${CVSOLDTAG}" > "${WORK_PFX}/updating_from" } DOIT() { local really_do_it=false local reply echo "In directory $(pwd)" echo "ABOUT TO DO:" "$(shell_quote "$@")" read -p "Really do it? [yes/no/quit] " reply case "${reply}" in [yY]*) really_do_it=true ;; [nN]*) really_do_it=false ;; [qQ]*) echo "Aborting" return 1 ;; *) echo "Huh?" return 1 ;; esac if $really_do_it; then echo "REALLY DOING IT NOW..." "$@" else echo "NOT REALLY DOING THE ABOVE COMMAND" fi } # Quote args to make them safe in the shell. # Usage: quotedlist="$(shell_quote args...)" # # After building up a quoted list, use it by evaling it inside # double quotes, like this: # eval "set -- $quotedlist" # or like this: # eval "\$command $quotedlist \$filename" # shell_quote() ( local result='' local arg qarg LC_COLLATE=C ; export LC_COLLATE # so [a-zA-Z0-9] works in ASCII for arg in "$@" ; do case "${arg}" in '') qarg="''" ;; *[!-./a-zA-Z0-9]*) # Convert each embedded ' to '\'', # then insert ' at the beginning of the first line, # and append ' at the end of the last line. # Finally, elide unnecessary '' pairs at the # beginning and end of the result and as part of # '\'''\'' sequences that result from multiple # adjacent quotes in the input. qarg="$(printf '%s\n' "$arg" | \ ${SED:-sed} -e "s/'/'\\\\''/g" \ -e "1s/^/'/" -e "\$s/\$/'/" \ -e "1s/^''//" -e "\$s/''\$//" \ -e "s/'''/'/g" )" ;; *) # Arg is not the empty string, and does not contain # any unsafe characters. Leave it unchanged for # readability. qarg="${arg}" ;; esac result="${result}${result:+ }${qarg}" done printf '%s\n' "$result" ) validate_pwd() { local P="$(pwd)" || return 1 test -d "${P}" && test -d "${P}/CVS" && test -d "${P}/dist" && test -f "${P}/dist/zone.tab" && test -f "${P}/tzdata2netbsd" || { printf >&2 '%s\n' "Please change to the correct directory" return 1 } test -f "${P}/CVS/Tag" && { # Here (for local use only) if needed for private branch work # insert tests for the value of $(cat "${P}/CVS/Tag") and # allow your private branch tag to pass. Eg: # case "$(cat "${P}/CVS/Tag")" in # my-branch-name) return 0;; # esac # Do not commit a version of this script modified that way, # (not even on the private branch) - keep it as a local # modified file. (This script will not commit it.) printf >&2 '%s\n' \ "This script should be run in a checkout of HEAD only" return 1 } return 0 } findcvsroot() { [ -n "${CVSROOT}" ] && return 0 CVSROOT="$( cat ./CVS/Root )" [ -n "${CVSROOT}" ] && return 0 echo >&2 "Failed to set CVSROOT value" return 1 } mkworkpfx() { mkdir -p "${WORK_PFX}" || fail "Unable to make missing ${WORK_PFX}" } mkworkdir() { mkdir -p "${WORKDIR}" || fail "Unable to make missing ${WORKDIR}" } cvsupdate() ( # Make sure our working directory is up to date (and HEAD) cd "${TZBASE}/dist" || exit 1 cvs -d "${CVSROOT}" -q update -AdP || exit 2 ) fetch() { [ -f "${DISTFILE}" ] || ftp -o "${DISTFILE}" "${DISTURL}" || fail "fetch of ${DISTFILE} failed" if [ -n "${SIGURL}" ] then [ -f "${SIGFILE}" ] || ftp -o "${SIGFILE}" "${SIGURL}" || fail "fetch of ${SIGFILE} failed" fi [ -f "${NEWSFILE}" ] || ftp -o "${NEWSFILE}" "${NEWSURL}" || fail "fetch of ${NEWSFILE} failed" } checksig() { { gpg --verify "${SIGFILE}" "${DISTFILE}" echo gpg exit status $? } 2>&1 | tee "${PGPVERIFYLOG}" # The output should contain lines that match all the following regexps # while read line; do if ! grep -E -q -e "^${line}\$" "${PGPVERIFYLOG}"; then echo >&2 "Failed to verify signature: ${line}" return 1 fi done <<'EOF' gpg: Signature made .* using RSA key ID (62AA7E34|44AD418C) gpg: Good signature from "Paul Eggert " gpg exit status 0 EOF } extract() { [ -f "${EXTRACTDIR}/zone.tab" ] && return mkdir -p "${EXTRACTDIR}" tar -z -xf "${DISTFILE}" -C "${EXTRACTDIR}" } rflist() ( test "${1}" && cd "${1}" && find * -print | sort ) zonelists() { [ -f "${WORKDIR}"/addedzones ] && return rm -fr "${WORK_PFX}"/oldzones "${WORK_PFX}"/newzones ( cd "${TZBASE}/share/zoneinfo" || exit 1 make TOOL_ZIC=/usr/sbin/zic \ DESTDIR= \ TZBUILDDIR="${WORK_PFX}"/oldzones \ TZDIR="${WORK_PFX}"/oldzones \ TZDISTDIR="${TZBASE}"/dist \ posix_only >/dev/null 2>&1 ) || fail 'Unable to compile old zone data files' ( cd "${TZBASE}/share/zoneinfo" || exit 1 make TOOL_ZIC=/usr/sbin/zic \ DESTDIR= \ TZBUILDDIR="${WORK_PFX}"/newzones \ TZDIR="${WORK_PFX}"/newzones \ TZDISTDIR="${EXTRACTDIR}" \ posix_only >/dev/null 2>&1 ) || fail 'Unable to compile new zone data files' rflist "${WORK_PFX}"/oldzones > "${WORKDIR}"/oldzones rflist "${WORK_PFX}"/newzones > "${WORKDIR}"/newzones if cmp -s "${WORKDIR}"/oldzones "${WORKDIR}"/newzones >/dev/null then printf "No zones added or deleted by this update" > "${WORKDIR}"/removedzones > "${WORKDIR}"/addedzones return 0 fi comm -23 "${WORKDIR}"/oldzones "${WORKDIR}"/newzones \ > "${WORKDIR}"/removedzones test "${REMOVEOK:-no}" != yes && test -s "${WORKDIR}"/removedzones && { printf '%s\n' 'This update wants to remove these zone files:' '' sed 's/^/ /' < "${WORKDIR}"/removedzones printf '%s\n' '' 'It probably should not' '' printf 'If this is OK, rerun this script with REMOVEOK=yes\n' printf 'Otherwise, fix the problem, and then rerun the script\n' exit 1 } comm -13 "${WORKDIR}"/oldzones "${WORKDIR}"/newzones \ > "${WORKDIR}"/addedzones test -s "${WORKDIR}"/addedzones && { printf '%s\n' '' '********************************* NOTE:' \ '********************************* New Zones Created' \ '' sed 's/^/ /' < "${WORKDIR}"/addedzones printf '%s\n' '' '*********************************' '' } return 0 } updatedzones() { [ -f "${WORKDIR}"/.zonesOK ] && return rm -fr "${WORK_PFX}"/updzones ( cd "${TZBASE}/share/zoneinfo" || exit 1 make TOOL_ZIC=/usr/sbin/zic \ DESTDIR= \ TZBUILDDIR="${WORK_PFX}"/updzones \ TZDIR="${WORK_PFX}"/updzones \ TZDISTDIR="${TZBASE}"/dist \ posix_only >/dev/null 2>&1 ) || fail 'Unable to compile updated zone data. HELP' rflist "${WORK_PFX}"/updzones > "${WORKDIR}"/updzones cmp -s "${WORKDIR}"/newzones "${WORKDIR}"/updzones || { printf '%s\n' '' '*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*' \ 'After cvs work, zones created are not as intended' \ '-------------------------------------------------' \ 'Zones not created but should have been:' comm -23 "${WORKDIR}"/newzones "${WORKDIR}"/updzones | sed 's/^/ /' printf '%s\n' \ '-------------------------------------------------' \ 'Zones created that should not have been:' comm -13 "${WORKDIR}"/newzones "${WORKDIR}"/updzones | sed 's/^/ /' prinrf '%s\n' \ '-------------------------------------------------' fail 'cvs import/merge/update/commit botch' } > "${WORKDIR}"/.zonesOK } addnews() { [ -f "${EXTRACTDIR}/NEWS" ] && return cp -p "${NEWSFILE}" "${EXTRACTDIR}"/NEWS } # Find the relevant part of the NEWS file for all releases between # OLDVER and NEWVER, and save them to NEWSTRIMFILE. # trimnews() { [ -s "${NEWSTRIMFILE}" ] && return awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \ ' BEGIN {inrange = 0} /^Release [0-9]+[a-z]+ - .*/ { # "Release - " # Note: must handle transition from 2018z to 2018aa # Assumptions: OLDVER and NEWVER have been sanitized, # and format of NEWS file does not alter (and # contains valid data) inrange = ((length($2) > length(oldver) || \ $2 > oldver) && \ (length($2) < newver || $2 <= newver)) } // { if (inrange) print; } ' \ <"${NEWSFILE}" >"${NEWSTRIMFILE}" if $GLOBAL then printf '%s\n' "tzdata-${NEWVER}gtz" else printf '%s\n' "tzdata-${NEWVER}" fi > ${TZDISTDIR}/TZDATA_VERSION } # Create IMPORTMSGFILE from NEWSTRIMFILE, by ignoring some sections, # keeping only the first sentence from paragraphs in other sections, # and changing the format. # # The result should be edited by hand before performing a cvs commit. # A message to that effect is inserted at the beginning of the file. # mkimportmsg() { [ -s "${IMPORTMSGFILE}" ] && return { cat < - " ver = $2; date = gensub(".* - ", "", 1, $0); print ""; print "Summary of changes in tzdata"ver \ " ("date"):"; } /^$/ { blankline = 1; havesentence = 0; } /^ Changes / { goodsection = 0; } /^ Changes to future timestamps/ { goodsection = 1; } /^ Changes to past timestamps/ { goodsection = 1; } /^ Changes to documentation/ || \ /^ Changes to commentary/ { t = gensub("^ *", "", 1, $0); t = gensub("\\.*$", ".", 1, t); print bullet t; goodsection = 0; } /^ .*/ && goodsection { # In a paragraph in a "good" section. # Ignore leading spaces, and ignore anything # after the first sentence. # First line of paragraph gets a bullet. t = gensub("^ *", "", 1, $0); t = gensub("\\. .*", ".", 1, t); if (blankline) print bullet t; else if (! havesentence) print indent t; havesentence = (havesentence || (t ~ "\\.$")); } /./ { blankline = 0; } ' \ <"${NEWSTRIMFILE}" } >"${IMPORTMSGFILE}" if [ -s "${WORKDIR}"/addedzones ] then printf '%s\n' '' 'Zones added by this update:' sed 's/^/ /' < "${WORKDIR}"/addedzones fi >> "${IMPORTMSGFILE}" if [ -s "${WORKDIR}"/removedzones ] then printf '%s\n' '' 'Zones removed by this update:' sed 's/^/ /' < "${WORKDIR}"/removedzones fi >> "${IMPORTMSGFILE}" } editimportmsg() { if [ -s "${IMPORTMSGFILE}" ] && ! grep -q '^EDIT' "${IMPORTMSGFILE}" then return 0 # file has already been edited fi # Pass both IMPORTMSGFILE and NEWSFILE to the editor, so that the # user can easily consult NEWSFILE while editing IMPORTMSGFILE. ${EDITOR} "${IMPORTMSGFILE}" "${NEWSFILE}" } cvsimport() { if [ -e "${IMPORTDONEFILE}" ]; then cat >&2 <&2 <&2 <][<=>][<=>]' * then cat <][<=>][<=>]' * then cat >&2 <&2 < "${WORKDIR}/sedcmds" while read file do P=./usr/share/zoneinfo/"${file}" T2=' ' case "$(( 48 - ${#P} ))" in -*|0) T2=' ' T=' ' ;; [12345678]) T=' ' ;; 9|1[0123456]) T=' ';; 1[789]|2[01234]) T=' ';; 2[5-9]|3[012]) T=' ';; # the following cases can't happen, # but for completeness... 3[3-9]) T=' ';; *) T=' ' T="${T} " ;; esac if [ -d "${WORKDIR}/newzones/${file}" ] then printf '%s%sbase-sys-share\n' \ "${P}" "${T}" continue fi printf '%s%sbase-sys-share%sshare\n' \ "${P}" "${T}" "${T2}" # Deal with possibility that a new file # might have previously existed, and then # been deleted - marked obsolete printf '\\!^%s .*obsolete!d\n' "${P}" \ >> "${WORKDIR}/sedcmds" done < "${WORKDIR}"/addedzones > "${WORKDIR}/setadded" printf '$r %s\n' "${WORKDIR}/setadded" \ >> "${WORKDIR}/sedcmds" if ! [ -s "${WORKDIR}/sedcmds" ] # impossible? then exit 0 fi MSG=$( printf 'tzdata update to %s\n' "$NEWVER" if [ -s "${WORKDIR}"/addedzones ] then printf 'Added zoneinfo files:\n' sed 's/^/ /' \ < "${WORKDIR}"/addedzones fi if [ -s "${WORKDIR}"/removedzones ] then printf 'Removed zoneinfo files:\n' sed 's/^/ /' \ < "${WORKDIR}"/removedzones fi printf '\nX' ) MSG=${MSG%X} # Now is where the changes start happening... cd ${TZBASE}/../../../distrib/sets || exit 1 cd lists/base || exit 2 DOIT cvs -d "${CVSROOT}" -q update mi || exit 3 cp mi mi.unsorted || exit 4 sh ../../sort-list mi || exit 5 cmp -s mi mi.unsorted || DOIT cvs -d "${CVSROOT}" -q commit \ -m 'Sort (NFCI)' mi || exit 6 rm -f mi.unsorted sed -f "${WORKDIR}/sedcmds" < mi > mi.new || exit 7 test ! -s mi.new || cmp -s mi mi.new && { printf 'Failed to make changes to set lists' exit 8 } mv mi.new mi || exit 9 sh ../../sort-list mi || exit 10 DOIT cvs -d "${CVSROOT}" commit -m "${MSG}" mi || exit 11 printf 'Sets list successfully updated' exit 0 ) || { printf '%s: %d\n%s %s\n' 'Sets list update failed' "$?"\ 'Update the sets list' \ '(src/distrib/sets/lists/base/mi) manually' if [ -s "${WORKDIR}"/removedzones ] then printf 'Removed Zones:' sed 's/^/ /' < "${WORKDIR}"/removedzones fi if [ -s "${WORKDIR}"/addedzones ] then printf 'Added Zones:' sed 's/^/ /' < "${WORKDIR}"/addedzones fi } fi } extra() { cat <