diff --git a/rhodecode/apps/_base/__init__.py b/rhodecode/apps/_base/__init__.py
--- a/rhodecode/apps/_base/__init__.py
+++ b/rhodecode/apps/_base/__init__.py
@@ -24,8 +24,10 @@ from pylons import tmpl_context as c
from pyramid.httpexceptions import HTTPFound
from rhodecode.lib import helpers as h
-from rhodecode.lib.utils2 import StrictAttributeDict, safe_int
+from rhodecode.lib.utils import PartialRenderer
+from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
+from rhodecode.lib.ext_json import json
from rhodecode.model import repo
from rhodecode.model.db import User
from rhodecode.model.scm import ScmModel
@@ -37,6 +39,24 @@ ADMIN_PREFIX = '/_admin'
STATIC_FILE_PREFIX = '/_static'
+def get_format_ref_id(repo):
+ """Returns a `repo` specific reference formatter function"""
+ if h.is_svn(repo):
+ return _format_ref_id_svn
+ else:
+ return _format_ref_id
+
+
+def _format_ref_id(name, raw_id):
+ """Default formatting of a given reference `name`"""
+ return name
+
+
+def _format_ref_id_svn(name, raw_id):
+ """Special way of formatting a reference for Subversion including path"""
+ return '%s@%s' % (name, raw_id)
+
+
class TemplateArgs(StrictAttributeDict):
pass
@@ -170,6 +190,56 @@ class DataGridAppView(object):
return draw, start, length
+class BaseReferencesView(RepoAppView):
+ """
+ Base for reference view for branches, tags and bookmarks.
+ """
+ def load_default_context(self):
+ c = self._get_local_tmpl_context()
+
+ # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
+ c.repo_info = self.db_repo
+
+ self._register_global_c(c)
+ return c
+
+ def load_refs_context(self, ref_items, partials_template):
+ _render = PartialRenderer(partials_template)
+ _data = []
+ pre_load = ["author", "date", "message"]
+
+ is_svn = h.is_svn(self.rhodecode_vcs_repo)
+ format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
+
+ for ref_name, commit_id in ref_items:
+ commit = self.rhodecode_vcs_repo.get_commit(
+ commit_id=commit_id, pre_load=pre_load)
+
+ # TODO: johbo: Unify generation of reference links
+ use_commit_id = '/' in ref_name or is_svn
+ files_url = h.url(
+ 'files_home',
+ repo_name=c.repo_name,
+ f_path=ref_name if is_svn else '',
+ revision=commit_id if use_commit_id else ref_name,
+ at=ref_name)
+
+ _data.append({
+ "name": _render('name', ref_name, files_url),
+ "name_raw": ref_name,
+ "date": _render('date', commit.date),
+ "date_raw": datetime_to_time(commit.date),
+ "author": _render('author', commit.author),
+ "commit": _render(
+ 'commit', commit.message, commit.raw_id, commit.idx),
+ "commit_raw": commit.idx,
+ "compare": _render(
+ 'compare', format_ref_id(ref_name, commit.raw_id)),
+ })
+ c.has_references = bool(_data)
+ c.data = json.dumps(_data)
+
+
class RepoRoutePredicate(object):
def __init__(self, val, config):
self.val = val
diff --git a/rhodecode/apps/repository/__init__.py b/rhodecode/apps/repository/__init__.py
--- a/rhodecode/apps/repository/__init__.py
+++ b/rhodecode/apps/repository/__init__.py
@@ -21,6 +21,21 @@
def includeme(config):
+ # Tags
+ config.add_route(
+ name='tags_home',
+ pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
+
+ # Branches
+ config.add_route(
+ name='branches_home',
+ pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
+
+ # Bookmarks
+ config.add_route(
+ name='bookmarks_home',
+ pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
+
# Settings
config.add_route(
name='edit_repo',
diff --git a/rhodecode/tests/functional/test_bookmarks.py b/rhodecode/apps/repository/tests/test_repo_bookmarks.py
rename from rhodecode/tests/functional/test_bookmarks.py
rename to rhodecode/apps/repository/tests/test_repo_bookmarks.py
--- a/rhodecode/tests/functional/test_bookmarks.py
+++ b/rhodecode/apps/repository/tests/test_repo_bookmarks.py
@@ -18,24 +18,35 @@
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
+import pytest
from rhodecode.model.db import Repository
-from rhodecode.tests import *
-class TestBookmarksController(TestController):
+def route_path(name, params=None, **kwargs):
+ import urllib
+
+ base_url = {
+ 'bookmarks_home': '/{repo_name}/bookmarks',
+ }[name].format(**kwargs)
+
+ if params:
+ base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
+ return base_url
+
+
+@pytest.mark.usefixtures('autologin_user', 'app')
+class TestBookmarks(object):
def test_index(self, backend):
- self.log_user()
if backend.alias == 'hg':
- response = self.app.get(url(controller='bookmarks',
- action='index',
- repo_name=backend.repo_name))
+ response = self.app.get(
+ route_path('bookmarks_home', repo_name=backend.repo_name))
repo = Repository.get_by_repo_name(backend.repo_name)
for commit_id, obj_name in repo.scm_instance().bookmarks.items():
assert commit_id in response
assert obj_name in response
else:
- self.app.get(url(controller='bookmarks',
- action='index',
- repo_name=backend.repo_name), status=404)
\ No newline at end of file
+ self.app.get(
+ route_path('bookmarks_home', repo_name=backend.repo_name),
+ status=404)
diff --git a/rhodecode/tests/functional/test_branches.py b/rhodecode/apps/repository/tests/test_repo_branches.py
rename from rhodecode/tests/functional/test_branches.py
rename to rhodecode/apps/repository/tests/test_repo_branches.py
--- a/rhodecode/tests/functional/test_branches.py
+++ b/rhodecode/apps/repository/tests/test_repo_branches.py
@@ -18,17 +18,28 @@
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
+import pytest
from rhodecode.model.db import Repository
-from rhodecode.tests import *
-class TestBranchesController(TestController):
+def route_path(name, params=None, **kwargs):
+ import urllib
+
+ base_url = {
+ 'branches_home': '/{repo_name}/branches',
+ }[name].format(**kwargs)
+
+ if params:
+ base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
+ return base_url
+
+
+@pytest.mark.usefixtures('autologin_user', 'app')
+class TestBranchesController(object):
def test_index(self, backend):
- self.log_user()
- response = self.app.get(url(controller='branches',
- action='index',
- repo_name=backend.repo_name))
+ response = self.app.get(
+ route_path('branches_home', repo_name=backend.repo_name))
repo = Repository.get_by_repo_name(backend.repo_name)
diff --git a/rhodecode/tests/functional/test_tags.py b/rhodecode/apps/repository/tests/test_repo_tags.py
rename from rhodecode/tests/functional/test_tags.py
rename to rhodecode/apps/repository/tests/test_repo_tags.py
--- a/rhodecode/tests/functional/test_tags.py
+++ b/rhodecode/apps/repository/tests/test_repo_tags.py
@@ -18,16 +18,27 @@
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
+import pytest
from rhodecode.model.db import Repository
-from rhodecode.tests import *
-class TestTagsController(TestController):
+def route_path(name, params=None, **kwargs):
+ import urllib
+
+ base_url = {
+ 'tags_home': '/{repo_name}/tags',
+ }[name].format(**kwargs)
+
+ if params:
+ base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
+ return base_url
+
+
+@pytest.mark.usefixtures('autologin_user', 'app')
+class TestTagsController(object):
def test_index(self, backend):
- self.log_user()
- response = self.app.get(url(controller='tags',
- action='index',
- repo_name=backend.repo_name))
+ response = self.app.get(
+ route_path('tags_home', repo_name=backend.repo_name))
repo = Repository.get_by_repo_name(backend.repo_name)
diff --git a/rhodecode/apps/repository/views/repo_bookmarks.py b/rhodecode/apps/repository/views/repo_bookmarks.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/repository/views/repo_bookmarks.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2011-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+import logging
+
+from pyramid.httpexceptions import HTTPNotFound
+from pyramid.view import view_config
+
+from rhodecode.apps._base import BaseReferencesView
+from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
+from rhodecode.lib import helpers as h
+
+log = logging.getLogger(__name__)
+
+
+class RepoBookmarksView(BaseReferencesView):
+
+ @LoginRequired()
+ @HasRepoPermissionAnyDecorator(
+ 'repository.read', 'repository.write', 'repository.admin')
+ @view_config(
+ route_name='bookmarks_home', request_method='GET',
+ renderer='rhodecode:templates/bookmarks/bookmarks.mako')
+ def bookmarks(self):
+ c = self.load_default_context()
+
+ if not h.is_hg(self.db_repo):
+ raise HTTPNotFound()
+
+ ref_items = self.rhodecode_vcs_repo.bookmarks.items()
+ self.load_refs_context(
+ ref_items=ref_items, partials_template='bookmarks/bookmarks_data.mako')
+
+ return self._get_template_context(c)
diff --git a/rhodecode/apps/repository/views/repo_branches.py b/rhodecode/apps/repository/views/repo_branches.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/repository/views/repo_branches.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2011-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import logging
+from pyramid.view import view_config
+
+from rhodecode.apps._base import BaseReferencesView
+from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
+
+
+log = logging.getLogger(__name__)
+
+
+class RepoBranchesView(BaseReferencesView):
+
+ @LoginRequired()
+ @HasRepoPermissionAnyDecorator(
+ 'repository.read', 'repository.write', 'repository.admin')
+ @view_config(
+ route_name='branches_home', request_method='GET',
+ renderer='rhodecode:templates/branches/branches.mako')
+ def branches(self):
+ c = self.load_default_context()
+ c.closed_branches = self.rhodecode_vcs_repo.branches_closed
+ # NOTE(marcink):
+ # we need this trick because of PartialRenderer still uses the
+ # global 'c', we might not need this after full pylons migration
+ self._register_global_c(c)
+
+ ref_items = self.rhodecode_vcs_repo.branches_all.items()
+ self.load_refs_context(
+ ref_items=ref_items, partials_template='branches/branches_data.mako')
+
+ return self._get_template_context(c)
diff --git a/rhodecode/apps/repository/views/repo_tags.py b/rhodecode/apps/repository/views/repo_tags.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/repository/views/repo_tags.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2011-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import logging
+from pyramid.view import view_config
+
+from rhodecode.apps._base import BaseReferencesView
+from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
+
+log = logging.getLogger(__name__)
+
+
+class RepoTagsView(BaseReferencesView):
+
+ @LoginRequired()
+ @HasRepoPermissionAnyDecorator(
+ 'repository.read', 'repository.write', 'repository.admin')
+ @view_config(
+ route_name='tags_home', request_method='GET',
+ renderer='rhodecode:templates/tags/tags.mako')
+ def tags(self):
+ c = self.load_default_context()
+
+ ref_items = self.rhodecode_vcs_repo.tags.items()
+ self.load_refs_context(
+ ref_items=ref_items, partials_template='tags/tags_data.mako')
+
+ return self._get_template_context(c)
diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py
--- a/rhodecode/config/routing.py
+++ b/rhodecode/config/routing.py
@@ -890,18 +890,6 @@ def make_map(config):
controller='summary', conditions={'function': check_repo},
requirements=URL_NAME_REQUIREMENTS)
- rmap.connect('branches_home', '/{repo_name}/branches',
- controller='branches', conditions={'function': check_repo},
- requirements=URL_NAME_REQUIREMENTS)
-
- rmap.connect('tags_home', '/{repo_name}/tags',
- controller='tags', conditions={'function': check_repo},
- requirements=URL_NAME_REQUIREMENTS)
-
- rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
- controller='bookmarks', conditions={'function': check_repo},
- requirements=URL_NAME_REQUIREMENTS)
-
rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
controller='changelog', conditions={'function': check_repo},
requirements=URL_NAME_REQUIREMENTS)
diff --git a/rhodecode/controllers/base_references.py b/rhodecode/controllers/base_references.py
deleted file mode 100644
--- a/rhodecode/controllers/base_references.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2010-2017 RhodeCode GmbH
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License, version 3
-# (only), as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-# This program is dual-licensed. If you wish to learn more about the
-# RhodeCode Enterprise Edition, including its added features, Support services,
-# and proprietary license terms, please see https://rhodecode.com/licenses/
-
-from pylons import tmpl_context as c
-
-from rhodecode.controllers import utils
-from rhodecode.lib import helpers as h
-from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
-from rhodecode.lib.base import BaseRepoController, render
-from rhodecode.lib.ext_json import json
-from rhodecode.lib.utils import PartialRenderer
-from rhodecode.lib.utils2 import datetime_to_time
-
-
-class BaseReferencesController(BaseRepoController):
- """
- Base for reference controllers for branches, tags and bookmarks.
-
- Implement and set the following things:
-
- - `partials_template` is the source for the partials to use.
-
- - `template` is the template to render in the end.
-
- - `_get_reference_items(repo)` should return a sequence of tuples which
- map from `name` to `commit_id`.
-
- """
-
- @LoginRequired()
- @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
- 'repository.admin')
- def index(self):
- _render = PartialRenderer(self.partials_template)
- _data = []
- pre_load = ["author", "date", "message"]
- repo = c.rhodecode_repo
- is_svn = h.is_svn(repo)
- format_ref_id = utils.get_format_ref_id(repo)
-
- for ref_name, commit_id in self._get_reference_items(repo):
- commit = repo.get_commit(
- commit_id=commit_id, pre_load=pre_load)
-
- # TODO: johbo: Unify generation of reference links
- use_commit_id = '/' in ref_name or is_svn
- files_url = h.url(
- 'files_home',
- repo_name=c.repo_name,
- f_path=ref_name if is_svn else '',
- revision=commit_id if use_commit_id else ref_name,
- at=ref_name)
-
- _data.append({
- "name": _render('name', ref_name, files_url),
- "name_raw": ref_name,
- "date": _render('date', commit.date),
- "date_raw": datetime_to_time(commit.date),
- "author": _render('author', commit.author),
- "commit": _render(
- 'commit', commit.message, commit.raw_id, commit.idx),
- "commit_raw": commit.idx,
- "compare": _render(
- 'compare', format_ref_id(ref_name, commit.raw_id)),
- })
- c.has_references = bool(_data)
- c.data = json.dumps(_data)
- return render(self.template)
diff --git a/rhodecode/controllers/bookmarks.py b/rhodecode/controllers/bookmarks.py
deleted file mode 100644
--- a/rhodecode/controllers/bookmarks.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2011-2017 RhodeCode GmbH
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License, version 3
-# (only), as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-# This program is dual-licensed. If you wish to learn more about the
-# RhodeCode Enterprise Edition, including its added features, Support services,
-# and proprietary license terms, please see https://rhodecode.com/licenses/
-"""
-Bookmarks controller for rhodecode
-"""
-
-import logging
-
-from pylons import tmpl_context as c
-from webob.exc import HTTPNotFound
-
-from rhodecode.controllers.base_references import BaseReferencesController
-from rhodecode.lib import helpers as h
-
-log = logging.getLogger(__name__)
-
-
-class BookmarksController(BaseReferencesController):
-
- partials_template = 'bookmarks/bookmarks_data.mako'
- template = 'bookmarks/bookmarks.mako'
-
- def __before__(self):
- super(BookmarksController, self).__before__()
- if not h.is_hg(c.rhodecode_repo):
- raise HTTPNotFound()
-
- def _get_reference_items(self, repo):
- return repo.bookmarks.items()
diff --git a/rhodecode/controllers/branches.py b/rhodecode/controllers/branches.py
deleted file mode 100644
--- a/rhodecode/controllers/branches.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2010-2017 RhodeCode GmbH
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License, version 3
-# (only), as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-# This program is dual-licensed. If you wish to learn more about the
-# RhodeCode Enterprise Edition, including its added features, Support services,
-# and proprietary license terms, please see https://rhodecode.com/licenses/
-
-"""
-branches controller for rhodecode
-"""
-
-import logging
-
-from pylons import tmpl_context as c
-
-from rhodecode.controllers.base_references import BaseReferencesController
-
-
-log = logging.getLogger(__name__)
-
-
-class BranchesController(BaseReferencesController):
-
- partials_template = 'branches/branches_data.mako'
- template = 'branches/branches.mako'
-
- def __before__(self):
- super(BranchesController, self).__before__()
- c.closed_branches = c.rhodecode_repo.branches_closed
-
- def _get_reference_items(self, repo):
- return repo.branches_all.items()
diff --git a/rhodecode/controllers/tags.py b/rhodecode/controllers/tags.py
deleted file mode 100644
--- a/rhodecode/controllers/tags.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2010-2017 RhodeCode GmbH
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License, version 3
-# (only), as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-# This program is dual-licensed. If you wish to learn more about the
-# RhodeCode Enterprise Edition, including its added features, Support services,
-# and proprietary license terms, please see https://rhodecode.com/licenses/
-
-"""
-Tags controller for rhodecode
-"""
-
-import logging
-
-from rhodecode.controllers.base_references import BaseReferencesController
-
-log = logging.getLogger(__name__)
-
-
-class TagsController(BaseReferencesController):
-
- partials_template = 'tags/tags_data.mako'
- template = 'tags/tags.mako'
-
- def _get_reference_items(self, repo):
- return repo.tags.items()
diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js
--- a/rhodecode/public/js/rhodecode/routes.js
+++ b/rhodecode/public/js/rhodecode/routes.js
@@ -96,6 +96,9 @@ function registerRCRoutes() {
pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
pyroutes.register('repo_list_data', '/_repos', []);
pyroutes.register('goto_switcher_data', '/_goto_data', []);
+ pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
+ pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
+ pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
diff --git a/rhodecode/templates/summary/components.mako b/rhodecode/templates/summary/components.mako
--- a/rhodecode/templates/summary/components.mako
+++ b/rhodecode/templates/summary/components.mako
@@ -1,27 +1,27 @@
<%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
-
+
${ungettext(
'%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}
%if closed_branches:
-
+
${ungettext(
'%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}
%endif
-
+
${ungettext(
'%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}
%if bookmarks:
-
+
${ungettext(
'%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}