You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
4.1 KiB
147 lines
4.1 KiB
5 months ago
|
#!/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
|