Skip to content

Git Worktree: Feature Branch Isolation

git worktree is not just for parallel builds — it is equally powerful for managing a pipeline of feature branches in isolation. This guide covers the full lifecycle: spinning up per-feature worktrees from main, dedicated artifact directories, hotfix isolation, and the sharp edges you will hit with submodules and branch locks.


1. Why Feature-Branch Worktrees?

In a busy team, main advances dozens of times a day. When you are mid-way through feature/payments-v2 and a critical bug lands, the naïve approach is git stash — which silently drops uncommitted context when stashed carelessly. Worktrees eliminate that risk entirely.

Workflow Isolation Safe mid-WIP? Artifact dir?
git stash Shared dir No (context hidden) Collides
git checkout Full repo swap No Collides
git worktree Separate dirs Yes Per-worktree

2. Spin Up a Feature Worktree from main

# Ensure your local main is current
git checkout main && git pull

# Create a linked worktree for the new feature
# Naming convention: ../feature-NAME-wt  (sibling to repo dir)
git worktree add ../feature-payments-v2-wt -b feature/payments-v2

# You are now inside the new worktree on the new branch
git worktree list

The worktree sits at ../feature-payments-v2-wt relative to your repo root. If you prefer a flat layout:

git worktree add ~/wt/feature-payments-v2 -b feature/payments-v2

3. Per-Feature Artifact Directories

The single most practical benefit: each worktree has its own node_modules/, dist/, target/, and .next/ tree.

# In the feature worktree — local dev server
cd ../feature-payments-v2-wt
npm install
npm run dev   # writes .next/ inside THIS worktree only

# In the main worktree — keep shipping
git checkout main
npm run build # writes .next/ in the main working dir only

No .gitignore tricks needed. No make clean before switching. Each branch's build artifacts coexist without interference.

If your CI produces a binary artifact that multiple branches reference:

# In the main worktree
ln -s /shared/artifacts/main-build ./libs/shared-build

# The feature worktree inherits the symlink (worktrees share .git)
# Commit the symlink once on main; every worktree sees it.

Caveat: The symlink target must be an absolute path or a path relative to the worktree root — relative paths from within .git do not resolve predictably.


4. Hotfix Isolation from a Feature Branch

Scenario: you are deep in feature/payments-v2 when production alerts. You need to patch main without touching your feature work.

# 1. Stash any uncommitted work (worktree-safe — stays in this worktree)
git -C ../feature-payments-v2-wt stash push -m "wip: payment module"

# 2. Main worktree is already on main — pull latest
git checkout main && git pull

# 3. Create an isolated hotfix worktree
git worktree add ../hotfix-CVE-2024-9999-wt -b hotfix/CVE-2024-9999

# 4. Fix, commit, tag, merge (see scripts/wt-hotfix.sh for full pipeline)
# ...

# 5. Remove the hotfix worktree when done
git worktree remove ../hotfix-CVE-2024-9999-wt

# 6. Back to your feature worktree — unstash
git -C ../feature-payments-v2-wt stash pop

Key insight: removing a worktree does not touch main's history or your other worktrees. The hotfix is a clean, isolated bubble.


5. Multiple Features Simultaneously

If you are context-switching between a short task and a long one:

# Short task: bugfix
git worktree add ../fix-login-redirect-wt -b fix/login-redirect

# Long task: full feature
git worktree add ../feature-payments-v2-wt -b feature/payments-v2

# Backlog task: documentation
git worktree add ../docs-api-ref-wt -b docs/api-reference

# List all active worktrees
git worktree list

Each worktree is a fully independent git status, git log, and build environment.


6. Synchronising Feature Branches with Upstream main

When main has moved on while your feature branch grew stale:

git worktree list
# /repo                      @ a3f1d2c  (worktree — usually main or current branch)
# /home/user/wt/fix-login   @ b7c9e01  (feature branch)
# /home/user/wt/feature-pmt @ d2f8a44  (feature branch)

# Rebase the feature branch on latest main
git fetch origin main
git rebase origin/main feature/payments-v2

# Or, prefer merge commits for a clean history:
git merge origin/main --no-ff -m "merge: main into feature/payments-v2"

Tip: Run rebase/merge from inside the feature worktree to keep the main worktree untouched.


7. Pitfalls and Edge Cases

7.1 One Branch Per Worktree (Hard Limit)

Git enforces this at the filesystem level — you cannot have two worktrees on the same branch:

fatal: 'feature/payments-v2' is already being used by worktree '/home/user/wt/feature-payments-v2'

Workaround: delete the existing worktree first, then create a new one.

7.2 Submodules

Submodules are tracked in .gitmodules at the repo root. When you add a worktree, Git does not automatically initialise submodules inside it — you must do so explicitly:

# After creating the worktree
git -C ../feature-payments-v2-wt submodule update --init --recursive

# When a submodule on main changes:
git -C ../feature-payments-v2-wt git submodule update --remote path/to/sub

If your feature branch adds or removes a submodule, the worktree may be in a submodule state until you run git submodule update --init.

Pattern for submodule-heavy repos:

# Wrapper alias — put in your ~/.gitconfig
# alias.wt-sub = "!f() { git worktree add ../\"$1\"-wt -b \"$2\" && git submodule update --init --recursive; }; f"

7.3 Worktree vs. Bare Repo for Shared Artifact Cache

If your team shares a compiled artifact cache:

/shared/cache/
  main-latest.tar.gz
  feature-payments-v2-latest.tar.gz

Never point build scripts at a path inside a worktree's .git directory. Build outputs live outside .git so multiple worktrees can reference them concurrently.

7.4 Detached HEAD State

Running git checkout a3f1d2c (a specific SHA) in a worktree puts it in detached HEAD — which is fine for reproduction but not for commits. Git will warn you. Always use named branches for development worktrees.

7.5 Pruning Gone Worktrees

If a worktree directory is deleted manually (without git worktree remove), Git leaves a stale entry:

git worktree prune
# Cleans up .git/worktrees/

8. Cleanup and Lifecycle

Event Command
Finish feature, merge PR git worktree remove ../feature-NAME-wt
Abandon feature git worktree remove --force ../feature-NAME-wt
List all worktrees git worktree list
Prune stale entries git worktree prune
Find worktree path for branch git worktree list --porcelain \| grep -B1 branch-name

9. Quick Reference

# Create
git worktree add ../feature-NAME-wt -b feature/NAME

# Navigate
cd ../feature-NAME-wt

# Sync with main
git fetch origin main && git rebase origin/main

# Submodules
git submodule update --init --recursive

# Hotfix isolation (from any worktree)
git stash && git checkout main && git pull
git worktree add ../hotfix-XXX-wt -b hotfix/XXX
# ... fix, commit, merge, tag ...
git worktree remove ../hotfix-XXX-wt
git stash pop

# Cleanup
git worktree remove ../feature-NAME-wt
git worktree prune

See Also

  • scripts/wt-feature.sh — automated feature worktree lifecycle script
  • docs/devops-workflows/git-worktree-parallel-builds.md — parallel CI builds and hotfix flow
  • scripts/wt-hotfix.sh — hotfix worktree spin-up with signed tag pipeline