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)}}