From f6283456bbd59b3f47baa17aa6ed77ded1e60855 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:10:16 +0200 Subject: [PATCH 1/5] feat: relax host restriction in vcs.git URL parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parse_base_url and parse_repo_name hard-rejected any host other than bitbucket.org — a leftover from when bootstrapping required the Bitbucket API. With the Forgejo saas wrapper (#18) in place, downstream consumers feed Forgejo-shaped URLs through these helpers. Drop the host check; SCP-style format validation stays. Also corrects parse_repo_name's docstring, which was a stale copy of parse_base_url's. --- src/byteb4rb1e/utils/vcs/git.py | 46 ++++++++++++--------------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/src/byteb4rb1e/utils/vcs/git.py b/src/byteb4rb1e/utils/vcs/git.py index cd4cd87..7d718b2 100644 --- a/src/byteb4rb1e/utils/vcs/git.py +++ b/src/byteb4rb1e/utils/vcs/git.py @@ -26,54 +26,40 @@ class GitError(Exception): def parse_base_url(base_url: str) -> str: - """Extract workspace from an SCP-style Bitbucket base URL. + """Extract the workspace from an SCP-style base URL. - The host part must be exactly ``bitbucket.org`` — bootstrapping - requires the Bitbucket API, so other hosts are rejected. + Accepts any host (Bitbucket, Forgejo, GitHub, ...) as long as + the URL is SCP-style:: - >>> _parse_base_url("git@bitbucket.org:byteb4rb1e") - 'byteb4rb1e' + git@bitbucket.org:byteb4rb1e/foo.git → byteb4rb1e + git@git.code.tiararodney.com:h5p-mirror/foo.git → h5p-mirror """ - # SCP-style: git@bitbucket.org:workspace + # SCP-style: git@host:workspace/repo if ":" not in base_url or "//" in base_url: raise ValueError( - f"Expected SCP-style URL (git@bitbucket.org:workspace), " + f"Expected SCP-style URL (git@host:workspace), " f"got: {base_url}" ) - host_part, workspace = base_url.split(":", 1) - # host_part is e.g. "git@bitbucket.org" - host = host_part.split("@", 1)[-1] - if host != "bitbucket.org": - raise ValueError( - f"Mirror base URL must target bitbucket.org, " - f"got host: {host}" - ) + _, workspace = base_url.split(":", 1) return Path(workspace).parent def parse_repo_name(base_url: str) -> str: - """Extract workspace from an SCP-style Bitbucket base URL. + """Extract the repository name from an SCP-style base URL. - The host part must be exactly ``bitbucket.org`` — bootstrapping - requires the Bitbucket API, so other hosts are rejected. + Accepts any host (Bitbucket, Forgejo, GitHub, ...) as long as + the URL is SCP-style:: - >>> _parse_base_url("git@bitbucket.org:byteb4rb1e") - 'byteb4rb1e' + git@bitbucket.org:byteb4rb1e/foo.git → foo + git@git.code.tiararodney.com:h5p-mirror/foo.git → foo """ - # SCP-style: git@bitbucket.org:workspace + # SCP-style: git@host:workspace/repo if ":" not in base_url or "//" in base_url: raise ValueError( - f"Expected SCP-style URL (git@bitbucket.org:workspace), " + f"Expected SCP-style URL (git@host:workspace), " f"got: {base_url}" ) - host_part, workspace = base_url.split(":", 1) - # host_part is e.g. "git@bitbucket.org" - host = host_part.split("@", 1)[-1] - if host != "bitbucket.org": - raise ValueError( - f"Mirror base URL must target bitbucket.org, " - f"got host: {host}" - ) + _, workspace = base_url.split(":", 1) return Path(workspace).name.split('.')[0] From 297b7c49d0670bdc2476545eaa4e8e0510e88dcb Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:10:16 +0200 Subject: [PATCH 2/5] test: add vcs.git URL parsing unit tests --- tests/unit/byteb4rb1e/utils/vcs/test_git.py | 56 +++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/unit/byteb4rb1e/utils/vcs/test_git.py diff --git a/tests/unit/byteb4rb1e/utils/vcs/test_git.py b/tests/unit/byteb4rb1e/utils/vcs/test_git.py new file mode 100644 index 0000000..c56015d --- /dev/null +++ b/tests/unit/byteb4rb1e/utils/vcs/test_git.py @@ -0,0 +1,56 @@ +"""Tests for the git subprocess wrapper's URL parsing helpers.""" + +import pytest + +from byteb4rb1e.utils.vcs.git import parse_base_url, parse_repo_name + + +class TestParseBaseUrl: + + def test_bitbucket(self) -> None: + result = parse_base_url("git@bitbucket.org:byteb4rb1e/foo.git") + assert str(result) == "byteb4rb1e" + + def test_forgejo_host(self) -> None: + result = parse_base_url( + "git@git.code.tiararodney.com:h5p-mirror/foo.git" + ) + assert str(result) == "h5p-mirror" + + def test_github_host(self) -> None: + result = parse_base_url("git@github.com:h5p/h5p-multi-choice.git") + assert str(result) == "h5p" + + def test_rejects_https_url(self) -> None: + with pytest.raises(ValueError): + parse_base_url("https://bitbucket.org/byteb4rb1e/foo.git") + + def test_rejects_url_without_colon(self) -> None: + with pytest.raises(ValueError): + parse_base_url("bitbucket.org/byteb4rb1e/foo.git") + + +class TestParseRepoName: + + def test_bitbucket(self) -> None: + assert parse_repo_name( + "git@bitbucket.org:byteb4rb1e/foo.git" + ) == "foo" + + def test_forgejo_host(self) -> None: + assert parse_repo_name( + "git@git.code.tiararodney.com:h5p-mirror/foo.git" + ) == "foo" + + def test_without_git_suffix(self) -> None: + assert parse_repo_name( + "git@git.code.tiararodney.com:h5p-mirror/foo" + ) == "foo" + + def test_rejects_https_url(self) -> None: + with pytest.raises(ValueError): + parse_repo_name("https://git.code.tiararodney.com/x/foo.git") + + def test_rejects_url_without_colon(self) -> None: + with pytest.raises(ValueError): + parse_repo_name("git.code.tiararodney.com/x/foo.git") From b6d7ada521ee1960111510268e5b855c30d13d67 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:15:12 +0200 Subject: [PATCH 3/5] fix: make parse_base_url return str as annotated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It returned Path(workspace).parent — a Path — despite the declared str return type. Resolves the mypy return-value error. --- src/byteb4rb1e/utils/vcs/git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/byteb4rb1e/utils/vcs/git.py b/src/byteb4rb1e/utils/vcs/git.py index 7d718b2..9133794 100644 --- a/src/byteb4rb1e/utils/vcs/git.py +++ b/src/byteb4rb1e/utils/vcs/git.py @@ -41,7 +41,7 @@ def parse_base_url(base_url: str) -> str: f"got: {base_url}" ) _, workspace = base_url.split(":", 1) - return Path(workspace).parent + return str(Path(workspace).parent) def parse_repo_name(base_url: str) -> str: From c7d7adc36003d8f72586eab607f4b783cd59f801 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:15:13 +0200 Subject: [PATCH 4/5] test: assert parse_base_url returns str --- tests/unit/byteb4rb1e/utils/vcs/test_git.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/unit/byteb4rb1e/utils/vcs/test_git.py b/tests/unit/byteb4rb1e/utils/vcs/test_git.py index c56015d..e60a78e 100644 --- a/tests/unit/byteb4rb1e/utils/vcs/test_git.py +++ b/tests/unit/byteb4rb1e/utils/vcs/test_git.py @@ -9,17 +9,21 @@ class TestParseBaseUrl: def test_bitbucket(self) -> None: result = parse_base_url("git@bitbucket.org:byteb4rb1e/foo.git") - assert str(result) == "byteb4rb1e" + assert result == "byteb4rb1e" def test_forgejo_host(self) -> None: result = parse_base_url( "git@git.code.tiararodney.com:h5p-mirror/foo.git" ) - assert str(result) == "h5p-mirror" + assert result == "h5p-mirror" def test_github_host(self) -> None: result = parse_base_url("git@github.com:h5p/h5p-multi-choice.git") - assert str(result) == "h5p" + assert result == "h5p" + + def test_returns_str(self) -> None: + result = parse_base_url("git@bitbucket.org:byteb4rb1e/foo.git") + assert isinstance(result, str) def test_rejects_https_url(self) -> None: with pytest.raises(ValueError): From 657e8b1e4ce8762125ae33e788cc06b3b4dae6c8 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:15:41 +0200 Subject: [PATCH 5/5] todo(21): done parse_base_url and parse_repo_name accept SCP-style URLs for any host: the bitbucket.org-only ValueError is removed, SCP-style format validation (colon required, no scheme) retained, Bitbucket behavior unchanged. parse_repo_name's stale copy-pasted docstring corrected. Additionally parse_base_url now returns str as annotated (was Path), eliminating the mypy return-value error. 10 unit tests cover Bitbucket-, Forgejo- and GitHub-shaped URLs, .git suffix handling, str return type, and both rejection cases; 106/106 pass via tox -e unit-py313 with vcs/git.py down to 4 pre-existing baseline mypy errors. --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 82a0626..5cb99ce 100644 --- a/TODO +++ b/TODO @@ -284,7 +284,7 @@ Content-Type: application/issue ID: 21 Type: feature Title: relax host restriction in vcs.git parse_base_url and parse_repo_name -Status: in-progress +Status: done Priority: high Created: 2026-06-06 Relationships: