Skip to content

Commit 5f01197

Browse files
committed
Fix find_remote_for_url matching across path boundaries
target.startswith(remote_base) matched any URL whose host+path happened to share a leading string with the remote, regardless of path component boundaries. A remote base https://github.com/myorg therefore matched an unrelated URL https://github.com/myorg-private/repo, and dfetch add would attach the wrong remote (and compute a bogus repo-path). Require either an exact match or a "/" path boundary. https://claude.ai/code/session_01KKvrvnVvsBChohuxbRRmzA
1 parent 0d3defd commit 5f01197

3 files changed

Lines changed: 22 additions & 1 deletion

File tree

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Release 0.14.1 (unreleased)
33

44
* Fix ``dfetch import`` mangling the namespace of a generic VCS URL whose path contains ``.git`` other than as a suffix
55
* Fix ``Version`` comparison raising ``AttributeError`` when compared against a non-``Version`` object
6+
* Fix ``dfetch add`` matching a remote whose base URL is only a string prefix (not a path prefix) of the project URL
67

78
Release 0.14.0 (released 2026-06-14)
89
===========================

dfetch/manifest/manifest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ def find_remote_for_url(self, remote_url: str) -> Remote | None:
592592
target = remote_url.rstrip("/")
593593
for remote in self.remotes:
594594
remote_base = remote.url.rstrip("/")
595-
if target.startswith(remote_base):
595+
if target == remote_base or target.startswith(remote_base + "/"):
596596
return remote
597597
return None
598598

tests/test_add.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,26 @@ def test_determine_remote_returns_none_for_empty_remotes():
151151
assert result is None
152152

153153

154+
def test_determine_remote_requires_path_boundary():
155+
"""An org-scoped remote must not match a different org sharing its prefix."""
156+
m = Mock()
157+
m.remotes = [_make_remote("myorg", "https://github.com/myorg")]
158+
result = Manifest.find_remote_for_url(
159+
m, "https://github.com/myorg-private/repo.git"
160+
)
161+
assert result is None
162+
163+
164+
def test_determine_remote_matches_exact_and_subpath():
165+
"""The boundary check still matches the remote itself and any URL beneath it."""
166+
m = Mock()
167+
m.remotes = [_make_remote("myorg", "https://github.com/myorg")]
168+
assert Manifest.find_remote_for_url(m, "https://github.com/myorg") is not None
169+
assert (
170+
Manifest.find_remote_for_url(m, "https://github.com/myorg/repo.git") is not None
171+
)
172+
173+
154174
# ---------------------------------------------------------------------------
155175
# Add command – non-interactive
156176
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)