dir; } public function isCloned(): bool { return is_dir($this->dir . '/.git'); } public function ensure(): void { if (!$this->isCloned()) { $parent = dirname($this->dir); if (!is_dir($parent)) { mkdir($parent, 0777, true); } $this->git(['clone', '--tags', $this->url, $this->dir]); return; } $this->ensureFull(); $this->fetch(); } public function fetch(): void { $this->git(['fetch', '--tags', '--force', 'origin']); } /** * Si la caché es superficial, la completa. */ public function ensureFull(): void { if (is_file($this->dir . '/.git/shallow')) { $this->git(['fetch', '--unshallow', '--tags']); } } /** * @return list */ public function tags(?string $prefix = null): array { $tags = array_filter(explode("\n", trim($this->git(['tag', '-l'])[1]))); if ($prefix === null) { return array_values($tags); } return array_values(array_filter($tags, fn ($tag) => str_starts_with($tag, $prefix))); } public function commitOfTag(string $tag): ?string { $result = $this->git(['rev-list', '-n', '1', $tag]); return $result[0] === 0 && trim($result[1]) !== '' ? trim($result[1]) : null; } public function hasCommit(string $commit): bool { $result = $this->git(['cat-file', '-e', $commit . '^{commit}']); return $result[0] === 0; } public function fileAt(string $ref, string $path): ?string { $result = $this->git(['show', $ref . ':' . $path]); return $result[0] === 0 ? $result[1] : null; } /** * @param list $args * @return array{0:int, 1:string, 2:string} */ public function git(array $args): array { $command = 'git -C ' . escapeshellarg($this->dir) . ' ' . implode(' ', array_map('escapeshellarg', $args)) . ' 2>&1'; $output = []; $code = 0; exec($command, $output, $code); return [$code, implode("\n", $output), '']; } }