Dave Jarvis' Repositories

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

Adds cloning

AuthorDave Jarvis <email>
Date2026-02-12 12:49:48 GMT-0800
Commitf9319e4ff763e04c0c501dd88e89b3b0a3f80316
Parent5b075f6
Router.php
require_once __DIR__ . '/pages/RawPage.php';
require_once __DIR__ . '/pages/TagsPage.php';
+require_once __DIR__ . '/pages/ClonePage.php';
class Router {
- private $repositories = [];
+ private $repos = [];
private $git;
public function route(): Page {
$reqRepo = $_GET['repo'] ?? '';
- $action = $_GET['action'] ?? 'file';
- $hash = $this->sanitizePath( $_GET['hash'] ?? '' );
+ $action = $_GET['action'] ?? 'file';
+ $hash = $this->sanitize( $_GET['hash'] ?? '' );
$currRepo = null;
- $decoded = urldecode( $reqRepo );
+ $subPath = '';
+ $decoded = urldecode( $reqRepo );
foreach( $this->repos as $repo ) {
- if( $repo['safe_name'] === $reqRepo ||
- $repo['name'] === $decoded ) {
+ if( $repo['safe_name'] === $reqRepo || $repo['name'] === $decoded ) {
+ $currRepo = $repo;
+ break;
+ }
+
+ $prefix = $repo['safe_name'] . '/';
+
+ if( strpos( $reqRepo, $prefix ) === 0 ) {
$currRepo = $repo;
+ $subPath = substr( $reqRepo, strlen( $prefix ) );
+ $action = 'clone';
break;
}
}
if( $currRepo ) {
$this->git->setRepository( $currRepo['path'] );
}
$routes = [
- 'home' => fn() => new HomePage( $this->repos, $this->git ),
- 'file' => fn() => new FilePage( $this->repos, $currRepo, $this->git, $hash ),
- 'raw' => fn() => new RawPage( $this->git, $hash ),
- 'commit' => fn() => new DiffPage( $this->repos, $currRepo, $this->git, $hash ),
+ 'home' => fn() => new HomePage( $this->repos, $this->git ),
+ 'file' => fn() => new FilePage( $this->repos, $currRepo, $this->git, $hash ),
+ 'raw' => fn() => new RawPage( $this->git, $hash ),
+ 'commit' => fn() => new DiffPage( $this->repos, $currRepo, $this->git, $hash ),
'commits' => fn() => new CommitsPage( $this->repos, $currRepo, $this->git, $hash ),
- 'tags' => fn() => new TagsPage( $this->repos, $currRepo, $this->git ),
+ 'tags' => fn() => new TagsPage( $this->repos, $currRepo, $this->git ),
+ 'clone' => fn() => new ClonePage( $this->git, $subPath ),
];
$action = !$currRepo ? 'home' : $action;
return ($routes[$action] ?? $routes['file'])();
}
- private function sanitizePath( $path ) {
+ private function sanitize( $path ) {
$path = str_replace( [ '..', '\\', "\0" ], [ '', '/', '' ], $path );
Views.php
-<?php
-require_once __DIR__ . '/File.php';
-require_once __DIR__ . '/render/FileRenderer.php';
-require_once __DIR__ . '/RepositoryList.php';
-
-require_once __DIR__ . '/pages/BasePage.php';
-require_once __DIR__ . '/pages/HomePage.php';
-require_once __DIR__ . '/pages/CommitsPage.php';
-require_once __DIR__ . '/pages/FilePage.php';
-require_once __DIR__ . '/pages/RawPage.php';
git/Git.php
return isset( $parts[1] ) ? (int)$parts[1] : 0;
}
+
+ public function streamRaw( string $subPath ): bool {
+ if( strpos( $subPath, '..' ) !== false ) {
+ return false;
+ }
+
+ $fullPath = "{$this->repoPath}/$subPath";
+
+ if( !file_exists( $fullPath ) ) {
+ return false;
+ }
+
+ $realPath = realpath( $fullPath );
+ $repoReal = realpath( $this->repoPath );
+
+ if( !$realPath || strpos( $realPath, $repoReal ) !== 0 ) {
+ return false;
+ }
+
+ readfile( $fullPath );
+ return true;
+ }
+
+ 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 );
+ } );
+ }
}
pages/ClonePage.php
+<?php
+require_once __DIR__ . '/Page.php';
+
+class ClonePage implements Page {
+ private $git;
+ private $subPath;
+
+ public function __construct( Git $git, string $subPath ) {
+ $this->git = $git;
+ $this->subPath = $subPath;
+ }
+
+ public function render() {
+ if( $this->subPath === 'info/refs' ) {
+ $this->renderInfoRefs();
+ return;
+ }
+
+ if( $this->subPath === 'HEAD' ) {
+ $this->serve( 'HEAD', 'text/plain' );
+ return;
+ }
+
+ if( strpos( $this->subPath, 'objects/' ) === 0 ) {
+ $this->serve( $this->subPath, 'application/x-git-object' );
+ return;
+ }
+
+ $this->serve( $this->subPath, 'text/plain' );
+ }
+
+ private function renderInfoRefs(): void {
+ 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 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";
+ }
+
+ exit;
+ }
+}
Delta119 lines added, 22 lines removed, 97-line increase