This commit is contained in:
nuno maduro
2026-04-23 10:56:17 -07:00
parent 470a5833d4
commit caabebf2a1
2 changed files with 39 additions and 5 deletions

View File

@ -833,14 +833,21 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
return $arguments;
}
$changed = $changedFiles->since($graph->recordedAtSha($this->branch)) ?? [];
$branchSha = $graph->recordedAtSha($this->branch);
$changed = $changedFiles->since($branchSha) ?? [];
// Drop files whose content hash matches the last-run snapshot. This
// is the "dirty but identical" filter: if a file is uncommitted but
// its content hasn't moved since the last `--tia` invocation, its
// dependents already re-ran last time and don't need re-running
// again.
$changed = $changedFiles->filterUnchangedSinceLastRun($changed, $graph->lastRunTree($this->branch));
// again. Passing the recorded sha also catches reverts: a file
// that was edited last run but is now back to its committed
// form no longer looks "changed".
$changed = $changedFiles->filterUnchangedSinceLastRun(
$changed,
$graph->lastRunTree($this->branch),
$branchSha,
);
$affected = $changed === [] ? [] : $graph->affected($changed);

View File

@ -42,7 +42,7 @@ final readonly class ChangedFiles
* @param array<string, string> $lastRunTree path → content hash from last run.
* @return array<int, string>
*/
public function filterUnchangedSinceLastRun(array $files, array $lastRunTree): array
public function filterUnchangedSinceLastRun(array $files, array $lastRunTree, ?string $sha = null): array
{
if ($lastRunTree === []) {
return $files;
@ -87,9 +87,36 @@ final readonly class ChangedFiles
$hash = ContentHash::of($absolute);
if ($hash === false || $hash !== $snapshot) {
if ($hash === false) {
$remaining[] = $file;
continue;
}
if ($hash === $snapshot) {
// Same state as the last TIA invocation — unchanged.
continue;
}
// Differs from the snapshot, but may still be a revert back
// to the committed version (scenario: last run had an edit,
// this run reverted it). Skipping this check causes stale
// snapshots from previous scenarios to cascade into the
// current run's invalidation set. Cheap to verify via
// `git show <sha>:<path>`.
if ($sha !== null && $sha !== '') {
$baselineContent = $this->contentAtSha($sha, $file);
if ($baselineContent !== null) {
$baselineHash = ContentHash::ofContent($file, $baselineContent);
if ($hash === $baselineHash) {
continue;
}
}
}
$remaining[] = $file;
}
return $remaining;