Willy Tarreau | be9b00f | 2020-02-05 04:45:18 +0100 | [diff] [blame] | 1 | #!/usr/bin/env bash |
Willy Tarreau | e77a13a | 2020-02-04 13:50:36 +0100 | [diff] [blame] | 2 | |
| 3 | USAGE="Usage: ${0##*/} <last> <commit> [...]" |
| 4 | START="$PWD" |
| 5 | LAST= |
| 6 | UPSTREAM= |
| 7 | COMMIT= |
| 8 | BRANCH= |
| 9 | |
| 10 | die() { |
| 11 | [ "$#" -eq 0 ] || echo "$*" >&2 |
| 12 | exit 1 |
| 13 | } |
| 14 | |
| 15 | err() { |
| 16 | echo "$*" >&2 |
| 17 | } |
| 18 | |
| 19 | quit() { |
| 20 | [ "$#" -eq 0 ] || echo "$*" |
| 21 | exit 0 |
| 22 | } |
| 23 | |
| 24 | short() { |
| 25 | # git rev-parse --short $1 |
| 26 | echo "${1::8}" |
| 27 | } |
| 28 | |
| 29 | # returns the latest commit ID in $REPLY. Returns 0 on success, non-zero on |
| 30 | # failure with $REPLY empty. |
| 31 | get_last_commit() { |
| 32 | REPLY=$(git rev-parse HEAD) |
| 33 | test -n "$REPLY" |
| 34 | } |
| 35 | |
| 36 | # returns the name of the current branch (1.8, 1.9, etc) in $REPLY. Returns 0 |
| 37 | # on success, non-zero on failure with $REPLY empty. |
| 38 | get_branch() { |
| 39 | local major subver ext |
| 40 | REPLY=$(git describe --tags HEAD --abbrev=0 2>/dev/null) |
| 41 | REPLY=${REPLY#v} |
| 42 | subver=${REPLY#[0-9]*.[0-9]*[-.]*[0-9].} |
| 43 | [ "${subver}" != "${REPLY}" ] || subver="" |
| 44 | major=${REPLY%.$subver} |
| 45 | ext=${major#*[0-9].*[0-9]} |
| 46 | REPLY=${major%${ext}} |
| 47 | test -n "$REPLY" |
| 48 | } |
| 49 | |
| 50 | # returns the path to the next "up" remote in $REPLY, and zero on success |
| 51 | # or non-zero when the last one was reached. |
| 52 | up() { |
| 53 | REPLY=$(git remote -v | awk '/^up\t.*\(fetch\)$/{print $2}') |
| 54 | test -n "$REPLY" |
| 55 | } |
| 56 | |
| 57 | # returns the path to the next "down" remote in $REPLY, and zero on success |
| 58 | # or non-zero when the last one was reached. |
| 59 | down() { |
| 60 | REPLY=$(git remote -v | awk '/^down\t.*\(fetch\)$/{print $2}') |
| 61 | test -n "$REPLY" |
| 62 | } |
| 63 | |
| 64 | # verifies that the repository is clean of any pending changes |
| 65 | check_clean() { |
| 66 | test -z "$(git status -s -uno)" |
| 67 | } |
| 68 | |
| 69 | # verifies that HEAD is the master |
| 70 | check_master() { |
| 71 | test "$(git rev-parse --verify -q HEAD 2>&1)" != "$(git rev-parse --verify -q master 2>&1)" |
| 72 | } |
| 73 | |
| 74 | # tries to switch to the master branch, only if the current one is clean. Dies on failure. |
| 75 | switch_master() { |
| 76 | check_clean || die "$BRANCH: local changes, stopping on commit $COMMIT (upstream $UPSTREAM)" |
| 77 | git checkout master >/dev/null 2>&1 || die "$BRANCH: failed to checkout master, stopping on commit $COMMIT (upstream $UPSTREAM)" |
| 78 | } |
| 79 | |
| 80 | # walk up to the first repo |
| 81 | walk_up() { |
| 82 | cd "$START" |
| 83 | } |
| 84 | |
| 85 | # updates the "up" remote repository. Returns non-zero on error. |
| 86 | update_up() { |
| 87 | git remote update up >/dev/null 2>&1 |
| 88 | } |
| 89 | |
| 90 | # backports commit "$1" with a signed-off by tag. In case of failure, aborts |
| 91 | # the change and returns non-zero. Unneeded cherry-picks do return an error |
| 92 | # because we don't want to accidently backport the latest commit instead of |
| 93 | # this one, and we don't know this one's ID. |
| 94 | backport_commit() { |
| 95 | local empty=1 |
| 96 | |
| 97 | if ! git cherry-pick -sx "$1"; then |
| 98 | [ -n "$(git diff)" -o -n "$(git diff HEAD)" ] || empty=0 |
| 99 | git cherry-pick --abort |
| 100 | return 1 |
| 101 | fi |
| 102 | } |
| 103 | |
| 104 | [ "$1" != "-h" -a "$1" != "--help" ] || quit "$USAGE" |
| 105 | [ -n "$1" -a -n "$2" ] || die "$USAGE" |
| 106 | |
| 107 | LAST="$1" |
| 108 | shift |
| 109 | |
| 110 | # go back to the root of the repo |
| 111 | cd $(git rev-parse --show-toplevel) |
| 112 | START="$PWD" |
| 113 | |
| 114 | while [ -n "$1" ]; do |
| 115 | UPSTREAM="$1" |
| 116 | COMMIT="$1" |
| 117 | BRANCH="-source-" |
| 118 | while :; do |
| 119 | if ! down; then |
| 120 | err "branch $BRANCH: can't go further, is repository 'down' properly set ?" |
| 121 | break |
| 122 | fi |
| 123 | |
| 124 | cd "$REPLY" || die "Failed to 'cd' to '$REPLY' from '$PWD', is repository 'down' properly set ?" |
| 125 | |
| 126 | check_clean || die "Local changes in $PWD, stopping before backporting commit $COMMIT (upstream $UPSTREAM)" |
| 127 | |
| 128 | check_master || switch_master || die "Cannot switch to 'master' branch in $PWD, stopping before backporting commit $COMMIT (upstream $UPSTREAM)" |
| 129 | get_branch || die "Failed to get branch name in $PWD, stopping before backporting commit $COMMIT (upstream $UPSTREAM)" |
| 130 | BRANCH="$REPLY" |
| 131 | |
| 132 | update_up || die "$BRANCH: failed to update repository 'up', stopping before backporting commit $COMMIT (upstream $UPSTREAM)" |
| 133 | |
| 134 | backport_commit "$COMMIT" || die "$BRANCH: failed to backport commit $COMMIT (upstream $UPSTREAM). Leaving repository $PWD intact." |
| 135 | |
| 136 | if [ "$BRANCH" = "$LAST" ]; then |
| 137 | # reached the stop point, don't apply further |
| 138 | break |
| 139 | fi |
| 140 | |
| 141 | get_last_commit || die "$BRANCH: cannot retrieve last commit ID, stopping after backporting commit $COMMIT (upstream $UPSTREAM)" |
| 142 | COMMIT="$REPLY" |
| 143 | done |
| 144 | walk_up || die "Failed to go back to $PWD, stopping *after* backporting upstream $UPSTREAM" |
| 145 | shift |
| 146 | done |