ID, 'line' => そのコミットでの行の番号(1から), 'text' => 行] */ public static function file(Repository $repo, string $rev, string $path, int $maxCommits = 10000): array { $start = $repo->resolve($rev); $start = $start === null ? null : $repo->peel($start, 'commit'); if ($start === null) throw new GitException("リビジョンが有りません:{$rev}"); $path = trim($path, '/'); $entry = $repo->entry($start, $path); if ($entry === null || $entry[0] === Tree::DIR) throw new GitException("ファイルが有りません:{$path}"); $lines = Diff::split($repo->blob($entry[2])); $result = array_fill(0, count($lines), null); // コミット => [ブロブのID, [そのコミットでの行の番号 => 最後の行の番号]] $pending = [$start => [$entry[2], array_combine(array_keys($lines), array_keys($lines))]]; $queue = new \SplPriorityQueue(); $seq = PHP_INT_MAX; $queue->insert($start, [$repo->commit($start)->committer?->time ?? 0, $seq--]); $blobs = []; $split = function(string $id) use ($repo, &$blobs): array { return $blobs[$id] ??= Diff::split($repo->blob($id)); }; $done = 0; while (!$queue->isEmpty()) { $id = $queue->extract(); if (!isset($pending[$id])) continue; [$blob, $map] = $pending[$id]; unset($pending[$id]); $c = $repo->commit($id); if (++$done > $maxCommits) { foreach ($map as $cur => $final) $result[$final] = [$id, $cur]; continue; } foreach ($c->parents as $p) { if (empty($map)) break; if (!$repo->objects->exists($p)) continue; $pe = $repo->entry($p, $path); if ($pe === null || $pe[0] === Tree::DIR) continue; if ($pe[2] === $blob) { // 同じ内容:全部を親に self::pass($repo, $pending, $queue, $seq, $p, $pe[2], $map); $map = []; break; } $mine = $split($blob); $theirs = $split($pe[2]); $passed = []; foreach (Diff::match($theirs, $mine) as [$pi, $ci]) { if (isset($map[$ci])) { $passed[$pi] = $map[$ci]; unset($map[$ci]); } } if (!empty($passed)) self::pass($repo, $pending, $queue, $seq, $p, $pe[2], $passed); } foreach ($map as $cur => $final) $result[$final] = [$id, $cur]; } $out = []; foreach ($lines as $i => $text) $out[] = ['commit' => $result[$i][0], 'line' => $result[$i][1] + 1, 'text' => $text]; return $out; } private static function pass(Repository $repo, array &$pending, \SplPriorityQueue $queue, int &$seq, string $p, string $blob, array $map): void { if (isset($pending[$p])) { $pending[$p][1] += $map; return; } $pending[$p] = [$blob, $map]; $queue->insert($p, [$repo->commit($p)->committer?->time ?? 0, $seq--]); } }