| Author | Dave Jarvis <email> |
|---|---|
| Date | 2026-02-21 20:26:34 GMT-0800 |
| Commit | 615160a0f6f0f3819edb6f6d477aff96715a93a9 |
| Parent | b1ae5b6 |
| if( !$doneBuffer ) { | ||
| if( $state < 2 ) { | ||
| - $pos = $offset; | ||
| - $found = false; | ||
| - | ||
| - while( !$found && $pos < $offset + $len ) { | ||
| - if( !(ord( $buffer[$pos] ) & 128) ) { | ||
| - $found = true; | ||
| - } | ||
| - | ||
| - $pos++; | ||
| - } | ||
| - | ||
| - if( $found ) { | ||
| - $offset = $pos; | ||
| - $state++; | ||
| - } else { | ||
| - $doneBuffer = true; | ||
| - } | ||
| + $this->advanceToInstructions( | ||
| + $buffer, | ||
| + $offset, | ||
| + $len, | ||
| + $state, | ||
| + $doneBuffer | ||
| + ); | ||
| } else { | ||
| - $op = ord( $buffer[$offset] ); | ||
| + yield from $this->processInstruction( | ||
| + $buffer, | ||
| + $offset, | ||
| + $len, | ||
| + $doneBuffer, | ||
| + $isStream, | ||
| + $base, | ||
| + $yieldBuffer | ||
| + ); | ||
| + } | ||
| + } | ||
| + } | ||
| + } | ||
| - if( $op & 128 ) { | ||
| - $need = $this->calculateCopyInstructionSize( $op ); | ||
| + if( $yieldBuffer !== '' ) { | ||
| + yield $yieldBuffer; | ||
| + } | ||
| + } | ||
| - if( $len < 1 + $need ) { | ||
| - $doneBuffer = true; | ||
| - } | ||
| + private function advanceToInstructions( | ||
| + string $buffer, | ||
| + int &$offset, | ||
| + int $len, | ||
| + int &$state, | ||
| + bool &$doneBuffer | ||
| + ): void { | ||
| + $pos = $offset; | ||
| + $found = false; | ||
| - if( !$doneBuffer ) { | ||
| - $off = 0; | ||
| - $ln = 0; | ||
| - $ptr = $offset + 1; | ||
| + while( !$found && $pos < $offset + $len ) { | ||
| + if( !(ord( $buffer[$pos] ) & 128) ) { | ||
| + $found = true; | ||
| + } | ||
| - $this->parseCopyInstruction( | ||
| - $op, | ||
| - $buffer, | ||
| - $ptr, | ||
| - $off, | ||
| - $ln | ||
| - ); | ||
| + $pos++; | ||
| + } | ||
| - if( $isStream ) { | ||
| - $base->seek( $off ); | ||
| + if( $found ) { | ||
| + $offset = $pos; | ||
| + $state++; | ||
| + } else { | ||
| + $doneBuffer = true; | ||
| + } | ||
| + } | ||
| - $rem = $ln; | ||
| + private function processInstruction( | ||
| + string $buffer, | ||
| + int &$offset, | ||
| + int $len, | ||
| + bool &$doneBuffer, | ||
| + bool $isStream, | ||
| + mixed $base, | ||
| + string &$yieldBuffer | ||
| + ): Generator { | ||
| + $op = ord( $buffer[$offset] ); | ||
| - while( $rem > 0 ) { | ||
| - $slc = $base->read( min( self::CHUNK_SIZE, $rem ) ); | ||
| + if( $op & 128 ) { | ||
| + yield from $this->processCopyInstruction( | ||
| + $op, | ||
| + $buffer, | ||
| + $offset, | ||
| + $len, | ||
| + $doneBuffer, | ||
| + $isStream, | ||
| + $base, | ||
| + $yieldBuffer | ||
| + ); | ||
| + } else { | ||
| + yield from $this->processInsertInstruction( | ||
| + $op, | ||
| + $buffer, | ||
| + $offset, | ||
| + $len, | ||
| + $doneBuffer, | ||
| + $yieldBuffer | ||
| + ); | ||
| + } | ||
| + } | ||
| - if( $slc === '' ) { | ||
| - $rem = 0; | ||
| - } else { | ||
| - $yieldBuffer .= $slc; | ||
| - $rem -= strlen( $slc ); | ||
| + private function processCopyInstruction( | ||
| + int $op, | ||
| + string $buffer, | ||
| + int &$offset, | ||
| + int $len, | ||
| + bool &$doneBuffer, | ||
| + bool $isStream, | ||
| + mixed $base, | ||
| + string &$yieldBuffer | ||
| + ): Generator { | ||
| + $need = $this->calculateCopyInstructionSize( $op ); | ||
| - if( strlen( $yieldBuffer ) >= self::CHUNK_SIZE ) { | ||
| - yield $yieldBuffer; | ||
| + if( $len < 1 + $need ) { | ||
| + $doneBuffer = true; | ||
| + } | ||
| - $yieldBuffer = ''; | ||
| - } | ||
| - } | ||
| - } | ||
| - } else { | ||
| - $yieldBuffer .= substr( $base, $off, $ln ); | ||
| + if( !$doneBuffer ) { | ||
| + $off = 0; | ||
| + $ln = 0; | ||
| + $ptr = $offset + 1; | ||
| - if( strlen( $yieldBuffer ) >= self::CHUNK_SIZE ) { | ||
| - yield $yieldBuffer; | ||
| + $this->parseCopyInstruction( $op, $buffer, $ptr, $off, $ln ); | ||
| - $yieldBuffer = ''; | ||
| - } | ||
| - } | ||
| + if( $isStream ) { | ||
| + $base->seek( $off ); | ||
| - $offset += 1 + $need; | ||
| - } | ||
| - } else { | ||
| - $ln = $op & 127; | ||
| + $rem = $ln; | ||
| - if( $len < 1 + $ln ) { | ||
| - $doneBuffer = true; | ||
| - } | ||
| + while( $rem > 0 ) { | ||
| + $slc = $base->read( min( self::CHUNK_SIZE, $rem ) ); | ||
| - if( !$doneBuffer ) { | ||
| - $yieldBuffer .= substr( $buffer, $offset + 1, $ln ); | ||
| - $offset += 1 + $ln; | ||
| + if( $slc === '' ) { | ||
| + $rem = 0; | ||
| + } else { | ||
| + $yieldBuffer .= $slc; | ||
| + $rem -= strlen( $slc ); | ||
| - if( strlen( $yieldBuffer ) >= self::CHUNK_SIZE ) { | ||
| - yield $yieldBuffer; | ||
| + if( strlen( $yieldBuffer ) >= self::CHUNK_SIZE ) { | ||
| + yield $yieldBuffer; | ||
| - $yieldBuffer = ''; | ||
| - } | ||
| - } | ||
| + $yieldBuffer = ''; | ||
| } | ||
| } | ||
| + } | ||
| + } else { | ||
| + $yieldBuffer .= substr( $base, $off, $ln ); | ||
| + | ||
| + if( strlen( $yieldBuffer ) >= self::CHUNK_SIZE ) { | ||
| + yield $yieldBuffer; | ||
| + | ||
| + $yieldBuffer = ''; | ||
| } | ||
| } | ||
| + | ||
| + $offset += 1 + $need; | ||
| } | ||
| + } | ||
| - if( $yieldBuffer !== '' ) { | ||
| - yield $yieldBuffer; | ||
| + private function processInsertInstruction( | ||
| + int $op, | ||
| + string $buffer, | ||
| + int &$offset, | ||
| + int $len, | ||
| + bool &$doneBuffer, | ||
| + string &$yieldBuffer | ||
| + ): Generator { | ||
| + $ln = $op & 127; | ||
| + | ||
| + if( $len < 1 + $ln ) { | ||
| + $doneBuffer = true; | ||
| + } | ||
| + | ||
| + if( !$doneBuffer ) { | ||
| + $yieldBuffer .= substr( $buffer, $offset + 1, $ln ); | ||
| + $offset += 1 + $ln; | ||
| + | ||
| + if( strlen( $yieldBuffer ) >= self::CHUNK_SIZE ) { | ||
| + yield $yieldBuffer; | ||
| + | ||
| + $yieldBuffer = ''; | ||
| + } | ||
| } | ||
| } | ||
| $pos = 0; | ||
| $this->readDeltaSize( $head, $pos ); | ||
| + | ||
| $result = $this->readDeltaSize( $head, $pos ); | ||
| } | ||
| while( !$done && $pos < $len ) { | ||
| - $byte = ord( $data[$pos++] ); | ||
| - $val |= ($byte & 0x7F) << $shift; | ||
| - $done = !($byte & 0x80); | ||
| + $byte = ord( $data[$pos++] ); | ||
| + $val |= ($byte & 0x7F) << $shift; | ||
| + $done = !($byte & 0x80); | ||
| $shift += 7; | ||
| } | ||
| } | ||
| + $stream = $this->buildFullStream( | ||
| + $oldLines, | ||
| + $newLines, | ||
| + $m, | ||
| + $n, | ||
| + $start, | ||
| + $end | ||
| + ); | ||
| + | ||
| + return $this->formatDiffOutput( $stream ); | ||
| + } | ||
| + | ||
| + private function buildFullStream( | ||
| + array $oldLines, | ||
| + array $newLines, | ||
| + int $m, | ||
| + int $n, | ||
| + int $start, | ||
| + int $end | ||
| + ): array { | ||
| $context = 2; | ||
| $limit = 100000; | ||
| $stream = []; | ||
| - | ||
| - $pStart = max( 0, $start - $context ); | ||
| + $pStart = max( 0, $start - $context ); | ||
| for( $i = $pStart; $i < $start; $i++ ) { | ||
| $oldSlice = array_slice( $oldLines, $start, $m - $start - $end ); | ||
| $newSlice = array_slice( $newLines, $start, $n - $start - $end ); | ||
| - $mid = []; | ||
| - | ||
| - if( (count( $oldSlice ) * count( $newSlice )) > $limit ) { | ||
| - $mid = $this->buildFallbackDiff( $oldSlice, $newSlice, $start ); | ||
| - } else { | ||
| - $ops = $this->computeLCS( $oldSlice, $newSlice ); | ||
| - $mid = $this->buildDiffStream( $ops, $start ); | ||
| - } | ||
| + $mid = (count( $oldSlice ) * count( $newSlice )) > $limit | ||
| + ? $this->buildFallbackDiff( $oldSlice, $newSlice, $start ) | ||
| + : $this->buildDiffStream( | ||
| + $this->computeLCS( $oldSlice, $newSlice ), | ||
| + $start | ||
| + ); | ||
| foreach( $mid as $line ) { | ||
| $idxO = $m - $end + $i; | ||
| $idxN = $n - $end + $i; | ||
| + | ||
| $stream[] = [ | ||
| 't' => ' ', | ||
| 'l' => $oldLines[$idxO], | ||
| 'no' => $idxO + 1, | ||
| 'nn' => $idxN + 1 | ||
| ]; | ||
| } | ||
| - return $this->formatDiffOutput( $stream ); | ||
| + return $stream; | ||
| } | ||
| for( $i = 0; $i < $n; $i++ ) { | ||
| if( $keep[$i] ) { | ||
| - $cnt = count( $buffer ); | ||
| - | ||
| - if( $cnt > 0 ) { | ||
| - if( $cnt > 5 ) { | ||
| - $result[] = [ 't' => 'gap' ]; | ||
| - } else { | ||
| - foreach( $buffer as $bufLine ) { | ||
| - $result[] = $bufLine; | ||
| - } | ||
| - } | ||
| - | ||
| - $buffer = []; | ||
| - } | ||
| + $this->flushBuffer( $result, $buffer ); | ||
| $result[] = $stream[$i]; | ||
| } else { | ||
| $buffer[] = $stream[$i]; | ||
| } | ||
| } | ||
| + | ||
| + $this->flushBuffer( $result, $buffer ); | ||
| + | ||
| + return $result; | ||
| + } | ||
| + private function flushBuffer( array &$result, array &$buffer ): void { | ||
| $cnt = count( $buffer ); | ||
| } | ||
| } | ||
| - } | ||
| - return $result; | ||
| + $buffer = []; | ||
| + } | ||
| } | ||
| require_once __DIR__ . '/DeltaDecoder.php'; | ||
| require_once __DIR__ . '/PackEntryReader.php'; | ||
| +require_once __DIR__ . '/PackContext.php'; | ||
| class GitPacks { | ||
| public function __construct( string $objectsPath ) { | ||
| $this->manager = new PackStreamManager(); | ||
| - $this->locator = new PackLocator( $objectsPath ); | ||
| + $this->locator = new PackLocator( $this->manager, $objectsPath ); | ||
| $this->reader = new PackEntryReader( new DeltaDecoder() ); | ||
| } | ||
| public function peek( string $sha, int $len = 12 ): string { | ||
| $result = ''; | ||
| $this->locator->locate( | ||
| - $this->manager, | ||
| $sha, | ||
| function( string $packFile, int $offset ) use ( &$result, $len ): void { | ||
| - $result = $this->reader->read( | ||
| - $this->manager, | ||
| - $packFile, | ||
| - $offset, | ||
| + $context = $this->createContext( $packFile, $offset, 0 ); | ||
| + $result = $this->reader->read( | ||
| + $context, | ||
| $len, | ||
| function( string $baseSha, int $cap ): string { | ||
| $this->locator->locate( | ||
| - $this->manager, | ||
| $sha, | ||
| function( string $packFile, int $offset ) use ( &$result ): void { | ||
| - $size = $this->reader->getSize( | ||
| - $this->manager, | ||
| - $packFile, | ||
| - $offset | ||
| - ); | ||
| + $context = $this->createContext( $packFile, $offset, 0 ); | ||
| + $size = $this->reader->getSize( $context ); | ||
| if( $size <= self::MAX_RAM ) { | ||
| $result = $this->reader->read( | ||
| - $this->manager, | ||
| - $packFile, | ||
| - $offset, | ||
| + $context, | ||
| 0, | ||
| function( string $baseSha, int $cap ): string { | ||
| $this->locator->locate( | ||
| - $this->manager, | ||
| $sha, | ||
| function( string $packFile, int $offset ) use ( | ||
| if( $found ) { | ||
| - yield from $this->reader->streamRawCompressed( | ||
| - $this->manager, | ||
| - $file, | ||
| - $off | ||
| - ); | ||
| + $context = $this->createContext( $file, $off, 0 ); | ||
| + | ||
| + yield from $this->reader->streamRawCompressed( $context ); | ||
| } | ||
| } | ||
| private function streamShaGenerator( string $sha, int $depth ): Generator { | ||
| $found = false; | ||
| $file = ''; | ||
| $off = 0; | ||
| $this->locator->locate( | ||
| - $this->manager, | ||
| $sha, | ||
| function( string $packFile, int $offset ) use ( | ||
| if( $found ) { | ||
| - yield from $this->reader->streamEntryGenerator( | ||
| - $this->manager, | ||
| - $file, | ||
| - $off, | ||
| - $depth, | ||
| - function( string $baseSha ): int { | ||
| - return $this->getSize( $baseSha ); | ||
| - }, | ||
| - function( string $baseSha, int $baseDepth ): Generator { | ||
| - yield from $this->streamShaGenerator( $baseSha, $baseDepth ); | ||
| - } | ||
| - ); | ||
| + $context = $this->createContext( $file, $off, $depth ); | ||
| + | ||
| + yield from $this->reader->streamEntryGenerator( $context ); | ||
| } | ||
| } | ||
| public function getSize( string $sha ): int { | ||
| $result = 0; | ||
| $this->locator->locate( | ||
| - $this->manager, | ||
| $sha, | ||
| function( string $packFile, int $offset ) use ( &$result ): void { | ||
| - $result = $this->reader->getSize( | ||
| - $this->manager, | ||
| - $packFile, | ||
| - $offset | ||
| - ); | ||
| + $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 ); | ||
| + } | ||
| + ); | ||
| } | ||
| } | ||
| +<?php | ||
| +require_once __DIR__ . '/StreamReader.php'; | ||
| +require_once __DIR__ . '/PackStreamManager.php'; | ||
| + | ||
| +class PackContext { | ||
| + private PackStreamManager $manager; | ||
| + private string $packFile; | ||
| + private int $offset; | ||
| + private int $depth; | ||
| + private Closure $sizeResolver; | ||
| + private Closure $streamResolver; | ||
| + | ||
| + public function __construct( | ||
| + PackStreamManager $manager, | ||
| + string $packFile, | ||
| + int $offset, | ||
| + int $depth, | ||
| + Closure $sizeResolver, | ||
| + Closure $streamResolver | ||
| + ) { | ||
| + $this->manager = $manager; | ||
| + $this->packFile = $packFile; | ||
| + $this->offset = $offset; | ||
| + $this->depth = $depth; | ||
| + $this->sizeResolver = $sizeResolver; | ||
| + $this->streamResolver = $streamResolver; | ||
| + } | ||
| + | ||
| + public function deriveOffsetContext( int $negativeOffset ): self { | ||
| + return new self( | ||
| + $this->manager, | ||
| + $this->packFile, | ||
| + $this->offset - $negativeOffset, | ||
| + $this->depth, | ||
| + $this->sizeResolver, | ||
| + $this->streamResolver | ||
| + ); | ||
| + } | ||
| + | ||
| + public function computeInt( callable $callback, int $default ): int { | ||
| + return $this->manager->computeInt( | ||
| + $this->packFile, | ||
| + function( StreamReader $stream ) use ( $callback ): int { | ||
| + return $callback( $stream, $this->offset ); | ||
| + }, | ||
| + $default | ||
| + ); | ||
| + } | ||
| + | ||
| + public function computeStringDedicated( | ||
| + callable $callback, | ||
| + string $default | ||
| + ): string { | ||
| + return $this->manager->computeStringDedicated( | ||
| + $this->packFile, | ||
| + function( StreamReader $stream ) use ( $callback ): string { | ||
| + return $callback( $stream, $this->offset ); | ||
| + }, | ||
| + $default | ||
| + ); | ||
| + } | ||
| + | ||
| + public function streamGenerator( callable $callback ): Generator { | ||
| + yield from $this->manager->streamGenerator( | ||
| + $this->packFile, | ||
| + function( StreamReader $stream ) use ( $callback ): Generator { | ||
| + yield from $callback( $stream, $this->offset ); | ||
| + } | ||
| + ); | ||
| + } | ||
| + | ||
| + public function streamGeneratorDedicated( callable $callback ): Generator { | ||
| + yield from $this->manager->streamGeneratorDedicated( | ||
| + $this->packFile, | ||
| + function( StreamReader $stream ) use ( $callback ): Generator { | ||
| + yield from $callback( $stream, $this->offset ); | ||
| + } | ||
| + ); | ||
| + } | ||
| + | ||
| + public function resolveBaseSize( string $sha ): int { | ||
| + return ($this->sizeResolver)( $sha ); | ||
| + } | ||
| + | ||
| + public function resolveBaseStream( string $sha ): Generator { | ||
| + yield from ($this->streamResolver)( $sha, $this->depth + 1 ); | ||
| + } | ||
| + | ||
| + public function isWithinDepth( int $maxDepth ): bool { | ||
| + return $this->depth < $maxDepth; | ||
| + } | ||
| +} | ||
| require_once __DIR__ . '/CompressionStream.php'; | ||
| require_once __DIR__ . '/BufferedReader.php'; | ||
| +require_once __DIR__ . '/PackContext.php'; | ||
| class PackEntryReader { | ||
| } | ||
| - public function getSize( | ||
| - PackStreamManager $manager, | ||
| - string $packFile, | ||
| - int $offset | ||
| - ): int { | ||
| - $result = $manager->computeInt( | ||
| - $packFile, | ||
| - function( StreamReader $stream ) use ( $offset ): int { | ||
| + public function getSize( PackContext $context ): int { | ||
| + return $context->computeInt( | ||
| + function( StreamReader $stream, int $offset ): int { | ||
| $stream->seek( $offset ); | ||
| 0 | ||
| ); | ||
| - | ||
| - return $result; | ||
| } | ||
| public function read( | ||
| - PackStreamManager $manager, | ||
| - string $packFile, | ||
| - int $offset, | ||
| + PackContext $context, | ||
| int $cap, | ||
| callable $readShaBaseFn | ||
| ): string { | ||
| - $result = $manager->computeStringDedicated( | ||
| - $packFile, | ||
| - function( StreamReader $stream ) use ( | ||
| - $offset, | ||
| + return $context->computeStringDedicated( | ||
| + function( StreamReader $stream, int $offset ) use ( | ||
| $cap, | ||
| $readShaBaseFn | ||
| ): string { | ||
| - $result = $this->readWithStream( | ||
| + return $this->readWithStream( | ||
| $stream, | ||
| $offset, | ||
| $cap, | ||
| $readShaBaseFn | ||
| ); | ||
| - | ||
| - return $result; | ||
| }, | ||
| '' | ||
| ); | ||
| - | ||
| - return $result; | ||
| } | ||
| } | ||
| - public function streamRawCompressed( | ||
| - PackStreamManager $manager, | ||
| - string $packFile, | ||
| - int $offset | ||
| - ): Generator { | ||
| - yield from $manager->streamGenerator( | ||
| - $packFile, | ||
| - function( StreamReader $stream ) use ( $offset ): Generator { | ||
| + public function streamRawCompressed( PackContext $context ): Generator { | ||
| + yield from $context->streamGenerator( | ||
| + function( StreamReader $stream, int $offset ): Generator { | ||
| $stream->seek( $offset ); | ||
| } | ||
| - public function streamEntryGenerator( | ||
| - PackStreamManager $manager, | ||
| - string $packFile, | ||
| - int $offset, | ||
| - int $depth, | ||
| - callable $getSizeShaFn, | ||
| - callable $streamShaFn | ||
| - ): Generator { | ||
| - yield from $manager->streamGeneratorDedicated( | ||
| - $packFile, | ||
| - function( StreamReader $stream ) use ( | ||
| - $manager, | ||
| - $packFile, | ||
| - $offset, | ||
| - $depth, | ||
| - $getSizeShaFn, | ||
| - $streamShaFn | ||
| + public function streamEntryGenerator( PackContext $context ): Generator { | ||
| + yield from $context->streamGeneratorDedicated( | ||
| + function( StreamReader $stream, int $offset ) use ( | ||
| + $context | ||
| ): Generator { | ||
| $stream->seek( $offset ); | ||
| $header = $this->readVarInt( $stream ); | ||
| $type = $header['byte'] >> 4 & 7; | ||
| if( $type === 6 || $type === 7 ) { | ||
| yield from $this->streamDeltaObjectGenerator( | ||
| $stream, | ||
| - $manager, | ||
| - $packFile, | ||
| - $offset, | ||
| - $type, | ||
| - $depth, | ||
| - $getSizeShaFn, | ||
| - $streamShaFn | ||
| + $context, | ||
| + $type | ||
| ); | ||
| } else { | ||
| private function streamDeltaObjectGenerator( | ||
| StreamReader $stream, | ||
| - PackStreamManager $manager, | ||
| - string $packFile, | ||
| - int $offset, | ||
| - int $type, | ||
| - int $depth, | ||
| - callable $getSizeShaFn, | ||
| - callable $streamShaFn | ||
| + PackContext $context, | ||
| + int $type | ||
| ): Generator { | ||
| - if( $depth < self::MAX_DEPTH ) { | ||
| + if( $context->isWithinDepth( self::MAX_DEPTH ) ) { | ||
| if( $type === 6 ) { | ||
| - $neg = $this->readOffsetDelta( $stream ); | ||
| - $baseSize = $this->getSize( | ||
| - $manager, | ||
| - $packFile, | ||
| - $offset - $neg | ||
| - ); | ||
| - | ||
| - if( $baseSize > self::MAX_BASE_RAM ) { | ||
| - $tmpStream = $this->resolveBaseToTempFile( | ||
| - $manager, | ||
| - $packFile, | ||
| - $offset - $neg, | ||
| - $depth, | ||
| - $getSizeShaFn, | ||
| - $streamShaFn | ||
| - ); | ||
| + yield from $this->processOffsetDelta( $stream, $context ); | ||
| + } else { | ||
| + yield from $this->processRefDelta( $stream, $context ); | ||
| + } | ||
| + } | ||
| + } | ||
| - yield from $this->decoder->applyStreamGenerator( | ||
| - $stream, | ||
| - $tmpStream | ||
| - ); | ||
| - } else { | ||
| - $readShaBaseFn = function( | ||
| - string $sha, | ||
| - int $cap | ||
| - ) use ( | ||
| - $streamShaFn, | ||
| - $depth | ||
| - ): string { | ||
| - $chunks = []; | ||
| + private function processOffsetDelta( | ||
| + StreamReader $stream, | ||
| + PackContext $context | ||
| + ): Generator { | ||
| + $neg = $this->readOffsetDelta( $stream ); | ||
| + $baseCtx = $context->deriveOffsetContext( $neg ); | ||
| + $baseSz = $this->getSize( $baseCtx ); | ||
| - foreach( $streamShaFn( $sha, $depth + 1 ) as $chunk ) { | ||
| - $chunks[] = $chunk; | ||
| - } | ||
| + if( $baseSz > self::MAX_BASE_RAM ) { | ||
| + $tmp = BufferedReader::createTemp(); | ||
| - $result = implode( '', $chunks ); | ||
| + foreach( $this->streamEntryGenerator( $baseCtx ) as $chunk ) { | ||
| + $tmp->write( $chunk ); | ||
| + } | ||
| - if( $cap > 0 && strlen( $result ) > $cap ) { | ||
| - $result = substr( $result, 0, $cap ); | ||
| - } | ||
| + $tmp->rewind(); | ||
| - return $result; | ||
| - }; | ||
| + yield from $this->decoder->applyStreamGenerator( $stream, $tmp ); | ||
| + } else { | ||
| + $base = $this->read( | ||
| + $baseCtx, | ||
| + 0, | ||
| + function( string $sha, int $cap ) use ( $context ): string { | ||
| + return $this->resolveBaseSha( $sha, $cap, $context ); | ||
| + } | ||
| + ); | ||
| - $base = $this->read( | ||
| - $manager, | ||
| - $packFile, | ||
| - $offset - $neg, | ||
| - 0, | ||
| - $readShaBaseFn | ||
| - ); | ||
| + yield from $this->decoder->applyStreamGenerator( $stream, $base ); | ||
| + } | ||
| + } | ||
| - yield from $this->decoder->applyStreamGenerator( | ||
| - $stream, | ||
| - $base | ||
| - ); | ||
| - } | ||
| - } else { | ||
| - $baseSha = bin2hex( $stream->read( 20 ) ); | ||
| - $baseSize = $getSizeShaFn( $baseSha ); | ||
| + private function processRefDelta( | ||
| + StreamReader $stream, | ||
| + PackContext $context | ||
| + ): Generator { | ||
| + $baseSha = bin2hex( $stream->read( 20 ) ); | ||
| + $baseSize = $context->resolveBaseSize( $baseSha ); | ||
| - if( $baseSize > self::MAX_BASE_RAM ) { | ||
| - $tmpStream = BufferedReader::createTemp(); | ||
| - $written = false; | ||
| + if( $baseSize > self::MAX_BASE_RAM ) { | ||
| + $tmp = BufferedReader::createTemp(); | ||
| + $add = false; | ||
| - foreach( $streamShaFn( $baseSha, $depth + 1 ) as $chunk ) { | ||
| - $tmpStream->write( $chunk ); | ||
| + foreach( $context->resolveBaseStream( $baseSha ) as $chunk ) { | ||
| + $tmp->write( $chunk ); | ||
| - $written = true; | ||
| - } | ||
| + $add = true; | ||
| + } | ||
| - if( $written ) { | ||
| - $tmpStream->rewind(); | ||
| + if( $add ) { | ||
| + $tmp->rewind(); | ||
| - yield from $this->decoder->applyStreamGenerator( | ||
| - $stream, | ||
| - $tmpStream | ||
| - ); | ||
| - } | ||
| - } else { | ||
| - $chunks = []; | ||
| - $written = false; | ||
| + yield from $this->decoder->applyStreamGenerator( $stream, $tmp ); | ||
| + } | ||
| + } else { | ||
| + $chunks = []; | ||
| + $add = false; | ||
| - foreach( $streamShaFn( $baseSha, $depth + 1 ) as $chunk ) { | ||
| - $chunks[] = $chunk; | ||
| - $written = true; | ||
| - } | ||
| + foreach( $context->resolveBaseStream( $baseSha ) as $chunk ) { | ||
| + $chunks[] = $chunk; | ||
| + $add = true; | ||
| + } | ||
| - if( $written ) { | ||
| - $base = implode( '', $chunks ); | ||
| + if( $add ) { | ||
| + $base = implode( '', $chunks ); | ||
| - yield from $this->decoder->applyStreamGenerator( | ||
| - $stream, | ||
| - $base | ||
| - ); | ||
| - } | ||
| - } | ||
| + yield from $this->decoder->applyStreamGenerator( $stream, $base ); | ||
| } | ||
| } | ||
| } | ||
| - private function resolveBaseToTempFile( | ||
| - PackStreamManager $manager, | ||
| - string $packFile, | ||
| - int $baseOffset, | ||
| - int $depth, | ||
| - callable $getSizeShaFn, | ||
| - callable $streamShaFn | ||
| - ): StreamReader { | ||
| - $result = BufferedReader::createTemp(); | ||
| + private function resolveBaseSha( | ||
| + string $sha, | ||
| + int $cap, | ||
| + PackContext $context | ||
| + ): string { | ||
| + $chunks = []; | ||
| - foreach( $this->streamEntryGenerator( | ||
| - $manager, | ||
| - $packFile, | ||
| - $baseOffset, | ||
| - $depth + 1, | ||
| - $getSizeShaFn, | ||
| - $streamShaFn | ||
| - ) as $chunk ) { | ||
| - $result->write( $chunk ); | ||
| + foreach( $context->resolveBaseStream( $sha ) as $chunk ) { | ||
| + $chunks[] = $chunk; | ||
| } | ||
| - $result->rewind(); | ||
| + $result = implode( '', $chunks ); | ||
| + | ||
| + if( $cap > 0 && strlen( $result ) > $cap ) { | ||
| + $result = substr( $result, 0, $cap ); | ||
| + } | ||
| return $result; | ||
| class PackLocator { | ||
| - private array $indexes; | ||
| - private array $cache; | ||
| + private PackStreamManager $manager; | ||
| + private array $indexes; | ||
| + private array $cache; | ||
| - public function __construct( string $objectsPath ) { | ||
| + public function __construct( | ||
| + PackStreamManager $manager, | ||
| + string $objectsPath | ||
| + ) { | ||
| + $this->manager = $manager; | ||
| $this->indexes = []; | ||
| $this->cache = []; | ||
| } | ||
| - public function locate( | ||
| - PackStreamManager $manager, | ||
| - string $sha, | ||
| - callable $action | ||
| - ): void { | ||
| + public function locate( string $sha, callable $action ): void { | ||
| if( strlen( $sha ) === 40 && ctype_xdigit( $sha ) ) { | ||
| $binarySha = hex2bin( $sha ); | ||
| while( !$found && $index < $count ) { | ||
| $this->indexes[$index]->search( | ||
| - $manager, | ||
| + $this->manager, | ||
| $binarySha, | ||
| function( | ||
| Delta | 414 lines added, 315 lines removed, 99-line increase |
|---|