Supercharge Your Workflow with Git Worktrees
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:
- I maintain a bare repository with
.gitconfigured as the central git database (no working files) - My
mainbranch lives in a directory namedmain - 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!