Skip to content

Instantly share code, notes, and snippets.

@eculver
Last active November 1, 2022 19:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eculver/a690bf0ead818d98b9c4fddb84d715f5 to your computer and use it in GitHub Desktop.
Save eculver/a690bf0ead818d98b9c4fddb84d715f5 to your computer and use it in GitHub Desktop.
Consul OSS→Enterprise Merge Shell Helpers
function ci-for-oss-ent-merge-branch {
local branch
local base
branch="${1:-}"
if [ -z "${branch}" ]; then
echo "branch name required"
echo "usage: ci-for-oss-ent-merge-branch <branch-name>"
return 1
fi
# There are two forms of branch names currently, one for when the merge is
# clean and the other when there were conflicts:
# oss/merge-$BRANCH-$SHA
# and
# oss/$BRANCH-$SHA
#
# Eventually, we should make these consistent but for now, let's just handle
# them both. The only difference is which field to use after cutting by '-'.
if grep -q '^oss/merge' <<< "$branch"; then
cut_flags=( "-f2" )
else
cut_flags=( "-f1" )
fi
base=$(echo "${branch}" | cut -d '/' -f2- | cut -d '-' ${cut_flags[@]})
if [ -z "${base}" ]; then
echo "could not parse base from ${branch}, bailing"
return 1
fi
open "https://app.circleci.com/pipelines/github/hashicorp/consul-enterprise?branch=${branch}"
}
function ci-for-all-oss-ent-merge-prs {
local branch
local pull_numbers
branch="${1:=main}"
if [ -z "${BOB_GITHUB_TOKEN}" ]; then
echo "must set \$BOB_GITHUB_TOKEN"
return 1
fi
echo "Fetching all open oss-merge PRs for ${branch} branch"
pull_numbers=$(_get_oss_ent_merge_pull_numbers "${branch}" || echo "failed")
num_pulls=$(echo "${pull_numbers}" | wc -l | awk '{print $1}')
echo -n "Found ${num_pulls} oss-merge PRs for ${branch}, continue? (y/n) "
read -q
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
return
fi
while read -r num; do
echo "Fetching PR info for #${num}"
merge_branch=$(curl -s \
-H "Authorization: token ${BOB_GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/hashicorp/consul-enterprise/pulls/${num} | jq -r '.head.ref')
if [ -z ${merge_branch} ]; then
echo "Could not find merge branch for PR #${num}, bailing"
return 1
fi
ci-for-oss-ent-merge-branch "${merge_branch}"
done <<< "${pull_numbers}"
}
function delete-oss-merge-branches {
echo "Delete branches with ^oss/ pattern? (y/n) "
read -q
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
return
fi
for b in $(git branch | awk '{$1=$1;print}' | egrep '^oss/'); do git branch -D "$b"; done
}
function rebase-oss-ent-merge-branch {
local branch
local pull_number
local base
branch="${1:-}"
pull_number="${2:-}"
if [ -z "${branch}" ]; then
echo "branch name required"
echo "usage: rebase-oss-ent-merge-branch <branch-name> <pull-number>"
return 1
fi
if [ -z "${pull_number}" ]; then
echo "pull_number name required"
echo "usage: rebase-oss-ent-merge-branch <branch-name> <pull-number>"
return 1
fi
# There are two forms of branch names currently, one for when the merge is
# clean and the other when there were conflicts:
# oss/merge-$BRANCH-$SHA
# and
# oss/$BRANCH-$SHA
#
# Eventually, we should make these consistent but for now, let's just handle
# them both. The only difference is which field to use after cutting by '-'.
if grep -q '^oss/merge' <<< "$branch"; then
cut_flags=( "-f2" )
else
cut_flags=( "-f1" )
fi
base=$(echo "${branch}" | cut -d '/' -f2- | cut -d '-' ${cut_flags[@]})
if [ -z "${base}" ]; then
echo "could not parse base from ${branch}, bailing"
return 1
fi
echo -n "fixing up ${branch} for ${base}, continue? (y/n) "
read -q
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
return
fi
git co "${base}"
git pull origin "${base}"
git co "${branch}"
if ! git merge "${base}"; then
echo
echo "Looks like there were conflicts, stopping here."
echo "When done resolving conflicts, run"
echo
echo " git push origin ${branch}"
echo
return 1
fi
git push origin "${branch}"
if git --no-pager diff "origin/${base}"; then
echo
echo -n "There are no changes between this branch and ${base}, would you like to close this PR (${pull_number})? (y/n)"
read -q
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
_close_ent_pr_with_comment "${pull_number}" "Closing as this was included in a subsequent merge."
# ensure the the PR branch gets cleaned up
git push origin --delete "${branch}"
fi
fi
}
function rebase-all-oss-ent-merge-prs {
local branch
local pull_numbers
branch="${1:=main}"
if [ -z "${BOB_GITHUB_TOKEN}" ]; then
echo "must set \$BOB_GITHUB_TOKEN"
return 1
fi
echo "Fetching all open oss-merge PRs for ${branch} branch"
pull_numbers=$(_get_oss_ent_merge_pull_numbers "${branch}" || echo "failed")
num_pulls=$(echo "${pull_numbers}" | wc -l | awk '{print $1}')
echo -n "Found ${num_pulls} oss-merge PRs for ${branch}, continue? (y/n) "
read -q
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
return
fi
while read -r num; do
echo "Fetching PR info for #${num}"
merge_branch=$(curl -s \
-H "Authorization: token ${BOB_GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/hashicorp/consul-enterprise/pulls/${num} | jq -r '.head.ref')
if [ -z ${merge_branch} ]; then
echo "Could not find merge branch for PR #${num}, bailing"
return 1
fi
rebase-oss-ent-merge-branch "${merge_branch}" "${num}"
done <<< "${pull_numbers}"
}
function _get_oss_ent_merge_pull_numbers {
local branch
branch="${1:=main}"
if [ -z "${BOB_GITHUB_TOKEN}" ]; then
echo "must set \$BOB_GITHUB_TOKEN"
return 1
fi
curl -s \
-H "Authorization: token ${BOB_GITHUB_TOKEN}" \
-H 'Accept: application/vnd.github.text-match+json' \
'https://api.github.com/search/issues?q=repo%3Ahashicorp/consul-enterprise+is%3Apr+label%3Aoss-merge+is%3Aopen+%5B'${branch}'%5D' | jq -r '.items[].number' | sort -n
}
function _close_ent_pr_with_comment {
local pull_number
local comment
local response
local exit_code
pull_number="${1:-}"
comment="${2:-}"
if [ -z "${pull_number}" ]; then
echo "pull_number required"
echo "usage: _close_ent_pr_with_comment <pull-number> <comment>"
return 1
fi
if [ -z "${comment}" ]; then
echo "comment required"
echo "usage: _close_ent_pr_with_comment <pull-number> <comment>"
return 1
fi
if [ -z "${BOB_GITHUB_TOKEN}" ]; then
echo "must set \$BOB_GITHUB_TOKEN"
return 1
fi
# create the comment, note: this does not do any escaping of the comment string
response=$(curl -s \
-H "Authorization: token ${BOB_GITHUB_TOKEN}" \
-H 'Accept: application/vnd.github.full+json' \
-X POST \
-d '{"body": "'${comment}'"}' \
'https://api.github.com/repos/hashicorp/consul-enterprise/issues/'${pull_number}'/comments')
exit_code=$?
if [ $exit_code -ne 0 ]; then
echo "API request to create a comment returned non-zero exit. Got: ${exit_code}."
echo
echo "Response:"
echo "${response}"
echo
exit 1
fi
# close the pull request
response=$(curl -s \
-H "Authorization: token ${BOB_GITHUB_TOKEN}" \
-H 'Accept: application/vnd.github.full+json' \
-X POST \
-d '{"state": "closed"}' \
'https://api.github.com/repos/hashicorp/consul-enterprise/pulls/'${pull_number})
exit_code=$?
if [ $exit_code -ne 0 ]; then
echo "API request to close the pull request returned non-zero exit. Got: ${exit_code}."
echo
echo "Response:"
echo "${response}"
echo
exit 1
fi
}
@eculver
Copy link
Author

eculver commented Aug 17, 2022

Note: In here, I'm using my BOB_GITHUB_TOKEN for the GitHub API interactions; I would suggest setting up your own if you don't already have this one.

Typical workflow goes like:

  • Load merge queue in GitHub UI
  • Find the "head" of the line blocking for a given branch, open the PR
  • Click the copy icon next to the branch name
  • Go to the shell and run rebase-oss-ent-merge-branch ${PASTE_BRANCH_NAME}
    -- This will kick you out to deal with merge conflicts if they need resolving
    -- Or it will just continue and push the rebased branch back to origin
  • For those that have conflicts, go back to the UI refresh and then click "Ready for Review"
  • Then click the auto-merge BUT be sure to select the option that creates a merge commit
  • Then approve the PR
  • The PR should auto-merge once tests pass

Then, once the queue is unblocked, you can use the rebase-all-oss-ent-merge-prs helper to roll through all the other PRs that should now be no-ops since the first PR at the head should have included it's change. You can just close these, but I like to create a merge commit anyways for posterity. Up to you. Either way, you should ensure they are actually no-ops first by rebasing all of them.

This makes dealing with a big queue much more manageable since you only have to unblock the head for each branch before mindlessly rolling through all of them at once and waiting for them to resolve themselves.

@eculver
Copy link
Author

eculver commented Aug 17, 2022

And then there's the delete-oss-merge-branches which removes all the branches that were created locally on the way. It only deletes them locally so it's safe to run.

@eculver
Copy link
Author

eculver commented Aug 17, 2022

I also baked confirmations in there to keep my attention on it since it can be a bit brain-dead after a few gos.

@eculver
Copy link
Author

eculver commented Aug 26, 2022

I added two new helpers just now:

  • ci-for-oss-ent-merge-branch - opens CircleCI for a merge branch
  • ci-for-all-oss-ent-merge-prs - opens CircleCI build UI for all open PRs

These are handy when you just need to kick builds.

@eculver
Copy link
Author

eculver commented Nov 1, 2022

I added support to detect whether the PR has a diff against the base and if not, to prompt the user for closing of the PR. If the user accepts the prompt, a comment with "Closing as this was included in a subsequent merge." is added, the PR is closed and the PR branch is deleted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment