Skip to content

Commit 87c2a39

Browse files
committed
Return None from submodules.get() for a nested plain repository
libgit2's git_submodule_lookup reports GIT_EEXISTS, not GIT_ENOTFOUND, when a repository exists at the path but was never registered as a submodule. check_error turns that into AlreadyExistsError, which get() did not catch, so it raised instead of returning None as its docstring promises. __contains__ is built on get(), so `name in repo.submodules` raised for the same input. __getitem__ keeps raising AlreadyExistsError, so callers that need to tell "a repository is there" from "nothing is there" still can. Fixes #1405 Assisted-by: Claude Code (Claude Opus 5)
1 parent d532da7 commit 87c2a39

2 files changed

Lines changed: 25 additions & 2 deletions

File tree

pygit2/submodules.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from ._pygit2 import Oid
3333
from .callbacks import RemoteCallbacks, git_fetch_options
3434
from .enums import SubmoduleIgnore, SubmoduleStatus
35-
from .errors import check_error
35+
from .errors import AlreadyExistsError, check_error
3636
from .ffi import C, ffi
3737
from .utils import decode_fs_path, decode_string, encode_string
3838

@@ -210,7 +210,11 @@ def get(self, name: str) -> Submodule | None:
210210
"""
211211
try:
212212
return self[name]
213-
except KeyError:
213+
except (KeyError, AlreadyExistsError):
214+
# libgit2 reports GIT_EEXISTS, which check_error turns into
215+
# AlreadyExistsError, when a repository exists at the path but was
216+
# never registered as a submodule. There is still no submodule by
217+
# that name, so report it the same way as a missing one.
214218
return None
215219

216220
def add(

test/test_submodule.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,25 @@ def test_lookup_missing_submodule(repo: Repository) -> None:
6868
assert repo.submodules.get('does-not-exist') is None
6969

7070

71+
def test_lookup_nested_repo_that_is_not_a_submodule(tmp_path: Path) -> None:
72+
"""A plain repository inside another is not a submodule.
73+
74+
libgit2 reports GIT_EEXISTS for this case rather than GIT_ENOTFOUND, which
75+
reaches Python as AlreadyExistsError. get() and __contains__ must still
76+
describe it as absent, per their documented contracts.
77+
"""
78+
outer = pygit2.init_repository(tmp_path / 'outer')
79+
pygit2.init_repository(tmp_path / 'outer' / 'nested')
80+
81+
assert outer.submodules.get('nested') is None
82+
assert 'nested' not in outer.submodules
83+
84+
# __getitem__ keeps reporting the distinction, so callers that care can
85+
# still tell "there is a repository there" from "there is nothing there".
86+
with pytest.raises(pygit2.AlreadyExistsError):
87+
outer.submodules['nested']
88+
89+
7190
def test_listall_submodules(repo: Repository) -> None:
7291
submodules = repo.listall_submodules()
7392
assert len(submodules) == 1

0 commit comments

Comments
 (0)