Skip to content

Git worktrees

Worktrees live in .worktrees/ and are provisioned by name from the main checkout:

Terminal window
just worktree-up NAME # URL, migrated + seeded database, test database, CSS
just worktree-up NAME card:<cardId> # the same, and link widget comments to that card
just worktrees # list them with URL, database and sidecar status
just worktree-down NAME # remove it along with its sidecar, route and databases
just worktree-prune # drop sidecars and databases whose worktree is gone

Each 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:

Terminal window
( cd .worktrees/<name> && just worktree-up )

The preview token of a worktree

just worktree-up mints a preview token for the worktree. It writes the token to SITE_REVIEW_WIDGET_PREVIEW_TOKEN in the .env.local of the worktree. The widget then signs in with no popup. just worktree-down revokes the token.

The scripts pass the widget backend as --instance. A CLI that is signed in to another instance mints nothing.

A failed mint never stops just worktree-up. The script prints one warning line and writes an empty token, and the widget shows its Sign in button. The warning appears when the CLI is missing, signed out, or signed in to another instance than SITE_REVIEW_WIDGET_BACKEND.

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

Terminal window
( 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.