Git Worktrees: Work on Multiple Branches Simultaneously
English original published: 2026-05-16T20:16:22.751+03:00. Original English article
Corrected educational edition. Technical review against primary sources: 2026-10-28. Code examples were not compiled or runtime-tested. The original English publication date is preserved; the Hebrew edition uses its actual publication date, not the English original date.
Work on Multiple Branches
Simultaneously
git worktree add ../feature
- main
- feature-auth
- hotfix-ssl
Advanced Git Feature · Parallel Development · Branch Isolation
Original illustration (optional, enlarged)
Optional enlarged artwork. Scroll horizontally or vertically within this area if needed; the readable text version above contains the same labels and branch connections.
Git Worktrees
Work on multiple branches in separate directories, reducing the need to stash or switch the current checkout source
simultaneously
operations
across worktrees
since 2.5
Introduction: The Problem Git Worktrees Solve
A common scenario is being in the middle of a feature on feature/auth-refactor, and suddenly you get a message — "critical bug in production, fix needed ASAP!" You have two options:
- Stash your changes, switch branches, fix the bug, commit, push, then switch back and pop the stash — hoping nothing conflicts
- Clone the entire repository again into a second folder and work in parallel
Both options are tedious. Git Worktrees provide an elegant built-in solution: they allow you to check out multiple branches simultaneously in separate directories, all sharing the same .git repository and history.
๐️ How Worktrees Work
src/
package.json
Branch: main
src/ (hotfix versions)
Branch: hotfix/critical-fix
src/ (feature versions)
Branch: feature/new-ui
Linked worktrees share the common object store and most refs, but each has its own HEAD and index. Their working files do not automatically mirror one another. The common Git directory is usually the main worktree’s .git directory. source
Basic Commands
Git worktrees are managed with git worktree commands. Here are the essentials:
# Illustrative commands: not executed or runtime-tested.
# Choose one operation at a time in an authorized test repository.
# Existing branch (must not already be checked out elsewhere):
git worktree add ../my-repo-hotfix hotfix/critical-bug
# Or create a different worktree and a new branch from local main:
git worktree add -b feature/new-payment ../my-repo-payment main
# Detached review of a real commit; replace COMMIT_SHA with a verified hash:
git worktree add --detach ../my-repo-review COMMIT_SHA
# Inspect registered worktrees:
git worktree list
# Illustrative paths/commit abbreviations, not actual command output:
# /home/example/my-repo a1b2c3d [main]
# /home/example/my-repo-hotfix b2c3d4e [hotfix/critical-bug]
# Inspect and preserve work before removal; run outside the target directory:
git -C ../my-repo-hotfix status --short
git worktree remove ../my-repo-hotfix
# If removal refuses, inspect the cause. Do not add --force automatically.
# Preview stale administrative records, then prune only after inspection:
git worktree prune --dry-run
# git worktree prune
# Alternative operation for a STILL-EXISTING worktree (not after removing it):
# Ensure the destination parent exists and the target is not in use.
# git worktree move ../my-repo-hotfix ../archives/hotfix-exampleReal-World Example: Hotfix While Keeping Feature Work
# Bash workflow outline; runtime-untested, not a single paste-and-run script.
# Begin in the feature worktree; choose unused absolute paths.
cd "$HOME/my-repo" || exit 1
repo=$(git rev-parse --show-toplevel) || exit 1
hotfix="$HOME/my-repo-hotfix"
# local main must be an appropriate, verified base for this fix.
git worktree add -b hotfix/login-null-check "$hotfix" main || exit 1
cd "$hotfix" || exit 1
# Edit only the intended file, inspect, then commit and push when authorized:
vim src/auth/login.ts
git diff -- src/auth/login.ts
git add -- src/auth/login.ts
git commit -m "fix: handle null user on login redirect"
git push origin hotfix/login-null-check
# STOP: create/review/merge the PR through the repository's normal process.
# Do not additionally merge or push main locally after a remote PR merge.
# Only after confirming the PR is merged and the hotfix worktree is no longer needed:
cd "$repo" || exit 1
git -C "$hotfix" status --short
git worktree remove "$hotfix" # refuses unsafe removal; inspect any refusal
# Optional: after verifying reachability of its commits in the relevant branch:
# git branch -d hotfix/login-null-check
# A squash/rebase merge may cause -d to refuse. Do not force-delete blindly.
# The original feature worktree was not switched or reset.Advanced Usage: CI/CD, Code Reviews & Parallel Testing
# Independent Bash patterns; all runtime-untested.
# Trusted repository code only: tests/builds can execute scripts.
repo=$(git rev-parse --show-toplevel) || exit 1
# Pattern 1: review a verified remote-tracking ref without checking out its branch.
# Refresh the intended ref separately if needed; use a unique unused path.
git worktree add --detach /tmp/review-pr-example origin/feature/payment-v2
# Inspect code first; only run it if trusted and authorized.
# Return outside the review tree before cleanup:
cd "$repo" || exit 1
git worktree remove /tmp/review-pr-example
# Pattern 2: detached checkout avoids main already checked out elsewhere.
git worktree add --detach /tmp/test-main-example main
# Prepare dependencies separately; use separate ports/databases for concurrent tests.
(cd /tmp/test-main-example && npm test) &
first=$!
(cd "$repo" && npm test) &
second=$!
wait "$first"; first_status=$?
wait "$second"; second_status=$?
printf 'Test exit statuses: %s %s\n' "$first_status" "$second_status"
# Inspect generated files before a separate cleanup step; never force-remove.
# Pattern 3: release branches must exist and not be checked out elsewhere.
git worktree add "$HOME/releases/v2.1" release/2.1
git worktree add "$HOME/releases/v2.0" release/2.0
patch_file="$HOME/patches/example-reviewed-fix.patch" # illustrative, not a CVE
# Review the patch and changed files in EACH tree; commit only intended paths.
git -C "$HOME/releases/v2.1" apply --check "$patch_file"
git -C "$HOME/releases/v2.1" apply "$patch_file"
git -C "$HOME/releases/v2.1" diff
# Repeat review/apply explicitly for v2.0; then stage specific files and commit.
# Pattern 4: separate CI script, run only with a verified CI_COMMIT_SHA.
# Generated/untracked files may make cleanup refuse; preserve and inspect them.
ci_worktree_build() (
repo=$(git rev-parse --show-toplevel) || exit 1
: "${CI_COMMIT_SHA:?Set a verified commit hash}"
build_dir=$(mktemp -d "${TMPDIR:-/tmp}/ci-worktree.XXXXXX") || exit 1
git worktree add --detach "$build_dir" "$CI_COMMIT_SHA" || exit 1
cleanup() {
cd "$repo" || return
git worktree remove "$build_dir" ||
printf 'Cleanup refused; inspect retained worktree: %s\n' "$build_dir" >&2
}
trap cleanup EXIT
cd "$build_dir" || exit 1
npm ci && npm run build && npm test
)
# Calling ci_worktree_build executes repository scripts; not called here.⚠️ Important Rules & Constraints
By default Git prevents checking out the same branch in two worktrees at once. This protects against conflicting updates to one branch. The usual error is: fatal: 'feature/x' is already checked out source
All worktrees share a single .git object database. Git operations like fetch, log, and blame can access shared history, but some refs are per-worktree. Commands that inspect the working tree or index can produce different results. source
Open each directory as a separate project in your editor. Integration details depend on the editor and version; verify your setup. For VS Code, Remote - Containers is an older extension name; Dev Containers is the current product name. Container/worktree behavior must be checked for the selected configuration. source
Worktrees don't duplicate the .git objects directory, but each worktree needs its checked-out files and can have separate dependencies and build outputs. Additional disk usage depends on the repository and tooling; the original 500MB/100–200MB example was not a measured result. source
Worktrees vs Alternatives
⚖️ Comparison: Worktrees vs Other Approaches
| Approach | Context Switch | Disk Usage | Parallel Work | Best For |
|---|---|---|---|---|
| git worktree | No switch of current checkout | Shared objects; extra files | ✅ True parallel | Parallel tasks in one repo |
| git stash | ⚠️ Full switch | No second checkout | ❌ Sequential only | Quick, temporary |
| Second clone | Separate checkout | Separate repository storage | ✅ True parallel | Isolated experiments |
| WIP commit + switch | ⚠️ Full switch | No second checkout | ❌ Sequential only | When stash isn't clean |
Scripting Worktrees for Productivity
# Bash-only educational helpers; runtime-untested.
# Review before adding to shell configuration. No settings changed by this article.
gwt-review() {
local branch="$1" repo dir
repo=$(git rev-parse --show-toplevel) || return
git check-ref-format --branch "$branch" >/dev/null || return
git fetch origin "refs/heads/$branch" || return
dir=$(mktemp -d "${TMPDIR:-/tmp}/review-worktree.XXXXXX") || return
git worktree add --detach "$dir" FETCH_HEAD || return
printf 'Reviewing at: %s\n' "$dir"
cd "$dir" || return
}
gwt-add() {
local branch="$1" repo repo_name short_branch dir
repo=$(git rev-parse --show-toplevel) || return
git check-ref-format --branch "$branch" >/dev/null || return
repo_name=$(basename "$repo")
short_branch=$(printf '%s' "$branch" | sed 's/[/:]/-/g')
dir="$(dirname "$repo")/${repo_name}-${short_branch}"
# main must exist; existing branch/path names intentionally cause refusal.
git worktree add -b "$branch" "$dir" main || return
printf 'Worktree at: %s\n' "$dir"
cd "$dir" || return
}
gwt-list() {
# Use Git's own path-safe human output rather than fragile awk parsing.
git worktree list
}
gwt-clean-reviews() {
# Inventory only. No bulk deletion from parsed text or global wildcard paths.
git worktree list
printf '%s\n' 'Inspect one exact review path, leave it, then use git worktree remove PATH.'
}Worktrees with Monorepos and Node.js/npm
# Alternative package-manager examples, not consecutive steps.
# Dependencies and lifecycle scripts can execute code; trusted projects only.
# npm option: create the worktree once at an unused path.
git worktree add ../my-repo-feature feature/new-ui
(cd ../my-repo-feature && npm ci)
# pnpm ALTERNATIVE, after creating a suitable worktree once:
# (cd ../my-repo-feature && pnpm install --frozen-lockfile)
# Select the manager matching the repository lockfile and supported version.
# Caches/stores may reuse downloaded content, but install time is workload-specific.
# Each worktree can have separate node_modules and build outputs.
# In the repository's .gitignore, an in-tree rule may be appropriate:
# node_modules/
# Do NOT put ../*-worktree/ or sibling paths in this repository's .gitignore:
# ignore rules cannot match files outside the repository working tree.๐ก Best Practices & Pro Tips
../repo-hotfix, ../repo-review-123, ../repo-release-2.1. Makes gwt-list output readable.git worktree prune only after inspecting its dry-run output, to remove stale administrative records rather than working files. A file named ~/.gitignore is not automatically a global ignore file unless core.excludesFile points to it. Sibling worktrees outside a repository cannot be ignored by that repository’s .gitignore. source1 source2 code /path/to/worktree) or use JetBrains' "Open Directory" for parallel IDEs on the same codebase.~/projects/my-repo → ~/projects/my-repo-hotfix. Avoids deep nesting and keeps related work grouped.Summary
Git worktrees let several checkouts of one repository coexist. They can help with hotfixes, pull-request review, parallel tests and release maintenance, while sharing objects and preserving separate working files. They do not isolate every external resource: ports, databases, build caches and credentials may still be shared. source
Start small: next time you need to context-switch, try git worktree add instead of stashing. Once you build the habit, you'll find yourself using worktrees as a core part of your daily Git workflow.
All shell examples are runtime-untested and must be adapted in an authorized test repository. Creating worktrees, commits and pushes changes state; dependency installs/tests may execute repository code. No example was executed. Force removal was removed: it can destroy uncommitted files. Cleanup must run from outside the target worktree and stop if Git refuses; inspect and preserve files rather than adding --force. Sample branch names, hashes and patch names are illustrative, not real security advisories.