Last active
March 11, 2026 11:12
-
-
Save lukKowalski/1464f0e7b6eccdbb5a0f56fd1be0fe5b to your computer and use it in GitHub Desktop.
Switch through git worktrees of a current repo
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
| #!/usr/bin/env bash | |
| # wt-pick: interactive git worktree picker, outputs selected path | |
| # | |
| # How to install and use: | |
| # 1. Copy this to your ~/.local under wt-pick file name | |
| # 2. Add to ~/.zshrc to cd on pick: | |
| # wt() { local p; p=$(wt-pick "$@") && cd "$p"; } | |
| # use with wt-pick - to output picked dir path or wt to also cd into in | |
| set -euo pipefail | |
| # ── colors ──────────────────────────────────────────────────────────────────── | |
| BOLD=$'\033[1m' | |
| DIM=$'\033[2m' | |
| RESET=$'\033[0m' | |
| GREEN=$'\033[32m' | |
| CYAN=$'\033[36m' | |
| YELLOW=$'\033[33m' | |
| RED=$'\033[31m' | |
| # ── check git repo ───────────────────────────────────────────────────────────── | |
| if ! git rev-parse --git-dir > /dev/null 2>&1; then | |
| printf '%b✗ Not in a git repository%b\n' "$RED$BOLD" "$RESET" >&2 | |
| exit 1 | |
| fi | |
| # ── parse worktrees into "branch\tpath" lines ────────────────────────────────── | |
| worktrees=() | |
| while IFS= read -r line; do | |
| worktrees+=("$line") | |
| done < <( | |
| git worktree list --porcelain | awk ' | |
| /^worktree / { path = substr($0, 10) } | |
| /^branch / { branch = substr($0, 8); sub("refs/heads/", "", branch) } | |
| /^HEAD / { if (!branch) branch = "(detached " substr($0, 6, 7) ")" } | |
| /^$/ { if (path) printf "%s\t%s\n", branch, path; path=""; branch="" } | |
| ' | |
| ) | |
| if [[ ${#worktrees[@]} -eq 0 ]]; then | |
| printf '%b⚠ No worktrees found%b\n' "$YELLOW" "$RESET" >&2 | |
| exit 1 | |
| fi | |
| # ── fzf picker ───────────────────────────────────────────────────────────────── | |
| if command -v fzf &>/dev/null; then | |
| selected=$( | |
| printf '%s\n' "${worktrees[@]}" | awk -F'\t' '{ | |
| n = split($2, parts, "/") | |
| printf "%-25s\t%-40s %s\n", parts[n], $1, $2 | |
| }' | fzf \ | |
| --prompt=" worktree › " \ | |
| --height=50% \ | |
| --reverse \ | |
| --ansi \ | |
| --no-sort \ | |
| --header=$' DIR BRANCH' \ | |
| --color='header:italic:dim,prompt:cyan:bold,pointer:green,hl:green:bold' | |
| ) || exit 0 | |
| awk '{ print $NF }' <<< "$selected" | |
| exit 0 | |
| fi | |
| # ── arrow-key menu (fallback when fzf is not available) ─────────────────────── | |
| branches=() | |
| dirs=() | |
| paths=() | |
| for item in "${worktrees[@]}"; do | |
| IFS=$'\t' read -r br pt <<< "$item" | |
| branches+=("$br") | |
| paths+=("$pt") | |
| dirs+=("$(basename "$pt")") | |
| done | |
| n=${#branches[@]} | |
| idx=0 | |
| # Save terminal state and switch to raw mode | |
| old_stty="" | |
| old_stty=$(stty -g 2>/dev/null) || true | |
| cleanup() { | |
| [[ -n "$old_stty" ]] && stty "$old_stty" 2>/dev/null || true | |
| printf '\033[?25h' >&2 # restore cursor | |
| } | |
| trap cleanup EXIT INT TERM | |
| stty -echo -icanon min 1 time 0 | |
| printf '\033[?25l' >&2 # hide cursor | |
| draw() { | |
| printf '\033[2J\033[H' >&2 # clear screen, move to top-left | |
| printf '\n%s Git Worktrees%s %s↑↓ / j k navigate · enter select · q quit%s\n\n' \ | |
| "$BOLD$CYAN" "$RESET" "$DIM" "$RESET" >&2 | |
| printf ' %s%-25s %s%s\n' "$DIM" "DIR" "BRANCH" "$RESET" >&2 | |
| local i | |
| for (( i=0; i<n; i++ )); do | |
| if (( i == idx )); then | |
| printf ' %s▶ %-25s %s%s%s\n' \ | |
| "$GREEN$BOLD" "${dirs[$i]}" "$RESET$CYAN" "${branches[$i]}" "$RESET" >&2 | |
| else | |
| printf ' %s%-25s%s %s%s%s\n' \ | |
| "$BOLD" "${dirs[$i]}" "$RESET" "$DIM" "${branches[$i]}" "$RESET" >&2 | |
| fi | |
| done | |
| } | |
| draw | |
| while true; do | |
| read -rsn1 key | |
| if [[ $key == $'\x1b' ]]; then | |
| read -rsn2 -t 1 key | |
| key=$'\x1b'"$key" | |
| fi | |
| case "$key" in | |
| $'\x1b[A'|k) idx=$(( idx > 0 ? idx - 1 : idx )) ; draw ;; | |
| $'\x1b[B'|j) idx=$(( idx < n-1 ? idx + 1 : idx )) ; draw ;; | |
| ''|$'\n') break ;; | |
| q|Q) printf '\n' >&2 ; exit 0 ;; | |
| esac | |
| done | |
| printf '\n' >&2 | |
| echo "${paths[$idx]}" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Demo: