feat: add git worktree management utilities for Fish and Bash

Add comprehensive worktree tooling with FZF integration:
- wtb: create branch worktree with gitignored file cloning
- wtcd/wtr: interactive picker and multi-remove with FZF
- wtf/wth: feature/hotfix branch shortcuts
- wts/wtl: status overview and listing
- Automatic .worktrees/ organization and .gitignore management
This commit is contained in:
2026-01-23 13:42:30 -06:00
parent 61a66df27d
commit 8804b425fb
11 changed files with 461 additions and 0 deletions
@@ -0,0 +1,50 @@
function _wt_clone_ignored --description "Clone gitignored files from source to destination worktree"
set -l src_path $argv[1]
set -l dst_path $argv[2]
# Find gitignored files (excluding .git and .worktrees)
set -l ignored_files (git -C "$src_path" ls-files --others --ignored --exclude-standard 2>/dev/null | \
grep -v '^\.git' | grep -v '^\.worktrees')
if test -z "$ignored_files"
return 0
end
echo ""
echo "Found gitignored files to copy..."
# Measure size with timeout
set -l size_bytes 0
set -l size_human "unknown"
set -l size_output (timeout 10s du -sb "$src_path" --exclude='.git' --exclude='.worktrees' 2>/dev/null | head -n1)
if test $status -eq 0 -a -n "$size_output"
set size_bytes (echo "$size_output" | awk '{print $1}')
set size_human (numfmt --to=iec --suffix=B "$size_bytes" 2>/dev/null; or echo "$size_bytes bytes")
end
# 100MB threshold
set -l threshold (math "100 * 1024 * 1024")
if test "$size_bytes" -gt "$threshold" -o "$size_human" = "unknown"
echo "Gitignored files size: $size_human"
read -P "Copy these files to new worktree? [y/N] " -n 1 confirm
echo ""
if not string match -qi 'y' "$confirm"
echo "Skipping gitignored file copy"
return 0
end
end
echo "Copying gitignored files..."
for file in $ignored_files
set -l src "$src_path/$file"
set -l dst "$dst_path/$file"
if test -e "$src"
mkdir -p (dirname "$dst")
cp -r "$src" "$dst" 2>/dev/null
end
end
echo "Done copying gitignored files"
end
+54
View File
@@ -0,0 +1,54 @@
function wtb --description "Add git worktree with new branch"
if test (count $argv) -lt 1
echo "Usage: wtb <branch-name> [base-ref]" >&2
return 1
end
set -l root (git rev-parse --show-toplevel 2>/dev/null)
if test -z "$root"
echo "Not in a git repository" >&2
return 1
end
set -l branch $argv[1]
# Get base ref: argument, or current branch tip
set -l base
if test (count $argv) -ge 2
set base $argv[2]
else
# Get current branch name (not HEAD if detached)
set base (git symbolic-ref --short HEAD 2>/dev/null)
if test -z "$base"
set base (git rev-parse HEAD)
end
end
# Sanitize branch name for directory (replace / with -)
set -l dir_name (string replace --all '/' '-' "$branch")
set -l wt_path "$root/.worktrees/$dir_name"
# Create .worktrees directory if needed
mkdir -p "$root/.worktrees"
# Add to .gitignore if not already there
if not grep -q '^\.worktrees/?$' "$root/.gitignore" 2>/dev/null
echo ".worktrees/" >> "$root/.gitignore"
echo "Added .worktrees/ to .gitignore"
end
echo "Creating worktree at $wt_path"
echo " Branch: $branch"
echo " Base: $base"
if git worktree add -b "$branch" "$wt_path" "$base"
echo ""
echo "Worktree created: $wt_path"
# Clone gitignored files from current worktree
set -l current_wt (pwd)
_wt_clone_ignored "$current_wt" "$wt_path"
cd "$wt_path"
end
end
+16
View File
@@ -0,0 +1,16 @@
function wtcd --description "FZF-based worktree picker - cd into selected"
set -l root (git rev-parse --show-toplevel 2>/dev/null)
if test -z "$root"
echo "Not in a git repository" >&2
return 1
end
set -l selected (git worktree list | fzf --height=40% --reverse \
--header="Select worktree" \
--preview="git -C {1} log --oneline -5 2>/dev/null; echo ''; git -C {1} status --short 2>/dev/null")
if test -n "$selected"
set -l wt_path (echo "$selected" | awk '{print $1}')
cd "$wt_path"
end
end
+11
View File
@@ -0,0 +1,11 @@
function wtf --description "Create feature branch worktree"
if test (count $argv) -lt 1
echo "Usage: wtf <feature-name> [base-ref]" >&2
return 1
end
set -l name $argv[1]
set -l base $argv[2]
wtb "feature/$name" $base
end
@@ -0,0 +1,3 @@
function wtfeature --description "Create feature branch worktree (alias for wtf)"
wtf $argv
end
@@ -0,0 +1,3 @@
function wtfix --description "Create hotfix branch worktree (alias for wth)"
wth $argv
end
+11
View File
@@ -0,0 +1,11 @@
function wth --description "Create hotfix branch worktree"
if test (count $argv) -lt 1
echo "Usage: wth <hotfix-name> [base-ref]" >&2
return 1
end
set -l name $argv[1]
set -l base $argv[2]
wtb "hotfix/$name" $base
end
+3
View File
@@ -0,0 +1,3 @@
function wtl --description "List all git worktrees"
git worktree list $argv
end
+40
View File
@@ -0,0 +1,40 @@
function wtr --description "FZF-based git worktree remover (multi-select)"
set -l root (git rev-parse --show-toplevel 2>/dev/null)
if test -z "$root"
echo "Not in a git repository" >&2
return 1
end
# Get the main worktree path to exclude it
set -l main_wt (git worktree list --porcelain | grep '^worktree ' | head -n1 | string replace 'worktree ' '')
# Select worktrees with fzf (excluding main)
set -l selected (git worktree list | grep -v "^$main_wt " | fzf --multi --height=40% --reverse \
--header="Select worktrees to remove (Tab to multi-select)" \
--preview="git -C {1} log --oneline -5 2>/dev/null || echo 'No commits'")
if test -z "$selected"
echo "No worktrees selected"
return 0
end
echo "Will remove the following worktrees:"
for line in $selected
set -l wt_path (echo "$line" | awk '{print $1}')
echo " - $wt_path"
end
echo ""
read -P "Confirm removal? [y/N] " -n 1 confirm
echo ""
if string match -qi 'y' "$confirm"
for line in $selected
set -l wt_path (echo "$line" | awk '{print $1}')
echo "Removing $wt_path..."
git worktree remove "$wt_path"
end
else
echo "Cancelled"
end
end
+31
View File
@@ -0,0 +1,31 @@
function wts --description "Show git status for all worktrees"
set -l root (git rev-parse --show-toplevel 2>/dev/null)
if test -z "$root"
echo "Not in a git repository" >&2
return 1
end
for wt_path in (git worktree list --porcelain | grep '^worktree ' | string replace 'worktree ' '')
set -l branch (git -C "$wt_path" symbolic-ref --short HEAD 2>/dev/null; or echo "detached")
# Print header with box drawing
set_color --bold blue
echo "╭─────────────────────────────────────────────────────────────╮"
echo -n "│ "
set_color --bold yellow
echo -n "$wt_path"
set_color normal
echo ""
set_color --bold blue
echo -n "│ "
set_color cyan
echo "[$branch]"
set_color --bold blue
echo "╰─────────────────────────────────────────────────────────────╯"
set_color normal
# Run git status
git -C "$wt_path" status --short
echo ""
end
end