Skip to content

Git Worktrees: Work on Multiple Branches Simultaneously

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.

GIT WORKTREES

Work on Multiple Branches
Simultaneously

git worktree add ../feature

Connections shown in the illustration
  • 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 Simultaneously
๐ŸŒฟ Git Advanced Features

Git Worktrees

Work on multiple branches in separate directories, reducing the need to stash or switch the current checkout source

N+1
Branches
simultaneously
Fewer
Stash
operations
Shared
.git history
across worktrees
Built-in
Git core
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:

  1. Stash your changes, switch branches, fix the bug, commit, push, then switch back and pop the stash — hoping nothing conflicts
  2. 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

๐Ÿ“
my-repo/ (main)
.git/ → full history
src/
package.json
Branch: main
→
๐Ÿ“
my-repo-hotfix/
.git → (linked to main .git)
src/ (hotfix versions)
Branch: hotfix/critical-fix
๐Ÿ“
my-repo-feature/
.git → (linked to main .git)
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:

bash — Essential git worktree commands
# 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-example

Real-World Example: Hotfix While Keeping Feature Work

bash — Complete hotfix workflow with worktrees
# 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

bash — Advanced patterns
# 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

๐Ÿšซ No Duplicate Checkouts

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

๐Ÿ“ฆ Shared Object Store

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

๐Ÿ”ง IDE Support

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

๐Ÿ’พ Disk Space

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 worktreeNo switch of current checkoutShared objects; extra files✅ True parallelParallel tasks in one repo
git stash⚠️ Full switchNo second checkout❌ Sequential onlyQuick, temporary
Second cloneSeparate checkoutSeparate repository storage✅ True parallelIsolated experiments
WIP commit + switch⚠️ Full switchNo second checkout❌ Sequential onlyWhen stash isn't clean

Scripting Worktrees for Productivity

bash — Shell helper functions for worktrees
# 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

bash — Handling node_modules with worktrees
# 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

๐Ÿ“‚ Naming Convention
Use descriptive names: ../repo-hotfix, ../repo-review-123, ../repo-release-2.1. Makes gwt-list output readable.
๐Ÿงน Regular Cleanup
Run 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
๐Ÿ”— IDE Integration
Open each worktree as a separate VS Code window (code /path/to/worktree) or use JetBrains' "Open Directory" for parallel IDEs on the same codebase.
๐Ÿ“ Location Strategy
Keep sibling worktrees next to the main repo: ~/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.

Sources for corrections

Post a Comment

Previous Post Next Post