A GitButler-inspired workflow using native git worktrees, allowing you to work on multiple features simultaneously while keeping changes organized in a dedicated staging branch.
- Modernized git workflow: Make changes and then decide which worktree/branch to assign them to
- Uses standard git commands: Be confident that your changes are safe
- Dedicated staging branch: All work happens in
ww-working, keepingmainclean - Interactive file selection: Use fzf for visual multi-select file assignment
- File-level assignment: Selectively assign files to different worktrees
- Directory assignment: Assign all changed files in a directory or all files at once
- Selective staging: Stage specific files in worktrees before committing
- Smart commits: Automatically detects staged files and commits accordingly
- Commit tracking: Track which commits have been applied between branches
- Safe operations: Work in isolation without affecting your main branch
- Simple CLI: Single
wwcommand with intuitive subcommands - Simplified worktree creation:
ww create <branch>- branch name is the worktree name
- Git 2.5 or higher
- jq (JSON processor)
- Bash 4+
- fzf (fuzzy finder) - For interactive file selection
- gh (GitHub CLI) - Optional, for PR links in status and
ww prcommand
curl -fsSL https://raw.githubusercontent.com/richcorbs/ww/main/install.sh | bashThe installer will check dependencies, download ww to ~/.local/share/ww, and create a symlink in ~/.local/bin.
# Clone the repository
git clone https://github.com/richcorbs/ww.git
cd ww
# Run the installer (symlinks to your clone)
./install.shcurl -fsSL https://raw.githubusercontent.com/richcorbs/ww/main/uninstall.sh | bash# 1. Initialize in your repository
cd your-repo
ww init
# This creates ww-working branch and checks it out
# 2. Make some changes
# ... edit files ...
# 3. Check status
ww status
# Output:
# Working in: ww-working
#
# Unassigned changes:
# M app/models/user.rb
# A app/controllers/sessions_controller.rb
# A app/controllers/passwords_controller.rb
#
# Worktrees:
# (none)
#
# Use 'ww create <branch>' to create a worktree
# 4. Create a worktree for a feature
ww create feature/user-auth
# 5. Assign files to the worktree (opens interactive fzf selector)
ww assign feature/user-auth
# OR assign files directly:
# ww assign feature/user-auth app/models/user.rb # Single file
# ww assign feature/user-auth app/controllers/ # Directory
# ww assign feature/user-auth . # All files
# 6. Review and stage changes (optional)
ww diff feature/user-auth
# Navigate files with arrow keys, see diff preview
# Press 's' to toggle stage/unstage, enter/esc to exit
# 7. Commit changes in the worktree
ww commit feature/user-auth "Add user authentication"
# 8. Push the feature branch
ww push feature/user-auth
# 9. Create pull request
ww pr feature/user-auth # Opens GitHub PR creation page in browser
# 10. After PR is merged to main, update ww-working
ww update
# This merges main into ww-working and automatically cleans up merged worktreesInstead of working directly in main, all your work happens in a dedicated ww-working branch:
- Initialize:
ww initcreates and checks outww-working - Work: Make all changes in
ww-workingbut you don't have to. You can still checkout and branch off ofmainif you need to. - Assign:
ww-workingstays in sync when files are assigned to worktrees - Worktrees:
wwautomatically branches off ofww-workingfor you - Merge: When features are done, merge to
mainvia normal git PR workflow - Update: Use
ww updateto mergemainback intoww-workingand cleanup your local branches and worktrees
This keeps your main branch pristine while giving you a flexible staging area.
your-repo/
.worktrees/ ← worktrees (gitignored)
feature-auth/
bugfix-123/
.worktree-flow/ ← metadata (gitignored)
metadata.json
abbreviations.json
app/
...
Initialize worktree workflow in the current repository.
ww initThis:
- Creates and checks out
ww-workingbranch - Creates
.worktree-flow/directory for metadata (gitignored) - Creates
.worktrees/directory (gitignored) - Adds entries to
.gitignore
Show unassigned changes and the status of each worktree.
ww statusOutput:
Working in: ww-working
Unassigned changes:
ab M app/models/user.rb
cd A app/controllers/sessions_controller.rb
ef ? config/routes.rb
Worktrees:
feature-auth (feature/user-auth) - 2 uncommitted, 1 commit(s)
PR #123: https://github.com/user/repo/pull/123
gh M app/models/user.rb
ij A app/services/auth_service.rb
bugfix-login (bugfix/login-issue) - 0 uncommitted, 0 commit(s)
Shows:
- Unassigned changes with two-letter abbreviations for making assignments easy
- Worktree status with commit counts and unstaged changes
- Associated PR links (requires GitHub CLI)
- Uncommitted files in each worktree with git status codes
Switch between branches. If no branch is specified, toggles between ww-working and main.
# Toggle between ww-working and main
ww switch
# Switch to a specific branch
ww switch developThis is a convenient shortcut for git checkout with smart defaults:
- If on
ww-working: switches tomain - If on any other branch: switches to
ww-working
Create a new worktree branching from ww-working. The branch name is used as the worktree name.
ww create feature/user-auth # Creates .worktrees/feature/user-auth/
ww create bugfix/issue-123 # Creates .worktrees/bugfix/issue-123/Arguments:
branch: Branch name (also used as worktree name)
List all worktrees with their status.
ww listAssign files to a worktree. Opens fzf for interactive multi-select if no file is specified.
# Interactive selection with fzf (recommended)
ww assign feature/user-auth
# Or specify files directly:
ww assign feature/user-auth app/models/user.rb # Single file
ww assign feature/user-auth app/models/ # Directory
ww assign feature/user-auth . # All filesWhat happens:
- Files remain in
ww-working(committed) - Changes are copied to the worktree (uncommitted)
- Files are removed from "unassigned" list
Review diffs in a worktree with interactive preview and stage/unstage toggle.
ww diff # Select worktree, then review diffs
ww diff feature/user-auth # Review diffs in specific worktreeFeatures:
- Navigate files with arrow keys
- See diff preview on the right (or file contents for new files)
- Press
sto toggle stage/unstage - Press
enterorescto exit - Header shows file count and staged count
Commit changes in a worktree with interactive file selection.
ww commit feature/user-auth # Select files, then enter message
ww commit feature/user-auth "Add user authentication" # Select files, message pre-filledFeatures:
- Always opens fzf to select files to commit
- Staged files shown with
[S]indicator - Message argument pre-fills the commit message prompt
- Use
ww diffbeforehand to stage specific files
Uncommit the last commit in a worktree (brings changes back to uncommitted).
ww uncommit feature/user-authUnassign file(s) from a worktree - reverts the commit in ww-working and removes changes from the worktree.
ww unassign feature/user-auth app/models/user.rb # Single file
ww unassign feature/user-auth . # All assigned filesThe file(s) will show up as "unassigned" again.
Apply (cherry-pick) commits from a worktree to ww-working. This means that all of the code will be available for further development or testing in ww-working.
ww apply feature-authUnapply (revert) commits that were applied from a worktree. This means that you effectively remove the worktree changeset from the ww-working branch and those changes are no longer available for further development or testing in ww-working. You can add them back to ww-working with ww apply <worktree>.
ww unapply feature-authPush a worktree's branch to the remote.
ww push feature-authOpen the GitHub PR creation page for a worktree's branch in your browser. If the branch hasn't been pushed yet, ww will push it automatically.
ww pr feature-authThis command:
- Checks if the branch is pushed to origin (pushes if not)
- Detects the worktree's branch name
- Constructs the GitHub PR creation URL
- Opens it in your default browser
Works with both HTTPS and SSH remote URLs.
Update ww-working with another branch (default: main). Automatically detects and cleans up worktrees whose branches have been merged.
ww update # Update from main and clean up merged worktrees
ww update develop # Update from developWhat it does:
- Fetches latest changes from origin
- Updates local branch from origin
- Merges branch into
ww-working - Automatically detects worktrees with merged branches
- Removes merged worktrees
- Deletes corresponding remote branches
This is now a one-stop command for staying in sync and cleaning up finished work.
Remove a worktree and clean up metadata.
ww remove feature-auth
ww remove bugfix --force# Start fresh
$ ww init
✓ Created .worktree-flow directory
✓ Created .worktrees directory
✓ Updated .gitignore
✓ Created ww-working branch
✓ Worktree workflow initialized
# Create feature worktree
$ ww create feature/auth
✓ Created worktree 'feature/auth' at .worktrees/feature/auth
✓ Branched from ww-working as feature/auth
# Make changes in ww-working
# ... edit files ...
$ ww status
Working in: ww-working
Unassigned changes:
M app/models/user.rb
A app/controllers/auth_controller.rb
Worktrees:
feature/auth (feature/auth) - 0 uncommitted, 0 commit(s)
# Assign files interactively with fzf
$ ww assign feature/auth
Select files to assign (TAB to select, ENTER to confirm)...
# fzf opens, select both files
✓ Assigned 2 file(s) to 'feature/auth' and committed to ww-working
# Commit in worktree
$ ww commit feature/auth "Add authentication"
No files staged, auto-staging all changes...
✓ Committed changes in 'feature/auth'
# Push and create PR
$ ww push feature/auth
✓ Pushed branch 'feature/auth' to origin
$ ww pr feature/auth
Opening PR creation page for branch 'feature/auth'...
URL: https://github.com/user/repo/compare/main...feature/auth?expand=1
✓ PR page opened for 'feature/auth'
# After PR is merged to main, update
$ ww update
Updating ww-working with 'main'...
Fetching latest changes from origin...
Updating local main from origin/main...
✓ Successfully updated ww-working with 'main'
Merge commit: a1b2c3d
Checking for merged branches...
Branch 'feature/auth' has been merged into main
Removing worktree 'feature/auth'...
Deleting remote branch 'feature/auth'...
✓ Cleaned up 'feature/auth'
✓ Cleaned up 1 merged worktree(s)ww create feature/auth
ww create feature/api-refactor
# Make changes
# ... edit multiple files ...
# Assign to different features using fzf
ww assign feature/auth # Select auth-related files
ww assign feature/api-refactor # Select API files
# Commit separately
ww commit feature/auth "Add authentication"
ww commit feature/api-refactor "Refactor API base"
# Push and create PRs
ww pr feature/auth
ww pr feature/api-refactorww create feature/model-refactor
# Make changes to multiple files in app/models/
# ... edit files ...
# Assign entire directory
ww assign feature/model-refactor app/models/
# Commit
ww commit feature/model-refactor "Refactor models"ww create feature/multi-part
# Assign files interactively
ww assign feature/multi-part
# Stage and commit only specific files
ww stage feature/multi-part app/models/user.rb
ww commit feature/multi-part "Add user model"
# Stage and commit remaining files
ww stage feature/multi-part .
ww commit feature/multi-part "Add controllers and views"Or commit everything at once without staging:
# If no files are staged, commit auto-stages all changes
ww commit feature/multi-part "Implement complete feature"# Assigned wrong file
ww unassign feature/user-auth app/models/admin.rb
# Committed too early in worktree
ww uncommit feature/user-auth
# Unassign all files from a worktree
ww unassign feature/user-auth .# Your feature branches got merged to main on GitHub
# Just run update - it handles everything automatically
ww update
# Output:
# ✓ Successfully updated ww-working with 'main'
# ✓ Branch 'feature/user-auth' has been merged into main
# - Removing worktree 'auth'...
# - Deleting remote branch 'feature/user-auth'...
# ✓ Cleaned up 'auth'
# ✓ Cleaned up 1 merged worktree(s)
# Done! ww-working is updated and merged work is cleaned up
# Continue working on new features- Always work in
ww-working- Don't make changes inmain - Use
ww switchto quickly toggle betweenww-workingandmain - Run
ww statusoften to see your abbreviations, worktree state, and uncommitted files - Use directory assignment for bulk file operations:
ww assign app/models feature-x - Use selective staging for multi-part commits:
ww stage <worktree> <file>thenww commit - Skip staging for quick commits -
ww commitauto-stages everything if nothing is staged - Update regularly after merging features to main:
ww update - Use
ww unassignto correct assignment mistakes - Commit small, logical changes in worktrees for clearer history
- Use abbreviated commands for speed:
ww as(assign),ww ap(apply),ww cr(create)
Make sure the installation directory is in your PATH:
export PATH="$HOME/.local/bin:$PATH"Add this to your ~/.bashrc or ~/.zshrc.
ww status will warn you if you're not on ww-working. Switch back with:
git checkout ww-workingThis can happen if files have diverged. Try:
- Committing changes in the worktree first
- Using
ww applyif needed - Resolving any conflicts
When running ww update, you may encounter merge conflicts. Resolve them normally:
# Fix conflicts in files
git add .
git commitSimilar:
- Work in one staging area
- Selectively assign files to branches
- Multiple features in parallel
- Virtual branch concept (ww-working)
Different:
- Uses native git worktrees
- Explicit commit on assign (no hidden commits)
- Two-letter abbreviations for file selection
- Pure bash (no Rust/Tauri)
- File/directory-level (not hunk-level) assignment
- Dedicated staging branch instead of virtual layer
MIT
Contributions welcome! The code is organized as:
bin/ww- Main dispatchercommands/*.sh- Subcommand implementationslib/ww-lib.sh- Shared library functionslib/abbreviations.sh- Abbreviation generation
To hack on wW:
cd ~/Code/wW
# Edit files
# ... make changes ...
# Run tests (one command - fast and local)
./run-tests.sh
# Test without installing
./bin/ww status
# When ready, run install
./install.shRun the test suite with a single command:
./run-tests.sh- ✅ Fast: All tests run locally in ~2 seconds
- ✅ Isolated: Creates fresh test repos for each test
- ✅ Auto-cleanup: Removes test artifacts automatically
- ✅ No setup: Just run the script
Requirements: git and jq (the script will check and tell you if anything is missing)