| Author | Dave Jarvis <email> |
|---|---|
| Date | 2026-02-20 17:13:52 GMT-0800 |
| Commit | 85cb79ea30d368a63cec35fa16ed2f8ff5cfcf47 |
| Parent | d9fb252 |
| Delta | 80 lines added, 85 lines removed, 5-line decrease |
|---|
| } | ||
| + public function isEmpty(): bool { | ||
| + return $this->size === 0; | ||
| + } | ||
| + | ||
| public function compare( File $other ): int { | ||
| return $this->isDir !== $other->isDir |
| private function findTreeEntry( string $treeSha, string $name ): array { | ||
| $data = $this->read( $treeSha ); | ||
| - $pos = 0; | ||
| - $len = strlen( $data ); | ||
| $entry = [ 'sha' => '', 'mode' => '' ]; | ||
| - | ||
| - while( $pos < $len ) { | ||
| - $space = strpos( $data, ' ', $pos ); | ||
| - $eos = strpos( $data, "\0", $space ); | ||
| - if( $space === false || $eos === false ) { | ||
| - break; | ||
| - } | ||
| + $this->parseTreeData( | ||
| + $data, | ||
| + function( $file, $n, $sha, $mode ) use ( $name, &$entry ) { | ||
| + if( $file->isName( $name ) ) { | ||
| + $entry = [ 'sha' => $sha, 'mode' => $mode ]; | ||
| - if( substr( $data, $space + 1, $eos - $space - 1 ) === $name ) { | ||
| - $entry = [ | ||
| - 'sha' => bin2hex( substr( $data, $eos + 1, 20 ) ), | ||
| - 'mode' => substr( $data, $pos, $space - $pos ) | ||
| - ]; | ||
| - break; | ||
| + return false; | ||
| + } | ||
| } | ||
| - | ||
| - $pos = $eos + 21; | ||
| - } | ||
| + ); | ||
| return $entry; | ||
| private function processTree( string $data, callable $callback ): void { | ||
| + $this->parseTreeData( | ||
| + $data, | ||
| + function( $file, $n, $s, $m ) use ( $callback ) { | ||
| + $callback( $file ); | ||
| + } | ||
| + ); | ||
| + } | ||
| + | ||
| + public function parseTreeData( string $data, callable $callback ): void { | ||
| $pos = 0; | ||
| $len = strlen( $data ); | ||
| while( $pos < $len ) { | ||
| $space = strpos( $data, ' ', $pos ); | ||
| $eos = strpos( $data, "\0", $space ); | ||
| - $entry = null; | ||
| - | ||
| - if( $space !== false && $eos !== false && $eos + 21 <= $len ) { | ||
| - $mode = substr( $data, $pos, $space - $pos ); | ||
| - $sha = bin2hex( substr( $data, $eos + 1, 20 ) ); | ||
| - $dir = $mode === '40000' || $mode === '040000'; | ||
| - $isSub = $mode === '160000'; | ||
| - $entry = [ | ||
| - 'file' => new File( | ||
| - substr( $data, $space + 1, $eos - $space - 1 ), | ||
| - $sha, | ||
| - $mode, | ||
| - 0, | ||
| - $dir || $isSub ? 0 : $this->getObjectSize( $sha ), | ||
| - $dir || $isSub ? '' : $this->peek( $sha ) | ||
| - ), | ||
| - 'nextPosition' => $eos + 21 | ||
| - ]; | ||
| + if( $space === false || $eos === false || $eos + 21 > $len ) { | ||
| + break; | ||
| } | ||
| - if( $entry === null ) { | ||
| + $mode = substr( $data, $pos, $space - $pos ); | ||
| + $name = substr( $data, $space + 1, $eos - $space - 1 ); | ||
| + $sha = bin2hex( substr( $data, $eos + 1, 20 ) ); | ||
| + $dir = $mode === '40000' || $mode === '040000'; | ||
| + $isSub = $mode === '160000'; | ||
| + | ||
| + $file = new File( | ||
| + $name, | ||
| + $sha, | ||
| + $mode, | ||
| + 0, | ||
| + $dir || $isSub ? 0 : $this->getObjectSize( $sha ), | ||
| + $dir || $isSub ? '' : $this->peek( $sha ) | ||
| + ); | ||
| + | ||
| + if( $callback( $file, $name, $sha, $mode ) === false ) { | ||
| break; | ||
| } | ||
| - $callback( $entry['file'] ); | ||
| - $pos = $entry['nextPosition']; | ||
| + $pos = $eos + 21; | ||
| } | ||
| } | ||
| if( !$old && $new ) { | ||
| - if( $new['is_dir'] ) { | ||
| + if( $new['file']->isDir() ) { | ||
| yield from $this->diffTrees( '', $new['sha'], $currentPath ); | ||
| } else { | ||
| - yield $this->createChange( 'A', $currentPath, '', $new['sha'] ); | ||
| + yield $this->createChange( | ||
| + 'A', | ||
| + $currentPath, | ||
| + '', | ||
| + $new['sha'], | ||
| + null, | ||
| + $new['file'] | ||
| + ); | ||
| } | ||
| } elseif( !$new && $old ) { | ||
| - if( $old['is_dir'] ) { | ||
| + if( $old['file']->isDir() ) { | ||
| yield from $this->diffTrees( $old['sha'], '', $currentPath ); | ||
| } else { | ||
| - yield $this->createChange( 'D', $currentPath, $old['sha'], '' ); | ||
| + yield $this->createChange( | ||
| + 'D', | ||
| + $currentPath, | ||
| + $old['sha'], | ||
| + '', | ||
| + $old['file'], | ||
| + null | ||
| + ); | ||
| } | ||
| } elseif( $old && $new && $old['sha'] !== $new['sha'] ) { | ||
| - if( $old['is_dir'] && $new['is_dir'] ) { | ||
| + if( $old['file']->isDir() && $new['file']->isDir() ) { | ||
| yield from $this->diffTrees( | ||
| $old['sha'], | ||
| $new['sha'], | ||
| $currentPath | ||
| ); | ||
| - } elseif( !$old['is_dir'] && !$new['is_dir'] ) { | ||
| + } elseif( !$old['file']->isDir() && !$new['file']->isDir() ) { | ||
| yield $this->createChange( | ||
| 'M', | ||
| $currentPath, | ||
| $old['sha'], | ||
| - $new['sha'] | ||
| + $new['sha'], | ||
| + $old['file'], | ||
| + $new['file'] | ||
| ); | ||
| } | ||
| $data = $this->git->read( $sha ); | ||
| $entries = []; | ||
| - $len = strlen( $data ); | ||
| - $pos = 0; | ||
| - | ||
| - while( $pos < $len ) { | ||
| - $space = strpos( $data, ' ', $pos ); | ||
| - $null = strpos( $data, "\0", $space ); | ||
| - if( $space === false || $null === false ) { | ||
| - break; | ||
| + $this->git->parseTreeData( | ||
| + $data, | ||
| + function( $file, $name, $hash, $mode ) use ( &$entries ) { | ||
| + $entries[$name] = [ | ||
| + 'file' => $file, | ||
| + 'sha' => $hash | ||
| + ]; | ||
| } | ||
| - | ||
| - $mode = substr( $data, $pos, $space - $pos ); | ||
| - $name = substr( $data, $space + 1, $null - $space - 1 ); | ||
| - $hash = bin2hex( substr( $data, $null + 1, 20 ) ); | ||
| - | ||
| - $entries[$name] = [ | ||
| - 'mode' => $mode, | ||
| - 'sha' => $hash, | ||
| - 'is_dir' => $mode === '40000' || $mode === '040000' | ||
| - ]; | ||
| - | ||
| - $pos = $null + 21; | ||
| - } | ||
| + ); | ||
| return $entries; | ||
| } | ||
| private function createChange( | ||
| string $type, | ||
| string $path, | ||
| string $oldSha, | ||
| - string $newSha | ||
| + string $newSha, | ||
| + ?File $oldFile = null, | ||
| + ?File $newFile = null | ||
| ): array { | ||
| $oldSize = $oldSha !== '' ? $this->git->getObjectSize( $oldSha ) : 0; | ||
| $oldContent = $oldSha !== '' ? $this->git->read( $oldSha ) : ''; | ||
| $newContent = $newSha !== '' ? $this->git->read( $newSha ) : ''; | ||
| - $vDiffOld = new VirtualDiffFile( $path, $oldContent ); | ||
| - $vDiffNew = new VirtualDiffFile( $path, $newContent ); | ||
| + $isBinary = false; | ||
| - $isBinary = ($newSha !== '' && $vDiffNew->isBinary()) || | ||
| - ($newSha === '' && $oldSha !== '' && $vDiffOld->isBinary()); | ||
| + if( $newFile !== null ) { | ||
| + $isBinary = $newFile->isBinary(); | ||
| + } elseif( $oldFile !== null ) { | ||
| + $isBinary = $oldFile->isBinary(); | ||
| + } | ||
| $result = [ | ||
| return array_reverse( $diff ); | ||
| - } | ||
| -} | ||
| - | ||
| -class VirtualDiffFile extends File { | ||
| - public function __construct( string $name, string $content ) { | ||
| - parent::__construct( | ||
| - $name, | ||
| - '', | ||
| - '100644', | ||
| - 0, | ||
| - strlen( $content ), | ||
| - $content | ||
| - ); | ||
| } | ||
| } | ||