diff --git a/boot.php b/boot.php index 3a5b62b..5e36efe 100644 --- a/boot.php +++ b/boot.php @@ -1,7 +1,9 @@ set('git', 'repositories', rtrim($config->get('git', 'repositories'), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR); // Startup and configure Silex application diff --git a/composer.json b/composer.json index a62f8d9..49508ff 100644 --- a/composer.json +++ b/composer.json @@ -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.*", diff --git a/composer.lock b/composer.lock index 7e3f6b3..786811c 100644 --- a/composer.lock +++ b/composer.lock @@ -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 + } } diff --git a/index.php b/index.php index bed5d93..51eaaa6 100644 --- a/index.php +++ b/index.php @@ -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(); \ No newline at end of file +$app->run(); diff --git a/src/GitList/Application.php b/src/GitList/Application.php index 739f0a2..7f7e2cd 100644 --- a/src/GitList/Application.php +++ b/src/GitList/Application.php @@ -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')); diff --git a/src/GitList/Config.php b/src/GitList/Config.php index a7f09e3..33d5b39 100644 --- a/src/GitList/Config.php +++ b/src/GitList/Config.php @@ -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(); } diff --git a/src/GitList/Controller/BlobController.php b/src/GitList/Controller/BlobController.php index 82fc2a4..9a44a27 100644 --- a/src/GitList/Controller/BlobController.php +++ b/src/GitList/Controller/BlobController.php @@ -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'); diff --git a/src/GitList/Controller/CommitController.php b/src/GitList/Controller/CommitController.php index f47b0a5..776f669 100644 --- a/src/GitList/Controller/CommitController.php +++ b/src/GitList/Controller/CommitController.php @@ -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'); diff --git a/src/GitList/Controller/MainController.php b/src/GitList/Controller/MainController.php index dcf17cc..89b6f62 100644 --- a/src/GitList/Controller/MainController.php +++ b/src/GitList/Controller/MainController.php @@ -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'); diff --git a/src/GitList/Controller/TreeController.php b/src/GitList/Controller/TreeController.php index f7cd7eb..fe463b7 100644 --- a/src/GitList/Controller/TreeController.php +++ b/src/GitList/Controller/TreeController.php @@ -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'); diff --git a/src/GitList/Provider/RoutingUtilServiceProvider.php b/src/GitList/Provider/RoutingUtilServiceProvider.php new file mode 100644 index 0000000..95b43d6 --- /dev/null +++ b/src/GitList/Provider/RoutingUtilServiceProvider.php @@ -0,0 +1,26 @@ +share(function () use ($app) { + return new Routing($app); + }); + } + + public function boot(Application $app) + { + } +} diff --git a/src/GitList/Util/Routing.php b/src/GitList/Util/Routing.php new file mode 100644 index 0000000..884e251 --- /dev/null +++ b/src/GitList/Util/Routing.php @@ -0,0 +1,52 @@ +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) + ); + } + } +} diff --git a/tests/InterfaceTest.php b/tests/InterfaceTest.php index 95c8b86..38e437d 100644 --- a/tests/InterfaceTest.php +++ b/tests/InterfaceTest.php @@ -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(); diff --git a/views/index.twig b/views/index.twig index db725ac..07093cd 100644 --- a/views/index.twig +++ b/views/index.twig @@ -9,8 +9,8 @@ {% for repository in repositories %}
{{ repository.description }}