ci(tracker): require one metadata owner per pull request
This commit is contained in:
@@ -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
|
||||
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
|
||||
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
|
||||
contributes without completing it.
|
||||
|
||||
Gitea Actions enforces the either/or PR rule, validates any issue references,
|
||||
and repairs the empty issue-label state. Branch protection makes the PR policy
|
||||
check required.
|
||||
Gitea Actions enforces the exclusive either/or PR rule, validates any issue
|
||||
references, and repairs the empty issue-label state. Branch protection makes
|
||||
the PR policy check required.
|
||||
|
||||
The policy applies from 2026-07-18 onward. Existing issues may be labelled as
|
||||
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
|
||||
|
||||
- Issue #405.
|
||||
- `.gitea/workflows/tracker-policy.yml`.
|
||||
- `.gitea/workflows/tracker-policy-pr.yml`.
|
||||
- `.gitea/workflows/tracker-policy-issues.yml`.
|
||||
- `scripts/tracker_policy.py`.
|
||||
|
||||
@@ -55,6 +55,12 @@ def check_pull_request(event: dict[str, Any], api: GiteaApi) -> list[str]:
|
||||
errors: list[str] = []
|
||||
labels = pull.get("labels") or []
|
||||
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 labels:
|
||||
errors.append(
|
||||
|
||||
@@ -47,7 +47,21 @@ class TestCheckPullRequest(unittest.TestCase):
|
||||
self.assertIn("either have a label or reference an issue", errors[0])
|
||||
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.request.return_value = {"number": 12, "pull_request": {}}
|
||||
event = {
|
||||
@@ -58,8 +72,9 @@ class TestCheckPullRequest(unittest.TestCase):
|
||||
}
|
||||
}
|
||||
errors = check_pull_request(event, api)
|
||||
self.assertEqual(len(errors), 1)
|
||||
self.assertIn("not an issue", errors[0])
|
||||
self.assertEqual(len(errors), 2)
|
||||
self.assertIn("exactly one tracking mode", errors[0])
|
||||
self.assertIn("not an issue", errors[1])
|
||||
|
||||
|
||||
class TestEnsureIssueLabel(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user