Zero-friction git worktree manager
Project description
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?
- Quick Start
- Tutorial: Your First Worktree
- How-To Guides
- Configuration
- Hooks System
- Advanced Topics
- Troubleshooting
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_NAMEWT_BRANCH_NAME,WT_SOURCE_BRANCHWT_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 preventwt 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.jsondoesn'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/
- Repo at
- Base branch:
origin/main - Update strategy:
rebase - Protected branches:
["main"]
Hooks System
Hooks run in two phases:
- Local hooks first: All executable files from
<repo>/.wt/hooks/post_create.d/(sorted alphabetically) - 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
.bator.cmdfile instead, or ensure you have Git Bash installed to run.shscripts.
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]
wtuses 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 fetchper 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 -rfor file explorer) leaves stale git entries. Always usewt rminstead.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a5228c6dc9671872059d669e1c484f141ec62640210862a037648c9f4716e5c8
|
|
| MD5 |
c0d9dc74ce650ed7a5fb017486a48c25
|
|
| BLAKE2b-256 |
880e324f57242f6aae4c2b0c4e548ba3751925c4c9892c512724b65a7838b8ff
|
Provenance
The following attestation bundles were made for wt_cli-0.1.1.tar.gz:
Publisher:
publish.yml on tdhopper/wt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wt_cli-0.1.1.tar.gz -
Subject digest:
a5228c6dc9671872059d669e1c484f141ec62640210862a037648c9f4716e5c8 - Sigstore transparency entry: 585950023
- Sigstore integration time:
-
Permalink:
tdhopper/wt@1d9841537d0b19798753b3713b72c94378583042 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/tdhopper
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1d9841537d0b19798753b3713b72c94378583042 -
Trigger Event:
workflow_dispatch
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
219edc66c5e8fffb705baf1fc3ec54d4b0466c4681b2f99604600582c91dec15
|
|
| MD5 |
689bbabb1884ef10cf7b977166996565
|
|
| BLAKE2b-256 |
c9a91317ef53e124ced49612e1462e87218884f2361ee5bac7ea09cf9826c14f
|
Provenance
The following attestation bundles were made for wt_cli-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on tdhopper/wt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wt_cli-0.1.1-py3-none-any.whl -
Subject digest:
219edc66c5e8fffb705baf1fc3ec54d4b0466c4681b2f99604600582c91dec15 - Sigstore transparency entry: 585950040
- Sigstore integration time:
-
Permalink:
tdhopper/wt@1d9841537d0b19798753b3713b72c94378583042 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/tdhopper
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1d9841537d0b19798753b3713b72c94378583042 -
Trigger Event:
workflow_dispatch
-
Statement type: