diff --git a/.github/workflows/handle_potential_conflicts.py b/.github/workflows/handle_potential_conflicts.py index f2c460d00bd3..c564231e8ffd 100755 --- a/.github/workflows/handle_potential_conflicts.py +++ b/.github/workflows/handle_potential_conflicts.py @@ -154,7 +154,10 @@ def normalize_state(state: Any) -> dict[str, Any]: def extract_state(body: str) -> dict[str, Any]: - marker_index = body.find(COMMENT_START) + # The state block is rendered last, but comments written before that change + # start with it. Anchor on the last marker unless the body opens with one, + # so marker-like text in advisory content cannot shadow the real block. + marker_index = 0 if body.startswith(COMMENT_START) else body.rfind(COMMENT_START) if marker_index == -1: return normalize_state({}) @@ -218,9 +221,6 @@ def render_comment_body(state: dict[str, Any]) -> str: state_json = json.dumps(state, sort_keys=True, separators=(",", ":")) state_encoded = base64.b64encode(state_json.encode("utf8")).decode("ascii") lines = [ - COMMENT_START, - state_encoded, - COMMENT_END, "## Potential PR merge conflicts", "", "This is advisory only. It does not block CI, but it marks PRs that will likely need a rebase depending on merge order.", @@ -247,6 +247,14 @@ def render_comment_body(state: dict[str, Any]) -> str: inbound_items = sorted(state["inbound"].values(), key=lambda item: int(item["number"])) lines.extend(format_pr_line(item) for item in inbound_items) + # The state block goes last so notification previews start with the advisory text. + lines.extend([ + "", + COMMENT_START, + state_encoded, + COMMENT_END, + ]) + return "\n".join(lines).rstrip() + "\n" diff --git a/.github/workflows/test_handle_potential_conflicts.py b/.github/workflows/test_handle_potential_conflicts.py index 29f12ff7f7ea..55898d7e9666 100755 --- a/.github/workflows/test_handle_potential_conflicts.py +++ b/.github/workflows/test_handle_potential_conflicts.py @@ -91,7 +91,7 @@ def test_rendered_comment_state_round_trips(self): "outbound": [ { "number": 12, - "title": "title with --> marker-like text", + "title": f"title with {handle_potential_conflicts.COMMENT_START} --> marker-like text", "url": "https://github.com/dashpay/dash/pull/12", "files": ["src/net.cpp"], } @@ -103,7 +103,22 @@ def test_rendered_comment_state_round_trips(self): extracted = handle_potential_conflicts.extract_state(body) self.assertEqual(state, extracted) - self.assertNotIn("title with --> marker-like text", body.split("-->")[0]) + self.assertTrue(body.startswith("## Potential PR merge conflicts")) + self.assertTrue(body.rstrip("\n").endswith(handle_potential_conflicts.COMMENT_END)) + + def test_extract_state_from_legacy_marker_first_comment(self): + state = { + "outbound": [ + {"number": 2, "title": "two", "url": "https://example.test/2", "files": []}, + ], + "inbound": {}, + } + body = handle_potential_conflicts.render_comment_body(state) + marker_index = body.rfind(handle_potential_conflicts.COMMENT_START) + legacy_body = body[marker_index:] + body[:marker_index] + + self.assertTrue(legacy_body.startswith(handle_potential_conflicts.COMMENT_START)) + self.assertEqual(state, handle_potential_conflicts.extract_state(legacy_body)) def test_rendered_comment_escapes_markdown_content(self): body = handle_potential_conflicts.render_comment_body({