Skip to content

Instantly share code, notes, and snippets.

@podratz
Last active March 13, 2026 16:41
Show Gist options
  • Select an option

  • Save podratz/d25270ada86e090ae9051b0af25cbd4a to your computer and use it in GitHub Desktop.

Select an option

Save podratz/d25270ada86e090ae9051b0af25cbd4a to your computer and use it in GitHub Desktop.
Shell functions for managing git bare repos and worktrees with fzf. Clone repos as bare, create/switch/remove worktrees by branch name, with fuzzy selection and interactive prompts.
# git-worktree.zsh — bare-repo + worktree workflow for zsh
#
# Layout:
# ~/Code/repo/<name>.git — bare repos
# ~/Code/work/<name>-<branch> — worktrees
#
# Requirements: git, fzf
#
# Commands:
# rp [URL] — jump to bare repo, clone if URL given, fuzzy-select if not in a worktree
# repos — fuzzy-select a bare repo
# mkwt [branch] — create worktree (fuzzy from branchless if no args, auto-detects existing branches)
# wt [branch] — jump to worktree (fuzzy if no args, create if missing)
# wts — fuzzy-select across all worktrees
# lswt — list worktrees for current repo
# lswts — list all worktrees
# rmwt [branch] — remove worktree (fuzzy if no args, current if in one)
#
# Install: source this file from .zshrc
# source /path/to/git-worktree.zsh
#
# Note: avoid `local path` in zsh functions — it shadows the special
# path variable tied to $PATH.
# --- internals ---
# Convert branch name to a folder-safe name: feat/name -> feat-name
_branch_to_path() {
printf '%s' "${1//\//-}"
}
# Infer repo base name from the current git dir:
# /path/to/project.git -> project
# /path/to/project/.git -> project
_repo_base() {
local gitdir repo
gitdir="$(cd -- "$(git rev-parse --git-common-dir 2>/dev/null)" 2>/dev/null && pwd)" || {
echo "Not inside a git repository." >&2
return 1
}
repo="$(basename "$gitdir")"
repo="${repo%.git}"
printf '%s\n' "$repo"
}
# --- repos ---
# Jump to bare repo from worktree, clone if URL given, fuzzy-select otherwise
rp() {
if [ $# -gt 0 ]; then
local url="$1" name
name="$(basename "$url" .git).git"
local target="$HOME/Code/repo/$name"
if [ -d "$target" ]; then
echo "Already exists: $target" >&2
cd "$target"
return
fi
git clone --bare "$url" "$target" && cd "$target"
return
fi
if [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]; then
local gitdir
gitdir="$(cd -- "$(git rev-parse --git-common-dir 2>/dev/null)" 2>/dev/null && pwd)"
cd "$gitdir"
else
printf "Already in bare repo. Switch to another? [y/N] "
read -r reply
if [[ "$reply" =~ ^[Yy]$ ]]; then
repos
fi
fi
}
# Fuzzy-jump to a bare repo under ~/Code/repo or ~/Code/*/repo
repos() {
local dir
dir="$(
{
find "$HOME/Code/repo" -mindepth 1 -maxdepth 1 -type d 2>/dev/null
find "$HOME/Code" -mindepth 2 -maxdepth 2 -path "*/repo/*" -type d 2>/dev/null
} | sort -u | fzf
)" || return 1
cd "$dir"
}
# --- worktrees ---
# Create a worktree (new or existing branch)
# Usage: mkwt (fuzzy select from branches without worktrees)
# mkwt feat name (creates branch feat/name, or checks out if it exists)
# Result: ~/Code/work/project-feat-name on branch feat/name
mkwt() {
local branch repo dashed target
repo="$(_repo_base)" || return 1
if [ $# -eq 0 ]; then
local existing
existing="$(git worktree list --porcelain | awk '/^branch refs\/heads\//{print substr($0,19)}')"
branch="$(git for-each-ref --format='%(refname:short)' refs/heads/ | grep -vxF "$existing" | fzf)" || return 1
else
branch="$(IFS=/; echo "$*")"
fi
dashed="$(_branch_to_path "$branch")"
target="$HOME/Code/work/${repo}-${dashed}"
if [ -d "$target" ]; then
cd "$target"
return
fi
if git show-ref --verify --quiet "refs/heads/$branch"; then
git worktree add "$target" "$branch" && cd "$target"
else
git worktree add "$target" -b "$branch" && cd "$target"
fi
}
# Jump to a worktree for the current repo
# Usage: wt (fuzzy select)
# wt main (open main worktree)
# wt feat name (open feat/name worktree, offer to create if missing)
wt() {
local repo branch dashed target dir
repo="$(_repo_base)" || return 1
if [ $# -eq 0 ]; then
dir="$(find "$HOME/Code/work" -mindepth 1 -maxdepth 1 -type d -name "${repo}-*" 2>/dev/null | sort | fzf)" || return 1
cd "$dir"
return
fi
branch="$(IFS=/; echo "$*")"
dashed="$(_branch_to_path "$branch")"
target="$HOME/Code/work/${repo}-${dashed}"
if [ -d "$target" ]; then
cd "$target"
return
fi
echo "Worktree not found: $target"
printf "Create worktree for branch '%s'? [y/N] " "$branch"
read -r reply
if [[ "$reply" =~ ^[Yy]$ ]]; then
mkwt "$@"
fi
}
# Fuzzy-jump to any worktree across all repos
wts() {
local dir
dir="$(find "$HOME/Code/work" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort | fzf)" || return 1
cd "$dir"
}
# List worktrees for the current repo
lswt() {
local repo
repo="$(_repo_base)" || return 1
git worktree list | grep "${repo}-"
}
# List all worktrees (excluding bare repos)
lswts() {
find "$HOME/Code/work" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort
}
# Remove a worktree (fuzzy select, with confirmation)
# Usage: rmwt (fuzzy select, or current worktree if in one)
# rmwt feat name (remove feat/name worktree directly)
rmwt() {
local selected repo dashed
if [ $# -gt 0 ]; then
repo="$(_repo_base)" || return 1
dashed="$(_branch_to_path "$(IFS=/; echo "$*")")"
selected="$HOME/Code/work/${repo}-${dashed}"
if [ ! -d "$selected" ]; then
echo "Worktree not found: $selected" >&2
return 1
fi
elif [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]; then
selected="$(git rev-parse --show-toplevel 2>/dev/null)"
else
local worktrees
worktrees="$(git worktree list --porcelain | awk '/^worktree /{print substr($0,10)}' | grep -v '\.git$')"
if [ -z "$worktrees" ]; then
echo "No worktrees found (are you inside a git repo?)." >&2
return 1
fi
selected="$(echo "$worktrees" | fzf)" || return 1
fi
printf "Remove worktree '%s'? [y/N] " "$selected"
read -r reply
if [[ "$reply" =~ ^[Yy]$ ]]; then
local gitdir branch
gitdir="$(cd -- "$(git rev-parse --git-common-dir 2>/dev/null)" 2>/dev/null && pwd)"
branch="$(git -C "$selected" rev-parse --abbrev-ref HEAD 2>/dev/null)"
git worktree remove "$selected" || return 1
[[ "$PWD" == "$selected"* ]] && cd "$gitdir"
if [ -n "$branch" ] && [ "$branch" != "HEAD" ]; then
printf "Also delete branch '%s'? [y/N] " "$branch"
read -r reply
if [[ "$reply" =~ ^[Yy]$ ]]; then
git branch -d "$branch"
fi
fi
fi
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment