Skip to content

fix(tia): support a Pest project nested in a git repository - #1914

Open
jfatr wants to merge 2 commits into
pestphp:5.xfrom
jfatr:fix/tia-nested-project
Open

jfatr wants to merge 2 commits into
pestphp:5.xfrom
jfatr:fix/tia-nested-project

Conversation

@jfatr

@jfatr jfatr commented Sep 15, 2026

Copy link
Copy Markdown

What happens

Run vendor/bin/pest --tia in a Pest project that sits below the root of its git repository, backend/ in a monorepo for example, and Tia refuses:

Tia mode requires the git repository root

Git prints and addresses every path relative to the repository root, backend/app/Service.php, while the dependency graph keys its edges on paths relative to the project root, app/Service.php. The plugin guarded against the mismatch instead of translating it.

Related: #1805, #1809, #1820.

The fix

Translate at every place where Tia talks to git, and let a project at the repository root keep the paths, the state key and the fingerprint it has today.

  • Git::pathPrefix() returns the directory of the project inside the repository, backend/, or an empty string at the root or without git.
  • ChangedFiles strips that prefix off the output of git diff --name-only and git status --porcelain, drops the paths of sibling packages, and prepends it to git show <sha>:<path>. Both reads take -z records, because core.quotePath C-quotes a path outside ASCII, and --no-relative keeps the repository frame when the user sets diff.relative.
  • git check-ignore runs without --no-index, so a tracked file that an ignore rule matches still counts as part of the project.
  • Three places read <projectRoot>/.git directly, which is absent in a nested project and in a worktree, and now ask git: BaseRemote::readOriginUrl(), Storage and Fingerprint::isTrackedByGit().
  • Storage folds the prefix into the state key, so two projects in one repository do not share a graph. The key of a project at the repository root does not change.
  • The structural fingerprint carries the prefix of a nested project, so a baseline that a sibling project published to the same GitHub artifact name is rejected. The fingerprint of a project at the repository root stays byte-identical.
  • The run summary reports the count of changed files outside the project, so a nested project does not look idle when the repository around it moved.
  • TiaRequiresRepositoryRoot and the test that expected it go away.

Tests

tests/Features/Tia/Monorepo.php runs a nested project through replay, a working tree change, a committed change, a sibling change, an undone commit, two projects in one repository, and remote baselines for a nested project. tests/Unit/Plugins/Tia/ChangedFiles.php covers the path translation, the ignore rules on both sides of the boundary, non-ASCII paths and diff.relative. tests/Unit/Plugins/Tia/Storage.php covers the state key.

  • pest --group=tia: 222 passed.
  • rector, pint, phpstan and type coverage at 100 % pass.

A second pull request builds on this one and makes Tia see the code a nested project loads from outside its root, such as a Composer path repository or a sibling package.

🤖 Generated with Claude Code

Tia refused to run whenever the Pest project was not the git repository
root, because git prints and addresses paths relative to the repository
root while the dependency graph is keyed on project-relative paths.

Translate at every git boundary instead of guarding against the mismatch.
`Git::pathPrefix()` resolves the directory of the project inside the
repository, and `ChangedFiles` strips that prefix off the paths git prints,
drops the paths of sibling packages, and prepends it to `git show`. Both
diffs read `-z` records, because `core.quotePath` C-quotes a path outside
ASCII, and `--no-relative` holds the frame when a user sets `diff.relative`.

Three places read `<projectRoot>/.git` directly, which is absent in a
nested project and in a worktree, and now ask git: `BaseRemote`, `Storage`
and `Fingerprint::isTrackedByGit()`. `Storage` folds the prefix into the
state key so two projects in one repository do not share a graph, and the
fingerprint carries the prefix so a baseline published by a sibling project
is rejected. A project at the repository root keeps the key and the
fingerprint it had.

`git check-ignore` runs without `--no-index`, so a tracked file that an
ignore rule matches counts as part of the project on both sides of the
boundary.

Changes outside the project are dropped and reported by count in the run
summary, so a nested project does not look idle when the repository around
it moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	src/Plugins/Tia/Baselines/BaseRemote.php
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.

1 participant