Dave Jarvis' Repositories

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

Removes redundant assignments

AuthorDave Jarvis <email>
Date2026-02-14 15:53:19 GMT-0800
Commit44da3fb2a4cf3446bc0366422083a9d63542ec9d
Parent8717fdc
git/Git.php
public function eachTag( callable $callback ): void {
- $this->refs->scanRefs( 'refs/tags', function( $name, $sha ) use ( $callback ) {
- $data = $this->read( $sha );
- $tag = $this->parseTagData( $name, $sha, $data );
- $callback( $tag );
- } );
- }
-
- private function parseTagData( string $name, string $sha, string $data ): Tag {
- $isAnnotated = strncmp( $data, 'object ', 7 ) === 0;
- $targetSha = $isAnnotated ? $this->extractPattern( $data, '/^object ([0-9a-f]{40})$/m', 1, $sha ) : $sha;
- $pattern = $isAnnotated ? '/^tagger (.*) <.*> (\d+) [+\-]\d{4}$/m' : '/^author (.*) <.*> (\d+) [+\-]\d{4}$/m';
-
- list( $author, $timestamp ) = $this->extractAuthorAndTime( $data, $pattern );
- $message = $this->extractMessage( $data );
-
- return new Tag( $name, $sha, $targetSha, $timestamp, $message, $author );
- }
-
- private function extractPattern( string $data, string $pattern, int $group, string $default = '' ): string {
- $matches = [];
- $result = preg_match( $pattern, $data, $matches ) ? $matches[$group] : $default;
- return $result;
- }
-
- private function extractAuthorAndTime( string $data, string $pattern ): array {
- $matches = [];
- $found = preg_match( $pattern, $data, $matches );
- $author = $found ? trim( $matches[1] ) : '';
- $timestamp = $found && isset( $matches[3] ) ? (int)$matches[3] : ( $found && isset( $matches[2] ) ? (int)$matches[2] : 0 );
- return [ $author, $timestamp ];
- }
-
- private function extractMessage( string $data ): string {
- $pos = strpos( $data, "\n\n" );
- $message = ( $pos !== false ) ? trim( substr( $data, $pos + 2 ) ) : '';
- return $message;
- }
-
- public function getObjectSize( string $sha ): int {
- $size = $this->packs->getSize( $sha );
- $result = ( $size !== null ) ? $size : $this->getLooseObjectSize( $sha );
- return $result;
- }
-
- public function peek( string $sha, int $length = 255 ): string {
- $size = $this->packs->getSize( $sha );
- $result = ( $size === null )
- ? $this->peekLooseObject( $sha, $length )
- : ( $this->packs->peek( $sha, $length ) ?? '' );
- return $result;
- }
-
- public function read( string $sha ): string {
- $size = $this->getObjectSize( $sha );
-
- if( $size > self::MAX_READ_SIZE ) {
- return '';
- }
-
- $content = '';
-
- $this->slurp( $sha, function( $chunk ) use ( &$content ) {
- $content .= $chunk;
- } );
-
- return $content;
- }
-
- public function readFile( string $hash, string $name ) {
- return new File(
- $name,
- $hash,
- '100644',
- 0,
- $this->getObjectSize( $hash ),
- $this->peek( $hash )
- );
- }
-
- public function stream( string $sha, callable $callback ): void {
- $this->slurp( $sha, $callback );
- }
-
- private function slurp( string $sha, callable $callback ): void {
- $loosePath = $this->getLoosePath( $sha );
-
- if( is_file( $loosePath ) ) {
- $this->slurpLooseObject( $loosePath, $callback );
- } else {
- $this->slurpPackedObject( $sha, $callback );
- }
- }
-
- private function slurpLooseObject( string $path, callable $callback ): void {
- $this->withInflatedFile( $path, function( $fileHandle, $inflator ) use ( $callback ) {
- $buffer = '';
- $headerFound = false;
-
- while( !feof( $fileHandle ) ) {
- $chunk = fread( $fileHandle, 16384 );
- $inflatedChunk = inflate_add( $inflator, $chunk );
-
- if( $inflatedChunk === false ) break;
-
- $headerFound = $this->processInflatedChunk(
- $inflatedChunk,
- $headerFound,
- $buffer,
- $callback
- );
- }
- } );
- }
-
- private function withInflatedFile( string $path, callable $callback ): void {
- $fileHandle = fopen( $path, 'rb' );
- $inflator = $fileHandle ? inflate_init( ZLIB_ENCODING_DEFLATE ) : null;
-
- if( $fileHandle && $inflator ) {
- $callback( $fileHandle, $inflator );
- fclose( $fileHandle );
- }
- }
-
- private function processInflatedChunk(
- string $chunk,
- bool $headerFound,
- string &$buffer,
- callable $callback
- ): bool {
- if( !$headerFound ) {
- $buffer .= $chunk;
- $nullPos = strpos( $buffer, "\0" );
-
- if( $nullPos !== false ) {
- $body = substr( $buffer, $nullPos + 1 );
-
- if( $body !== '' ) {
- $callback( $body );
- }
-
- $buffer = '';
- return true;
- }
- } else {
- $callback( $chunk );
- }
-
- return $headerFound;
- }
-
- private function slurpPackedObject( string $sha, callable $callback ): void {
- $streamed = $this->packs->stream( $sha, $callback );
-
- if( !$streamed ) {
- $data = $this->packs->read( $sha );
-
- if( $data !== null && $data !== '' ) {
- $callback( $data );
- }
- }
- }
-
- private function peekLooseObject( string $sha, int $length ): string {
- $path = $this->getLoosePath( $sha );
- $buffer = '';
-
- if( is_file( $path ) ) {
- $buffer = $this->inflateLooseObjectPrefix( $path, $length );
- }
-
- return $buffer;
- }
-
- private function inflateLooseObjectPrefix( string $path, int $length ): string {
- $buffer = '';
-
- $this->withInflatedFile( $path, function( $fileHandle, $inflator ) use ( $length, &$buffer ) {
- $headerFound = false;
-
- while( !feof( $fileHandle ) && strlen( $buffer ) < $length ) {
- $chunk = fread( $fileHandle, 128 );
- $inflated = inflate_add( $inflator, $chunk );
-
- if( $inflated === false ) {
- break;
- }
-
- $headerFound = $this->appendPrefixChunk( $inflated, $headerFound, $buffer );
- }
-
- $buffer = substr( $buffer, 0, $length );
- } );
-
- return $buffer;
- }
-
- private function appendPrefixChunk(
- string $chunk,
- bool $headerFound,
- string &$buffer
- ): bool {
- if( !$headerFound ) {
- $nullPos = strpos( $chunk, "\0" );
-
- if( $nullPos !== false ) {
- $buffer .= substr( $chunk, $nullPos + 1 );
- return true;
- }
- } else {
- $buffer .= $chunk;
- }
-
- return $headerFound;
- }
-
- public function history( string $ref, int $limit, callable $callback ): void {
- $currentSha = $this->resolve( $ref );
- $count = 0;
-
- while( $currentSha !== '' && $count < $limit ) {
- $commit = $this->parseCommit( $currentSha );
-
- if( $commit === null ) {
- break;
- }
-
- $callback( $commit );
- $currentSha = $commit->parentSha;
- $count++;
- }
- }
-
- private function parseCommit( string $sha ): ?object {
- $data = $this->read( $sha );
- $result = ( $data === '' ) ? null : $this->buildCommitObject( $sha, $data );
- return $result;
- }
-
- private function buildCommitObject( string $sha, string $data ): object {
- list( $author, $timestamp ) = $this->extractAuthorAndTime( $data, '/^author (.*) <(.*)> (\d+)/m' );
- $email = $this->extractPattern( $data, '/^author .* <(.*)> \d+/m', 1 );
- $message = $this->extractMessage( $data );
- $parentSha = $this->extractPattern( $data, '/^parent ([0-9a-f]{40})$/m', 1 );
-
- return (object)[
- 'sha' => $sha,
- 'message' => $message,
- 'author' => $author ?: 'Unknown',
- 'email' => $email,
- 'date' => $timestamp,
- 'parentSha' => $parentSha
- ];
- }
-
- public function walk( string $refOrSha, callable $callback ): void {
- $sha = $this->resolve( $refOrSha );
-
- if( $sha !== '' ) {
- $this->walkTree( $sha, $callback );
- }
- }
-
- private function walkTree( string $sha, callable $callback ): void {
- $data = $this->read( $sha );
- $treeData = ( $data !== '' && preg_match( '/^tree ([0-9a-f]{40})$/m', $data, $matches ) )
- ? $this->read( $matches[1] )
- : $data;
-
- if( $treeData !== '' && $this->isTreeData( $treeData ) ) {
- $this->processTree( $treeData, $callback );
- }
- }
-
- private function processTree( string $data, callable $callback ): void {
- $position = 0;
- $length = strlen( $data );
-
- while( $position < $length ) {
- $result = $this->parseTreeEntry( $data, $position, $length );
-
- if( $result === null ) {
- break;
- }
-
- $callback( $result['file'] );
- $position = $result['nextPosition'];
- }
- }
-
- private function parseTreeEntry( string $data, int $position, int $length ): ?array {
- $spacePos = strpos( $data, ' ', $position );
- $nullPos = strpos( $data, "\0", $spacePos );
- $hasValidPositions = ( $spacePos !== false && $nullPos !== false && $nullPos + 21 <= $length );
- $result = $hasValidPositions ? $this->buildTreeEntryResult( $data, $position, $spacePos, $nullPos ) : null;
- return $result;
- }
-
- private function buildTreeEntryResult( string $data, int $position, int $spacePos, int $nullPos ): array {
- $mode = substr( $data, $position, $spacePos - $position );
- $name = substr( $data, $spacePos + 1, $nullPos - $spacePos - 1 );
- $sha = bin2hex( substr( $data, $nullPos + 1, 20 ) );
-
- $isDirectory = $mode === '40000' || $mode === '040000';
- $size = $isDirectory ? 0 : $this->getObjectSize( $sha );
- $contents = $isDirectory ? '' : $this->peek( $sha );
-
- $file = new File( $name, $sha, $mode, 0, $size, $contents );
-
- return [
- 'file' => $file,
- 'nextPosition' => $nullPos + 21
- ];
- }
-
- private function isTreeData( string $data ): bool {
- $pattern = '/^(40000|100644|100755|120000|160000) /';
- $minLength = strlen( $data ) >= 25;
- $matchesPattern = $minLength && preg_match( $pattern, $data );
- $nullPos = $matchesPattern ? strpos( $data, "\0" ) : false;
- $result = $matchesPattern && $nullPos !== false && ( $nullPos + 21 <= strlen( $data ) );
- return $result;
- }
-
- private function getLoosePath( string $sha ): string {
- return "{$this->objectsPath}/" . substr( $sha, 0, 2 ) . "/" .
- substr( $sha, 2 );
- }
-
- private function getLooseObjectSize( string $sha ): int {
- $path = $this->getLoosePath( $sha );
- $size = is_file( $path ) ? $this->readLooseObjectHeader( $path ) : 0;
- return $size;
- }
-
- private function readLooseObjectHeader( string $path ): int {
- $size = 0;
-
- $this->withInflatedFile( $path, function( $fileHandle, $inflator ) use ( &$size ) {
- $data = '';
-
- while( !feof( $fileHandle ) ) {
- $chunk = fread( $fileHandle, self::CHUNK_SIZE );
- $output = inflate_add( $inflator, $chunk, ZLIB_NO_FLUSH );
-
- if( $output === false ) {
- break;
- }
-
- $data .= $output;
-
- if( strpos( $data, "\0" ) !== false ) {
- break;
- }
- }
-
- $size = $this->parseSizeFromHeader( $data );
- } );
-
- return $size;
- }
-
- private function parseSizeFromHeader( string $data ): int {
- $header = explode( "\0", $data, 2 )[0];
- $parts = explode( ' ', $header );
- $size = isset( $parts[1] ) ? (int)$parts[1] : 0;
- return $size;
- }
-
- public function streamRaw( string $subPath ): bool {
- $result = false;
-
- if( strpos( $subPath, '..' ) === false ) {
- $result = $this->streamRawFile( $subPath );
- }
-
- return $result;
- }
-
- private function streamRawFile( string $subPath ): bool {
- $fullPath = "{$this->repoPath}/$subPath";
- $result = file_exists( $fullPath ) ? $this->streamIfPathValid( $fullPath ) : false;
- return $result;
- }
-
- private function streamIfPathValid( string $fullPath ): bool {
- $realPath = realpath( $fullPath );
- $repoReal = realpath( $this->repoPath );
- $isValid = ( $realPath && strpos( $realPath, $repoReal ) === 0 );
-
- if( $isValid ) {
- readfile( $fullPath );
- }
-
- return $isValid;
- }
-
- public function eachRef( callable $callback ): void {
- $head = $this->resolve( 'HEAD' );
-
- if( $head !== '' ) {
- $callback( 'HEAD', $head );
- }
-
- $this->refs->scanRefs( 'refs/heads', function( $name, $sha ) use ( $callback ) {
- $callback( "refs/heads/$name", $sha );
- } );
-
- $this->refs->scanRefs( 'refs/tags', function( $name, $sha ) use ( $callback ) {
+ $this->refs->scanRefs( 'refs/tags', function( $name, $sha ) use (
+ $callback
+ ) {
+ $data = $this->read( $sha );
+ $tag = $this->parseTagData( $name, $sha, $data );
+ $callback( $tag );
+ } );
+ }
+
+ private function parseTagData( string $name, string $sha, string $data): Tag {
+ $isAnnotated = strncmp( $data, 'object ', 7 ) === 0;
+ $targetSha = $isAnnotated ? $this->extractPattern( $data,
+ '/^object ([0-9a-f]{40})$/m', 1, $sha ) : $sha;
+ $pattern = $isAnnotated
+ ? '/^tagger (.*) <.*> (\d+) [+\-]\d{4}$/m'
+ : '/^author (.*) <.*> (\d+) [+\-]\d{4}$/m';
+
+ list( $author, $timestamp ) =
+ $this->extractAuthorAndTime( $data, $pattern );
+ $message = $this->extractMessage( $data );
+
+ return new Tag( $name, $sha, $targetSha, $timestamp, $message, $author );
+ }
+
+ private function extractPattern( string $data, string $pattern, int $group,
+ string $default = '' ): string {
+ $matches = [];
+ $result = preg_match( $pattern, $data, $matches )
+ ? $matches[$group]
+ : $default;
+
+ return $result;
+ }
+
+ private function extractAuthorAndTime( string $data, string $pattern
+ ): array {
+ $matches = [];
+ $found = preg_match( $pattern, $data, $matches );
+ $author = $found ? trim( $matches[1] ) : '';
+ $timestamp = $found && isset( $matches[3] )
+ ? (int)$matches[3]
+ : $found && isset( $matches[2] ? (int)$matches[2] : 0 );
+
+ return [ $author, $timestamp ];
+ }
+
+ private function extractMessage( string $data ): string {
+ $pos = strpos( $data, "\n\n" );
+
+ return $pos !== false ? trim( substr( $data, $pos + 2 ) ) : '';
+ }
+
+ public function getObjectSize( string $sha ): int {
+ $size = $this->packs->getSize( $sha );
+ return $size !== null ? $size : $this->getLooseObjectSize( $sha );
+ }
+
+ public function peek( string $sha, int $length = 255 ): string {
+ $size = $this->packs->getSize( $sha );
+
+ return $size === null
+ ? $this->peekLooseObject( $sha, $length )
+ : $this->packs->peek( $sha, $length ) ?? '';
+ }
+
+ public function read( string $sha ): string {
+ $size = $this->getObjectSize( $sha );
+
+ if( $size > self::MAX_READ_SIZE ) {
+ return '';
+ }
+
+ $content = '';
+
+ $this->slurp( $sha, function( $chunk ) use ( &$content ) {
+ $content .= $chunk;
+ } );
+
+ return $content;
+ }
+
+ public function readFile( string $hash, string $name ) {
+ return new File(
+ $name,
+ $hash,
+ '100644',
+ 0,
+ $this->getObjectSize( $hash ),
+ $this->peek( $hash )
+ );
+ }
+
+ public function stream( string $sha, callable $callback ): void {
+ $this->slurp( $sha, $callback );
+ }
+
+ private function slurp( string $sha, callable $callback ): void {
+ $loosePath = $this->getLoosePath( $sha );
+
+ if( is_file( $loosePath ) ) {
+ $this->slurpLooseObject( $loosePath, $callback );
+ } else {
+ $this->slurpPackedObject( $sha, $callback );
+ }
+ }
+
+ private function slurpLooseObject( string $path, callable $callback ):
+ void {
+ $this->withInflatedFile( $path, function( $fileHandle, $inflator ) use (
+ $callback
+ ) {
+ $buffer = '';
+ $headerFound = false;
+
+ while( !feof( $fileHandle ) ) {
+ $chunk = fread( $fileHandle, 16384 );
+ $inflatedChunk = inflate_add( $inflator, $chunk );
+
+ if( $inflatedChunk === false ) {
+ break;
+ }
+
+ $headerFound = $this->processInflatedChunk(
+ $inflatedChunk,
+ $headerFound,
+ $buffer,
+ $callback
+ );
+ }
+ } );
+ }
+
+ private function withInflatedFile( string $path, callable $callback ):
+ void {
+ $fileHandle = fopen( $path, 'rb' );
+ $inflator = $fileHandle ? inflate_init( ZLIB_ENCODING_DEFLATE ) : null;
+
+ if( $fileHandle && $inflator ) {
+ $callback( $fileHandle, $inflator );
+ fclose( $fileHandle );
+ }
+ }
+
+ private function processInflatedChunk(
+ string $chunk,
+ bool $headerFound,
+ string &$buffer,
+ callable $callback
+ ): bool {
+ if( !$headerFound ) {
+ $buffer .= $chunk;
+ $nullPos = strpos( $buffer, "\0" );
+
+ if( $nullPos !== false ) {
+ $body = substr( $buffer, $nullPos + 1 );
+
+ if( $body !== '' ) {
+ $callback( $body );
+ }
+
+ $buffer = '';
+ return true;
+ }
+ } else {
+ $callback( $chunk );
+ }
+
+ return $headerFound;
+ }
+
+ private function slurpPackedObject( string $sha, callable $callback ): void {
+ $streamed = $this->packs->stream( $sha, $callback );
+
+ if( !$streamed ) {
+ $data = $this->packs->read( $sha );
+
+ if( $data !== null && $data !== '' ) {
+ $callback( $data );
+ }
+ }
+ }
+
+ private function peekLooseObject( string $sha, int $length ): string {
+ $path = $this->getLoosePath( $sha );
+
+ return is_file( $path )
+ ? $this->inflateLooseObjectPrefix( $path, $length )
+ : '';
+ }
+
+ private function inflateLooseObjectPrefix( string $path, int $length
+ ): string {
+ $buffer = '';
+
+ $this->withInflatedFile( $path, function( $fileHandle, $inflator ) use (
+ $length, &$buffer
+ ) {
+ $headerFound = false;
+
+ while( !feof( $fileHandle ) && strlen( $buffer ) < $length ) {
+ $chunk = fread( $fileHandle, 128 );
+ $inflated = inflate_add( $inflator, $chunk );
+
+ if( $inflated === false ) {
+ break;
+ }
+
+ $headerFound = $this->appendPrefixChunk( $inflated, $headerFound,
+ $buffer );
+ }
+
+ $buffer = substr( $buffer, 0, $length );
+ } );
+
+ return $buffer;
+ }
+
+ private function appendPrefixChunk(
+ string $chunk,
+ bool $headerFound,
+ string &$buffer
+ ): bool {
+ if( !$headerFound ) {
+ $nullPos = strpos( $chunk, "\0" );
+
+ if( $nullPos !== false ) {
+ $buffer .= substr( $chunk, $nullPos + 1 );
+ return true;
+ }
+ } else {
+ $buffer .= $chunk;
+ }
+
+ return $headerFound;
+ }
+
+ public function history( string $ref, int $limit, callable $callback ):
+ void {
+ $currentSha = $this->resolve( $ref );
+ $count = 0;
+
+ while( $currentSha !== '' && $count < $limit ) {
+ $commit = $this->parseCommit( $currentSha );
+
+ if( $commit === null ) {
+ break;
+ }
+
+ $callback( $commit );
+ $currentSha = $commit->parentSha;
+ $count++;
+ }
+ }
+
+ private function parseCommit( string $sha ): ?object {
+ $data = $this->read( $sha );
+
+ return $data === '' ? null : $this->buildCommitObject( $sha, $data );
+ }
+
+ private function buildCommitObject( string $sha, string $data ): object {
+ list( $author, $timestamp ) = $this->extractAuthorAndTime(
+ $data, '/^author (.*) <(.*) (\d+)/m'
+ );
+ $email = $this->extractPattern( $data, '/^author .* <(.*)> \d+/m', 1 );
+ $message = $this->extractMessage( $data );
+ $parentSha = $this->extractPattern( $data, '/^parent ([0-9a-f]{40})$/m',
+ 1 );
+
+ return (object)[
+ 'sha' => $sha,
+ 'message' => $message,
+ 'author' => $author ?: 'Unknown',
+ 'email' => $email,
+ 'date' => $timestamp,
+ 'parentSha' => $parentSha
+ ];
+ }
+
+ public function walk( string $refOrSha, callable $callback ): void {
+ $sha = $this->resolve( $refOrSha );
+
+ if( $sha !== '' ) {
+ $this->walkTree( $sha, $callback );
+ }
+ }
+
+ private function walkTree( string $sha, callable $callback ): void {
+ $data = $this->read( $sha );
+ $treeData = $data !== '' && preg_match( '/^tree ([0-9a-f]{40})$/m', $data,
+ $matches )
+ ? $this->read( $matches[1] )
+ : $data;
+
+ if( $treeData !== '' && $this->isTreeData( $treeData ) ) {
+ $this->processTree( $treeData, $callback );
+ }
+ }
+
+ private function processTree( string $data, callable $callback ): void {
+ $position = 0;
+ $length = strlen( $data );
+
+ while( $position < $length ) {
+ $result = $this->parseTreeEntry( $data, $position, $length );
+
+ if( $result === null ) {
+ break;
+ }
+
+ $callback( $result['file'] );
+ $position = $result['nextPosition'];
+ }
+ }
+
+ private function parseTreeEntry( string $data, int $position, int $length
+ ): ?array {
+ $spacePos = strpos( $data, ' ', $position );
+ $nullPos = strpos( $data, "\0", $spacePos );
+ $hasValidPositions =
+ $spacePos !== false &&
+ $nullPos !== false &&
+ $nullPos + 21 <= $length;
+
+ return $hasValidPositions
+ ? $this->buildTreeEntryResult( $data, $position, $spacePos, $nullPos )
+ : null;
+ }
+
+ private function buildTreeEntryResult( string $data, int $position,
+ int $spacePos, int $nullPos ): array {
+ $mode = substr( $data, $position, $spacePos - $position );
+ $name = substr( $data, $spacePos + 1, $nullPos - $spacePos - 1 );
+ $sha = bin2hex( substr( $data, $nullPos + 1, 20 ) );
+
+ $isDirectory = $mode === '40000' || $mode === '040000';
+ $size = $isDirectory ? 0 : $this->getObjectSize( $sha );
+ $contents = $isDirectory ? '' : $this->peek( $sha );
+
+ $file = new File( $name, $sha, $mode, 0, $size, $contents );
+
+ return [
+ 'file' => $file,
+ 'nextPosition' => $nullPos + 21
+ ];
+ }
+
+ private function isTreeData( string $data ): bool {
+ $pattern = '/^(40000|100644|100755|120000|160000) /';
+ $minLength = strlen( $data ) >= 25;
+ $matchesPattern = $minLength && preg_match( $pattern, $data );
+ $nullPos = $matchesPattern ? strpos( $data, "\0" ) : false;
+
+ return $matchesPattern &&
+ $nullPos !== false &&
+ $nullPos + 21 <= strlen( $data );
+ }
+
+ private function getLoosePath( string $sha ): string {
+ return "{$this->objectsPath}/" .
+ substr( $sha, 0, 2 ) . "/" .
+ substr( $sha, 2 );
+ }
+
+ private function getLooseObjectSize( string $sha ): int {
+ $path = $this->getLoosePath( $sha );
+
+ return is_file( $path ) ? $this->readLooseObjectHeader( $path ) : 0;
+ }
+
+ private function readLooseObjectHeader( string $path ): int {
+ $size = 0;
+
+ $this->withInflatedFile( $path, function( $fileHandle, $inflator ) use (
+ &$size
+ ) {
+ $data = '';
+
+ while( !feof( $fileHandle ) ) {
+ $chunk = fread( $fileHandle, self::CHUNK_SIZE );
+ $output = inflate_add( $inflator, $chunk, ZLIB_NO_FLUSH );
+
+ if( $output === false ) {
+ break;
+ }
+
+ $data .= $output;
+
+ if( strpos( $data, "\0" ) !== false ) {
+ break;
+ }
+ }
+
+ $size = $this->parseSizeFromHeader( $data );
+ } );
+
+ return $size;
+ }
+
+ private function parseSizeFromHeader( string $data ): int {
+ $header = explode( "\0", $data, 2 )[0];
+ $parts = explode( ' ', $header );
+
+ return isset( $parts[1] ) ? (int)$parts[1] : 0;
+ }
+
+ public function streamRaw( string $subPath ): bool {
+ return strpos( $subPath, '..' ) === false
+ ? $this->streamRawFile( $subPath )
+ : false;
+ }
+
+ private function streamRawFile( string $subPath ): bool {
+ $fullPath = "{$this->repoPath}/$subPath";
+
+ return file_exists( $fullPath )
+ ? $this->streamIfPathValid( $fullPath )
+ : false;
+ }
+
+ private function streamIfPathValid( string $fullPath ): bool {
+ $realPath = realpath( $fullPath );
+ $repoReal = realpath( $this->repoPath );
+ $isValid = $realPath && strpos( $realPath, $repoReal ) === 0;
+
+ return $isValid ? readfile( $fullPath ) !== false : false;
+ }
+
+ public function eachRef( callable $callback ): void {
+ $head = $this->resolve( 'HEAD' );
+
+ if( $head !== '' ) {
+ $callback( 'HEAD', $head );
+ }
+
+ $this->refs->scanRefs( 'refs/heads', function( $name, $sha ) use (
+ $callback
+ ) {
+ $callback( "refs/heads/$name", $sha );
+ } );
+
+ $this->refs->scanRefs( 'refs/tags', function( $name, $sha ) use (
+ $callback
+ ) {
$callback( "refs/tags/$name", $sha );
} );
Delta444 lines added, 409 lines removed, 35-line increase