Git Work Trees

A Step-by-Step Guide to Git Work Trees

Introduction

Git work trees are a powerful feature that allows you to work with multiple branches simultaneously without switching between them. This guide will take you from basic concepts to advanced workflows, using practical examples with a repository structure in your ~/GitHub folder.

Prerequisites

  • Basic understanding of Git concepts (commits, branches, remotes)
  • Git installed on your system
  • A GitHub folder structure like: ~/GitHub/

Basic Concepts

What is a Git Work Tree?

A Git work tree is a working directory that contains a copy of your repository files. By default, Git has one work tree per repository, but you can create additional work trees to work on different branches simultaneously.

Why Use Work Trees?

  • Parallel Development: Work on multiple branches without switching
  • Build Testing: Test different branches in separate directories
  • Code Review: Review code in one branch while working on another
  • Performance: Avoid the overhead of switching branches

Level 1: Basic Work Tree Operations

Step 1: Understanding Your Repository Structure

First, let’s understand the typical structure:

~/GitHub/
├── my-repo/           # Your main repository
   ├── .git/         # Git metadata
   ├── src/          # Source code
   ├── docs/         # Documentation
   └── README.md     # Project readme

Step 2: Creating Your First Work Tree

Navigate to your repository and create a work tree for a feature branch:

# Navigate to your repository
cd ~/GitHub/my-repo

# Create a new branch (if it doesn't exist)
git checkout -b my-feature

# Create a work tree for this branch
git worktree add ../my-repo-my-feature my-feature

Step 3: Understanding the Result

After creating the work tree, your structure becomes:

~/GitHub/
├── my-repo/                    # Main repository (main branch)
   ├── .git/                  # Git metadata
   ├── src/                   # Source code
   └── README.md              # Project readme
└── my-repo-my-feature/        # Work tree for my-feature branch
    ├── .git                   # Git worktree reference
    ├── src/                   # Source code (my-feature version)
    └── README.md              # Project readme (my-feature version)

Step 4: Working with Multiple Work Trees

Now you can work on both branches simultaneously:

# In one terminal - work on main branch
cd ~/GitHub/my-repo
echo "Working on main branch" >> main-work.txt
git add main-work.txt
git commit -m "Add main branch work"

# In another terminal - work on feature branch
cd ~/GitHub/my-repo-my-feature
echo "Working on feature branch" >> feature-work.txt
git add feature-work.txt
git commit -m "Add feature branch work"

Level 2: Intermediate Work Tree Management

Step 5: Listing and Managing Work Trees

View all your work trees:

cd ~/GitHub/my-repo
git worktree list

Expected output:

~/GitHub/my-repo                    main     [main]
~/GitHub/my-repo-my-feature         my-feature [my-feature]

Step 6: Removing Work Trees

When you’re done with a work tree:

# Remove the work tree directory
git worktree remove ../my-repo-my-feature

# Or if you want to keep the directory but remove the Git association
git worktree remove --force ../my-repo-my-feature

Step 7: Creating Work Trees for Existing Branches

Create work trees for branches that already exist:

# Create work tree for an existing branch
git worktree add ../my-repo-existing-branch existing-branch

# Create work tree for a remote branch
git fetch origin
git worktree add ../my-repo-remote-feature origin/remote-feature

Level 3: Advanced Work Tree Scenarios

Step 8: Temporary Work Trees

Create temporary work trees that are automatically cleaned up:

# Create a temporary work tree (automatically removed when branch is deleted)
git worktree add --detach ../my-repo-temp HEAD

# Work on a temporary fix
cd ../my-repo-temp
echo "Quick fix" > quick-fix.txt
git add quick-fix.txt
git commit -m "Quick fix"

# Merge back to main
cd ../my-repo
git merge ../my-repo-temp
git worktree remove ../my-repo-temp

Step 9: Work Trees with Different Base Directories

Organize work trees in a structured way:

# Create a work tree in a specific directory
git worktree add ~/GitHub/worktrees/my-repo-feature my-feature

# Your structure now looks like:
~/GitHub/
├── my-repo/                           # Main repository
├── my-repo-my-feature/               # Feature work tree
└── worktrees/                        # Organized work trees
    └── my-repo-feature/              # Another feature work tree

Step 10: Work Trees for Different Git Operations

Use work trees for specific Git operations:

# Create work tree for testing
git worktree add ../my-repo-test test-branch

# Create work tree for documentation
git worktree add ../my-repo-docs docs-branch

# Create work tree for hotfix
git worktree add ../my-repo-hotfix hotfix-branch

Level 4: Complex Workflows and Best Practices

Step 11: Work Tree for Continuous Integration

Set up a work tree for CI/CD testing:

# Create a work tree for CI testing
git worktree add ../my-repo-ci ci-test

# Set up CI environment
cd ../my-repo-ci
npm install
npm run test
npm run build

# If tests pass, merge back
cd ../my-repo
git merge ci-test
git worktree remove ../my-repo-ci

Step 12: Work Tree for Code Review

Use work trees for thorough code review:

# Create work tree for review
git worktree add ../my-repo-review review-branch

# Review code in the work tree
cd ../my-repo-review
code .  # Open in VS Code
# Review files, run tests, check formatting

# Make review comments
git commit -m "Address review comments"

# Clean up
cd ../my-repo
git worktree remove ../my-repo-review

Step 13: Work Tree for Parallel Development

Work on multiple features simultaneously:

# Create work trees for different features
git worktree add ../my-repo-feature-a feature-a
git worktree add ../my-repo-feature-b feature-b
git worktree add ../my-repo-feature-c feature-c

# Work on feature A
cd ../my-repo-feature-a
# Make changes, commit

# Switch to feature B
cd ../my-repo-feature-b
# Make changes, commit

# Switch to feature C
cd ../my-repo-feature-c
# Make changes, commit

Level 5: Troubleshooting and Maintenance

Step 14: Common Issues and Solutions

Issue: Work tree is locked

# Check if work tree is locked
git worktree list

# Force remove locked work tree
git worktree remove --force ../my-repo-my-feature

Issue: Work tree directory was deleted manually

# Prune orphaned work tree references
git worktree prune

# Or prune with verbose output
git worktree prune --verbose

Issue: Work tree is out of sync

# Update work tree
cd ../my-repo-my-feature
git pull origin my-feature

# Or reset to match main repository
git reset --hard origin/my-feature

Step 15: Work Tree Maintenance

Regular maintenance tasks:

# List all work trees
git worktree list

# Check work tree status
cd ~/GitHub/my-repo
git worktree list --porcelain

# Prune orphaned work trees
git worktree prune

# Check for work tree conflicts
git worktree list | grep -E "(locked|prunable)"

Level 6: Advanced Integration and Automation

Step 16: Scripting Work Tree Operations

Create scripts for common work tree operations:

#!/bin/bash
# create-worktree.sh

REPO_NAME=$1
BRANCH_NAME=$2
WORKTREE_PATH="../${REPO_NAME}-${BRANCH_NAME}"

if [ -z "$REPO_NAME" ] || [ -z "$BRANCH_NAME" ]; then
    echo "Usage: $0 <repo-name> <branch-name>"
    exit 1
fi

cd ~/GitHub/$REPO_NAME

# Create branch if it doesn't exist
git checkout -b $BRANCH_NAME 2>/dev/null || git checkout $BRANCH_NAME

# Create work tree
git worktree add $WORKTREE_PATH $BRANCH_NAME

echo "Created work tree at: $WORKTREE_PATH"
echo "Branch: $BRANCH_NAME"

Usage:

chmod +x create-worktree.sh
./create-worktree.sh my-repo my-feature

Step 17: Work Tree with Git Hooks

Set up hooks for work tree management:

# In your main repository
cd ~/GitHub/my-repo

# Create a post-commit hook
cat > .git/hooks/post-commit << 'EOF'
#!/bin/bash
# Update all work trees after commit
git worktree list | while read path branch hash; do
    if [ "$path" != "$(pwd)" ]; then
        echo "Updating work tree: $path"
        cd "$path"
        git pull origin "$branch"
    fi
done
EOF

chmod +x .git/hooks/post-commit

Step 18: Work Tree with IDE Integration

Configure your IDE to work with multiple work trees:

# Create a VS Code workspace for all work trees
cat > ~/GitHub/my-repo.code-workspace << 'EOF'
{
    "folders": [
        {
            "name": "Main Repository",
            "path": "."
        },
        {
            "name": "Feature Branch",
            "path": "../my-repo-my-feature"
        }
    ],
    "settings": {
        "git.autoRepositoryDetection": false
    }
}
EOF

Level 7: Custom Shell Functions for Work Tree Management

Step 19: Essential Work Tree Functions

Add these functions to your ~/.zshrc or ~/.bashrc for seamless work tree management:

# Function to create a work tree and switch to it
add-wt() {
    local branch_name="$1"

    if [ -z "$branch_name" ]; then
        echo "Usage: add-wt <branch-name>"
        echo "Creates a work tree for the specified branch and switches to it"
        return 1
    fi

    # Get the current repository name and path
    local repo_path=$(git rev-parse --show-toplevel 2>/dev/null)
    if [ $? -ne 0 ]; then
        echo "Error: Not in a Git repository"
        return 1
    fi

    local repo_name=$(basename "$repo_path")
    local parent_dir=$(dirname "$repo_path")
    local worktree_path="$parent_dir/${repo_name}-${branch_name}"

    # Check if work tree already exists
    if git worktree list | grep -q "$worktree_path"; then
        echo "Work tree already exists at: $worktree_path"
        echo "Switching to existing work tree..."
        cd "$worktree_path"
        return 0
    fi

    # Check if branch exists locally or remotely
    if ! git show-ref --verify --quiet "refs/heads/$branch_name" && \
       ! git show-ref --verify --quiet "refs/remotes/origin/$branch_name"; then
        echo "Branch '$branch_name' does not exist. Creating new branch..."
        git checkout -b "$branch_name"
    else
        # Switch to existing branch
        git checkout "$branch_name"
    fi

    # Create the work tree
    echo "Creating work tree for branch '$branch_name'..."
    if git worktree add "$worktree_path" "$branch_name"; then
        echo "✅ Work tree created successfully at: $worktree_path"
        echo "Switching to work tree..."
        cd "$worktree_path"

        # Show current status
        echo "Current branch: $(git branch --show-current)"
        echo "Work tree path: $(pwd)"
    else
        echo "❌ Failed to create work tree"
        return 1
    fi
}

# Function to navigate to an existing work tree
go-wt() {
    local branch_name="$1"

    if [ -z "$branch_name" ]; then
        echo "Usage: go-wt <branch-name>"
        echo "Navigates to the work tree for the specified branch"
        return 1
    fi

    # Get the current repository name and path
    local repo_path=$(git rev-parse --show-toplevel 2>/dev/null)
    if [ $? -ne 0 ]; then
        echo "Error: Not in a Git repository"
        return 1
    fi

    local repo_name=$(basename "$repo_path")
    local parent_dir=$(dirname "$repo_path")
    local worktree_path="$parent_dir/${repo_name}-${branch_name}"

    # Check if work tree exists
    if [ -d "$worktree_path" ] && git worktree list | grep -q "$worktree_path"; then
        echo "Switching to work tree: $worktree_path"
        cd "$worktree_path"

        # Show current status
        echo "Current branch: $(git branch --show-current)"
        echo "Work tree path: $(pwd)"
    else
        echo "❌ Work tree for branch '$branch_name' does not exist"
        echo "Use 'add-wt $branch_name' to create it first"
        return 1
    fi
}

# Helper function to list all work trees with current status
list-wt() {
    local repo_path=$(git rev-parse --show-toplevel 2>/dev/null)
    if [ $? -ne 0 ]; then
        echo "Error: Not in a Git repository"
        return 1
    fi

    local repo_name=$(basename "$repo_path")
    local current_path=$(pwd)

    echo "📁 Work Trees for repository: $repo_name"
    echo "=========================================="

    git worktree list | while read path branch hash; do
        local status=""
        if [ "$path" = "$current_path" ]; then
            status="📍 CURRENT"
        fi

        local branch_name=$(echo "$branch" | sed 's/\[\(.*\)\]/\1/')
        printf "%-50s %-20s %s\n" "$path" "$branch_name" "$status"
    done

    echo ""
    echo "💡 Use 'add-wt <branch>' to create a new work tree"
    echo "💡 Use 'go-wt <branch>' to navigate to an existing work tree"
}

# Function to remove a work tree
remove-wt() {
    local branch_name="$1"

    if [ -z "$branch_name" ]; then
        echo "Usage: remove-wt <branch-name>"
        echo "Removes the work tree for the specified branch"
        return 1
    fi

    # Get the current repository name and path
    local repo_path=$(git rev-parse --show-toplevel 2>/dev/null)
    if [ $? -ne 0 ]; then
        echo "Error: Not in a Git repository"
        return 1
    fi

    local repo_name=$(basename "$repo_path")
    local parent_dir=$(dirname "$repo_path")
    local worktree_path="$parent_dir/${repo_name}-${branch_name}"

    # Check if work tree exists
    if [ -d "$worktree_path" ] && git worktree list | grep -q "$worktree_path"; then
        echo "Removing work tree: $worktree_path"

        # Check if we're currently in the work tree to be removed
        if [ "$(pwd)" = "$worktree_path" ]; then
            echo "⚠️  You are currently in the work tree to be removed"
            echo "Switching to main repository first..."
            cd "$repo_path"
        fi

        # Remove the work tree
        if git worktree remove "$worktree_path"; then
            echo "✅ Work tree removed successfully"
        else
            echo "❌ Failed to remove work tree. Use --force if needed:"
            echo "   git worktree remove --force $worktree_path"
        fi
    else
        echo "❌ Work tree for branch '$branch_name' does not exist"
        return 1
    fi
}

Step 20: Installing and Using the Functions

  1. Add to your shell configuration:

    # Add the functions to your ~/.zshrc or ~/.bashrc
    echo 'source ~/.git-worktree-functions.sh' >> ~/.zshrc
    
    # Or copy the functions directly to your shell config file
  2. Create a dedicated functions file (recommended):

    # Create a dedicated file for Git work tree functions
    cp ~/.zshrc ~/.zshrc.backup
    echo 'source ~/.git-worktree-functions.sh' >> ~/.zshrc
    
    # Reload your shell configuration
    source ~/.zshrc
  3. Usage examples:

    # Create a new feature branch with work tree
    add-wt feature-login
    
    # Navigate to an existing work tree
    go-wt feature-login
    
    # List all work trees
    list-wt
    
    # Remove a work tree when done
    remove-wt feature-login

Step 21: Advanced Function Features

The functions include several advanced features:

  • Automatic branch creation: Creates the branch if it doesn’t exist
  • Smart path detection: Automatically determines work tree paths
  • Conflict prevention: Checks for existing work trees before creation
  • Status information: Shows current branch and path information
  • Error handling: Provides helpful error messages and suggestions
  • Safety checks: Prevents accidental deletion of current work tree

Step 22: Function Aliases and Shortcuts

For even faster access, add these aliases:

# Add these aliases to your shell configuration
alias wt='add-wt'           # Quick work tree creation
alias gwt='go-wt'           # Quick work tree navigation
alias lwt='list-wt'         # Quick work tree listing
alias rwt='remove-wt'       # Quick work tree removal

# Example usage with aliases
wt login-feature    # Same as add-wt login-feature
gwt login-feature   # Same as go-wt login-feature
lwt                 # Same as list-wt
rwt login-feature   # Same as remove-wt login-feature

Best Practices Summary

Do’s:

  • ✅ Use descriptive names for work tree directories
  • ✅ Keep work trees organized in a dedicated folder
  • ✅ Regularly prune orphaned work trees
  • ✅ Use work trees for parallel development
  • ✅ Clean up work trees when done

Don’ts:

  • ❌ Don’t modify work tree .git files manually
  • ❌ Don’t create too many work trees (Git has limits)
  • ❌ Don’t forget to remove work trees when done
  • ❌ Don’t work directly in work tree directories for long periods

Performance Considerations:

  • Work trees consume disk space
  • Each work tree has its own Git metadata
  • Consider using temporary work trees for short tasks
  • Monitor disk usage with many work trees

Conclusion

Git work trees provide a powerful way to work with multiple branches simultaneously. By following this step-by-step guide, you can:

  1. Start Simple: Begin with basic work tree creation
  2. Scale Up: Gradually add more complex workflows
  3. Maintain: Keep your work trees organized and clean
  4. Automate: Use scripts and hooks for efficiency

Remember that work trees are tools to enhance your workflow, not replace good Git practices. Use them when they make sense for your development process, and always clean up when you’re done.

Additional Resources


This guide assumes all repositories are in a ~/GitHub/ folder structure. Adjust paths according to your actual setup.