ci(tracker): require one metadata owner per pull request

This commit is contained in:
2026-07-26 06:49:06 +00:00
committed by didericis
parent 5828f5e900
commit c0493f0b01
3 changed files with 31 additions and 8 deletions
@@ -29,7 +29,8 @@ item exists. Every issue has at least one label. An issue opened or left
without labels receives `Status/Needs Triage` automatically until it is without labels receives `Status/Needs Triage` automatically until it is
classified. classified.
Every new pull request is tracked in one of two ways: Every new pull request is tracked in exactly one of two mutually exclusive
ways:
1. It deliberately references at least one existing issue in its title or 1. It deliberately references at least one existing issue in its title or
description. Tracker metadata stays on that issue and the PR remains description. Tracker metadata stays on that issue and the PR remains
@@ -43,9 +44,9 @@ Issue references use one of these forms:
- `Part of #123`, `Related to #123`, `Refs #123`, or `References #123` when it - `Part of #123`, `Related to #123`, `Refs #123`, or `References #123` when it
contributes without completing it. contributes without completing it.
Gitea Actions enforces the either/or PR rule, validates any issue references, Gitea Actions enforces the exclusive either/or PR rule, validates any issue
and repairs the empty issue-label state. Branch protection makes the PR policy references, and repairs the empty issue-label state. Branch protection makes
check required. the PR policy check required.
The policy applies from 2026-07-18 onward. Existing issues may be labelled as The policy applies from 2026-07-18 onward. Existing issues may be labelled as
they are encountered, but closed PRs are grandfathered: no retrospective they are encountered, but closed PRs are grandfathered: no retrospective
@@ -70,5 +71,6 @@ issues or PR labels are created solely to make history conform.
## Links ## Links
- Issue #405. - Issue #405.
- `.gitea/workflows/tracker-policy.yml`. - `.gitea/workflows/tracker-policy-pr.yml`.
- `.gitea/workflows/tracker-policy-issues.yml`.
- `scripts/tracker_policy.py`. - `scripts/tracker_policy.py`.
+6
View File
@@ -55,6 +55,12 @@ def check_pull_request(event: dict[str, Any], api: GiteaApi) -> list[str]:
errors: list[str] = [] errors: list[str] = []
labels = pull.get("labels") or [] labels = pull.get("labels") or []
numbers = deliberate_issue_numbers(pull.get("title", ""), pull.get("body", "")) numbers = deliberate_issue_numbers(pull.get("title", ""), pull.get("body", ""))
if labels and numbers:
errors.append(
"PR must use exactly one tracking mode: remove PR labels when "
"linking an issue, or remove the issue reference when labels "
"belong on the PR."
)
if not numbers: if not numbers:
if not labels: if not labels:
errors.append( errors.append(
+18 -3
View File
@@ -47,7 +47,21 @@ class TestCheckPullRequest(unittest.TestCase):
self.assertIn("either have a label or reference an issue", errors[0]) self.assertIn("either have a label or reference an issue", errors[0])
api.request.assert_not_called() api.request.assert_not_called()
def test_validates_issue_reference_even_when_pr_is_labelled(self): def test_rejects_labelled_pr_linked_to_real_issue(self):
api = Mock()
api.request.return_value = {"number": 12, "pull_request": None}
event = {
"pull_request": {
"title": "Change",
"body": "Closes #12",
"labels": [{"name": "Kind/Documentation"}],
}
}
errors = check_pull_request(event, api)
self.assertEqual(len(errors), 1)
self.assertIn("exactly one tracking mode", errors[0])
def test_still_validates_issue_reference_when_both_modes_are_used(self):
api = Mock() api = Mock()
api.request.return_value = {"number": 12, "pull_request": {}} api.request.return_value = {"number": 12, "pull_request": {}}
event = { event = {
@@ -58,8 +72,9 @@ class TestCheckPullRequest(unittest.TestCase):
} }
} }
errors = check_pull_request(event, api) errors = check_pull_request(event, api)
self.assertEqual(len(errors), 1) self.assertEqual(len(errors), 2)
self.assertIn("not an issue", errors[0]) self.assertIn("exactly one tracking mode", errors[0])
self.assertIn("not an issue", errors[1])
class TestEnsureIssueLabel(unittest.TestCase): class TestEnsureIssueLabel(unittest.TestCase):