A small CLI to manage task-based Git worktrees with predictable naming and cleanup flows.
# Install latest release (run from your target folder)cd$HOME/.local/bin
curl -fsSL https://raw.githubusercontent.com/pi2pie/git-worktree-tasks/main/scripts/install.sh | bash
# Create a task worktree (defaults to current branch)
gwtt create "my-feature"# List all worktrees
gwtt list
# Show status
gwtt status
# Cleanup when done
gwtt cleanup "my-feature"- Installation
- Binary Naming and Shell Configuration
- Configuration
- Usage Guide
- Output Formats & Piping
- Development
- Troubleshooting
- License
curl -fsSL https://raw.githubusercontent.com/pi2pie/git-worktree-tasks/main/scripts/install.sh | bash
# Or with wget:
wget -qO- https://raw.githubusercontent.com/pi2pie/git-worktree-tasks/main/scripts/install.sh | bashRun from the directory where you want gwtt to live:
cd$HOME/.local/bin
curl -fsSL https://raw.githubusercontent.com/pi2pie/git-worktree-tasks/main/scripts/install.sh | bashCustom install directory (default is current folder):
curl -fsSL https://raw.githubusercontent.com/pi2pie/git-worktree-tasks/main/scripts/install.sh | bash -s -- ~/.local/binmake installNote: make install installs into the current directory (the repo root if you run it here).
Directly from the repo:
./scripts/install.shgo install github.com/pi2pie/git-worktree-tasks@latestNote:
go installcreatesgit-worktree-tasks. Release assets installgwttand can optionally create agit-worktree-taskssymlink.
make build
# Binaries in dist/./scripts/uninstall.sh
# Or with Makefile:
make uninstallIf the binary is not in the current directory, pass the install path:
./scripts/uninstall.sh $HOME/.local/binGo install removal:
./scripts/go-uninstall.sh
# Or:
make go-uninstall- curl or wget for release install
- tar/unzip for release install archives
- sha256sum or shasum for release checksum verification
- Go 1.25.5+ for building from source
$GOPATH/binin$PATHforgo-installtargets
Windows PATH Note: If you install
gwtt.exeinto a custom folder (e.g.,C:\Users\<you>\bin), add that folder to your PATH and open a new terminal to pick it up.
Note
Ownership Change (v0.0.7+)
The repository ownership changed after v0.0.6. The old dev-pi2pie path no longer exists, so use the new module path for all installs and imports:
- v0.0.7 and later:
github.com/pi2pie/git-worktree-tasks
- Release assets ship the
gwttbinary. go installproducesgit-worktree-tasks.- If you prefer
git-worktree-tasks, create an alias or symlink manually.
Set up the gwtt alias for convenience:
| Shell | Config File | Alias Syntax |
|---|---|---|
| Bash | ~/.bashrc | alias gwtt="git-worktree-tasks" |
| Zsh | ~/.zshrc | alias gwtt="git-worktree-tasks" |
| Fish | ~/.config/fish/config.fish | alias gwtt git-worktree-tasks |
After adding, reload your shell (source ~/.bashrc, source ~/.zshrc, or exec fish).
Alternative: Create a symlink:
ln -s $(which git-worktree-tasks)$(dirname $(which git-worktree-tasks))/gwttSettings resolve in this order (highest precedence first):
- CLI flags (for example
--theme,--mode/-m,--mask-sensitive-paths,--no-mask-sensitive-paths) - Environment variables
- Project config (
gwtt.config.tomlorgwtt.tomlin repo root) - User config (
$HOME/.config/gwtt/config.toml) - Built-in defaults
# Environment variableexport GWTT_THEME=nord
# Disable color outputexport GWTT_COLOR=0
# Mode selectionexport GWTT_MODE=codex
# Dry-run path masking (1/true/on/yes to enable, 0/false/off/no to disable)export GWTT_DRY_RUN_MASK_SENSITIVE_PATHS=1
# List available themes
gwtt --themes[theme]
name = "nord"mode = "classic"# or "codex"Common defaults you can set once:
[ui]
color_enabled = true
[table]
grid = false
[dry_run]
mask_sensitive_paths = true# mask $HOME/%USERPROFILE% prefixes in --dry-run output
[list]
output = "table"field = "path"absolute_path = falsegrid = falsestrict = false
[status]
output = "table"absolute_path = falsegrid = falsestrict = false
[finish]
merge_mode = "ff"# ff, no-ff, squash, rebaseconfirm = true# set false to bypass prompts (same as --yes)cleanup = falseremove_worktree = falseremove_branch = falseforce_branch = false
[cleanup]
confirm = true# set false to bypass prompts (same as --yes)remove_worktree = trueremove_branch = trueworktree_only = falseforce_branch = false[dry_run].mask_sensitive_paths defaults to true. Set it to false if you need raw absolute paths in --dry-run output.
When enabled, home-prefixed paths are rendered as $HOME/... on POSIX and %USERPROFILE%\\... on Windows.
You can override this per-invocation with --mask-sensitive-paths=true|false, --no-mask-sensitive-paths, or via GWTT_DRY_RUN_MASK_SENSITIVE_PATHS.
For bool flags, prefer --mask-sensitive-paths=false (with =) rather than --mask-sensitive-paths false.
Project: gwtt.config.toml or gwtt.toml in the repo root
User: $HOME/.config/gwtt/config.toml
Minimal config:
[theme]
name = "nord"| Command | Alias | Description |
|---|---|---|
apply | Apply non-destructive changes between Codex worktree and local checkout (codex mode only) | |
overwrite | Destructively replace destination with source changes in codex mode | |
create | Create a worktree and branch for a task | |
list | ls | List task worktrees |
status | Show detailed worktree status | |
finish | Merge a task branch into target | |
cleanup | rm | Remove a task worktree and/or branch |
# Basic usage (defaults to current branch)
gwtt create "my-task"# Explicit base override
gwtt create "my-task" --base main
# Reuse existing worktree (no error if exists)
gwtt create "my-task" --skip-existing
# Custom path
gwtt create "my-task" --path ../custom-location
# Preview without executing
gwtt create "my-task" --dry-runFlags:
| Flag | Short | Description |
|---|---|---|
--base | Base branch to create from (default: current branch) | |
--path | -p | Override worktree path |
--output | -o | Output format: text, raw |
--skip-existing | --skip | Reuse existing worktree |
--dry-run | Show git commands without executing |
Notes:
- The default base is the current local branch (for example
main,master, ordev). - If you are in a detached HEAD state, you must pass
--baseexplicitly.
# List all worktrees
gwtt list
# Filter by task name (contains match)
gwtt list "my-task"# Exact match
gwtt list "my-task" --strict
# Filter by branch
gwtt list --branch feature-branch
# Custom path layout still searchable by task name
gwtt create new-task -p ./.worktrees/new-task
gwtt list new-task -o raw
# Show absolute paths
gwtt list --abs
# Grid borders in table
gwtt list --grid
# Codex mode: list Codex-managed worktrees for this repo
gwtt --mode codex listFlags:
| Flag | Short | Description |
|---|---|---|
--output | -o | Format: table, json, csv, raw |
--field | -f | Raw output field: path, task, branch |
--branch | Filter by branch name | |
--absolute-path | --abs | Show absolute paths |
--strict | Require exact task match | |
--grid | Render table with grid borders |
Task lookup behavior (classic mode):
list <task>first resolves task names from<repo>_<task>paths.- If the path does not match that convention, task lookup falls back to branch-backed inference for non-main, non-detached worktrees.
--branchremains the explicit/authoritative branch filter.
Note
If you place worktrees under a nested path inside the main repo (for example, ./.worktrees/<task>), add that root path (for example, .worktrees/) to .gitignore in the main checkout.
# Show status of all worktrees
gwtt status
# Filter by task
gwtt status "my-task"# Compare against specific target branch
gwtt status --target main
# Filter by exact task name
gwtt status --task "my-task"# Codex mode: show Codex-managed worktree status
gwtt --mode codex statusStatus columns: Task, Branch, Path, Modified Time (RFC3339 UTC), Base, Target, Last Commit, Dirty, Ahead, Behind
Flags:
| Flag | Short | Description |
|---|---|---|
--output | -o | Format: table, json, csv |
--target | Target branch for ahead/behind comparison | |
--task | Filter by task name (enables strict match) | |
--branch | Filter by branch name | |
--absolute-path | --abs | Show absolute paths |
--strict | Require exact task match | |
--grid | Render table with grid borders |
Task lookup behavior (classic mode):
status <task>uses the same task resolution aslist <task>(path-first, then branch-backed fallback for eligible rows).--branchremains the explicit/authoritative branch filter.
# Merge task branch into target
gwtt finish "my-task" --target main
# Merge with cleanup (remove worktree + branch)
gwtt finish "my-task" --target main --cleanup
# Merge strategies
gwtt finish "my-task" --no-ff # No fast-forward
gwtt finish "my-task" --squash # Squash commits
gwtt finish "my-task" --rebase # Rebase before merge# Skip confirmation
gwtt finish "my-task" --cleanup --yesFlags:
| Flag | Description |
|---|---|
--target | Target branch (default: current branch) |
--cleanup | Remove worktree and branch after merge |
--remove-worktree | Remove only the worktree after merge |
--remove-branch | Remove only the branch after merge |
--force-branch | Force delete branch (-D instead of -d) |
--no-ff | Use --no-ff merge |
--squash | Use --squash merge |
--rebase | Rebase task branch onto target first |
--yes | Skip confirmation prompts |
--dry-run | Show git commands without executing |
# Non-destructive apply (default direction: worktree -> local)
gwtt --mode codex apply <opaque-id># Reverse non-destructive apply (local -> worktree)
gwtt --mode codex apply <opaque-id> --to worktree
# Destructive overwrite (requires confirmation unless --yes)
gwtt --mode codex overwrite <opaque-id> --to local
gwtt --mode codex overwrite <opaque-id> --to worktree --yes
# Compatibility alias for overwrite
gwtt --mode codex apply <opaque-id> --to worktree --force --yes
# Preview with structured plan + command echo
gwtt --mode codex apply <opaque-id> --dry-runNotes:
- In codex mode,
<opaque-id>is the directory directly under$CODEX_HOME/worktrees. applyis non-destructive and will not switch direction automatically on conflict.- On conflict,
applyexits with a next-step hint foroverwrite --to .... overwriteresets/cleans the destination before transfer and is destructive by design.--dry-runprintsplan,preflight, andactionssections, then echoes the underlying git/copy operations.
# Remove worktree and branch (with confirmation)
gwtt cleanup "my-task"# Remove only the worktree (keep branch)
gwtt cleanup "my-task" --worktree-only
# Force delete branch
gwtt cleanup "my-task" --force-branch
# Skip confirmation
gwtt cleanup "my-task" --yes
# Preview without executing
gwtt cleanup "my-task" --dry-run
# Codex mode: remove a Codex-managed worktree by opaque id
gwtt --mode codex cleanup <opaque-id>Flags:
| Flag | Description |
|---|---|
--remove-worktree | Remove the task worktree (default: true) |
--remove-branch | Remove the task branch (default: true) |
--worktree-only | Remove only worktree, keep branch |
--force-branch | Force delete branch (-D) |
--yes | Skip confirmation prompts |
--dry-run | Show git commands without executing |
The --output (-o) and --field (-f) flags enable powerful shell integrations.
| Format | Description | Available In |
|---|---|---|
table | Human-readable table (default) | list, status |
json | JSON array | list, status |
csv | CSV with headers | list, status |
raw | Single value, no decoration | create, list |
text | Styled text output (default) | create |
When using --output raw with list, specify which field to output:
| Field | Description |
|---|---|
path | Worktree path (default) |
task | Task name |
branch | Branch name |
# Change to task worktree directorycd"$(gwtt list my-task -o raw)"# Or using createcd"$(gwtt create my-task -o raw)"# Copy worktree path
gwtt list my-task -o raw | pbcopy # macOS
gwtt list my-task -o raw | xclip -selection clipboard # Linux# Copy task name
gwtt list my-task -o raw -f task | pbcopy
# Copy branch name
gwtt list my-task -o raw -f branch | pbcopy# Open worktree in VS Code
code "$(gwtt list my-task -o raw)"# Open Worktree in Zed
zed "$(gwtt list my-task -o raw)"# Open in Cursor
cursor "$(gwtt list my-task -o raw)"# Create and open in one command
code "$(gwtt create my-feature -o raw)"# List all branches as plain text
gwtt list -o json | jq -r '.[].branch'# Get paths for all worktrees
gwtt list -o json | jq -r '.[].path'# Filter dirty worktrees
gwtt status -o json | jq '.[] | select(.dirty == true)'# Count worktrees ahead of target
gwtt status -o json | jq '[.[] | select(.ahead > 0)] | length'# Fish: Create and cd to worktreefunctiongwtt-newset path (gwtt create $argv[1] -o raw)
and cd$path
end
# Bash/Zsh: Create and cd to worktreegwtt-new() {
local path
path=$(gwtt create "$1" -o raw)&&cd"$path"
}When using --output raw with list:
- If a matching worktree row exists (including custom path layouts), raw output uses that row.
- If no matching worktree exists but the branch does, raw output falls back to a synthetic main-worktree row.
- Fallback row output respects
--field:path-> main worktree pathbranch-> fallback branch nametask->-
- Requires either a task filter or
--branchflag. --branchremains the explicit/authoritative branch selector.
# Returns path even if no worktree exists (fallback to main repo)
gwtt list feature-branch -o raw
# Field-aware fallback output
gwtt list feature-branch -o raw -f branchmake build # Build binaries to dist/
make install # Install gwtt from latest release
make uninstall # Remove release install
make go-install # Install to $GOPATH/bin
make go-uninstall # Remove installed binaries
make clean # Remove dist/
make help# Show all targetsgo test ./...
golangci-lint run├── main.go # Entry point
├── cli/ # CLI command definitions
├── internal/ # Internal packages (config, git, worktree)
├── ui/ # UI/styling utilities
├── tui/ # Terminal UI components (preview)
├── examples/ # Example configs and shell functions
├── scripts/ # Installation scripts
├── docs/ # Documentation and plans
├── Makefile # Build targets
└── go.mod # Go module definition
# Check if it's in PATHecho$PATH| grep $(go env GOPATH)/bin
# Add to shell config if missingexport PATH="$(go env GOPATH)/bin:$PATH"# Use custom directory
./scripts/install.sh $HOME/.local/binReload your shell after adding the alias:
source~/.bashrc # Bashsource~/.zshrc # Zshexec fish # FishThis project is licensed under the MIT License — see the LICENSE file for details.
- Default worktree path:
../<repo>_<task> - Task names are slugified (lowercase, hyphens replace spaces)
- Paths are relative by default; use
--absfor absolute - Use
--dry-runto preview git commands - Global flags:
--mode(-m),--theme,--nocolor,--themes