The "Pull Request Labeler" workflow read the full label set of a pull request and wrote it back with setLabels(), which is a full replace. Other workflows label the same pull request at the same time: claude-review.yml adds the claude-review label on the pull_request_target "opened" event, which is the very same event that starts the labeler. Whenever that add landed between the labeler's listLabelsOnIssue() and its setLabels(), the label was dropped again and the review never ran. Only ever add or remove the individual labels the labeler owns, so concurrent changes by other workflows survive. removeLabel() returns 404 when the label is already gone, which is exactly what a racing run causes, so swallow that status.
181 lines
6.8 KiB
YAML
181 lines
6.8 KiB
YAML
---
|
|
# vi: ts=2 sw=2 et:
|
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
#
|
|
name: "Pull Request Labeler"
|
|
|
|
on:
|
|
pull_request_target:
|
|
types: [opened, synchronize, reopened, ready_for_review, closed]
|
|
paths-ignore:
|
|
- '.github/labeler.yml'
|
|
- '.github/workflows/labeler.yml'
|
|
# Allow testing changes made to the labeler configuration
|
|
pull_request:
|
|
paths:
|
|
- '.github/labeler.yml'
|
|
- '.github/workflows/labeler.yml'
|
|
issue_comment:
|
|
types: [created]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
triage:
|
|
if: github.repository == 'systemd/systemd'
|
|
runs-on: ubuntu-24.04
|
|
permissions:
|
|
pull-requests: write
|
|
|
|
steps:
|
|
- name: Repository checkout
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
|
if: github.event_name == 'pull_request'
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Label PR based on policy in labeler.yml
|
|
uses: actions/labeler@c5dadc2a45784a4b6adfcd20fea3465da3a5f904
|
|
if: startsWith(github.event_name, 'pull_request') && github.base_ref == 'main' && github.event.action != 'closed'
|
|
with:
|
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
|
configuration-path: .github/labeler.yml
|
|
sync-labels: false
|
|
|
|
- name: Set or remove labels based on systemd development workflow
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
|
|
if: startsWith(github.event_name, 'pull_request') && github.event.action != 'closed' && !github.event.pull_request.draft
|
|
with:
|
|
script: |
|
|
/* Never read the full label set and write it back: other workflows
|
|
* label the same pull request concurrently and setLabels() would
|
|
* clobber whatever they added in the meantime. Only ever add or
|
|
* remove the individual labels this step owns. */
|
|
const removeLabel = async (name) => {
|
|
try {
|
|
await github.rest.issues.removeLabel({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
name: name,
|
|
});
|
|
} catch (error) {
|
|
/* Somebody else removed it first. */
|
|
if (error.status !== 404) {
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
|
|
response = await github.rest.issues.listLabelsOnIssue({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
});
|
|
|
|
labels = new Set(response.data.map(l => l.name));
|
|
|
|
good_to_merge = new Set([
|
|
"good-to-merge/waiting-for-ci 👍",
|
|
"good-to-merge/after-next-release",
|
|
"good-to-merge/with-minor-suggestions",
|
|
"good-to-merge/waiting-for-reporter-feedback 👍",
|
|
]);
|
|
|
|
/* A README.md that still carries the AI review notice has not been
|
|
* looked at by its author yet, so nobody else should review it. */
|
|
const readme = await github.rest.repos.getContent({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
path: "README.md",
|
|
ref: context.payload.pull_request.head.sha,
|
|
});
|
|
const unreviewed = Buffer.from(readme.data.content, "base64").toString()
|
|
.includes("Remove this line to confirm you've reviewed this PR before submitting.");
|
|
|
|
if (unreviewed) {
|
|
/* Only GraphQL can do this. The author marks the PR ready again once
|
|
* the notice is gone, which triggers the labeling below. */
|
|
await github.graphql(`
|
|
mutation($id: ID!) {
|
|
convertPullRequestToDraft(input: { pullRequestId: $id }) {
|
|
pullRequest { isDraft }
|
|
}
|
|
}
|
|
`, { id: context.payload.pull_request.node_id });
|
|
} else if (!Array.from(good_to_merge).some(l => labels.has(l)) && !labels.has("please-review")) {
|
|
await github.rest.issues.addLabels({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
labels: ["please-review"],
|
|
});
|
|
}
|
|
|
|
for (const label of ["reviewed/needs-rework 🔨",
|
|
"ci-fails/needs-rework 🔥",
|
|
"ci-failure-appears-unrelated",
|
|
"needs-rebase"]) {
|
|
if (labels.has(label)) {
|
|
await removeLabel(label);
|
|
}
|
|
}
|
|
|
|
- name: Add please-review label on command in issue comment
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
|
|
if: github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/please-review')
|
|
with:
|
|
script: |
|
|
await github.rest.issues.addLabels({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
labels: ["please-review"]
|
|
})
|
|
|
|
- name: Remove specific labels when PR is closed or merged
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
|
|
if: startsWith(github.event_name, 'pull_request') && github.event.action == 'closed'
|
|
with:
|
|
script: |
|
|
response = await github.rest.issues.listLabelsOnIssue({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
});
|
|
|
|
labels = new Set(response.data.map(l => l.name));
|
|
|
|
for (const label of ["please-review",
|
|
"reviewed/needs-rework 🔨",
|
|
"ci-fails/needs-rework 🔥",
|
|
"needs-rebase",
|
|
"good-to-merge/waiting-for-ci 👍",
|
|
"good-to-merge/after-next-release",
|
|
"good-to-merge/with-minor-suggestions",
|
|
"good-to-merge/waiting-for-reporter-feedback 👍",
|
|
"needs-discussion 🤔",
|
|
"needs-reporter-feedback ❓",
|
|
"dont-merge 💣",
|
|
"squash-on-merge",
|
|
"quick-review 🏃♂️"]) {
|
|
if (!labels.has(label)) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
await github.rest.issues.removeLabel({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
name: label,
|
|
});
|
|
} catch (error) {
|
|
/* Somebody else removed it first. */
|
|
if (error.status !== 404) {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|