Skip to content

Commit e082804

Browse files
committed
sync/git,tests(feat[update_repo]) Fix checkout error detection, add errored sync tests
why: cmd.checkout() without check_returncode=True silently swallowed checkout failures, making the except CommandError block dead code. Also, SVN and HG had no error-path tests for update_repo(). what: - Add check_returncode=True to both cmd.checkout() calls in GitSync.update_repo() - Add test_update_repo_checkout_failure_returns_sync_result for git (nonexistent branch) - Add test_update_repo_checkout_failure_returns_sync_result for svn (deleted remote) - Add test_update_repo_pull_failure_returns_sync_result for hg (deleted remote)
1 parent 373770a commit e082804

4 files changed

Lines changed: 96 additions & 2 deletions

File tree

src/libvcs/sync/git.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,10 @@ def update_repo(
498498

499499
# Checkout the remote branch
500500
try:
501-
process = self.cmd.checkout(branch=git_tag)
501+
process = self.cmd.checkout(
502+
branch=git_tag,
503+
check_returncode=True,
504+
)
502505
except exc.CommandError as e:
503506
self.log.exception("Failed to checkout tag: '%s'", git_tag)
504507
result.add_error("checkout", str(e), exception=e)
@@ -546,7 +549,10 @@ def update_repo(
546549

547550
else:
548551
try:
549-
process = self.cmd.checkout(branch=git_tag)
552+
process = self.cmd.checkout(
553+
branch=git_tag,
554+
check_returncode=True,
555+
)
550556
except exc.CommandError as e:
551557
self.log.exception("Failed to checkout tag: '%s'", git_tag)
552558
result.add_error("checkout", str(e), exception=e)

tests/sync/test_git.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,3 +992,36 @@ def test_update_repo_fetch_failure_returns_sync_result(
992992
assert result.errors[0].step == "fetch"
993993
assert result.errors[0].exception is not None
994994
assert isinstance(result.errors[0].exception, exc.CommandError)
995+
996+
997+
def test_update_repo_checkout_failure_returns_sync_result(
998+
create_git_remote_bare_repo: CreateRepoPytestFixtureFn,
999+
tmp_path: pathlib.Path,
1000+
) -> None:
1001+
"""Test that a checkout failure in update_repo() returns SyncResult with error."""
1002+
git_server = create_git_remote_bare_repo()
1003+
git_repo = GitSync(
1004+
path=tmp_path / "myrepo",
1005+
url=git_server.as_uri(),
1006+
)
1007+
git_repo.obtain()
1008+
1009+
# Make a commit and push so the repo has a valid HEAD
1010+
initial_file = git_repo.path / "initial_file"
1011+
initial_file.write_text("content", encoding="utf-8")
1012+
git_repo.run(["add", str(initial_file)])
1013+
git_repo.run(["commit", "-m", "initial commit"])
1014+
git_repo.run(["push"])
1015+
1016+
# Set rev to a nonexistent branch to trigger a checkout failure
1017+
git_repo.rev = "nonexistent-branch"
1018+
1019+
result = git_repo.update_repo()
1020+
1021+
assert isinstance(result, SyncResult)
1022+
assert result.ok is False
1023+
assert bool(result) is False
1024+
assert len(result.errors) > 0
1025+
assert result.errors[0].step == "checkout"
1026+
assert result.errors[0].exception is not None
1027+
assert isinstance(result.errors[0].exception, exc.CommandError)

tests/sync/test_hg.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from libvcs import exc
1111
from libvcs._internal.run import run
1212
from libvcs._internal.shortcuts import create_project
13+
from libvcs.sync.base import SyncResult
1314
from libvcs.sync.hg import HgSync
1415

1516
if not shutil.which("hg"):
@@ -100,3 +101,31 @@ def test_vulnerability_2022_03_12_command_injection(
100101
assert not pathlib.Path(
101102
random_dir / "HELLO",
102103
).exists(), "Prevent command injection in hg aliases"
104+
105+
106+
def test_update_repo_pull_failure_returns_sync_result(
107+
tmp_path: pathlib.Path,
108+
projects_path: pathlib.Path,
109+
hg_remote_repo: pathlib.Path,
110+
) -> None:
111+
"""Test that a deleted remote in update_repo() returns SyncResult with error."""
112+
repo_name = "my_hg_error_project"
113+
114+
hg_repo = HgSync(
115+
url=f"file://{hg_remote_repo}",
116+
path=projects_path / repo_name,
117+
)
118+
119+
# First update_repo clones since .hg doesn't exist yet
120+
hg_repo.update_repo()
121+
122+
# Delete the remote to cause a pull failure
123+
shutil.rmtree(hg_remote_repo)
124+
125+
result = hg_repo.update_repo()
126+
127+
assert isinstance(result, SyncResult)
128+
assert result.ok is False
129+
assert len(result.errors) > 0
130+
assert result.errors[0].step == "pull"
131+
assert isinstance(result.errors[0].exception, exc.CommandError)

tests/sync/test_svn.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import pytest
99

10+
from libvcs import exc
11+
from libvcs.sync.base import SyncResult
1012
from libvcs.sync.svn import SvnSync
1113

1214
if t.TYPE_CHECKING:
@@ -74,3 +76,27 @@ def test_repo_svn_remote_checkout(
7476
assert svn_repo.get_revision_file("./") == 0
7577

7678
assert svn_repo_checkout_dir.exists()
79+
80+
81+
def test_update_repo_checkout_failure_returns_sync_result(
82+
create_svn_remote_repo: CreateRepoPytestFixtureFn,
83+
tmp_path: pathlib.Path,
84+
projects_path: pathlib.Path,
85+
) -> None:
86+
"""Test that a deleted remote in update_repo() returns SyncResult with error."""
87+
svn_server = create_svn_remote_repo()
88+
svn_repo_checkout_dir = projects_path / "my_svn_checkout"
89+
svn_repo = SvnSync(path=svn_repo_checkout_dir, url=f"file://{svn_server!s}")
90+
91+
svn_repo.obtain()
92+
93+
# Delete the remote to cause an update failure
94+
shutil.rmtree(svn_server)
95+
96+
result = svn_repo.update_repo()
97+
98+
assert isinstance(result, SyncResult)
99+
assert result.ok is False
100+
assert len(result.errors) > 0
101+
assert result.errors[0].step == "checkout"
102+
assert isinstance(result.errors[0].exception, exc.CommandError)

0 commit comments

Comments
 (0)