diff --git a/README.md b/README.md index 7f61937..629626a 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Then reload your shell (`source ~/.zshrc`) or open a new terminal. ```sh wt # list all worktrees wt # cd into worktree by branch name +wt ~ # cd to the repo root (also: wt root) wt mk # create worktree in .claude/worktrees/ and cd into it wt mk # create worktree at a specific path and cd into it wt rm # remove a worktree diff --git a/test/cd.bats b/test/cd.bats index 6a9c1b9..2f23498 100644 --- a/test/cd.bats +++ b/test/cd.bats @@ -20,3 +20,36 @@ teardown() { wt_common_teardown; } [ "$status" -eq 1 ] [[ "$output" == *"no worktree matching"* ]] } + +@test "wt ~ goes to the repo root" { + cd "$TEST_REPO-feature" + _wt_cd "~" + [ "$PWD" = "$TEST_REPO" ] +} + +@test "wt ~ works after the shell has expanded it to \$HOME" { + cd "$TEST_REPO-feature" + _wt_cd "$HOME" + [ "$PWD" = "$TEST_REPO" ] +} + +@test "wt root goes to the repo root" { + cd "$TEST_REPO-other" + wt root + [ "$PWD" = "$TEST_REPO" ] +} + +@test "a worktree named root still wins over the repo root" { + git -C "$TEST_REPO" worktree add -q "$TEST_REPO-root" -b root + cd "$TEST_REPO-feature" + _wt_cd root + [ "$PWD" = "$TEST_REPO-root" ] + cd "$TEST_REPO" + git worktree remove "$TEST_REPO-root" +} + +@test "wt ~ outside a repo fails rather than going home" { + cd "$(mktemp -d)" + run _wt_cd "~" + [ "$status" -ne 0 ] +} diff --git a/wt.sh b/wt.sh index 504b050..e634871 100644 --- a/wt.sh +++ b/wt.sh @@ -145,11 +145,21 @@ _wt_claude_rm_sessions() { return "$rc" } -# navigate to a worktree by branch name or directory basename +# navigate to a worktree by branch name or directory basename; ~ (or root) goes +# to the repo root. The shell expands a bare ~ before wt sees it, so $HOME counts +# as ~ too. A real worktree still wins, so a branch named "root" keeps working. _wt_cd() { - local target - target=$(_wt_resolve "${1?usage: wt }") - [ -z "$target" ] && { echo "wt: no worktree matching '$1'" >&2; return 1; } + local name="${1?usage: wt }" target root + if [ "$name" != "~" ] && { [ -z "$HOME" ] || [ "$name" != "$HOME" ]; }; then + target=$(_wt_resolve "$name") + fi + if [ -z "$target" ]; then + case "$name" in + "~"|root|"${HOME:-~}") root=$(_wt_root) || return 1; cd "$root"; return ;; + esac + echo "wt: no worktree matching '$name'" >&2 + return 1 + fi cd "$target" } @@ -420,6 +430,7 @@ Usage: wt [command] [args] Commands: wt list all worktrees wt cd into worktree by branch name + wt ~ cd to the repo root (also: wt root) wt cd cd into worktree (explicit form) wt ls [opts] list worktrees (same as bare wt) wt mk [path] [opts] create worktree (default: .claude/worktrees/) @@ -503,7 +514,7 @@ if [ -n "$ZSH_VERSION" ]; then local -a matches # l:|=* matches the typed text anywhere in a candidate if [ $CURRENT -eq 2 ]; then - matches=(ls cd mk rm prune merged help $(_wt_branches)) + matches=(ls cd mk rm prune merged root help $(_wt_branches)) compadd -M 'l:|=*' -a matches elif [ $CURRENT -gt 2 ]; then case "${words[2]}" in @@ -535,7 +546,7 @@ elif [ -n "$BASH_VERSION" ]; then _wt_complete() { local cur="${COMP_WORDS[COMP_CWORD]}" if [ $COMP_CWORD -eq 1 ]; then - _wt_compreply "$cur" ls cd mk rm prune merged help $(_wt_branches) + _wt_compreply "$cur" ls cd mk rm prune merged root help $(_wt_branches) else case "${COMP_WORDS[1]}" in rm|remove|cd|merged)