Created
August 9, 2025 14:06
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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