Last active
September 18, 2025 21:14
-
-
Save abpai/0ceb7235be9adac83a8cc31bd21d9ebc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ==================== | |
| # 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