One of my favorite "damage control" scripts on small teams (10-20 devs) is a tool that takes the latest master code and attempts to merge it into all other active branches. This has a few concrete use cases.
Stopping CI errors from propagating to random feature branches. If someone merges code with a broken test (e.g., test or SDK generation failures), anyone who subsequently merges master into their feature branch will inherit this failure. Typically, they will assume the failure is related to their own changes and waste time debugging it. Therefore, it makes sense to have a way to quickly merge a fixed master into all branches before too many people get confused.
Major dependency changes (e.g., Django versions). Switching branches between code with Django v4 vs. v5, for example, is a frustrating experience. It requires re-installing libraries and carrying the mental load of tracking version differences. Furthermore, there is a risk of bugs arising when engineers develop against one version, only to realize their changes are incompatible with master when it is too late.
Stopping serious bugs quickly. This is vital for bugs that cause the development or staging environment to crash. You need a way to propagate the fix immediately.
I've copied the script in full below, but here is the gist of what it does:
Fetches all open GitHub PRs under active development. We don't look at all branches because most are irrelevant for these purposes.
Handles migration conflicts using Django’s built-in tools and our own layers on top of it. (That's a topic for another blog post!)
Attempts to merge each branch in a loop. If one branch has conflicts, it skips it and moves on to the next one—but creates a report of PRs with conflicts so you can communicate this to the team for immediate triage.
Pushes it automatically to GitHub
The code
#!/usr/bin/env bash
# PURPOSE: Sometimes every branch needs the latest master in order
# to avoid a serious breaking change -- such as a library upgrade without
# which it would be impossible to stage a PR for review. Another situation
# is when failing tests are contagious on many branches due to making
# it into master.
#
# This script merges master into all PRs that open in GitHub.
#
# It provides a report of branches that were successfully merged and which
# ones had conflicts. The ones with conflicts will need to be manually addressed.
#
# USAGE: ./bin/git/merge_master_into_all_branches
merged_branches=()
conflicted_branches=()
# Exit script if any uncommited files or changes
if ! git diff-index --quiet HEAD --; then
echo "You have uncommitted changes. Please commit or stash them before running this script."
exit 1
fi
# Loop through a list of open pull requests
while read -r pr; do
branch=$(jq -r '.headRefName' <<< "$pr")
title=$(jq -r '.title' <<< "$pr")
echo
echo "Merging master into $branch..."
echo
# Checkout the pull request branch, going to next iteration if failed
# skip the hook to make faster
git -c core.hooksPath=/dev/null checkout "$branch" || continue
# Pull latest changes
git pull
# Fetch the latest changes from master
git fetch origin master
# Merge master into the current branch
if git merge master --no-edit; then
# Handle merge migrations
./bin/manage makemigrations --merge --noinput
# Commit only if there are changes to the merge files
find -E . -type f -regex '.*/[0-9]+_merge_[0-9]+_[0-9]+\.py' | xargs -o git add
if ! git diff --exit-code --quiet; then
echo -e "\nCommitting merge migrations...\n\n"
git commit -m "Add merge migration via bin/merge_master_into_all_branches"
fi
# Push changes to origin
git push origin "$branch"
echo "Successfully merged and pushed $branch."
merged_branches+=("$title")
else
echo "-----Merge conflict in $branch."
# Handle merge conflicts
# Add the conflicted branch to the list
conflicted_branches+=("$title")
# Continue to the next pull request
git reset --hard
git clean -fd
echo "Failed to merge master into $branch. Moving on to the next PR."
continue
fi
# Need to use process substitution with while loop in order to avoid clobbering conflicted_branches array
done < <(gh pr list --state open --json number,title,headRefName -q '.[]')
# Print a report of branches with merge conflicts
echo -e "\nBranches with merge conflicts:"
for conflicted_branch in "${conflicted_branches[@]}"; do
echo "- $conflicted_branch"
done
# Print a report of branches successfully merged
if [ ${#merged_branches[@]} -gt 0 ]; then
echo -e "\nBranches successfully merged:"
for merged_branch in "${merged_branches[@]}"; do
echo "- $merged_branch"
done
else
echo "No branches successfully merged."
fi