Roaming through git worktrees

git worktree is great. It has become an integral part of my workflow in any non-trivial project.

If you’re not familiar with git worktrees start here. If you need some motivation see here.

Manually cding across directories gets old really quickly. Generally, I rely on zoxide to efficiently traverse my filesystem. However, zoxide struggles when it comes to worktrees, as I explain here.

Instead, here are the techniques I use to improve my worktree flow, especially in monorepos.

Move between different worktrees

worktree-welder is a tiny cli utility I created to quickly switch between worktrees.

My favorite part about it is that it preserves our position relative to the worktree root. This means that if we’re in worktree-1/packages/backend and want to switch to worktree-2, it’ll take us to worktree-2/packages/backend. I find this to be helpful, especially in large monorepos.

Move within the current worktree

Inspired by fzf’s ALT-C keybinding, I created a zsh widget to seamlessly move within the current worktree. When invoked:

  1. It presents an fzf picker for all the directories in the current worktree.
  2. Once a selection is made, we cd to our selection.

One thing I find very useful is that the picker is scoped to the root of current git repository, not $PWD, which enables this kind of thing:

  1. Assume we’re in a nested directory such as myproject/packages/backend.
  2. We invoke our widget.
  3. We can fuzzy search across all the paths in the current repo, not just backend/**/*.

Also, it always presents the root of the current worktree as the first option. This means that going to the root of our current worktree is always 2 keystrokes away, regardless of how deep we are in the worktree (invoke the widget with ALT-i then hit ENTER to select the first option).

#!/usr/bin/env zsh

# fzf picker to cd to any directory of the current git repo (regardless of cwd)
# Inspired by fzf-cd-widget: https://github.com/junegunn/fzf/blob/7320b7df62039c879c4f609bca946ea09b438a98/shell/key-bindings.zsh#L74-L95

fzf-cd-repo-widget() {
	local original_path=$(pwd)

	# Find the root of the monorepo
	local monorepo_path=$(git rev-parse --show-toplevel 2> /dev/null)
	if [[ -z $monorepo_path ]]; then
		zle redisplay
		return 0
	fi

	# Change to the monorepo root
	cd "$monorepo_path"

	# Command to search for directories within the current git repo, including the root
	local cmd="{ echo '.'; fd --type=directory .; }"

	setopt localoptions pipefail no_aliases 2> /dev/null

	# Use fzf to select a directory
	local dir="$(eval "$cmd" | fzf --height=40%)"

	# Change back to the original directory
	cd "$original_path"

	if [[ -z "$dir" ]]; then
		zle redisplay
		return 0
	fi

	# Prepare the command to change directory
	zle push-line
	BUFFER="builtin cd -- '${monorepo_path}/${dir}'"
	zle accept-line
	local ret=$?
	unset dir
	zle reset-prompt
	return $ret
}

zle -N fzf-cd-repo-widget
# bind the widget to ALT+i
bindkey "^[i" fzf-cd-repo-widget

To use the preceding snippet, save it to a new file fzf-cd-repo-widget.zsh, then source it from your .zshrc:

# ~/.zshrc
source path/to/fzf-cd-repo-widget.zsh