| #!/bin/sh | |
| # In a git/autoconf/automake-enabled project with a NEWS file and a version- | |
| # controlled .prev-version file, automate the procedure by which we record | |
| # the date, release-type and version string in the NEWS file. That commit | |
| # will serve to identify the release, so apply a signed tag to it as well. | |
| VERSION=2026-02-04.14 # UTC | |
| # Note: this is a bash script (could be zsh or dash) | |
| # Copyright (C) 2009-2026 Free Software Foundation, Inc. | |
| # This program is free software: you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation, either version 3 of the License, or | |
| # (at your option) any later version. | |
| # This program is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # You should have received a copy of the GNU General Public License | |
| # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
| # Written by Jim Meyering | |
| ME=$(basename "$0") | |
| warn() { printf '%s: %s\n' "$ME" "$*" >&2; } | |
| die() { warn "$*"; exit 1; } | |
| help() | |
| { | |
| cat <<EOF | |
| Usage: $ME [OPTION...] VERSION RELEASE_TYPE | |
| Run this script from top_srcdir to perform the final pre-release NEWS | |
| update in which the date, release-type and version string are | |
| recorded. Commit that result with a log entry marking the release, | |
| and apply a signed tag. Run it from your project's top-level | |
| directory. | |
| Requirements: | |
| - you use git for version-control | |
| - a version-controlled .prev-version file | |
| - a NEWS file, by default with line 3 identical to this: | |
| $noteworthy_stub | |
| Options: | |
| --branch=BRANCH set release branch (default: $branch) | |
| -C, --builddir=DIR location of (configured) Makefile (default: $builddir) | |
| --news=FILE NEWS file name (default: $news) | |
| --stub=STR release heading placeholder (default: | |
| $noteworthy_stub) | |
| --stub-version=STR release version placeholder (default: $ver_stub) | |
| --stub-date=STR release date placeholder (default: $date_stub) | |
| --stub-type=STR release type placeholder (default: $type_stub) | |
| --stub-lines=RANGE sed line address range to search for heading | |
| (default: $stub_range) | |
| --help print this help, then exit | |
| --version print version number, then exit | |
| EXAMPLE: | |
| To update NEWS and tag the beta 8.1 release of coreutils, I would run this: | |
| $ME 8.1 beta | |
| Report bugs and patches to <bug-gnulib@gnu.org>. | |
| EOF | |
| exit | |
| } | |
| version() | |
| { | |
| year=$(echo "$VERSION" | sed 's/[^0-9].*//') | |
| cat <<EOF | |
| $ME $VERSION | |
| Copyright (C) $year Free Software Foundation, Inc, | |
| License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html> | |
| This is free software: you are free to change and redistribute it. | |
| There is NO WARRANTY, to the extent permitted by law. | |
| EOF | |
| exit | |
| } | |
| ## ------ ## | |
| ## Main. ## | |
| ## ------ ## | |
| # Constants. | |
| noteworthy='* Noteworthy changes in release' | |
| # Variables. | |
| branch=$(git branch | sed -ne '/^\* /{s///;p;q;}') | |
| builddir=. | |
| news=NEWS | |
| ver_stub='?.?' | |
| date_stub='(????-??-??)' | |
| type_stub='[?]' | |
| noteworthy_stub="$noteworthy $ver_stub $date_stub $type_stub" | |
| stub_range=3 | |
| while test $# != 0 | |
| do | |
| # Handle --option=value by splitting apart and putting back on argv. | |
| case $1 in | |
| --*=*) | |
| opt=$(echo "$1" | sed -e 's/=.*//') | |
| val=$(echo "$1" | sed -e 's/[^=]*=//') | |
| shift | |
| set dummy "$opt" "$val" "$@"; shift | |
| ;; | |
| esac | |
| case $1 in | |
| --help|--version) ${1#--};; | |
| --branch) shift; branch=$1; shift ;; | |
| -C|--builddir) shift; builddir=$1; shift ;; | |
| --news) shift; news=$1; shift ;; | |
| --stub) shift; noteworthy_stub=$1; shift ;; | |
| --stub-version) shift; ver_stub=$1; shift ;; | |
| --stub-date) shift; date_stub=$1; shift ;; | |
| --stub-type) shift; type_stub=$1; shift ;; | |
| --stub-lines) shift; stub_range=$1; shift ;; | |
| --*) die "unrecognized option: $1";; | |
| *) break;; | |
| esac | |
| done | |
| test $# = 2 \ | |
| || die "Usage: $ME [OPTION...] VERSION TYPE" | |
| ver=$1 | |
| type=$2 | |
| ## ---------------------- ## | |
| ## First, sanity checks. ## | |
| ## ---------------------- ## | |
| # Verify that $ver looks like a version number, and... | |
| echo "$ver"|grep -E '^[0-9][0-9.]*[0-9]$' > /dev/null \ | |
| || die "invalid version: $ver" | |
| prev_ver=$(cat .prev-version) \ | |
| || die 'failed to determine previous version number from .prev-version' | |
| # Verify that $ver is sensible (> .prev-version). | |
| case $(printf "%s\n%s\n" "$prev_ver" "$ver"|sort -V -u|tr '\n' ':') in | |
| "$prev_ver:$ver:") ;; | |
| *) die "invalid version: $ver (<= $prev_ver)";; | |
| esac | |
| case $type in | |
| alpha|beta|stable) ;; | |
| *) die "invalid release type: $type";; | |
| esac | |
| # No local modifications allowed. | |
| case $(git diff-index --name-only HEAD) in | |
| '') ;; | |
| *) die 'this tree is dirty; commit your changes first';; | |
| esac | |
| # Ensure the current branch name is correct: | |
| curr_br=$(git rev-parse --symbolic-full-name HEAD) | |
| test "$curr_br" = "refs/heads/$branch" || die not on branch $branch | |
| # Extract package name from Makefile. | |
| Makefile=$builddir/Makefile | |
| pkg=$(sed -n 's/^PACKAGE = \(.*\)/\1/p' "$Makefile") \ | |
| || die "failed to determine package name from $Makefile" | |
| # Check that stub about to be replaced is in given NEWS line range. | |
| stub_line=$(sed -n "${stub_range}p" $news \ | |
| | env gl_stub="$noteworthy_stub" \ | |
| awk '$0 == ENVIRON["gl_stub"] { print NR }') | |
| test -n "$stub_line" \ | |
| || die "one of line(s) $stub_range of $news must be exactly '$noteworthy_stub'" | |
| # One more than the line number $stub_line is relative to. | |
| stub_offset=$(sed -n "${stub_range}{=;q;}" "$news") | |
| ## --------------- ## | |
| ## Then, changes. ## | |
| ## --------------- ## | |
| # Update NEWS to have today's date, plus desired version number and $type. | |
| env gl_ver=$ver gl_ver_stub="$ver_stub" \ | |
| gl_type=$type gl_type_stub="$type_stub" gl_date_stub="$date_stub" \ | |
| gl_offset="$stub_offset" gl_line="$stub_line" \ | |
| perl -MPOSIX -pi \ | |
| -e 'if ($. == $ENV{gl_offset} + $ENV{gl_line} - 1) {' \ | |
| -e ' my $today = strftime "%F", localtime time;' \ | |
| -e ' s/\Q$ENV{gl_ver_stub}/$ENV{gl_ver}/;' \ | |
| -e ' s/\Q$ENV{gl_date_stub}/($today)/;' \ | |
| -e ' s/\Q$ENV{gl_type_stub}/[$ENV{gl_type}]/;' \ | |
| -e '}' \ | |
| $news \ | |
| || die 'failed to update NEWS' | |
| printf "version %s\n\n* ${news}: Record release date.\n" "$ver" \ | |
| | git commit -F - -a || die 'git commit failed' | |
| git tag -s -m "$pkg $ver" v$ver HEAD || die 'git tag failed' | |
| # Local variables: | |
| # indent-tabs-mode: nil | |
| # eval: (add-hook 'before-save-hook 'time-stamp nil t) | |
| # time-stamp-start: "VERSION=" | |
| # time-stamp-format: "%Y-%02m-%02d.%02H" | |
| # time-stamp-time-zone: "UTC0" | |
| # time-stamp-end: " # UTC" | |
| # End: |