Skip to content

Commit 3e6280e

Browse files
Enforce single commit PRs
Change: single-commit-pr
1 parent 13de3dd commit 3e6280e

File tree

15 files changed

+1688
-241
lines changed

15 files changed

+1688
-241
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# .github/workflows/convert-pr-to-draft-on-fail.yml
2+
name: Convert PR to draft on failure (single commit check)
3+
4+
on:
5+
workflow_run:
6+
workflows: ["Enforce single commit & up-to-date"]
7+
types: [completed]
8+
9+
permissions:
10+
contents: read
11+
pull-requests: write
12+
13+
jobs:
14+
convert:
15+
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }}
16+
runs-on: ubuntu-22.04
17+
steps:
18+
- name: Convert associated PR to draft
19+
uses: actions/github-script@v7
20+
with:
21+
script: |
22+
const run = context.payload.workflow_run;
23+
const prs = run.pull_requests || [];
24+
if (prs.length === 0) {
25+
core.info('No pull request associated with this run.');
26+
return;
27+
}
28+
const prNumber = prs[0].number;
29+
const owner = context.repo.owner;
30+
const repo = context.repo.repo;
31+
const { data: pr } = await github.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', {
32+
owner,
33+
repo,
34+
pull_number: prNumber,
35+
headers: { 'X-GitHub-Api-Version': '2022-11-28' }
36+
});
37+
if (pr.draft) {
38+
core.info(`PR #${prNumber} is already draft.`);
39+
return;
40+
}
41+
await github.request('POST /repos/{owner}/{repo}/pulls/{pull_number}/convert-to-draft', {
42+
owner,
43+
repo,
44+
pull_number: prNumber,
45+
headers: { 'X-GitHub-Api-Version': '2022-11-28' }
46+
});
47+
core.info(`Converted PR #${prNumber} to draft.`);
48+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# .github/workflows/pr-single-commit-up-to-date.yml
2+
name: Enforce single commit & up-to-date
3+
4+
on:
5+
pull_request:
6+
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
7+
8+
permissions:
9+
contents: read
10+
pull-requests: write
11+
12+
jobs:
13+
check-ahead-behind:
14+
if: ${{ !github.event.pull_request.draft }}
15+
runs-on: ubuntu-22.04
16+
steps:
17+
- name: Checkout PR HEAD (full history)
18+
uses: actions/checkout@v4
19+
with:
20+
ref: ${{ github.event.pull_request.head.sha }}
21+
fetch-depth: 0
22+
23+
- name: Fetch base branch
24+
run: |
25+
git fetch origin ${{ github.event.pull_request.base.ref }}
26+
27+
- name: Compute ahead/behind
28+
id: ab
29+
shell: bash
30+
run: |
31+
BASE_SHA=${{ github.event.pull_request.base.sha }}
32+
read BEHIND AHEAD < <(git rev-list --left-right --count ${BASE_SHA}...HEAD)
33+
echo "head=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
34+
echo "base=${BASE_SHA}" >> $GITHUB_OUTPUT
35+
echo "behind=$BEHIND" >> $GITHUB_OUTPUT
36+
echo "ahead=$AHEAD" >> $GITHUB_OUTPUT
37+
echo "Behind: $BEHIND, Ahead: $AHEAD"
38+
39+
- name: Enforce rule
40+
shell: bash
41+
run: |
42+
BASE=${{ steps.ab.outputs.base }}
43+
HEAD=${{ steps.ab.outputs.head}}
44+
BEHIND=${{ steps.ab.outputs.behind }}
45+
AHEAD=${{ steps.ab.outputs.ahead }}
46+
if [[ "$BEHIND" -ne 0 || "$AHEAD" -ne 1 ]]; then
47+
echo "PR must be exactly 1 commit ahead and 0 behind the base branch."
48+
echo "base=$BASE, HEAD=$HEAD"
49+
echo "Ahead=$AHEAD, Behind=$BEHIND"
50+
echo "Rebase and squash to fix."
51+
exit 1
52+
fi
53+
echo "OK: PR is exactly 1 ahead and 0 behind."
54+
55+
# Draft conversion is handled by a separate workflow via workflow_run

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

josh-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ clap = { workspace = true }
2121
rs_tracing = { workspace = true }
2222
juniper = { workspace = true }
2323
git2 = { workspace = true }
24+
toml = { workspace = true }

0 commit comments

Comments
 (0)