Skip to content

Commit 9250995

Browse files
rohoswaggerclaude
andcommitted
feat: ez checkout auto-cd into worktree
When checking out a branch that lives in a worktree, ez now prints the worktree path to stdout instead of attempting `git checkout` (which would fail). The shell wrapper (from `ez shell-init`) intercepts this and cd's into the worktree automatically. - `ez checkout feat/auth` → cd's to .worktrees/feat-auth if it's there - `ez checkout 42` → same, by PR number - Interactive selector also works with worktrees - Non-worktree branches still use regular git checkout - Agents: `cd $(ez checkout feat/auth)` works without the shell wrapper Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 177c535 commit 9250995

5 files changed

Lines changed: 63 additions & 21 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ These features exist specifically to make ez useable by AI agents:
133133
| 0.1.16 | (skipped) |
134134
| 0.1.17 | `ez shell-init` — shell integration for auto-cd on worktree create/delete (zoxide pattern); remove redundant sync summary |
135135
| 0.1.18 | `ez setup` — one-command shell configuration (PATH + shell-init); first-run hint prompts users to run it; `ez setup --yes` for agents |
136+
| 0.1.19 | `ez checkout` auto-cd's into worktree if branch is checked out there; shell wrapper intercepts checkout for cd |
136137

137138
---
138139

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ez-stack"
3-
version = "0.1.18"
3+
version = "0.1.19"
44
edition = "2024"
55
rust-version = "1.85"
66
description = "A CLI tool for managing stacked PRs with GitHub"

src/cmd/checkout.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,58 @@
11
use anyhow::Result;
22
use dialoguer::Select;
3+
use std::collections::HashMap;
34

45
use crate::git;
56
use crate::github;
67
use crate::stack::StackState;
78
use crate::ui;
89

10+
/// Build a map of branch name → worktree path for branches in worktrees.
11+
fn worktree_map() -> HashMap<String, String> {
12+
git::worktree_list()
13+
.unwrap_or_default()
14+
.into_iter()
15+
.filter(|wt| wt.path.contains("/.worktrees/"))
16+
.filter_map(|wt| wt.branch.map(|b| (b, wt.path)))
17+
.collect()
18+
}
19+
20+
/// Switch to a branch. If it's in a worktree, print the path to stdout for cd.
21+
fn switch_to(target: &str, wt_map: &HashMap<String, String>) -> Result<()> {
22+
if let Some(wt_path) = wt_map.get(target) {
23+
// Branch is in a worktree — print path to stdout for shell wrapper to cd.
24+
ui::success(&format!("Switching to `{target}` in worktree `{wt_path}`"));
25+
println!("{wt_path}");
26+
} else {
27+
git::checkout(target)?;
28+
ui::success(&format!("Switched to `{target}`"));
29+
}
30+
Ok(())
31+
}
32+
933
pub fn run(name: Option<&str>) -> Result<()> {
1034
let state = StackState::load()?;
1135
let current = git::current_branch()?;
36+
let wt_map = worktree_map();
1237

1338
// Direct checkout by name or PR number.
1439
if let Some(arg) = name {
1540
let target = if let Ok(pr_num) = arg.parse::<u64>() {
16-
// Look up branch by PR number.
1741
state
1842
.branches
1943
.values()
2044
.find(|m| m.pr_number == Some(pr_num))
2145
.map(|m| m.name.clone())
22-
.ok_or_else(|| anyhow::anyhow!("No branch found with PR #{pr_num}"))?
46+
.ok_or_else(|| {
47+
anyhow::anyhow!(
48+
"No branch found with PR #{pr_num}\n → Run `ez branch` to see all branches"
49+
)
50+
})?
2351
} else {
24-
// Direct branch name — must be managed or trunk.
2552
if !state.is_trunk(arg) && !state.is_managed(arg) {
26-
anyhow::bail!("Branch `{arg}` is not tracked by ez");
53+
anyhow::bail!(
54+
"Branch `{arg}` is not tracked by ez\n → Run `ez branch` to see all branches"
55+
);
2756
}
2857
arg.to_string()
2958
};
@@ -33,8 +62,7 @@ pub fn run(name: Option<&str>) -> Result<()> {
3362
return Ok(());
3463
}
3564

36-
git::checkout(&target)?;
37-
ui::success(&format!("Switched to `{target}`"));
65+
switch_to(&target, &wt_map)?;
3866
return Ok(());
3967
}
4068

@@ -87,8 +115,7 @@ pub fn run(name: Option<&str>) -> Result<()> {
87115
return Ok(());
88116
}
89117

90-
git::checkout(selected)?;
91-
ui::success(&format!("Switched to `{selected}`"));
118+
switch_to(selected, &wt_map)?;
92119

93120
Ok(())
94121
}

src/cmd/shell_init.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,36 @@ use anyhow::Result;
22

33
pub fn run() -> Result<()> {
44
// Print a shell function that wraps the ez binary.
5-
// The function intercepts `ez worktree create` and `ez worktree delete` to auto-cd.
6-
// All other commands pass through to the binary unchanged.
5+
// Intercepts commands that output a path to stdout for auto-cd:
6+
// ez worktree create/delete — prints worktree path or repo root
7+
// ez checkout — prints worktree path if branch is in a worktree
78
print!(
89
r#"# ez shell integration — generated by `ez shell-init`
910
# Add to your .bashrc/.zshrc: eval "$(ez shell-init)"
1011
1112
ez() {{
12-
if [ "$1" = "worktree" ] && {{ [ "$2" = "create" ] || [ "$2" = "delete" ]; }}; then
13-
local _ez_path
14-
_ez_path=$(command ez "$@")
15-
local _ez_exit=$?
16-
if [ $_ez_exit -eq 0 ] && [ -n "$_ez_path" ] && [ -d "$_ez_path" ]; then
17-
cd "$_ez_path" || true
18-
fi
19-
return $_ez_exit
20-
fi
13+
case "$1" in
14+
worktree)
15+
if [ "$2" = "create" ] || [ "$2" = "delete" ]; then
16+
local _ez_path
17+
_ez_path=$(command ez "$@")
18+
local _ez_exit=$?
19+
if [ $_ez_exit -eq 0 ] && [ -n "$_ez_path" ] && [ -d "$_ez_path" ]; then
20+
cd "$_ez_path" || true
21+
fi
22+
return $_ez_exit
23+
fi
24+
;;
25+
checkout|co)
26+
local _ez_path
27+
_ez_path=$(command ez "$@")
28+
local _ez_exit=$?
29+
if [ $_ez_exit -eq 0 ] && [ -n "$_ez_path" ] && [ -d "$_ez_path" ]; then
30+
cd "$_ez_path" || true
31+
fi
32+
return $_ez_exit
33+
;;
34+
esac
2135
command ez "$@"
2236
}}
2337
"#

0 commit comments

Comments
 (0)