Skip to content

Instantly share code, notes, and snippets.

@abpai
Last active September 18, 2025 21:14
Show Gist options
  • Select an option

  • Save abpai/0ceb7235be9adac83a8cc31bd21d9ebc to your computer and use it in GitHub Desktop.

Select an option

Save abpai/0ceb7235be9adac83a8cc31bd21d9ebc to your computer and use it in GitHub Desktop.
# ====================
# Git Worktree Functions (wt)
# ====================
wt() {
local GIT='/usr/bin/git'
local GREP='/usr/bin/grep'
local CUT='/usr/bin/cut'
local HEAD='/usr/bin/head'
local BASENAME='/usr/bin/basename'
local LN='/bin/ln'
local MKDIR='/bin/mkdir'
local RM='/bin/rm'
local DIRNAME='/usr/bin/dirname'
if [[ ! -x "$GIT" ]]; then
echo 'Error: git not found at /usr/bin/git' >&2
return 1
fi
case "$1" in
new)
[[ -z "$2" ]] && { echo 'usage: wt new <branch> [base]' >&2; return 1; }
local repo=$($BASENAME "$($GIT rev-parse --show-toplevel)")
local path="$HOME/worktrees/$repo/$2"
$GIT worktree add -b "$2" "$path" "${3:-origin/HEAD}"
echo "Created: $path"
;;
open)
if [[ -z "$2" ]]; then
echo 'Select a worktree:'
$GIT worktree list
return 1
fi
local repo=$($BASENAME "$($GIT rev-parse --show-toplevel)")
local expected_path="$HOME/worktrees/$repo/$2"
if [[ -d "$expected_path" ]]; then
builtin cd "$expected_path"
else
local path=$($GIT worktree list | $GREP "\[$2\]" | $CUT -d' ' -f1)
if [[ -n "$path" && -d "$path" ]]; then
builtin cd "$path"
else
echo "Worktree not found: $2" >&2
echo 'Available worktrees:'
$GIT worktree list
return 1
fi
fi
;;
list)
$GIT worktree list
;;
rm)
# usage: wt rm <branch> [-f|--force] [-d|--delete-branch]
shift
local force='no'
local delete_branch='no'
local branch=''
# parse args in any order
while [[ $# -gt 0 ]]; do
case "$1" in
-f|--force) force='yes' ;;
-d|--delete-branch) delete_branch='yes' ;;
*) branch="${branch:-$1}" ;;
esac
shift
done
[[ -z "$branch" ]] && { echo 'usage: wt rm <branch> [-f|--force] [-d|--delete-branch]' >&2; return 1; }
# resolve worktree path for the branch (robust: porcelain output)
local path
path=$($GIT worktree list --porcelain | /usr/bin/awk -v b="$branch" '
$1=="worktree"{w=$2}
$1=="branch"{gsub("refs/heads/","",$2); if($2==b){print w; exit}}
')
[[ -z "$path" ]] && { echo "Worktree not found for branch: $branch" >&2; return 1; }
# refuse to remove the primary worktree
local main_path
main_path=$($GIT worktree list --porcelain | /usr/bin/awk '$1=="worktree"{print $2; exit}')
if [[ "$path" == "$main_path" ]]; then
echo 'Refusing to remove the primary worktree.' >&2
return 1
fi
# unlock if forced (handles locked worktrees)
if [[ "$force" == 'yes' ]]; then
$GIT worktree unlock "$path" >/dev/null 2>&1 || true
fi
if [[ "$force" == 'yes' ]]; then
$GIT worktree remove --force "$path" || { echo 'remove failed' >&2; return 1; }
else
$GIT worktree remove "$path" || { echo 'remove failed (use -f to force)' >&2; return 1; }
fi
if [[ "$delete_branch" == 'yes' ]]; then
$GIT branch -D "$branch" 2>/dev/null || echo "Note: branch '$branch' not deleted (checked out elsewhere?)" >&2
fi
;;
main)
local main_path=$($GIT worktree list | $HEAD -1 | $CUT -d' ' -f1)
builtin cd "$main_path"
;;
link)
# usage: wt link [-f] <path> [path2 ...]
# Symlinks repo-relative paths from the primary worktree into the current worktree.
shift
local force='no'
if [[ "$1" == '-f' ]]; then
force='yes'
shift
fi
[[ "$#" -lt 1 ]] && { echo 'usage: wt link [-f] <path> [path2 ...]' >&2; return 1; }
local main_path=$($GIT worktree list | $HEAD -1 | $CUT -d' ' -f1)
local current_path=$($GIT rev-parse --show-toplevel)
if [[ -z "$main_path" || -z "$current_path" ]]; then
echo 'Error: could not resolve worktree paths' >&2
return 1
fi
for rel in "$@"; do
if [[ "$rel" == /* ]]; then
echo "Skip absolute path '$rel' (use repo-relative paths like 'data' or 'data/raw')" >&2
continue
fi
if [[ "$rel" == .git* ]]; then
echo "Refusing to link git internals: '$rel'" >&2
continue
fi
local src="$main_path/$rel"
local dst="$current_path/$rel"
if [[ ! -e "$src" ]]; then
echo "Missing in primary worktree: $rel" >&2
continue
fi
local parent
parent=$($DIRNAME "$dst")
$MKDIR -p "$parent"
if [[ -e "$dst" || -L "$dst" ]]; then
if [[ "$force" != 'yes' ]]; then
echo "Exists: $rel (use -f to replace)" >&2
continue
fi
$RM -rf "$dst"
fi
$LN -s "$src" "$dst"
echo "Linked $rel -> $src"
done
;;
unlink)
# usage: wt unlink <path> [path2 ...]
# Removes symlinks in the current worktree (won't delete real dirs/files).
shift
[[ "$#" -lt 1 ]] && { echo 'usage: wt unlink <path> [path2 ...]' >&2; return 1; }
local current_path=$($GIT rev-parse --show-toplevel)
for rel in "$@"; do
local dst="$current_path/$rel"
if [[ -L "$dst" ]]; then
$RM "$dst"
echo "Unlinked $rel"
else
echo "Not a symlink (skipped): $rel" >&2
fi
done
;;
*)
echo 'Usage: wt {new|open|list|rm|main|link|unlink} [args]'
echo ''
echo 'Commands:'
echo ' new <branch> [base] - Create new worktree'
echo ' open <branch> - Switch to worktree'
echo ' list - List all worktrees'
echo ' rm <branch> [-d] - Remove worktree (-d deletes branch too)'
echo ' main - Switch to main worktree'
echo ' link [-f] <paths...> - Symlink paths from primary -> current worktree'
echo ' unlink <paths...> - Remove symlinks in current worktree'
;;
esac
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment