Skip to content

Commit e214bf0

Browse files
committed
Disable version history instead of crashing on an unusable .git
A config dir bind-mounted from a git submodule has a .git pointer file whose target lives in the parent repo's .git/modules tree; inside the container that tree isn't mounted, so git init over the pointer aborts. The GitCommandError (a CalledProcessError, not OSError) escaped discover_or_init's catch and crash-looped startup. Detect an unusable .git before init and disable cleanly, and broaden the catch to any git setup failure so version history stays the soft, optional feature it's documented to be.
1 parent 7526534 commit e214bf0

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

esphome_device_builder/controllers/version_history/git_repo.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,19 @@ def discover_or_init(self) -> None:
191191
self.config_dir,
192192
toplevel,
193193
)
194+
elif (self.config_dir / ".git").exists():
195+
# rev-parse found no work tree yet ``.git`` is present: an
196+
# unusable git dir (a submodule / worktree pointer whose
197+
# target isn't mounted, or a corrupt repo). Re-initialising
198+
# over it fails, so disable rather than crash on init.
199+
_LOGGER.info(
200+
"Config dir %s has a .git git can't use here (likely a submodule or "
201+
"worktree whose git dir isn't mounted); version history disabled",
202+
self.config_dir,
203+
)
204+
return
194205
self._init_repo()
195-
except OSError as exc:
206+
except (OSError, subprocess.CalledProcessError) as exc:
196207
_LOGGER.warning("Could not set up version-history git repo: %s", exc)
197208

198209
def _discover_toplevel(self) -> Path | None:

tests/controllers/version_history/test_git_repo.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,39 @@ def test_init_creates_repo_and_gitignore(tmp_path: Path) -> None:
7474
assert "Initialize version history" in _git(tmp_path, "log", "--format=%s")
7575

7676

77+
def test_unusable_git_pointer_disables_without_raising(tmp_path: Path) -> None:
78+
"""A ``.git`` pointing at a missing git dir (broken submodule) disables, not crash."""
79+
# Mirrors a submodule whose parent ``.git/modules`` tree isn't mounted:
80+
# ``git init`` over this pointer aborts with ``fatal: not a git repository``.
81+
(tmp_path / ".git").write_text("gitdir: ./nonexistent/git/dir\n", encoding="utf-8")
82+
repo = GitRepo(config_dir=tmp_path)
83+
84+
repo.discover_or_init()
85+
86+
assert not repo.enabled
87+
88+
89+
def test_init_failure_disables_without_raising(
90+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
91+
) -> None:
92+
"""A ``git init`` that exits non-zero disables the feature instead of crashing."""
93+
real_run = subprocess.run
94+
95+
def _fake(cmd: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
96+
if "init" in cmd:
97+
return subprocess.CompletedProcess(cmd, 128, "", "fatal: boom")
98+
return real_run(cmd, **kwargs) # type: ignore[arg-type]
99+
100+
monkeypatch.setattr(
101+
"esphome_device_builder.controllers.version_history.git_repo.subprocess.run", _fake
102+
)
103+
repo = GitRepo(config_dir=tmp_path)
104+
105+
repo.discover_or_init()
106+
107+
assert not repo.enabled
108+
109+
77110
def test_adopts_existing_repo_without_touching_gitignore(tmp_path: Path) -> None:
78111
"""A pre-existing work tree is adopted; the user's .gitignore is untouched."""
79112
_make_repo(tmp_path)

0 commit comments

Comments
 (0)