Supercharge Your Workflow with Git Worktrees

6 min read 1124 words

Supercharge Your Workflow with Git Worktrees

Git worktrees have completely transformed how I manage multiple branches in my development workflow. If you're tired of constantly stashing changes or context-switching between branches, this approach might be just what you need.

What are Git Worktrees?

Git worktrees allow you to check out multiple branches simultaneously in separate directories. Instead of switching between branches in a single working directory, you can have multiple working directories, each with its own branch.

# Basic usage
git worktree add ../feature-branch feature-branch

This command creates a new directory called feature-branch with that branch checked out.

My Worktree Workflow

I've developed a workflow where:

  1. I maintain a bare repository with .git configured as the central git database (no working files)
  2. My main branch lives in a directory named main
  3. Each feature branch gets its own directory

This means I can work on multiple features simultaneously without context switching or stashing changes.

Initial Setup: Converting to Bare Repository Structure

Here's how to set up the bare repository structure from scratch or convert an existing repository:

Starting Fresh

If you're cloning a new repository:

# Clone as a bare repository into my-project/.git
git clone --bare https://github.com/username/repo.git my-project/.git

# Enter the project directory
cd my-project

# Configure proper fetch refspec for the bare repo
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"

# Fetch to ensure we have remote refs after configuring refspec
git fetch origin

# Create the main worktree
git worktree add main origin/main

# Move into the main directory
cd main

Converting an Existing Repository

If you already have a regular clone and want to convert it:

# From your repository root
# 1. Configure your existing .git to be bare
git config --bool core.bare true

# 2. Move up one directory
cd ..

# 3. Rename your project folder to indicate it's the main branch
mv my-project main

# 4. Create a new project root and move main into it
mkdir my-project
mv main my-project/

# 5. Move the .git directory to the project root
mv my-project/main/.git my-project/

# 6. Configure and fetch remote refs
cd my-project
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch origin

# 7. Create the main worktree
git worktree add main origin/main --force

# Your structure is now:
# my-project/
#   .git/   (bare repository)
#   main/   (main branch worktree)

Directory Structure

After setup, your directory structure should look like:

my-project/
├── .git/           # Git database (configured as bare)
├── _scripts/       # Helper scripts (optional)
├── _hooks/         # Shared git hooks (optional)
├── main/           # Main branch worktree
├── feature-a/      # Feature branch worktree (when created)
└── feature-b/      # Another feature branch worktree

Creating Your First Feature Worktree

Once you have the bare structure set up:

# From the project root (containing .git and main)
# For an existing remote branch (sets up proper tracking)
git worktree add --track -b feature-branch feature-branch origin/feature-branch

# Or create a new branch and worktree
git worktree add -b new-feature new-feature main
cd new-feature
git push --set-upstream origin new-feature

Simplifying Worktree Creation

To avoid remembering all the flags, create a simple wrapper script:

# Create a scripts directory for helper utilities
mkdir -p _scripts

# Create the worktree helper script
cat > _scripts/worktree-add << 'EOF'
#!/bin/bash
# Simplified worktree creation with automatic tracking

BRANCH_NAME=$1

if [ -z "$BRANCH_NAME" ]; then
    echo "Usage: ./worktree-add <branch-name>"
    exit 1
fi

# Ensure we have proper remote tracking (needed for bare repos)
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"

# Fetch latest refs to ensure we have all remote branches
git fetch origin

# Check if local branch already exists
if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
    echo "Using existing local branch: $BRANCH_NAME"
    git worktree add "$BRANCH_NAME" "$BRANCH_NAME"
    # Ensure it tracks the remote if one exists
    if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then
        cd "$BRANCH_NAME"
        git branch --set-upstream-to="origin/$BRANCH_NAME" 2>/dev/null
        cd ..
    fi
# Check if remote branch exists
elif git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then
    echo "Creating worktree for existing remote branch: $BRANCH_NAME"
    git worktree add --track -b "$BRANCH_NAME" "$BRANCH_NAME" "origin/$BRANCH_NAME"
else
    echo "Creating worktree for new local branch: $BRANCH_NAME"
    # Use the first existing worktree's branch as base, or fallback to origin/main
    BASE_BRANCH=$(git worktree list --porcelain | grep "branch refs/heads/" | head -1 | sed 's/branch refs\/heads\///')
    if [ -z "$BASE_BRANCH" ]; then
        BASE_BRANCH="origin/main"
    fi
    git worktree add -b "$BRANCH_NAME" "$BRANCH_NAME" "$BASE_BRANCH"
    echo "Remember to 'git push -u origin $BRANCH_NAME' when ready to push"
fi
EOF

chmod +x _scripts/worktree-add

# Create a git alias (wa = worktree add, or nw = new worktree)
git config alias.wa '!_scripts/worktree-add'
# Alternative: git config alias.nw '!_scripts/worktree-add'

Now you can simply use:

# For any branch (existing or new)
git wa feature-branch

# Or use the script directly
./_scripts/worktree-add feature-branch

Git Hooks with Worktrees

If you use git hooks in your project, you'll want them to apply to all worktrees. By default, each worktree has its own .git directory with its own hooks. To share hooks across all worktrees:

# Create a shared hooks directory
mkdir -p _hooks

# Add your hooks (e.g., pre-commit, pre-push)
# ... create your hook files in _hooks/ ...

# Configure git to use this directory for all worktrees (use absolute path!)
git config core.hooksPath "$(pwd)/_hooks"

Important: The hooks path must be absolute for worktrees to find it correctly. Using $(pwd)/_hooks ensures the full path is stored in the git config.

Benefits I've Experienced

  • No more stashing: I can leave work-in-progress changes in one branch while working on another
  • Faster context switching: Switching between tasks is as simple as changing directories
  • Better organization: Each feature gets its own isolated workspace
  • Parallel workflows: I can run tests in one worktree while coding in another
  • Persistent context: Temporary NOTES.md files (gitignored) stay with each worktree, preserving non-committed context throughout the work lifecycle
  • Agent-friendly: AI coding assistants like Claude Code can work in parallel on different features without conflicts

Limitations

While git worktrees are powerful, they do have some limitations:

  • Dependencies: If package dependencies differ between branches, you'll need to run install commands when switching worktrees
  • IDE integration: Some IDEs may need to be configured to recognize each worktree as a separate project
  • Disk space: Each worktree contains a full copy of your working files

Getting Started

If you want to try this workflow, start by following the setup instructions above to create a bare repository structure. This approach will give you:

  • Clean separation between git database and working directories
  • Easy parallel development across branches
  • Persistent untracked files per worktree
  • Automatic upstream tracking with the hook setup

Give git worktrees a try - they might just revolutionize your development workflow as they did mine!