#!/bin/bash ### automerge - automatically merge the Emacs release branch to master ## Copyright (C) 2018-2021 Free Software Foundation, Inc. ## Author: Glenn Morris <rgm@gnu.org> ## Maintainer: emacs-devel@gnu.org ## This file is part of GNU Emacs. ## GNU Emacs 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. ## GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ### Commentary: ## Automatically merge the Emacs release branch to master. ## If the merge succeeds, optionally build and test the results, ## and then push it. ## Intended usage: ## Have a dedicated git directory just for this. ## Have a cron job that calls this script with -r -p. ## ## Modifying a running shell script can have unpredictable results, ## so the paranoid will first make a copy of this script, and then run ## it with the -d option in the repository directory, in case a pull ## updates this script while it is working. die () # write error to stderr and exit { [ $# -gt 0 ] && echo "$PN: $@" >&2 exit 1 } PN=${0##*/} # basename of script PD=${0%/*} [ "$PD" = "$0" ] && PD=. # if PATH includes PWD usage () { cat 1>&2 <<EOF Usage: ${PN} [-b] [-d] [-e emacs] [-n nmin] [-p] [-r] [-t] [-- mflags] Merge the Emacs release branch to master. Passes any non-option args to make (eg -- -j2). Options: -d: no initial cd to parent of script directory -e: Emacs executable to use for the initial merge (default $emacs) -n: minimum number of commits to try merging (default $nmin) -b: try to build after merging -t: try to check after building -p: if merge, build, check all succeed, push when finished (caution!) -r: start by doing a hard reset (caution!) and pull EOF exit 1 } ## Defaults. emacs=emacs nmin=10 build= test= push= quiet= reset= nocd= while getopts ":hbde:n:pqrt" option ; do case $option in (h) usage ;; (b) build=1 ;; (d) nocd=1 ;; (e) emacs=$OPTARG ;; (n) nmin=$OPTARG ;; (p) push=1 ;; (q) quiet=1 ;; (r) reset=1 ;; (t) test=1 ;; (\?) die "Bad option -$OPTARG" ;; (:) die "Option -$OPTARG requires an argument" ;; (*) die "getopts error" ;; esac done shift $(( --OPTIND )) OPTIND=1 [ "$nocd" ] || { cd $PD # this should be the admin directory cd ../ } [ -d admin ] || die "Could not locate admin directory" [ -e .git ] || die "No .git" ## Does not work 100% because a lot of Emacs batch output comes on ## stderr (?). [ "$quiet" ] && exec 1> /dev/null [ "$push" ] && test=1 [ "$test" ] && build=1 tempfile=/tmp/$PN.$$ trap "rm -f $tempfile 2> /dev/null" EXIT [ -e Makefile ] && [ "$build" ] && { echo "Cleaning..." make maintainer-clean >& /dev/null } [ "$reset" ] && { echo "Resetting..." git reset -q --hard origin/master || die "reset error" echo "Pulling..." git pull -q --ff-only || die "pull error" } rev=$(git rev-parse HEAD) [ $(git rev-parse @{u}) = $rev ] || die "Local state does not match origin" merge () { echo "Merging..." if $emacs --batch -Q -l ./admin/gitmerge.el \ --eval "(setq gitmerge-minimum-missing $nmin)" -f gitmerge \ >| $tempfile 2>&1; then echo "merged ok" return 0 else grep -E "Nothing to merge|Number of missing commits" $tempfile && \ exit 0 cat "$tempfile" 1>&2 die "merge error" fi } merge ## FIXME it would be better to trap this in gitmerge. ## NEWS should never be modified, only eg NEWS.26. git diff --stat --cached origin/master | grep -q "etc/NEWS " && \ die "etc/NEWS has been modified" [ "$build" ] || exit 0 echo "Running autoreconf..." autoreconf -i -I m4 2>| $tempfile retval=$? ## Annoyingly, autoreconf puts the "installing `./foo' messages on stderr. if [ "$quiet" ]; then grep -v 'installing `\.' $tempfile 1>&2 else cat "$tempfile" 1>&2 fi [ $retval -ne 0 ] && die "autoreconf error" echo "Running ./configure..." ## Minimize required packages. ./configure --without-x || die "configure error" echo "Building..." make "$@" || die "make error" echo "Build finished ok" [ "$test" ] || exit 0 echo "Testing..." ## We just want a fast pass/fail, we don't want to debug. make "$@" check TEST_LOAD_EL=no || die "check error" echo "Tests finished ok" [ "$push" ] || exit 0 ## In case someone else pushed while we were working. echo "Checking for remote changes..." git fetch || die "fetch error" [ $(git rev-parse @{u}) = $rev ] || { echo "Upstream has changed" ## Rebasing would be incorrect, since it would rewrite the ## (already published) release branch commits. ## Ref eg https://lists.gnu.org/r/emacs-devel/2014-12/msg01435.html ## Instead, we throw away what we just did, and do the merge again. echo "Resetting..." git reset --hard $rev echo "Pulling..." git pull --ff-only || die "pull error" merge ## If the merge finished ok again, we don't bother doing a second ## build and test. } echo "Pushing..." git push || die "push error" exit 0 ### automerge ends here