Skip to main content

Zero-friction git worktree manager

Project description

wt logo

wt

Zero-friction git worktree manager

wt is a fast, deterministic CLI for creating and managing git worktrees without interactive prompts. Stop juggling branches in a single working directory - work on multiple features simultaneously with isolated, organized worktrees.

# Create a worktree for a new feature
wt new my-feature

# See what you're working on
wt status

# Keep everything in sync
wt pull-main --stash

# Clean up merged work
wt prune-merged --yes

Table of Contents


Why wt?

Git worktrees let you check out multiple branches simultaneously in separate directories. They're perfect for:

  • Working on multiple features without stashing or committing incomplete work
  • Running tests on one branch while developing on another
  • Reviewing PRs in isolation without disrupting your current work
  • Keeping long-running branches separate from daily work

But vanilla git worktrees are tedious: paths are manual, cleanup is forgotten, and there's no workflow automation.

wt fixes this: deterministic paths via templates, automatic cleanup, post-create hooks, and zero prompts for scripting.


Quick Start

Installation

[!IMPORTANT] Requires Python ≥3.11 (for native TOML support via tomllib)

# Install from source
uv tool install -e .

# Verify installation
wt doctor

Basic Usage

# Create a worktree for a new feature branch
wt new my-feature
# -> /Users/you/repos/myproject-worktrees/my-feature

# List all worktrees
wt list

# See status with tracking info
wt status --rich

# Jump to a worktree
cd $(wt where my-feature)

# Remove a worktree when done
wt rm my-feature --yes --delete-branch

Tutorial: Your First Worktree

Let's create a worktree for a new feature, make some changes, and clean up.

1. Create a worktree

cd ~/repos/myproject
wt new login-redesign

This creates a new branch login-redesign from origin/main and checks it out in a separate directory adjacent to your repo:

~/repos/myproject-worktrees/login-redesign

2. Work in the worktree

cd $(wt where login-redesign)
# Make your changes
git add .
git commit -m "Redesign login form"
git push -u origin login-redesign

Your main worktree is untouched - you can keep working there simultaneously.

3. Keep both worktrees in sync

# From anywhere in your repo
wt pull-main --stash

This fetches updates and rebases (or merges) all your worktrees with origin/main, stashing dirty changes automatically.

4. Clean up when merged

After your PR is merged:

wt prune-merged --yes --delete-branch

Automatically removes worktrees for merged branches and optionally deletes the local branches too.


How-To Guides

Set up auto-prefixing for branches

Add your username to all new branches automatically:

~/.config/wt/config.toml:

[branches]
auto_prefix = "alice/"

Now wt new feature creates branch alice/feature.

Customize worktree paths

Use template variables to organize worktrees:

~/repos/myproject/.wt/config.toml:

[paths]
worktree_root = "/Users/alice/work"
worktree_path_template = "$WT_ROOT/$REPO_NAME/$BRANCH_NAME"

Variables available:

  • $REPO_ROOT - Absolute path to main repo
  • $REPO_NAME - Repository directory name
  • $WT_ROOT - Resolved worktree root
  • $BRANCH_NAME - Branch name (with slashes for nesting)
  • $SOURCE_BRANCH - Branch this worktree was created from
  • $DATE_ISO / $TIME_ISO - Timestamps

[!TIP] Branch names containing slashes (e.g., feature/login) automatically create nested directories. Useful for organizing worktrees by category.

Run commands after creating worktrees

Example: Install dependencies automatically

Create ~/repos/myproject/.wt/hooks/post_create.d/01-install.sh:

#!/bin/bash
set -e

echo "Installing dependencies in $WT_WORKTREE_PATH..."
npm install

Make it executable:

chmod +x ~/repos/myproject/.wt/hooks/post_create.d/01-install.sh

Now every wt new runs this hook in the new worktree.

Hook environment variables:

  • WT_REPO_ROOT, WT_REPO_NAME
  • WT_BRANCH_NAME, WT_SOURCE_BRANCH
  • WT_WORKTREE_PATH
  • All template variables available

Use different update strategies

# Merge instead of rebase
wt pull-main --strategy merge

# Fast-forward only (fails on divergence)
wt pull-main --strategy ff-only

Set defaults in config:

[update]
base = "origin/develop"  # Use develop instead of main
strategy = "merge"       # Default to merge
auto_stash = true        # Always stash automatically

Protect important branches

Prevent accidental deletion during batch operations:

[prune]
protected = ["main", "develop", "staging"]
delete_branch_with_worktree = false

[!TIP] Protected branches only affect wt prune-merged (batch cleanup). They do not prevent wt rm <branch> (explicit removal), since explicitly naming a branch indicates intent.

Find and jump to worktrees

# Print path (useful for scripting)
wt where my-feature

# Jump to worktree
cd $(wt where my-feature)

# Open in editor
code $(wt where my-feature)

Recreate worktrees for existing branches

If you removed a worktree but kept the branch, you can recreate it:

# Previously: wt rm my-feature (without --delete-branch)
# Branch still exists, but no worktree

# Recreate the worktree for the existing branch
wt new my-feature

# This checks out the existing branch at a new worktree location

[!WARNING] You cannot create multiple worktrees for the same branch. Git prevents the same branch from being checked out in multiple locations simultaneously.

Make VS Code windows visually distinct

Enable automatic creation of VS Code settings to make each worktree window unique:

[vscode]
create_settings = true
color_borders = true     # Deterministic colored borders per branch
custom_title = true      # Show repo name + branch in title bar

When enabled, wt new creates .vscode/settings.json with:

  • Colored window borders - Each branch gets a unique color (generated from branch name hash)
  • Custom window title - Shows {repo_name} | {branch_name} instead of just the path

This makes it easy to visually distinguish between multiple VS Code windows when working across worktrees.

[!NOTE] Settings are only created if .vscode/settings.json doesn't already exist, so existing customizations are preserved.


Configuration

Configuration uses TOML with precedence: CLI args > local > global > defaults

Global: ~/.config/wt/config.toml (all repos) Local: <repo>/.wt/config.toml (specific repo)

Full Example

[paths]
worktree_root = "$REPO_ROOT/../$REPO_NAME-worktrees"
worktree_path_template = "$WT_ROOT/$BRANCH_NAME"

[branches]
auto_prefix = "alice/"

[hooks]
post_create_dir = "post_create.d"
continue_on_error = false
timeout_seconds = 300

[update]
base = "origin/main"
strategy = "rebase"  # or "merge", "ff-only"
auto_stash = false

[prune]
protected = ["main", "develop"]
delete_branch_with_worktree = false

[ui]
rich = false
json_indent = 2

[vscode]
create_settings = false
color_borders = true
custom_title = true

Default Behavior

  • Worktree location: Sibling directory to your repo
    • Repo at /Users/alice/repos/myproject
    • Worktrees at /Users/alice/repos/myproject-worktrees/
  • Base branch: origin/main
  • Update strategy: rebase
  • Protected branches: ["main"]

Hooks System

Hooks run in two phases:

  1. Local hooks first: All executable files from <repo>/.wt/hooks/post_create.d/ (sorted alphabetically)
  2. Global hooks second: All executable files from ~/.config/wt/hooks/post_create.d/ (sorted alphabetically)

This gives repo-specific hooks priority to run before global hooks.

Supported:

  • Shell scripts: *.sh
  • Python scripts: *.py
  • Any executable

Hook Environment

[!NOTE] All template variables are automatically injected as WT_* environment variables into your hooks.

#!/bin/bash
echo "Created worktree for $WT_BRANCH_NAME at $WT_WORKTREE_PATH"
echo "Repo: $WT_REPO_NAME ($WT_REPO_ROOT)"

Hook Examples

Install dependencies:

#!/bin/bash
# .wt/hooks/post_create.d/10-install.sh
npm install

Open worktree in editor:

#!/bin/bash
# ~/.config/wt/hooks/post_create.d/02_open.sh
cursor .

Make it executable:

chmod +x ~/.config/wt/hooks/post_create.d/02_open.sh

You can use any editor: code . for VS Code, nvim . for Neovim, etc.

[!TIP] On Windows, use a .bat or .cmd file instead, or ensure you have Git Bash installed to run .sh scripts.

Create feature branch tracking:

#!/usr/bin/env python3
# .wt/hooks/post_create.d/20-tracking.py
import subprocess
import os

branch = os.environ["WT_BRANCH_NAME"]
subprocess.run(["git", "push", "-u", "origin", branch], check=True)

Initialize database:

#!/bin/bash
# .wt/hooks/post_create.d/30-db.sh
docker-compose up -d db
sleep 2
./scripts/migrate.sh

Advanced Topics

Scripting with wt

All commands support JSON output and use exit codes:

# Get all worktrees as JSON
wt list --json | jq -r '.[] | select(.dirty) | .path'

# Find worktrees behind main
wt status --json | jq -r '.[] | select(.behind_main > 0) | .branch'

# Batch operations
for branch in $(wt list --json | jq -r '.[].branch'); do
  cd $(wt where $branch)
  npm test
done

Concurrent Safety

[!IMPORTANT] wt uses a repo-scoped advisory lock (.git/wt.lock) to prevent concurrent modifications. Operations are serialized per-repo but can run concurrently across different repos.

Windows Support

Fully supported. Uses PID-based locking on Windows instead of fcntl.

Performance

  • Single git fetch per operation (not per worktree)
  • Serial execution with minimal subprocess overhead
  • Stdlib-only, no external dependencies

Troubleshooting

Stale worktree entries

[!CAUTION] Manually deleting worktree directories (with rm -rf or file explorer) leaves stale git entries. Always use wt rm instead.

If you already manually deleted a worktree directory:

wt gc

Hooks timing out

Increase timeout in config:

[hooks]
timeout_seconds = 600

Wrong branch prefix

Check your auto_prefix config:

wt doctor

Worktree paths not found

Ensure paths don't contain special shell characters. Quote template variables if using spaces.


License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wt_cli-0.1.1.tar.gz (371.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wt_cli-0.1.1-py3-none-any.whl (25.5 kB view details)

Uploaded Python 3

File details

Details for the file wt_cli-0.1.1.tar.gz.

File metadata

  • Download URL: wt_cli-0.1.1.tar.gz
  • Upload date:
  • Size: 371.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for wt_cli-0.1.1.tar.gz
Algorithm Hash digest
SHA256 a5228c6dc9671872059d669e1c484f141ec62640210862a037648c9f4716e5c8
MD5 c0d9dc74ce650ed7a5fb017486a48c25
BLAKE2b-256 880e324f57242f6aae4c2b0c4e548ba3751925c4c9892c512724b65a7838b8ff

See more details on using hashes here.

Provenance

The following attestation bundles were made for wt_cli-0.1.1.tar.gz:

Publisher: publish.yml on tdhopper/wt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file wt_cli-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: wt_cli-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 25.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for wt_cli-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 219edc66c5e8fffb705baf1fc3ec54d4b0466c4681b2f99604600582c91dec15
MD5 689bbabb1884ef10cf7b977166996565
BLAKE2b-256 c9a91317ef53e124ced49612e1462e87218884f2361ee5bac7ea09cf9826c14f

See more details on using hashes here.

Provenance

The following attestation bundles were made for wt_cli-0.1.1-py3-none-any.whl:

Publisher: publish.yml on tdhopper/wt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page