2024-10-03 17:49:44 -06:00
#!/usr/bin/env bash
# Check that a PR doesn't include commits from other development branches.
# Fails with next steps if it does
set -euo pipefail
tmp = $( mktemp -d)
trap 'rm -rf "$tmp"' exit
SCRIPT_DIR = $( dirname " $0 " )
log( ) {
echo " $@ " >& 2
}
# Small helper to check whether an element is in a list
# Usage: `elementIn foo "${list[@]}"`
elementIn( ) {
local e match = $1
shift
for e; do
if [ [ " $e " = = " $match " ] ] ; then
return 0
fi
done
return 1
}
if ( ( $# < 6 ) ) ; then
log " Usage: $0 LOCAL_REPO HEAD_REF BASE_REPO BASE_BRANCH PR_REPO PR_BRANCH "
exit 1
fi
localRepo = $1
headRef = $2
baseRepo = $3
baseBranch = $4
prRepo = $5
prBranch = $6
# All development branches
devBranchPatterns = ( )
while read -r pattern; do
if [ [ " $pattern " != '#' * ] ] ; then
devBranchPatterns += ( " $pattern " )
fi
done < " $SCRIPT_DIR /dev-branches.txt "
git -C " $localRepo " branch --list --format "%(refname:short)" " ${ devBranchPatterns [@] } " > " $tmp /dev-branches "
readarray -t devBranches < " $tmp /dev-branches "
if [ [ " $baseRepo " = = " $prRepo " ] ] && elementIn " $prBranch " " ${ devBranches [@] } " ; then
log " This PR merges $prBranch into $baseBranch , no commit check necessary "
exit 0
fi
# The current merge base of the PR
prMergeBase = $( git -C " $localRepo " merge-base " $baseBranch " " $headRef " )
log " The PR's merge base with the base branch $baseBranch is $prMergeBase "
# This is purely for debugging
git -C " $localRepo " rev-list --reverse " $baseBranch " .." $headRef " > " $tmp /pr-commits "
log " The PR includes these $( wc -l < " $tmp /pr-commits " ) commits: "
cat <" $tmp /pr-commits " >& 2
for testBranch in " ${ devBranches [@] } " ; do
if [ [ -z " $( git -C " $localRepo " rev-list -1 --since= "1 month ago" " $testBranch " ) " ] ] ; then
log " Not checking $testBranch , was inactive for the last month "
continue
fi
log " Checking if commits from $testBranch are included in the PR "
# We need to check for any commits that are in the PR which are also in the test branch.
# We could check each commit from the PR individually, but that's unnecessarily slow.
#
# This does _almost_ what we want: `git rev-list --count headRef testBranch ^baseBranch`,
# except that it includes commits that are reachable from _either_ headRef or testBranch,
# instead of restricting it to ones reachable by both
# Easily fixable though, because we can use `git merge-base testBranch headRef`
# to get the least common ancestor (aka merge base) commit reachable by both.
# If the branch being tested is indeed the right base branch,
# this is then also the commit from that branch that the PR is based on top of.
testMergeBase = $( git -C " $localRepo " merge-base " $testBranch " " $headRef " )
# And then use the `git rev-list --count`, but replacing the non-working
# `headRef testBranch` with the merge base of the two.
extraCommits = $( git -C " $localRepo " rev-list --count " $testMergeBase " ^" $baseBranch " )
if ( ( extraCommits != 0 ) ) ; then
log -e "\e[33m"
echo " The PR's base branch is set to $baseBranch , but $extraCommits commits from the $testBranch branch are included. Make sure you know the [right base branch for your changes](https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#branch-conventions), then: "
echo " - If the changes should go to the $testBranch branch, [change the base branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-base-branch-of-a-pull-request) to $testBranch "
2024-10-09 17:26:00 -06:00
echo " - If the changes should go to the $baseBranch branch, rebase your PR onto the merge base with the $baseBranch branch: "
echo " \`\`\`bash"
echo " # git rebase --onto \$(git merge-base upstream/ $baseBranch HEAD) \$(git merge-base upstream/ $testBranch HEAD) "
2024-10-03 17:49:44 -06:00
echo " git rebase --onto $prMergeBase $testMergeBase "
echo " git push --force-with-lease"
echo " \`\`\`"
log -e "\e[m"
exit 1
fi
done
log "Base branch is correct, no commits from development branches are included"