>= 4; $out = ''; while ($size > 0) { $out .= chr($b | 0x80); $b = $size & 0x7f; $size >>= 7; } return $out.chr($b); } /** * パックを書く * * @param array $objects ID => 種類 * @param callable $out fn(string $bytes) * @param callable|null $progress fn(int $done, int $total) * @return string パックのチェックサム(16進数) */ public function write(array $objects, callable $out, ?callable $progress = null): string { $ctx = hash_init($this->repo->hashAlgo); $buf = 'PACK'.pack('NN', 2, count($objects)); $flush = function(bool $force = false) use (&$buf, $ctx, $out): void { if ($buf === '' || (!$force && strlen($buf) < self::CHUNK)) return; hash_update($ctx, $buf); $out($buf); $buf = ''; }; $codes = array_flip(Pack::TYPES); $done = 0; $total = count($objects); foreach ($objects as $id => $type) { $loc = $this->repo->objects->locate($id); if ($loc === null) throw new GitException("オブジェクトが有りません:{$id}"); $written = false; if ($loc[0] === 'pack') { [$pack, $off] = [$loc[1], $loc[2]]; [$t, $size, $dataAt, $baseOff, $baseId] = $pack->header($off); if (isset(Pack::TYPES[$t])) { // そのままコピー $buf .= $pack->raw($off, $pack->entryEnd($off)); $written = true; } else { $base = $baseOff !== null ? $pack->idAtOffset($baseOff) : $baseId; if ($base !== null && isset($objects[bin2hex($base)])) { // 元も送るので差分のまま(REF_DELTA) $buf .= self::entryHeader(Pack::REF_DELTA, $size).$base.$pack->raw($dataAt, $pack->entryEnd($off)); $written = true; } } } if (!$written) { [$t, $data] = $this->repo->objects->read($id) ?? throw new GitException("オブジェクトが有りません:{$id}"); $buf .= self::entryHeader($codes[$t], strlen($data)).gzcompress($data, 6); } $flush(); $done++; if ($progress && ($done % 1000 === 0 || $done === $total)) $progress($done, $total); } $flush(true); $sum = hash_final($ctx, true); $out($sum); return bin2hex($sum); } }