Skip to content

Commit 905c4e4

Browse files
committed
Fix: Support running wt commands from secondary worktrees
Problem: When running wt commands from a secondary worktree, the tool would ask to run 'wt init' again because it couldn't find .wt.toml. Root cause: git rev-parse --show-toplevel returns the current worktree's root directory, not the main worktree root where .wt.toml is stored. Solution: - Added get_main_worktree_root() function in git.py - Uses 'git worktree list' to find the main worktree (always first) - Updated CLI to use main worktree root for config loading - Added test to verify commands work from secondary worktrees Changes: - wt/git.py: Added get_main_worktree_root() function - wt/cli.py: Use get_main_worktree_root() instead of get_repo_root() - tests/test_cli.py: Added test_commands_from_secondary_worktree - notes.md: Documented the issue and solution Tests: 58 tests pass (added 1 new test) Coverage: git.py increased from 83% to 86%
1 parent 80c9a70 commit 905c4e4

4 files changed

Lines changed: 78 additions & 6 deletions

File tree

tools/wt-worktree/notes.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,23 @@ wt-worktree/
9595
- Solution: Added conditional import with fallback to tomli package
9696
- Lesson: Always consider Python version compatibility
9797

98+
6. **Config Not Found in Secondary Worktrees**
99+
- Problem: When running wt commands from a secondary worktree, it would ask to run `wt init` again because it couldn't find `.wt.toml`
100+
- Cause: `git rev-parse --show-toplevel` returns the current worktree's root, not the main worktree root where `.wt.toml` is stored
101+
- Solution: Added `get_main_worktree_root()` function that uses `git worktree list` to find the main worktree (always the first in the list)
102+
- Lesson: When working with worktrees, distinguish between current worktree and main worktree
103+
- Test: Added `test_commands_from_secondary_worktree` to verify all commands work from secondary worktrees
104+
98105
### Test Results
99106

100-
- **Total Tests**: 57
101-
- **Passed**: 57
107+
- **Total Tests**: 58
108+
- **Passed**: 58
102109
- **Coverage**: 63%
103110
- **Key Coverage Areas**:
104-
- git.py: 83% (core git operations well tested)
111+
- git.py: 86% (core git operations well tested, including worktree detection)
105112
- config.py: 75% (configuration management tested)
106113
- worktree.py: 62% (worktree operations tested)
107-
- cli.py: 30% (basic CLI commands tested, some edge cases untested)
114+
- cli.py: 54% (CLI commands tested including secondary worktree usage)
108115

109116
### Future Improvements
110117

tools/wt-worktree/tests/test_cli.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,48 @@ def test_clean_command(runner, initialized_repo, no_prompt):
158158
"""Test wt clean command."""
159159
result = runner.invoke(cli, ["clean", "--dry-run"])
160160
assert result.exit_code == 0
161+
162+
163+
def test_commands_from_secondary_worktree(runner, initialized_repo, no_prompt):
164+
"""Test that wt commands work from secondary worktrees."""
165+
# Create a secondary worktree
166+
result = runner.invoke(cli, ["switch", "-c", "feat"])
167+
assert result.exit_code == 0
168+
169+
# Find the worktree path
170+
from wt import git
171+
worktrees = git.list_worktrees(initialized_repo)
172+
feat_worktree = None
173+
for wt in worktrees:
174+
if wt.get("branch") == "feature/feat":
175+
feat_worktree = wt["path"]
176+
break
177+
178+
assert feat_worktree is not None
179+
180+
# Change to the secondary worktree directory
181+
original_dir = os.getcwd()
182+
try:
183+
os.chdir(feat_worktree)
184+
185+
# Run wt list from the secondary worktree - should still work
186+
result = runner.invoke(cli, ["list"])
187+
assert result.exit_code == 0
188+
assert "main" in result.output
189+
assert "feat" in result.output
190+
191+
# Run wt status from the secondary worktree
192+
result = runner.invoke(cli, ["status"])
193+
assert result.exit_code == 0
194+
assert "main" in result.output
195+
196+
# Config should also work (reads from main worktree)
197+
result = runner.invoke(cli, ["config", "--list"])
198+
assert result.exit_code == 0
199+
assert "prefix" in result.output
200+
201+
finally:
202+
try:
203+
os.chdir(original_dir)
204+
except (OSError, FileNotFoundError):
205+
os.chdir("/tmp")

tools/wt-worktree/wt/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ def cli(ctx: Context):
4444
# Try to find repo root
4545
try:
4646
if git.is_git_repo():
47-
ctx.repo_root = git.get_repo_root()
47+
# Use main worktree root for config (important when running from secondary worktrees)
48+
ctx.repo_root = git.get_main_worktree_root()
4849
ctx.config = Config(ctx.repo_root)
4950
ctx.manager = WorktreeManager(ctx.config)
5051

tools/wt-worktree/wt/git.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def is_git_repo(path: Optional[Path] = None) -> bool:
5656

5757
def get_repo_root(path: Optional[Path] = None) -> Path:
5858
"""
59-
Get the root directory of the git repository.
59+
Get the root directory of the git repository (current worktree).
6060
6161
Raises:
6262
GitError: If not in a git repository
@@ -65,6 +65,25 @@ def get_repo_root(path: Optional[Path] = None) -> Path:
6565
return Path(result.stdout.strip())
6666

6767

68+
def get_main_worktree_root(path: Optional[Path] = None) -> Path:
69+
"""
70+
Get the root directory of the main worktree (where .git is a directory).
71+
72+
This is important because .wt.toml is stored in the main worktree,
73+
but we might be running commands from a secondary worktree.
74+
75+
Raises:
76+
GitError: If not in a git repository
77+
"""
78+
# List all worktrees - the first one is always the main worktree
79+
worktrees = list_worktrees(path)
80+
if not worktrees:
81+
raise GitError("No worktrees found")
82+
83+
# Return the path of the first worktree (main worktree)
84+
return worktrees[0]["path"]
85+
86+
6887
def get_current_branch(path: Optional[Path] = None) -> str:
6988
"""
7089
Get the name of the current branch.

0 commit comments

Comments
 (0)