Dave Jarvis' Repositories

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

Fixes cloning functionality

Author Dave Jarvis <email>
Date 2026-02-16 00:32:13 GMT-0800
Commit 0a6d5f05da5f1a50c25a8d889639cecd16628991
Parent d778bdb
git/Git.php
);
}
+
+ public function collectObjects( array $wants, array $haves = [] ): array {
+ $objects = [];
+ $visited = [];
+
+ foreach( $wants as $sha ) {
+ $this->collectObjectsRecursive( $sha, $objects, $visited );
+ }
+
+ foreach( $haves as $sha ) {
+ if( isset( $objects[$sha] ) ) {
+ unset( $objects[$sha] );
+ }
+ }
+
+ return $objects;
+ }
+
+ private function collectObjectsRecursive(
+ string $sha,
+ array &$objects,
+ array &$visited
+ ): void {
+ if( isset( $visited[$sha] ) ) return;
+ $visited[$sha] = true;
+
+ $data = $this->read( $sha );
+ if( $data === '' ) return;
+
+ $type = $this->getObjectType( $data );
+ $objects[$sha] = ['type' => $type, 'size' => strlen( $data )];
+
+ if( $type === 1 ) {
+ if( preg_match( '/^tree ([0-9a-f]{40})/m', $data, $matches ) ) {
+ $this->collectObjectsRecursive( $matches[1], $objects, $visited );
+ }
+
+ if( preg_match( '/^parent ([0-9a-f]{40})/m', $data, $matches ) ) {
+ $this->collectObjectsRecursive( $matches[1], $objects, $visited );
+ }
+ } elseif( $type === 2 ) {
+ $position = 0;
+ $length = strlen( $data );
+
+ while( $position < $length ) {
+ $spacePos = strpos( $data, ' ', $position );
+ $nullPos = strpos( $data, "\0", $spacePos );
+
+ if( $spacePos === false || $nullPos === false ) break;
+
+ $entrySha = bin2hex( substr( $data, $nullPos + 1, 20 ) );
+ $this->collectObjectsRecursive( $entrySha, $objects, $visited );
+
+ $position = $nullPos + 21;
+ }
+ }
+ }
+
+ private function getObjectType( string $data ): int {
+ if( strpos( $data, "tree " ) === 0 ) {
+ return 1;
+ }
+
+ if( $this->isTreeData( $data ) ) {
+ return 2;
+ }
+
+ return 3;
+ }
+
+ public function generatePackfile( array $objects ): string {
+ if( empty( $objects ) ) {
+ $pack = "PACK" . pack( 'N', 2 ) . pack( 'N', 0 );
+ return $pack . hash( 'sha1', $pack, true );
+ }
+
+ $packObjects = '';
+
+ foreach( $objects as $sha => $info ) {
+ $content = $this->read( $sha );
+ if( $content === '' ) continue;
+
+ $type = $info['type'];
+ $size = strlen( $content );
+
+ $byte = ($type << 4) | ($size & 0x0f);
+ $size >>= 4;
+
+ while( $size > 0 ) {
+ $packObjects .= chr( $byte | 0x80 );
+ $byte = $size & 0x7f;
+ $size >>= 7;
+ }
+ $packObjects .= chr( $byte );
+
+ $packObjects .= gzcompress( $content );
+ }
+
+ $objectCount = count( $objects );
+ $header = "PACK" . pack( 'N', 2 ) . pack( 'N', $objectCount );
+ $packData = $header . $packObjects;
+
+ $checksum = hash( 'sha1', $packData, true );
+ return $packData . $checksum;
+ }
}
pages/ClonePage.php
public function render() {
+ if( $this->subPath === 'git-receive-pack' ) {
+ http_response_code( 403 );
+ echo "Read-only repository.";
+ exit;
+ }
+
if( $this->subPath === 'info/refs' ) {
$this->renderInfoRefs();
+ return;
+ }
+
+ if( $this->subPath === 'git-upload-pack' ) {
+ $this->handleUploadPack();
return;
}
private function renderInfoRefs(): void {
+ $service = $_GET['service'] ?? '';
+
+ if( $service === 'git-receive-pack' ) {
+ http_response_code( 403 );
+ echo "Read-only repository.";
+ exit;
+ }
+
+ if( $service === 'git-upload-pack' ) {
+ header( 'Content-Type: application/x-git-upload-pack-advertisement' );
+ header( 'Cache-Control: no-cache' );
+
+ $this->packetWrite( "# service=git-upload-pack\n" );
+ $this->packetFlush();
+
+ $refs = [];
+ $this->git->eachRef( function( $ref, $sha ) use ( &$refs ) {
+ $refs[] = ['ref' => $ref, 'sha' => $sha];
+ } );
+
+ if( !empty( $refs ) ) {
+ $caps = "multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag";
+ $this->packetWrite( $refs[0]['sha'] . " " . $refs[0]['ref'] . "\0" . $caps . "\n" );
+
+ for( $i = 1; $i < count( $refs ); $i++ ) {
+ $this->packetWrite( $refs[$i]['sha'] . " " . $refs[$i]['ref'] . "\n" );
+ }
+ }
+
+ $this->packetFlush();
+ exit;
+ }
+
header( 'Content-Type: text/plain' );
if( $this->git->streamRaw( 'info/refs' ) ) {
exit;
}
$this->git->eachRef( function( $ref, $sha ) {
echo "$sha\t$ref\n";
} );
+
+ exit;
+ }
+
+ private function handleUploadPack(): void {
+ header( 'Content-Type: application/x-git-upload-pack-result' );
+ header( 'Cache-Control: no-cache' );
+
+ $input = file_get_contents( 'php://input' );
+ $wants = [];
+ $haves = [];
+ $offset = 0;
+
+ while( $offset < strlen( $input ) ) {
+ $line = $this->readPacketLine( $input, $offset );
+ if( $line === null ) break;
+ if( $line === '' ) continue;
+
+ $line = trim( $line );
+
+ if( strpos( $line, 'want ' ) === 0 ) {
+ $parts = explode( ' ', $line );
+ $wants[] = $parts[1];
+ } elseif( strpos( $line, 'have ' ) === 0 ) {
+ $parts = explode( ' ', $line );
+ $haves[] = $parts[1];
+ } elseif( $line === 'done' ) {
+ break;
+ }
+ }
+
+ if( empty( $wants ) ) {
+ $this->packetWrite( "ERR no wants provided\n" );
+ $this->packetFlush();
+ exit;
+ }
+
+ $this->packetWrite( "NAK\n" );
+
+ $objects = $this->git->collectObjects( $wants, $haves );
+ $packData = $this->git->generatePackfile( $objects );
+
+ $this->sendSidebandData( 1, $packData );
+ $this->packetFlush();
exit;
+ }
+
+ private function sendSidebandData( int $band, string $data ): void {
+ $chunkSize = 65000;
+ $offset = 0;
+
+ while( $offset < strlen( $data ) ) {
+ $chunk = substr( $data, $offset, $chunkSize );
+ $packet = chr( $band ) . $chunk;
+ $this->packetWrite( $packet );
+ $offset += strlen( $chunk );
+ }
+ }
+
+ private function readPacketLine( string $input, int &$offset ): ?string {
+ if( $offset + 4 > strlen( $input ) ) {
+ return null;
+ }
+
+ $lenHex = substr( $input, $offset, 4 );
+ $len = hexdec( $lenHex );
+
+ if( $len === 0 ) {
+ $offset += 4;
+ return '';
+ }
+
+ if( $len < 4 ) {
+ return null;
+ }
+
+ $offset += 4;
+ $data = substr( $input, $offset, $len - 4 );
+ $offset += $len - 4;
+
+ return $data;
}
private function serve( string $path, string $contentType ): void {
header( 'Content-Type: ' . $contentType );
$success = $this->git->streamRaw( $path );
if( !$success ) {
http_response_code( 404 );
- echo "File not found: $path";
+ echo "Missing: $path";
}
exit;
+ }
+
+ private function packetWrite( string $data ): void {
+ $len = strlen( $data ) + 4;
+ printf( "%04x%s", $len, $data );
+ }
+
+ private function packetFlush(): void {
+ echo "0000";
}
}
Delta 239 lines added, 1 line removed, 238-line increase