summaryrefslogtreecommitdiffstats
path: root/scripts/backport
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/backport')
-rwxr-xr-xscripts/backport146
1 files changed, 146 insertions, 0 deletions
diff --git a/scripts/backport b/scripts/backport
new file mode 100755
index 0000000..4f60140
--- /dev/null
+++ b/scripts/backport
@@ -0,0 +1,146 @@
+#!/usr/bin/env bash
+
+USAGE="Usage: ${0##*/} <last> <commit> [...]"
+START="$PWD"
+LAST=
+UPSTREAM=
+COMMIT=
+BRANCH=
+
+die() {
+ [ "$#" -eq 0 ] || echo "$*" >&2
+ exit 1
+}
+
+err() {
+ echo "$*" >&2
+}
+
+quit() {
+ [ "$#" -eq 0 ] || echo "$*"
+ exit 0
+}
+
+short() {
+ git rev-parse --short "$1"
+}
+
+# returns the latest commit ID in $REPLY. Returns 0 on success, non-zero on
+# failure with $REPLY empty.
+get_last_commit() {
+ REPLY=$(git rev-parse HEAD)
+ test -n "$REPLY"
+}
+
+# returns the name of the current branch (1.8, 1.9, etc) in $REPLY. Returns 0
+# on success, non-zero on failure with $REPLY empty.
+get_branch() {
+ local major subver ext
+ REPLY=$(git describe --tags HEAD --abbrev=0 2>/dev/null)
+ REPLY=${REPLY#v}
+ subver=${REPLY#[0-9]*.[0-9]*[-.]*[0-9].}
+ [ "${subver}" != "${REPLY}" ] || subver=""
+ major=${REPLY%.$subver}
+ ext=${major#*[0-9].*[0-9]}
+ REPLY=${major%${ext}}
+ test -n "$REPLY"
+}
+
+# returns the path to the next "up" remote in $REPLY, and zero on success
+# or non-zero when the last one was reached.
+up() {
+ REPLY=$(git remote -v | awk '/^up\t.*\(fetch\)$/{print $2}')
+ test -n "$REPLY"
+}
+
+# returns the path to the next "down" remote in $REPLY, and zero on success
+# or non-zero when the last one was reached.
+down() {
+ REPLY=$(git remote -v | awk '/^down\t.*\(fetch\)$/{print $2}')
+ test -n "$REPLY"
+}
+
+# verifies that the repository is clean of any pending changes
+check_clean() {
+ test -z "$(git status -s -uno)"
+}
+
+# verifies that HEAD is the master
+check_master() {
+ test "$(git rev-parse --verify -q HEAD 2>&1)" = "$(git rev-parse --verify -q master 2>&1)"
+}
+
+# tries to switch to the master branch, only if the current one is clean. Dies on failure.
+switch_master() {
+ check_clean || die "$BRANCH: local changes, stopping on commit $COMMIT (upstream $UPSTREAM)"
+ git checkout master >/dev/null 2>&1 || die "$BRANCH: failed to checkout master, stopping on commit $COMMIT (upstream $UPSTREAM)"
+}
+
+# walk up to the first repo
+walk_up() {
+ cd "$START"
+}
+
+# updates the "up" remote repository. Returns non-zero on error.
+update_up() {
+ git remote update up >/dev/null 2>&1
+}
+
+# backports commit "$1" with a signed-off by tag. In case of failure, aborts
+# the change and returns non-zero. Unneeded cherry-picks do return an error
+# because we don't want to accidentally backport the latest commit instead of
+# this one, and we don't know this one's ID.
+backport_commit() {
+ local empty=1
+
+ if ! git cherry-pick -sx "$1"; then
+ [ -n "$(git diff)" -o -n "$(git diff HEAD)" ] || empty=0
+ git cherry-pick --abort
+ return 1
+ fi
+}
+
+[ "$1" != "-h" -a "$1" != "--help" ] || quit "$USAGE"
+[ -n "$1" -a -n "$2" ] || die "$USAGE"
+
+LAST="$1"
+shift
+
+# go back to the root of the repo
+cd $(git rev-parse --show-toplevel)
+START="$PWD"
+
+while [ -n "$1" ]; do
+ UPSTREAM="$(short $1)"
+ [ -n "$UPSTREAM" ] || die "branch $BRANCH: unknown commit ID $1, cannot backport."
+ COMMIT="$UPSTREAM"
+ BRANCH="-source-"
+ while :; do
+ if ! down; then
+ err "branch $BRANCH: can't go further, is repository 'down' properly set ?"
+ break
+ fi
+
+ cd "$REPLY" || die "Failed to 'cd' to '$REPLY' from '$PWD', is repository 'down' properly set ?"
+
+ check_clean || die "Local changes in $PWD, stopping before backporting commit $COMMIT (upstream $UPSTREAM)"
+
+ check_master || switch_master || die "Cannot switch to 'master' branch in $PWD, stopping before backporting commit $COMMIT (upstream $UPSTREAM)"
+ get_branch || die "Failed to get branch name in $PWD, stopping before backporting commit $COMMIT (upstream $UPSTREAM)"
+ BRANCH="$REPLY"
+
+ update_up || die "$BRANCH: failed to update repository 'up', stopping before backporting commit $COMMIT (upstream $UPSTREAM)"
+
+ backport_commit "$COMMIT" || die "$BRANCH: failed to backport commit $COMMIT (upstream $UPSTREAM). Leaving repository $PWD intact."
+
+ if [ "$BRANCH" = "$LAST" ]; then
+ # reached the stop point, don't apply further
+ break
+ fi
+
+ get_last_commit || die "$BRANCH: cannot retrieve last commit ID, stopping after backporting commit $COMMIT (upstream $UPSTREAM)"
+ COMMIT="$(short $REPLY)"
+ done
+ walk_up || die "Failed to go back to $PWD, stopping *after* backporting upstream $UPSTREAM"
+ shift
+done