Bare-repo worktree manager — create, navigate, and switch worktrees with one command.
Works with any bare-repo-with-worktrees Git layout. Keeps your main worktree locked so branch switching is always through an explicit worktree, never by accident.
Homebrew (macOS / Linux):
brew tap pixelitemedia/tap
brew install pixelitemedia/tap/gitreenpm:
npm install -g @pixelitemedia/gitreeManual:
curl -fsSL https://raw.githubusercontent.com/pixelitemedia/gitree/main/bin/gitree \
-o /usr/local/bin/gitree && chmod +x /usr/local/bin/gitreeAfter any install, add shell functions for one-word navigation:
gitree install --shell-onlywt() { local d; d=$(gitree wt "$@") && cd "$d"; }
gitree-goto() { local d; d=$(gitree goto "$@") && cd "$d"; }Added automatically by gitree install. Then wt feature/foo creates the worktree and drops you in it. gitree-goto events-manager main navigates to a project's worktree from anywhere.
gitree init # init current directory as workspace root
gitree add [<repo>] [<name>|.] # add a repo as a bare worktree project
gitree restore # clone all projects listed in .gitree manifest
gitree convert multi|mono # convert between single- and multi-repo layouts
gitree branch-context init [<project>|*] # scaffold .gitree-context/ for existing reposgitree new [<project>] <branch> # create branch + worktree in one step
gitree wt [<project>] <branch> # get or create a worktree; print its path
gitree goto [<project>] <branch> # alias for wt (use via gitree-goto shell fn)
gitree remove [<project>] <branch> # remove a worktree safely (checks for dirty state)
gitree drop <project> # remove an entire project (prompts for confirmation)
gitree audit [<project>] # show branches with no worktree yetgitree list [<project>] # show worktrees; scoped to one project if specified
gitree switch [-l <loc>] [<project>] [<branch>] # point switch symlink at a worktree
gitree pull [<project>|*] # fetch + fast-forward main worktreegitree repair-head [<project>|*] # fix bare repo HEAD drift to match remote default
gitree lock [<project>|*] [<worktree>] # lock against branch switching (default: main)
gitree unlock [<project>|*] [<worktree>] # remove the lockgitree install [--shell-only] [<dir>] # install gitree globally + shell functions
gitree completion <bash|zsh> # print shell completion script
gitree --versionPlace a .gitree JSON file at the workspace root:
{
"switch": {
"default": "~/path/to/symlink/dir",
"staging": "~/path/to/staging/symlink/dir"
},
"projects": {
"my-plugin": "https://github.com/org/my-plugin.git",
"another-plugin": "https://github.com/org/another-plugin.git"
}
}switch: named symlink directories.gitree switchmanages a symlink in the target dir pointing to the active worktree. Use-l <name>to target a named location.projects: full project manifest.gitree restoreuses this to clone missing repos on a new machine.
Legacy key=value format (SWITCH_PATH=...) is still supported for single-location setups.
gitree add and gitree drop keep the manifest in sync automatically.
A switch location can be an object with a default fallback and per-project paths:
{
"switch": {
"default": {
"default": "~/path/to/plugins",
"my-mu-plugin": "~/path/to/mu-plugins"
},
"staging": "~/staging/plugins"
}
}gitree switch my-mu-plugin lands in ~/path/to/mu-plugins/my-mu-plugin; everything else lands in ~/path/to/plugins/<project>. The staging location stays a plain string — both forms coexist.
gitree switch events-manager # default location → main/
gitree switch events-manager feature/foo # default location → .worktrees/feature--foo/
gitree switch -l staging events-manager # staging location → main/Project name is auto-detected when run from inside a project directory.
Each project follows the bare-repo-with-worktrees pattern:
<project>/
├── .bare/ ← bare repo: refs, objects, worktree metadata
├── .git ← 1-line file: "gitdir: ./.bare"
├── main/ ← default working surface (main branch)
└── .worktrees/
└── <branch>/ ← additional worktrees, created on demand
Branch names with slashes map to dir names with -- (feature/foo → .worktrees/feature--foo/). Always pass the real branch name — gitree handles the translation.
A gitree workspace is the directory containing .gitree. Only AGENTS.md (the gitree-managed entry point) and .gitree live at the workspace root — everything else portable lives under .gitree-context/:
<workspace>/
├── .gitree ← config + project manifest
├── AGENTS.md ← workspace entry point (gitree baseline)
├── CLAUDE.md → AGENTS.md ← optional Claude entry point
└── .gitree-context/ ← all portable workspace state
├── AGENTS.md ← workspace operational context (overrides baseline)
├── README.md TODO.md ROADMAP.md ← workspace-wide (optional)
└── <project>/
├── README.md TODO.md ROADMAP.md ← project-level (optional)
└── <branch>/ ← per-branch context (symlinked from worktrees)
The two flavours of workspace differ only in where the project repos sit on disk:
- Multi-repo — projects as sibling directories:
<workspace>/<project>/<{.bare,main,.worktrees}>.gitree add <repo>creates this. - Mono-repo — workspace IS the project:
<workspace>/<{.bare,main,.worktrees}>directly.gitree add . <repo>creates this.
Branches always live under .gitree-context/<project>/<branch>/ — same in both flavours — so a mono-repo workspace can be converted to multi-repo later without restructuring the context tree.
Mono-repo loophole: when the workspace IS the project, the project-level README/TODO/ROADMAP/AGENTS can live at .gitree-context/ root instead of under <project>/ (workspace = project, so no need to duplicate). Saves AI agents an extra directory traversal. The branch tree at <project>/<branch>/ stays where it is.
gitree maintains a convention for four file types at each level (workspace, project, branch):
- AGENTS.md — operational instructions for agents: conventions, tooling, constraints
- README.md — persistent context: architecture, purpose, relationships
- TODO.md — tasks at this level only; per-branch work goes in
.branch-context/TODO.md - ROADMAP.md — strategic direction at this level only; per-feature planning belongs in branches
Each piece of information lives in exactly one place. Higher-level files describe structure; lower-level files describe specifics. They don't duplicate.
bin/AGENTS.md ships with gitree as the full reference for this convention; a condensed version is what gitree init writes to <workspace>/AGENTS.md.
<workspace>/.gitree + <workspace>/.gitree-context/ are the complete portable state. To replicate a workspace on another machine:
rsync -av <workspace>/.gitree <workspace>/.gitree-context/ user@host:<workspace>/
ssh user@host 'cd <workspace> && gitree restore'AGENTS.md at the workspace root is regenerated by gitree restore — no need to sync it. Branch context symlinks are wired automatically for any worktrees that already exist.
# zsh
eval "$(gitree completion zsh)"
# bash
eval "$(gitree completion bash)"Add to your .zshrc / .bashrc for persistent tab completion.
Worktree operations can move the bare repo's HEAD off the default branch. This causes git pull, git log, and similar to target the wrong branch. Run gitree repair-head to query the remote for the authoritative default branch and reset bare HEAD accordingly.
gitree repair-head # fix all projects
gitree repair-head my-plugin # fix one projectWhen plugins are served via symlinks from ~/Code/ into wp-content/plugins/, PHP's __FILE__ resolves the symlink target. WordPress's plugins_url() requires the real path to share a prefix with WP_PLUGIN_DIR — paths under ~/Code/ don't satisfy this.
Fix: add wp-content/mu-plugins/symlink-plugin-paths.php to pre-register every symlinked plugin dir in $wp_plugin_paths. Asset URLs then resolve correctly regardless of which worktree is active.
gitree lock installs a post-checkout hook in the bare repo. If someone tries to git checkout <branch> inside a locked worktree, the hook:
- Reverts HEAD and the working tree to the previous branch
- Deletes the switched-to branch if it was freshly created (reflog count ≤ 1)
- Prints a message:
Use: gitree wt <branch> to work on another branch
Locking is per-worktree. Lock main to protect your default surface while freely creating worktrees for feature branches.
- v1.1.0 —
gitree switch(no args) prints current state;gitree dropcleans stale switch symlinks;gitree listshows relative paths;gitree removeoffers remote branch delete; tab completion includes project names;gitree install --no-shellfor headless environments - v1.0.0 — initial release: bare-repo layout, worktree create/remove/lock, switch, pull, audit, install, shell completion
- JSON
.gitreeconfig with named switch locations (switch.*) and project manifest (projects) gitree switch -l <location>— multi-location switching (e.g. default / wpml / wpwc)gitree restore— clone all manifest projects on a new machine (idempotent)gitree drop— remove a project and update manifestgitree repair-head— fix bare repo HEAD drift by querying remote default branchgitree goto/gitree-goto()shell fn — navigate to any project worktree from anywheregitree list [project]— scoped listing, auto-detected from cwd- HEAD drift detection in
gitree listoutput with repair hint .gitree-context/branch context scaffolding — per-branchREADME.md,AGENTS.md,TODO.md,ROADMAP.mdcreated ongitree new/wt, symlinked as.branch-context/inside each worktreegitree branch-context init [<project>|*]— retrofit existing repos with branch contextAGENTS.md+CLAUDE.mdauto-generated at workspace root ongitree init/gitree add;.gitree-context/AGENTS.mdstub for workspace-specific agent context- Homebrew tap:
pixelitemedia/tap - npm package:
@pixelitemedia/gitree
MIT