Skip to content

Instantly share code, notes, and snippets.

@rkmax
Created August 9, 2025 14:06
Show Gist options
  • Save rkmax/4b5e21eb769bee9eaa27765eb28c0619 to your computer and use it in GitHub Desktop.
Save rkmax/4b5e21eb769bee9eaa27765eb28c0619 to your computer and use it in GitHub Desktop.
Git worktree wrapper for Zsh - create, remove, navigate, and open worktrees in editors with consistent branch naming
gtr() {
# Function to show usage
_gtr_usage() {
cat <<EOF
Usage: gtr {create|rm|remove|cd|claude|code|list|ls} <name> [<name2> ...]
Commands:
create <name> [...] Create one or more worktrees with branch ${GTR_BRANCH_PREFIX:-claude}/<name>
remove, rm [...] [-f] Remove one or more worktrees (use -f to force removal)
cd <name> Change to worktree directory
claude <name> Run claude in worktree (creates if needed)
code <name> Open worktree in code editor (creates if needed)
list, ls List all worktrees
Configuration (via environment variables):
GTR_BASE_DIR Base directory for worktrees (current: ${GTR_BASE_DIR:-$HOME/Development/.worktrees})
GTR_BRANCH_PREFIX Branch prefix (current: ${GTR_BRANCH_PREFIX:-claude})
Examples:
gtr create feature0 # Create worktree with branch prefix/feature0
gtr create feat1 feat2 # Create multiple worktrees
gtr rm feature0 # Remove worktree
gtr cd feature0 # Jump to worktree directory
gtr claude feature0 # Run claude in worktree
gtr code feature0 # Open worktree in code editor
gtr list # List all worktrees
EOF
}
# Check if no arguments provided
if [[ $# -eq 0 ]]; then
_gtr_usage
return 1
fi
local cmd="$1"
shift
# Configurable settings
local base_dir="${GTR_BASE_DIR:-$HOME/Development/.worktrees}"
local branch_prefix="${GTR_BRANCH_PREFIX:-claude}"
# Ensure base directory exists
[[ ! -d "$base_dir" ]] && mkdir -p "$base_dir"
case "$cmd" in
create)
# Check if name is provided
if [[ $# -eq 0 ]]; then
print -P "%F{red}Error: No worktree name provided%f" >&2
_gtr_usage
return 1
fi
# Confirm creation if worktrees already exist
local -a existing=()
for name in "$@"; do
[[ -d "$base_dir/$name" ]] && existing+=($name)
done
if [[ ${#existing[@]} -gt 0 ]]; then
print -P "%F{yellow}The following worktrees already exist:%f %F{cyan}${existing[*]}%f"
read -q "?Continue with remaining? [y/N] " || { echo; return 1; }
echo
fi
# Confirm creation for multiple worktrees
if [[ $# -gt 1 ]]; then
print -P "%F{yellow}Create $# worktrees?%f"
read -q "?Proceed? [y/N] " || { echo; return 1; }
echo
fi
for name in "$@"; do
if [[ -d "$base_dir/$name" ]]; then
print -P "%F{yellow}Skipping existing worktree: $name%f"
else
print -P "%F{green}Creating worktree: $name%f"
git worktree add "$base_dir/$name" -b "$branch_prefix/$name"
fi
done
;;
rm|remove)
# Check if name is provided
if [[ $# -eq 0 ]]; then
print -P "%F{red}Error: No worktree name provided%f" >&2
_gtr_usage
return 1
fi
# Check for force flag
local force_flag=""
local names=()
for arg in "$@"; do
if [[ "$arg" == "-f" || "$arg" == "--force" ]]; then
force_flag="--force"
else
names+=("$arg")
fi
done
# Check if any names were provided after filtering out flags
if [[ ${#names[@]} -eq 0 ]]; then
print -P "%F{red}Error: No worktree name provided%f" >&2
_gtr_usage
return 1
fi
# Confirm removal
if [[ -n "$force_flag" ]]; then
print -P "%F{yellow}Force remove ${#names[@]} worktree(s)?%f"
else
print -P "%F{yellow}Remove ${#names[@]} worktree(s)?%f"
fi
read -q "?Proceed? [y/N] " || { echo; return 1; }
echo
for name in "${names[@]}"; do
if [[ -d "$base_dir/$name" ]]; then
print -P "%F{red}Removing worktree: $name%f"
git worktree remove $force_flag "$base_dir/$name"
else
print -P "%F{yellow}Worktree not found: $name%f"
fi
done
;;
cd)
if [[ -z "$1" ]]; then
print -P "%F{red}Error: No worktree name provided%f" >&2
_gtr_usage
return 1
fi
if [[ -d "$base_dir/$1" ]]; then
cd "$base_dir/$1"
print -P "%F{green}Changed to worktree: $1%f"
else
print -P "%F{red}Error: No such worktree: $base_dir/$1%f" >&2
return 1
fi
;;
claude)
if [[ -z "$1" ]]; then
print -P "%F{red}Error: No worktree name provided%f" >&2
_gtr_usage
return 1
fi
local dir="$base_dir/$1"
if [[ ! -d "$dir" ]]; then
print -P "%F{yellow}Worktree '$1' doesn't exist.%f"
read -q "?Create it now? [y/N] " || { echo; return 1; }
echo
print -P "%F{green}Creating worktree '$1'…%f"
git worktree add "$dir" -b "$branch_prefix/$1" || {
print -P "%F{red}git worktree add failed%f" >&2
return 1
}
fi
( cd "$dir" && claude )
;;
code)
if [[ -z "$1" ]]; then
print -P "%F{red}Error: No worktree name provided%f" >&2
_gtr_usage
return 1
fi
local dir="$base_dir/$1"
if [[ ! -d "$dir" ]]; then
print -P "%F{yellow}Worktree '$1' doesn't exist.%f"
read -q "?Create it now? [y/N] " || { echo; return 1; }
echo
print -P "%F{green}Creating worktree '$1'…%f"
git worktree add "$dir" -b "$branch_prefix/$1" || {
print -P "%F{red}git worktree add failed%f" >&2
return 1
}
fi
code "$dir"
;;
list|ls)
if [[ -d "$base_dir" ]]; then
print -P "%F{cyan}Worktrees in $base_dir:%f"
local worktree
for worktree in "$base_dir"/*(/N); do
if [[ -d "$worktree" ]]; then
local name="${worktree:t}"
local branch=$(cd "$worktree" 2>/dev/null && git branch --show-current 2>/dev/null)
if [[ -n "$branch" ]]; then
print -P " %F{green}$name%f → %F{yellow}$branch%f"
else
print -P " %F{green}$name%f"
fi
fi
done
else
print -P "%F{yellow}No worktrees directory found at $base_dir%f"
fi
;;
*)
print -P "%F{red}Error: Unknown command: $cmd%f" >&2
_gtr_usage
return 1
;;
esac
}
# Enable tab completion for gtr
if [[ -n "$ZSH_VERSION" ]]; then
_gtr() {
local -a commands
commands=(
'create:Create new worktree(s)'
'rm:Remove worktree(s)'
'cd:Change to worktree directory'
'claude:Run claude in worktree'
'code:Open worktree in code editor'
'list:List all worktrees'
)
local base_dir="${GTR_BASE_DIR:-$HOME/Development/.worktrees}"
case "$words[2]" in
rm|cd|claude|code)
if [[ -d "$base_dir" ]]; then
local -a worktrees
worktrees=("$base_dir"/*(/:t))
_describe 'worktree' worktrees
fi
;;
*)
_describe 'command' commands
;;
esac
}
compdef _gtr gtr
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment