Skip to content

Instantly share code, notes, and snippets.

@dsaad68
Last active December 2, 2025 09:38
Show Gist options
  • Select an option

  • Save dsaad68/e466e320b53a9f4007be6ad57e803f1a to your computer and use it in GitHub Desktop.

Select an option

Save dsaad68/e466e320b53a9f4007be6ad57e803f1a to your computer and use it in GitHub Desktop.
Git Work Tree

Git Worktree Cheat Sheet (my-repo / my-feature layout)

Assumptions

  • All repos live in ~/GitHub/
  • You always start inside a repo: pwd = ~/GitHub/my-repo
  • Create worktrees as siblings (e.g., ../my-repo--my-feature) — never nested inside my-repo/

Basic

Mental model (30s)

  • A worktree = an extra working copy of the same Git object store.
  • One branch ↔ one worktree (a branch can’t be checked out twice).
  • Every worktree has its own working dir & index; objects are shared → fast & space-light.

List what you have

git worktree list
git worktree list --porcelain   # script-friendly

Create a worktree

A) New branch from main

# from: ~/GitHub/my-repo
git fetch origin
git worktree add ../my-repo--my-feature -b my-feature origin/main
cd ../my-repo--my-feature

B) Branch exists on remote (origin/my-feature)

git fetch origin my-feature
git worktree add ../my-repo--my-feature -b my-feature origin/my-feature
cd ../my-repo--my-feature

C) Branch already exists locally (and not checked out elsewhere)

git fetch origin
git worktree add ../my-repo--my-feature my-feature
cd ../my-repo--my-feature

# (optional) set upstream if missing
git branch --set-upstream-to=origin/my-feature my-feature

Do work (inside the feature worktree)

git status
git add <files>
git commit -m "Implement X"
git push -u origin my-feature

Switch between worktrees

# change directories
cd ../my-repo                   # e.g., main
cd ../my-repo--my-feature       # your feature

# or run in another tree without cd:
git -C ../my-repo--my-feature status

Merge my-feature back to main

Keep main in ~/GitHub/my-repo and your feature in ../my-repo--my-feature.

# ensure feature is pushed
git -C ../my-repo--my-feature push

# merge on main’s worktree
cd ~/GitHub/my-repo
git switch main
git pull --ff-only origin main          # or just: git pull
git merge --no-ff my-feature            # or: rebase feature first, then fast-forward
git push origin main

Remove a worktree (and optionally the branch)

git worktree remove ../my-repo--my-feature

# optional: delete the branch when truly done
git branch -d my-feature
git push origin --delete my-feature

# if you ever deleted a worktree folder by hand:
git worktree prune

Sibling vs nested (don’t nest!)

Sibling (✅ recommended):

~/GitHub/my-repo
~/GitHub/my-repo--my-feature

Nested (❌ avoid):

~/GitHub/my-repo/my-feature

Nesting confuses tools and can lead to accidental commits in the wrong place.


Advanced

When my-feature is already checked out somewhere

You can’t check out the same branch twice.

  • Free it in the other worktree:
    git -C ../my-repo--somewhere switch main
    git worktree add ../my-repo--my-feature my-feature
  • Detached view at the same tip:
    git worktree add --detach ../my-repo--my-feature-view my-feature
  • Sibling branch from the same commit:
    git worktree add ../my-repo--my-feature2 -b my-feature-2 my-feature

Create temporary/special worktrees

# Scratch at current commit (disposable)
git worktree add --detach ../my-repo--scratch HEAD

# Check out a tag/sha for debugging
git fetch --tags
git worktree add --detach ../my-repo--v1.5 v1.5.0

# Hotfix starting from a release branch
git fetch origin
git worktree add ../my-repo--hotfix -b hotfix/urgent origin/release/2.x

Keep things tidy

git fetch --all --prune       # refresh refs and nuke stale remotes
git worktree prune            # clear stale worktree records

Script & inspect

# Path ↔ branch map (great for quick overviews)
git worktree list --porcelain | awk '
  /^worktree / {p=$2}
  /^branch /   {b=$2; print p "\t" b}
'

Troubleshooting (greatest hits)

  • “‘my-feature’ is already checked out at …”
    Switch that other worktree off (git -C <path> switch main), or use --detach, or create a sibling branch.
  • Used -b with an existing branch → drop -b.
    git worktree add ../my-repo--my-feature my-feature
  • Upstream not set
    git branch --set-upstream-to=origin/my-feature my-feature
  • Accidentally nested
    git worktree remove ./nested-dir && git worktree prune then recreate as a sibling.

Nice shell helpers (drop into ~/.zshrc)

# wt-add <my-feature> [start-point]   # default start = origin/main
wt-add() {
  local repo="$(basename "$PWD")"
  local topic="$1"
  local start="${2:-origin/main}"
  git fetch --all --prune
  git worktree add "../${repo}--${topic}" -b "${topic}" "${start}"
}

# wt-go <my-feature>   # cd into sibling worktree
wt-go() {
  local repo="$(basename "$PWD")"
  cd "../${repo}--$1" || return
}

# wt-rm <my-feature>   # remove worktree (+ prune); uncomment branch delete if desired
wt-rm() {
  local repo="$(basename "$PWD")"
  local path="../${repo}--$1"
  git worktree remove "$path"
  # git branch -d "$1" && git push origin --delete "$1"
  git worktree prune
}

Copy-paste “mini+” quick block

# From: ~/GitHub/my-repo
git fetch --all --prune
git worktree list

# Create a new feature from main
git worktree add ../my-repo--my-feature -b my-feature origin/main
cd ../my-repo--my-feature
git add .
git commit -m "Start my-feature"
git push -u origin my-feature

# Merge on main
cd ~/GitHub/my-repo
git switch main
git pull --ff-only
git merge --no-ff my-feature
git push

# Remove when done
git worktree remove ../my-repo--my-feature
git branch -d my-feature
git push origin --delete my-feature
git worktree prune
# Creates a git worktree for a new branch in a sibling directory
#
# Usage:
# wt-add <feature> [start-point] [--go]
#
# Arguments:
# feature - Name of the branch and worktree
# start-point - Where to branch from (default: origin/main or main)
# Ignored if branch already exists
# --go - Change directory to the worktree after creation
#
# Examples:
# wt-add auth-feature # Branch from origin/main (or main)
# wt-add auth-feature --go # Create and cd into worktree
# wt-add hotfix v1.2.3 --go # Branch from tag and cd
# wt-add experiment feature/beta # Branch from another branch
# wt-add existing-pr # Checkout existing branch
#
# Creates: ../<repo-name>.<feature>/ on branch <feature>
wt-add() {
local repo="$(basename "$PWD")"
local feature="$1"
local start=""
local do_cd=false
# Parse arguments for start-point and --go flag
shift
while [ $# -gt 0 ]; do
case "$1" in
--go)
do_cd=true
;;
*)
start="$1"
;;
esac
shift
done
# Fetch latest to ensure start point and remote branches are up-to-date
git fetch --all --prune
# Determine default start point if not provided
if [ -z "$start" ]; then
if git show-ref --verify --quiet "refs/remotes/origin/main"; then
start="origin/main"
elif git show-ref --verify --quiet "refs/heads/main"; then
start="main"
else
echo "Error: Cannot find origin/main or main branch"
return 1
fi
fi
local worktree_path="../${repo}.${feature}"
# Check if branch already exists (locally or remotely)
if git show-ref --verify --quiet "refs/heads/${feature}" || \
git show-ref --verify --quiet "refs/remotes/origin/${feature}"; then
# Branch exists: checkout existing branch into worktree
echo "Branch '${feature}' exists, creating worktree..."
git worktree add "$worktree_path" "${feature}"
else
# Branch doesn't exist: create new branch from start point
echo "Creating new branch '${feature}' from ${start}..."
git worktree add "$worktree_path" -b "${feature}" "${start}"
fi
# Change directory if flag was provided
if [ "$do_cd" = true ]; then
cd "$worktree_path" || return 1
fi
}
# Changes directory to an existing worktree
#
# Usage:
# wt-go <feature>
#
# Arguments:
# feature - Name of the worktree to navigate to
#
# Examples:
# wt-go auth-feature # cd to ../my-repo.auth-feature/
# wt-go hotfix # cd to ../my-repo.hotfix/
wt-go() {
local repo="$(basename "$PWD")"
local target="../${repo}.$1"
if [ ! -d "$target" ]; then
echo "Error: Worktree '$target' does not exist"
return 1
fi
cd "$target" || return
}
# Lists all worktrees for the current repository
#
# Usage:
# wt-ls
wt-ls() {
git worktree list
}
# Removes a worktree and optionally deletes the branch
#
# Usage:
# wt-rm <feature>
#
# Arguments:
# feature - Name of the worktree to remove
#
# Prompts interactively whether to delete the branch
#
# Examples:
# wt-rm auth-feature # Prompts: delete branch? (y/N)
wt-rm() {
local repo="$(basename "$PWD")"
local feature="$1"
local GIT_CMD="/opt/homebrew/bin/git"
# Extract base repo name (handle being in a worktree)
repo="${repo%%.*}"
local path="../${repo}.${feature}"
# Validate worktree exists
if [ ! -d "$path" ]; then
echo "Error: Worktree '$path' does not exist"
return 1
fi
# Remove the worktree
$GIT_CMD worktree remove "$path"
# Prompt for branch deletion (zsh compatible)
echo -n "Delete branch '${feature}' locally and remotely? (y/N): "
read -r REPLY
if [[ $REPLY =~ ^[Yy]$ ]]; then
$GIT_CMD branch -d "${feature}" && $GIT_CMD push origin --delete "${feature}" 2>/dev/null
fi
# Clean up worktree metadata
$GIT_CMD worktree prune
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment