| Author | Dave Jarvis <email> |
|---|---|
| Date | 2026-02-21 11:31:56 GMT-0800 |
| Commit | 6de003a6a891dec281891454ab8cb43f1ac447b2 |
| Parent | fbed27e |
| $pos += $res['used']; | ||
| - $out = ''; | ||
| - $len = strlen( $delta ); | ||
| - $done = false; | ||
| + $chunks = []; | ||
| + $len = strlen( $delta ); | ||
| + $outLen = 0; | ||
| + $done = false; | ||
| + $result = ''; | ||
| while( !$done && $pos < $len ) { | ||
| - if( $cap > 0 && strlen( $out ) >= $cap ) { | ||
| + if( $cap > 0 && $outLen >= $cap ) { | ||
| $done = true; | ||
| } | ||
| if( !$done ) { | ||
| $op = ord( $delta[$pos++] ); | ||
| if( $op & 128 ) { | ||
| - $info = $this->parseCopyInstruction( $op, $delta, $pos ); | ||
| - $out .= substr( $base, $info['off'], $info['len'] ); | ||
| - $pos += $info['used']; | ||
| + $info = $this->parseCopyInstruction( $op, $delta, $pos ); | ||
| + $extracted = substr( $base, $info['off'], $info['len'] ); | ||
| + $chunks[] = $extracted; | ||
| + $outLen += strlen( $extracted ); | ||
| + $pos += $info['used']; | ||
| } else { | ||
| - $ln = $op & 127; | ||
| - $out .= substr( $delta, $pos, $ln ); | ||
| - $pos += $ln; | ||
| + $ln = $op & 127; | ||
| + $extracted = substr( $delta, $pos, $ln ); | ||
| + $chunks[] = $extracted; | ||
| + $outLen += strlen( $extracted ); | ||
| + $pos += $ln; | ||
| } | ||
| } | ||
| } | ||
| - return $out; | ||
| + $result = implode( '', $chunks ); | ||
| + | ||
| + if( $cap > 0 && strlen( $result ) > $cap ) { | ||
| + $result = substr( $result, 0, $cap ); | ||
| + } | ||
| + | ||
| + return $result; | ||
| } | ||
| if( !$doneBuffer ) { | ||
| if( $state < 2 ) { | ||
| - $pos = 0; | ||
| + $pos = 0; | ||
| + $found = false; | ||
| - while( $pos < $len && (ord( $buffer[$pos] ) & 128) ) { | ||
| - $pos++; | ||
| - } | ||
| + while( !$found && $pos < $len ) { | ||
| + if( !(ord( $buffer[$pos] ) & 128) ) { | ||
| + $found = true; | ||
| + } | ||
| - if( $pos === $len && (ord( $buffer[$pos - 1] ) & 128) ) { | ||
| - $doneBuffer = true; | ||
| + $pos++; | ||
| } | ||
| - if( !$doneBuffer ) { | ||
| - $buffer = substr( $buffer, $pos + 1 ); | ||
| + if( $found ) { | ||
| + $buffer = substr( $buffer, $pos ); | ||
| $state++; | ||
| + } else { | ||
| + $doneBuffer = true; | ||
| } | ||
| } else { | ||
| int $fallback = 0 | ||
| ): int { | ||
| - $result = $this->withHandle( $path, $action ); | ||
| - return is_int( $result ) ? $result : $fallback; | ||
| + $computed = $this->withHandle( $path, $action ); | ||
| + | ||
| + return is_int( $computed ) ? $computed : $fallback; | ||
| } | ||
| public function computeString( | ||
| string $path, | ||
| callable $action, | ||
| string $fallback = '' | ||
| ): string { | ||
| - $result = $this->withHandle( $path, $action ); | ||
| - return is_string( $result ) ? $result : $fallback; | ||
| + $computed = $this->withHandle( $path, $action ); | ||
| + | ||
| + return is_string( $computed ) ? $computed : $fallback; | ||
| } | ||
| } | ||
| - private function withHandle( string $path, callable $action ) { | ||
| + public function computeStringDedicated( | ||
| + string $path, | ||
| + callable $action, | ||
| + string $fallback = '' | ||
| + ): string { | ||
| + $computed = $this->withDedicatedHandle( $path, $action ); | ||
| + | ||
| + return is_string( $computed ) ? $computed : $fallback; | ||
| + } | ||
| + | ||
| + public function streamGeneratorDedicated( | ||
| + string $path, | ||
| + callable $action | ||
| + ): Generator { | ||
| + $resultGenerator = $this->withDedicatedHandle( $path, $action ); | ||
| + | ||
| + if( $resultGenerator instanceof Generator ) { | ||
| + yield from $resultGenerator; | ||
| + } | ||
| + } | ||
| + | ||
| + private function withHandle( string $path, callable $action ): mixed { | ||
| + $result = false; | ||
| + | ||
| if( !array_key_exists( $path, $this->handles ) ) { | ||
| - $this->handles[$path] = @fopen( $path, 'rb' ) ?: null; | ||
| + $handle = @fopen( $path, 'rb' ); | ||
| + | ||
| + if( is_resource( $handle ) ) { | ||
| + $this->handles[$path] = $handle; | ||
| + } | ||
| } | ||
| - $handle = $this->handles[$path] ?? null; | ||
| + if( array_key_exists( $path, $this->handles ) ) { | ||
| + $result = $action( $this->handles[$path] ); | ||
| + } | ||
| - return is_resource( $handle ) ? $action( $handle ) : null; | ||
| + return $result; | ||
| + } | ||
| + | ||
| + private function withDedicatedHandle( | ||
| + string $path, | ||
| + callable $action | ||
| + ): mixed { | ||
| + $handle = @fopen( $path, 'rb' ); | ||
| + $result = false; | ||
| + | ||
| + if( is_resource( $handle ) ) { | ||
| + $result = $action( $handle ); | ||
| + | ||
| + fclose( $handle ); | ||
| + } | ||
| + | ||
| + return $result; | ||
| } | ||
| } | ||
| callable $readShaBaseFn | ||
| ): string { | ||
| - return $pool->computeString( | ||
| + return $pool->computeStringDedicated( | ||
| $packFile, | ||
| function( mixed $handle ) use ( | ||
| callable $streamShaFn | ||
| ): Generator { | ||
| - yield from $pool->streamGenerator( | ||
| + yield from $pool->streamGeneratorDedicated( | ||
| $packFile, | ||
| function( mixed $handle ) use ( | ||
| } | ||
| } else { | ||
| - $base = ''; | ||
| + $chunks = []; | ||
| foreach( $this->streamEntryGenerator( | ||
| $pool, | ||
| $packFile, | ||
| $offset - $neg, | ||
| $depth + 1, | ||
| $getSizeShaFn, | ||
| $streamShaFn | ||
| ) as $chunk ) { | ||
| - $base .= $chunk; | ||
| + $chunks[] = $chunk; | ||
| } | ||
| + | ||
| + $base = implode( '', $chunks ); | ||
| fseek( $handle, $deltaPos ); | ||
| } | ||
| } else { | ||
| - $base = ''; | ||
| + $chunks = []; | ||
| $written = false; | ||
| foreach( $streamShaFn( $baseSha, $depth + 1 ) as $chunk ) { | ||
| - $base .= $chunk; | ||
| + $chunks[] = $chunk; | ||
| $written = true; | ||
| } | ||
| if( $written ) { | ||
| + $base = implode( '', $chunks ); | ||
| + | ||
| fseek( $handle, $deltaPos ); | ||
| private function inflate( mixed $handle, int $cap = 0 ): string { | ||
| $stream = CompressionStream::createInflater(); | ||
| + $chunks = []; | ||
| + $len = 0; | ||
| $result = ''; | ||
| foreach( $stream->stream( $handle ) as $data ) { | ||
| - $result .= $data; | ||
| - | ||
| - if( $cap > 0 && strlen( $result ) >= $cap ) { | ||
| - $result = substr( $result, 0, $cap ); | ||
| + $chunks[] = $data; | ||
| + $len += strlen( $data ); | ||
| + if( $cap > 0 && $len >= $cap ) { | ||
| break; | ||
| } | ||
| + } | ||
| + | ||
| + $result = implode( '', $chunks ); | ||
| + | ||
| + if( $cap > 0 && strlen( $result ) > $cap ) { | ||
| + $result = substr( $result, 0, $cap ); | ||
| } | ||
| callable $onFound | ||
| ): void { | ||
| - $byte = ord( $sha[0] ); | ||
| - $start = $byte === 0 ? 0 : $this->fanoutCache[$byte - 1]; | ||
| - $end = $this->fanoutCache[$byte]; | ||
| - $result = 0; | ||
| + $byte = ord( $sha[0] ); | ||
| + $start = $byte === 0 ? 0 : $this->fanoutCache[$byte - 1]; | ||
| + $end = $this->fanoutCache[$byte]; | ||
| if( $end > $start ) { | ||
| - $low = $start; | ||
| - $high = $end - 1; | ||
| + $low = $start; | ||
| + $high = $end - 1; | ||
| + $result = 0; | ||
| while( $result === 0 && $low <= $high ) { | ||
| } | ||
| } | ||
| - } | ||
| - if( $result !== 0 ) { | ||
| - $onFound( $this->packFile, $result ); | ||
| + if( $result !== 0 ) { | ||
| + $onFound( $this->packFile, $result ); | ||
| + } | ||
| } | ||
| } | ||
| private function readOffset( mixed $handle, int $mid ): int { | ||
| $total = $this->fanoutCache[255]; | ||
| $pos = 1032 + $total * 24 + $mid * 4; | ||
| - $result = 0; | ||
| fseek( $handle, $pos ); | ||
| class PackLocator { | ||
| private array $indexes; | ||
| + private array $cache; | ||
| public function __construct( string $objectsPath ) { | ||
| $this->indexes = []; | ||
| + $this->cache = []; | ||
| $packFiles = glob( "{$objectsPath}/pack/*.idx" ) ?: []; | ||
| if( strlen( $sha ) === 40 && ctype_xdigit( $sha ) ) { | ||
| $binarySha = hex2bin( $sha ); | ||
| - $found = false; | ||
| - $count = count( $this->indexes ); | ||
| - $index = 0; | ||
| - while( !$found && $index < $count ) { | ||
| - $this->indexes[$index]->search( | ||
| - $pool, | ||
| - $binarySha, | ||
| - function( | ||
| - string $packFile, | ||
| - int $offset | ||
| - ) use ( | ||
| - &$found, | ||
| - $index, | ||
| - $action | ||
| - ): void { | ||
| - $found = true; | ||
| + if( array_key_exists( $binarySha, $this->cache ) ) { | ||
| + $cached = $this->cache[$binarySha]; | ||
| - if( $index > 0 ) { | ||
| - $temp = $this->indexes[0]; | ||
| - $this->indexes[0] = $this->indexes[$index]; | ||
| - $this->indexes[$index] = $temp; | ||
| - } | ||
| + unset( $this->cache[$binarySha] ); | ||
| - $action( $packFile, $offset ); | ||
| - } | ||
| - ); | ||
| + $this->cache[$binarySha] = $cached; | ||
| - $index++; | ||
| + $action( $cached['pack'], $cached['offset'] ); | ||
| + } else { | ||
| + $found = false; | ||
| + $count = count( $this->indexes ); | ||
| + $index = 0; | ||
| + | ||
| + while( !$found && $index < $count ) { | ||
| + $this->indexes[$index]->search( | ||
| + $pool, | ||
| + $binarySha, | ||
| + function( | ||
| + string $packFile, | ||
| + int $offset | ||
| + ) use ( | ||
| + &$found, | ||
| + $index, | ||
| + $action, | ||
| + $binarySha | ||
| + ): void { | ||
| + $found = true; | ||
| + | ||
| + if( $index > 0 ) { | ||
| + $temp = $this->indexes[0]; | ||
| + $this->indexes[0] = $this->indexes[$index]; | ||
| + $this->indexes[$index] = $temp; | ||
| + } | ||
| + | ||
| + $this->cache[$binarySha] = [ | ||
| + 'pack' => $packFile, | ||
| + 'offset' => $offset | ||
| + ]; | ||
| + | ||
| + if( count( $this->cache ) > 512 ) { | ||
| + $keyToDrop = array_key_first( $this->cache ); | ||
| + | ||
| + unset( $this->cache[$keyToDrop] ); | ||
| + } | ||
| + | ||
| + $action( $packFile, $offset ); | ||
| + } | ||
| + ); | ||
| + | ||
| + $index++; | ||
| + } | ||
| } | ||
| } | ||
| Delta | 169 lines added, 72 lines removed, 97-line increase |
|---|