I use git worktrees constantly for parallel development, but typing git worktree list, copying the path, and cd-ing got tedious. So I wrote a shell function.

The function Link to heading

Add this to your .zshrc:

# Git worktree selector with fzf
wt() {
  local create=false
  local delete=false
  local copy_envrc=false
  local name=""
  local target=""

  _wt_usage() {
    cat <<USAGE
Usage: wt [-c <name>] [-e] [-d [path]] [-h]

Options:
  -c <name>   Create a new worktree with branch name <name>
  -e          Copy .envrc from root and run direnv allow (use with -c)
  -d [path]   Delete a worktree (fuzzy select if no path given, use '.' for current)
  -h          Show this help message
USAGE
  }

  while [[ $# -gt 0 ]]; do
    case "$1" in
      -c) create=true; name="$2"; shift 2 ;;
      -e) copy_envrc=true; shift ;;
      -d) delete=true; shift; [[ $# -gt 0 && ! "$1" =~ ^- ]] && { target="$1"; shift; } ;;
      -h) _wt_usage; return 0 ;;
      *) _wt_usage; return 1 ;;
    esac
  done

  if ($create || $copy_envrc) && $delete; then
    echo "Error: -c/-e and -d are mutually exclusive"; return 1
  fi

  if $create; then
    [[ -z "$name" ]] && { echo "Error: -c requires a name"; return 1; }
    local root=$(git rev-parse --show-toplevel)
    local new_path="$root/$name"
    git worktree add "$new_path" -b "$name" && cd "$new_path"
    if $copy_envrc && [[ -f "$root/.envrc" ]]; then
      cp "$root/.envrc" "$new_path/.envrc"
      direnv allow
    fi
  elif $delete; then
    local to_delete
    if [[ -n "$target" ]]; then
      to_delete=$(realpath "$target")
    else
      to_delete=$(git worktree list | fzf --height 40% --reverse | awk '{print $1}')
    fi
    [[ -z "$to_delete" ]] && return 0
    local main_wt=$(git worktree list | head -1 | awk '{print $1}')
    if [[ "$to_delete" == "$main_wt" ]]; then
      echo "Error: cannot delete main worktree"; return 1
    fi
    [[ "$(realpath .)" == "$to_delete"* ]] && cd "$main_wt"
    git worktree remove "$to_delete"
  else
    local selected=$(git worktree list | fzf --height 40% --reverse | awk '{print $1}')
    [[ -n "$selected" ]] && cd "$selected"
  fi
}

Usage Link to heading

# Fuzzy select and cd to a worktree
wt

# Create a new worktree with branch name
wt -c feature-x

# Create worktree and setup direnv
wt -c feature-x -e

# Delete a worktree (fuzzy select)
wt -d

# Delete current worktree
wt -d .

Why -e matters Link to heading

I use direnv for environment variables in each repo. Without -e, I’d have to manually copy .envrc and run direnv allow every time I create a worktree. Now it’s automatic.

See also my post on git worktrees for more background on parallel development.

Further reading Link to heading