id = $id; $len = strlen($data); $pos = 0; while ($pos < $len) { $sp = strpos($data, ' ', $pos); $nul = $sp === false ? false : strpos($data, "\0", $sp); if ($nul === false || $nul + 1 + $hashLen > $len) throw new GitException('壊れたツリー'.($id ? ":{$id}" : '')); $t->entries[] = [substr($data, $pos, $sp - $pos), substr($data, $sp + 1, $nul - $sp - 1), bin2hex(substr($data, $nul + 1, $hashLen))]; $pos = $nul + 1 + $hashLen; } return $t; } /** * エントリーからツリーを作る(Gitの順番に並べる) * * @param array $entries [mode, name, id] */ public static function create(array $entries): self { $t = new self(); foreach ($entries as [$mode, $name, $id]) { if ($name === '' || $name === '.' || $name === '..' || str_contains($name, '/') || str_contains($name, "\0")) throw new GitException("不正なファイル名:{$name}"); $t->entries[] = [ltrim((string)$mode, '0') ?: '0', $name, strtolower($id)]; } usort($t->entries, fn($a, $b) => strcmp(self::sortKey($a), self::sortKey($b))); for ($i = 1; $i < count($t->entries); $i++) { if ($t->entries[$i][1] === $t->entries[$i - 1][1]) throw new GitException("同じ名前が2つ有ります:{$t->entries[$i][1]}"); } return $t; } /** ディレクトリは名前の後に「/」が有るとして並べる */ private static function sortKey(array $e): string { return $e[0] === self::DIR ? $e[1].'/' : $e[1]; } public function toString(): string { $out = ''; foreach ($this->entries as [$mode, $name, $id]) $out .= "{$mode} {$name}\0".hex2bin($id); return $out; } /** 名前のエントリー */ public function find(string $name): ?array { foreach ($this->entries as $e) if ($e[1] === $name) return $e; return null; } public static function isTree(string $mode): bool { return $mode === self::DIR; } }