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
|
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`.
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user