Skip to content

Instantly share code, notes, and snippets.

@douglascayers
Last active September 25, 2023 13:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save douglascayers/fefd09a9a317796aaec22a309228c59f to your computer and use it in GitHub Desktop.
Save douglascayers/fefd09a9a317796aaec22a309228c59f to your computer and use it in GitHub Desktop.
Iteratively merges into local branch a list of pull requests in sequence using the GitHub and Git CLIs
#!/bin/bash
set -e
# Name of the branch to merge all the pull requests into.
MERGE_BRANCH="staging"
# Name of the branch to hard reset to before merging others into it.
RESET_BRANCH="master"
# Multi-line string of open, non-draft pull requests to merge locally.
# Each line is a space-delimited string of "pr_number base_branch head_branch".
PULL_REQUESTS=$(
gh pr list \
--state open \
--draft=false \
--json number,baseRefName,headRefName \
-q '.[] | "\(.number) \(.baseRefName) \(.headRefName)"'
)
# Map each line of the pull requests query result into a simple array
# so that we can more easily iterate the data later.
PR_INFO=()
while read -r pr_number base_branch head_branch; do
PR_INFO+=("$pr_number $base_branch $head_branch")
done <<< "$PULL_REQUESTS"
# Simple array of pull request numbers in the order they should be merged.
ORDERED_PRS=()
# ----------------------------------------------------------------------------
# $1 = message to log
function log_info() {
echo "" >&2
echo -e "> ${1}" >&2
echo "" >&2
}
# Fetches all history.
function git_fetch() {
git fetch --all --force --prune --prune-tags --no-tags --quiet
}
# Checks out a pull request into the current git repo.
# $1 = branch to checkout and merge pull requests into (required)
# $2 = remote branch to hard reset to (defaults to $1)
function git_checkout() {
local branch="${1}"
local reset_to="${2:-$1}"
log_info "checking out branch '${branch}'"
git checkout -B ${branch} origin/${branch}
git reset --hard origin/${reset_to}
}
# Get the head branch name of the currently checked out git repository.
# $1 = pull request url or number (optional)
function get_head_ref() {
local pr_id="${1}"
gh pr view ${pr_id} --json headRefName -q '.headRefName'
}
# $1 = pull request url or number (optional)
function merge_head_ref() {
local pr_id="${1}"
local head_ref="$(get_head_ref ${pr_id})"
log_info "merging branch '${head_ref}'"
git merge "origin/${head_ref}" --no-edit --no-ff --no-verify
}
# Iterates the pull requests to identify the dependency order for merging.
# Outputs the pull request numbers to new array variable `ORDERED_PRS`.
function calculate_merge_order() {
local branch="${1}"
for pr_info in "${PR_INFO[@]}"; do
read -r pr_number base_branch head_branch <<< "${pr_info}"
if [[ "${base_branch}" == "${branch}" ]]; then
ORDERED_PRS+=("${pr_number}")
calculate_merge_order "${head_branch}"
fi
done
}
# Iterates the pull requests in their dependency order and merges them locally.
function merge_ordered_prs() {
for pr_number in "${ORDERED_PRS[@]}"; do
head_ref=$(get_head_ref ${pr_number})
merge_result=$(merge_head_ref ${head_ref})
has_conflicts=$(git status | grep --quiet "fix conflicts" && echo 1 || echo 0)
if [[ "${has_conflicts}" == "1" ]]; then
log_info "merge conflicts, aborting"
git status
exit 1
fi
done
}
# ----------------------------------------------------------------------------
git_fetch
git_checkout "${MERGE_BRANCH}" "${RESET_BRANCH}"
calculate_merge_order "${RESET_BRANCH}"
merge_ordered_prs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment