<?php require_once __DIR__ . '/BufferedReader.php'; class GitRefs { private string $repoPath; public function __construct( string $repoPath ) { $this->repoPath = $repoPath; } public function resolve( string $input ): string { $result = ''; if( preg_match( '/^[0-9a-f]{40}$/', $input ) ) { $result = $input; } else { $headFile = "{$this->repoPath}/HEAD"; if( $input === 'HEAD' && is_file( $headFile ) ) { $size = filesize( $headFile ); $head = ''; if( $size > 0 ) { $reader = new BufferedReader( $headFile ); $head = trim( $reader->read( $size ) ); } $result = strpos( $head, 'ref: ' ) === 0 ? $this->resolve( substr( $head, 5 ) ) : $head; } else { $result = $this->resolveRef( $input ); } } return $result; } public function getMainBranch(): array { $branches = []; $this->scanRefs( 'refs/heads', function( string $name, string $sha ) use ( &$branches ) { $branches[$name] = $sha; } ); $found = []; foreach( ['main', 'master', 'trunk', 'develop'] as $try ) { if( isset( $branches[$try] ) ) { $found = [ 'name' => $try, 'hash' => $branches[$try] ]; break; } } if( empty( $found ) && !empty( $branches ) ) { $key = array_key_first( $branches ); $found = [ 'name' => $key, 'hash' => $branches[$key] ]; } elseif( empty( $found ) ) { $found = [ 'name' => '', 'hash' => '' ]; } return $found; } public function scanRefs( string $prefix, callable $callback ): void { $dir = "{$this->repoPath}/$prefix"; $this->traverseDirectory( $dir, $callback, '' ); } private function traverseDirectory( string $dir, callable $callback, string $subPath ): void { if( is_dir( $dir ) ) { $files = array_diff( scandir( $dir ), ['.', '..'] ); foreach( $files as $file ) { $path = "$dir/$file"; $name = $subPath === '' ? $file : "$subPath/$file"; if( is_dir( $path ) ) { $this->traverseDirectory( $path, $callback, $name ); } elseif( is_file( $path ) ) { $size = filesize( $path ); if( $size > 0 ) { $reader = new BufferedReader( $path ); $sha = trim( $reader->read( $size ) ); if( preg_match( '/^[0-9a-f]{40}$/', $sha ) ) { $callback( $name, $sha ); } } } } } } private function resolveRef( string $input ): string { $paths = [$input, "refs/heads/$input", "refs/tags/$input"]; $result = ''; foreach( $paths as $ref ) { $path = "{$this->repoPath}/$ref"; if( is_file( $path ) ) { $size = filesize( $path ); if( $size > 0 ) { $reader = new BufferedReader( $path ); $result = trim( $reader->read( $size ) ); } break; } } if( $result === '' ) { $packedPath = "{$this->repoPath}/packed-refs"; if( is_file( $packedPath ) ) { $result = $this->findInPackedRefs( $packedPath, $input ); } } if( !preg_match( '/^[0-9a-f]{40}$/', $result ) ) { $result = ''; } return $result; } private function findInPackedRefs( string $path, string $input ): string { $targets = [$input, "refs/heads/$input", "refs/tags/$input"]; $size = filesize( $path ); $lines = []; $result = ''; if( $size > 0 ) { $reader = new BufferedReader( $path ); $lines = explode( "\n", $reader->read( $size ) ); } foreach( $lines as $line ) { if( $line !== '' && $line[0] !== '#' && $line[0] !== '^' ) { $parts = explode( ' ', trim( $line ) ); if( count( $parts ) >= 2 && in_array( $parts[1], $targets ) ) { $result = $parts[0]; break; } } } return $result; } }