Skip to content

Commit 158d746

Browse files
committed
worktree_sync(fix[sync_worktree]) Guard against empty plan entries list
why: plan_worktree_sync should always return at least one entry for a single config input, but an internal bug could cause an empty list. An IndexError with no context is harder to debug than an explicit ERROR entry. what: - Add guard before entries[0] access in sync_worktree - Return ERROR entry with descriptive message when plan is empty - Add test mocking empty plan return to verify ERROR instead of crash
1 parent 3f45908 commit 158d746

File tree

2 files changed

+37
-0
lines changed

2 files changed

+37
-0
lines changed

src/vcspull/_internal/worktree_sync.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,15 @@ def sync_worktree(
655655
"""
656656
# Plan the operation
657657
entries = plan_worktree_sync(repo_path, [wt_config], workspace_root)
658+
if not entries:
659+
log.warning("plan_worktree_sync returned empty list for %s", wt_config)
660+
return WorktreePlanEntry(
661+
worktree_path=_resolve_worktree_path(wt_config, workspace_root),
662+
ref_type="unknown",
663+
ref_value="unknown",
664+
action=WorktreeAction.ERROR,
665+
error="internal: planning produced no entries",
666+
)
658667
entry = entries[0]
659668

660669
if dry_run or entry.action in (WorktreeAction.ERROR, WorktreeAction.BLOCKED):

tests/test_worktree.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3389,3 +3389,31 @@ def mock_run(*args: t.Any, **kwargs: t.Any) -> t.Any:
33893389
# Creation should succeed despite lock failure
33903390
assert entry.action == WorktreeAction.CREATE
33913391
assert worktree_path.exists()
3392+
3393+
3394+
def test_sync_worktree_empty_plan_returns_error(
3395+
tmp_path: pathlib.Path,
3396+
mocker: MockerFixture,
3397+
) -> None:
3398+
"""Test sync_worktree returns ERROR entry when plan returns empty list.
3399+
3400+
Defense-in-depth: plan_worktree_sync should always return at least one
3401+
entry, but if it doesn't, sync_worktree should return an ERROR entry
3402+
instead of crashing with IndexError.
3403+
"""
3404+
# Mock plan_worktree_sync to return empty list
3405+
mocker.patch(
3406+
"vcspull._internal.worktree_sync.plan_worktree_sync",
3407+
return_value=[],
3408+
)
3409+
3410+
wt_config: WorktreeConfigDict = {
3411+
"dir": "../empty-plan-wt",
3412+
"tag": "v1.0.0",
3413+
}
3414+
3415+
entry = sync_worktree(tmp_path, wt_config, tmp_path)
3416+
3417+
assert entry.action == WorktreeAction.ERROR
3418+
assert entry.error is not None
3419+
assert "no entries" in entry.error

0 commit comments

Comments
 (0)