git-wtree
v0.6.0
Published
Git worktree manager with .env syncing and IDE integration
Readme
git-wtree
Git worktree manager with .env syncing and IDE integration
Streamline your git worktree workflow: create isolated branches, sync environment files, install dependencies, give each worktree its own editor color, and open your IDE — all in one command.
Install
npm install -g git-wtreeUsage
gitwtree <command>
# or
gwt <command>Shell integration (recommended)
Run this once — it enables gwt switch (jumping between worktrees) and, on zsh + oh-my-zsh, frees the gwt name from the git plugin's alias:
gitwtree shell-init --installIt detects your shell, writes a small block to your rc (~/.zshrc, ~/.bashrc, or ~/.config/fish/config.fish), and is idempotent — re-run it any time to update or repair the block. Then open a new terminal.
Why
gitwtreeand notgwt? oh-my-zsh's git plugin aliasesgwt(andgwta,gwtls, …) togit worktree, which shadows this CLI.gitwtreeis never aliased, so it always works; the block it installs clears those aliases and defines agwtfunction that wins. Ifgwtrunsgit worktree, you haven't run the integration yet — rungitwtree shell-init --install, or usegitwtreedirectly.
Manual alternative. If you'd rather not let a command edit your rc, append the block yourself (put it after any PATH setup, e.g. nvm/fnm, so gitwtree resolves) and open a new terminal:
gitwtree shell-init zsh >> ~/.zshrc # or: bash | fishBoth write a static block (no gitwtree call at shell startup), so it's robust regardless of where your PATH is configured. To remove it later: gitwtree shell-init --uninstall.
Commands
| Command | Description |
| ----------------------------------- | ------------------------------------------------------------- |
| gwt add <branch> [--from <base>] | Create a worktree, sync .env files, and run the setup hook |
| gwt pr <number> | Create a worktree from a GitHub pull request |
| gwt rm [branch] [--force] | Remove a worktree (picker if omitted; guards unsaved changes) |
| gwt ls | List all worktrees |
| gwt open [branch] | Open a worktree in your IDE (picker if omitted) |
| gwt switch [query] | cd to another worktree (needs the shell integration) |
| gwt shell-init [--install] | Install (or print) the shell integration; --uninstall too |
| gwt doctor | Diagnose the install (integration present + version, git) |
| gwt sync-env [query] [--apply] | Re-copy .env from main into a worktree (--all for every) |
| gwt config | Show current configuration |
| gwt config ide | Configure your IDE |
| gwt config scan-dirs [dirs] | Set directories to scan for .env files |
| gwt config setup [commands...] | Post-create commands (auto / none / custom) |
| gwt config teardown [commands...] | Pre-remove commands run in the worktree (none to clear) |
| gwt config theme [on\|off] | Toggle per-worktree VS Code color + window title |
| gwt config statusline [on\|off] | Toggle the Claude Code branch statusline |
| gwt help | Show help |
gwt add <branch>
Creates a git worktree for the given branch, copies .env files from the main repo, and runs the setup hook.
- If the branch doesn't exist, it's created from
HEADby default — use--fromto specify a different base. - If the branch already exists locally, it fetches the latest remote changes and resets to them (handles force-pushes cleanly).
gwt add my-feature # create from HEAD
gwt add my-feature --from production # create from production
gwt add codex/fix-bug # checkout existing branch, reset to remotegwt pr <number>
Creates a worktree from a GitHub pull request (then runs the same .env sync, setup hook, and theming as gwt add). The worktree lives at <repo>-pr-<number>.
gwt pr 1234 # specific PR
gwt pr # no number → pick from open PRs (arrow-key picker, requires gh)- If
ghis installed, it runsgh pr checkoutinside the worktree — you get the PR's real branch with push tracking (works for forks too), so you can push fixes back. - Otherwise it falls back to
git fetch origin pull/<number>/headinto a localpr-<number>branch (review only).
gwt open [branch]
Opens a worktree in your configured IDE. On first use, a wizard will prompt you to choose your IDE.
gwt open my-feature # substring match on branch or path
gwt open # no argument → arrow-key pickerWorktrees are resolved from git worktree list (the same source as gwt ls): the argument is matched as a substring of the branch or path, and you get a picker when it's ambiguous or omitted. The same resolution powers gwt switch and gwt rm, so a PR worktree (dir repo-pr-<n>, different branch) is found whether you pass its branch, pr-<n>, or pick it.
To reconfigure your IDE at any time:
gwt config idegwt switch — jump between worktrees
gwt switch [query] changes your shell's directory to another worktree. Because a binary can't change its parent shell's working directory, this needs the shell function installed by Shell integration (gitwtree shell-init --install). Once that's in place:
gwt switch my-feature # cd to the worktree whose branch matches "my-feature"
gwt switch # no query → arrow-key picker
gwt sw my-feature # aliasquery is a substring match on the branch name. If it matches exactly one worktree you go straight there; if it's ambiguous or omitted, you get an arrow-key picker (same style as the rest of the prompts). (gwt path [query] is the underlying primitive the wrapper calls — it resolves the worktree and writes the path back to the wrapper.)
gwt doctor
Checks the install: git availability, the gitwtree version, and whether the shell-integration block is present in your rc (and whether its version matches — if not, it tells you to re-run gitwtree shell-init --install).
gwt doctorOne thing it can't check: a binary can't inspect the parent shell, so it can't tell whether gwt currently resolves to the function or to oh-my-zsh's alias. Run type gwt yourself — "function" is good; "alias" means the integration isn't active in this shell.
.env syncing
By default, gwt add recursively scans the repo for .env* files (excluding node_modules, .git, dist, etc.) and copies them into the new worktree.
To restrict scanning to specific directories:
gwt config scan-dirs apps/api,apps/webTo reset back to auto scan:
gwt config scan-dirs --resetRe-syncing after the fact
gwt add only copies .env files at creation time. When you refresh them on your main checkout (e.g. after pulling a new database), gwt sync-env re-copies them into existing worktrees. The main worktree is always the source (detected automatically, so it works from any worktree); it's never a target.
It is a dry run by default — it lists, per worktree, which files would be copied and which would overwrite an existing one — and only writes with --apply:
gwt sync-env # pick a worktree, preview the changes
gwt sync-env my-feature # preview for the worktree matching "my-feature"
gwt sync-env my-feature --apply # actually copy into that worktree
gwt sync-env --all # preview across every secondary worktree
gwt sync-env --all --apply # copy into all of themSetup & teardown hooks
gwt add runs a setup hook after creating the worktree, and gwt rm runs a teardown hook before removing it. Both run inside the worktree.
Setup defaults to auto: if a package.json is present it runs <package-manager> install (plus <pm> run prepare when that script exists); otherwise it does nothing — so non-Node repos stay untouched. Override it with your own commands for any stack:
gwt config setup # show current value
gwt config setup "bundle install" # Ruby
gwt config setup "go mod download" "make dev" # multiple commands, in order
gwt config setup none # do nothing
gwt config setup auto # back to auto-detectionTeardown is empty by default. Use it to release resources tied to a worktree (databases, containers, ports) before it's deleted. If a teardown command fails, removal is aborted unless you pass --force:
gwt config teardown "docker compose down"
gwt config teardown none # clearRemoving worktrees
gwt rm <branch> refuses to remove a worktree that has uncommitted changes, untracked files, or unpushed commits — to avoid losing work. Re-run with --force to remove anyway:
gwt rm my-feature
gwt rm my-feature --forceWorktree theming
To make parallel windows easy to tell apart, gwt add gives each worktree its own visual identity:
- A deterministic color (derived from the branch name) is applied to the VS Code / Cursor title bar and activity bar, plus a worktree-aware
window.title. Written to the worktree's.vscode/settings.jsonand merged into any existing settings without dropping your keys or comments. - A branch statusline is written to
.claude/settings.local.jsonso each Claude Code session shows its branch.
Both files are kept out of git status automatically — skip-worktree when the file is tracked, the worktree's local info/exclude otherwise. Your shared .gitignore is never touched.
Toggle either feature (both on by default):
gwt config theme off # disable color + window title
gwt config statusline off # disable the Claude statuslineWorktree location
Worktrees are created as siblings of your repo directory:
~/projects/
myrepo/ ← main repo
myrepo-my-feature ← worktree created by gwtSupported IDEs
VS Code, Cursor, Zed, WebStorm, IntelliJ IDEA, PyCharm, GoLand, Vim, Neovim, Sublime Text — or any custom IDE via the "Other" option in the wizard.
Requirements
- Node.js >= 18
- Git >= 2.5
gitwtreeon yourPATH.npm install -ghandles this normally. If you use a lazy-loaded nvm (or similar), make sure your global npm bin directory is exported on shell startup — otherwisegitwtree/gwtwon't resolve in a new shell. git-wtree assumes it's runnable; it deliberately doesn't touch yourPATH.
License
MIT
