Dave Jarvis' Repositories

git clone /repo/treetrek.git
<?php
require_once __DIR__ . '/RepositoryList.php';
require_once __DIR__ . '/git/Git.php';
require_once __DIR__ . '/pages/CommitsPage.php';
require_once __DIR__ . '/pages/DiffPage.php';
require_once __DIR__ . '/pages/HomePage.php';
require_once __DIR__ . '/pages/FilePage.php';
require_once __DIR__ . '/pages/RawPage.php';
require_once __DIR__ . '/pages/TagsPage.php';
require_once __DIR__ . '/pages/ClonePage.php';
require_once __DIR__ . '/pages/ComparePage.php';

class Router {
  private const ACTION_TREE    = 'tree';
  private const ACTION_BLOB    = 'blob';
  private const ACTION_RAW     = 'raw';
  private const ACTION_COMMITS = 'commits';
  private const ACTION_COMMIT  = 'commit';
  private const ACTION_COMPARE = 'compare';
  private const ACTION_TAGS    = 'tags';

  private const GET_REPOSITORY = 'repo';
  private const GET_ACTION     = 'action';
  private const GET_HASH       = 'hash';
  private const GET_NAME       = 'name';

  private const REFERENCE_HEAD = 'HEAD';
  private const ROUTE_REPO     = 'repo';
  private const EXTENSION_GIT  = '.git';

  private array $repos = [];
  private Git $git;

  private string $repoName   = '';
  private array $repoData    = [];
  private string $action     = '';
  private string $commitHash = '';
  private string $filePath   = '';
  private string $baseHash   = '';

  public function __construct( string $reposPath ) {
    $this->git = new Git( $reposPath );
    $list      = new RepositoryList( $reposPath );

    $list->eachRepository( function( $repo ) {
      $this->repos[$repo['safe_name']] = $repo;
    });
  }

  public function route(): Page {
    $this->normalizeQueryString();
    $uriParts = $this->parseUriParts();
    $repoName = !empty( $uriParts ) ? array_shift( $uriParts ) : '';
    $page     = new HomePage( $this->repos, $this->git );

    if( $repoName !== '' ) {
      if( str_ends_with( $repoName, self::EXTENSION_GIT ) ) {
        $page = $this->handleCloneRoute( $repoName, $uriParts );
      } elseif( isset( $this->repos[$repoName] ) ) {
        $page = $this->resolveActionRoute( $repoName, $uriParts );
      }
    }

    return $page;
  }

  private function handleCloneRoute(
    string $repoName,
    array $uriParts
  ): Page {
    $realName = substr( $repoName, 0, -4 );
    $path     = '';

    if( isset( $this->repos[$realName]['path'] ) ) {
      $path = $this->repos[$realName]['path'];
    } elseif( isset( $this->repos[$repoName]['path'] ) ) {
      $path = $this->repos[$repoName]['path'];
    }

    if( $path === '' ) {
      http_response_code( 404 );
      exit( "Repository not found" );
    }

    $this->git->setRepository( $path );

    return new ClonePage( $this->git, implode( '/', $uriParts ) );
  }

  private function resolveActionRoute(
    string $repoName,
    array $uriParts
  ): Page {
    $this->repoData = $this->repos[$repoName];
    $this->repoName = $repoName;

    $this->git->setRepository( $this->repoData['path'] );

    $act = array_shift( $uriParts );
    $this->action = $act ?: self::ACTION_TREE;

    $this->commitHash = self::REFERENCE_HEAD;
    $this->filePath   = '';
    $this->baseHash   = '';

    $hasHash = [
      self::ACTION_TREE, self::ACTION_BLOB, self::ACTION_RAW,
      self::ACTION_COMMITS
    ];

    if( in_array( $this->action, $hasHash ) ) {
      $hash = array_shift( $uriParts );
      $this->commitHash = $hash ?: self::REFERENCE_HEAD;
      $this->filePath   = implode( '/', $uriParts );
    } elseif( $this->action === self::ACTION_COMMIT ) {
      $this->commitHash = array_shift( $uriParts ) ?? self::REFERENCE_HEAD;
    } elseif( $this->action === self::ACTION_COMPARE ) {
      $this->commitHash = array_shift( $uriParts ) ?? self::REFERENCE_HEAD;
      $this->baseHash   = array_shift( $uriParts ) ?? '';
    }

    $this->populateGet();

    return $this->createPage();
  }

  private function createPage(): Page {
    return match( $this->action ) {
      self::ACTION_TREE,
      self::ACTION_BLOB    => new FilePage(
        $this->repos, $this->repoData, $this->git, $this->commitHash,
        $this->filePath
      ),
      self::ACTION_RAW     => new RawPage(
        $this->git, $this->commitHash
      ),
      self::ACTION_COMMITS => new CommitsPage(
        $this->repos, $this->repoData, $this->git, $this->commitHash
      ),
      self::ACTION_COMMIT  => new DiffPage(
        $this->repos, $this->repoData, $this->git, $this->commitHash
      ),
      self::ACTION_TAGS    => new TagsPage(
        $this->repos, $this->repoData, $this->git
      ),
      self::ACTION_COMPARE => new ComparePage(
        $this->repos, $this->repoData, $this->git, $this->commitHash,
        $this->baseHash
      ),
      default              => new FilePage(
        $this->repos, $this->repoData, $this->git, self::REFERENCE_HEAD, ''
      )
    };
  }

  private function normalizeQueryString(): void {
    if( empty( $_GET ) && !empty( $_SERVER['QUERY_STRING'] ) ) {
      parse_str( $_SERVER['QUERY_STRING'], $_GET );
    }
  }

  private function parseUriParts(): array {
    $requestUri = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
    $scriptName = dirname( $_SERVER['SCRIPT_NAME'] );

    if( $scriptName !== '/' && strpos( $requestUri, $scriptName ) === 0 ) {
      $requestUri = substr( $requestUri, strlen( $scriptName ) );
    }

    $requestUri = trim( $requestUri, '/' );
    $uriParts   = explode( '/', $requestUri );

    if( !empty( $uriParts ) && $uriParts[0] === self::ROUTE_REPO ) {
      array_shift( $uriParts );
    }

    return $uriParts;
  }

  private function populateGet(): void {
    $_GET[self::GET_REPOSITORY] = $this->repoName;
    $_GET[self::GET_ACTION]     = $this->action;
    $_GET[self::GET_HASH]       = $this->commitHash;
    $_GET[self::GET_NAME]       = $this->filePath;
  }
}