path.'/gc.pid'; $fh = @fopen($lock, 'c'); if ($fh === false || !flock($fh, LOCK_EX | LOCK_NB)) throw new GitException('別のgcが実行中です'); try { $tips = array_values($repo->refs->all()); $head = $repo->refs->resolve('HEAD'); if ($head !== null) $tips[] = $head; $tips = array_values(array_unique($tips)); $objects = empty($tips) ? [] : (new Walk($repo))->objects($tips, []); $old = $repo->objects->packs(true); $newPack = null; if (!empty($objects)) { $tmp = $repo->objects->dir().'/pack/tmp_gc_'.bin2hex(random_bytes(6)); $out = fopen($tmp, 'w+b'); try { (new PackWriter($repo))->write($objects, fn(string $b) => fwrite($out, $b)); rewind($out); $res = (new PackIndexer($repo))->receive($out, 0); $newPack = $res['pack']; } finally { fclose($out); @unlink($tmp); } } $cutoff = time() - $pruneAge; $removedPacks = 0; foreach ($old as $path => $pack) { if ($path === $newPack || !str_starts_with($path, $repo->objects->dir().'/')) continue; // 全部が新しいパックに有るか、古い(到達出来ない物だけ)なら消す $all = true; foreach ($pack->ids() as $id) if (!isset($objects[$id])) { $all = false; break; } if (!$all && filemtime($path) >= $cutoff) continue; if (is_file(substr($path, 0, -5).'.keep')) continue; unset($pack); foreach (['.idx', '.rev', '.pack'] as $ext) @unlink(substr($path, 0, -5).$ext); $removedPacks++; } $removedLoose = 0; foreach (glob($repo->objects->dir().'/[0-9a-f][0-9a-f]/*') ?: [] as $f) { $id = basename(dirname($f)).basename($f); if (!$repo->isId($id)) continue; if (isset($objects[$id]) || filemtime($f) < $cutoff) { if (@unlink($f)) $removedLoose++; } } foreach (glob($repo->objects->dir().'/[0-9a-f][0-9a-f]', GLOB_ONLYDIR) ?: [] as $d) @rmdir($d); // 残った一時ファイル foreach (glob($repo->objects->dir().'/pack/tmp_*') ?: [] as $f) if (filemtime($f) < $cutoff) @unlink($f); return ['objects' => count($objects), 'removedLoose' => $removedLoose, 'removedPacks' => $removedPacks]; } finally { flock($fh, LOCK_UN); fclose($fh); @unlink($lock); } } }