Skip to content

Commit 4c9dee5

Browse files
committed
fix(sync/git[remote]) Restore LibVCSException resilience
why: ``GitSync.remote()`` previously wrapped the underlying subprocess call in ``try / except LibVCSException: return None`` so a corrupt ``.git/config`` or a locked file did not crash a ``vcspull sync``. The ``git remote show`` -> ``git remote -v`` rewrite dropped the wrapper, so subprocess failures now leak as ``CommandError`` instead of degrading gracefully. what: - Wrap ``self.cmd.remotes.get()`` in ``try / except exc.LibVCSException`` and return ``None`` to preserve the old resilience contract. - Document the suppression in the Notes block so future readers see why the wrapper exists. - Add a regression test that mocks ``cmd.remotes.get`` to raise ``CommandError`` and asserts ``remote()`` returns ``None``.
1 parent 0d99779 commit 4c9dee5

2 files changed

Lines changed: 36 additions & 2 deletions

File tree

src/libvcs/sync/git.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -676,15 +676,24 @@ def remote(self, name: str, **kwargs: t.Any) -> GitRemote | None:
676676
677677
Notes
678678
-----
679-
Uses ``git remote -v`` (via the cached remote manager) instead of
679+
Uses ``git remote -v`` via the remote manager rather than
680680
``git remote show -n``. ``git remote show`` also enumerates every
681681
remote-tracking ref, which pipes thousands of lines through the
682682
subprocess progress callback for repositories with large branch
683683
counts (e.g. ``openai/codex`` at 2,400+ refs) and can appear to
684684
hang. ``git remote -v`` is O(remotes) and cannot block on ref
685685
enumeration.
686+
687+
Subprocess failures from the underlying ``git remote -v`` are
688+
suppressed and returned as ``None`` to preserve the resilience
689+
contract callers relied on with the previous ``git remote show``
690+
path (which wrapped the same call in ``try / except
691+
LibVCSException``).
686692
"""
687-
remote_cmd = self.cmd.remotes.get(remote_name=name, default=None)
693+
try:
694+
remote_cmd = self.cmd.remotes.get(remote_name=name, default=None)
695+
except exc.LibVCSException:
696+
return None
688697
if remote_cmd is None:
689698
return None
690699

tests/sync/test_git.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,3 +1709,28 @@ def test_remote_is_fast_for_repos_with_many_refs(
17091709
assert remote is not None
17101710
assert remote.fetch_url, "fetch URL must be populated"
17111711
assert elapsed < 5.0, f"remote() too slow: {elapsed:.2f}s with 500 fake refs"
1712+
1713+
1714+
def test_remote_swallows_libvcs_exception(
1715+
git_repo: GitSync,
1716+
mocker: MockerFixture,
1717+
) -> None:
1718+
"""``GitSync.remote`` returns ``None`` when ``git remote -v`` fails.
1719+
1720+
The pre-rewrite implementation wrapped the underlying subprocess call
1721+
in ``try / except LibVCSException`` so a corrupt config or a locked
1722+
``.git/config`` did not crash a ``vcspull sync``. The new path must
1723+
preserve that resilience -- callers should still see ``None`` rather
1724+
than a raised exception.
1725+
"""
1726+
mocker.patch.object(
1727+
git_repo.cmd.remotes,
1728+
"get",
1729+
side_effect=exc.CommandError(
1730+
output="fatal: bad config",
1731+
returncode=128,
1732+
cmd="git remote --verbose",
1733+
),
1734+
)
1735+
1736+
assert git_repo.remote("origin") is None

0 commit comments

Comments
 (0)