Coverage for src/workstack/cli/commands/jump.py: 83%
70 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-19 09:31 -0400
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-19 09:31 -0400
1"""Jump command - find and switch to a worktree by branch name."""
3import shlex
4from pathlib import Path
6import click
8from workstack.cli.activation import render_activation_script
9from workstack.cli.core import discover_repo_context
10from workstack.cli.graphite import find_worktrees_containing_branch, get_branch_stack
11from workstack.cli.shell_utils import write_script_to_temp
12from workstack.core.context import WorkstackContext
13from workstack.core.gitops import WorktreeInfo
16def _format_worktree_info(wt: WorktreeInfo, repo_root: Path) -> str:
17 """Format worktree information for display.
19 Args:
20 wt: WorktreeInfo to format
21 repo_root: Path to repository root (used to identify root worktree)
23 Returns:
24 Formatted string like "root (currently on 'main')" or "wt-name (currently on 'feature')"
25 """
26 current = wt.branch or "(detached HEAD)"
27 if wt.path == repo_root:
28 return f" - root (currently on '{current}')"
29 else:
30 # Get worktree name from path
31 wt_name = wt.path.name
32 return f" - {wt_name} (currently on '{current}')"
35def _perform_jump(
36 ctx: WorkstackContext,
37 repo_root: Path,
38 target_worktree: WorktreeInfo,
39 branch: str,
40 script: bool,
41) -> None:
42 """Perform the actual jump to a worktree.
44 Args:
45 ctx: Workstack context
46 repo_root: Repository root path
47 target_worktree: The worktree to jump to
48 branch: Target branch name
49 script: Whether to output only the activation script
50 """
51 target_path = target_worktree.path
52 current_branch_in_worktree = target_worktree.branch
54 # Defensive check: verify path exists before any operations
55 if not target_path.exists():
56 click.echo(
57 f"Error: Worktree path does not exist: {target_path}",
58 err=True,
59 )
60 raise SystemExit(1)
62 # Check if we're already on the target branch in the target worktree
63 current_cwd = Path.cwd()
64 if current_cwd == target_path and current_branch_in_worktree == branch:
65 if not script:
66 click.echo(f"Already on branch '{branch}' in this worktree")
67 return
69 # Check if branch is already checked out in the worktree
70 need_checkout = current_branch_in_worktree != branch
72 # If we need to checkout, do it before generating the activation script
73 if need_checkout:
74 # Checkout the branch in the target worktree
75 ctx.git_ops.checkout_branch(target_path, branch)
77 # Show stack context
78 if not script:
79 stack = get_branch_stack(ctx, repo_root, branch)
80 if stack:
81 click.echo(f"Stack: {' -> '.join(stack)}")
82 click.echo(f"Checked out '{branch}' in worktree")
84 # Generate activation script
85 if script:
86 # Script mode: always generate script (for shell integration or manual sourcing)
87 # Use shlex.quote() for branch name security (defense-in-depth)
88 safe_branch = shlex.quote(branch)
89 if need_checkout:
90 jump_message = f'echo "Jumped to branch {safe_branch}: $(pwd)"'
91 else:
92 jump_message = f'echo "Already on branch {safe_branch}: $(pwd)"'
93 script_content = render_activation_script(
94 worktree_path=target_path, final_message=jump_message
95 )
97 script_path = write_script_to_temp(
98 script_content,
99 command_name="jump",
100 comment=f"jump to {branch}",
101 )
102 click.echo(str(script_path), nl=False)
103 else:
104 # No shell integration available, show manual instructions
105 click.echo(
106 "Shell integration not detected. "
107 "Run 'workstack init --shell' to set up automatic activation."
108 )
109 click.echo(f"\nOr use: source <(workstack jump {branch} --script)")
112@click.command("jump")
113@click.argument("branch", metavar="BRANCH")
114@click.option(
115 "--script", is_flag=True, help="Print only the activation script without usage instructions."
116)
117@click.pass_obj
118def jump_cmd(ctx: WorkstackContext, branch: str, script: bool) -> None:
119 """Jump to BRANCH by finding and switching to its worktree.
121 This command finds which worktree contains the specified branch
122 in its Graphite stack and switches to it. The branch does not
123 need to be currently checked out - it just needs to exist in
124 the worktree's stack lineage.
126 Requires Graphite to be enabled.
128 Examples:
130 workstack jump feature/user-auth
132 workstack jump hotfix/critical-bug
134 If multiple worktrees contain the branch, all options are shown.
135 """
136 # Check if Graphite is enabled
137 if not ctx.global_config_ops.get_use_graphite():
138 click.echo(
139 "Error: Jump command requires Graphite. Enable Graphite with:\n"
140 " workstack config set use_graphite true",
141 err=True,
142 )
143 raise SystemExit(1)
145 repo = discover_repo_context(ctx, Path.cwd())
147 # Get all worktrees
148 worktrees = ctx.git_ops.list_worktrees(repo.root)
150 # Find worktrees containing the target branch
151 matching_worktrees = find_worktrees_containing_branch(ctx, repo.root, worktrees, branch)
153 # Handle three cases: no match, one match, multiple matches
154 if len(matching_worktrees) == 0:
155 # No worktrees contain this branch
156 click.echo(
157 f"Error: Branch '{branch}' not found in any worktree stack.\n"
158 f"To create a worktree with this branch, run:\n"
159 f" workstack create --from-branch {branch}",
160 err=True,
161 )
162 raise SystemExit(1)
164 if len(matching_worktrees) == 1:
165 # Exactly one worktree contains this branch
166 target_worktree = matching_worktrees[0]
167 _perform_jump(ctx, repo.root, target_worktree, branch, script)
169 else:
170 # Multiple worktrees contain this branch
171 # Check if any worktree has the branch directly checked out
172 directly_checked_out = [wt for wt in matching_worktrees if wt.branch == branch]
174 if len(directly_checked_out) == 1:
175 # Exactly one worktree has the branch directly checked out - jump to it
176 target_worktree = directly_checked_out[0]
177 _perform_jump(ctx, repo.root, target_worktree, branch, script)
178 else:
179 # Zero or multiple worktrees have it directly checked out
180 # Show error message listing all options
181 click.echo(f"Branch '{branch}' exists in multiple worktrees:", err=True)
182 for wt in matching_worktrees:
183 click.echo(_format_worktree_info(wt, repo.root), err=True)
185 click.echo("\nUse 'workstack switch' to choose a specific worktree first.", err=True)
186 raise SystemExit(1)