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:
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.
Shared Artifacts via Symbolic Links¶
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
.gitdo 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:
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:
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:
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 scriptdocs/devops-workflows/git-worktree-parallel-builds.md— parallel CI builds and hotfix flowscripts/wt-hotfix.sh— hotfix worktree spin-up with signed tag pipeline