Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/treetrek.git
<?php
require_once __DIR__ . '/BufferedReader.php';

class GitRefs {
  private string $repoPath;

  public function __construct( string $repoPath ) {
    $this->repoPath = $repoPath;
  }

  public function resolve( string $input ): string {
    $result = '';

    if( preg_match( '/^[0-9a-f]{40}$/', $input ) ) {
      $result = $input;
    } else {
      $headFile = "{$this->repoPath}/HEAD";

      if( $input === 'HEAD' && is_file( $headFile ) ) {
        $size = filesize( $headFile );
        $head = '';

        if( $size > 0 ) {
          $reader = new BufferedReader( $headFile );
          $head   = trim( $reader->read( $size ) );
        }

        $result = strpos( $head, 'ref: ' ) === 0
          ? $this->resolve( substr( $head, 5 ) )
          : $head;
      } else {
        $result = $this->resolveRef( $input );
      }
    }

    return $result;
  }

  public function getMainBranch(): array {
    $branches = [];

    $this->scanRefs(
      'refs/heads',
      function( string $name, string $sha ) use ( &$branches ) {
        $branches[$name] = $sha;
      }
    );

    $found = [];

    foreach( ['main', 'master', 'trunk', 'develop'] as $try ) {
      if( isset( $branches[$try] ) ) {
        $found = [ 'name' => $try, 'hash' => $branches[$try] ];
        break;
      }
    }

    if( empty( $found ) && !empty( $branches ) ) {
      $key   = array_key_first( $branches );
      $found = [ 'name' => $key, 'hash' => $branches[$key] ];
    } elseif( empty( $found ) ) {
      $found = [ 'name' => '', 'hash' => '' ];
    }

    return $found;
  }

  public function scanRefs( string $prefix, callable $callback ): void {
    $dir = "{$this->repoPath}/$prefix";

    $this->traverseDirectory( $dir, $callback, '' );
  }

  private function traverseDirectory(
    string $dir,
    callable $callback,
    string $subPath
  ): void {
    if( is_dir( $dir ) ) {
      $files = array_diff( scandir( $dir ), ['.', '..'] );

      foreach( $files as $file ) {
        $path = "$dir/$file";
        $name = $subPath === '' ? $file : "$subPath/$file";

        if( is_dir( $path ) ) {
          $this->traverseDirectory( $path, $callback, $name );
        } elseif( is_file( $path ) ) {
          $size = filesize( $path );

          if( $size > 0 ) {
            $reader = new BufferedReader( $path );
            $sha    = trim( $reader->read( $size ) );

            if( preg_match( '/^[0-9a-f]{40}$/', $sha ) ) {
              $callback( $name, $sha );
            }
          }
        }
      }
    }
  }

  private function resolveRef( string $input ): string {
    $paths  = [$input, "refs/heads/$input", "refs/tags/$input"];
    $result = '';

    foreach( $paths as $ref ) {
      $path = "{$this->repoPath}/$ref";

      if( is_file( $path ) ) {
        $size = filesize( $path );

        if( $size > 0 ) {
          $reader = new BufferedReader( $path );
          $result = trim( $reader->read( $size ) );
        }

        break;
      }
    }

    if( $result === '' ) {
      $packedPath = "{$this->repoPath}/packed-refs";

      if( is_file( $packedPath ) ) {
        $result = $this->findInPackedRefs( $packedPath, $input );
      }
    }

    if( !preg_match( '/^[0-9a-f]{40}$/', $result ) ) {
      $result = '';
    }

    return $result;
  }

  private function findInPackedRefs( string $path, string $input ): string {
    $targets = [$input, "refs/heads/$input", "refs/tags/$input"];
    $size    = filesize( $path );
    $lines   = [];
    $result  = '';

    if( $size > 0 ) {
      $reader = new BufferedReader( $path );
      $lines  = explode( "\n", $reader->read( $size ) );
    }

    foreach( $lines as $line ) {
      if( $line !== '' && $line[0] !== '#' && $line[0] !== '^' ) {
        $parts = explode( ' ', trim( $line ) );

        if( count( $parts ) >= 2 && in_array( $parts[1], $targets ) ) {
          $result = $parts[0];
          break;
        }
      }
    }

    return $result;
  }
}