Git Worktrees Part 2: ZSH Functions That Actually Save Time

  ·  6 min read

In the first post, I covered why Git worktrees pair well with Claude Code for parallel development. The theory is solid. The practice revealed something obvious: typing git worktree add ../myproject-feature-x feature-x repeatedly gets old fast.

The solution isn’t to abandon worktrees—it’s to script away the friction so good ideas don’t die on the keyboard.

Quick Start #

If you just want to get started:

  1. Download the functions: git-functions
  2. Add to .zshrc: source ~/path/to/git-functions
  3. Reload shell: source ~/.zshrc
  4. Try: gwa test/my-first-worktree

Keep reading for the full details on each function.

The Problem With Raw Git Commands #

The basic Git worktree commands work, but they have friction:

# Current directory: ~/projects/myapp
git worktree add ../myapp-feature-auth feature/auth
cd ../myapp-feature-auth
claude

Three commands to start working. And when you’re done:

cd ~/projects/myapp
git worktree remove ../myapp-feature-auth

This is manageable once or twice. When you’re juggling multiple worktrees across multiple projects, the cognitive load adds up. You forget which worktrees exist. You mistype paths. You end up with orphaned directories.

The solution is a set of ZSH functions that handle the path management, naming conventions, and cleanup for you.

The Functions #

I maintain these in my public dotfiles. They’re designed around a simple principle: worktrees live in a parallel directory structure, organized by branch name.

Directory Structure in Practice #

Here’s what it looks like after a few worktrees:

~/projects/
  myapp/                    # Main repo
  myapp-work/
    feature/auth/           # Worktree for feature/auth
    bugfix/login-timeout/   # Worktree for bugfix/login-timeout
    experiment/new-api/     # Worktree for experiment/new-api

Everything stays organized. The -work directory is clearly separate from your main repo. Each worktree has a self-documenting name.

When you’re done with a branch, delete the worktree with gwd. When you finish a project, delete the entire -work directory. Clean, predictable, no mystery directories.

gwa - Git Worktree Add #

Creates a worktree in a predictable location:

# From anywhere in your repo
gwa feature/new-auth

# Creates: ../myapp-work/feature/new-auth
# Automatically cd's there if zoxide is installed

What it does:

  • Finds your repo root (works from any subdirectory)
  • Creates a <repo>-work/<branch> structure in the parent directory
  • Checks out the specified branch
  • Optionally jumps to the new worktree using zoxide

Why this matters: The -work suffix separates worktrees from your main repo. One glance at ls tells you what’s permanent and what’s temporary.

Edge cases:

  • If the branch exists locally, it checks out that branch
  • If it exists only on remote, it creates a local tracking branch
  • If it doesn’t exist anywhere, you’ll get an error (create the branch first)

gwd - Git Worktree Delete #

Removes a worktree with optional force flag:

# Normal deletion (fails if uncommitted changes exist)
gwd feature/new-auth

# Force deletion (even with uncommitted changes)
gwd -f feature/old-experiment

# Tip: Check status before deleting
cd $(git worktree list | grep feature/new-auth | awk '{print $1}')
git status

What it does:

  • Finds the worktree path automatically
  • Validates it exists before attempting removal
  • Supports --force flag for stubborn worktrees
  • Cleans up Git metadata

Why this matters: You don’t have to remember the full path. You don’t have to cd to your main repo first. Just name the branch and it’s gone.

gwl - Git Worktree List #

Shows all worktrees for the current repo:

gwl

# Output:
# Worktrees for myapp:
# /Users/you/projects/myapp         abc1234 [main]
# /Users/you/projects/myapp-work/feature/auth  def5678 [feature/auth]

What it does:

  • Wraps git worktree list with context
  • Works from any directory in the repo

Why this matters: Quick sanity check for what’s active. Useful before creating a new worktree or when you’ve forgotten what you have running.

gwo - Git Worktree Open #

Navigates to a worktree (and optionally opens it in VS Code):

# With VSCODE_INTEGRATION=1
gwo feature/auth
# Result: cd's AND opens VS Code workspace

# Without it
gwo feature/auth
# Result: just cd's to the worktree

What it does:

  • Finds the worktree directory
  • Uses zoxide if available, falls back to cd
  • Optionally opens in VS Code (set VSCODE_INTEGRATION=1)
  • Shows available worktrees if the specified one doesn’t exist

Why this matters: Context switching becomes one command. You don’t navigate through directories manually. You don’t grep through your terminal history for the path.

Tab Completion #

The functions include ZSH completion:

  • gwa: Suggests branch names from git branch --all
  • gwd: Suggests existing worktree directories
  • gwo: Same as gwd

This means you can type gwd <TAB> and see only the worktrees that actually exist. No more guessing branch names or mistyping paths.

Integration With Claude Code #

This setup shines when combined with Claude Code sessions. Here’s a typical workflow:

Starting a new feature:

gwa feature/new-dashboard
# Note: First time in a worktree? Run your build/install commands
# npm install, go mod download, etc.
claude

Reviewing a PR while working on something else:

# Terminal 1: Working on feature-a
gwa feature/feature-a
claude
# (doing work...)

# Terminal 2: Review PR arrives
# Old way: stash changes, checkout PR branch, lose context
# New way: fresh worktree
gwa pr/456-fix-bug
claude
# Ask Claude Code to analyze the PR, run tests
# When done: gwd pr/456-fix-bug

# Terminal 1: Still has your original context intact

Comparing two implementation approaches:

# Approach 1
gwa feature/api-v1
claude  # Implement approach 1

# Approach 2
gwa feature/api-v2
claude  # Implement approach 2

# Compare results, keep the better one, delete the other

Each Claude session stays isolated. Each terminal window has a clear context. Switching between tasks is just gwo <branch>.

Installation #

Dependencies (optional but recommended):

# zoxide: for smart directory jumping
brew install zoxide

# Add to .zshrc after installing:
eval "$(zoxide init zsh)"

Setup:

Add to your .zshrc:

# Source the functions
source ~/path/to/git-functions

# Optional: Enable VS Code integration
export VSCODE_INTEGRATION=1

# Tab completion (if not already enabled)
autoload -Uz compinit && compinit

Get the functions:

# Direct link:
# https://github.com/rikdc/dotfiles-public/blob/main/config/zsh/git-functions

# Or clone the entire dotfiles repo:
git clone https://github.com/rikdc/dotfiles-public.git

Verify it works:

# Reload shell
source ~/.zshrc

# Test tab completion
gwa <TAB>  # Should show branch names

Common Mistakes #

Creating worktrees for the same branch twice: Git will error. Use gwl first to see what worktrees exist.

Forgetting to pull before creating a worktree: Your worktree gets the current local state. If main is behind, your worktree is too.

Deleting the main repo thinking it’s a worktree: The functions check for this, but always run gwl if you’re unsure which directory is which.

Running build commands in the wrong worktree: Build artifacts may be gitignored but they’re not shared across worktrees. Each worktree needs its own build.

What This Doesn’t Solve #

These functions don’t:

  • Automatically sync worktrees with upstream (you still need to git pull)
  • Manage merge conflicts across worktrees (that’s on you)
  • Prevent you from creating too many worktrees (discipline required)
  • Work with repos that have unusual directory structures (assumes standard Git setup)

They’re ergonomic wrappers, not magic. If your Git workflow is chaotic, these won’t fix it. They just make the good parts faster.

The Real Benefit #

These functions remove enough friction that worktrees become the default choice instead of a last resort. Before them, I’d think “I should check out that PR in a separate worktree” and not do it because typing the full path was annoying. Now I just type gwa pr/789 and it’s done.

The best workflows are the ones you actually use. These functions make worktrees trivial enough that I reach for them by default instead of as a last resort.

Simple automation, compounding returns.