Rebasing all relevant pull requests on a Github repo
If you chose rebasing as your primary git workflow strategy, it becomes important to keep your feature branches updated. Out of the box, Github offers automatic rebasing on many Dependabot pull requests. Dependabot PRs can be further rebased semi-automatically using the @dependabot rebase
command issued in the comments.
For all other PRs, you need to set up a bot or a Github Action to do this automatically. One such action that could be used is peter-evans/rebase
:
name: Rebase
on:
schedule:
- cron: '40 7 * * *'
workflow_dispatch:
jobs:
rebase:
runs-on: ubuntu-latest
steps:
- uses: peter-evans/rebase@v2
with:
# token: ${{ secrets.GH_PAT }}
# exclude-drafts: true
exclude-labels: |
no-rebase
dependencies
The action works reliably and allows to skip all Dependabot PRs (tagged with a dependencies
label). The latter is important to do because the Dependabot refuses to automatically update its PR once you make any manual changes to it, like rebasing it yourself.
The token
parameter allows the action to push on your behalf. Otherwise, Github Actions pushes do not trigger other Github Actions (mainly, CI). This means that you will only be able to know if the code has any conflicts with the main
branch, but not whether it is still safe to merge.
If this approach is not viable for you (e.g., you don’t have admin rights on the repo or don’t trust the action with your PAT token), you need to rebase on your machine. Below I provide a script (licensed under the Simplified BSD license) that rebases all PRs except those made by Dependabot:
#!/usr/bin/env bash
# Copyright 2023 Andrew Berezovskyi
# SPDX-License-Identifier: BSD-2-Clause
set -uo pipefail
set -e
# set -x
err=0
git_main_branch () {
command git rev-parse --git-dir &> /dev/null || return
local ref
for ref in refs/{heads,remotes/{origin,upstream}}/{main,trunk,mainline,default}
do
if command git show-ref -q --verify $ref
then
echo ${ref:t}
return
fi
done
echo master
}
if ! eval git diff --quiet ; then
>&2 echo "Git repo is not clean. Commit all the changes first."
exit 1
fi
CURR_BR="$(git branch --show-current)"
MAIN_BR="$(git_main_branch)"
trap 'git checkout "${CURR_BR}"' err exit SIGINT SIGTERM
git checkout "${MAIN_BR}"
git pull --ff-only
PR_LIST="$(sort <(gh pr list) <(gh pr list -A app/dependabot) | uniq -u | cut -f1)"
while IFS="" read -r pr || [ -n "$pr" ]
do
printf 'Processing PR #%s\n' "$pr"
gh pr checkout "$pr" || err=$?
# --reapply-cherry-picks if needed
# https://stackoverflow.com/questions/61905448/git-cherry-pick-and-then-rebase
[ "$err" -eq "128" ] && git rebase
# rebase -i if needed
git rebase "${MAIN_BR}" && git pull --rebase origin "${MAIN_BR}" && git push --force-with-lease
done <<< "${PR_LIST}"
Some explanation for the script:
git pull --ff-only
is done under the assumption that you don’t commit to the main branch yourself and thus, no conflicts should ever exist on that branch.git_main_branch()
allows the script to be used with the repos that usemain
,master
, ortrunk
main branch.sort X Y | uniq -u
pattern is used because Github has a bad support for negative patterns as of 2023.git push --force-with-lease
prevents you from overwriting any commits during a force-push that were made by someone else, thus providing some degree of safety.[ "$err" -eq "128" ] && git rebase
is done to rebase your local feature branch first, in case someone else made a similar rebase before you.trap "git checkout '${CURR_BR}'"
will restore the checked out branch you were on before running the script (whether the scripts succeeds or fails).set -u
makes the script fail if any undefined variables are used,set -o pipefail
makes a piped command fail if any of its stages fail (e.g. if gzip fails intar | gzip | gpg
),set -e
terminates the script immediately after the first error, andset -x
could be used to print every command that this script runs.
You can make the script available anywhere in your system if you put the script under /usr/local/bin/
(or $HOME/.local/bin
just for your user account) and make it executable: chmod +x /usr/local/bin/git-rebase-all
.
Finally, this script only requires the Github CLI and plain old Bash to be installed on your system for this to work. Happy rebasing! 🦸♂️
Comments