Merge pull request #200 from cschorn/nested-repos

Recursive scanning (should fix #2)
This commit is contained in:
Klaus Silveira
2012-11-06 06:32:25 -08:00
14 changed files with 190 additions and 45 deletions

View File

@@ -1,7 +1,9 @@
<?php
// Load configuration
$config = new GitList\Config('config.ini');
if (!isset($config)) {
die("No configuration object provided.");
}
$config->set('git', 'repositories', rtrim($config->get('git', 'repositories'), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR);
// Startup and configure Silex application

View File

@@ -4,7 +4,7 @@
"twig/twig": "1.9.*",
"symfony/twig-bridge": "2.1.*",
"symfony/filesystem": "2.1.*",
"klaussilveira/gitter": "0.1.2"
"klaussilveira/gitter": "dev-master"
},
"require-dev": {
"symfony/browser-kit": "2.1.*",

36
composer.lock generated
View File

@@ -1,18 +1,18 @@
{
"hash": "c5848b4657d12dc6db9a2f2ff1dd9069",
"hash": "b1fc3d7e61707618f68e5cf940e97081",
"packages": [
{
"name": "klaussilveira/gitter",
"version": "0.1.2",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/klaussilveira/gitter",
"reference": "0.1.2"
"reference": "1c9b6e4dde81d21acffe99d9f4559ed3bc59f947"
},
"dist": {
"type": "zip",
"url": "https://github.com/klaussilveira/gitter/zipball/0.1.2",
"reference": "0.1.2",
"url": "https://github.com/klaussilveira/gitter/zipball/1c9b6e4dde81d21acffe99d9f4559ed3bc59f947",
"reference": "1c9b6e4dde81d21acffe99d9f4559ed3bc59f947",
"shasum": ""
},
"require": {
@@ -22,9 +22,9 @@
"require-dev": {
"symfony/filesystem": ">=2.1"
},
"time": "2012-10-30 17:34:56",
"time": "1351643953",
"type": "library",
"installation-source": "dist",
"installation-source": "source",
"autoload": {
"psr-0": {
"Gitter": "lib/"
@@ -65,7 +65,6 @@
"require": {
"php": ">=5.3.0"
},
"time": "1347278988",
"type": "library",
"extra": {
"branch-alias": {
@@ -92,7 +91,8 @@
"keywords": [
"dependency injection",
"container"
]
],
"time": "1347278988"
},
{
"name": "silex/silex",
@@ -245,7 +245,6 @@
"require": {
"php": ">=5.3.3"
},
"time": "1350717030",
"type": "library",
"extra": {
"branch-alias": {
@@ -272,7 +271,8 @@
}
],
"description": "Symfony Filesystem Component",
"homepage": "http://symfony.com"
"homepage": "http://symfony.com",
"time": "1350717030"
},
{
"name": "symfony/http-foundation",
@@ -409,7 +409,6 @@
"require": {
"php": ">=5.3.3"
},
"time": "1351356874",
"type": "library",
"extra": {
"branch-alias": {
@@ -436,7 +435,8 @@
}
],
"description": "Symfony Process Component",
"homepage": "http://symfony.com"
"homepage": "http://symfony.com",
"time": "1351356874"
},
{
"name": "symfony/routing",
@@ -531,7 +531,6 @@
"symfony/yaml": "2.1.*",
"symfony/security": "2.1.*"
},
"time": "1349363877",
"type": "symfony-bridge",
"extra": {
"branch-alias": {
@@ -558,7 +557,8 @@
}
],
"description": "Symfony Twig Bridge",
"homepage": "http://symfony.com"
"homepage": "http://symfony.com",
"time": "1349363877"
},
{
"name": "twig/twig",
@@ -615,7 +615,7 @@
],
"minimum-stability": "dev",
"stability-flags": [
]
"stability-flags": {
"klaussilveira/gitter": 20
}
}

View File

@@ -11,5 +11,9 @@ if (!ini_get('date.timezone')) {
}
require 'vendor/autoload.php';
// Load configuration
$config = GitList\Config::fromFile('config.ini');
$app = require 'boot.php';
$app->run();
$app->run();

View File

@@ -8,6 +8,7 @@ use Silex\Provider\UrlGeneratorServiceProvider;
use GitList\Provider\GitServiceProvider;
use GitList\Provider\RepositoryUtilServiceProvider;
use GitList\Provider\ViewUtilServiceProvider;
use GitList\Provider\RoutingUtilServiceProvider;
/**
* GitList application.
@@ -44,6 +45,7 @@ class Application extends SilexApplication
$this->register(new ViewUtilServiceProvider());
$this->register(new RepositoryUtilServiceProvider());
$this->register(new UrlGeneratorServiceProvider());
$this->register(new RoutingUtilServiceProvider());
$this['twig'] = $this->share($this->extend('twig', function($twig, $app) {
$twig->addFilter('md5', new \Twig_Filter_Function('md5'));

View File

@@ -6,13 +6,17 @@ class Config
{
protected $data;
public function __construct($file)
{
public static function fromFile($file) {
if (!file_exists($file)) {
die(sprintf('Please, create the %1$s file.', $file));
}
$data = parse_ini_file($file, true);
return new static($data);
}
$this->data = parse_ini_file($file, true);
public function __construct($data)
{
$this->data = $data;
$this->validateOptions();
}

View File

@@ -39,7 +39,7 @@ class BlobController implements ControllerProviderInterface
'tags' => $repository->getTags(),
));
})->assert('file', '.+')
->assert('repo', '[\w-._]+')
->assert('repo', $app['util.routing']->getRepositoryRegex())
->assert('branch', '[\w-._\/]+')
->bind('blob');
@@ -59,7 +59,7 @@ class BlobController implements ControllerProviderInterface
return new Response($blob, 200, $headers);
})->assert('file', '.+')
->assert('repo', '[\w-._]+')
->assert('repo', $app['util.routing']->getRepositoryRegex())
->assert('branch', '[\w-._\/]+')
->bind('blob_raw');

View File

@@ -38,7 +38,7 @@ class CommitController implements ControllerProviderInterface
'commits' => $categorized,
'file' => $file,
));
})->assert('repo', '[\w-._]+')
})->assert('repo', $app['util.routing']->getRepositoryRegex())
->assert('branch', '[\w-._\/]+')
->assert('file', '.+')
->value('branch', 'master')
@@ -63,7 +63,7 @@ class CommitController implements ControllerProviderInterface
'branches' => $repository->getBranches(),
'tags' => $repository->getTags(),
));
})->assert('repo', '[\w-._]+')
})->assert('repo', $app['util.routing']->getRepositoryRegex())
->bind('searchcommits');
$route->get('{repo}/commit/{commit}/', function($repo, $commit) use ($app) {
@@ -75,7 +75,7 @@ class CommitController implements ControllerProviderInterface
'repo' => $repo,
'commit' => $commit,
));
})->assert('repo', '[\w-._]+')
})->assert('repo', $app['util.routing']->getRepositoryRegex())
->assert('commit', '[a-f0-9^]+')
->bind('commit');
@@ -94,7 +94,7 @@ class CommitController implements ControllerProviderInterface
'tags' => $repository->getTags(),
'blames' => $blames,
));
})->assert('repo', '[\w-._]+')
})->assert('repo', $app['util.routing']->getRepositoryRegex())
->assert('file', '.+')
->assert('branch', '[\w-._\/]+')
->bind('blame');

View File

@@ -13,7 +13,13 @@ class MainController implements ControllerProviderInterface
$route = $app['controllers_factory'];
$route->get('/', function() use ($app) {
$repositories = $app['git']->getRepositories($app['git.repos']);
$repositories = array_map(
function ($repo) use ($app) {
$repo['relativePath'] = $app['util.routing']->getRelativePath($repo['path']);
return $repo;
},
$app['git']->getRepositories($app['git.repos'])
);
return $app['twig']->render('index.twig', array(
'repositories' => $repositories,
@@ -33,7 +39,7 @@ class MainController implements ControllerProviderInterface
'stats' => $stats,
'authors' => $authors,
));
})->assert('repo', '[\w-._]+')
})->assert('repo', $app['util.routing']->getRepositoryRegex())
->assert('branch', '[\w-._\/]+')
->value('branch', 'master')
->bind('stats');
@@ -49,7 +55,7 @@ class MainController implements ControllerProviderInterface
));
return new Response($html, 200, array('Content-Type' => 'application/rss+xml'));
})->assert('repo', '[\w-._]+')
})->assert('repo', $app['util.routing']->getRepositoryRegex())
->assert('branch', '[\w-._\/]+')
->bind('rss');

View File

@@ -41,7 +41,7 @@ class TreeController implements ControllerProviderInterface
'tags' => $repository->getTags(),
'readme' => $app['util.repository']->getReadme($repo, $branch),
));
})->assert('repo', '[\w-._]+')
})->assert('repo', $app['util.routing']->getRepositoryRegex())
->assert('branch', '[\w-._\/]+')
->assert('tree', '.+')
->bind('tree');
@@ -65,19 +65,19 @@ class TreeController implements ControllerProviderInterface
'branches' => $repository->getBranches(),
'tags' => $repository->getTags(),
));
})->assert('repo', '[\w-._]+')
})->assert('repo', $app['util.routing']->getRepositoryRegex())
->assert('branch', '[\w-._\/]+')
->bind('search');
$route->get('{repo}/{branch}/', function($repo, $branch) use ($app, $treeController) {
return $treeController($repo, $branch);
})->assert('repo', '[\w-._]+')
})->assert('repo', $app['util.routing']->getRepositoryRegex())
->assert('branch', '[\w-._\/]+')
->bind('branch');
$route->get('{repo}/', function($repo) use ($app, $treeController) {
return $treeController($repo);
})->assert('repo', '[\w-._]+')
})->assert('repo', $app['util.routing']->getRepositoryRegex())
->bind('repository');
$route->get('{repo}/{format}ball/{branch}', function($repo, $format, $branch) use ($app) {
@@ -108,7 +108,7 @@ class TreeController implements ControllerProviderInterface
'Content-Transfer-Encoding' => 'binary',
));
})->assert('format', '(zip|tar)')
->assert('repo', '[\w-._]+')
->assert('repo', $app['util.routing']->getRepositoryRegex())
->assert('branch', '[\w-._\/]+')
->bind('archive');

View File

@@ -0,0 +1,26 @@
<?php
namespace GitList\Provider;
use GitList\Util\Routing;
use Silex\Application;
use Silex\ServiceProviderInterface;
class RoutingUtilServiceProvider implements ServiceProviderInterface
{
/**
* Register the Util\Repository class on the Application ServiceProvider
*
* @param Application $app Silex Application
*/
public function register(Application $app)
{
$app['util.routing'] = $app->share(function () use ($app) {
return new Routing($app);
});
}
public function boot(Application $app)
{
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Gitlist\Util;
use Silex\Application;
class Routing
{
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function getRepositoryRegex()
{
static $regex = null;
if ($regex === null) {
$app = $this->app;
$quotedPaths = array_map(
function ($repo) use ($app) {
return preg_quote($app['util.routing']->getRelativePath($repo['path']), '#');
},
$this->app['git']->getRepositories($this->app['git.repos'])
);
usort($quotedPaths, function ($a, $b) { return strlen($b) - strlen($a); });
$regex = implode('|', $quotedPaths);
}
return $regex;
}
/**
* Strips the base path from a full repository path
*
* @param string $repoPath Full path to the repository
* @return string Relative path to the repository from git.repositories
*/
public function getRelativePath($repoPath)
{
if (strpos($repoPath, $this->app['git.repos']) === 0) {
$relativePath = substr($repoPath, strlen($this->app['git.repos']));
return ltrim($relativePath, '/');
} else {
throw new \InvalidArgumentException(
sprintf("Path '%s' does not match configured repository directory", $repoPath)
);
}
}
}

View File

@@ -7,6 +7,7 @@ use Gitter\Client;
class InterfaceTest extends WebTestCase
{
protected static $tmpdir;
protected static $gitPath;
public static function setUpBeforeClass()
{
@@ -31,6 +32,8 @@ class InterfaceTest extends WebTestCase
$options['hidden'] = array(self::$tmpdir . '/hiddenrepo');
$git = new Client($options);
self::$gitPath = $options['path'];
// GitTest repository fixture
$git->createRepository(self::$tmpdir . 'GitTest');
$repository = $git->getRepository(self::$tmpdir . 'GitTest');
@@ -57,13 +60,39 @@ class InterfaceTest extends WebTestCase
$repository->setConfig('user.email', 'luke@rebel.org');
$repository->addAll();
$repository->commit("First commit");
// Nested repository fixture
$nested_dir = self::$tmpdir . 'nested/';
$fs->mkdir($nested_dir);
$git->createRepository($nested_dir . 'NestedRepo');
$repository = $git->getRepository($nested_dir . '/NestedRepo');
file_put_contents($nested_dir . 'NestedRepo/.git/description', 'This is a NESTED test repo!');
file_put_contents($nested_dir . 'NestedRepo/README.txt', 'NESTED TEST REPO README');
$repository->setConfig('user.name', 'Luke Skywalker');
$repository->setConfig('user.email', 'luke@rebel.org');
$repository->addAll();
$repository->commit("First commit");
$repository->createBranch("testing");
$repository->checkout("testing");
file_put_contents($nested_dir . 'NestedRepo/README.txt', 'NESTED TEST BRANCH README');
$repository->addAll();
$repository->commit("Changing branch");
$repository->checkout("master");
}
public function createApplication()
{
$config = new \GitList\Config(array(
'git' => array(
'client' => self::$gitPath,
'repositories' => self::$tmpdir,
),
'app' => array(
'debug' => true,
),
));
$app = require 'boot.php';
$app['debug'] = true;
$app['git.repos'] = self::$tmpdir;
return $app;
}
@@ -77,10 +106,12 @@ class InterfaceTest extends WebTestCase
$this->assertCount(1, $crawler->filter('div.repository-header:contains("GitTest")'));
$this->assertEquals('/GitTest/', $crawler->filter('.repository-header a')->eq(0)->attr('href'));
$this->assertEquals('/GitTest/master/rss/', $crawler->filter('.repository-header a')->eq(1)->attr('href'));
$this->assertEquals('/nested/NestedRepo/', $crawler->filter('.repository-header a')->eq(2)->attr('href'));
$this->assertEquals('/nested/NestedRepo/master/rss/', $crawler->filter('.repository-header a')->eq(3)->attr('href'));
$this->assertCount(1, $crawler->filter('div.repository-header:contains("foobar")'));
$this->assertCount(1, $crawler->filter('div.repository-body:contains("This is a test repo!")'));
$this->assertEquals('/foobar/', $crawler->filter('.repository-header a')->eq(2)->attr('href'));
$this->assertEquals('/foobar/master/rss/', $crawler->filter('.repository-header a')->eq(3)->attr('href'));
$this->assertEquals('/foobar/', $crawler->filter('.repository-header a')->eq(4)->attr('href'));
$this->assertEquals('/foobar/master/rss/', $crawler->filter('.repository-header a')->eq(5)->attr('href'));
}
public function testRepositoryPage()
@@ -201,6 +232,24 @@ class InterfaceTest extends WebTestCase
$this->assertRegexp('/Initial commit/', $client->getResponse()->getContent());
}
public function testNestedRepoPage()
{
$client = $this->createClient();
$crawler = $client->request('GET', '/nested/NestedRepo/');
$this->assertTrue($client->getResponse()->isOk());
$this->assertRegexp('/NESTED TEST REPO README/', $client->getResponse()->getContent());
}
public function testNestedRepoBranch()
{
$client = $this->createClient();
$crawler = $client->request('GET', '/nested/NestedRepo/testing/');
$this->assertTrue($client->getResponse()->isOk());
$this->assertRegexp('/NESTED TEST BRANCH README/', $client->getResponse()->getContent());
}
public static function tearDownAfterClass()
{
$fs = new Filesystem();

View File

@@ -9,8 +9,8 @@
{% for repository in repositories %}
<div class="repository">
<div class="repository-header">
<i class="icon-folder-open icon-spaced"></i> <a href="{{ path('repository', {repo: repository.name}) }}">{{ repository.name }}</a>
<a href="{{ path('rss', {repo: repository.name, branch: 'master'}) }}"><i class="rss pull-right"></i></a>
<i class="icon-folder-open icon-spaced"></i> <a href="{{ path('repository', {repo: repository.relativePath}) }}">{{ repository.name }}</a>
<a href="{{ path('rss', {repo: repository.relativePath, branch: 'master'}) }}"><i class="rss pull-right"></i></a>
</div>
<div class="repository-body">
<p>{{ repository.description }}</p>