backport: Fix the commit-ids that are used.

The commit hashes that appear in the `pull/12345/head` ref are the
ones _before_ any final rebase occurs, and as such may not match to
any commit hashes which exist in `main`.

Use the GitHub GraphQL API to pull the last "merge commit" on the PR,
which is post-rebase, and use that as the target commit when
cherry-picking.  Then walk backwards from that commit, including every
sequential commit which is still associated with the PR; we do this
because during the merge, commits may be added or removed, so the PR
is not reliable in the commit count.
This commit is contained in:
Alex Vandiver 2024-05-06 20:43:30 +00:00 committed by Tim Abbott
parent f13e94d9ae
commit 06543b54b1
1 changed files with 78 additions and 11 deletions

View File

@ -1,4 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
usage() { usage() {
@ -11,25 +12,91 @@ the current branch using 'git cherry-pick -x'.
Typical usage is: Typical usage is:
git fetch upstream git fetch upstream
git checkout -b 8.x upstream/8.x git checkout -b 8.x upstream/8.x
$0 FIRST_PR_ID FIRST_PR_COMMIT_COUNT $0 FIRST_PR_ID
$0 SECOND_PR_ID SECOND_PR_COMMIT_COUNT $0 SECOND_PR_ID
git push origin +HEAD:backport-changes git push origin +HEAD:backport-changes
EOF EOF
exit 1 exit 1
} }
remote_default="$(git config zulip.zulipRemote || echo upstream)" pr_id="$1"
request_id="$1" if [ -z "$pr_id" ]; then
commit_count="$2"
if [ -z "$request_id" ] || [ -z "$commit_count" ]; then
usage usage
fi fi
remote=${3:-"$remote_default"} fail() {
echo "$1"
exit 1
}
type gh >/dev/null 2>&1 \
|| fail "The 'gh' CLI tool is not installed; see https://cli.github.com/"
gh auth status 2>/dev/null || fail "Not authenticated to github"
# Find the last commit that was merged. We will look back in `main`
# for other commits from the same PR.
# shellcheck disable=SC2016
merge_commit="$(
gh api graphql \
-q '.data.repository.pullRequest.timelineItems.nodes[0].commit.oid' \
-F pr_id="$1" \
-f query='
query($pr_id:Int!) {
repository(name: "zulip", owner: "zulip") {
pullRequest(number:$pr_id) {
timelineItems(last:1, itemTypes: [MERGED_EVENT]) {
nodes {
... on MergedEvent {
commit {
oid
}
}
}
}
}
}
}
'
)"
# We cannot trust the "commits" count on the PR, since only part of it
# may get merged, or it may have commits squashed during the merge.
# Walk backwards on `main` from the merge commit we found, checking
# that each of those commits is still associated with the same PR.
commit_id="$merge_commit"
while true; do
# shellcheck disable=SC2016
this_pr="$(gh api graphql -F "commit_id=$commit_id 0" \
--jq '.data.repository.ref.target.history.edges[].node.associatedPullRequests.nodes[].number' \
-f query='
query($commit_id: String!) {
repository(owner: "zulip", name:"zulip") {
ref(qualifiedName:"main") {
target {
... on Commit {
history(first:1, after: $commit_id) {
edges {
node {
oid
associatedPullRequests(first: 1) {
nodes {
number
}
}
}
}
}
}
}
}
}
}')"
if [ "$this_pr" != "$pr_id" ]; then
break
fi
commit_id="$(git rev-parse "$commit_id"~)"
done
set -x set -x
git cherry-pick -x "$commit_id~..$merge_commit"
git fetch "$remote" "pull/$request_id/head"
git cherry-pick -x FETCH_HEAD~"$commit_count"..FETCH_HEAD