<?php require_once __DIR__ . '/PackStreamManager.php'; require_once __DIR__ . '/PackLocator.php'; require_once __DIR__ . '/DeltaDecoder.php'; require_once __DIR__ . '/PackEntryReader.php'; require_once __DIR__ . '/PackContext.php'; class GitPacks { private const MAX_RAM = 8388608; private PackStreamManager $manager; private PackLocator $locator; private PackEntryReader $reader; private array $cacheLoc = [ 'sha' => '', 'file' => '', 'off' => 0 ]; public function __construct( string $objectsPath ) { $this->manager = new PackStreamManager(); $this->locator = new PackLocator( $this->manager, $objectsPath ); $this->reader = new PackEntryReader( new DeltaDecoder() ); } private function locate( string $sha, callable $callback ): void { if( $this->cacheLoc['sha'] === $sha ) { $callback( $this->cacheLoc['file'], $this->cacheLoc['off'] ); } else { $this->locator->locate( $sha, function( string $packFile, int $offset ) use ( $sha, $callback ): void { $this->cacheLoc = [ 'sha' => $sha, 'file' => $packFile, 'off' => $offset ]; $callback( $packFile, $offset ); } ); } } public function getEntryMeta( string $sha ): array { $result = [ 'type' => 0, 'size' => 0, 'file' => '', 'offset' => 0, ]; $this->locate( $sha, function( string $packFile, int $offset ) use ( &$result ): void { $context = $this->createContext( $packFile, $offset, 0 ); $meta = $this->reader->getEntryMeta( $context ); $result = $meta; $result['file'] = $packFile; $result['offset'] = $offset; } ); return $result; } public function peek( string $sha, int $len = 12 ): string { $result = ''; $this->locate( $sha, function( string $packFile, int $offset ) use ( &$result, $len ): void { $context = $this->createContext( $packFile, $offset, 0 ); $result = $this->reader->read( $context, $len, function( string $baseSha, int $cap ): string { return $this->peek( $baseSha, $cap ); } ); } ); return $result; } public function read( string $sha ): string { $result = ''; $this->locate( $sha, function( string $packFile, int $offset ) use ( &$result ): void { $context = $this->createContext( $packFile, $offset, 0 ); $size = $this->reader->getSize( $context ); if( $size <= self::MAX_RAM ) { $result = $this->reader->read( $context, 0, function( string $baseSha, int $cap ): string { return $cap > 0 ? $this->peek( $baseSha, $cap ) : $this->read( $baseSha ); } ); } } ); return $result; } public function stream( string $sha, callable $callback ): bool { $result = false; foreach( $this->streamGenerator( $sha ) as $chunk ) { $callback( $chunk ); $result = true; } return $result; } public function streamGenerator( string $sha ): Generator { yield from $this->streamShaGenerator( $sha, 0 ); } public function streamRawCompressed( string $sha ): Generator { $found = false; $file = ''; $off = 0; $this->locate( $sha, function( string $packFile, int $offset ) use ( &$found, &$file, &$off ): void { $found = true; $file = $packFile; $off = $offset; } ); if( $found ) { $context = $this->createContext( $file, $off, 0 ); yield from $this->reader->streamRawCompressed( $context ); } } public function streamRawDelta( string $sha ): Generator { $found = false; $file = ''; $off = 0; $this->locate( $sha, function( string $packFile, int $offset ) use ( &$found, &$file, &$off ): void { $found = true; $file = $packFile; $off = $offset; } ); if( $found ) { $context = $this->createContext( $file, $off, 0 ); yield from $this->reader->streamRawDelta( $context ); } } private function streamShaGenerator( string $sha, int $depth ): Generator { $found = false; $file = ''; $off = 0; $this->locate( $sha, function( string $packFile, int $offset ) use ( &$found, &$file, &$off ): void { $found = true; $file = $packFile; $off = $offset; } ); if( $found ) { $context = $this->createContext( $file, $off, $depth ); yield from $this->reader->streamEntryGenerator( $context ); } } public function getSize( string $sha ): int { $result = 0; $this->locate( $sha, function( string $packFile, int $offset ) use ( &$result ): void { $context = $this->createContext( $packFile, $offset, 0 ); $result = $this->reader->getSize( $context ); } ); return $result; } private function createContext( string $packFile, int $offset, int $depth ): PackContext { return new PackContext( $this->manager, $packFile, $offset, $depth, function( string $baseSha ): int { return $this->getSize( $baseSha ); }, function( string $baseSha, int $baseDepth ): Generator { yield from $this->streamShaGenerator( $baseSha, $baseDepth ); } ); } }