Git worktrees
Worktrees live in .worktrees/ and are provisioned by name from the main
checkout:
just worktree-up NAME # URL, migrated + seeded database, test database, CSSjust worktree-up NAME card:<cardId> # the same, and link widget comments to that cardjust worktrees # list them with URL, database and sidecar statusjust worktree-down NAME # remove it along with its sidecar, route and databasesjust worktree-prune # drop sidecars and databases whose worktree is goneEach gets https://<slug>.<project>.dev.localhost and its own dev database, so
bin/console in a worktree never touches the main development database. Log in
with the seeded dev@loupe.test / password, or admin@loupe.test for the
admin area.
Rules that prevent real damage
Never remove one with a bare git worktree remove. That leaves the sidecar
container, the Traefik route and both databases behind, and the route then
serves 502s. Use just worktree-down.
Never match worktrees by directory name. A worktree called Feature_X owns
the slug feature-x; looking for a directory of that name would declare a live
worktree orphaned and drop its databases. The tooling matches on the derived
slug for exactly this reason.
Run just recipes from the worktree in a subshell, so the working directory
dies with the command:
( cd .worktrees/<name> && just worktree-up )Running tests in parallel
PHPUnit’s database is app_test<TEST_TOKEN>. just worktree-up writes a unique
TEST_TOKEN into each worktree’s .env.test.local, so each worktree gets its own
schema. ParaTest replaces an exported TEST_TOKEN with the worker number, so set
it in that file, not in the shell.
ParaTest adds the worker number, so worker 3 of card-116 uses
app_test_card_116__p3. just worktree-down drops the worker databases too.
The e2e suite is the exception and cannot be parallelised — Mailpit is shared.
Warm the cache before an e2e run
( cd .worktrees/<name> && bin/worktrees/compose-exec.sh bin/console cache:warmup )A run started against a cold var/cache/dev, or one whose cache is rebuilt
mid-flight by a concurrent just cs, produces false failures that look like
unrelated flaky specs: a request that never completes, a submit button left
disabled, and no validation errors — because the container-load fatal happens
before the logger exists, so nothing is logged at all.
If a spec fails and then passes in isolation, count the container hashes in
var/cache/dev/. More than one means a rebuild happened during the run. That is
a diagnosis, not a licence to re-run until green.