# HG changeset patch # User Marcin Kuzminski # Date 2017-06-07 11:23:21 # Node ID 1cce4ff21ebb7c6d9e9797e74347ad5e5036c8e0 # Parent 376e1699668efa1ba6b1356119d14b51b7d152d4 repo-summary: re-implemented summary view as pyramid. - changed exception handler, to now work in pure pyramid mode - changed missing requirements logic on repo based views that require vcs-connection 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 @@ -105,9 +105,14 @@ class BaseAppView(object): raise HTTPFound( self.request.route_path('my_account_password')) - def _get_local_tmpl_context(self): + def _get_local_tmpl_context(self, include_app_defaults=False): c = TemplateArgs() c.auth_user = self.request.user + if include_app_defaults: + # NOTE(marcink): after full pyramid migration include_app_defaults + # should be turned on by default + from rhodecode.lib.base import attach_context_attributes + attach_context_attributes(c, self.request, self.request.user.user_id) return c def _register_global_c(self, tmpl_args): @@ -154,8 +159,10 @@ class RepoAppView(BaseAppView): 'Requirements are missing for repository %s: %s', self.db_repo_name, error.message) - def _get_local_tmpl_context(self): - c = super(RepoAppView, self)._get_local_tmpl_context() + def _get_local_tmpl_context(self, include_app_defaults=False): + c = super(RepoAppView, self)._get_local_tmpl_context( + include_app_defaults=include_app_defaults) + # register common vars for this type of view c.rhodecode_db_repo = self.db_repo c.repo_name = self.db_repo_name @@ -309,7 +316,7 @@ class RepoTypeRoutePredicate(object): # _('Action not supported for %s.' % rhodecode_repo.alias)), # category='warning') # return redirect( - # url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name)) + # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name)) return False diff --git a/rhodecode/apps/home/views.py b/rhodecode/apps/home/views.py --- a/rhodecode/apps/home/views.py +++ b/rhodecode/apps/home/views.py @@ -123,7 +123,7 @@ class HomeView(BaseAppView): 'text': obj['name'], 'type': 'repo', 'obj': obj['dbrepo'], - 'url': h.url('summary_home', repo_name=obj['name']) + 'url': h.route_path('repo_summary', repo_name=obj['name']) } for obj in repo_iter] 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 @@ -17,18 +17,33 @@ # 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 rhodecode.apps._base import add_route_with_slash def includeme(config): # Summary - config.add_route( - name='repo_summary', - pattern='/{repo_name:.*?[^/]}', repo_route=True) - + # NOTE(marcink): one additional route is defined in very bottom, catch + # all pattern config.add_route( name='repo_summary_explicit', pattern='/{repo_name:.*?[^/]}/summary', repo_route=True) + config.add_route( + name='repo_summary_commits', + pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True) + + # refs data + config.add_route( + name='repo_refs_data', + pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True) + + config.add_route( + name='repo_refs_changelog_data', + pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True) + + config.add_route( + name='repo_stats', + pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True) # Tags config.add_route( @@ -40,7 +55,6 @@ def includeme(config): name='branches_home', pattern='/{repo_name:.*?[^/]}/branches', repo_route=True) - # Bookmarks config.add_route( name='bookmarks_home', pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True) @@ -125,9 +139,10 @@ def includeme(config): pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True) # NOTE(marcink): needs to be at the end for catch-all - # config.add_route( - # name='repo_summary', - # pattern='/{repo_name:.*?[^/]}', repo_route=True) + add_route_with_slash( + config, + name='repo_summary', + pattern='/{repo_name:.*?[^/]}', repo_route=True) # Scan module for configuration decorators. config.scan() diff --git a/rhodecode/tests/functional/test_summary.py b/rhodecode/apps/repository/tests/test_repo_summary.py rename from rhodecode/tests/functional/test_summary.py rename to rhodecode/apps/repository/tests/test_repo_summary.py --- a/rhodecode/tests/functional/test_summary.py +++ b/rhodecode/apps/repository/tests/test_repo_summary.py @@ -23,16 +23,16 @@ import re import mock import pytest -from rhodecode.controllers import summary +from rhodecode.apps.repository.views.repo_summary import RepoSummaryView from rhodecode.lib import helpers as h from rhodecode.lib.compat import OrderedDict +from rhodecode.lib.utils2 import AttributeDict from rhodecode.lib.vcs.exceptions import RepositoryRequirementError from rhodecode.model.db import Repository from rhodecode.model.meta import Session from rhodecode.model.repo import RepoModel from rhodecode.model.scm import ScmModel -from rhodecode.tests import ( - TestController, url, HG_REPO, assert_session_flash) +from rhodecode.tests import assert_session_flash from rhodecode.tests.fixture import Fixture from rhodecode.tests.utils import AssertResponse, repo_on_filesystem @@ -40,14 +40,31 @@ from rhodecode.tests.utils import Assert fixture = Fixture() -class TestSummaryController(TestController): - def test_index(self, backend, http_host_only_stub): - self.log_user() +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'repo_summary': '/{repo_name}', + 'repo_stats': '/{repo_name}/repo_stats/{commit_id}', + 'repo_refs_data': '/{repo_name}/refs-data', + 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog' + + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + +@pytest.mark.usefixtures('app') +class TestSummaryView(object): + def test_index(self, autologin_user, backend, http_host_only_stub): repo_id = backend.repo.repo_id repo_name = backend.repo_name with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy', return_value=False): - response = self.app.get(url('summary_home', repo_name=repo_name)) + response = self.app.get( + route_path('repo_summary', repo_name=repo_name)) # repo type response.mustcontain( @@ -66,11 +83,11 @@ class TestSummaryController(TestControll 'id="clone_url_id" readonly="readonly"' ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, )) - def test_index_svn_without_proxy(self, backend_svn, http_host_only_stub): - self.log_user() + def test_index_svn_without_proxy( + self, autologin_user, backend_svn, http_host_only_stub): repo_id = backend_svn.repo.repo_id repo_name = backend_svn.repo_name - response = self.app.get(url('summary_home', repo_name=repo_name)) + response = self.app.get(route_path('repo_summary', repo_name=repo_name)) # clone url... response.mustcontain( 'id="clone_url" disabled' @@ -79,14 +96,15 @@ class TestSummaryController(TestControll 'id="clone_url_id" disabled' ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, )) - def test_index_with_trailing_slash(self, autologin_user, backend, - http_host_only_stub): + def test_index_with_trailing_slash( + self, autologin_user, backend, http_host_only_stub): + repo_id = backend.repo.repo_id repo_name = backend.repo_name with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy', return_value=False): response = self.app.get( - url('summary_home', repo_name=repo_name) + '/', + route_path('repo_summary', repo_name=repo_name) + '/', status=200) # clone url... @@ -97,11 +115,10 @@ class TestSummaryController(TestControll 'id="clone_url_id" readonly="readonly"' ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, )) - def test_index_by_id(self, backend): - self.log_user() + def test_index_by_id(self, autologin_user, backend): repo_id = backend.repo.repo_id - response = self.app.get(url( - 'summary_home', repo_name='_%s' % (repo_id,))) + response = self.app.get( + route_path('repo_summary', repo_name='_%s' % (repo_id,))) # repo type response.mustcontain( @@ -112,10 +129,9 @@ class TestSummaryController(TestControll """""" ) - def test_index_by_repo_having_id_path_in_name_hg(self): - self.log_user() + def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user): fixture.create_repo(name='repo_1') - response = self.app.get(url('summary_home', repo_name='repo_1')) + response = self.app.get(route_path('repo_summary', repo_name='repo_1')) try: response.mustcontain("repo_1") @@ -123,11 +139,11 @@ class TestSummaryController(TestControll RepoModel().delete(Repository.get_by_repo_name('repo_1')) Session().commit() - def test_index_with_anonymous_access_disabled(self): - with fixture.anon_access(False): - response = self.app.get(url('summary_home', repo_name=HG_REPO), - status=302) - assert 'login' in response.location + def test_index_with_anonymous_access_disabled( + self, backend, disable_anonymous_user): + response = self.app.get( + route_path('repo_summary', repo_name=backend.repo_name), status=302) + assert 'login' in response.location def _enable_stats(self, repo): r = Repository.get_by_repo_name(repo) @@ -174,17 +190,15 @@ class TestSummaryController(TestControll }, } - def test_repo_stats(self, backend, xhr_header): - self.log_user() + def test_repo_stats(self, autologin_user, backend, xhr_header): response = self.app.get( - url('repo_stats', - repo_name=backend.repo_name, commit_id='tip'), + route_path( + 'repo_stats', repo_name=backend.repo_name, commit_id='tip'), extra_environ=xhr_header, status=200) assert re.match(r'6[\d\.]+ KiB', response.json['size']) - def test_repo_stats_code_stats_enabled(self, backend, xhr_header): - self.log_user() + def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header): repo_name = backend.repo_name # codes stats @@ -192,8 +206,8 @@ class TestSummaryController(TestControll ScmModel().mark_for_invalidation(repo_name) response = self.app.get( - url('repo_stats', - repo_name=backend.repo_name, commit_id='tip'), + route_path( + 'repo_stats', repo_name=backend.repo_name, commit_id='tip'), extra_environ=xhr_header, status=200) @@ -204,7 +218,7 @@ class TestSummaryController(TestControll def test_repo_refs_data(self, backend): response = self.app.get( - url('repo_refs_data', repo_name=backend.repo_name), + route_path('repo_refs_data', repo_name=backend.repo_name), status=200) # Ensure that there is the correct amount of items in the result @@ -221,72 +235,68 @@ class TestSummaryController(TestControll Repository, 'scm_instance', side_effect=RepositoryRequirementError) with scm_patcher: - response = self.app.get(url('summary_home', repo_name=repo_name)) + response = self.app.get(route_path('repo_summary', repo_name=repo_name)) assert_response = AssertResponse(response) assert_response.element_contains( '.main .alert-warning strong', 'Missing requirements') assert_response.element_contains( '.main .alert-warning', - 'These commits cannot be displayed, because this repository' - ' uses the Mercurial largefiles extension, which was not enabled.') + 'Commits cannot be displayed, because this repository ' + 'uses one or more extensions, which was not enabled.') def test_missing_requirements_page_does_not_contains_switch_to( - self, backend): - self.log_user() + self, autologin_user, backend): repo_name = backend.repo_name scm_patcher = mock.patch.object( Repository, 'scm_instance', side_effect=RepositoryRequirementError) with scm_patcher: - response = self.app.get(url('summary_home', repo_name=repo_name)) + response = self.app.get(route_path('repo_summary', repo_name=repo_name)) response.mustcontain(no='Switch To') -@pytest.mark.usefixtures('pylonsapp') -class TestSwitcherReferenceData: +@pytest.mark.usefixtures('app') +class TestRepoLocation(object): - def test_creates_reference_urls_based_on_name(self): - references = { - 'name': 'commit_id', - } - controller = summary.SummaryController() - is_svn = False - result = controller._switcher_reference_data( - 'repo_name', references, is_svn) - expected_url = h.url( - 'files_home', repo_name='repo_name', revision='name', - at='name') - assert result[0]['files_url'] == expected_url + @pytest.mark.parametrize("suffix", [u'', u'ąęł'], ids=['', 'non-ascii']) + def test_manual_delete(self, autologin_user, backend, suffix, csrf_token): + repo = backend.create_repo(name_suffix=suffix) + repo_name = repo.repo_name + + # delete from file system + RepoModel()._delete_filesystem_repo(repo) - def test_urls_contain_commit_id_if_slash_in_name(self): - references = { - 'name/with/slash': 'commit_id', - } - controller = summary.SummaryController() - is_svn = False - result = controller._switcher_reference_data( - 'repo_name', references, is_svn) - expected_url = h.url( - 'files_home', repo_name='repo_name', revision='commit_id', - at='name/with/slash') - assert result[0]['files_url'] == expected_url + # test if the repo is still in the database + new_repo = RepoModel().get_by_repo_name(repo_name) + assert new_repo.repo_name == repo_name - def test_adds_reference_to_path_for_svn(self): - references = { - 'name/with/slash': 'commit_id', - } - controller = summary.SummaryController() - is_svn = True - result = controller._switcher_reference_data( - 'repo_name', references, is_svn) - expected_url = h.url( - 'files_home', repo_name='repo_name', f_path='name/with/slash', - revision='commit_id', at='name/with/slash') - assert result[0]['files_url'] == expected_url + # check if repo is not in the filesystem + assert not repo_on_filesystem(repo_name) + self.assert_repo_not_found_redirect(repo_name) + + def assert_repo_not_found_redirect(self, repo_name): + # run the check page that triggers the other flash message + response = self.app.get(h.url('repo_check_home', repo_name=repo_name)) + assert_session_flash( + response, 'The repository at %s cannot be located.' % repo_name) -@pytest.mark.usefixtures('pylonsapp') -class TestCreateReferenceData: +@pytest.fixture() +def summary_view(context_stub, request_stub, user_util): + """ + Bootstrap view to test the view functions + """ + request_stub.matched_route = AttributeDict(name='test_view') + + request_stub.user = user_util.create_user().AuthUser + request_stub.db_repo = user_util.create_repo() + + view = RepoSummaryView(context=context_stub, request=request_stub) + return view + + +@pytest.mark.usefixtures('app') +class TestCreateReferenceData(object): @pytest.fixture def example_refs(self): @@ -297,14 +307,13 @@ class TestCreateReferenceData: ] return example_refs - def test_generates_refs_based_on_commit_ids(self, example_refs): + def test_generates_refs_based_on_commit_ids(self, example_refs, summary_view): repo = mock.Mock() repo.name = 'test-repo' repo.alias = 'git' full_repo_name = 'pytest-repo-group/' + repo.name - controller = summary.SummaryController() - result = controller._create_reference_data( + result = summary_view._create_reference_data( repo, full_repo_name, example_refs) expected_files_url = '/{}/files/'.format(full_repo_name) @@ -333,13 +342,13 @@ class TestCreateReferenceData: }] assert result == expected_result - def test_generates_refs_with_path_for_svn(self, example_refs): + def test_generates_refs_with_path_for_svn(self, example_refs, summary_view): repo = mock.Mock() repo.name = 'test-repo' repo.alias = 'svn' full_repo_name = 'pytest-repo-group/' + repo.name - controller = summary.SummaryController() - result = controller._create_reference_data( + + result = summary_view._create_reference_data( repo, full_repo_name, example_refs) expected_files_url = '/{}/files/'.format(full_repo_name) @@ -373,35 +382,9 @@ class TestCreateReferenceData: assert result == expected_result -@pytest.mark.usefixtures("app") -class TestRepoLocation: - - @pytest.mark.parametrize("suffix", [u'', u'ąęł'], ids=['', 'non-ascii']) - def test_manual_delete(self, autologin_user, backend, suffix, csrf_token): - repo = backend.create_repo(name_suffix=suffix) - repo_name = repo.repo_name - - # delete from file system - RepoModel()._delete_filesystem_repo(repo) - - # test if the repo is still in the database - new_repo = RepoModel().get_by_repo_name(repo_name) - assert new_repo.repo_name == repo_name +class TestCreateFilesUrl(object): - # check if repo is not in the filesystem - assert not repo_on_filesystem(repo_name) - self.assert_repo_not_found_redirect(repo_name) - - def assert_repo_not_found_redirect(self, repo_name): - # run the check page that triggers the other flash message - response = self.app.get(url('repo_check_home', repo_name=repo_name)) - assert_session_flash( - response, 'The repository at %s cannot be located.' % repo_name) - - -class TestCreateFilesUrl(object): - def test_creates_non_svn_url(self): - controller = summary.SummaryController() + def test_creates_non_svn_url(self, summary_view): repo = mock.Mock() repo.name = 'abcde' full_repo_name = 'test-repo-group/' + repo.name @@ -409,16 +392,15 @@ class TestCreateFilesUrl(object): raw_id = 'deadbeef0123456789' is_svn = False - with mock.patch.object(summary.h, 'url') as url_mock: - result = controller._create_files_url( + with mock.patch('rhodecode.lib.helpers.url') as url_mock: + result = summary_view._create_files_url( repo, full_repo_name, ref_name, raw_id, is_svn) url_mock.assert_called_once_with( 'files_home', repo_name=full_repo_name, f_path='', revision=ref_name, at=ref_name) assert result == url_mock.return_value - def test_creates_svn_url(self): - controller = summary.SummaryController() + def test_creates_svn_url(self, summary_view): repo = mock.Mock() repo.name = 'abcde' full_repo_name = 'test-repo-group/' + repo.name @@ -426,16 +408,15 @@ class TestCreateFilesUrl(object): raw_id = 'deadbeef0123456789' is_svn = True - with mock.patch.object(summary.h, 'url') as url_mock: - result = controller._create_files_url( + with mock.patch('rhodecode.lib.helpers.url') as url_mock: + result = summary_view._create_files_url( repo, full_repo_name, ref_name, raw_id, is_svn) url_mock.assert_called_once_with( 'files_home', repo_name=full_repo_name, f_path=ref_name, revision=raw_id, at=ref_name) assert result == url_mock.return_value - def test_name_has_slashes(self): - controller = summary.SummaryController() + def test_name_has_slashes(self, summary_view): repo = mock.Mock() repo.name = 'abcde' full_repo_name = 'test-repo-group/' + repo.name @@ -443,8 +424,8 @@ class TestCreateFilesUrl(object): raw_id = 'deadbeef0123456789' is_svn = False - with mock.patch.object(summary.h, 'url') as url_mock: - result = controller._create_files_url( + with mock.patch('rhodecode.lib.helpers.url') as url_mock: + result = summary_view._create_files_url( repo, full_repo_name, ref_name, raw_id, is_svn) url_mock.assert_called_once_with( 'files_home', repo_name=full_repo_name, f_path='', revision=raw_id, @@ -463,42 +444,39 @@ class TestReferenceItems(object): def _format_function(name, id_): return 'format_function_{}_{}'.format(name, id_) - def test_creates_required_amount_of_items(self): + def test_creates_required_amount_of_items(self, summary_view): amount = 100 refs = { 'ref{}'.format(i): '{0:040d}'.format(i) for i in range(amount) } - controller = summary.SummaryController() - - url_patcher = mock.patch.object( - controller, '_create_files_url') - svn_patcher = mock.patch.object( - summary.h, 'is_svn', return_value=False) + url_patcher = mock.patch.object(summary_view, '_create_files_url') + svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn', + return_value=False) with url_patcher as url_mock, svn_patcher: - result = controller._create_reference_items( + result = summary_view._create_reference_items( self.repo, self.repo_full_name, refs, self.ref_type, self._format_function) assert len(result) == amount assert url_mock.call_count == amount - def test_single_item_details(self): + def test_single_item_details(self, summary_view): ref_name = 'ref1' ref_id = 'deadbeef' refs = { ref_name: ref_id } - controller = summary.SummaryController() + svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn', + return_value=False) + url_patcher = mock.patch.object( - controller, '_create_files_url', return_value=self.fake_url) - svn_patcher = mock.patch.object( - summary.h, 'is_svn', return_value=False) + summary_view, '_create_files_url', return_value=self.fake_url) with url_patcher as url_mock, svn_patcher: - result = controller._create_reference_items( + result = summary_view._create_reference_items( self.repo, self.repo_full_name, refs, self.ref_type, self._format_function) diff --git a/rhodecode/apps/repository/views/repo_summary.py b/rhodecode/apps/repository/views/repo_summary.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repository/views/repo_summary.py @@ -0,0 +1,368 @@ +# -*- 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 +import string + +from pyramid.view import view_config + +from beaker.cache import cache_region + + +from rhodecode.controllers import utils + +from rhodecode.apps._base import RepoAppView +from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP) +from rhodecode.lib import caches, helpers as h +from rhodecode.lib.helpers import RepoPage +from rhodecode.lib.utils2 import safe_str, safe_int +from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator +from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links +from rhodecode.lib.ext_json import json +from rhodecode.lib.vcs.backends.base import EmptyCommit +from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError +from rhodecode.model.db import Statistics, CacheKey, User +from rhodecode.model.meta import Session +from rhodecode.model.repo import ReadmeFinder +from rhodecode.model.scm import ScmModel + +log = logging.getLogger(__name__) + + +class RepoSummaryView(RepoAppView): + + def load_default_context(self): + c = self._get_local_tmpl_context(include_app_defaults=True) + + # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead + c.repo_info = self.db_repo + c.rhodecode_repo = None + if not c.repository_requirements_missing: + c.rhodecode_repo = self.rhodecode_vcs_repo + + self._register_global_c(c) + return c + + def _get_readme_data(self, db_repo, default_renderer): + repo_name = db_repo.repo_name + log.debug('Looking for README file') + + @cache_region('long_term') + def _generate_readme(cache_key): + readme_data = None + readme_node = None + readme_filename = None + commit = self._get_landing_commit_or_none(db_repo) + if commit: + log.debug("Searching for a README file.") + readme_node = ReadmeFinder(default_renderer).search(commit) + if readme_node: + relative_url = h.url('files_raw_home', + repo_name=repo_name, + revision=commit.raw_id, + f_path=readme_node.path) + readme_data = self._render_readme_or_none( + commit, readme_node, relative_url) + readme_filename = readme_node.path + return readme_data, readme_filename + + invalidator_context = CacheKey.repo_context_cache( + _generate_readme, repo_name, CacheKey.CACHE_TYPE_README) + + with invalidator_context as context: + context.invalidate() + computed = context.compute() + + return computed + + def _get_landing_commit_or_none(self, db_repo): + log.debug("Getting the landing commit.") + try: + commit = db_repo.get_landing_commit() + if not isinstance(commit, EmptyCommit): + return commit + else: + log.debug("Repository is empty, no README to render.") + except CommitError: + log.exception( + "Problem getting commit when trying to render the README.") + + def _render_readme_or_none(self, commit, readme_node, relative_url): + log.debug( + 'Found README file `%s` rendering...', readme_node.path) + renderer = MarkupRenderer() + try: + html_source = renderer.render( + readme_node.content, filename=readme_node.path) + if relative_url: + return relative_links(html_source, relative_url) + return html_source + except Exception: + log.exception( + "Exception while trying to render the README") + + def _load_commits_context(self, c): + p = safe_int(self.request.GET.get('page'), 1) + size = safe_int(self.request.GET.get('size'), 10) + + def url_generator(**kw): + query_params = { + 'size': size + } + query_params.update(kw) + return h.route_path( + 'repo_summary_commits', + repo_name=c.rhodecode_db_repo.repo_name, _query=query_params) + + pre_load = ['author', 'branch', 'date', 'message'] + try: + collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load) + except EmptyRepositoryError: + collection = self.rhodecode_vcs_repo + + c.repo_commits = RepoPage( + collection, page=p, items_per_page=size, url=url_generator) + page_ids = [x.raw_id for x in c.repo_commits] + c.comments = self.db_repo.get_comments(page_ids) + c.statuses = self.db_repo.statuses(page_ids) + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_summary_commits', request_method='GET', + renderer='rhodecode:templates/summary/summary_commits.mako') + def summary_commits(self): + c = self.load_default_context() + self._load_commits_context(c) + return self._get_template_context(c) + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_summary', request_method='GET', + renderer='rhodecode:templates/summary/summary.mako') + @view_config( + route_name='repo_summary_slash', request_method='GET', + renderer='rhodecode:templates/summary/summary.mako') + def summary(self): + c = self.load_default_context() + + # Prepare the clone URL + username = '' + if self._rhodecode_user.username != User.DEFAULT_USER: + username = safe_str(self._rhodecode_user.username) + + _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl + if '{repo}' in _def_clone_uri: + _def_clone_uri_by_id = _def_clone_uri.replace( + '{repo}', '_{repoid}') + elif '{repoid}' in _def_clone_uri: + _def_clone_uri_by_id = _def_clone_uri.replace( + '_{repoid}', '{repo}') + + c.clone_repo_url = self.db_repo.clone_url( + user=username, uri_tmpl=_def_clone_uri) + c.clone_repo_url_id = self.db_repo.clone_url( + user=username, uri_tmpl=_def_clone_uri_by_id) + + # If enabled, get statistics data + + c.show_stats = bool(self.db_repo.enable_statistics) + + stats = Session().query(Statistics) \ + .filter(Statistics.repository == self.db_repo) \ + .scalar() + + c.stats_percentage = 0 + + if stats and stats.languages: + c.no_data = False is self.db_repo.enable_statistics + lang_stats_d = json.loads(stats.languages) + + # Sort first by decreasing count and second by the file extension, + # so we have a consistent output. + lang_stats_items = sorted(lang_stats_d.iteritems(), + key=lambda k: (-k[1], k[0]))[:10] + lang_stats = [(x, {"count": y, + "desc": LANGUAGES_EXTENSIONS_MAP.get(x)}) + for x, y in lang_stats_items] + + c.trending_languages = json.dumps(lang_stats) + else: + c.no_data = True + c.trending_languages = json.dumps({}) + + scm_model = ScmModel() + c.enable_downloads = self.db_repo.enable_downloads + c.repository_followers = scm_model.get_followers(self.db_repo) + c.repository_forks = scm_model.get_forks(self.db_repo) + c.repository_is_user_following = scm_model.is_following_repo( + self.db_repo_name, self._rhodecode_user.user_id) + + # first interaction with the VCS instance after here... + if c.repository_requirements_missing: + self.request.override_renderer = \ + 'rhodecode:templates/summary/missing_requirements.mako' + return self._get_template_context(c) + + c.readme_data, c.readme_file = \ + self._get_readme_data(self.db_repo, c.visual.default_renderer) + + # loads the summary commits template context + self._load_commits_context(c) + + return self._get_template_context(c) + + def get_request_commit_id(self): + return self.request.matchdict['commit_id'] + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_stats', request_method='GET', + renderer='json_ext') + def repo_stats(self): + commit_id = self.get_request_commit_id() + + _namespace = caches.get_repo_namespace_key( + caches.SUMMARY_STATS, self.db_repo_name) + show_stats = bool(self.db_repo.enable_statistics) + cache_manager = caches.get_cache_manager( + 'repo_cache_long', _namespace) + _cache_key = caches.compute_key_from_params( + self.db_repo_name, commit_id, show_stats) + + def compute_stats(): + code_stats = {} + size = 0 + try: + scm_instance = self.db_repo.scm_instance() + commit = scm_instance.get_commit(commit_id) + + for node in commit.get_filenodes_generator(): + size += node.size + if not show_stats: + continue + ext = string.lower(node.extension) + ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext) + if ext_info: + if ext in code_stats: + code_stats[ext]['count'] += 1 + else: + code_stats[ext] = {"count": 1, "desc": ext_info} + except EmptyRepositoryError: + pass + return {'size': h.format_byte_size_binary(size), + 'code_stats': code_stats} + + stats = cache_manager.get(_cache_key, createfunc=compute_stats) + return stats + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_refs_data', request_method='GET', + renderer='json_ext') + def repo_refs_data(self): + _ = self.request.translate + self.load_default_context() + + repo = self.rhodecode_vcs_repo + refs_to_create = [ + (_("Branch"), repo.branches, 'branch'), + (_("Tag"), repo.tags, 'tag'), + (_("Bookmark"), repo.bookmarks, 'book'), + ] + res = self._create_reference_data( + repo, self.db_repo_name, refs_to_create) + data = { + 'more': False, + 'results': res + } + return data + + @LoginRequired() + @HasRepoPermissionAnyDecorator( + 'repository.read', 'repository.write', 'repository.admin') + @view_config( + route_name='repo_refs_changelog_data', request_method='GET', + renderer='json_ext') + def repo_refs_changelog_data(self): + _ = self.request.translate + self.load_default_context() + + repo = self.rhodecode_vcs_repo + + refs_to_create = [ + (_("Branches"), repo.branches, 'branch'), + (_("Closed branches"), repo.branches_closed, 'branch_closed'), + # TODO: enable when vcs can handle bookmarks filters + # (_("Bookmarks"), repo.bookmarks, "book"), + ] + res = self._create_reference_data( + repo, self.db_repo_name, refs_to_create) + data = { + 'more': False, + 'results': res + } + return data + + def _create_reference_data(self, repo, full_repo_name, refs_to_create): + format_ref_id = utils.get_format_ref_id(repo) + + result = [] + for title, refs, ref_type in refs_to_create: + if refs: + result.append({ + 'text': title, + 'children': self._create_reference_items( + repo, full_repo_name, refs, ref_type, + format_ref_id), + }) + return result + + def _create_reference_items(self, repo, full_repo_name, refs, ref_type, + format_ref_id): + result = [] + is_svn = h.is_svn(repo) + for ref_name, raw_id in refs.iteritems(): + files_url = self._create_files_url( + repo, full_repo_name, ref_name, raw_id, is_svn) + result.append({ + 'text': ref_name, + 'id': format_ref_id(ref_name, raw_id), + 'raw_id': raw_id, + 'type': ref_type, + 'files_url': files_url, + }) + return result + + def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn): + use_commit_id = '/' in ref_name or is_svn + return h.url( + 'files_home', + repo_name=full_repo_name, + f_path=ref_name if is_svn else '', + revision=raw_id if use_commit_id else ref_name, + at=ref_name) diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py --- a/rhodecode/config/middleware.py +++ b/rhodecode/config/middleware.py @@ -39,11 +39,15 @@ from routes.middleware import RoutesMidd import routes.util import rhodecode + from rhodecode.model import meta from rhodecode.config import patches from rhodecode.config.routing import STATIC_FILE_PREFIX from rhodecode.config.environment import ( load_environment, load_pyramid_environment) + +from rhodecode.lib.vcs import VCSCommunicationError +from rhodecode.lib.exceptions import VCSServerUnavailable from rhodecode.lib.middleware import csrf from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled from rhodecode.lib.middleware.error_handling import ( @@ -51,7 +55,7 @@ from rhodecode.lib.middleware.error_hand from rhodecode.lib.middleware.https_fixup import HttpsFixup from rhodecode.lib.middleware.vcs import VCSMiddleware from rhodecode.lib.plugins.utils import register_rhodecode_plugin -from rhodecode.lib.utils2 import aslist as rhodecode_aslist +from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict from rhodecode.subscribers import ( scan_repositories_if_enabled, write_js_routes_if_enabled, write_metadata_if_needed) @@ -221,7 +225,6 @@ def add_pylons_compat_data(registry, glo def error_handler(exception, request): import rhodecode - from rhodecode.lib.utils2 import AttributeDict from rhodecode.lib import helpers rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode' @@ -230,6 +233,8 @@ def error_handler(exception, request): # prefer original exception for the response since it may have headers set if isinstance(exception, HTTPException): base_response = exception + elif isinstance(exception, VCSCommunicationError): + base_response = VCSServerUnavailable() def is_http_error(response): # error which should have traceback @@ -257,6 +262,7 @@ def error_handler(exception, request): if hasattr(base_response, 'causes'): c.causes = base_response.causes c.messages = helpers.flash.pop_messages() + response = render_to_response( '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request, response=base_response) @@ -404,7 +410,6 @@ def wrap_app_in_wsgi_middlewares(pyramid pool = meta.Base.metadata.bind.engine.pool log.debug('sa pool status: %s', pool.status()) - return pyramid_app_with_cleanup diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -117,8 +117,9 @@ class JSRoutesMapper(Mapper): def make_map(config): """Create, configure and return the routes Mapper""" - rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'], - always_scan=config['debug']) + rmap = JSRoutesMapper( + directory=config['pylons.paths']['controllers'], + always_scan=config['debug']) rmap.minimization = False rmap.explicit = False @@ -609,18 +610,6 @@ def make_map(config): controller='admin/repos', action='repo_check', requirements=URL_NAME_REQUIREMENTS) - rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}', - controller='summary', action='repo_stats', - conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS, jsroute=True) - - rmap.connect('repo_refs_data', '/{repo_name}/refs-data', - controller='summary', action='repo_refs_data', - requirements=URL_NAME_REQUIREMENTS, jsroute=True) - rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog', - controller='summary', action='repo_refs_changelog_data', - requirements=URL_NAME_REQUIREMENTS, jsroute=True) - rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}', controller='changeset', revision='tip', conditions={'function': check_repo}, @@ -834,19 +823,10 @@ def make_map(config): conditions={'function': check_repo, 'method': ['DELETE']}, requirements=URL_NAME_REQUIREMENTS, jsroute=True) - rmap.connect('summary_home_explicit', '/{repo_name}/summary', - controller='summary', 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) - rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary', - controller='changelog', action='changelog_summary', - conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('changelog_file_home', '/{repo_name}/changelog/{revision}/{f_path}', controller='changelog', f_path=None, @@ -999,19 +979,4 @@ def make_map(config): conditions={'function': check_repo}, requirements=URL_NAME_REQUIREMENTS) - # catch all, at the end - _connect_with_slash( - rmap, 'summary_home', '/{repo_name}', jsroute=True, - controller='summary', action='index', - conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - return rmap - - -def _connect_with_slash(mapper, name, path, *args, **kwargs): - """ - Connect a route with an optional trailing slash in `path`. - """ - mapper.connect(name + '_slash', path + '/', *args, **kwargs) - mapper.connect(name, path, *args, **kwargs) diff --git a/rhodecode/controllers/admin/repos.py b/rhodecode/controllers/admin/repos.py --- a/rhodecode/controllers/admin/repos.py +++ b/rhodecode/controllers/admin/repos.py @@ -274,9 +274,9 @@ class ReposController(BaseRepoController h.flash(_('Created repository %s from %s') % (repo.repo_name, clone_uri), category='success') else: - repo_url = h.link_to(repo.repo_name, - h.url('summary_home', - repo_name=repo.repo_name)) + repo_url = h.link_to( + repo.repo_name, + h.route_path('repo_summary',repo_name=repo.repo_name)) fork = repo.fork if fork: fork_name = fork.repo_name @@ -366,7 +366,7 @@ class ReposController(BaseRepoController log.exception("Exception during unlocking") h.flash(_('An error occurred during unlocking'), category='error') - return redirect(url('summary_home', repo_name=repo_name)) + return redirect(h.route_path('repo_summary', repo_name=repo_name)) @HasRepoPermissionAllDecorator('repository.admin') @auth.CSRFRequired() diff --git a/rhodecode/controllers/changelog.py b/rhodecode/controllers/changelog.py --- a/rhodecode/controllers/changelog.py +++ b/rhodecode/controllers/changelog.py @@ -46,27 +46,6 @@ log = logging.getLogger(__name__) DEFAULT_CHANGELOG_SIZE = 20 -def _load_changelog_summary(): - p = safe_int(request.GET.get('page'), 1) - size = safe_int(request.GET.get('size'), 10) - - def url_generator(**kw): - return url('summary_home', - repo_name=c.rhodecode_db_repo.repo_name, size=size, **kw) - - pre_load = ['author', 'branch', 'date', 'message'] - try: - collection = c.rhodecode_repo.get_commits(pre_load=pre_load) - except EmptyRepositoryError: - collection = c.rhodecode_repo - - c.repo_commits = RepoPage( - collection, page=p, items_per_page=size, url=url_generator) - page_ids = [x.raw_id for x in c.repo_commits] - c.comments = c.rhodecode_db_repo.get_comments(page_ids) - c.statuses = c.rhodecode_db_repo.statuses(page_ids) - - class ChangelogController(BaseRepoController): def __before__(self): @@ -211,7 +190,7 @@ class ChangelogController(BaseRepoContro except EmptyRepositoryError as e: h.flash(safe_str(e), category='warning') - return redirect(url('summary_home', repo_name=repo_name)) + return redirect(h.route_path('repo_summary', repo_name=repo_name)) except (RepositoryError, CommitDoesNotExistError, Exception) as e: msg = safe_str(e) log.exception(msg) @@ -279,12 +258,3 @@ class ChangelogController(BaseRepoContro c.rhodecode_repo, c.pagination, prev_data=prev_data, next_data=next_data) return render('changelog/changelog_elements.mako') - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - def changelog_summary(self, repo_name): - if request.environ.get('HTTP_X_PJAX'): - _load_changelog_summary() - return render('changelog/changelog_summary_data.mako') - raise HTTPNotFound() diff --git a/rhodecode/controllers/compare.py b/rhodecode/controllers/compare.py --- a/rhodecode/controllers/compare.py +++ b/rhodecode/controllers/compare.py @@ -63,14 +63,14 @@ class CompareController(BaseRepoControll return repo.scm_instance().EMPTY_COMMIT h.flash(h.literal(_('There are no commits yet')), category='warning') - redirect(url('summary_home', repo_name=repo.repo_name)) + redirect(h.route_path('repo_summary', repo_name=repo.repo_name)) except RepositoryError as e: msg = safe_str(e) log.exception(msg) h.flash(msg, category='warning') if not partial: - redirect(h.url('summary_home', repo_name=repo.repo_name)) + redirect(h.route_path('repo_summary', repo_name=repo.repo_name)) raise HTTPBadRequest() @LoginRequired() diff --git a/rhodecode/controllers/feed.py b/rhodecode/controllers/feed.py --- a/rhodecode/controllers/feed.py +++ b/rhodecode/controllers/feed.py @@ -113,7 +113,7 @@ class FeedController(BaseRepoController) def _generate_feed(cache_key): feed = Atom1Feed( title=self.title % repo_name, - link=url('summary_home', repo_name=repo_name, qualified=True), + link=h.route_url('repo_summary', repo_name=repo_name), description=self.description % repo_name, language=self.language, ttl=self.ttl @@ -150,8 +150,7 @@ class FeedController(BaseRepoController) def _generate_feed(cache_key): feed = Rss201rev2Feed( title=self.title % repo_name, - link=url('summary_home', repo_name=repo_name, - qualified=True), + link=h.route_url('repo_summary', repo_name=repo_name), description=self.description % repo_name, language=self.language, ttl=self.ttl diff --git a/rhodecode/controllers/files.py b/rhodecode/controllers/files.py --- a/rhodecode/controllers/files.py +++ b/rhodecode/controllers/files.py @@ -101,7 +101,7 @@ class FilesController(BaseRepoController add_new = "" h.flash(h.literal( _('There are no files yet. %s') % add_new), category='warning') - redirect(h.url('summary_home', repo_name=repo_name)) + redirect(h.route_path('repo_summary', repo_name=repo_name)) except (CommitDoesNotExistError, LookupError): msg = _('No such commit exists for this repository') h.flash(msg, category='error') @@ -669,14 +669,14 @@ class FilesController(BaseRepoController # If there's no commit, redirect to repo summary if type(c.commit) is EmptyCommit: - redirect_url = "summary_home" + redirect_url = h.route_path('repo_summary', repo_name=c.repo_name) else: - redirect_url = "changeset_home" + redirect_url = url("changeset_home", repo_name=c.repo_name, + revision='tip') if not filename: h.flash(_('No filename'), category='warning') - return redirect(url(redirect_url, repo_name=c.repo_name, - revision='tip')) + return redirect(redirect_url) # extract the location from filename, # allows using foo/bar.txt syntax to create subdirectories diff --git a/rhodecode/controllers/pullrequests.py b/rhodecode/controllers/pullrequests.py --- a/rhodecode/controllers/pullrequests.py +++ b/rhodecode/controllers/pullrequests.py @@ -85,7 +85,7 @@ class PullrequestsController(BaseRepoCon except EmptyRepositoryError: h.flash(h.literal(_('There are no commits yet')), category='warning') - redirect(url('summary_home', repo_name=source_repo.repo_name)) + redirect(h.route_path('repo_summary', repo_name=source_repo.repo_name)) commit_id = request.GET.get('commit') branch_ref = request.GET.get('branch') diff --git a/rhodecode/controllers/summary.py b/rhodecode/controllers/summary.py deleted file mode 100644 --- a/rhodecode/controllers/summary.py +++ /dev/null @@ -1,315 +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/ - -""" -Summary controller for RhodeCode Enterprise -""" - -import logging -from string import lower - -from pylons import tmpl_context as c, request -from pylons.i18n.translation import _ -from beaker.cache import cache_region - -from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP) -from rhodecode.controllers import utils -from rhodecode.controllers.changelog import _load_changelog_summary -from rhodecode.lib import caches, helpers as h -from rhodecode.lib.utils import jsonify -from rhodecode.lib.utils2 import safe_str -from rhodecode.lib.auth import ( - LoginRequired, HasRepoPermissionAnyDecorator, XHRRequired) -from rhodecode.lib.base import BaseRepoController, render -from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links -from rhodecode.lib.ext_json import json -from rhodecode.lib.vcs.backends.base import EmptyCommit -from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError -from rhodecode.model.db import Statistics, CacheKey, User -from rhodecode.model.repo import ReadmeFinder - - -log = logging.getLogger(__name__) - - -class SummaryController(BaseRepoController): - - def __before__(self): - super(SummaryController, self).__before__() - - def __get_readme_data(self, db_repo): - repo_name = db_repo.repo_name - log.debug('Looking for README file') - default_renderer = c.visual.default_renderer - - @cache_region('long_term') - def _generate_readme(cache_key): - readme_data = None - readme_node = None - readme_filename = None - commit = self._get_landing_commit_or_none(db_repo) - if commit: - log.debug("Searching for a README file.") - readme_node = ReadmeFinder(default_renderer).search(commit) - if readme_node: - relative_url = h.url('files_raw_home', - repo_name=repo_name, - revision=commit.raw_id, - f_path=readme_node.path) - readme_data = self._render_readme_or_none( - commit, readme_node, relative_url) - readme_filename = readme_node.path - return readme_data, readme_filename - - invalidator_context = CacheKey.repo_context_cache( - _generate_readme, repo_name, CacheKey.CACHE_TYPE_README) - - with invalidator_context as context: - context.invalidate() - computed = context.compute() - - return computed - - def _get_landing_commit_or_none(self, db_repo): - log.debug("Getting the landing commit.") - try: - commit = db_repo.get_landing_commit() - if not isinstance(commit, EmptyCommit): - return commit - else: - log.debug("Repository is empty, no README to render.") - except CommitError: - log.exception( - "Problem getting commit when trying to render the README.") - - def _render_readme_or_none(self, commit, readme_node, relative_url): - log.debug( - 'Found README file `%s` rendering...', readme_node.path) - renderer = MarkupRenderer() - try: - html_source = renderer.render( - readme_node.content, filename=readme_node.path) - if relative_url: - return relative_links(html_source, relative_url) - return html_source - except Exception: - log.exception( - "Exception while trying to render the README") - - @LoginRequired() - @HasRepoPermissionAnyDecorator( - 'repository.read', 'repository.write', 'repository.admin') - def index(self, repo_name): - - # Prepare the clone URL - - username = '' - if c.rhodecode_user.username != User.DEFAULT_USER: - username = safe_str(c.rhodecode_user.username) - - _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl - if '{repo}' in _def_clone_uri: - _def_clone_uri_by_id = _def_clone_uri.replace( - '{repo}', '_{repoid}') - elif '{repoid}' in _def_clone_uri: - _def_clone_uri_by_id = _def_clone_uri.replace( - '_{repoid}', '{repo}') - - c.clone_repo_url = c.rhodecode_db_repo.clone_url( - user=username, uri_tmpl=_def_clone_uri) - c.clone_repo_url_id = c.rhodecode_db_repo.clone_url( - user=username, uri_tmpl=_def_clone_uri_by_id) - - # If enabled, get statistics data - - c.show_stats = bool(c.rhodecode_db_repo.enable_statistics) - - stats = self.sa.query(Statistics)\ - .filter(Statistics.repository == c.rhodecode_db_repo)\ - .scalar() - - c.stats_percentage = 0 - - if stats and stats.languages: - c.no_data = False is c.rhodecode_db_repo.enable_statistics - lang_stats_d = json.loads(stats.languages) - - # Sort first by decreasing count and second by the file extension, - # so we have a consistent output. - lang_stats_items = sorted(lang_stats_d.iteritems(), - key=lambda k: (-k[1], k[0]))[:10] - lang_stats = [(x, {"count": y, - "desc": LANGUAGES_EXTENSIONS_MAP.get(x)}) - for x, y in lang_stats_items] - - c.trending_languages = json.dumps(lang_stats) - else: - c.no_data = True - c.trending_languages = json.dumps({}) - - c.enable_downloads = c.rhodecode_db_repo.enable_downloads - c.repository_followers = self.scm_model.get_followers( - c.rhodecode_db_repo) - c.repository_forks = self.scm_model.get_forks(c.rhodecode_db_repo) - c.repository_is_user_following = self.scm_model.is_following_repo( - c.repo_name, c.rhodecode_user.user_id) - - if c.repository_requirements_missing: - return render('summary/missing_requirements.mako') - - c.readme_data, c.readme_file = \ - self.__get_readme_data(c.rhodecode_db_repo) - - _load_changelog_summary() - - if request.is_xhr: - return render('changelog/changelog_summary_data.mako') - - return render('summary/summary.mako') - - @LoginRequired() - @XHRRequired() - @HasRepoPermissionAnyDecorator( - 'repository.read', 'repository.write', 'repository.admin') - @jsonify - def repo_stats(self, repo_name, commit_id): - _namespace = caches.get_repo_namespace_key( - caches.SUMMARY_STATS, repo_name) - show_stats = bool(c.rhodecode_db_repo.enable_statistics) - cache_manager = caches.get_cache_manager('repo_cache_long', _namespace) - _cache_key = caches.compute_key_from_params( - repo_name, commit_id, show_stats) - - def compute_stats(): - code_stats = {} - size = 0 - try: - scm_instance = c.rhodecode_db_repo.scm_instance() - commit = scm_instance.get_commit(commit_id) - - for node in commit.get_filenodes_generator(): - size += node.size - if not show_stats: - continue - ext = lower(node.extension) - ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext) - if ext_info: - if ext in code_stats: - code_stats[ext]['count'] += 1 - else: - code_stats[ext] = {"count": 1, "desc": ext_info} - except EmptyRepositoryError: - pass - return {'size': h.format_byte_size_binary(size), - 'code_stats': code_stats} - - stats = cache_manager.get(_cache_key, createfunc=compute_stats) - return stats - - def _switcher_reference_data(self, repo_name, references, is_svn): - """Prepare reference data for given `references`""" - items = [] - for name, commit_id in references.items(): - use_commit_id = '/' in name or is_svn - items.append({ - 'name': name, - 'commit_id': commit_id, - 'files_url': h.url( - 'files_home', - repo_name=repo_name, - f_path=name if is_svn else '', - revision=commit_id if use_commit_id else name, - at=name) - }) - return items - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - @jsonify - def repo_refs_data(self, repo_name): - repo = c.rhodecode_repo - refs_to_create = [ - (_("Branch"), repo.branches, 'branch'), - (_("Tag"), repo.tags, 'tag'), - (_("Bookmark"), repo.bookmarks, 'book'), - ] - res = self._create_reference_data(repo, repo_name, refs_to_create) - data = { - 'more': False, - 'results': res - } - return data - - @jsonify - def repo_refs_changelog_data(self, repo_name): - repo = c.rhodecode_repo - - refs_to_create = [ - (_("Branches"), repo.branches, 'branch'), - (_("Closed branches"), repo.branches_closed, 'branch_closed'), - # TODO: enable when vcs can handle bookmarks filters - # (_("Bookmarks"), repo.bookmarks, "book"), - ] - res = self._create_reference_data(repo, repo_name, refs_to_create) - data = { - 'more': False, - 'results': res - } - return data - - def _create_reference_data(self, repo, full_repo_name, refs_to_create): - format_ref_id = utils.get_format_ref_id(repo) - - result = [] - for title, refs, ref_type in refs_to_create: - if refs: - result.append({ - 'text': title, - 'children': self._create_reference_items( - repo, full_repo_name, refs, ref_type, format_ref_id), - }) - return result - - def _create_reference_items(self, repo, full_repo_name, refs, ref_type, - format_ref_id): - result = [] - is_svn = h.is_svn(repo) - for ref_name, raw_id in refs.iteritems(): - files_url = self._create_files_url( - repo, full_repo_name, ref_name, raw_id, is_svn) - result.append({ - 'text': ref_name, - 'id': format_ref_id(ref_name, raw_id), - 'raw_id': raw_id, - 'type': ref_type, - 'files_url': files_url, - }) - return result - - def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, - is_svn): - use_commit_id = '/' in ref_name or is_svn - return h.url( - 'files_home', - repo_name=full_repo_name, - f_path=ref_name if is_svn else '', - revision=raw_id if use_commit_id else ref_name, - at=ref_name) diff --git a/rhodecode/lib/action_parser.py b/rhodecode/lib/action_parser.py --- a/rhodecode/lib/action_parser.py +++ b/rhodecode/lib/action_parser.py @@ -161,8 +161,9 @@ class ActionParser(object): return action_map def get_fork_name(self): + from rhodecode.lib import helpers as h repo_name = self.action_params - _url = url('summary_home', repo_name=repo_name) + _url = h.route_path('repo_summary', repo_name=repo_name) return _('fork name %s') % link_to(self.action_params, _url) def get_user_name(self): diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py --- a/rhodecode/lib/auth.py +++ b/rhodecode/lib/auth.py @@ -1342,7 +1342,7 @@ class HasAcceptedRepoType(object): _('Action not supported for %s.' % rhodecode_repo.alias)), category='warning') return redirect( - url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name)) + h.route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name)) class PermsDecorator(object): diff --git a/rhodecode/lib/base.py b/rhodecode/lib/base.py --- a/rhodecode/lib/base.py +++ b/rhodecode/lib/base.py @@ -580,7 +580,7 @@ class BaseRepoController(BaseController) 'Requirements are missing for repository %s: %s', c.repo_name, error.message) - summary_url = url('summary_home', repo_name=c.repo_name) + summary_url = h.route_path('repo_summary', repo_name=c.repo_name) statistics_url = url('edit_repo_statistics', repo_name=c.repo_name) settings_update_url = url('repo', repo_name=c.repo_name) path = request.path diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -1538,7 +1538,7 @@ def breadcrumb_repo_link(repo): link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name)) for group in repo.groups_with_parents ] + [ - link_to(repo.just_name, url('summary_home', repo_name=repo.repo_name)) + link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name)) ] return literal(' » '.join(path)) diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py --- a/rhodecode/model/comment.py +++ b/rhodecode/model/comment.py @@ -268,8 +268,7 @@ class CommentsModel(BaseModel): target_repo_url = h.link_to( repo.repo_name, - h.url('summary_home', - repo_name=repo.repo_name, qualified=True)) + h.route_url('repo_summary', repo_name=repo.repo_name)) # commit specifics kwargs.update({ @@ -300,13 +299,11 @@ class CommentsModel(BaseModel): qualified=True,) # set some variables for email notification - pr_target_repo_url = h.url( - 'summary_home', repo_name=pr_target_repo.repo_name, - qualified=True) + pr_target_repo_url = h.route_url( + 'repo_summary', repo_name=pr_target_repo.repo_name) - pr_source_repo_url = h.url( - 'summary_home', repo_name=pr_source_repo.repo_name, - qualified=True) + pr_source_repo_url = h.route_url( + 'repo_summary', repo_name=pr_source_repo.repo_name) # pull request specifics kwargs.update({ diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -1765,6 +1765,7 @@ class Repository(Base, BaseModel): # TODO: mikhail: Here there is an anti-pattern, we probably need to # move this methods on models level. from rhodecode.model.settings import SettingsModel + from rhodecode.model.repo import RepoModel repo = self _user_id, _time, _reason = self.locked @@ -1774,7 +1775,7 @@ class Repository(Base, BaseModel): 'repo_name': repo.repo_name, 'repo_type': repo.repo_type, 'clone_uri': repo.clone_uri or '', - 'url': repo.home_url(), + 'url': RepoModel().get_url(self), 'private': repo.private, 'created_on': repo.created_on, 'description': repo.description, @@ -1935,10 +1936,6 @@ class Repository(Base, BaseModel): repo_name=self.repo_name, repo_id=self.repo_id, **override) - def home_url(self): - request = get_current_request() - return request.route_url('repo_summary', repo_name=self.repo_name) - def set_state(self, state): self.repo_state = state Session().add(self) diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py --- a/rhodecode/model/pull_request.py +++ b/rhodecode/model/pull_request.py @@ -999,15 +999,11 @@ class PullRequestModel(BaseModel): qualified=True,) # set some variables for email notification - pr_target_repo_url = h.url( - 'summary_home', - repo_name=pr_target_repo.repo_name, - qualified=True) + pr_target_repo_url = h.route_url( + 'repo_summary', repo_name=pr_target_repo.repo_name) - pr_source_repo_url = h.url( - 'summary_home', - repo_name=pr_source_repo.repo_name, - qualified=True) + pr_source_repo_url = h.route_url( + 'repo_summary', repo_name=pr_source_repo.repo_name) # pull request specifics pull_request_commits = [ diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -30,6 +30,7 @@ import time import traceback from datetime import datetime, timedelta +from pyramid.threadlocal import get_current_request from zope.cachedescriptors.property import Lazy as LazyProperty from rhodecode import events @@ -154,9 +155,10 @@ class RepoModel(BaseModel): repos = Repository.query().filter(Repository.group == root).all() return repos - def get_url(self, repo): - return h.url('summary_home', repo_name=safe_str(repo.repo_name), - qualified=True) + def get_url(self, repo, request=None): + if not request: + request = get_current_request() + return request.route_url('repo_summary', repo_name=safe_str(repo.repo_name)) @classmethod def update_repoinfo(cls, repositories=None): diff --git a/rhodecode/public/css/summary.less b/rhodecode/public/css/summary.less --- a/rhodecode/public/css/summary.less +++ b/rhodecode/public/css/summary.less @@ -215,6 +215,7 @@ float: left; display: block; position: relative; + width: 100%; // adds some space to make copy and paste easier .left-label, diff --git a/rhodecode/public/js/rhodecode/base/keyboard-bindings.js b/rhodecode/public/js/rhodecode/base/keyboard-bindings.js --- a/rhodecode/public/js/rhodecode/base/keyboard-bindings.js +++ b/rhodecode/public/js/rhodecode/base/keyboard-bindings.js @@ -62,7 +62,7 @@ function setRCMouseBindings(repoName, re // nav in repo context Mousetrap.bind(['g s'], function(e) { window.location = pyroutes.url( - 'summary_home', {'repo_name': repoName}); + 'repo_summary', {'repo_name': repoName}); }); Mousetrap.bind(['g c'], function(e) { window.location = pyroutes.url( 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 @@ -18,9 +18,6 @@ function registerRCRoutes() { pyroutes.register('gists', '/_admin/gists', []); pyroutes.register('new_gist', '/_admin/gists/new', []); pyroutes.register('toggle_following', '/_admin/toggle_following', []); - pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']); - pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']); - pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']); pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']); pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']); @@ -46,8 +43,6 @@ function registerRCRoutes() { pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']); pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']); - pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']); - pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']); pyroutes.register('favicon', '/favicon.ico', []); pyroutes.register('robots', '/robots.txt', []); pyroutes.register('auth_home', '/_admin/auth*traverse', []); @@ -99,8 +94,11 @@ 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('repo_summary', '/%(repo_name)s', ['repo_name']); pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']); + pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']); + pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']); + pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']); + pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']); 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']); @@ -122,6 +120,8 @@ function registerRCRoutes() { pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']); pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']); pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']); + pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']); + pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']); pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']); pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']); pyroutes.register('search', '/_admin/search', []); diff --git a/rhodecode/templates/admin/admin_log_base.mako b/rhodecode/templates/admin/admin_log_base.mako --- a/rhodecode/templates/admin/admin_log_base.mako +++ b/rhodecode/templates/admin/admin_log_base.mako @@ -45,7 +45,7 @@ %if l.repository is not None: - ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))} + ${h.link_to(l.repository.repo_name, h.route_path('repo_summary',repo_name=l.repository.repo_name))} %else: ${l.repository_name} %endif diff --git a/rhodecode/templates/admin/integrations/list.mako b/rhodecode/templates/admin/integrations/list.mako --- a/rhodecode/templates/admin/integrations/list.mako +++ b/rhodecode/templates/admin/integrations/list.mako @@ -160,7 +160,7 @@ %if integration.repo: - + ${_('repo')}:${integration.repo.repo_name} %elif integration.repo_group: diff --git a/rhodecode/templates/admin/repos/repo_creating.mako b/rhodecode/templates/admin/repos/repo_creating.mako --- a/rhodecode/templates/admin/repos/repo_creating.mako +++ b/rhodecode/templates/admin/repos/repo_creating.mako @@ -48,12 +48,12 @@ if (jsonResponse === undefined) { setTimeout(function () { // we might have a backend problem, try dashboard again - window.location = "${h.url('summary_home', repo_name = c.repo)}"; + window.location = "${h.route_path('repo_summary', repo_name = c.repo)}"; }, 3000); } else { if (skipCheck || jsonResponse.result === true) { // success, means go to dashboard - window.location = "${h.url('summary_home', repo_name = c.repo)}"; + window.location = "${h.route_path('repo_summary', repo_name = c.repo)}"; } else { // Schedule the next request when the current one's complete setTimeout(worker, 1000); diff --git a/rhodecode/templates/admin/repos/repo_edit_advanced.mako b/rhodecode/templates/admin/repos/repo_edit_advanced.mako --- a/rhodecode/templates/admin/repos/repo_edit_advanced.mako +++ b/rhodecode/templates/admin/repos/repo_edit_advanced.mako @@ -27,7 +27,7 @@ ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='POST')} % if c.repo_info.fork: -
${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.repo_info.fork.repo_name, h.url('summary_home', repo_name=c.repo_info.fork.repo_name))})} +
${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.repo_info.fork.repo_name, h.route_path('repo_summary', repo_name=c.repo_info.fork.repo_name))})} |
% endif diff --git a/rhodecode/templates/admin/settings/settings_system.mako b/rhodecode/templates/admin/settings/settings_system.mako --- a/rhodecode/templates/admin/settings/settings_system.mako +++ b/rhodecode/templates/admin/settings/settings_system.mako @@ -52,6 +52,6 @@ diff --git a/rhodecode/templates/base/base.mako b/rhodecode/templates/base/base.mako --- a/rhodecode/templates/base/base.mako +++ b/rhodecode/templates/base/base.mako @@ -186,7 +186,7 @@ %if repo_instance.fork:

${_('Fork of')} - ${repo_instance.fork.repo_name} + ${repo_instance.fork.repo_name}

%endif @@ -225,7 +225,7 @@