blob: 36cdb577346e928417b0d7f1ce0eff1491c78330 [file] [log] [blame]
#!/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 accidently 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