<?php class GitRefs { private string $repoPath; public function __construct( string $repoPath ) { $this->repoPath = $repoPath; } public function resolve( string $input ): string { if( preg_match( '/^[0-9a-f]{40}$/', $input ) ) { return $input; } $headFile = "{$this->repoPath}/HEAD"; if( $input === 'HEAD' && file_exists( $headFile ) ) { $head = trim( file_get_contents( $headFile ) ); return strpos( $head, 'ref: ' ) === 0 ? $this->resolve( substr( $head, 5 ) ) : $head; } return $this->resolveRef( $input ); } public function getMainBranch(): array { $branches = []; $this->scanRefs( 'refs/heads', function( string $name, string $sha ) use ( &$branches ) { $branches[$name] = $sha; } ); foreach( ['main', 'master', 'trunk', 'develop'] as $try ) { if( isset( $branches[$try] ) ) { return ['name' => $try, 'hash' => $branches[$try]]; } } $firstKey = array_key_first( $branches ); return $firstKey ? ['name' => $firstKey, 'hash' => $branches[$firstKey]] : ['name' => '', 'hash' => '']; } public function scanRefs( string $prefix, callable $callback ): void { $dir = "{$this->repoPath}/$prefix"; if( is_dir( $dir ) ) { $files = array_diff( scandir( $dir ), ['.', '..'] ); foreach( $files as $file ) { $callback( $file, trim( file_get_contents( "$dir/$file" ) ) ); } } } private function resolveRef( string $input ): string { $paths = [$input, "refs/heads/$input", "refs/tags/$input"]; foreach( $paths as $ref ) { $path = "{$this->repoPath}/$ref"; if( file_exists( $path ) ) { return trim( file_get_contents( $path ) ); } } $packedPath = "{$this->repoPath}/packed-refs"; return file_exists( $packedPath ) ? $this->findInPackedRefs( $packedPath, $input ) : ''; } private function findInPackedRefs( string $path, string $input ): string { $targets = [$input, "refs/heads/$input", "refs/tags/$input"]; foreach( file( $path ) as $line ) { if( $line[0] === '#' || $line[0] === '^' ) { continue; } $parts = explode( ' ', trim( $line ) ); if( count( $parts ) >= 2 && in_array( $parts[1], $targets ) ) { return $parts[0]; } } return ''; } }