Git Worktrees for Parallel Development
A practical guide to Git worktrees for working on multiple branches simultaneously without stashing or switching, including setup, workflows, and cleanup.
Git Worktrees for Parallel Development
You are deep in a feature branch, files open in your editor, debugger attached, test runner watching. Then someone asks you to review a pull request on a different branch. Your choices: stash everything and switch branches (losing your context), commit half-finished work (polluting history), or open a second clone of the repository (wasting disk space and setup time). Git worktrees give you a fourth option — check out a different branch in a separate directory while keeping your current branch exactly as it is.
I use worktrees daily for code reviews, hotfixes, and running tests against different branches. The overhead is near zero — creating a worktree takes under a second. This guide covers setup, workflows, and the patterns that make worktrees practical.
Prerequisites
- Git installed (v2.15+ for full worktree support)
- A Git repository
- Terminal access
- Basic understanding of Git branches
What Worktrees Are
A worktree is an additional working directory linked to the same repository. Each worktree has its own checked-out branch and its own set of files, but they share the same .git data:
my-project/ # Main worktree (your usual directory)
.git/ # Shared Git data
src/
package.json
my-project-hotfix/ # Additional worktree
.git -> ../my-project/.git # Link to shared data
src/
package.json # Checked out at hotfix branch
Both directories share commits, stashes, remotes, and configuration. Changes committed in either worktree are immediately visible to the other.
Basic Operations
Adding a Worktree
# Create a worktree for an existing branch
git worktree add ../my-project-hotfix hotfix/login-bug
# Create a worktree with a new branch
git worktree add -b feature/search ../my-project-search
# Create a worktree from a specific commit or tag
git worktree add ../my-project-v2 v2.0.0
The path can be anywhere on your filesystem:
# Common patterns
git worktree add ../project-review feature/new-api
git worktree add ~/worktrees/project-hotfix hotfix/urgent
git worktree add /tmp/project-test main
Listing Worktrees
git worktree list
# /home/shane/projects/my-project abc1234 [main]
# /home/shane/projects/my-project-hotfix def5678 [hotfix/login-bug]
# /home/shane/projects/my-project-search ghi9012 [feature/search]
# Verbose output
git worktree list --porcelain
Removing a Worktree
# Remove a worktree (deletes the directory)
git worktree remove ../my-project-hotfix
# Force remove if it has uncommitted changes
git worktree remove --force ../my-project-hotfix
# Clean up stale worktree references
git worktree prune
Moving a Worktree
git worktree move ../my-project-hotfix ../hotfixes/login-bug
Worktree Workflows
Workflow 1: Hotfix While Working on a Feature
# You are on feature/dashboard with work in progress
cd ~/projects/my-project
# Urgent bug report comes in
git worktree add ../my-project-hotfix -b hotfix/payment-error main
# Fix the bug in the new worktree
cd ../my-project-hotfix
npm install
# Edit src/payment.js
git add src/payment.js
git commit -m "fix: correct payment rounding error"
git push origin hotfix/payment-error
# Return to your feature work — everything is exactly as you left it
cd ../my-project
# Editor state, running processes, everything preserved
# Clean up after the hotfix is merged
git worktree remove ../my-project-hotfix
Workflow 2: Code Review
# Check out the PR branch in a separate worktree
git fetch origin
git worktree add ../my-project-review origin/feature/new-api
cd ../my-project-review
npm install
npm test
# Review the code, run it, test it
# Leave review comments, then clean up
cd ../my-project
git worktree remove ../my-project-review
Workflow 3: Running Tests Against Multiple Branches
# Test your feature branch against main
git worktree add ../my-project-main main
# Run tests in both simultaneously
cd ../my-project-main && npm test &
cd ../my-project && npm test &
wait
# Compare results
Workflow 4: Comparing Behavior Between Branches
# Run the old version
git worktree add ../my-project-old v1.5.0
cd ../my-project-old
npm install
PORT=3001 npm start &
# Run the new version
cd ../my-project
PORT=3000 npm start &
# Compare behavior side by side in the browser
# http://localhost:3000 vs http://localhost:3001
Workflow 5: Long-Running Branch Maintenance
# Keep a persistent worktree for the release branch
git worktree add ~/worktrees/my-project-release release/2.x
# Periodically update it
cd ~/worktrees/my-project-release
git pull
# Apply cherry-picks from main
git cherry-pick abc1234
Worktree-Specific Settings
Each worktree can have its own Git configuration:
# Set config for a specific worktree
cd ../my-project-hotfix
git config --worktree core.sparseCheckout true
# Worktree-specific configs are stored in:
# .git/worktrees/<name>/config
Separate node_modules
Each worktree has its own file system, so npm install creates separate node_modules:
cd ../my-project-hotfix
npm install # Independent node_modules
This is actually an advantage — different branches can have different dependency versions without conflicts.
Automation Scripts
Worktree Helper Script
// scripts/worktree.js
var childProcess = require("child_process");
var path = require("path");
function run(cmd) {
return childProcess.execSync(cmd, { encoding: "utf-8" }).trim();
}
var action = process.argv[2];
var branch = process.argv[3];
if (action === "review") {
// Quick review setup
var worktreePath = path.resolve("..", path.basename(process.cwd()) + "-review");
console.log("Creating review worktree at " + worktreePath);
run("git fetch origin");
run("git worktree add " + worktreePath + " " + branch);
console.log("Installing dependencies...");
childProcess.execSync("npm install", { cwd: worktreePath, stdio: "inherit" });
console.log("\nReady for review: " + worktreePath);
console.log("When done: git worktree remove " + worktreePath);
} else if (action === "hotfix") {
var hotfixPath = path.resolve("..", path.basename(process.cwd()) + "-hotfix");
var hotfixBranch = "hotfix/" + (branch || "fix");
run("git worktree add -b " + hotfixBranch + " " + hotfixPath + " main");
childProcess.execSync("npm install", { cwd: hotfixPath, stdio: "inherit" });
console.log("\nHotfix worktree ready: " + hotfixPath);
console.log("Branch: " + hotfixBranch);
} else if (action === "list") {
console.log(run("git worktree list"));
} else if (action === "clean") {
run("git worktree prune");
console.log("Pruned stale worktrees.");
console.log(run("git worktree list"));
} else {
console.log("Usage:");
console.log(" node scripts/worktree.js review <branch> — Review a branch");
console.log(" node scripts/worktree.js hotfix [name] — Create hotfix from main");
console.log(" node scripts/worktree.js list — List worktrees");
console.log(" node scripts/worktree.js clean — Prune stale worktrees");
}
Git Aliases for Worktrees
git config --global alias.wt "worktree"
git config --global alias.wtl "worktree list"
git config --global alias.wta "worktree add"
git config --global alias.wtr "worktree remove"
git config --global alias.wtp "worktree prune"
Complete Working Example: Full Worktree Workflow
#!/bin/bash
# demonstrate-worktrees.sh
PROJECT_DIR=$(pwd)
PROJECT_NAME=$(basename "$PROJECT_DIR")
echo "=== Git Worktree Workflow Demo ==="
echo "Project: $PROJECT_NAME"
echo "Directory: $PROJECT_DIR"
echo ""
# Show current state
echo "Current branch: $(git branch --show-current)"
echo ""
# Create a hotfix worktree
echo "Creating hotfix worktree..."
HOTFIX_DIR="../${PROJECT_NAME}-hotfix"
git worktree add -b hotfix/demo "$HOTFIX_DIR" main
echo ""
# Create a review worktree
echo "Creating review worktree..."
REVIEW_DIR="../${PROJECT_NAME}-review"
git fetch origin
REVIEW_BRANCH=$(git branch -r --sort=-committerdate | head -1 | tr -d ' ')
git worktree add "$REVIEW_DIR" "$REVIEW_BRANCH" 2>/dev/null || \
git worktree add "$REVIEW_DIR" main
echo ""
# List all worktrees
echo "=== Active Worktrees ==="
git worktree list
echo ""
# Show that they share the same objects
echo "All worktrees share the same .git database:"
echo "Main: $(du -sh .git 2>/dev/null | cut -f1)"
echo "Hotfix: $(ls -la "$HOTFIX_DIR/.git" 2>/dev/null)"
echo "Review: $(ls -la "$REVIEW_DIR/.git" 2>/dev/null)"
echo ""
# Clean up
echo "Cleaning up demo worktrees..."
git worktree remove "$HOTFIX_DIR" 2>/dev/null
git worktree remove "$REVIEW_DIR" 2>/dev/null
git branch -D hotfix/demo 2>/dev/null
git worktree prune
echo "Done."
Common Issues and Troubleshooting
"fatal: branch is already checked out at another worktree"
You cannot check out the same branch in two worktrees simultaneously:
Fix: This is by design — having the same branch in two worktrees would cause confusion when committing. Create a new branch instead: git worktree add -b new-branch ../dir existing-branch. Or use a detached HEAD: git worktree add --detach ../dir commit-hash.
Worktree has stale lock file
A worktree was not properly removed (e.g., the directory was deleted manually):
Fix: Run git worktree prune to clean up references to worktrees whose directories no longer exist. If a lock file persists, remove it: rm .git/worktrees/<name>/locked.
npm install fails in worktree due to postinstall scripts
Some packages assume they are in the repository root:
Fix: Run npm install from within the worktree directory. If scripts reference .git directly, they may need updating since worktrees use a .git file (pointer) instead of a .git directory.
IDE does not recognize the worktree as a Git repository
Some editors look for a .git directory, not a .git file:
Fix: Most modern editors (VS Code, IntelliJ) support worktrees natively. Open the worktree directory directly — the editor should detect the Git repository through the .git file. If not, update your editor.
Changes in one worktree conflict with another
Both worktrees modify the same file on different branches:
Fix: Worktrees on different branches cannot conflict during normal work — conflicts only appear during merge or rebase, which is expected. Each worktree's changes are independent until branches are integrated.
Best Practices
- Use worktrees for context switching, not stashing. Stashing loses your mental context. Worktrees preserve everything — open files, running processes, terminal state.
- Name worktree directories consistently. Use
project-hotfix,project-review,project-releasenaming. This makes it obvious what each directory is for. - Clean up worktrees when done. Use
git worktree removeinstead of deleting directories manually. This keeps Git's internal references clean. - Run
git worktree pruneperiodically. This removes references to worktrees whose directories were deleted outside of Git. - Keep worktrees close to the main repo. Sibling directories (
../project-hotfix) are conventional and easy to navigate. - Install dependencies separately in each worktree. Each worktree has its own
node_modules. Runnpm installafter creating the worktree if you need to run the project. - Use worktrees for code reviews. Checking out a PR branch in a worktree lets you run the code, run tests, and explore without disrupting your current work.