feat: require approval at current PR head for merge (#471)

Add merge_approval_gate assessment so gitea_merge_pr fails closed when
visible APPROVED reviews do not pin the live head SHA. Expose
approval_at_current_head and stale_approval_block_reason in review
feedback. Document re-review requirement in canonical merge workflow.

Closes #471.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-08 00:37:54 -04:00
co-authored by Claude Opus 4.8
parent a1de4ab8f9
commit 89582a0f2a
7 changed files with 184 additions and 1 deletions
+16
View File
@@ -115,6 +115,22 @@ class TestPRReviewFeedbackDiscovery(unittest.TestCase):
result["latest_review_state_by_reviewer"], {"reviewer1": "APPROVED"})
self.assertFalse(result["has_blocking_change_requests"])
self.assertTrue(result["approval_visible"])
self.assertTrue(result["approval_at_current_head"])
@patch("mcp_server.api_request")
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
@patch("mcp_server.get_profile")
def test_stale_approval_not_at_current_head(self, mock_get_profile, _auth, mock_api):
mock_get_profile.return_value = self._profile()
mock_api.side_effect = [
_pr_details(head_sha="newhead3"),
[_review("reviewer1", "APPROVED", commit_id="oldhead1")],
]
result = gitea_get_pr_review_feedback(pr_number=5, remote="prgs")
self.assertTrue(result["approval_visible"])
self.assertFalse(result["approval_at_current_head"])
self.assertEqual(result["latest_approved_head_sha"], "oldhead1")
self.assertIn("stale approval", result["stale_approval_block_reason"])
@patch("mcp_server.api_request")
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)