Dave Jarvis' Repositories

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

Reformats git classes

AuthorDave Jarvis <email>
Date2026-02-09 22:51:19 GMT-0800
Commitd850750a2925cac69fe50dd37de41a8884147003
Parent9c020f8
Git.php
private const CHUNK_SIZE = 128;
- private string $path;
- private string $objPath;
+ private string $repoPath;
+ private string $objectsPath;
private GitRefs $refs;
private GitPacks $packs;
public function __construct( string $repoPath ) {
$this->setRepository( $repoPath );
}
- public function setRepository( $repoPath ) {
- $this->path = rtrim( $repoPath, '/' );
- $this->objPath = $this->path . '/objects';
+ public function setRepository( string $repoPath ): void {
+ $this->repoPath = rtrim( $repoPath, '/' );
+ $this->objectsPath = $this->repoPath . '/objects';
- $this->refs = new GitRefs( $this->path );
- $this->packs = new GitPacks( $this->objPath );
+ $this->refs = new GitRefs( $this->repoPath );
+ $this->packs = new GitPacks( $this->objectsPath );
}
- public function resolve( string $ref ): string {
- return $this->refs->resolve( $ref );
+ public function resolve( string $reference ): string {
+ return $this->refs->resolve( $reference );
}
public function getMainBranch(): array {
return $this->refs->getMainBranch();
}
- public function eachBranch( callable $cb ): void {
- $this->refs->scanRefs( 'refs/heads', $cb );
+ public function eachBranch( callable $callback ): void {
+ $this->refs->scanRefs( 'refs/heads', $callback );
}
- public function eachTag( callable $cb ): void {
- $this->refs->scanRefs( 'refs/tags', $cb );
+ public function eachTag( callable $callback ): void {
+ $this->refs->scanRefs( 'refs/tags', $callback );
}
public function getObjectSize( string $sha ): int {
$size = $this->packs->getSize( $sha );
- if( $size !== null ) return $size;
+
+ if( $size !== null ) {
+ return $size;
+ }
return $this->getLooseObjectSize( $sha );
}
public function read( string $sha ): string {
$loosePath = $this->getLoosePath( $sha );
if( file_exists( $loosePath ) ) {
- $raw = file_get_contents( $loosePath );
- $inflated = $raw ? @gzuncompress( $raw ) : false;
+ $rawContent = file_get_contents( $loosePath );
+ $inflated = $rawContent ? @gzuncompress( $rawContent ) : false;
+
return $inflated ? explode( "\0", $inflated, 2 )[1] : '';
}
return $this->packs->read( $sha ) ?? '';
}
public function stream( string $sha, callable $callback ): void {
$data = $this->read( $sha );
- if( $data !== '' ) $callback( $data );
+
+ if( $data !== '' ) {
+ $callback( $data );
+ }
}
- public function history( string $ref, int $limit, callable $cb ): void {
- $curr = $this->resolve( $ref );
- $count = 0;
+ public function history( string $ref, int $limit, callable $callback ): void {
+ $currentSha = $this->resolve( $ref );
+ $count = 0;
- while( $curr !== '' && $count < $limit ) {
- $data = $this->read( $curr );
- if( $data === '' ) break;
+ while( $currentSha !== '' && $count < $limit ) {
+ $data = $this->read( $currentSha );
- $pos = strpos( $data, "\n\n" );
- $msg = ($pos !== false) ? substr( $data, $pos + 2 ) : '';
- preg_match( '/^author (.*) <(.*)> (\d+)/m', $data, $m );
+ if( $data === '' ) {
+ break;
+ }
- $cb( (object)[
- 'sha' => $curr,
- 'message' => trim( $msg ),
- 'author' => $m[1] ?? 'Unknown',
- 'email' => $m[2] ?? '',
- 'date' => (int)( $m[3] ?? 0 )
+ $position = strpos( $data, "\n\n" );
+ $message = $position !== false ? substr( $data, $position + 2 ) : '';
+ preg_match( '/^author (.*) <(.*)> (\d+)/m', $data, $matches );
+
+ $callback( (object)[
+ 'sha' => $currentSha,
+ 'message' => trim( $message ),
+ 'author' => $matches[1] ?? 'Unknown',
+ 'email' => $matches[2] ?? '',
+ 'date' => (int)( $matches[3] ?? 0 )
] );
- $curr = preg_match( '/^parent ([0-9a-f]{40})$/m', $data, $ms ) ? $ms[1] : '';
+ $currentSha = preg_match(
+ '/^parent ([0-9a-f]{40})$/m',
+ $data,
+ $parentMatches
+ ) ? $parentMatches[1] : '';
+
$count++;
}
}
public function walk( string $refOrSha, callable $callback ): void {
- $sha = $this->resolve( $refOrSha );
- $data = ($sha !== '') ? $this->read( $sha ) : '';
+ $sha = $this->resolve( $refOrSha );
+ $data = $sha !== '' ? $this->read( $sha ) : '';
- if( preg_match( '/^tree ([0-9a-f]{40})$/m', $data, $m ) ) {
- $data = $this->read( $m[1] );
+ if( preg_match( '/^tree ([0-9a-f]{40})$/m', $data, $matches ) ) {
+ $data = $this->read( $matches[1] );
}
if( $this->isTreeData( $data ) ) {
$this->processTree( $data, $callback );
}
}
private function processTree( string $data, callable $callback ): void {
- $pos = 0;
- $len = strlen( $data );
+ $position = 0;
+ $length = strlen( $data );
- while( $pos < $len ) {
- $space = strpos( $data, ' ', $pos );
- $null = strpos( $data, "\0", $space );
- if( $space === false || $null === false ) break;
+ while( $position < $length ) {
+ $spacePos = strpos( $data, ' ', $position );
+ $nullPos = strpos( $data, "\0", $spacePos );
- $mode = substr( $data, $pos, $space - $pos );
- $name = substr( $data, $space + 1, $null - $space - 1 );
- $sha = bin2hex( substr( $data, $null + 1, 20 ) );
+ if( $spacePos === false || $nullPos === false ) {
+ break;
+ }
- $isDir = ($mode === '40000' || $mode === '040000');
- $size = $isDir ? 0 : $this->getObjectSize( $sha );
+ $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 );
$callback( new File( $name, $sha, $mode, 0, $size ) );
- $pos = $null + 21;
+
+ $position = $nullPos + 21;
}
}
private function isTreeData( string $data ): bool {
- $pat = '/^(40000|100644|100755|120000|160000) /';
- if( strlen( $data ) >= 25 && preg_match( $pat, $data ) ) {
- $null = strpos( $data, "\0" );
- return ($null !== false && ($null + 21 <= strlen( $data )));
+ $pattern = '/^(40000|100644|100755|120000|160000) /';
+
+ if( strlen( $data ) >= 25 && preg_match( $pattern, $data ) ) {
+ $nullPos = strpos( $data, "\0" );
+
+ return $nullPos !== false && ($nullPos + 21 <= strlen( $data ));
}
+
return false;
}
private function getLoosePath( string $sha ): string {
- return "{$this->objPath}/" . substr( $sha, 0, 2 ) . "/" . substr( $sha, 2 );
+ return "{$this->objectsPath}/" . substr( $sha, 0, 2 ) . "/" .
+ substr( $sha, 2 );
}
private function getLooseObjectSize( string $sha ): int {
$path = $this->getLoosePath( $sha );
- if( !file_exists( $path ) ) return 0;
- $h = @fopen( $path, 'rb' );
- if( !$h ) return 0;
+ if( !file_exists( $path ) ) {
+ return 0;
+ }
- $data = '';
- $inf = inflate_init( ZLIB_ENCODING_DEFLATE );
+ $fileHandle = @fopen( $path, 'rb' );
- while( !feof( $h ) ) {
- $chunk = fread( $h, self::CHUNK_SIZE );
- $out = @inflate_add( $inf, $chunk, ZLIB_NO_FLUSH );
+ if( !$fileHandle ) {
+ return 0;
+ }
- if( $out === false ) break;
- $data .= $out;
+ $data = '';
+ $inflator = inflate_init( ZLIB_ENCODING_DEFLATE );
- if( strpos( $data, "\0" ) !== false ) break;
+ 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;
+ }
}
- fclose( $h );
+
+ fclose( $fileHandle );
$header = explode( "\0", $data, 2 )[0];
- $parts = explode( ' ', $header );
+ $parts = explode( ' ', $header );
return isset( $parts[1] ) ? (int)$parts[1] : 0;
GitPacks.php
private const MAX_READ = 16777216;
- private string $objPath;
- private array $packFiles;
- private ?string $lastPack = null;
-
- // Caches and Resources
- private array $fileHandles = [];
- private array $fanoutCache = [];
- private array $shaBucketCache = [];
- private array $offsetBucketCache = [];
-
- public function __construct(string $objPath) {
- $this->objPath = $objPath;
- $this->packFiles = glob("{$this->objPath}/pack/*.idx") ?: [];
- }
-
- public function __destruct() {
- foreach ($this->fileHandles as $h) {
- if (is_resource($h)) fclose($h);
- }
- }
-
- public function read(string $sha): ?string {
- $info = $this->findPackInfo($sha);
- if ($info['offset'] === -1) return null;
-
- $handle = $this->getHandle($info['file']);
- return $handle ? $this->readPackEntry($handle, $info['offset']) : null;
- }
-
- public function getSize(string $sha): ?int {
- $info = $this->findPackInfo($sha);
- if ($info['offset'] === -1) return null;
-
- return $this->extractPackedSize($info['file'], $info['offset']);
- }
-
- private function findPackInfo(string $sha): array {
- if (!ctype_xdigit($sha) || strlen($sha) !== 40) return ['offset' => -1];
- $binSha = hex2bin($sha);
-
- // Try last used pack first
- if ($this->lastPack) {
- $offset = $this->findInIdx($this->lastPack, $binSha);
- if ($offset !== -1) return $this->makeResult($this->lastPack, $offset);
- }
-
- foreach ($this->packFiles as $idx) {
- if ($idx === $this->lastPack) continue;
- $offset = $this->findInIdx($idx, $binSha);
- if ($offset !== -1) {
- $this->lastPack = $idx;
- return $this->makeResult($idx, $offset);
- }
- }
-
- return ['offset' => -1];
- }
-
- private function makeResult(string $idxPath, int $offset): array {
- return [
- 'file' => str_replace('.idx', '.pack', $idxPath),
- 'offset' => $offset
- ];
- }
-
- private function findInIdx(string $idxFile, string $binSha): int {
- $h = $this->getHandle($idxFile);
- if (!$h) return -1;
-
- // Load Fanout
- if (!isset($this->fanoutCache[$idxFile])) {
- fseek($h, 0);
- if (fread($h, 8) === "\377tOc\0\0\0\2") {
- $this->fanoutCache[$idxFile] = array_values(unpack('N*', fread($h, 1024)));
- } else {
- return -1;
- }
- }
- $fanout = $this->fanoutCache[$idxFile];
-
- $first = ord($binSha[0]);
- $start = ($first === 0) ? 0 : $fanout[$first - 1];
- $end = $fanout[$first];
- if ($end <= $start) return -1;
-
- // Load SHA Cache
- $key = "$idxFile:$first";
- if (!isset($this->shaBucketCache[$key])) {
- $count = $end - $start;
- fseek($h, 1032 + ($start * 20));
- $this->shaBucketCache[$key] = fread($h, $count * 20);
-
- // Load Offsets (4-byte)
- fseek($h, 1032 + ($fanout[255] * 24) + ($start * 4));
- $this->offsetBucketCache[$key] = fread($h, $count * 4);
- }
-
- // Binary Search
- $shaBlock = $this->shaBucketCache[$key];
- $count = strlen($shaBlock) / 20;
- $low = 0;
- $high = $count - 1;
- $foundIdx = -1;
-
- while ($low <= $high) {
- $mid = ($low + $high) >> 1;
- $cmp = substr($shaBlock, $mid * 20, 20);
- if ($cmp < $binSha) $low = $mid + 1;
- elseif ($cmp > $binSha) $high = $mid - 1;
- else { $foundIdx = $mid; break; }
- }
-
- if ($foundIdx === -1) return -1;
-
- $offData = substr($this->offsetBucketCache[$key], $foundIdx * 4, 4);
- $offset = unpack('N', $offData)[1];
-
- // Handle 64-bit offsets
- if ($offset & 0x80000000) {
- $packTotal = $fanout[255];
- $pos64 = 1032 + ($packTotal * 28) + (($offset & 0x7FFFFFFF) * 8);
- fseek($h, $pos64);
- $offset = unpack('J', fread($h, 8))[1];
- }
-
- return (int)$offset;
- }
-
- private function readPackEntry($h, int $offset): string {
- fseek($h, $offset);
- $header = $this->readVarInt($h);
- $type = ($header['byte'] >> 4) & 7;
-
- if ($type === 6) return $this->handleOfsDelta($h, $offset);
- if ($type === 7) return $this->handleRefDelta($h);
-
- $inf = inflate_init(ZLIB_ENCODING_DEFLATE);
- $res = '';
- while (!feof($h)) {
- $chunk = fread($h, 8192);
- $data = @inflate_add($inf, $chunk);
- if ($data !== false) $res .= $data;
- if ($data === false || inflate_get_status($inf) === ZLIB_STREAM_END) break;
- }
- return $res;
- }
-
- private function extractPackedSize(string $packPath, int $offset): int {
- $h = $this->getHandle($packPath);
- if (!$h) return 0;
-
- fseek($h, $offset);
- $header = $this->readVarInt($h);
- $size = $header['value'];
- $type = ($header['byte'] >> 4) & 7;
-
- if ($type === 6 || $type === 7) {
- return $this->readDeltaTargetSize($h, $type);
- }
- return $size;
- }
-
- // --- Delta & Helper Methods ---
-
- private function handleOfsDelta($h, int $offset): string {
- $byte = ord(fread($h, 1));
- $neg = $byte & 127;
- while ($byte & 128) {
- $byte = ord(fread($h, 1));
- $neg = (($neg + 1) << 7) | ($byte & 127);
- }
- $cur = ftell($h);
- $base = $this->readPackEntry($h, $offset - $neg);
- fseek($h, $cur);
- $delta = @gzuncompress(fread($h, self::MAX_READ)) ?: '';
- return $this->applyDelta($base, $delta);
- }
-
- private function handleRefDelta($h): string {
- $baseSha = bin2hex(fread($h, 20));
- // Note: Recursive call to read() for base object
- // We need a way to callback to main Git class or resolve internally.
- // For simplicity assuming self-contained or base is in same pack system:
- $base = $this->read($baseSha) ?? '';
- $delta = @gzuncompress(fread($h, self::MAX_READ)) ?: '';
- return $this->applyDelta($base, $delta);
- }
-
- private function applyDelta(string $base, string $delta): string {
- $pos = 0;
- $this->skipSize($delta, $pos); // src size
- $this->skipSize($delta, $pos); // dst size
- $out = '';
-
- while ($pos < strlen($delta)) {
- $opcode = ord($delta[$pos++]);
- if ($opcode & 128) {
- $off = 0; $len = 0;
- if ($opcode & 0x01) $off |= ord($delta[$pos++]);
- if ($opcode & 0x02) $off |= ord($delta[$pos++]) << 8;
- if ($opcode & 0x04) $off |= ord($delta[$pos++]) << 16;
- if ($opcode & 0x08) $off |= ord($delta[$pos++]) << 24;
- if ($opcode & 0x10) $len |= ord($delta[$pos++]);
- if ($opcode & 0x20) $len |= ord($delta[$pos++]) << 8;
- if ($opcode & 0x40) $len |= ord($delta[$pos++]) << 16;
- if ($len === 0) $len = 0x10000;
- $out .= substr($base, $off, $len);
- } else {
- $len = $opcode & 127;
- $out .= substr($delta, $pos, $len);
- $pos += $len;
- }
- }
- return $out;
- }
-
- private function readVarInt($h): array {
- $byte = ord(fread($h, 1));
- $val = $byte & 15;
- $shift = 4;
- $first = $byte;
- while ($byte & 128) {
- $byte = ord(fread($h, 1));
- $val |= (($byte & 127) << $shift);
- $shift += 7;
- }
- return ['value' => $val, 'byte' => $first];
- }
-
- private function readDeltaTargetSize($h, int $type): int {
- if ($type === 6) { // Offset delta
- $byte = ord(fread($h, 1));
- while ($byte & 128) $byte = ord(fread($h, 1));
- } else { // Ref delta
- fseek($h, 20, SEEK_CUR);
- }
-
- // Decompress just enough to read target size
- $inf = inflate_init(ZLIB_ENCODING_DEFLATE);
- $hdr = '';
- while (!feof($h) && strlen($hdr) < 32) {
- $chunk = fread($h, 512);
- $out = @inflate_add($inf, $chunk, ZLIB_NO_FLUSH);
- if ($out !== false) $hdr .= $out;
- if (inflate_get_status($inf) === ZLIB_STREAM_END) break;
- }
-
- $pos = 0;
- if (strlen($hdr) > 0) {
- $this->skipSize($hdr, $pos);
- return $this->readSize($hdr, $pos);
- }
- return 0;
- }
-
- private function skipSize(string $d, int &$p): void {
- while (ord($d[$p++]) & 128);
- }
-
- private function readSize(string $d, int &$p): int {
- $byte = ord($d[$p++]);
- $val = $byte & 127;
- $shift = 7;
- while ($byte & 128) {
- $byte = ord($d[$p++]);
- $val |= (($byte & 127) << $shift);
- $shift += 7;
- }
- return $val;
- }
-
- private function getHandle(string $path) {
- if (!isset($this->fileHandles[$path])) {
- $this->fileHandles[$path] = @fopen($path, 'rb');
- }
- return $this->fileHandles[$path];
- }
-}
-
+ private string $objectsPath;
+ private array $packFiles;
+ private ?string $lastPack = null;
+
+ private array $fileHandles = [];
+ private array $fanoutCache = [];
+ private array $shaBucketCache = [];
+ private array $offsetBucketCache = [];
+
+ public function __construct( string $objectsPath ) {
+ $this->objectsPath = $objectsPath;
+ $this->packFiles = glob( "{$this->objectsPath}/pack/*.idx" ) ?: [];
+ }
+
+ public function __destruct() {
+ foreach( $this->fileHandles as $handle ) {
+ if( is_resource( $handle ) ) {
+ fclose( $handle );
+ }
+ }
+ }
+
+ public function read( string $sha ): ?string {
+ $info = $this->findPackInfo( $sha );
+
+ if( $info['offset'] === -1 ) {
+ return null;
+ }
+
+ $handle = $this->getHandle( $info['file'] );
+
+ return $handle
+ ? $this->readPackEntry( $handle, $info['offset'] )
+ : null;
+ }
+
+ public function getSize( string $sha ): ?int {
+ $info = $this->findPackInfo( $sha );
+
+ if( $info['offset'] === -1 ) {
+ return null;
+ }
+
+ return $this->extractPackedSize( $info['file'], $info['offset'] );
+ }
+
+ private function findPackInfo( string $sha ): array {
+ if( !ctype_xdigit( $sha ) || strlen( $sha ) !== 40 ) {
+ return ['offset' => -1];
+ }
+
+ $binarySha = hex2bin( $sha );
+
+ if( $this->lastPack ) {
+ $offset = $this->findInIdx( $this->lastPack, $binarySha );
+
+ if( $offset !== -1 ) {
+ return $this->makeResult( $this->lastPack, $offset );
+ }
+ }
+
+ foreach( $this->packFiles as $indexFile ) {
+ if( $indexFile === $this->lastPack ) {
+ continue;
+ }
+
+ $offset = $this->findInIdx( $indexFile, $binarySha );
+
+ if( $offset !== -1 ) {
+ $this->lastPack = $indexFile;
+
+ return $this->makeResult( $indexFile, $offset );
+ }
+ }
+
+ return ['offset' => -1];
+ }
+
+ private function makeResult( string $indexPath, int $offset ): array {
+ return [
+ 'file' => str_replace( '.idx', '.pack', $indexPath ),
+ 'offset' => $offset
+ ];
+ }
+
+ private function findInIdx( string $indexFile, string $binarySha ): int {
+ $fileHandle = $this->getHandle( $indexFile );
+
+ if( !$fileHandle ) {
+ return -1;
+ }
+
+ if( !isset( $this->fanoutCache[$indexFile] ) ) {
+ fseek( $fileHandle, 0 );
+
+ if( fread( $fileHandle, 8 ) === "\377tOc\0\0\0\2" ) {
+ $this->fanoutCache[$indexFile] = array_values(
+ unpack( 'N*', fread( $fileHandle, 1024 ) )
+ );
+ } else {
+ return -1;
+ }
+ }
+
+ $fanout = $this->fanoutCache[$indexFile];
+
+ $firstByte = ord( $binarySha[0] );
+ $start = $firstByte === 0 ? 0 : $fanout[$firstByte - 1];
+ $end = $fanout[$firstByte];
+
+ if( $end <= $start ) {
+ return -1;
+ }
+
+ $cacheKey = "$indexFile:$firstByte";
+
+ if( !isset( $this->shaBucketCache[$cacheKey] ) ) {
+ $count = $end - $start;
+ fseek( $fileHandle, 1032 + ($start * 20) );
+ $this->shaBucketCache[$cacheKey] = fread( $fileHandle, $count * 20 );
+
+ fseek(
+ $fileHandle,
+ 1032 + ($fanout[255] * 24) + ($start * 4)
+ );
+ $this->offsetBucketCache[$cacheKey] = fread( $fileHandle, $count * 4 );
+ }
+
+ $shaBlock = $this->shaBucketCache[$cacheKey];
+ $count = strlen( $shaBlock ) / 20;
+ $low = 0;
+ $high = $count - 1;
+ $foundIdx = -1;
+
+ while( $low <= $high ) {
+ $mid = ($low + $high) >> 1;
+ $compare = substr( $shaBlock, $mid * 20, 20 );
+
+ if( $compare < $binarySha ) {
+ $low = $mid + 1;
+ } elseif( $compare > $binarySha ) {
+ $high = $mid - 1;
+ } else {
+ $foundIdx = $mid;
+ break;
+ }
+ }
+
+ if( $foundIdx === -1 ) {
+ return -1;
+ }
+
+ $offsetData = substr(
+ $this->offsetBucketCache[$cacheKey],
+ $foundIdx * 4,
+ 4
+ );
+ $offset = unpack( 'N', $offsetData )[1];
+
+ if( $offset & 0x80000000 ) {
+ $packTotal = $fanout[255];
+ $pos64 = 1032 + ($packTotal * 28) +
+ (($offset & 0x7FFFFFFF) * 8);
+ fseek( $fileHandle, $pos64 );
+ $offset = unpack( 'J', fread( $fileHandle, 8 ) )[1];
+ }
+
+ return (int)$offset;
+ }
+
+ // $fileHandle is resource, no type hint used for compatibility
+ private function readPackEntry( $fileHandle, int $offset ): string {
+ fseek( $fileHandle, $offset );
+
+ $header = $this->readVarInt( $fileHandle );
+ $type = ($header['byte'] >> 4) & 7;
+
+ if( $type === 6 ) {
+ return $this->handleOfsDelta( $fileHandle, $offset );
+ }
+
+ if( $type === 7 ) {
+ return $this->handleRefDelta( $fileHandle );
+ }
+
+ $inflator = inflate_init( ZLIB_ENCODING_DEFLATE );
+ $result = '';
+
+ while( !feof( $fileHandle ) ) {
+ $chunk = fread( $fileHandle, 8192 );
+ $data = @inflate_add( $inflator, $chunk );
+
+ if( $data !== false ) {
+ $result .= $data;
+ }
+
+ if(
+ $data === false ||
+ inflate_get_status( $inflator ) === ZLIB_STREAM_END
+ ) {
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ private function extractPackedSize( string $packPath, int $offset ): int {
+ $fileHandle = $this->getHandle( $packPath );
+
+ if( !$fileHandle ) {
+ return 0;
+ }
+
+ fseek( $fileHandle, $offset );
+
+ $header = $this->readVarInt( $fileHandle );
+ $size = $header['value'];
+ $type = ($header['byte'] >> 4) & 7;
+
+ if( $type === 6 || $type === 7 ) {
+ return $this->readDeltaTargetSize( $fileHandle, $type );
+ }
+
+ return $size;
+ }
+
+ private function handleOfsDelta( $fileHandle, int $offset ): string {
+ $byte = ord( fread( $fileHandle, 1 ) );
+ $negative = $byte & 127;
+
+ while( $byte & 128 ) {
+ $byte = ord( fread( $fileHandle, 1 ) );
+ $negative = (($negative + 1) << 7) | ($byte & 127);
+ }
+
+ $currentPos = ftell( $fileHandle );
+ $base = $this->readPackEntry( $fileHandle, $offset - $negative );
+
+ fseek( $fileHandle, $currentPos );
+
+ $delta = @gzuncompress( fread( $fileHandle, self::MAX_READ ) ) ?: '';
+
+ return $this->applyDelta( $base, $delta );
+ }
+
+ private function handleRefDelta( $fileHandle ): string {
+ $baseSha = bin2hex( fread( $fileHandle, 20 ) );
+ $base = $this->read( $baseSha ) ?? '';
+ $delta = @gzuncompress( fread( $fileHandle, self::MAX_READ ) ) ?: '';
+
+ return $this->applyDelta( $base, $delta );
+ }
+
+ private function applyDelta( string $base, string $delta ): string {
+ $position = 0;
+ $this->skipSize( $delta, $position );
+ $this->skipSize( $delta, $position );
+
+ $output = '';
+ $deltaLength = strlen( $delta );
+
+ while( $position < $deltaLength ) {
+ $opcode = ord( $delta[$position++] );
+
+ if( $opcode & 128 ) {
+ $offset = 0;
+ $length = 0;
+
+ if( $opcode & 0x01 ) {
+ $offset |= ord( $delta[$position++] );
+ }
+ if( $opcode & 0x02 ) {
+ $offset |= ord( $delta[$position++] ) << 8;
+ }
+ if( $opcode & 0x04 ) {
+ $offset |= ord( $delta[$position++] ) << 16;
+ }
+ if( $opcode & 0x08 ) {
+ $offset |= ord( $delta[$position++] ) << 24;
+ }
+
+ if( $opcode & 0x10 ) {
+ $length |= ord( $delta[$position++] );
+ }
+ if( $opcode & 0x20 ) {
+ $length |= ord( $delta[$position++] ) << 8;
+ }
+ if( $opcode & 0x40 ) {
+ $length |= ord( $delta[$position++] ) << 16;
+ }
+
+ if( $length === 0 ) {
+ $length = 0x10000;
+ }
+
+ $output .= substr( $base, $offset, $length );
+ } else {
+ $length = $opcode & 127;
+ $output .= substr( $delta, $position, $length );
+ $position += $length;
+ }
+ }
+
+ return $output;
+ }
+
+ private function readVarInt( $fileHandle ): array {
+ $byte = ord( fread( $fileHandle, 1 ) );
+ $value = $byte & 15;
+ $shift = 4;
+ $first = $byte;
+
+ while( $byte & 128 ) {
+ $byte = ord( fread( $fileHandle, 1 ) );
+ $value |= (($byte & 127) << $shift);
+ $shift += 7;
+ }
+
+ return ['value' => $value, 'byte' => $first];
+ }
+
+ private function readDeltaTargetSize( $fileHandle, int $type ): int {
+ if( $type === 6 ) {
+ $byte = ord( fread( $fileHandle, 1 ) );
+
+ while( $byte & 128 ) {
+ $byte = ord( fread( $fileHandle, 1 ) );
+ }
+ } else {
+ fseek( $fileHandle, 20, SEEK_CUR );
+ }
+
+ $inflator = inflate_init( ZLIB_ENCODING_DEFLATE );
+ $header = '';
+
+ while( !feof( $fileHandle ) && strlen( $header ) < 32 ) {
+ $chunk = fread( $fileHandle, 512 );
+ $output = @inflate_add( $inflator, $chunk, ZLIB_NO_FLUSH );
+
+ if( $output !== false ) {
+ $header .= $output;
+ }
+
+ if( inflate_get_status( $inflator ) === ZLIB_STREAM_END ) {
+ break;
+ }
+ }
+
+ $position = 0;
+
+ if( strlen( $header ) > 0 ) {
+ $this->skipSize( $header, $position );
+
+ return $this->readSize( $header, $position );
+ }
+
+ return 0;
+ }
+
+ private function skipSize( string $data, int &$position ): void {
+ while( ord( $data[$position++] ) & 128 ) {
+ // Empty loop body
+ }
+ }
+
+ private function readSize( string $data, int &$position ): int {
+ $byte = ord( $data[$position++] );
+ $value = $byte & 127;
+ $shift = 7;
+
+ while( $byte & 128 ) {
+ $byte = ord( $data[$position++] );
+ $value |= (($byte & 127) << $shift);
+ $shift += 7;
+ }
+
+ return $value;
+ }
+
+ private function getHandle( string $path ) {
+ if( !isset( $this->fileHandles[$path] ) ) {
+ $this->fileHandles[$path] = @fopen( $path, 'rb' );
+ }
+
+ return $this->fileHandles[$path];
+ }
+}
GitRefs.php
private string $repoPath;
- public function __construct(string $repoPath) {
+ public function __construct( string $repoPath ) {
$this->repoPath = $repoPath;
}
- public function resolve(string $input): string {
- if (preg_match('/^[0-9a-f]{40}$/', $input)) {
+ public function resolve( string $input ): string {
+ if( preg_match( '/^[0-9a-f]{40}$/', $input ) ) {
return $input;
}
- if ($input === 'HEAD' && file_exists($headFile = "{$this->repoPath}/HEAD")) {
- $head = trim(file_get_contents($headFile));
- return (strpos($head, 'ref: ') === 0)
- ? $this->resolve(substr($head, 5))
+ $headFile = "{$this->repoPath}/HEAD";
+
+ if( $input === 'HEAD' && file_exists( $headFile ) ) {
+ $head = trim( file_get_contents( $headFile ) );
+
+ return strpos( $head, 'ref: ' ) === 0
+ ? $this->resolve( substr( $head, 5 ) )
: $head;
}
- return $this->resolveRef($input);
+ return $this->resolveRef( $input );
}
public function getMainBranch(): array {
$branches = [];
- $this->scanRefs('refs/heads', function ($name, $sha) use (&$branches) {
- $branches[$name] = $sha;
- });
- foreach (['main', 'master', 'trunk', 'develop'] as $try) {
- if (isset($branches[$try])) {
+ $this->scanRefs(
+ 'refs/heads',
+ function( string $name, string $sha ) use ( &$branches ) {
+ $branches[$name] = $sha;
+ }
+ );
+
+ foreach( ['main', 'master', 'trunk', 'develop'] as $try ) {
+ if( isset( $branches[$try] ) ) {
return ['name' => $try, 'hash' => $branches[$try]];
}
}
- $first = array_key_first($branches);
- return $first ? ['name' => $first, 'hash' => $branches[$first]] : ['name' => '', 'hash' => ''];
+ $firstKey = array_key_first( $branches );
+
+ return $firstKey
+ ? ['name' => $firstKey, 'hash' => $branches[$firstKey]]
+ : ['name' => '', 'hash' => ''];
}
- public function scanRefs(string $prefix, callable $callback): void {
+ public function scanRefs( string $prefix, callable $callback ): void {
$dir = "{$this->repoPath}/$prefix";
- if (is_dir($dir)) {
- foreach (array_diff(scandir($dir), ['.', '..']) as $file) {
- $callback($file, trim(file_get_contents("$dir/$file")));
+
+ if( is_dir( $dir ) ) {
+ $files = array_diff( scandir( $dir ), ['.', '..'] );
+
+ foreach( $files as $file ) {
+ $callback( $file, trim( file_get_contents( "$dir/$file" ) ) );
}
}
}
- private function resolveRef(string $input): string {
+ private function resolveRef( string $input ): string {
$paths = [$input, "refs/heads/$input", "refs/tags/$input"];
- foreach ($paths as $ref) {
- if (file_exists($path = "{$this->repoPath}/$ref")) {
- return trim(file_get_contents($path));
+ foreach( $paths as $ref ) {
+ $path = "{$this->repoPath}/$ref";
+
+ if( file_exists( $path ) ) {
+ return trim( file_get_contents( $path ) );
}
}
- return file_exists($packed = "{$this->repoPath}/packed-refs")
- ? $this->findInPackedRefs($packed, $input)
+ $packedPath = "{$this->repoPath}/packed-refs";
+
+ return file_exists( $packedPath )
+ ? $this->findInPackedRefs( $packedPath, $input )
: '';
}
- private function findInPackedRefs(string $path, string $input): string {
+ private function findInPackedRefs( string $path, string $input ): string {
$targets = [$input, "refs/heads/$input", "refs/tags/$input"];
- foreach (file($path) as $line) {
- if ($line[0] === '#' || $line[0] === '^') continue;
- $parts = explode(' ', trim($line));
- if (count($parts) >= 2 && in_array($parts[1], $targets)) {
+
+ foreach( file( $path ) as $line ) {
+ if( $line[0] === '#' || $line[0] === '^' ) {
+ continue;
+ }
+
+ $parts = explode( ' ', trim( $line ) );
+
+ if( count( $parts ) >= 2 && in_array( $parts[1], $targets ) ) {
return $parts[0];
}
}
+
return '';
}
Delta543 lines added, 376 lines removed, 167-line increase