Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/workflows/pr-single-commit-up-to-date.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# .github/workflows/pr-single-commit-up-to-date.yml
name: Enforce single commit & up-to-date

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]

permissions:
contents: read
pull-requests: write

jobs:
check-ahead-behind:
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-22.04
steps:
- name: Checkout PR HEAD (full history)
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0

- name: Fetch base branch
run: |
git fetch origin ${{ github.event.pull_request.base.ref }}

- name: Compute ahead/behind
id: ab
shell: bash
run: |
BASE_SHA=${{ github.event.pull_request.base.sha }}
read BEHIND AHEAD < <(git rev-list --left-right --count ${BASE_SHA}...HEAD)
echo "head=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "base=${BASE_SHA}" >> $GITHUB_OUTPUT
echo "behind=$BEHIND" >> $GITHUB_OUTPUT
echo "ahead=$AHEAD" >> $GITHUB_OUTPUT
echo "Behind: $BEHIND, Ahead: $AHEAD"

- name: Enforce rule
shell: bash
run: |
BASE=${{ steps.ab.outputs.base }}
HEAD=${{ steps.ab.outputs.head}}
BEHIND=${{ steps.ab.outputs.behind }}
AHEAD=${{ steps.ab.outputs.ahead }}
if [[ "$BEHIND" -ne 0 || "$AHEAD" -ne 1 ]]; then
echo "PR must be exactly 1 commit ahead and 0 behind the base branch."
echo "base=$BASE, HEAD=$HEAD"
echo "Ahead=$AHEAD, Behind=$BEHIND"
echo "Rebase and squash to fix."
exit 1
fi
echo "OK: PR is exactly 1 ahead and 0 behind."

- name: Convert PR to draft on failure (base repo context)
if: failure()
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
if (!pr) {
core.setFailed('No pull_request context available.');
} else if (pr.draft) {
core.info('PR is already in draft.');
} else {
await github.request('POST /repos/{owner}/{repo}/pulls/{pull_number}/convert-to-draft', {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
headers: { 'X-GitHub-Api-Version': '2022-11-28' }
});
core.info(`Converted PR #${pr.number} to draft.`);
}