Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion scripts/src/scverse_template_scripts/cruft_prs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import json
import math
import os
import re
import sys
from collections.abc import Iterable
from dataclasses import KW_ONLY, InitVar, dataclass, field
Expand Down Expand Up @@ -72,6 +73,16 @@
]


def _escape_github_mentions(text: str) -> str:
"""Escape GitHub @mentions with backticks to prevent notifications.

Wraps ``@username`` patterns in backticks so that GitHub doesn't treat them
as real mentions when the text is used in PRs.
Already-escaped mentions and email addresses are left unchanged.
"""
return re.sub(r"(?<![`\w])@([a-zA-Z\d](?:[a-zA-Z\d-]*[a-zA-Z\d])?)", r"`@\1`", text)


@dataclass
class GitHubConnection:
"""API connection to a GitHub user (e.g. scverse-bot)"""
Expand Down Expand Up @@ -138,10 +149,11 @@ def namespaced_head(self) -> str:

@property
def body(self) -> str:
return PR_BODY_TEMPLATE.format(
body = PR_BODY_TEMPLATE.format(
release=self.release,
template_usage="https://cookiecutter-scverse-instance.readthedocs.io/en/latest/template_usage.html",
)
return _escape_github_mentions(body)

def matches_prefix(self, pr: PullRequest) -> bool:
"""Check if `pr` is either a current or previous template update PR by matching the branch name"""
Expand Down
35 changes: 35 additions & 0 deletions scripts/tests/test_cruft.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
_apply_update,
_clone_and_prepare_repo,
_commit_update,
_escape_github_mentions,
_get_cruft_config_from_upstream,
get_repo_urls,
get_template_release,
Expand Down Expand Up @@ -143,3 +144,37 @@ def test_commit_update(clone: Repo, exclude_files: list[str], expected_untracked

def test_commit_update_no_files(clone: Repo) -> None:
assert _commit_update(clone, commit_msg="foo", commit_author="scverse-bot") is False


@pytest.mark.parametrize(
("input_text", "expected"),
[
# Basic mention gets escaped
("by @grst in", "by `@grst` in"),
# Multiple mentions get escaped
("@alice and @bob", "`@alice` and `@bob`"),
# Already-escaped mention stays unchanged
("`@grst`", "`@grst`"),
# Email address stays unchanged
("user@example.com", "user@example.com"),
# Mention with hyphenated username
("by @some-user in", "by `@some-user` in"),
# Mention at start of line
("@grst made changes", "`@grst` made changes"),
# No mentions
("no mentions here", "no mentions here"),
# Single char username
("@a contributed", "`@a` contributed"),
# Realistic release notes
(
"* Fix bug by @grst in https://github.com/scverse/cookiecutter-scverse/pull/1\n"
"* Add feature by @some-user in https://github.com/scverse/cookiecutter-scverse/pull/2",
"* Fix bug by `@grst` in https://github.com/scverse/cookiecutter-scverse/pull/1\n"
"* Add feature by `@some-user` in https://github.com/scverse/cookiecutter-scverse/pull/2",
),
# Bot email should not be escaped
("108668866+scverse-bot@users.noreply.github.com", "108668866+scverse-bot@users.noreply.github.com"),
],
)
def test_escape_github_mentions(input_text: str, expected: str) -> None:
assert _escape_github_mentions(input_text) == expected
Loading