Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/treetrek.git

Adds caching and binary search

AuthorDave Jarvis <email>
Date2026-02-09 20:15:38 GMT-0800
Commit25ecd97520ff5cdb81183d06e8ccac730ebd2b11
Parent055bcdb
Git.php
private array $fanoutCache = [];
- public function __construct( string $repoPath ) {
- $this->path = rtrim( $repoPath, '/' );
- $this->objPath = $this->path . '/objects';
- $this->packFiles = glob( "{$this->objPath}/pack/*.idx" ) ?: [];
- }
-
- public function __destruct() {
- foreach( $this->fileHandles as $handle ) {
- if( is_resource( $handle ) ) {
- fclose( $handle );
- }
- }
- }
-
- public function getObjectSize( string $sha ): int {
- $prefix = substr( $sha, 0, 2 );
- $suffix = substr( $sha, 2 );
- $loosePath = "{$this->objPath}/{$prefix}/{$suffix}";
-
- $size = file_exists( $loosePath )
- ? $this->getLooseObjectSize( $loosePath )
- : $this->getPackedObjectSize( $sha );
-
- return $size;
- }
-
- private function getLooseObjectSize( string $path ): int {
- $size = 0;
- $fileHandle = @fopen( $path, 'rb' );
-
- if( $fileHandle ) {
- $data = $this->decompressHeader( $fileHandle );
- $header = explode( "\0", $data, 2 )[0];
- $parts = explode( ' ', $header );
- $size = isset( $parts[1] ) ? (int)$parts[1] : 0;
- fclose( $fileHandle );
- }
-
- return $size;
- }
-
- private function decompressHeader( $fileHandle ): string {
- $data = '';
- $inflateContext = inflate_init( ZLIB_ENCODING_DEFLATE );
-
- while( !feof( $fileHandle ) ) {
- $chunk = fread( $fileHandle, self::CHUNK_SIZE );
- $inflated = @inflate_add( $inflateContext, $chunk, ZLIB_NO_FLUSH );
-
- if( $inflated === false ) {
- break;
- }
-
- $data .= $inflated;
-
- if( strpos( $data, "\0" ) !== false ) {
- break;
- }
- }
-
- return $data;
- }
-
- private function getPackedObjectSize( string $sha ): int {
- $info = $this->getPackOffset( $sha );
-
- $size = ($info['offset'] !== -1)
- ? $this->extractPackedSize( $info )
- : 0;
-
- return $size;
- }
-
- private function extractPackedSize( array $info ): int {
- $targetSize = 0;
- $packPath = $info['file'];
-
- if( !isset( $this->fileHandles[$packPath] ) ) {
- $this->fileHandles[$packPath] = @fopen( $packPath, 'rb' );
- }
-
- $packFile = $this->fileHandles[$packPath];
-
- if( $packFile ) {
- fseek( $packFile, $info['offset'] );
- $header = $this->readVarInt( $packFile );
- $type = ($header['byte'] >> 4) & 7;
-
- $targetSize = ($type === 6 || $type === 7)
- ? $this->readDeltaTargetSize( $packFile, $type )
- : $header['value'];
-
- // Handle is cached, do not close
- }
-
- return $targetSize;
- }
-
- private function readVarInt( $fileHandle ): array {
- $byte = ord( fread( $fileHandle, 1 ) );
- $value = $byte & 15;
- $shift = 4;
- $firstByte = $byte;
-
- while( $byte & 128 ) {
- $byte = ord( fread( $fileHandle, 1 ) );
- $value |= (($byte & 127) << $shift);
- $shift += 7;
- }
-
- return ['value' => $value, 'byte' => $firstByte];
- }
-
- private function readDeltaTargetSize( $fileHandle, int $type ): int {
- $dummy = ($type === 6)
- ? $this->skipOffsetDelta( $fileHandle )
- : fread( $fileHandle, 20 );
-
- $inflateContext = inflate_init( ZLIB_ENCODING_DEFLATE );
- $headerData = '';
-
- while( !feof( $fileHandle ) && strlen( $headerData ) < 32 ) {
- $inflated = @inflate_add(
- $inflateContext,
- fread( $fileHandle, 512 ),
- ZLIB_NO_FLUSH
- );
-
- if( $inflated !== false ) {
- $headerData .= $inflated;
- }
- }
-
- $result = 0;
- $position = 0;
-
- if( strlen( $headerData ) > 0 ) {
- $this->skipSize( $headerData, $position );
- $result = $this->readSize( $headerData, $position );
- }
-
- return $result;
- }
-
- public function getMainBranch(): array {
- $result = ['name' => '', 'hash' => ''];
- $branches = [];
- $this->eachBranch( function( $name, $sha ) use( &$branches ) {
- $branches[$name] = $sha;
- } );
-
- foreach( ['main', 'master', 'trunk', 'develop'] as $branch ) {
- if( isset( $branches[$branch] ) ) {
- $result = ['name' => $branch, 'hash' => $branches[$branch]];
- break;
- }
- }
-
- if( $result['name'] === '' ) {
- $firstKey = array_key_first( $branches );
-
- if( $firstKey !== null ) {
- $result = ['name' => $firstKey, 'hash' => $branches[$firstKey]];
- }
- }
-
- return $result;
- }
-
- public function eachBranch( callable $callback ): void {
- $this->scanRefs( 'refs/heads', $callback );
- }
-
- public function eachTag( callable $callback ): void {
- $this->scanRefs( 'refs/tags', $callback );
- }
-
- public function walk( string $refOrSha, callable $callback ): void {
- $sha = $this->resolve( $refOrSha );
- $data = ($sha !== '') ? $this->read( $sha ) : '';
-
- if( preg_match( '/^tree ([0-9a-f]{40})$/m', $data, $matches ) ) {
- $data = $this->read( $matches[1] );
- }
-
- if( $this->isTreeData( $data ) ) {
- $this->processTree( $data, $callback );
- }
- }
-
- private function processTree( string $data, callable $callback ): void {
- $position = 0;
-
- while( $position < strlen( $data ) ) {
- $spacePos = strpos( $data, ' ', $position );
- $nullPos = strpos( $data, "\0", $spacePos );
-
- if( $spacePos === false || $nullPos === false ) {
- break;
- }
-
- $mode = substr( $data, $position, $spacePos - $position );
- $name = substr( $data, $spacePos + 1, $nullPos - $spacePos - 1 );
- $entrySha = bin2hex( substr( $data, $nullPos + 1, 20 ) );
-
- $isDir = ($mode === self::MODE_TREE || $mode === self::MODE_TREE_A);
-
- // Because we cached the handles and fanout table, this is now much faster.
- $size = $isDir ? 0 : $this->getObjectSize( $entrySha );
-
- $callback( new File( $name, $entrySha, $mode, 0, $size ) );
- $position = $nullPos + 21;
- }
- }
-
- private function isTreeData( string $data ): bool {
- $result = false;
- $pattern = '/^(40000|100644|100755|120000|160000) /';
-
- if( strlen( $data ) >= 25 && preg_match( $pattern, $data ) ) {
- $nullPos = strpos( $data, "\0" );
- $result = ($nullPos !== false && ($nullPos + 21 <= strlen( $data )));
- }
-
- return $result;
- }
-
- public function history( string $ref, int $limit, callable $cb ): void {
- $currentSha = $this->resolve( $ref );
- $count = 0;
-
- while( $currentSha !== '' && $count < $limit ) {
- $data = $this->read( $currentSha );
-
- if( $data === '' ) {
- break;
- }
-
- $pos = strpos( $data, "\n\n" );
- $message = ($pos !== false) ? substr( $data, $pos + 2 ) : '';
- preg_match( '/^author (.*) <(.*)> (\d+)/m', $data, $m );
-
- $cb( (object)[
- 'sha' => $currentSha,
- 'message' => trim( $message ),
- 'author' => $m[1] ?? 'Unknown',
- 'email' => $m[2] ?? '',
- 'date' => (int)($m[3] ?? 0)
- ] );
-
- $currentSha = preg_match( '/^parent ([0-9a-f]{40})$/m', $data, $ms )
- ? $ms[1] : '';
- $count++;
- }
- }
-
- public function stream( string $sha, callable $callback ): void {
- $data = $this->read( $sha );
-
- if( $data !== '' ) {
- $callback( $data );
- }
- }
-
- public function resolve( string $input ): string {
- $result = '';
-
- if( preg_match( '/^[0-9a-f]{40}$/', $input ) ) {
- $result = $input;
- } elseif( $input === 'HEAD' &&
- file_exists( $headFile = "{$this->path}/HEAD" ) ) {
- $head = trim( file_get_contents( $headFile ) );
- $result = (strpos( $head, 'ref: ' ) === 0)
- ? $this->resolve( substr( $head, 5 ) ) : $head;
- } else {
- $result = $this->resolveRef( $input );
- }
-
- return $result;
- }
-
- private function resolveRef( string $input ): string {
- $found = '';
- $refPaths = [$input, "refs/heads/$input", "refs/tags/$input"];
-
- foreach( $refPaths as $path ) {
- if( file_exists( $filePath = "{$this->path}/$path" ) ) {
- $found = trim( file_get_contents( $filePath ) );
- break;
- }
- }
-
- if( $found === '' &&
- file_exists( $packed = "{$this->path}/packed-refs" ) ) {
- $found = $this->findInPackedRefs( $packed, $input );
- }
-
- return $found;
- }
-
- private function findInPackedRefs( string $path, string $input ): string {
- $result = '';
- $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 ) ) {
- $result = $parts[0];
- break;
- }
- }
-
- return $result;
- }
-
- public function read( string $sha ): string {
- $result = '';
- $prefix = substr( $sha, 0, 2 );
- $suffix = substr( $sha, 2 );
- $loose = "{$this->objPath}/{$prefix}/{$suffix}";
-
- if( file_exists( $loose ) ) {
- $raw = file_get_contents( $loose );
- $inflated = $raw ? @gzuncompress( $raw ) : false;
- $result = $inflated ? explode( "\0", $inflated, 2 )[1] : '';
- } else {
- $result = $this->fromPack( $sha );
- }
-
- return $result;
- }
-
- private function fromPack( string $sha ): string {
- $info = $this->getPackOffset( $sha );
- $result = '';
-
- if( $info['offset'] !== -1 ) {
- $packPath = $info['file'];
-
- if( !isset( $this->fileHandles[$packPath] ) ) {
- $this->fileHandles[$packPath] = @fopen( $packPath, 'rb' );
- }
-
- $packFile = $this->fileHandles[$packPath];
-
- if( $packFile ) {
- $result = $this->readPackEntry( $packFile, $info['offset'] );
- // Handle is cached, do not close
- }
- }
-
- return $result;
- }
-
- private function getPackOffset( string $sha ): array {
- $result = ['file' => '', 'offset' => -1];
-
- if( strlen( $sha ) === 40 && ctype_xdigit( $sha ) ) {
- $binSha = hex2bin( $sha );
-
- foreach( $this->packFiles as $idxFile ) {
- $offset = $this->findInPack( $idxFile, $binSha );
-
- if( $offset !== -1 ) {
- $result = [
- 'file' => str_replace( '.idx', '.pack', $idxFile ),
- 'offset' => $offset
- ];
-
- break;
- }
- }
- }
-
- return $result;
- }
-
- private function findInPack( string $idxFile, string $binSha ): int {
- if( !isset( $this->fileHandles[$idxFile] ) ) {
- $handle = @fopen( $idxFile, 'rb' );
- if( !$handle ) return -1;
-
- $this->fileHandles[$idxFile] = $handle;
-
- // Cache the fanout table (first 1024 bytes after 8-byte header)
- fseek( $handle, 0 );
- if( fread( $handle, 8 ) === "\377tOc\0\0\0\2" ) {
- $this->fanoutCache[$idxFile] = array_values( unpack( 'N*', fread( $handle, 1024 ) ) );
- } else {
- $this->fanoutCache[$idxFile] = null;
- }
- }
-
- $handle = $this->fileHandles[$idxFile];
- $fanout = $this->fanoutCache[$idxFile] ?? null;
-
- if( !$handle || !$fanout ) return -1;
-
- $firstByte = ord( $binSha[0] );
- $start = ($firstByte === 0) ? 0 : $fanout[$firstByte - 1];
- $end = $fanout[$firstByte];
- $offset = -1;
-
- if( $end > $start ) {
- // The last entry in fanout is the total object count
- $total = $fanout[255];
-
- $foundIdx = $this->searchShaTable(
- $handle,
- $start,
- $end,
- $binSha
- );
-
- if( $foundIdx !== -1 ) {
- $offset = $this->getOffsetFromTable(
- $handle,
- $foundIdx,
- $total
- );
- }
- }
-
- return $offset;
- }
-
- private function searchShaTable(
- $fileHandle,
- int $start,
- int $end,
- string $binSha
- ): int {
- $result = -1;
- fseek( $fileHandle, 1032 + ($start * 20) );
-
- for( $i = $start; $i < $end; $i++ ) {
- if( fread( $fileHandle, 20 ) === $binSha ) {
- $result = $i;
- break;
- }
- }
-
- return $result;
- }
-
- private function getOffsetFromTable( $fileHandle, int $idx, int $total ): int {
- $pos = 1032 + ($total * 20) + ($total * 4) + ($idx * 4);
- fseek( $fileHandle, $pos );
- $data = fread( $fileHandle, 4 );
- $offset = $data ? unpack( 'N', $data )[1] : 0;
-
- if( $offset & 0x80000000 ) {
- $base = 1032 + ($total * 24) + ($total * 4);
- fseek( $fileHandle, $base + (($offset & 0x7FFFFFFF) * 8) );
- $data64 = fread( $fileHandle, 8 );
- $offset = $data64 ? unpack( 'J', $data64 )[1] : 0;
- }
-
- return (int)$offset;
- }
-
- private function readPackEntry( $fileHandle, int $offset ): string {
- fseek( $fileHandle, $offset );
- $header = $this->readVarInt( $fileHandle );
- $type = ($header['byte'] >> 4) & 7;
-
- if( $type === 6 ) return $this->handleOfsDelta( $fileHandle, $offset );
- if( $type === 7 ) return $this->handleRefDelta( $fileHandle );
-
- $inf = inflate_init( ZLIB_ENCODING_DEFLATE );
- $res = '';
-
- while( !feof( $fileHandle ) ) {
- $chunk = fread( $fileHandle, 8192 );
- $data = @inflate_add( $inf, $chunk );
-
- if( $data !== false ) $res .= $data;
- if( $data === false || ($inf && inflate_get_status( $inf ) === ZLIB_STREAM_END) ) break;
- }
-
- return $res;
- }
-
- private function deltaCopy(
- string $base, string $delta, int &$position, int $opcode
- ): string {
- $offset = 0;
- $length = 0;
-
- if( $opcode & 0x01 ) $offset |= ord( $delta[$position++] );
- if( $opcode & 0x02 ) $offset |= ord( $delta[$position++] ) << 8;
- if( $opcode & 0x04 ) $offset |= ord( $delta[$position++] ) << 16;
- if( $opcode & 0x08 ) $offset |= ord( $delta[$position++] ) << 24;
-
- if( $opcode & 0x10 ) $length |= ord( $delta[$position++] );
- if( $opcode & 0x20 ) $length |= ord( $delta[$position++] ) << 8;
- if( $opcode & 0x40 ) $length |= ord( $delta[$position++] ) << 16;
-
- if( $length === 0 ) $length = 0x10000;
-
- return substr( $base, $offset, $length );
- }
-
- private function handleOfsDelta( $fileHandle, int $offset ): string {
- $byte = ord( fread( $fileHandle, 1 ) );
- $negOffset = $byte & 127;
-
- while( $byte & 128 ) {
- $byte = ord( fread( $fileHandle, 1 ) );
- $negOffset = (($negOffset + 1) << 7) | ($byte & 127);
- }
-
- // Capture the current position (start of delta data)
- $currentPos = ftell( $fileHandle );
-
- // Recursive call moves the file pointer
- $base = $this->readPackEntry( $fileHandle, $offset - $negOffset );
-
- // Restore the position to read the delta data
+ // Cache for the last successfully used pack file (Locality Optimization)
+ private ?string $lastPack = null;
+
+ public function __construct( string $repoPath ) {
+ $this->path = rtrim( $repoPath, '/' );
+ $this->objPath = $this->path . '/objects';
+ $this->packFiles = glob( "{$this->objPath}/pack/*.idx" ) ?: [];
+ }
+
+ public function __destruct() {
+ foreach( $this->fileHandles as $handle ) {
+ if( is_resource( $handle ) ) {
+ fclose( $handle );
+ }
+ }
+ }
+
+ public function getObjectSize( string $sha ): int {
+ $prefix = substr( $sha, 0, 2 );
+ $suffix = substr( $sha, 2 );
+ $loosePath = "{$this->objPath}/{$prefix}/{$suffix}";
+
+ $size = file_exists( $loosePath )
+ ? $this->getLooseObjectSize( $loosePath )
+ : $this->getPackedObjectSize( $sha );
+
+ return $size;
+ }
+
+ private function getLooseObjectSize( string $path ): int {
+ $size = 0;
+ $fileHandle = @fopen( $path, 'rb' );
+
+ if( $fileHandle ) {
+ $data = $this->decompressHeader( $fileHandle );
+ $header = explode( "\0", $data, 2 )[0];
+ $parts = explode( ' ', $header );
+ $size = isset( $parts[1] ) ? (int)$parts[1] : 0;
+ fclose( $fileHandle );
+ }
+
+ return $size;
+ }
+
+ private function decompressHeader( $fileHandle ): string {
+ $data = '';
+ $inflateContext = inflate_init( ZLIB_ENCODING_DEFLATE );
+
+ while( !feof( $fileHandle ) ) {
+ $chunk = fread( $fileHandle, self::CHUNK_SIZE );
+ $inflated = @inflate_add( $inflateContext, $chunk, ZLIB_NO_FLUSH );
+
+ if( $inflated === false ) {
+ break;
+ }
+
+ $data .= $inflated;
+
+ if( strpos( $data, "\0" ) !== false ) {
+ break;
+ }
+ }
+
+ return $data;
+ }
+
+ private function getPackedObjectSize( string $sha ): int {
+ $info = $this->getPackOffset( $sha );
+
+ $size = ($info['offset'] !== -1)
+ ? $this->extractPackedSize( $info )
+ : 0;
+
+ return $size;
+ }
+
+ private function extractPackedSize( array $info ): int {
+ $targetSize = 0;
+ $packPath = $info['file'];
+
+ if( !isset( $this->fileHandles[$packPath] ) ) {
+ $this->fileHandles[$packPath] = @fopen( $packPath, 'rb' );
+ }
+
+ $packFile = $this->fileHandles[$packPath];
+
+ if( $packFile ) {
+ fseek( $packFile, $info['offset'] );
+ $header = $this->readVarInt( $packFile );
+ $type = ($header['byte'] >> 4) & 7;
+
+ $targetSize = ($type === 6 || $type === 7)
+ ? $this->readDeltaTargetSize( $packFile, $type )
+ : $header['value'];
+ }
+
+ return $targetSize;
+ }
+
+ private function readVarInt( $fileHandle ): array {
+ $byte = ord( fread( $fileHandle, 1 ) );
+ $value = $byte & 15;
+ $shift = 4;
+ $firstByte = $byte;
+
+ while( $byte & 128 ) {
+ $byte = ord( fread( $fileHandle, 1 ) );
+ $value |= (($byte & 127) << $shift);
+ $shift += 7;
+ }
+
+ return ['value' => $value, 'byte' => $firstByte];
+ }
+
+ private function readDeltaTargetSize( $fileHandle, int $type ): int {
+ $dummy = ($type === 6)
+ ? $this->skipOffsetDelta( $fileHandle )
+ : fread( $fileHandle, 20 );
+
+ $inflateContext = inflate_init( ZLIB_ENCODING_DEFLATE );
+ $headerData = '';
+
+ while( !feof( $fileHandle ) && strlen( $headerData ) < 32 ) {
+ $inflated = @inflate_add(
+ $inflateContext,
+ fread( $fileHandle, 512 ),
+ ZLIB_NO_FLUSH
+ );
+
+ if( $inflated !== false ) {
+ $headerData .= $inflated;
+ }
+ }
+
+ $result = 0;
+ $position = 0;
+
+ if( strlen( $headerData ) > 0 ) {
+ $this->skipSize( $headerData, $position );
+ $result = $this->readSize( $headerData, $position );
+ }
+
+ return $result;
+ }
+
+ public function getMainBranch(): array {
+ $result = ['name' => '', 'hash' => ''];
+ $branches = [];
+ $this->eachBranch( function( $name, $sha ) use( &$branches ) {
+ $branches[$name] = $sha;
+ } );
+
+ foreach( ['main', 'master', 'trunk', 'develop'] as $branch ) {
+ if( isset( $branches[$branch] ) ) {
+ $result = ['name' => $branch, 'hash' => $branches[$branch]];
+ break;
+ }
+ }
+
+ if( $result['name'] === '' ) {
+ $firstKey = array_key_first( $branches );
+
+ if( $firstKey !== null ) {
+ $result = ['name' => $firstKey, 'hash' => $branches[$firstKey]];
+ }
+ }
+
+ return $result;
+ }
+
+ public function eachBranch( callable $callback ): void {
+ $this->scanRefs( 'refs/heads', $callback );
+ }
+
+ public function eachTag( callable $callback ): void {
+ $this->scanRefs( 'refs/tags', $callback );
+ }
+
+ public function walk( string $refOrSha, callable $callback ): void {
+ $sha = $this->resolve( $refOrSha );
+ $data = ($sha !== '') ? $this->read( $sha ) : '';
+
+ if( preg_match( '/^tree ([0-9a-f]{40})$/m', $data, $matches ) ) {
+ $data = $this->read( $matches[1] );
+ }
+
+ if( $this->isTreeData( $data ) ) {
+ $this->processTree( $data, $callback );
+ }
+ }
+
+ private function processTree( string $data, callable $callback ): void {
+ $position = 0;
+
+ while( $position < strlen( $data ) ) {
+ $spacePos = strpos( $data, ' ', $position );
+ $nullPos = strpos( $data, "\0", $spacePos );
+
+ if( $spacePos === false || $nullPos === false ) {
+ break;
+ }
+
+ $mode = substr( $data, $position, $spacePos - $position );
+ $name = substr( $data, $spacePos + 1, $nullPos - $spacePos - 1 );
+ $entrySha = bin2hex( substr( $data, $nullPos + 1, 20 ) );
+
+ $isDir = ($mode === self::MODE_TREE || $mode === self::MODE_TREE_A);
+
+ // Fast size calculation due to caching + binary search
+ $size = $isDir ? 0 : $this->getObjectSize( $entrySha );
+
+ $callback( new File( $name, $entrySha, $mode, 0, $size ) );
+ $position = $nullPos + 21;
+ }
+ }
+
+ private function isTreeData( string $data ): bool {
+ $result = false;
+ $pattern = '/^(40000|100644|100755|120000|160000) /';
+
+ if( strlen( $data ) >= 25 && preg_match( $pattern, $data ) ) {
+ $nullPos = strpos( $data, "\0" );
+ $result = ($nullPos !== false && ($nullPos + 21 <= strlen( $data )));
+ }
+
+ return $result;
+ }
+
+ public function history( string $ref, int $limit, callable $cb ): void {
+ $currentSha = $this->resolve( $ref );
+ $count = 0;
+
+ while( $currentSha !== '' && $count < $limit ) {
+ $data = $this->read( $currentSha );
+
+ if( $data === '' ) {
+ break;
+ }
+
+ $pos = strpos( $data, "\n\n" );
+ $message = ($pos !== false) ? substr( $data, $pos + 2 ) : '';
+ preg_match( '/^author (.*) <(.*)> (\d+)/m', $data, $m );
+
+ $cb( (object)[
+ 'sha' => $currentSha,
+ 'message' => trim( $message ),
+ 'author' => $m[1] ?? 'Unknown',
+ 'email' => $m[2] ?? '',
+ 'date' => (int)($m[3] ?? 0)
+ ] );
+
+ $currentSha = preg_match( '/^parent ([0-9a-f]{40})$/m', $data, $ms )
+ ? $ms[1] : '';
+ $count++;
+ }
+ }
+
+ public function stream( string $sha, callable $callback ): void {
+ $data = $this->read( $sha );
+
+ if( $data !== '' ) {
+ $callback( $data );
+ }
+ }
+
+ public function resolve( string $input ): string {
+ $result = '';
+
+ if( preg_match( '/^[0-9a-f]{40}$/', $input ) ) {
+ $result = $input;
+ } elseif( $input === 'HEAD' &&
+ file_exists( $headFile = "{$this->path}/HEAD" ) ) {
+ $head = trim( file_get_contents( $headFile ) );
+ $result = (strpos( $head, 'ref: ' ) === 0)
+ ? $this->resolve( substr( $head, 5 ) ) : $head;
+ } else {
+ $result = $this->resolveRef( $input );
+ }
+
+ return $result;
+ }
+
+ private function resolveRef( string $input ): string {
+ $found = '';
+ $refPaths = [$input, "refs/heads/$input", "refs/tags/$input"];
+
+ foreach( $refPaths as $path ) {
+ if( file_exists( $filePath = "{$this->path}/$path" ) ) {
+ $found = trim( file_get_contents( $filePath ) );
+ break;
+ }
+ }
+
+ if( $found === '' &&
+ file_exists( $packed = "{$this->path}/packed-refs" ) ) {
+ $found = $this->findInPackedRefs( $packed, $input );
+ }
+
+ return $found;
+ }
+
+ private function findInPackedRefs( string $path, string $input ): string {
+ $result = '';
+ $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 ) ) {
+ $result = $parts[0];
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ public function read( string $sha ): string {
+ $result = '';
+ $prefix = substr( $sha, 0, 2 );
+ $suffix = substr( $sha, 2 );
+ $loose = "{$this->objPath}/{$prefix}/{$suffix}";
+
+ if( file_exists( $loose ) ) {
+ $raw = file_get_contents( $loose );
+ $inflated = $raw ? @gzuncompress( $raw ) : false;
+ $result = $inflated ? explode( "\0", $inflated, 2 )[1] : '';
+ } else {
+ $result = $this->fromPack( $sha );
+ }
+
+ return $result;
+ }
+
+ private function fromPack( string $sha ): string {
+ $info = $this->getPackOffset( $sha );
+ $result = '';
+
+ if( $info['offset'] !== -1 ) {
+ $packPath = $info['file'];
+
+ if( !isset( $this->fileHandles[$packPath] ) ) {
+ $this->fileHandles[$packPath] = @fopen( $packPath, 'rb' );
+ }
+
+ $packFile = $this->fileHandles[$packPath];
+
+ if( $packFile ) {
+ $result = $this->readPackEntry( $packFile, $info['offset'] );
+ }
+ }
+
+ return $result;
+ }
+
+ private function getPackOffset( string $sha ): array {
+ $result = ['file' => '', 'offset' => -1];
+
+ if( strlen( $sha ) === 40 && ctype_xdigit( $sha ) ) {
+ $binSha = hex2bin( $sha );
+
+ // Locality Cache: Check the last successful pack first
+ if( $this->lastPack ) {
+ $offset = $this->findInPack( $this->lastPack, $binSha );
+ if( $offset !== -1 ) {
+ return [
+ 'file' => str_replace( '.idx', '.pack', $this->lastPack ),
+ 'offset' => $offset
+ ];
+ }
+ }
+
+ foreach( $this->packFiles as $idxFile ) {
+ if( $idxFile === $this->lastPack ) {
+ continue;
+ }
+
+ $offset = $this->findInPack( $idxFile, $binSha );
+
+ if( $offset !== -1 ) {
+ $this->lastPack = $idxFile; // Update cache
+ $result = [
+ 'file' => str_replace( '.idx', '.pack', $idxFile ),
+ 'offset' => $offset
+ ];
+ break;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ private function findInPack( string $idxFile, string $binSha ): int {
+ // 1. Ensure file handle and fanout table are cached
+ if( !isset( $this->fileHandles[$idxFile] ) ) {
+ $handle = @fopen( $idxFile, 'rb' );
+ if( !$handle ) return -1;
+
+ $this->fileHandles[$idxFile] = $handle;
+
+ // Cache fanout table (first 1KB after header)
+ fseek( $handle, 0 );
+ if( fread( $handle, 8 ) === "\377tOc\0\0\0\2" ) {
+ $this->fanoutCache[$idxFile] = array_values( unpack( 'N*', fread( $handle, 1024 ) ) );
+ } else {
+ $this->fanoutCache[$idxFile] = null;
+ }
+ }
+
+ $handle = $this->fileHandles[$idxFile];
+ $fanout = $this->fanoutCache[$idxFile] ?? null;
+
+ if( !$handle || !$fanout ) return -1;
+
+ // 2. Determine search range using fanout
+ $firstByte = ord( $binSha[0] );
+ $start = ($firstByte === 0) ? 0 : $fanout[$firstByte - 1];
+ $end = $fanout[$firstByte];
+ $offset = -1;
+
+ if( $end > $start ) {
+ $total = $fanout[255];
+
+ // 3. Binary search
+ $foundIdx = $this->searchShaTable(
+ $handle,
+ $start,
+ $end,
+ $binSha
+ );
+
+ if( $foundIdx !== -1 ) {
+ $offset = $this->getOffsetFromTable(
+ $handle,
+ $foundIdx,
+ $total
+ );
+ }
+ }
+
+ return $offset;
+ }
+
+ private function searchShaTable(
+ $fileHandle,
+ int $start,
+ int $end,
+ string $binSha
+ ): int {
+ $low = $start;
+ $high = $end - 1;
+
+ // Binary search within the bucket
+ while( $low <= $high ) {
+ $mid = ($low + $high) >> 1;
+
+ fseek( $fileHandle, 1032 + ($mid * 20) );
+ $currentSha = fread( $fileHandle, 20 );
+
+ if( $currentSha < $binSha ) {
+ $low = $mid + 1;
+ } elseif( $currentSha > $binSha ) {
+ $high = $mid - 1;
+ } else {
+ return $mid;
+ }
+ }
+
+ return -1;
+ }
+
+ private function getOffsetFromTable( $fileHandle, int $idx, int $total ): int {
+ $pos = 1032 + ($total * 20) + ($total * 4) + ($idx * 4);
+ fseek( $fileHandle, $pos );
+ $data = fread( $fileHandle, 4 );
+ $offset = $data ? unpack( 'N', $data )[1] : 0;
+
+ if( $offset & 0x80000000 ) {
+ $base = 1032 + ($total * 24) + ($total * 4);
+ fseek( $fileHandle, $base + (($offset & 0x7FFFFFFF) * 8) );
+ $data64 = fread( $fileHandle, 8 );
+ $offset = $data64 ? unpack( 'J', $data64 )[1] : 0;
+ }
+
+ return (int)$offset;
+ }
+
+ private function readPackEntry( $fileHandle, int $offset ): string {
+ fseek( $fileHandle, $offset );
+ $header = $this->readVarInt( $fileHandle );
+ $type = ($header['byte'] >> 4) & 7;
+
+ if( $type === 6 ) return $this->handleOfsDelta( $fileHandle, $offset );
+ if( $type === 7 ) return $this->handleRefDelta( $fileHandle );
+
+ $inf = inflate_init( ZLIB_ENCODING_DEFLATE );
+ $res = '';
+
+ while( !feof( $fileHandle ) ) {
+ $chunk = fread( $fileHandle, 8192 );
+ $data = @inflate_add( $inf, $chunk );
+
+ if( $data !== false ) $res .= $data;
+ if( $data === false || ($inf && inflate_get_status( $inf ) === ZLIB_STREAM_END) ) break;
+ }
+
+ return $res;
+ }
+
+ private function deltaCopy(
+ string $base, string $delta, int &$position, int $opcode
+ ): string {
+ $offset = 0;
+ $length = 0;
+
+ if( $opcode & 0x01 ) $offset |= ord( $delta[$position++] );
+ if( $opcode & 0x02 ) $offset |= ord( $delta[$position++] ) << 8;
+ if( $opcode & 0x04 ) $offset |= ord( $delta[$position++] ) << 16;
+ if( $opcode & 0x08 ) $offset |= ord( $delta[$position++] ) << 24;
+
+ if( $opcode & 0x10 ) $length |= ord( $delta[$position++] );
+ if( $opcode & 0x20 ) $length |= ord( $delta[$position++] ) << 8;
+ if( $opcode & 0x40 ) $length |= ord( $delta[$position++] ) << 16;
+
+ if( $length === 0 ) $length = 0x10000;
+
+ return substr( $base, $offset, $length );
+ }
+
+ private function handleOfsDelta( $fileHandle, int $offset ): string {
+ $byte = ord( fread( $fileHandle, 1 ) );
+ $negOffset = $byte & 127;
+
+ while( $byte & 128 ) {
+ $byte = ord( fread( $fileHandle, 1 ) );
+ $negOffset = (($negOffset + 1) << 7) | ($byte & 127);
+ }
+
+ $currentPos = ftell( $fileHandle );
+ $base = $this->readPackEntry( $fileHandle, $offset - $negOffset );
fseek( $fileHandle, $currentPos );
Delta545 lines added, 524 lines removed, 21-line increase