diff --git a/src/GitList/Controller/BlobController.php b/src/GitList/Controller/BlobController.php index 9a44a27..90f753f 100644 --- a/src/GitList/Controller/BlobController.php +++ b/src/GitList/Controller/BlobController.php @@ -12,8 +12,12 @@ class BlobController implements ControllerProviderInterface { $route = $app['controllers_factory']; - $route->get('{repo}/blob/{branch}/{file}', function($repo, $branch, $file) use ($app) { + $route->get('{repo}/blob/{commitish_path}', function($repo, $commitish_path) use ($app) { $repository = $app['git']->getRepository($app['git.repos'] . $repo); + + list($branch, $file) = $app['util.routing'] + ->parseCommitishPathParam($commitish_path, $repo); + list($branch, $file) = $app['util.repository']->extractRef($repository, $branch, $file); $blob = $repository->getBlob("$branch:\"$file\""); @@ -38,14 +42,18 @@ class BlobController implements ControllerProviderInterface 'branches' => $repository->getBranches(), 'tags' => $repository->getTags(), )); - })->assert('file', '.+') - ->assert('repo', $app['util.routing']->getRepositoryRegex()) - ->assert('branch', '[\w-._\/]+') + })->assert('repo', $app['util.routing']->getRepositoryRegex()) + ->assert('commitish_path', '.+') ->bind('blob'); - $route->get('{repo}/raw/{branch}/{file}', function($repo, $branch, $file) use ($app) { + $route->get('{repo}/raw/{commitish_path}', function($repo, $commitish_path) use ($app) { $repository = $app['git']->getRepository($app['git.repos'] . $repo); + + list($branch, $file) = $app['util.routing'] + ->parseCommitishPathParam($commitish_path, $repo); + list($branch, $file) = $app['util.repository']->extractRef($repository, $branch, $file); + $blob = $repository->getBlob("$branch:\"$file\"")->output(); $headers = array(); @@ -58,9 +66,8 @@ class BlobController implements ControllerProviderInterface } return new Response($blob, 200, $headers); - })->assert('file', '.+') - ->assert('repo', $app['util.routing']->getRepositoryRegex()) - ->assert('branch', '[\w-._\/]+') + })->assert('repo', $app['util.routing']->getRepositoryRegex()) + ->assert('commitish_path', $app['util.routing']->getCommitishPathRegex()) ->bind('blob_raw'); return $route; diff --git a/src/GitList/Controller/CommitController.php b/src/GitList/Controller/CommitController.php index 2383ccf..e778d08 100644 --- a/src/GitList/Controller/CommitController.php +++ b/src/GitList/Controller/CommitController.php @@ -12,13 +12,16 @@ class CommitController implements ControllerProviderInterface { $route = $app['controllers_factory']; - $route->get('{repo}/commits/{branch}/{file}', function($repo, $branch, $file) use ($app) { + $route->get('{repo}/commits/{commitish_path}', function($repo, $commitish_path) use ($app) { $repository = $app['git']->getRepository($app['git.repos'] . $repo); - if ($branch === null) { - $branch = $repository->getHead(); + if ($commitish_path === null) { + $commitish_path = $repository->getHead(); } + list($branch, $file) = $app['util.routing'] + ->parseCommitishPathParam($commitish_path, $repo); + list($branch, $file) = $app['util.repository']->extractRef($repository, $branch, $file); $type = $file ? "$branch -- \"$file\"" : $branch; @@ -45,10 +48,8 @@ class CommitController implements ControllerProviderInterface 'file' => $file, )); })->assert('repo', $app['util.routing']->getRepositoryRegex()) - ->assert('branch', '[\w-._\/]+') - ->assert('file', '.+') - ->value('branch', null) - ->value('file', '') + ->assert('commitish_path', $app['util.routing']->getCommitishPathRegex()) + ->value('commitish_path', null) ->bind('commits'); $route->post('{repo}/commits/{branch}/search', function(Request $request, $repo, $branch = '') use ($app) { @@ -73,7 +74,7 @@ class CommitController implements ControllerProviderInterface 'query' => $query )); })->assert('repo', $app['util.routing']->getRepositoryRegex()) - ->assert('branch', '[\w-._\/]+') + ->assert('branch', $app['util.routing']->getBranchRegex()) ->bind('searchcommits'); $route->get('{repo}/commit/{commit}', function($repo, $commit) use ($app) { @@ -90,9 +91,12 @@ class CommitController implements ControllerProviderInterface ->assert('commit', '[a-f0-9^]+') ->bind('commit'); - $route->get('{repo}/blame/{branch}/{file}', function($repo, $branch, $file) use ($app) { + $route->get('{repo}/blame/{branch_file}', function($repo, $branch_file) use ($app) { $repository = $app['git']->getRepository($app['git.repos'] . $repo); + list($branch, $file) = $app['util.routing'] + ->parseCommitishPathParam($branch_file, $repo); + list($branch, $file) = $app['util.repository']->extractRef($repository, $branch, $file); $blames = $repository->getBlame("$branch -- \"$file\""); @@ -106,8 +110,7 @@ class CommitController implements ControllerProviderInterface 'blames' => $blames, )); })->assert('repo', $app['util.routing']->getRepositoryRegex()) - ->assert('file', '.+') - ->assert('branch', '[\w-._\/]+') + ->assert('branch_file', $app['util.routing']->getCommitishPathRegex()) ->bind('blame'); return $route; diff --git a/src/GitList/Controller/MainController.php b/src/GitList/Controller/MainController.php index 3262b04..8e76ba4 100644 --- a/src/GitList/Controller/MainController.php +++ b/src/GitList/Controller/MainController.php @@ -46,7 +46,7 @@ class MainController implements ControllerProviderInterface 'authors' => $authors, )); })->assert('repo', $app['util.routing']->getRepositoryRegex()) - ->assert('branch', '[\w-._\/]+') + ->assert('branch', $app['util.routing']->getBranchRegex()) ->value('branch', null) ->bind('stats'); @@ -62,7 +62,7 @@ class MainController implements ControllerProviderInterface return new Response($html, 200, array('Content-Type' => 'application/rss+xml')); })->assert('repo', $app['util.routing']->getRepositoryRegex()) - ->assert('branch', '[\w-._\/]+') + ->assert('branch', $app['util.routing']->getBranchRegex()) ->bind('rss'); return $route; diff --git a/src/GitList/Controller/TreeController.php b/src/GitList/Controller/TreeController.php index 86be94b..1ffb767 100644 --- a/src/GitList/Controller/TreeController.php +++ b/src/GitList/Controller/TreeController.php @@ -13,12 +13,14 @@ class TreeController implements ControllerProviderInterface { $route = $app['controllers_factory']; - $route->get('{repo}/tree/{branch}/{tree}/', $treeController = function($repo, $branch = '', $tree = '') use ($app) { + $route->get('{repo}/tree/{commitish_path}/', $treeController = function($repo, $commitish_path = '') use ($app) { $repository = $app['git']->getRepository($app['git.repos'] . $repo); - if (!$branch) { - $branch = $repository->getHead(); + if (!$commitish_path) { + $commitish_path = $repository->getHead(); } + list($branch, $tree) = $app['util.routing']->parseCommitishPathParam($commitish_path, $repo); + list($branch, $tree) = $app['util.repository']->extractRef($repository, $branch, $tree); $files = $repository->getTree($tree ? "$branch:\"$tree\"/" : $branch); $breadcrumbs = $app['util.view']->getBreadcrumbs($tree); @@ -42,8 +44,7 @@ class TreeController implements ControllerProviderInterface 'readme' => $app['util.repository']->getReadme($repo, $branch), )); })->assert('repo', $app['util.routing']->getRepositoryRegex()) - ->assert('branch', '[\w-._\/]+') - ->assert('tree', '.+') + ->assert('commitish_path', $app['util.routing']->getCommitishPathRegex()) ->bind('tree'); $route->post('{repo}/tree/{branch}/search', function(Request $request, $repo, $branch = '', $tree = '') use ($app) { diff --git a/src/GitList/Git/Repository.php b/src/GitList/Git/Repository.php index 57c6095..3541b6f 100644 --- a/src/GitList/Git/Repository.php +++ b/src/GitList/Git/Repository.php @@ -10,6 +10,20 @@ use Symfony\Component\Filesystem\Filesystem; class Repository extends BaseRepository { + /** + * Return TRUE if the repo contains this commit. + * + * @param $commitHash Hash of commit whose existence we want to check + * @return boolean Whether or not the commit exists in this repo + */ + public function hasCommit($commitHash) + { + $logs = $this->getClient()->run($this, "show $commitHash"); + $logs = explode("\n", $logs); + + return strpos($logs[0], 'commit') === 0; + } + /** * Show the data from a specific commit * @@ -313,4 +327,23 @@ class Repository extends BaseRepository $fs->mkdir(dirname($output)); $this->getClient()->run($this, "archive --format=$format --output=$output $tree"); } + + /** + * Return TRUE if $path exists in $branch; return FALSE otherwise. + * + * @param string $commitish Commitish reference; branch, tag, SHA1, etc. + * @param string $path Path whose existence we want to verify. + * + * GRIPE Arguably belongs in Gitter, as it's generally useful functionality. + * Also, this really may not be the best way to do this. + */ + public function pathExists($commitish, $path) { + $output = $this->getClient()->run($this, "ls-tree $commitish $path"); + + if (strlen($output) > 0) { + return TRUE; + } + + return FALSE; + } } diff --git a/src/GitList/Util/Routing.php b/src/GitList/Util/Routing.php index d0a60ff..3f3c866 100644 --- a/src/GitList/Util/Routing.php +++ b/src/GitList/Util/Routing.php @@ -13,6 +13,93 @@ class Routing $this->app = $app; } + /* @brief Return $commitish, $path parsed from $commitish_path, based on + * what's in $repo. Raise a 404 if $branchpath does not represent a + * valid branch and path. + * + * A helper for parsing routes that use commit-ish names and paths + * separated by /, since route regexes are not enough to get that right. + */ + public function parseCommitishPathParam($commitish_path, $repo) { + $app = $this->app; + $repository = $app['git']->getRepository($app['git.repos'] . $repo); + + $commitish = null; + $path = null; + + $slash_pos = strpos($commitish_path, '/'); + if (strlen($commitish_path) >= 40 && + ($slash_pos === FALSE || + $slash_pos === 40)) { + // We may have a commit hash as our commitish. + $hash = substr($commitish_path, 0, 40); + if ($repository->hasCommit($hash)) { + $commitish = $hash; + } + } + + if ($commitish === null) { + // DEBUG Can you have a repo with no branches? How should we handle + // that? + $branches = $repository->getBranches(); + + $tags = $repository->getTags(); + if ($tags !== null && count($tags) > 0) { + $branches = array_merge($branches, $tags); + } + + $matched_branch = null; + $matched_branch_name_len = 0; + foreach ($branches as $branch) { + if (strpos($commitish_path, $branch) === 0 && + strlen($branch) > $matched_branch_name_len) { + $matched_branch = $branch; + $matched_branch_name_len = strlen($matched_branch); + } + } + + $commitish = $matched_branch; + } + + if ($commitish === null) { + $app->abort(404, "'$branch_path' does not appear to contain a " . + "commit-ish for '$repo.'"); + } + + $commitish_len = strlen($commitish); + $path = substr($commitish_path, $commitish_len); + if (strpos($path, '/') === 0) { + $path = substr($path, 1); + } + + $commit_has_path = $repository->pathExists($commitish, $path); + if ($commit_has_path !== TRUE) { + $app->abort(404, "\"$path\" does not exist in \"$commitish\"."); + } + + return array($commitish, $path); + } + + public function getBranchRegex() { + static $branch_regex = null; + + if ($branch_regex === null) { + $branch_regex = '[\w-._\/]+'; + } + + return $branch_regex; + } + + public function getCommitishPathRegex() { + static $commitish_path_regex = null; + + if ($commitish_path_regex === null) { + $commitish_path_regex = '.+'; + } + + return $commitish_path_regex; + } + public function getRepositoryRegex() { static $regex = null; diff --git a/views/breadcrumb.twig b/views/breadcrumb.twig index c7e954a..73fff7a 100644 --- a/views/breadcrumb.twig +++ b/views/breadcrumb.twig @@ -2,7 +2,7 @@
  • {{ repo }}
  • {% for breadcrumb in breadcrumbs %} / - {% if not loop.last %}{{ breadcrumb.dir }}{% endif %}{% if loop.last %}{{ breadcrumb.dir }}{% endif %} + {% if not loop.last %}{{ breadcrumb.dir }}{% endif %}{% if loop.last %}{{ breadcrumb.dir }}{% endif %} {% endfor %} {% block extra %}{% endblock %} diff --git a/views/commit.twig b/views/commit.twig index 058d037..fae6865 100644 --- a/views/commit.twig +++ b/views/commit.twig @@ -30,8 +30,8 @@ diff --git a/views/file.twig b/views/file.twig index 71adbec..6b18290 100644 --- a/views/file.twig +++ b/views/file.twig @@ -12,9 +12,9 @@
    {% if fileType == 'image' %} diff --git a/views/menu.twig b/views/menu.twig index d35bd4e..b920e65 100644 --- a/views/menu.twig +++ b/views/menu.twig @@ -1,5 +1,5 @@ diff --git a/views/tree.twig b/views/tree.twig index 10fcc4f..d53c736 100644 --- a/views/tree.twig +++ b/views/tree.twig @@ -32,7 +32,7 @@ {% if not parent %} .. {% else %} - .. + .. {% endif %} @@ -43,9 +43,9 @@ {{ file.name }} {{ file.mode }}