fix(plugins): normalize browser-pasted GitHub repo URLs (#33539)

Accept common GitHub web URLs in `hermes plugins install` by normalizing repository views back to cloneable `.git` URLs, with focused parser coverage.
This commit is contained in:
Teknium 2026-06-13 13:23:59 -07:00 committed by GitHub
parent 425e777f54
commit 08890d77e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 75 additions and 0 deletions

View file

@ -135,6 +135,20 @@ def _sanitize_plugin_name(
return target
_GITHUB_BROWSER_SEGMENTS = {
"actions",
"blob",
"commit",
"commits",
"issues",
"pull",
"pulls",
"releases",
"tree",
"wiki",
}
def _resolve_git_url(identifier: str) -> tuple[str, Optional[str]]:
"""Turn an identifier into a cloneable Git URL and optional subdirectory.
@ -146,6 +160,8 @@ def _resolve_git_url(identifier: str) -> tuple[str, Optional[str]]:
- Full URL: https://github.com/owner/repo.git
- Full URL: git@github.com:owner/repo.git
- Full URL: ssh://git@github.com/owner/repo.git
- Browser URL: https://github.com/owner/repo/tree/main/path
(https://github.com/owner/repo.git, "path")
- Shorthand: owner/repo https://github.com/owner/repo.git
- Shorthand w/ subdir: owner/repo/path/to/plugin
(https://github.com/owner/repo.git, "path/to/plugin")
@ -161,6 +177,17 @@ def _resolve_git_url(identifier: str) -> tuple[str, Optional[str]]:
"""
# Already a URL.
if identifier.startswith(("https://", "http://", "git@", "ssh://", "file://")):
if identifier.startswith("https://github.com/"):
path = identifier[len("https://github.com/") :]
path = path.split("?", 1)[0].split("#", 1)[0].strip("/")
parts = path.split("/")
if len(parts) >= 3 and all(parts[:2]) and parts[2] in _GITHUB_BROWSER_SEGMENTS:
repo = parts[1].removesuffix(".git")
subdir = None
if parts[2] == "tree" and len(parts) >= 5:
subdir = "/".join(p for p in parts[4:] if p).strip("/") or None
return f"https://github.com/{parts[0]}/{repo}.git", subdir
# Explicit ``#subdir`` fragment — unambiguous for any scheme.
if "#" in identifier:
git_url, _, frag = identifier.partition("#")