Last active
April 22, 2026 08:43
-
-
Save hotzen/3c6dd63818f9cbb4e6a4bf9d78af20bd to your computer and use it in GitHub Desktop.
git worktree helpers
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
| # wtswitch [query] | |
| # Switch to an existing worktree using fzf. Works from any worktree. | |
| # With no argument: opens fzf picker. | |
| # With argument: if exactly one worktree matches, cd there directly. | |
| # if multiple match, open fzf pre-filled with the query. | |
| # if no match, exit with error. | |
| # | |
| # Requires: fzf | |
| # | |
| # Example: | |
| # wtswitch # interactive picker | |
| # wtswitch foo # direct cd if unambiguous, fzf otherwise | |
| # | |
| wtswitch() { | |
| if ! git rev-parse --show-toplevel &>/dev/null; then | |
| echo "Not in a git repository" >&2 | |
| return 1 | |
| fi | |
| local worktrees | |
| worktrees=$(git worktree list --porcelain | awk '/^worktree /{print $2}') | |
| if [[ -z "$worktrees" ]]; then | |
| echo "No worktrees found" >&2 | |
| return 1 | |
| fi | |
| local selection | |
| if [[ -n "$1" ]]; then | |
| local matches | |
| matches=$(echo "$worktrees" | fzf --filter="$1") | |
| local match_count | |
| match_count=$(echo "$matches" | grep -c .) | |
| if [[ $match_count -eq 0 ]]; then | |
| echo "No worktree matching '$1' found" >&2 | |
| return 1 | |
| elif [[ $match_count -eq 1 ]]; then | |
| selection="$matches" | |
| fi | |
| fi | |
| if [[ -z "$selection" ]]; then | |
| selection=$(echo "$worktrees" | fzf --prompt="worktree > " ${1:+--query="$1"}) | |
| [[ -z "$selection" ]] && return 0 | |
| fi | |
| echo "✓ Switched to ${selection/#$HOME/~}" | |
| cd "$selection" | |
| } | |
| # wtcreate <branch> | |
| # Create a new branch and worktree from the base branch. | |
| # Must be called from a base repo (~/src/REPO), not from within a worktree. | |
| # Worktree is created at ~/src/REPO.branch and cd'd into automatically. | |
| # | |
| # Base branch resolution order: | |
| # 1. .worktreebase file in repo root (single line, branch name) | |
| # 2. origin/HEAD (set via: git remote set-head origin --auto) | |
| # 3. main or master, whichever exists | |
| # | |
| # Branch names are sanitized: lowercased, non-alphanumeric runs replaced | |
| # with dashes, leading/trailing dashes stripped, truncated to 200 chars. | |
| # | |
| # Example: | |
| # ~/src/myrepo $ wtcreate feature/my-thing | |
| # ✓ Created branch feature-my-thing from main and worktree @ ~/src/myrepo.feature-my-thing | |
| # | |
| wtcreate() { | |
| local branch_name="$1" | |
| if [[ -z "$branch_name" ]]; then | |
| echo "Usage: wtc <branch-name>" >&2 | |
| return 1 | |
| fi | |
| local git_root | |
| git_root=$(git rev-parse --show-toplevel 2>/dev/null) | |
| if [[ -z "$git_root" ]]; then | |
| echo "Not in a git repository" >&2 | |
| return 1 | |
| fi | |
| local src_dir="${HOME}/src" | |
| local repo_name | |
| repo_name=$(basename "$git_root") | |
| if [[ "$git_root" != "${src_dir}/${repo_name}" ]]; then | |
| echo "Not in a base repo under ${src_dir}" >&2 | |
| return 1 | |
| fi | |
| branch_name="${branch_name:l}" | |
| branch_name="${branch_name//[^a-z0-9]/-}" | |
| branch_name="${branch_name##-}" | |
| branch_name="${branch_name%%-}" | |
| branch_name="${branch_name[1,200]}" | |
| branch_name="${branch_name%%-}" | |
| if [[ -z "$branch_name" ]]; then | |
| echo "Branch name is empty after sanitization" >&2 | |
| return 1 | |
| fi | |
| local base_branch | |
| local worktreebase_file="${git_root}/.worktreebase" | |
| if [[ -f "$worktreebase_file" ]]; then | |
| base_branch=$(cat "$worktreebase_file" | tr -d '[:space:]') | |
| fi | |
| if [[ -z "$base_branch" ]]; then | |
| base_branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||') | |
| fi | |
| if [[ -z "$base_branch" ]]; then | |
| if git show-ref --verify --quiet refs/heads/main; then | |
| base_branch="main" | |
| elif git show-ref --verify --quiet refs/heads/master; then | |
| base_branch="master" | |
| else | |
| echo "Could not determine base branch" >&2 | |
| return 1 | |
| fi | |
| fi | |
| if ! git show-ref --verify --quiet "refs/heads/${base_branch}" && \ | |
| ! git show-ref --verify --quiet "refs/remotes/origin/${base_branch}"; then | |
| echo "Base branch '${base_branch}' not found locally or in origin" >&2 | |
| return 1 | |
| fi | |
| local worktree_path="${src_dir}/${repo_name}.${branch_name}" | |
| if [[ -d "$worktree_path" ]]; then | |
| echo "Worktree already exists: ${worktree_path}" >&2 | |
| return 1 | |
| fi | |
| if git show-ref --verify --quiet "refs/heads/${branch_name}"; then | |
| echo "Branch '${branch_name}' already exists" >&2 | |
| return 1 | |
| fi | |
| git worktree add -b "$branch_name" "$worktree_path" "$base_branch" || return 1 | |
| echo "✓ Created branch ${branch_name} from ${base_branch} and worktree @ ${worktree_path/#$HOME/~}" | |
| cd "$worktree_path" | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment