Skip to content

Commit 6bde084

Browse files
committed
Add backward compatibility for detached worktrees
Detached worktrees created before this fix (or via raw git commands) don't have stored names in config. Added path-based inference to handle these cases. Changes: - Added _infer_name_from_path() method to infer names from worktree paths - Fallback chain: stored config → inferred from path → (detached-<commit>) - Works with common path patterns like ../{repo}-{name} and ../{name} - Added test for backward compatibility scenario This ensures switch/list/run commands work even for legacy detached worktrees.
1 parent 0919308 commit 6bde084

3 files changed

Lines changed: 72 additions & 3 deletions

File tree

tools/wt-worktree/notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ wt-worktree/
169169
- Key Insight: Git's `--worktree` config flag requires `extensions.worktreeConfig` to be enabled first
170170
- Tests: Added 6 comprehensive tests for detached worktree creation, listing, switching, running commands, and deletion
171171
- Lesson: Per-worktree config in git requires enabling the worktreeConfig extension, and is the right way to store worktree-specific metadata
172+
- Backward Compatibility: Added `_infer_name_from_path()` to infer names from path patterns for detached worktrees created before this fix or via raw git commands
173+
- Fallback chain: stored config → inferred from path → `(detached-<commit>)`
172174

173175
### Future Improvements
174176

tools/wt-worktree/tests/test_cli.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,3 +404,34 @@ def test_detached_worktree_delete(runner, initialized_repo, no_prompt):
404404
manager = WorktreeManager(config)
405405
wt = manager.find_worktree_by_name("mydetached")
406406
assert wt is None
407+
408+
409+
def test_detached_worktree_backward_compatibility(runner, initialized_repo, no_prompt):
410+
"""Test that detached worktrees created without stored name still work."""
411+
# Create a detached worktree using raw git (simulates old behavior)
412+
from wt import git
413+
from wt.config import Config
414+
from wt.worktree import WorktreeManager
415+
416+
config = Config(initialized_repo)
417+
wt_path = config.resolve_path_pattern("legacy", "feature/legacy")
418+
git.add_worktree(wt_path, "legacy", create_branch=False, base="HEAD",
419+
detached=True, repo_path=initialized_repo)
420+
421+
# Note: Not calling set_worktree_name - simulates old behavior
422+
423+
# List should infer name from path
424+
manager = WorktreeManager(config)
425+
worktrees = manager.list_worktrees()
426+
legacy_wt = None
427+
for wt in worktrees:
428+
if "legacy" in wt["name"]:
429+
legacy_wt = wt
430+
break
431+
432+
assert legacy_wt is not None
433+
assert legacy_wt["name"] == "legacy" # Inferred from path
434+
435+
# Should be able to find it by inferred name
436+
found_wt = manager.find_worktree_by_name("legacy")
437+
assert found_wt is not None

tools/wt-worktree/wt/worktree.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,37 @@ def __init__(self, config: Config):
2020
self.config = config
2121
self.repo_root = config.repo_root
2222

23+
def _infer_name_from_path(self, wt_path: Path) -> Optional[str]:
24+
"""
25+
Try to infer worktree name from its path based on path_pattern.
26+
27+
Provides backward compatibility for detached worktrees created before
28+
the name-storing feature was added.
29+
30+
Args:
31+
wt_path: Path to the worktree
32+
33+
Returns:
34+
Inferred name or None
35+
"""
36+
# Get the pattern and try common formats
37+
pattern = self.config.get("path_pattern")
38+
repo_name = self.repo_root.name
39+
40+
# Try pattern: ../{repo}-{name}
41+
if pattern == "../{repo}-{name}":
42+
expected_prefix = f"{repo_name}-"
43+
if wt_path.name.startswith(expected_prefix):
44+
return wt_path.name[len(expected_prefix):]
45+
46+
# Try pattern: ../{name}
47+
elif pattern == "../{name}":
48+
# Exclude the main worktree
49+
if wt_path != self.repo_root:
50+
return wt_path.name
51+
52+
return None
53+
2354
def list_worktrees(self) -> List[dict]:
2455
"""
2556
List all worktrees with enhanced information.
@@ -40,13 +71,18 @@ def list_worktrees(self) -> List[dict]:
4071
if wt.get("branch"):
4172
wt["name"] = self.config.extract_worktree_name(wt["branch"])
4273
else:
43-
# For detached worktrees, try to get name from git config
74+
# For detached worktrees, try multiple sources
4475
stored_name = git.get_worktree_name(wt["path"])
4576
if stored_name:
4677
wt["name"] = stored_name
4778
else:
48-
# Fallback: use commit hash as identifier
49-
wt["name"] = f"(detached-{wt['commit'][:7]})"
79+
# Try to infer from path (backward compatibility)
80+
inferred_name = self._infer_name_from_path(wt["path"])
81+
if inferred_name:
82+
wt["name"] = inferred_name
83+
else:
84+
# Fallback: use commit hash as identifier
85+
wt["name"] = f"(detached-{wt['commit'][:7]})"
5086

5187
return worktrees
5288

0 commit comments

Comments
 (0)