Skip to content

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

Description

@officialsyntec

What happened

In a monorepo where the Pest project lives in a subdirectory of the git repository (e.g. backend/), TIA never detects any PHP source change. In the worst case it replays the previous results as passing without executing the tests affected by the change.

repo/                <- git root
├── frontend/
└── backend/         <- Laravel app, composer.json, pest runs here
    ├── app/
    └── tests/

Reproduction:

  1. Record a baseline: PEST_ALLOW_XDEBUG=1 XDEBUG_MODE=coverage vendor/bin/pest --tia (from backend/, all green, graph written).
  2. Make a semantic change to backend/app/Services/Billing/PartialInvoiceService.php (e.g. add a private const).
  3. Run vendor/bin/pest --tia again.

Expected: the tests covering that file are re-executed (in our suite: 40 tests across 4 files).
Actual: Tests: 1669 passed (…, 1669 replayed) in ~3s — the change maps to zero tests and everything is replayed as unchanged.

With no coverage driver loaded the failure is the safe-but-slow variant instead: WARN Detected PHP source changes but no coverage driver is available followed by a full-suite run on every invocation.

Root cause

src/Plugins/Tia/ChangedFiles.php collects changed paths via:

  • workingTreeChanges()git status --porcelain -z --untracked-files=all (cwd = project root)
  • diffSinceSha()git diff --name-only <sha>..HEAD

Both git commands print paths relative to the repository root (backend/app/Services/...), regardless of cwd. Downstream these are consumed as project-relative paths:

  • currentHash() prepends the project root → …/backend/backend/app/...is_file() false
  • the dependency-graph keys are project-relative (app/Services/...) → the changed path never matches an edge → affected set is empty

Verified identical logic in v4.7.5 (installed) and v5.0.0 (GitHub source). Projects whose composer root equals the git root never notice, since both path notions coincide.

Proof it is exactly this

Creating a throwaway git repository inside backend/ (making project root == git root) and repeating the same experiment yields correct behavior immediately: Tests: 1669 passed (…, 40 affected, 1629 replayed) in ~100s. Removing the inner repo restores the broken behavior.

Suggested fix

Ask git for the prefix once (git rev-parse --show-prefix, empty at repo root) and strip it from every path returned by workingTreeChanges() / diffSinceSha() before they are compared with project-relative graph keys; paths outside the prefix (sibling packages, e.g. frontend/…) can be discarded or kept for the structural-change checks.

Environment

  • Pest v4.7.5 (same code confirmed in v5.0.0)
  • PHP 8.4.10, Laravel 12.64, PostgreSQL, macOS (darwin arm64)
  • Layout: Laravel app in backend/ subdirectory of the git repo, tests/Pest.php maps Tests\TestCase onto Feature + Unit, suite is 100% functional Pest style (1669 tests)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions