Skip to content

Git Worktree Development

Git worktrees enable you to work on multiple branches simultaneously without constantly switching contexts. This is particularly useful for the Jubiloop monorepo where builds can take time and environment files need to be maintained.

What Are Git Worktrees?

Git worktrees allow you to have multiple working directories from the same repository, each checked out to different branches. Instead of switching branches in place (and potentially losing uncommitted work or having to rebuild), you can have separate directories for each feature you're working on.

Benefits for Jubiloop Development

  • Parallel Development: Work on multiple features simultaneously without context switching
  • Preserve Build Artifacts: Each worktree maintains its own node_modules and build outputs
  • Source Isolation: Each worktree has its own checkout and .env files
  • Quick Bug Fixes: Jump to a bugfix without stashing or committing work-in-progress
  • Testing Different Versions: Compare behavior across branches side-by-side
  • Faster Context Switching: Source and host build outputs remain in place; container dependencies may still need reinstalling when branches differ

Quick Start

Initial Setup

First, make the worktree scripts executable:

bash
./scripts/setup-scripts.sh

This will:

  • Check for required dependencies (git, pnpm)
  • Create the worktrees directory structure
  • Set executable permissions on all scripts
  • Display available commands

Creating Your First Worktree

Create a new worktree for a feature branch:

bash
./scripts/worktree.sh feature/sc-187-new-feature

This automatically:

  • Creates a new branch from develop when run in the main worktree; from a secondary worktree it defaults to that worktree's current branch
  • Sets up a worktree in ../jubiloop-worktrees/feature-sc-187-new-feature
  • Copies all .env files from the main worktree
  • Copies supported gitignored local editor and agent settings when present
  • Opens the worktree in your preferred editor

Worktree Management Scripts

worktree.sh - Create and Set Up Worktrees

Creates a new worktree with automatic environment configuration.

bash
./scripts/worktree.sh <branch-name> [options]

Options:

  • -b, --base <branch> - Base for a new branch. Defaults to develop in the main worktree and to the current branch in a secondary worktree
  • -e, --copy-env-from <worktree> - Copy .env files from specified worktree (default: main)
  • -n, --no-editor - Don't open the worktree in an editor
  • -i, --install - Run pnpm install after creating worktree
  • -o, --open-dir - Open worktree directory in file manager

For a new branch, the script fetches first and prefers origin/<base> when that remote branch exists; otherwise it uses the local base. The base option has no effect when opening an existing local or remote branch.

Examples:

bash
# Create feature branch from develop
./scripts/worktree.sh feature/sc-123-user-auth

# Create from specific base branch
./scripts/worktree.sh hotfix/critical -b main

# Create with pnpm install and open in file manager
./scripts/worktree.sh feature/new-ui -i -o

# Copy environment from another worktree
./scripts/worktree.sh feature/test -e feature/sc-123-user-auth

worktree-remove.sh - Safely Remove Worktrees

Removes worktrees with safety checks for uncommitted changes.

bash
./scripts/worktree-remove.sh <branch-name> [options]

Options:

  • -f, --force - Skip confirmation prompts (use with caution)

Safety Features:

  • Warns about uncommitted changes
  • Warns about unpushed commits
  • Asks for confirmation before deletion
  • Optionally deletes the associated branch
  • Cleans up orphaned worktree registrations

Example:

bash
# Remove with confirmations
./scripts/worktree-remove.sh feature/sc-123-user-auth

# Automated cleanup only after independently checking status and upstream commits
./scripts/worktree-remove.sh feature/old-feature -f

worktree-list.sh - View All Worktrees

Lists all worktrees with their status and configuration.

bash
./scripts/worktree-list.sh [options]

Options:

  • -v, --verbose - Show detailed environment file information

Information Displayed:

  • Branch name and location
  • Current worktree indicator
  • Clean/dirty status (uncommitted changes)
  • Environment file presence
  • Last commit information

Visual Indicators:

  • [MAIN] - Main worktree
  • ← YOU ARE HERE - Current worktree
  • - Present/clean
  • - Warning/partial
  • - Missing/error

Example:

bash
# Basic list
./scripts/worktree-list.sh

# Detailed view with env file sizes and line counts
./scripts/worktree-list.sh -v

worktree-switch.sh - Quick Switch Between Worktrees

Opens a different worktree in your editor without manual navigation.

bash
./scripts/worktree-switch.sh <branch-name>

Features:

  • Automatically opens in Zed or VS Code
  • Shows worktree status before switching
  • Displays current commit information
  • Lists available worktrees if branch not found

Example:

bash
# Switch to another worktree
./scripts/worktree-switch.sh develop

# Switch to main worktree
./scripts/worktree-switch.sh main

Common Workflows

Working on Multiple Features

When you need to work on several features simultaneously:

bash
# Create worktrees for each feature
./scripts/worktree.sh feature/sc-100-api-endpoints -i
./scripts/worktree.sh feature/sc-101-ui-components -i
./scripts/worktree.sh feature/sc-102-documentation

# List all worktrees to see status
./scripts/worktree-list.sh

# Switch between them as needed
./scripts/worktree-switch.sh feature/sc-100-api-endpoints

Emergency Hotfix

When you need to fix a critical bug while working on a feature:

bash
# Create hotfix worktree from main
./scripts/worktree.sh hotfix/critical-bug -b main -i

# After fixing and pushing
./scripts/worktree-remove.sh hotfix/critical-bug

# Return to your feature
./scripts/worktree-switch.sh feature/sc-100-api-endpoints

Testing Changes Across Branches

Compare behavior between branches one at a time with the shared Docker stack:

bash
# Set up worktrees for comparison
./scripts/worktree.sh develop -i
./scripts/worktree.sh feature/new-implementation -i

# Stop containers created from the current worktree without deleting volumes
pnpm run dev:stack:down

# Open the comparison worktree in your editor
./scripts/worktree-switch.sh feature/new-implementation

# In this terminal, explicitly enter that worktree before starting its stack
cd ../jubiloop-worktrees/feature-new-implementation
pnpm run dev:stack:up

worktree-switch.sh opens an editor and prints the destination path; it cannot change the current directory of the terminal that invoked it.

The Compose project uses fixed container names, host ports, and named volumes, so two full-stack worktrees cannot run at the same time. The PostgreSQL, Redis, and container node_modules volumes are shared. docker compose restart only restarts existing containers and does not change their source bind mount; stop and recreate the containers when changing worktrees.

Code Review Preparation

Review code locally before creating a PR:

bash
# Create worktree for the branch to review
./scripts/worktree.sh feature/sc-200-colleague-branch

# Open in editor to review
# The worktree is automatically opened

# Clean up after review
./scripts/worktree-remove.sh feature/sc-200-colleague-branch

Environment File Management

The scripts automatically handle environment files to ensure each worktree is ready to run:

Automatic Copying

When creating a worktree, the following files are copied:

  • apps/server/.env
  • apps/webapp/.env
  • apps/marketing/.env

Local Configurations

The script copies these gitignored machine-local files when present:

  • .claude/settings.local.json
  • .zed/settings.json
  • .cursor/rules/better_auth.mdc

Committed editor and agent files already come from the checked-out branch and are not overwritten.

HTTPS Certificates

The generated infra/local_dev/certs/ directory is gitignored and is not copied by the worktree script. Before running Docker in a new worktree, run its infra/local_dev/setup-https.sh or copy the certificate directory from a trusted local worktree. The mkcert CA trust is machine-wide, but each worktree's Caddy container bind-mounts certificate files from that worktree.

Fallback Behavior

If .env files don't exist in the source:

  • Falls back to .env.example files
  • Warns you about missing configurations
  • Creates basic .env from examples

Directory Structure

Worktrees are organized in a sibling directory to keep the main repository clean:

parent-directory/
├── jubiloop/                    # Main repository
│   ├── apps/
│   ├── packages/
│   └── scripts/
│       ├── worktree.sh
│       ├── worktree-remove.sh
│       ├── worktree-list.sh
│       └── worktree-switch.sh

└── jubiloop-worktrees/          # All worktrees
    ├── feature-sc-100-api/      # Each branch gets its own directory
    ├── feature-sc-101-ui/       # Branch name with slashes replaced
    └── hotfix-critical-bug/     # Completely isolated workspace

Best Practices

When to Use Worktrees

Good Use Cases:

  • Working on long-running features
  • Reviewing colleague's code
  • Testing changes that require different configurations
  • Emergency fixes while preserving work in progress
  • Comparing behavior between versions

When NOT to Use:

  • Quick one-line fixes (just use regular branch switching)
  • When disk space is very limited
  • For branches you'll only visit once briefly

Naming Conventions

Follow the standard branch naming:

  • feature/sc-XXX-description for features
  • bugfix/sc-XXX-description for bugs
  • hotfix/description for urgent fixes

The scripts automatically sanitize branch names for filesystem compatibility.

Maintenance

Regular Cleanup:

bash
# List worktrees to see what's active
./scripts/worktree-list.sh

# Remove completed feature worktrees
./scripts/worktree-remove.sh feature/completed-feature

# Inspect registrations; use the project removal script for cleanup
./scripts/worktree-list.sh -v

Disk Space: Each worktree includes:

  • Full copy of source code
  • Separate node_modules (can be 1-2GB)
  • Build artifacts

Consider removing unused worktrees to save space.

Troubleshooting

"Worktree already exists"

If you get this error, the worktree directory already exists:

bash
# Check existing worktrees
./scripts/worktree-list.sh

# Remove if needed
./scripts/worktree-remove.sh <branch-name>

Missing Environment Files

If .env files aren't copied:

bash
# Manually copy from main worktree
cp ../jubiloop/apps/server/.env ../jubiloop-worktrees/your-branch/apps/server/.env

Editor Not Opening

If the editor doesn't open automatically:

bash
# Check installed editors
which zed
which code

# Manually open
code ../jubiloop-worktrees/your-branch

Orphaned Worktrees

If a worktree directory was manually deleted:

bash
# Use the project script; it cleans a missing registered path when it can match the branch
./scripts/worktree-remove.sh <branch-name>

The removal script's checks have blind spots: git diff-index does not report every untracked file, and a branch without an upstream can make the unpushed-commit check inconclusive. Before confirming removal, run git status --short in the target worktree and verify its upstream and commits. Avoid --force unless losing the directory contents is intentional.

The script also does not stop the shared Docker stack or determine which worktree its bind mounts reference. If the target worktree owns the running stack, run pnpm run dev:stack:down from that worktree before removal. This preserves the shared named data volumes while preventing containers from retaining mounts to a removed directory.

Branch Conflicts

If the branch already exists:

bash
# The script will use the existing branch
./scripts/worktree.sh existing-branch

# Or create with a different name
./scripts/worktree.sh feature/sc-XXX-alternative

Advanced Usage

Custom Base Branches

Create worktrees from any branch or commit:

bash
# From a specific remote branch
./scripts/worktree.sh feature/new -b origin/experimental

# From the current branch (the default when run inside a secondary worktree)
cd ../jubiloop-worktrees/feature-base
./scripts/worktree.sh feature/extension

Environment Variations

Test with different configurations:

bash
# Create worktree
./scripts/worktree.sh feature/test-config

# Modify environment for this specific worktree
cd ../jubiloop-worktrees/feature-test-config
vim apps/server/.env  # Edit for different API endpoint

Parallel Testing

Use separate worktrees for repeatable comparisons, but run the shared full stack from only one at a time:

bash
# In the active worktree
pnpm run dev:stack:down

# In the destination worktree; this recreates containers with its bind mounts
pnpm run dev:stack:up

For truly simultaneous comparisons, use host mode with explicit, non-conflicting ports for every app and dependency. The standard Docker configuration does not support that topology.

Tips and Tricks

  1. Tab Completion: Use tab completion with branch names for faster navigation

  2. Aliases: Add to your shell config:

    bash
    alias wt='./scripts/worktree.sh'
    alias wts='./scripts/worktree-switch.sh'
    alias wtl='./scripts/worktree-list.sh'
    alias wtr='./scripts/worktree-remove.sh'
  3. Quick Status Check: Run ./scripts/worktree-list.sh regularly to track active work

  4. Shared pnpm Store: pnpm's content-addressable store is shared between worktrees, speeding up installs

  5. Git Commands: Regular git commands work in any worktree:

    bash
    cd ../jubiloop-worktrees/feature-branch
    git add .
    git commit -m "[sc-123] implement feature"
    git push origin feature-branch

Summary

Git worktrees provide a powerful way to manage parallel development in the Jubiloop monorepo. The custom scripts handle the complexity of environment setup, making it easy to:

  • Work on multiple features without context switching
  • Preserve build state and configurations
  • Quickly respond to urgent fixes
  • Review code without disrupting your work

Use worktrees whenever you need to maintain multiple working states or when switching branches would be disruptive to your current work.


Related Documentation:

Built with ❤️ by the Jubiloop team