Coverage for plugin/scripts/_rhiza_forge.py: 100%

36 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-15 14:46 +0000

1#!/usr/bin/env python3 

2"""Which forge a repository is hosted on, and a read-only way to ask git. 

3 

4Two entry points need this and neither may guess: ``platform_cli.py`` picks between 

5`gh` and `glab` before it *writes* anything (a PR, an issue, a release), and 

6``pr_status.py`` picks between them before it *reads* CI state. Both start from the same 

7question — what is `origin`? — and both get the same wrong answer if it is answered 

8loosely. 

9 

10The detection lived inside ``platform_cli.py`` while it was the only caller. It is here 

11now because the alternative was the second caller copying it, and ``classify_host`` is 

12not a function to have two of: it is the piece that refuses ``github.com.evil.example``, 

13a host that embeds a known domain without being a subdomain of one. A copy that drifts 

14from this one acts against the wrong forge, which is strictly worse than refusing to act. 

15""" 

16 

17from __future__ import annotations 

18 

19import os 

20import re 

21import shutil 

22import subprocess # nosec B404 

23from pathlib import Path 

24 

25_KNOWN_HOSTS = {"github.com": "github", "gitlab.com": "gitlab"} 

26 

27# `git@host:owner/repo` and `scheme://[user@]host/owner/repo`, the two spellings a 

28# remote is written in. 

29_SSH_REMOTE = re.compile(r"git@([^:]+):") 

30_URL_REMOTE = re.compile(r"[a-zA-Z]+://(?:[^@]+@)?([^/]+)/") 

31 

32 

33class PlatformError(Exception): 

34 """The hosting platform could not be determined.""" 

35 

36 

37def git_stdout(target_dir: Path, args: list[str]) -> str: 

38 """Run a read-only git command in *target_dir*, returning stdout ('' on failure). 

39 

40 ``GIT_TERMINAL_PROMPT=0`` so a repo whose remote wants credentials fails fast 

41 instead of blocking a non-interactive run on a password prompt. 

42 """ 

43 env = os.environ.copy() 

44 env["GIT_TERMINAL_PROMPT"] = "0" 

45 result = subprocess.run( # nosec B603 

46 [shutil.which("git") or "git", *args], 

47 cwd=str(target_dir), 

48 capture_output=True, 

49 text=True, 

50 env=env, 

51 check=False, 

52 ) 

53 return result.stdout.strip() if result.returncode == 0 else "" 

54 

55 

56def classify_host(host: str) -> str | None: 

57 """Return ``github``/``gitlab`` for *host*, or None when it is neither. 

58 

59 Label-boundary matching, so ``github.com.evil.example`` — which embeds a known 

60 domain without being a subdomain of it — is not taken for GitHub. Self-hosted 

61 GitLab conventionally lives at ``gitlab.<company>.<tld>``. 

62 

63 The exact host, and a real subdomain of it: 

64 

65 >>> classify_host("github.com") 

66 'github' 

67 >>> classify_host("code.gitlab.com") 

68 'gitlab' 

69 

70 Self-hosted GitLab, matched on the first label: 

71 

72 >>> classify_host("gitlab.acme.io") 

73 'gitlab' 

74 

75 A host that *embeds* a known domain without being a subdomain of one is refused 

76 rather than guessed at — this is the case the function exists for: 

77 

78 >>> classify_host("github.com.evil.example") is None 

79 True 

80 

81 So is anything simply unrecognised: 

82 

83 >>> classify_host("bitbucket.org") is None 

84 True 

85 """ 

86 h = host.lower().rstrip(".") 

87 for domain, platform in _KNOWN_HOSTS.items(): 

88 if h == domain or h.endswith(f".{domain}"): 

89 return platform 

90 if domain in h: 

91 return None 

92 return "gitlab" if h.split(".", 1)[0] == "gitlab" else None 

93 

94 

95def detect_platform(target_dir: Path) -> str: 

96 """Return the hosting platform for *target_dir*'s `origin` remote.""" 

97 url = git_stdout(target_dir, ["remote", "get-url", "origin"]) 

98 if not url: 

99 raise PlatformError("no `origin` remote — cannot tell which platform to use") 

100 match = _SSH_REMOTE.match(url) or _URL_REMOTE.match(url) 

101 if match is None: 

102 raise PlatformError(f"could not parse a host from origin: {url}") 

103 platform = classify_host(match.group(1)) 

104 if platform is None: 

105 raise PlatformError( 

106 f"unsupported host {match.group(1)!r} — only GitHub and GitLab are handled" 

107 ) 

108 return platform 

109 

110 

111def current_branch(target_dir: Path) -> str | None: 

112 """Return the branch checked out in *target_dir*, or None when there is none. 

113 

114 ``symbolic-ref`` rather than ``rev-parse --abbrev-ref``: the caller uses this to 

115 find *the request for the branch you are on*, so the two cases that must not be 

116 confused are a detached HEAD (no branch — ``rev-parse`` answers the literal string 

117 ``HEAD``, which is a plausible-looking branch name) and a branch with no commits on 

118 it yet (a branch, which ``rev-parse`` fails outright on). ``symbolic-ref`` gets both 

119 right. 

120 """ 

121 return git_stdout(target_dir, ["symbolic-ref", "--short", "HEAD"]) or None