From 54a5f6f5c2a9d93b36a9a5aeb4eaf57d02ab5e6b Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 11 Aug 2026 18:00:27 -0400 Subject: [PATCH] Key the prgate comment marker on the reason alone The hidden marker the gate leaves on a pull request was "", carrying the head sha, so _already_commented() only suppressed a repeat when the reason *and* the commit both matched. The gate runs on opened/reopened, which means a contributor who is closed, pushes a commit and reopens gets a second copy of the same rejection. The sha isn't part of the decision: the gate reads the pull request title and body and the state of the issue they name, never the code, so a new head commit cannot change the outcome without also changing the reason, which is in the marker already. Drop it. The pull request number doesn't need to be in the marker either, since the comments are fetched per pull request to begin with. Matching is done against the unterminated "" % (MARKER_PREFIX, reason, sha) +def marker(reason: str) -> str: + return "%s:%s -->" % (MARKER_PREFIX, reason) + + +def marker_match(reason: str) -> str: + """The substring that recognizes any past comment for ``reason``. + + Deliberately unterminated: the marker used to carry the head sha as + well, and matching on the open prefix means the comments already + posted with the old format are still recognized. + + """ + + return "%s:%s" % (MARKER_PREFIX, reason) def claim_marker(issue: int) -> str: @@ -132,7 +148,6 @@ def accepted_message(issue: int, label: str, review_label: str) -> str: def close_message( result: util.GateResult, label: str, - sha: str, review_label: str = util.DEFAULT_REVIEW_LABEL, deny_label: str = util.DEFAULT_DENY_LABEL, policy_url: Optional[str] = None, @@ -162,7 +177,7 @@ def close_message( steps = _STEPS_NO_ISSUE paragraphs = [ - marker(result.reason, sha), + marker(result.reason), _INTRO, _POLICY % subs, (steps % subs).strip(), diff --git a/tests/test_prgate_hook.py b/tests/test_prgate_hook.py index d58ed25..666218e 100644 --- a/tests/test_prgate_hook.py +++ b/tests/test_prgate_hook.py @@ -304,13 +304,27 @@ def test_redelivery_does_not_repeat_the_comment(): assert gh_repo.closed == ["7", "7"] -def test_new_commit_gets_a_fresh_comment(): +def test_new_commit_does_not_get_a_fresh_comment(): + """A reopen after a push is the same rejection, not a new one. + + The marker used to carry the head sha, so pushing a commit and + reopening produced a second identical comment. + + """ + + gh_repo = FakeRepo(comments=[messages.marker(util.CLOSE_NO_ISSUE)]) + run(gh_repo, make_event(body="no reference")) + + assert len(gh_repo.comments) == 1 + + +def test_comments_from_the_old_sha_bearing_marker_are_recognized(): gh_repo = FakeRepo( - comments=[messages.marker(util.CLOSE_NO_ISSUE, "different-sha")] + comments=["" % util.CLOSE_NO_ISSUE] ) run(gh_repo, make_event(body="no reference")) - assert len(gh_repo.comments) == 2 + assert len(gh_repo.comments) == 1 def test_close_can_be_disabled(): @@ -335,19 +349,21 @@ def test_maintainer_exemption_is_per_repo(): assert gh_repo.closed == ["7"] -def test_marker_distinguishes_reason_and_sha(): - assert messages.marker("no_issue", "aaa") != messages.marker( - "no_issue", "bbb" - ) - assert messages.marker("no_issue", "aaa") != messages.marker( - "issue_closed", "aaa" - ) +def test_marker_distinguishes_reason(): + assert messages.marker("no_issue") != messages.marker("issue_closed") def test_already_commented_matches_only_its_own_marker(): gh_repo = FakeRepo(comments=["a normal human comment mentioning #5"]) assert not prgate_github._already_commented( - gh_repo, "7", util.CLOSE_NO_ISSUE, SHA + gh_repo, "7", util.CLOSE_NO_ISSUE + ) + + +def test_already_commented_does_not_match_a_different_reason(): + gh_repo = FakeRepo(comments=[messages.marker(util.CLOSE_ISSUE_CLOSED)]) + assert not prgate_github._already_commented( + gh_repo, "7", util.CLOSE_NO_ISSUE )