diff --git a/.git-management.json b/.git-management.json index facfd67..e13386a 100644 --- a/.git-management.json +++ b/.git-management.json @@ -1,11 +1,16 @@ { - "policy_version": 1, + "policy_version": 3, "default_branch": "main", "branch_pattern": "^(ai|feat|fix|refactor|chore)/.+$", "required_pr_metadata": ["task_id", "run_id"], + "task_id_pattern": "^[A-Z][A-Z0-9]*-[0-9]+$", + "require_task_id_in_pr_title": true, "required_checks": ["ci", "git-governance"], - "required_approvals": 1, - "require_resolved_conversations": true, + "review_policy": { + "enabled": false, + "required_approvals": 0, + "require_resolved_conversations": false + }, "require_up_to_date_branch": true, "merge_method": "squash" } diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4bdc94f..ee7fe2c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,6 +4,8 @@ run_id: <必填运行标识> change_id: <可选变更标识> --> + + ## 目标 ## 变更范围 diff --git a/.github/scripts/test_validate_pr.py b/.github/scripts/test_validate_pr.py index f804cc5..c2a2fe3 100644 --- a/.github/scripts/test_validate_pr.py +++ b/.github/scripts/test_validate_pr.py @@ -12,6 +12,8 @@ "default_branch": "main", "branch_pattern": r"^(ai|feat|fix|refactor|chore)/.+$", "required_pr_metadata": ["task_id", "run_id"], + "task_id_pattern": r"^[A-Z][A-Z0-9]*-[0-9]+$", + "require_task_id_in_pr_title": True, } @@ -19,6 +21,7 @@ class ValidatePullRequestTest(unittest.TestCase): def test_accepts_valid_pull_request(self): event = { "pull_request": { + "title": "feat(example): MT-1 add example", "body": "", "head": {"ref": "feat/example"}, "base": {"ref": "main"}, @@ -39,6 +42,42 @@ def test_rejects_missing_metadata_and_invalid_branch(self): self.assertIn("missing PR metadata: task_id", failures) self.assertIn("missing PR metadata: run_id", failures) + def test_rejects_title_without_task_id(self): + event = { + "pull_request": { + "title": "feat(home): add team homepage", + "body": "", + "head": {"ref": "feat/home"}, + "base": {"ref": "main"}, + } + } + failures = VALIDATE_PR.validate(event, POLICY) + self.assertIn("PR title must contain task_id: TML-741", failures) + + def test_rejects_title_with_different_task_id(self): + event = { + "pull_request": { + "title": "feat(home): TML-742 add team homepage", + "body": "", + "head": {"ref": "feat/home"}, + "base": {"ref": "main"}, + } + } + failures = VALIDATE_PR.validate(event, POLICY) + self.assertIn("PR title must contain task_id: TML-741", failures) + + def test_rejects_task_id_prefix_match(self): + event = { + "pull_request": { + "title": "feat(home): TML-7410 add team homepage", + "body": "", + "head": {"ref": "feat/home"}, + "base": {"ref": "main"}, + } + } + failures = VALIDATE_PR.validate(event, POLICY) + self.assertIn("PR title must contain task_id: TML-741", failures) + if __name__ == "__main__": unittest.main() diff --git a/.github/scripts/validate_pr.py b/.github/scripts/validate_pr.py index fe83f2f..9252e88 100644 --- a/.github/scripts/validate_pr.py +++ b/.github/scripts/validate_pr.py @@ -19,6 +19,7 @@ def parse_metadata(body): def validate(event, policy): pull_request = event.get("pull_request") or {} + title = pull_request.get("title") or "" body = pull_request.get("body") or "" head_ref = pull_request.get("head", {}).get("ref") or "" base_ref = pull_request.get("base", {}).get("ref") or "" @@ -43,6 +44,19 @@ def validate(event, policy): if not value or value.startswith("<"): failures.append(f"missing PR metadata: {field}") + task_id = metadata.get("task_id", "") + task_id_is_present = bool(task_id) and not task_id.startswith("<") + task_id_is_valid = task_id_is_present + task_id_pattern = policy.get("task_id_pattern") + if task_id_is_present and task_id_pattern and not re.fullmatch(task_id_pattern, task_id): + failures.append(f"invalid PR metadata: task_id: {task_id}") + task_id_is_valid = False + + if policy.get("require_task_id_in_pr_title") and task_id_is_valid: + exact_task_id = rf"(?