mirror of
https://github.com/klaussilveira/gitlist.git
synced 2026-05-06 23:37:00 +02:00
Merge pull request #200 from cschorn/nested-repos
Recursive scanning (should fix #2)
This commit is contained in:
6
boot.php
6
boot.php
@@ -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
|
||||
|
||||
@@ -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
36
composer.lock
generated
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
26
src/GitList/Provider/RoutingUtilServiceProvider.php
Normal file
26
src/GitList/Provider/RoutingUtilServiceProvider.php
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
52
src/GitList/Util/Routing.php
Normal file
52
src/GitList/Util/Routing.php
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user