Skip to content

feat(tia): support projects in git repository subdirectories (monorepos) - #1809

Open
einar-hansen wants to merge 2 commits into
pestphp:5.xfrom
einar-hansen:tia-monorepo-support
Open

feat(tia): support projects in git repository subdirectories (monorepos)#1809
einar-hansen wants to merge 2 commits into
pestphp:5.xfrom
einar-hansen:tia-monorepo-support

Conversation

@einar-hansen

@einar-hansen einar-hansen commented Jul 31, 2026

Copy link
Copy Markdown

What:

  • Bug Fix
  • New Feature

Description:

Love the Tia engine — but it refuses to run when the Pest project lives in a subdirectory of its git repository (apps/api in a monorepo):

ERROR Tia mode requires the git repository root.

The root cause is a path-frame mismatch: git addresses paths relative to the repo root, while Tia's dependency graph is keyed on project-relative paths. This PR teaches every git boundary to translate instead of blocking the scenario. (Written with Claude Code; reviewed by me and verified on a real monorepo, see below.)

  • ChangedFiles resolves the prefix once (git rev-parse --show-prefix), strips it from everything git reports, prepends it to everything Tia asks git for (git show <sha>:apps/api/composer.lock), and ignores changes outside the project — commits that only touch a sibling app replay 100%.
  • Diffs run with -z and --no-renames. Without -z, git C-quotes non-ASCII filenames, the quoted path misses the prefix, and the change is silently dropped — stale results replayed as passing, the one failure Tia can never afford. Without --no-renames, a file renamed out of the subtree reports only its new (ignored) path and vanishes from Tia's view.
  • A new GitRepository helper fixes three more root==repo assumptions: BaselineSync (read .git/config from the project folder, so baseline fetching silently never activated in a subfolder), Storage (cache key now includes the subfolder, so two apps in one repo get distinct graph stores), and Fingerprint (gitignore detection).

Root-level projects are untouched: the prefix is empty, every translation is a no-op, and storage keys stay byte-identical — pinned by a regression test, so no existing caches are invalidated.

Verification

Known limitation: the baseline artifact name is fixed (pest-tia-baseline), so two baselined projects in one repository would collide. Happy to follow up if that's a setup you'd want supported.

Related:

Tia previously refused to run when the project root was not the git
repository root, telling monorepo users to "give the project its own git
repository". The underlying problem was a path-space mismatch: git's
plumbing (status --porcelain, diff --name-only, show SHA:path) speaks
repository-root-relative paths, while the dependency graph, watch patterns
and coverage edges all speak project-root-relative paths.

ChangedFiles now detects the project's location inside the repository once
(git rev-parse --show-prefix) and translates at every git boundary:

- status/diff output is mapped to project-relative paths, and paths outside
  the project subtree are dropped — they are as invisible to the project's
  graph as files outside the repository are for a root-level project, and
  leaking them through would hit the unknown-file catch-all and force a
  full rerun on every sibling-project change;
- the diff listing runs with -z (git C-quotes non-ASCII paths, which would
  silently fail the prefix match and drop a real change — a stale replay)
  and --no-renames (rename detection reports only the NEW path, so a
  project file renamed to a path outside the subtree would vanish from the
  listing; as delete+add both sides surface);
- git show SHA:path (behavioural-change filtering, composer.lock drift
  summaries) resolves project-relative paths through the prefix.

A new GitRepository helper locates the governing repository by walking
ancestors, fixing three more root==project assumptions: Fingerprint's
tracked-lockfile check, BaselineSync's GitHub-repo detection (baseline
fetching silently never activated for subdirectory projects), and
Storage's origin-based project key — which now also carries the
subdirectory prefix, so `apps/api` and `services/api` in one repository
never share a graph store.

Root-level projects keep their exact existing behaviour: the prefix is
empty and every translation is the identity. The TiaRequiresRepositoryRoot
exception is removed along with the guard.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TIA: change detection misses all changes when the Pest project is a subdirectory of the git repository (monorepo)

1 participant