Mark CipollaLogo

Mark Cipolla

I run a lot of git worktrees. One branch per worktree means several copies of the same app on disk at once, and every one of them wants port 3000 and a database called myapp_development. Something has to hand out the ports and the databases, or they trample each other.

worktrees

PythonShellPostgresGitCLIHomebrewGithub Actions
GitHub

worktrees is a small CLI that a worktree tool (or a shell script, or you) calls when a worktree is created or destroyed. It reserves a free TCP port for each service the app declares, creates one Postgres database per environment, writes an env file at the root of the worktree with every allocated value, and cleans all of it up again on the way out.

Each app describes what it needs in a .worktree-config.json checked into the repo: its services and their port ranges, its databases and the environment variables they should land in, and the commands it's driven with. The tool never touches anything else the app owns; the env file it writes is the only file it puts in your working tree.

Central state lives in ~/.config/worktrees/state.json, so ports are picked from the declared range while skipping anything that's currently listening or already allocated somewhere else. Database names are templated from the app and worktree names, and shortened with a digest of the full name if they'd run past Postgres's 63 byte identifier limit. That last part matters more than it sounds: Postgres truncates long identifiers silently, so without it a long branch name's _dev and _test databases would quietly resolve to the same database.

The part I use most is the shell hook. Adding eval "$(worktrees shell-init)" to your .zshrc means that every new shell, and every cd into a worktree, prints what this worktree got:

worktrees acme @ snailfish ~/dev/worktrees/snailfish rails http://localhost:3001 $PORT vite http://localhost:5171 $VITE_PORT development acme_snailfish_dev $DATABASE_URL test acme_snailfish_test $TEST_DATABASE_URL serve bin/dev console bin/rails console env .env.worktree

It prints once per visit rather than on every prompt, and it stays quiet while you move around inside the same worktree. Because it runs from a prompt hook it's written to never take the shell down with it: an unreadable config, a corrupt state file or a missing allocation each print at most one line and exit 0.

It's a single Python file with no dependencies beyond psql, tested by a set of shell scripts that run against a real Postgres in Github Actions, and published to a Homebrew tap on every push to main.

Install with brew install markcipolla/tap/worktrees, drop a .worktree-config.json in your app, and call worktrees up after git worktree add and worktrees down before git worktree remove.

Mark lives and works on unceded Wurundjeri land in Naarm / Melbourne, Australia, and acknowledges the traditional custodians of the land on which he lives, paying respects to elders past and present, recognising the resilience, strength and pride of the Aboriginal and Torres Strait Islander communities.