feat(ci): gate pull requests on an accepted issue (#6894)
This commit is contained in:
@@ -28,7 +28,7 @@ jobs:
|
||||
}
|
||||
|
||||
base_version=$(git show "$BASE_SHA:pyproject.toml" 2>/dev/null | extract_version || echo "")
|
||||
head_version=$(extract_version < pyproject.toml)
|
||||
head_version=$(git show "$HEAD_SHA:pyproject.toml" | extract_version)
|
||||
|
||||
echo "Base version: ${base_version:-<unknown>}"
|
||||
echo "Head version: $head_version"
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
name: PR Gate
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened, edited, ready_for_review]
|
||||
|
||||
concurrency:
|
||||
group: pr-gate-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GATE_EFFECTIVE_FROM: '2026-08-12T00:00:00Z'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: read
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
if: >-
|
||||
github.event.pull_request.draft == false &&
|
||||
github.event.pull_request.user.type != 'Bot' &&
|
||||
!contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
const effectiveFrom = process.env.GATE_EFFECTIVE_FROM;
|
||||
if (effectiveFrom && Date.parse(pr.created_at) < Date.parse(effectiveFrom)) {
|
||||
core.info(`Opened ${pr.created_at}, before the gate took effect ${effectiveFrom}. Skipped.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: current } = await github.rest.pulls.get({
|
||||
owner, repo, pull_number: pr.number,
|
||||
});
|
||||
if (current.state !== 'open') {
|
||||
core.info(`#${pr.number} is already ${current.state}. Skipped.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const files = await github.paginate(github.rest.pulls.listFiles, {
|
||||
owner, repo, pull_number: pr.number, per_page: 100,
|
||||
});
|
||||
if (files.length > 0 && files.every((file) => file.filename.startsWith('docs/'))) {
|
||||
core.info('Docs-only PR, gate skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
const { repository } = await github.graphql(
|
||||
`query ($owner: String!, $repo: String!, $number: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $number) {
|
||||
closingIssuesReferences(first: 20) {
|
||||
nodes { number labels(first: 50) { nodes { name } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
{ owner, repo, number: pr.number },
|
||||
);
|
||||
|
||||
const accepted = repository.pullRequest.closingIssuesReferences.nodes
|
||||
.filter((issue) => issue.labels.nodes.some((label) => label.name === 'accepted'))
|
||||
.map((issue) => issue.number);
|
||||
|
||||
if (accepted.length > 0) {
|
||||
core.info(`Accepted issue linked: #${accepted.join(', #')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = [
|
||||
'Thanks for taking the time to open this.',
|
||||
'',
|
||||
'We only review pull requests that fix an issue we have already agreed to take on, so this one is closed for now.',
|
||||
'**Closed does not mean rejected.** It means it is not in the queue yet, and reopening takes about a minute.',
|
||||
'',
|
||||
'To get it reviewed:',
|
||||
'',
|
||||
'1. Make sure an issue describes the problem, with the version you are on, a runnable reproduction, and the real output or traceback you saw.',
|
||||
'2. Link it from this pull request description with `Closes #<number>`.',
|
||||
'3. Ask a maintainer to label that issue `accepted`.',
|
||||
'4. Reopen this pull request. The check runs again and it stays open.',
|
||||
'',
|
||||
'Already linked an accepted issue? Edit the description to include `Closes #<number>` and reopen. The check reruns automatically.',
|
||||
'',
|
||||
'Documentation-only changes skip this gate entirely.',
|
||||
'',
|
||||
'See [CONTRIBUTING.md](https://github.com/mem0ai/mem0/blob/main/CONTRIBUTING.md) for the full policy.',
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner, repo, issue_number: pr.number, body,
|
||||
});
|
||||
await github.rest.pulls.update({
|
||||
owner, repo, pull_number: pr.number, state: 'closed',
|
||||
});
|
||||
core.info(`Closed #${pr.number}: no accepted issue linked`);
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
set -euo pipefail
|
||||
|
||||
base_version=$(git show "$BASE_SHA:mem0-ts/package.json" 2>/dev/null | jq -r .version || echo "")
|
||||
head_version=$(jq -r .version mem0-ts/package.json)
|
||||
head_version=$(git show "$HEAD_SHA:mem0-ts/package.json" | jq -r .version)
|
||||
|
||||
echo "Base version: ${base_version:-<unknown>}"
|
||||
echo "Head version: $head_version"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Vouch - Check PR
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened]
|
||||
|
||||
concurrency:
|
||||
group: vouch-check-pr-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
check:
|
||||
if: >-
|
||||
github.event.pull_request.user.type != 'Bot' &&
|
||||
!contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: mitchellh/vouch/action/check-pr@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0
|
||||
with:
|
||||
pr-number: ${{ github.event.pull_request.number }}
|
||||
auto-close: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Vouch - Manage by Issue
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
concurrency:
|
||||
group: vouch-manage
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
manage:
|
||||
if: contains(github.event.comment.body, '!vouch') || contains(github.event.comment.body, '!denounce') || contains(github.event.comment.body, '!unvouch')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.VOUCH_APP_ID }}
|
||||
private-key: ${{ secrets.VOUCH_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- uses: mitchellh/vouch/action/manage-by-issue@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0
|
||||
with:
|
||||
repo: ${{ github.repository }}
|
||||
issue-id: ${{ github.event.issue.number }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
vouch-keyword: "!vouch"
|
||||
denounce-keyword: "!denounce"
|
||||
unvouch-keyword: "!unvouch"
|
||||
pull-request: "true"
|
||||
merge-immediately: "true"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
Reference in New Issue
Block a user