Skip to content

Commit e25ca63

Browse files
committed
Escape auto-linked GitHub usernames more precisely
More precisely match how GitHub auto-links usernames when escaping usernames to suppress pings. Perhaps most noticeably, this now keeps punctuation outside of the escaped username.
1 parent d9ed287 commit e25ca63

File tree

2 files changed

+72
-5
lines changed

2 files changed

+72
-5
lines changed

homu/main.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,31 @@
4141

4242
VARIABLES_RE = re.compile(r'\${([a-zA-Z_]+)}')
4343

44+
# Pattern for matching an auto-linked GitHub username.
45+
#
46+
# This is the behavior used by GitHub when detecting usernames, as deduced from
47+
# the username constraints for new accounts and testing in their Markdown
48+
# renderer:
49+
# - Usernames are auto-linked with an @ prefix.
50+
# - Usernames can only contain alphanumeric characters or hyphens and must be
51+
# between 1 and 39 characters (inclusive): /@[A-Za-z0-9\-]{1,39}/
52+
# - Usernames cannot start with hyphen: /@[A-Za-z0-9][A-Za-z0-9\-]{,38}/
53+
# - A username preceded by an alphanumeric character or underscore is not
54+
# auto-linked: /(?<![A-Za-z0-9_])/
55+
# - A username followed by an underscore or slash is not auto-linked:
56+
# /(?![A-Za-z0-9\-_\/])/
57+
#
58+
# Although usernames cannot contain consecutive hyphens or end with a hyphen,
59+
# GitHub still auto-links such patterns.
60+
#
61+
# The logic for username auto-linking appears to not be open source.
62+
# github-markup (https://github.com/github/markup), the markup renderer used by
63+
# GitHub, does not contain this logic, nor do its dependencies. Their README
64+
# says this is part of their "special sauce".
65+
#
66+
# This pattern does not handle Markdown code blocks.
67+
GITHUB_USERNAME_RE = re.compile(r'(?<![A-Za-z0-9_])(@[A-Za-z0-9][A-Za-z0-9\-]{,38})(?![A-Za-z0-9\-_/])')
68+
4469
IGNORE_BLOCK_START = '<!-- homu-ignore:start -->'
4570
IGNORE_BLOCK_END = '<!-- homu-ignore:end -->'
4671
IGNORE_BLOCK_RE = re.compile(
@@ -56,7 +81,7 @@
5681
# Replace @mention with `@mention` to suppress pings in merge commits.
5782
# Note: Don't replace non-mentions like "[email protected]".
5883
def suppress_pings(text):
59-
return re.sub(r'\B(@\S+)', r'`\g<1>`', text) # noqa
84+
return GITHUB_USERNAME_RE.sub(r'`\g<1>`', text)
6085

6186

6287
# Replace any text between IGNORE_BLOCK_START and IGNORE_BLOCK_END

homu/tests/test_pr_body.py

+46-4
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,58 @@
77

88

99
def test_suppress_pings_in_PR_body():
10+
# This behavior can be verified by pasting the text into a Markdown editor
11+
# on Github and checking which usernames are auto-linked.
12+
1013
body = (
11-
"r? @matklad\n" # should escape
12-
"@bors r+\n" # shouldn't
13-
"[email protected]" # shouldn't
14+
# Should escape:
15+
"r? @matklad\n"
16+
"@bors r+\n"
17+
"@a\n" # Minimum length
18+
"@abcdefghijklmnopqrstuvwxyzabcdefghijklm\n" # Maximum length
19+
"@user. @user, @user; @user? @user!\n"
20+
"@user@user\n" # Only the first is auto-linked
21+
"@user/@user/@user\n" # Only the last is auto-linked
22+
"@@user\n"
23+
"/@user\n"
24+
"-@user\n"
25+
"@user--name\n" # Auto-linked, despite being an invalid username
26+
"@user-\n" # Auto-linked, despite being an invalid username
27+
"`@user`\n" # Code block handling is not implemented
28+
29+
# Shouldn't escape:
30+
31+
"@abcdefghijklmnopqrstuvwxyzabcdefghijklmo\n" # Over maximum length
32+
"text@user\n"
33+
"@-\n"
34+
"@-user\n"
35+
"@user/\n"
36+
"@user_\n"
37+
"_@user\n"
1438
)
1539

1640
expect = (
1741
"r? `@matklad`\n"
1842
"`@bors` r+\n"
19-
43+
"`@a`\n"
44+
"`@abcdefghijklmnopqrstuvwxyzabcdefghijklm`\n"
45+
"`@user`. `@user`, `@user`; `@user`? `@user`!\n"
46+
"`@user`@user\n"
47+
"@user/@user/`@user`\n"
48+
"@`@user`\n"
49+
"/`@user`\n"
50+
"-`@user`\n"
51+
"`@user--name`\n"
52+
"`@user-`\n"
53+
"``@user``\n"
54+
55+
"@abcdefghijklmnopqrstuvwxyzabcdefghijklmo\n"
56+
"text@user\n"
57+
"@-\n"
58+
"@-user\n"
59+
"@user/\n"
60+
"@user_\n"
61+
"_@user\n"
2062
)
2163

2264
assert suppress_pings(body) == expect

0 commit comments

Comments
 (0)