diff --git a/rhodecode/apps/admin/__init__.py b/rhodecode/apps/admin/__init__.py --- a/rhodecode/apps/admin/__init__.py +++ b/rhodecode/apps/admin/__init__.py @@ -205,6 +205,19 @@ def admin_routes(config): name='edit_user_group_perms_summary_json', pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary/json') + # repos admin + config.add_route( + name='repos', + pattern='/repos') + + config.add_route( + name='repo_new', + pattern='/repos/new') + + config.add_route( + name='repo_create', + pattern='/repos/create') + def includeme(config): settings = config.get_settings() diff --git a/rhodecode/tests/functional/test_admin_repos.py b/rhodecode/apps/admin/tests/test_admin_repos.py rename from rhodecode/tests/functional/test_admin_repos.py rename to rhodecode/apps/admin/tests/test_admin_repos.py --- a/rhodecode/tests/functional/test_admin_repos.py +++ b/rhodecode/apps/admin/tests/test_admin_repos.py @@ -23,19 +23,19 @@ import urllib import mock import pytest +from rhodecode.apps._base import ADMIN_PREFIX from rhodecode.lib import auth -from rhodecode.lib.utils2 import safe_str, str2bool +from rhodecode.lib.utils2 import safe_str from rhodecode.lib import helpers as h from rhodecode.model.db import ( Repository, RepoGroup, UserRepoToPerm, User, Permission) from rhodecode.model.meta import Session from rhodecode.model.repo import RepoModel from rhodecode.model.repo_group import RepoGroupModel -from rhodecode.model.settings import SettingsModel, VcsSettingsModel from rhodecode.model.user import UserModel from rhodecode.tests import ( - login_user_session, url, assert_session_flash, TEST_USER_ADMIN_LOGIN, - TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, logout_user_session) + login_user_session, assert_session_flash, TEST_USER_ADMIN_LOGIN, + TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) from rhodecode.tests.fixture import Fixture, error_function from rhodecode.tests.utils import AssertResponse, repo_on_filesystem @@ -46,7 +46,10 @@ def route_path(name, params=None, **kwar import urllib base_url = { - 'repo_summary': '/{repo_name}', + 'repos': ADMIN_PREFIX + '/repos', + 'repo_new': ADMIN_PREFIX + '/repos/new', + 'repo_create': ADMIN_PREFIX + '/repos/create', + 'repo_creating_check': '/{repo_name}/repo_creating_check', }[name].format(**kwargs) @@ -55,35 +58,48 @@ def route_path(name, params=None, **kwar return base_url +def _get_permission_for_user(user, repo): + perm = UserRepoToPerm.query()\ + .filter(UserRepoToPerm.repository == + Repository.get_by_repo_name(repo))\ + .filter(UserRepoToPerm.user == User.get_by_username(user))\ + .all() + return perm + + @pytest.mark.usefixtures("app") class TestAdminRepos(object): - def test_index(self): - self.app.get(url('repos')) + def test_repo_list(self, autologin_user, user_util): + repo = user_util.create_repo() + response = self.app.get( + route_path('repos'), status=200) - def test_create_page_restricted(self, autologin_user, backend): + response.mustcontain(repo.repo_name) + + def test_create_page_restricted_to_single_backend(self, autologin_user, backend): with mock.patch('rhodecode.BACKENDS', {'git': 'git'}): - response = self.app.get(url('new_repo'), status=200) + response = self.app.get(route_path('repo_new'), status=200) assert_response = AssertResponse(response) element = assert_response.get_element('#repo_type') assert element.text_content() == '\ngit\n' - def test_create_page_non_restricted(self, autologin_user, backend): - response = self.app.get(url('new_repo'), status=200) + def test_create_page_non_restricted_backends(self, autologin_user, backend): + response = self.app.get(route_path('repo_new'), status=200) assert_response = AssertResponse(response) assert_response.element_contains('#repo_type', 'git') assert_response.element_contains('#repo_type', 'svn') assert_response.element_contains('#repo_type', 'hg') - @pytest.mark.parametrize("suffix", - [u'', u'xxa'], ids=['', 'non-ascii']) + @pytest.mark.parametrize( + "suffix", [u'', u'xxa'], ids=['', 'non-ascii']) def test_create(self, autologin_user, backend, suffix, csrf_token): repo_name_unicode = backend.new_repo_name(suffix=suffix) repo_name = repo_name_unicode.encode('utf8') description_unicode = u'description for newly created repo' + suffix description = description_unicode.encode('utf8') response = self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -95,12 +111,12 @@ class TestAdminRepos(object): self.assert_repository_is_created_correctly( repo_name, description, backend) - def test_create_numeric(self, autologin_user, backend, csrf_token): + def test_create_numeric_name(self, autologin_user, backend, csrf_token): numeric_repo = '1234' repo_name = numeric_repo description = 'description for newly created repo' + numeric_repo self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -126,7 +142,7 @@ class TestAdminRepos(object): [group_name, repo_name]) description = u'description for newly created repo' self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=safe_str(repo_name), @@ -149,7 +165,7 @@ class TestAdminRepos(object): RepoGroupModel().delete(group_name) Session().commit() - def test_create_in_group_numeric( + def test_create_in_group_numeric_name( self, autologin_user, backend, csrf_token): # create GROUP group_name = 'sometest_%s' % backend.alias @@ -162,7 +178,7 @@ class TestAdminRepos(object): repo_name_full = RepoGroup.url_sep().join([group_name, repo_name]) description = 'description for newly created repo' self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -221,7 +237,7 @@ class TestAdminRepos(object): repo_name = 'ingroup' description = 'description for newly created repo' response = self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -238,7 +254,7 @@ class TestAdminRepos(object): [group_name_allowed, repo_name]) description = 'description for newly created repo' response = self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -282,7 +298,7 @@ class TestAdminRepos(object): repo_name_full = RepoGroup.url_sep().join([group_name, repo_name]) description = 'description for newly created repo' self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -326,7 +342,7 @@ class TestAdminRepos(object): repo_name = backend.new_repo_name() response = self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -354,7 +370,7 @@ class TestAdminRepos(object): repo_name = backend.new_repo_name() description = 'description for newly created repo' response = self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -370,7 +386,7 @@ class TestAdminRepos(object): repo_name = backend.new_repo_name() description = 'description for newly created repo' response = self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -385,7 +401,7 @@ class TestAdminRepos(object): repo_name = backend.new_repo_name() + ".git" description = 'description for newly created repo' response = self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -394,11 +410,8 @@ class TestAdminRepos(object): csrf_token=csrf_token)) response.mustcontain('Repository name cannot end with .git') - def test_show(self, autologin_user, backend): - self.app.get(url('repo', repo_name=backend.repo_name)) - def test_default_user_cannot_access_private_repo_in_a_group( - self, autologin_user, user_util, backend, csrf_token): + self, autologin_user, user_util, backend): group = user_util.create_repo_group() @@ -434,7 +447,7 @@ class TestAdminRepos(object): repo_name = backend.new_repo_name() description = 'description for newly created repo' response = self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -453,7 +466,7 @@ class TestAdminRepos(object): description = 'description for newly created repo' response = self.app.post( - url('repos'), + route_path('repo_create'), fixture._get_repo_create_params( repo_private=False, repo_name=repo_name, @@ -494,638 +507,3 @@ class TestAdminRepos(object): response.mustcontain(backend.alias) assert repo_on_filesystem(repo_name) - - -@pytest.mark.usefixtures("app") -class TestVcsSettings(object): - FORM_DATA = { - 'inherit_global_settings': False, - 'hooks_changegroup_repo_size': False, - 'hooks_changegroup_push_logger': False, - 'hooks_outgoing_pull_logger': False, - 'extensions_largefiles': False, - 'extensions_evolve': False, - 'phases_publish': 'False', - 'rhodecode_pr_merge_enabled': False, - 'rhodecode_use_outdated_comments': False, - 'new_svn_branch': '', - 'new_svn_tag': '' - } - - @pytest.mark.skip_backends('svn') - def test_global_settings_initial_values(self, autologin_user, backend): - repo_name = backend.repo_name - response = self.app.get(url('repo_vcs_settings', repo_name=repo_name)) - - expected_settings = ( - 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled', - 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger', - 'hooks_outgoing_pull_logger' - ) - for setting in expected_settings: - self.assert_repo_value_equals_global_value(response, setting) - - def test_show_settings_requires_repo_admin_permission( - self, backend, user_util, settings_util): - repo = backend.create_repo() - repo_name = repo.repo_name - user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN) - user_util.grant_user_permission_to_repo(repo, user, 'repository.admin') - login_user_session( - self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) - self.app.get(url('repo_vcs_settings', repo_name=repo_name), status=200) - - def test_inherit_global_settings_flag_is_true_by_default( - self, autologin_user, backend): - repo_name = backend.repo_name - response = self.app.get(url('repo_vcs_settings', repo_name=repo_name)) - - assert_response = AssertResponse(response) - element = assert_response.get_element('#inherit_global_settings') - assert element.checked - - @pytest.mark.parametrize('checked_value', [True, False]) - def test_inherit_global_settings_value( - self, autologin_user, backend, checked_value, settings_util): - repo = backend.create_repo() - repo_name = repo.repo_name - settings_util.create_repo_rhodecode_setting( - repo, 'inherit_vcs_settings', checked_value, 'bool') - response = self.app.get(url('repo_vcs_settings', repo_name=repo_name)) - - assert_response = AssertResponse(response) - element = assert_response.get_element('#inherit_global_settings') - assert element.checked == checked_value - - @pytest.mark.skip_backends('svn') - def test_hooks_settings_are_created( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - settings = SettingsModel(repo=repo_name) - try: - for section, key in VcsSettingsModel.HOOKS_SETTINGS: - ui = settings.get_ui_by_section_and_key(section, key) - assert ui.ui_active is False - finally: - self._cleanup_repo_settings(settings) - - def test_hooks_settings_are_not_created_for_svn( - self, autologin_user, backend_svn, csrf_token): - repo_name = backend_svn.repo_name - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - settings = SettingsModel(repo=repo_name) - try: - for section, key in VcsSettingsModel.HOOKS_SETTINGS: - ui = settings.get_ui_by_section_and_key(section, key) - assert ui is None - finally: - self._cleanup_repo_settings(settings) - - @pytest.mark.skip_backends('svn') - def test_hooks_settings_are_updated( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - settings = SettingsModel(repo=repo_name) - for section, key in VcsSettingsModel.HOOKS_SETTINGS: - settings.create_ui_section_value(section, '', key=key, active=True) - - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - try: - for section, key in VcsSettingsModel.HOOKS_SETTINGS: - ui = settings.get_ui_by_section_and_key(section, key) - assert ui.ui_active is False - finally: - self._cleanup_repo_settings(settings) - - def test_hooks_settings_are_not_updated_for_svn( - self, autologin_user, backend_svn, csrf_token): - repo_name = backend_svn.repo_name - settings = SettingsModel(repo=repo_name) - for section, key in VcsSettingsModel.HOOKS_SETTINGS: - settings.create_ui_section_value(section, '', key=key, active=True) - - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - try: - for section, key in VcsSettingsModel.HOOKS_SETTINGS: - ui = settings.get_ui_by_section_and_key(section, key) - assert ui.ui_active is True - finally: - self._cleanup_repo_settings(settings) - - @pytest.mark.skip_backends('svn') - def test_pr_settings_are_created( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - settings = SettingsModel(repo=repo_name) - try: - for name in VcsSettingsModel.GENERAL_SETTINGS: - setting = settings.get_setting_by_name(name) - assert setting.app_settings_value is False - finally: - self._cleanup_repo_settings(settings) - - def test_pr_settings_are_not_created_for_svn( - self, autologin_user, backend_svn, csrf_token): - repo_name = backend_svn.repo_name - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - settings = SettingsModel(repo=repo_name) - try: - for name in VcsSettingsModel.GENERAL_SETTINGS: - setting = settings.get_setting_by_name(name) - assert setting is None - finally: - self._cleanup_repo_settings(settings) - - def test_pr_settings_creation_requires_repo_admin_permission( - self, backend, user_util, settings_util, csrf_token): - repo = backend.create_repo() - repo_name = repo.repo_name - - logout_user_session(self.app, csrf_token) - session = login_user_session( - self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) - new_csrf_token = auth.get_csrf_token(session) - - user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN) - repo = Repository.get_by_repo_name(repo_name) - user_util.grant_user_permission_to_repo(repo, user, 'repository.admin') - data = self.FORM_DATA.copy() - data['csrf_token'] = new_csrf_token - settings = SettingsModel(repo=repo_name) - - try: - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, - status=302) - finally: - self._cleanup_repo_settings(settings) - - @pytest.mark.skip_backends('svn') - def test_pr_settings_are_updated( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - settings = SettingsModel(repo=repo_name) - for name in VcsSettingsModel.GENERAL_SETTINGS: - settings.create_or_update_setting(name, True, 'bool') - - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - try: - for name in VcsSettingsModel.GENERAL_SETTINGS: - setting = settings.get_setting_by_name(name) - assert setting.app_settings_value is False - finally: - self._cleanup_repo_settings(settings) - - def test_pr_settings_are_not_updated_for_svn( - self, autologin_user, backend_svn, csrf_token): - repo_name = backend_svn.repo_name - settings = SettingsModel(repo=repo_name) - for name in VcsSettingsModel.GENERAL_SETTINGS: - settings.create_or_update_setting(name, True, 'bool') - - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - try: - for name in VcsSettingsModel.GENERAL_SETTINGS: - setting = settings.get_setting_by_name(name) - assert setting.app_settings_value is True - finally: - self._cleanup_repo_settings(settings) - - def test_svn_settings_are_created( - self, autologin_user, backend_svn, csrf_token, settings_util): - repo_name = backend_svn.repo_name - data = self.FORM_DATA.copy() - data['new_svn_tag'] = 'svn-tag' - data['new_svn_branch'] = 'svn-branch' - data['csrf_token'] = csrf_token - - # Create few global settings to make sure that uniqueness validators - # are not triggered - settings_util.create_rhodecode_ui( - VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch') - settings_util.create_rhodecode_ui( - VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag') - - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - settings = SettingsModel(repo=repo_name) - try: - svn_branches = settings.get_ui_by_section( - VcsSettingsModel.SVN_BRANCH_SECTION) - svn_branch_names = [b.ui_value for b in svn_branches] - svn_tags = settings.get_ui_by_section( - VcsSettingsModel.SVN_TAG_SECTION) - svn_tag_names = [b.ui_value for b in svn_tags] - assert 'svn-branch' in svn_branch_names - assert 'svn-tag' in svn_tag_names - finally: - self._cleanup_repo_settings(settings) - - def test_svn_settings_are_unique( - self, autologin_user, backend_svn, csrf_token, settings_util): - repo = backend_svn.repo - repo_name = repo.repo_name - data = self.FORM_DATA.copy() - data['new_svn_tag'] = 'test_tag' - data['new_svn_branch'] = 'test_branch' - data['csrf_token'] = csrf_token - settings_util.create_repo_rhodecode_ui( - repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch') - settings_util.create_repo_rhodecode_ui( - repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag') - - response = self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=200) - response.mustcontain('Pattern already exists') - - def test_svn_settings_with_empty_values_are_not_created( - self, autologin_user, backend_svn, csrf_token): - repo_name = backend_svn.repo_name - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - settings = SettingsModel(repo=repo_name) - try: - svn_branches = settings.get_ui_by_section( - VcsSettingsModel.SVN_BRANCH_SECTION) - svn_tags = settings.get_ui_by_section( - VcsSettingsModel.SVN_TAG_SECTION) - assert len(svn_branches) == 0 - assert len(svn_tags) == 0 - finally: - self._cleanup_repo_settings(settings) - - def test_svn_settings_are_shown_for_svn_repository( - self, autologin_user, backend_svn, csrf_token): - repo_name = backend_svn.repo_name - response = self.app.get( - url('repo_vcs_settings', repo_name=repo_name), status=200) - response.mustcontain('Subversion Settings') - - @pytest.mark.skip_backends('svn') - def test_svn_settings_are_not_created_for_not_svn_repository( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - settings = SettingsModel(repo=repo_name) - try: - svn_branches = settings.get_ui_by_section( - VcsSettingsModel.SVN_BRANCH_SECTION) - svn_tags = settings.get_ui_by_section( - VcsSettingsModel.SVN_TAG_SECTION) - assert len(svn_branches) == 0 - assert len(svn_tags) == 0 - finally: - self._cleanup_repo_settings(settings) - - @pytest.mark.skip_backends('svn') - def test_svn_settings_are_shown_only_for_svn_repository( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - response = self.app.get( - url('repo_vcs_settings', repo_name=repo_name), status=200) - response.mustcontain(no='Subversion Settings') - - def test_hg_settings_are_created( - self, autologin_user, backend_hg, csrf_token): - repo_name = backend_hg.repo_name - data = self.FORM_DATA.copy() - data['new_svn_tag'] = 'svn-tag' - data['new_svn_branch'] = 'svn-branch' - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - settings = SettingsModel(repo=repo_name) - try: - largefiles_ui = settings.get_ui_by_section_and_key( - 'extensions', 'largefiles') - assert largefiles_ui.ui_active is False - phases_ui = settings.get_ui_by_section_and_key( - 'phases', 'publish') - assert str2bool(phases_ui.ui_value) is False - finally: - self._cleanup_repo_settings(settings) - - def test_hg_settings_are_updated( - self, autologin_user, backend_hg, csrf_token): - repo_name = backend_hg.repo_name - settings = SettingsModel(repo=repo_name) - settings.create_ui_section_value( - 'extensions', '', key='largefiles', active=True) - settings.create_ui_section_value( - 'phases', '1', key='publish', active=True) - - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - try: - largefiles_ui = settings.get_ui_by_section_and_key( - 'extensions', 'largefiles') - assert largefiles_ui.ui_active is False - phases_ui = settings.get_ui_by_section_and_key( - 'phases', 'publish') - assert str2bool(phases_ui.ui_value) is False - finally: - self._cleanup_repo_settings(settings) - - def test_hg_settings_are_shown_for_hg_repository( - self, autologin_user, backend_hg, csrf_token): - repo_name = backend_hg.repo_name - response = self.app.get( - url('repo_vcs_settings', repo_name=repo_name), status=200) - response.mustcontain('Mercurial Settings') - - @pytest.mark.skip_backends('hg') - def test_hg_settings_are_created_only_for_hg_repository( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - settings = SettingsModel(repo=repo_name) - try: - largefiles_ui = settings.get_ui_by_section_and_key( - 'extensions', 'largefiles') - assert largefiles_ui is None - phases_ui = settings.get_ui_by_section_and_key( - 'phases', 'publish') - assert phases_ui is None - finally: - self._cleanup_repo_settings(settings) - - @pytest.mark.skip_backends('hg') - def test_hg_settings_are_shown_only_for_hg_repository( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - response = self.app.get( - url('repo_vcs_settings', repo_name=repo_name), status=200) - response.mustcontain(no='Mercurial Settings') - - @pytest.mark.skip_backends('hg') - def test_hg_settings_are_updated_only_for_hg_repository( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - settings = SettingsModel(repo=repo_name) - settings.create_ui_section_value( - 'extensions', '', key='largefiles', active=True) - settings.create_ui_section_value( - 'phases', '1', key='publish', active=True) - - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - try: - largefiles_ui = settings.get_ui_by_section_and_key( - 'extensions', 'largefiles') - assert largefiles_ui.ui_active is True - phases_ui = settings.get_ui_by_section_and_key( - 'phases', 'publish') - assert phases_ui.ui_value == '1' - finally: - self._cleanup_repo_settings(settings) - - def test_per_repo_svn_settings_are_displayed( - self, autologin_user, backend_svn, settings_util): - repo = backend_svn.create_repo() - repo_name = repo.repo_name - branches = [ - settings_util.create_repo_rhodecode_ui( - repo, VcsSettingsModel.SVN_BRANCH_SECTION, - 'branch_{}'.format(i)) - for i in range(10)] - tags = [ - settings_util.create_repo_rhodecode_ui( - repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i)) - for i in range(10)] - - response = self.app.get( - url('repo_vcs_settings', repo_name=repo_name), status=200) - assert_response = AssertResponse(response) - for branch in branches: - css_selector = '[name=branch_value_{}]'.format(branch.ui_id) - element = assert_response.get_element(css_selector) - assert element.value == branch.ui_value - for tag in tags: - css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id) - element = assert_response.get_element(css_selector) - assert element.value == tag.ui_value - - def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn( - self, autologin_user, backend_svn, settings_util): - repo = backend_svn.create_repo() - repo_name = repo.repo_name - response = self.app.get( - url('repo_vcs_settings', repo_name=repo_name), status=200) - response.mustcontain(no='') - response.mustcontain(no='') - - def test_inherit_global_settings_value_is_saved( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - data['inherit_global_settings'] = True - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - - settings = SettingsModel(repo=repo_name) - vcs_settings = VcsSettingsModel(repo=repo_name) - try: - assert vcs_settings.inherit_global_settings is True - finally: - self._cleanup_repo_settings(settings) - - def test_repo_cache_is_invalidated_when_settings_are_updated( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - data['inherit_global_settings'] = True - settings = SettingsModel(repo=repo_name) - - invalidation_patcher = mock.patch( - 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation') - with invalidation_patcher as invalidation_mock: - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, - status=302) - try: - invalidation_mock.assert_called_once_with(repo_name, delete=True) - finally: - self._cleanup_repo_settings(settings) - - def test_other_settings_not_saved_inherit_global_settings_is_true( - self, autologin_user, backend, csrf_token): - repo_name = backend.repo_name - data = self.FORM_DATA.copy() - data['csrf_token'] = csrf_token - data['inherit_global_settings'] = True - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, status=302) - - settings = SettingsModel(repo=repo_name) - ui_settings = ( - VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS) - - vcs_settings = [] - try: - for section, key in ui_settings: - ui = settings.get_ui_by_section_and_key(section, key) - if ui: - vcs_settings.append(ui) - vcs_settings.extend(settings.get_ui_by_section( - VcsSettingsModel.SVN_BRANCH_SECTION)) - vcs_settings.extend(settings.get_ui_by_section( - VcsSettingsModel.SVN_TAG_SECTION)) - for name in VcsSettingsModel.GENERAL_SETTINGS: - setting = settings.get_setting_by_name(name) - if setting: - vcs_settings.append(setting) - assert vcs_settings == [] - finally: - self._cleanup_repo_settings(settings) - - def test_delete_svn_branch_and_tag_patterns( - self, autologin_user, backend_svn, settings_util, csrf_token): - repo = backend_svn.create_repo() - repo_name = repo.repo_name - branch = settings_util.create_repo_rhodecode_ui( - repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch', - cleanup=False) - tag = settings_util.create_repo_rhodecode_ui( - repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False) - data = { - '_method': 'delete', - 'csrf_token': csrf_token - } - for id_ in (branch.ui_id, tag.ui_id): - data['delete_svn_pattern'] = id_, - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, - headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200) - settings = VcsSettingsModel(repo=repo_name) - assert settings.get_repo_svn_branch_patterns() == [] - - def test_delete_svn_branch_requires_repo_admin_permission( - self, backend_svn, user_util, settings_util, csrf_token): - repo = backend_svn.create_repo() - repo_name = repo.repo_name - - logout_user_session(self.app, csrf_token) - session = login_user_session( - self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) - csrf_token = auth.get_csrf_token(session) - - repo = Repository.get_by_repo_name(repo_name) - user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN) - user_util.grant_user_permission_to_repo(repo, user, 'repository.admin') - branch = settings_util.create_repo_rhodecode_ui( - repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch', - cleanup=False) - data = { - '_method': 'delete', - 'csrf_token': csrf_token, - 'delete_svn_pattern': branch.ui_id - } - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, - headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200) - - def test_delete_svn_branch_raises_400_when_not_found( - self, autologin_user, backend_svn, settings_util, csrf_token): - repo_name = backend_svn.repo_name - data = { - '_method': 'delete', - 'delete_svn_pattern': 123, - 'csrf_token': csrf_token - } - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, - headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400) - - def test_delete_svn_branch_raises_400_when_no_id_specified( - self, autologin_user, backend_svn, settings_util, csrf_token): - repo_name = backend_svn.repo_name - data = { - '_method': 'delete', - 'csrf_token': csrf_token - } - self.app.post( - url('repo_vcs_settings', repo_name=repo_name), data, - headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400) - - def _cleanup_repo_settings(self, settings_model): - cleanup = [] - ui_settings = ( - VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS) - - for section, key in ui_settings: - ui = settings_model.get_ui_by_section_and_key(section, key) - if ui: - cleanup.append(ui) - - cleanup.extend(settings_model.get_ui_by_section( - VcsSettingsModel.INHERIT_SETTINGS)) - cleanup.extend(settings_model.get_ui_by_section( - VcsSettingsModel.SVN_BRANCH_SECTION)) - cleanup.extend(settings_model.get_ui_by_section( - VcsSettingsModel.SVN_TAG_SECTION)) - - for name in VcsSettingsModel.GENERAL_SETTINGS: - setting = settings_model.get_setting_by_name(name) - if setting: - cleanup.append(setting) - - for object_ in cleanup: - Session().delete(object_) - Session().commit() - - def assert_repo_value_equals_global_value(self, response, setting): - assert_response = AssertResponse(response) - global_css_selector = '[name={}_inherited]'.format(setting) - repo_css_selector = '[name={}]'.format(setting) - repo_element = assert_response.get_element(repo_css_selector) - global_element = assert_response.get_element(global_css_selector) - assert repo_element.value == global_element.value - - -def _get_permission_for_user(user, repo): - perm = UserRepoToPerm.query()\ - .filter(UserRepoToPerm.repository == - Repository.get_by_repo_name(repo))\ - .filter(UserRepoToPerm.user == User.get_by_username(user))\ - .all() - return perm diff --git a/rhodecode/apps/admin/views/open_source_licenses.py b/rhodecode/apps/admin/views/open_source_licenses.py --- a/rhodecode/apps/admin/views/open_source_licenses.py +++ b/rhodecode/apps/admin/views/open_source_licenses.py @@ -48,7 +48,7 @@ class OpenSourceLicensesAdminSettingsVie c = self.load_default_context() c.active = 'open_source' c.navlist = navigation_list(self.request) - c.opensource_licenses = collections.OrderedDict( - sorted(read_opensource_licenses().items(), key=lambda t: t[0])) + items = sorted(read_opensource_licenses().items(), key=lambda t: t[0]) + c.opensource_licenses = collections.OrderedDict(items) return self._get_template_context(c) diff --git a/rhodecode/apps/admin/views/repositories.py b/rhodecode/apps/admin/views/repositories.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/admin/views/repositories.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2016-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 formencode + +from pyramid.httpexceptions import HTTPFound, HTTPForbidden +from pyramid.view import view_config +from pyramid.renderers import render +from pyramid.response import Response + +from rhodecode.apps._base import BaseAppView, DataGridAppView + +from rhodecode.lib.ext_json import json +from rhodecode.lib.auth import ( + LoginRequired, CSRFRequired, NotAnonymous, + HasPermissionAny, HasRepoGroupPermissionAny) +from rhodecode.lib import helpers as h +from rhodecode.lib.utils import repo_name_slug +from rhodecode.lib.utils2 import safe_int, safe_unicode +from rhodecode.model.forms import RepoForm +from rhodecode.model.repo import RepoModel +from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel +from rhodecode.model.settings import SettingsModel +from rhodecode.model.db import Repository, RepoGroup + +log = logging.getLogger(__name__) + + +class AdminReposView(BaseAppView, DataGridAppView): + + def load_default_context(self): + c = self._get_local_tmpl_context() + self._register_global_c(c) + return c + + def _load_form_data(self, c): + acl_groups = RepoGroupList(RepoGroup.query().all(), + perm_set=['group.write', 'group.admin']) + c.repo_groups = RepoGroup.groups_choices(groups=acl_groups) + c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups) + c.landing_revs_choices, c.landing_revs = \ + ScmModel().get_repo_landing_revs() + c.personal_repo_group = self._rhodecode_user.personal_repo_group + + @LoginRequired() + @NotAnonymous() + @view_config( + route_name='repos', request_method='GET', + renderer='rhodecode:templates/admin/repos/repos.mako') + def repository_list(self): + c = self.load_default_context() + + repo_list = Repository.get_all_repos() + c.repo_list = RepoList(repo_list, perm_set=['repository.admin']) + repos_data = RepoModel().get_repos_as_dict( + repo_list=c.repo_list, admin=True, super_user_actions=True) + # json used to render the grid + c.data = json.dumps(repos_data) + + return self._get_template_context(c) + + @LoginRequired() + @NotAnonymous() + # perms check inside + @view_config( + route_name='repo_new', request_method='GET', + renderer='rhodecode:templates/admin/repos/repo_add.mako') + def repository_new(self): + c = self.load_default_context() + + new_repo = self.request.GET.get('repo', '') + parent_group = safe_int(self.request.GET.get('parent_group')) + _gr = RepoGroup.get(parent_group) + + if not HasPermissionAny('hg.admin', 'hg.create.repository')(): + # you're not super admin nor have global create permissions, + # but maybe you have at least write permission to a parent group ? + + gr_name = _gr.group_name if _gr else None + # create repositories with write permission on group is set to true + create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')() + group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name) + group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name) + if not (group_admin or (group_write and create_on_write)): + raise HTTPForbidden() + + self._load_form_data(c) + c.new_repo = repo_name_slug(new_repo) + + # apply the defaults from defaults page + defaults = SettingsModel().get_default_repo_settings(strip_prefix=True) + # set checkbox to autochecked + defaults['repo_copy_permissions'] = True + + parent_group_choice = '-1' + if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group: + parent_group_choice = self._rhodecode_user.personal_repo_group + + if parent_group and _gr: + if parent_group in [x[0] for x in c.repo_groups]: + parent_group_choice = safe_unicode(parent_group) + + defaults.update({'repo_group': parent_group_choice}) + + data = render('rhodecode:templates/admin/repos/repo_add.mako', + self._get_template_context(c), self.request) + html = formencode.htmlfill.render( + data, + defaults=defaults, + encoding="UTF-8", + force_defaults=False + ) + return Response(html) + + @LoginRequired() + @NotAnonymous() + @CSRFRequired() + # perms check inside + @view_config( + route_name='repo_create', request_method='POST', + renderer='rhodecode:templates/admin/repos/repos.mako') + def repository_create(self): + c = self.load_default_context() + + form_result = {} + task_id = None + self._load_form_data(c) + + try: + # CanWriteToGroup validators checks permissions of this POST + form_result = RepoForm(repo_groups=c.repo_groups_choices, + landing_revs=c.landing_revs_choices)()\ + .to_python(dict(self.request.POST)) + + # create is done sometimes async on celery, db transaction + # management is handled there. + task = RepoModel().create(form_result, self._rhodecode_user.user_id) + from celery.result import BaseAsyncResult + if isinstance(task, BaseAsyncResult): + task_id = task.task_id + except formencode.Invalid as errors: + data = render('rhodecode:templates/admin/repos/repo_add.mako', + self._get_template_context(c), self.request) + html = formencode.htmlfill.render( + data, + defaults=errors.value, + errors=errors.error_dict or {}, + prefix_error=False, + encoding="UTF-8", + force_defaults=False + ) + return Response(html) + + except Exception as e: + msg = self._log_creation_exception(e, form_result.get('repo_name')) + h.flash(msg, category='error') + raise HTTPFound(h.route_path('home')) + + raise HTTPFound( + h.route_path('repo_creating', + repo_name=form_result['repo_name_full'], + _query=dict(task_id=task_id))) diff --git a/rhodecode/apps/admin/views/user_groups.py b/rhodecode/apps/admin/views/user_groups.py --- a/rhodecode/apps/admin/views/user_groups.py +++ b/rhodecode/apps/admin/views/user_groups.py @@ -19,7 +19,6 @@ # and proprietary license terms, please see https://rhodecode.com/licenses/ import logging -import datetime from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config @@ -28,12 +27,11 @@ from rhodecode.model.scm import UserGrou from rhodecode.apps._base import BaseAppView, DataGridAppView from rhodecode.lib.auth import ( - LoginRequired, HasPermissionAllDecorator, CSRFRequired, NotAnonymous, + LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator) from rhodecode.lib import helpers as h from rhodecode.lib.utils import PartialRenderer -from rhodecode.lib.utils2 import safe_int, safe_unicode -from rhodecode.model.user_group import UserGroupModel +from rhodecode.lib.utils2 import safe_unicode from rhodecode.model.db import ( joinedload, or_, count, User, UserGroup, UserGroupMember, UserGroupRepoToPerm, UserGroupRepoGroupToPerm) diff --git a/rhodecode/apps/ops/tests/__init__.py b/rhodecode/apps/ops/tests/__init__.py new file mode 100644 diff --git a/rhodecode/apps/repo_group/tests/__init__.py b/rhodecode/apps/repo_group/tests/__init__.py new file mode 100644 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 @@ -337,7 +337,76 @@ def includeme(config): name='edit_repo_perms', pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True) - # Repo Review Rules + # Maintenance + config.add_route( + name='edit_repo_maintenance', + pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True) + + config.add_route( + name='edit_repo_maintenance_execute', + pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True) + + # Fields + config.add_route( + name='edit_repo_fields', + pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True) + config.add_route( + name='edit_repo_fields_create', + pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True) + config.add_route( + name='edit_repo_fields_delete', + pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True) + + # Locking + config.add_route( + name='repo_edit_toggle_locking', + pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True) + + # Remote + config.add_route( + name='edit_repo_remote', + pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True) + config.add_route( + name='edit_repo_remote_pull', + pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True) + + + # Statistics + config.add_route( + name='edit_repo_statistics', + pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True) + config.add_route( + name='edit_repo_statistics_reset', + pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True) + + # Issue trackers + config.add_route( + name='edit_repo_issuetracker', + pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True) + config.add_route( + name='edit_repo_issuetracker_test', + pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True) + config.add_route( + name='edit_repo_issuetracker_delete', + pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True) + config.add_route( + name='edit_repo_issuetracker_update', + pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True) + + # VCS Settings + config.add_route( + name='edit_repo_vcs', + pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True) + config.add_route( + name='edit_repo_vcs_update', + pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True) + + # svn pattern + config.add_route( + name='edit_repo_vcs_svn_pattern_delete', + pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True) + + # Repo Review Rules (EE feature) config.add_route( name='repo_reviewers', pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True) @@ -346,18 +415,9 @@ def includeme(config): name='repo_default_reviewers_data', pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True) - # Maintenance - config.add_route( - name='repo_maintenance', - pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True) - - config.add_route( - name='repo_maintenance_execute', - pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True) - # Strip config.add_route( - name='strip', + name='edit_repo_strip', pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True) config.add_route( diff --git a/rhodecode/tests/functional/test_admin_repos_issuetracker.py b/rhodecode/apps/repository/tests/test_repo_issue_tracker.py rename from rhodecode/tests/functional/test_admin_repos_issuetracker.py rename to rhodecode/apps/repository/tests/test_repo_issue_tracker.py --- a/rhodecode/tests/functional/test_admin_repos_issuetracker.py +++ b/rhodecode/apps/repository/tests/test_repo_issue_tracker.py @@ -24,23 +24,38 @@ from rhodecode.lib.utils2 import md5 from rhodecode.model.db import Repository from rhodecode.model.meta import Session from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel -from rhodecode.tests import url + + +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'repo_summary': '/{repo_name}', + 'edit_repo_issuetracker': '/{repo_name}/settings/issue_trackers', + 'edit_repo_issuetracker_test': '/{repo_name}/settings/issue_trackers/test', + 'edit_repo_issuetracker_delete': '/{repo_name}/settings/issue_trackers/delete', + 'edit_repo_issuetracker_update': '/{repo_name}/settings/issue_trackers/update', + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url @pytest.mark.usefixtures("app") -class TestAdminRepos: +class TestRepoIssueTracker(object): def test_issuetracker_index(self, autologin_user, backend): repo = backend.create_repo() - response = self.app.get(url('repo_settings_issuetracker', + response = self.app.get(route_path('edit_repo_issuetracker', repo_name=repo.repo_name)) assert response.status_code == 200 - def test_add_issuetracker_patterns( - self, autologin_user, backend, csrf_token, request): + def test_add_and_test_issuetracker_patterns( + self, autologin_user, backend, csrf_token, request, xhr_header): pattern = 'issuetracker_pat' another_pattern = pattern+'1' - post_url = url('repo_issuetracker_save', - repo_name=backend.repo.repo_name) + post_url = route_path( + 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name) post_data = { 'new_pattern_pattern_0': pattern, 'new_pattern_url_0': 'url', @@ -60,6 +75,17 @@ class TestAdminRepos: self.another_uid = md5(another_pattern) assert settings[self.another_uid]['pat'] == another_pattern + # test pattern + data = {'test_text': 'example of issuetracker_pat replacement', + 'csrf_token': csrf_token} + response = self.app.post( + route_path('edit_repo_issuetracker_test', + repo_name=backend.repo.repo_name), + extra_environ=xhr_header, params=data) + + assert response.body == \ + 'example of prefix replacement' + @request.addfinalizer def cleanup(): self.settings_model.delete_entries(self.uid) @@ -76,8 +102,8 @@ class TestAdminRepos: entry_key+old_uid, old_pattern, 'unicode') Session().add(sett) Session().commit() - post_url = url('repo_issuetracker_save', - repo_name=backend.repo.repo_name) + post_url = route_path( + 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name) post_data = { 'new_pattern_pattern_0': pattern, 'new_pattern_url_0': 'url', @@ -92,7 +118,7 @@ class TestAdminRepos: self.uid = md5(pattern) assert settings[self.uid]['pat'] == pattern with pytest.raises(KeyError): - settings[old_uid] + key = settings[old_uid] @request.addfinalizer def cleanup(): @@ -110,10 +136,10 @@ class TestAdminRepos: value=entry_key, type_='unicode', cleanup=False) self.app.post( - url('repo_issuetracker_delete', + route_path( + 'edit_repo_issuetracker_delete', repo_name=backend.repo.repo_name), { - '_method': 'delete', 'uid': uid, 'csrf_token': csrf_token }, status=302) diff --git a/rhodecode/apps/repository/tests/test_repo_settings.py b/rhodecode/apps/repository/tests/test_repo_settings.py --- a/rhodecode/apps/repository/tests/test_repo_settings.py +++ b/rhodecode/apps/repository/tests/test_repo_settings.py @@ -26,8 +26,7 @@ from rhodecode.lib.vcs.exceptions import from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User from rhodecode.model.meta import Session from rhodecode.tests import ( - url, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, - assert_session_flash) + TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, assert_session_flash) from rhodecode.tests.fixture import Fixture fixture = Fixture() @@ -41,6 +40,11 @@ def route_path(name, params=None, **kwar 'edit_repo_advanced': '/{repo_name}/settings/advanced', 'edit_repo_caches': '/{repo_name}/settings/caches', 'edit_repo_perms': '/{repo_name}/settings/permissions', + 'edit_repo_vcs': '/{repo_name}/settings/vcs', + 'edit_repo_issuetracker': '/{repo_name}/settings/issue_trackers', + 'edit_repo_fields': '/{repo_name}/settings/fields', + 'edit_repo_remote': '/{repo_name}/settings/remote', + 'edit_repo_statistics': '/{repo_name}/settings/statistics', }[name].format(**kwargs) if params: @@ -64,6 +68,11 @@ class TestAdminRepoSettings(object): 'edit_repo_caches', 'edit_repo_perms', 'edit_repo_advanced', + 'edit_repo_vcs', + 'edit_repo_issuetracker', + 'edit_repo_fields', + 'edit_repo_remote', + 'edit_repo_statistics', ]) def test_show_page(self, urlname, app, backend): app.get(route_path(urlname, repo_name=backend.repo_name), status=200) @@ -75,16 +84,6 @@ class TestAdminRepoSettings(object): with scm_patcher: self.app.get(route_path('edit_repo', repo_name=backend_hg.repo_name)) - @pytest.mark.parametrize('urlname', [ - 'repo_vcs_settings', - 'repo_settings_issuetracker', - 'edit_repo_fields', - 'edit_repo_remote', - 'edit_repo_statistics', - ]) - def test_show_page_pylons(self, urlname, app): - app.get(url(urlname, repo_name=HG_REPO)) - @pytest.mark.parametrize('update_settings', [ {'repo_description': 'alter-desc'}, {'repo_owner': TEST_USER_REGULAR_LOGIN}, diff --git a/rhodecode/apps/repository/tests/test_repo_vcs_settings.py b/rhodecode/apps/repository/tests/test_repo_vcs_settings.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repository/tests/test_repo_vcs_settings.py @@ -0,0 +1,685 @@ +# -*- 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/ + +import mock +import pytest + +from rhodecode.lib import auth +from rhodecode.lib.utils2 import str2bool +from rhodecode.model.db import ( + Repository, UserRepoToPerm, User) +from rhodecode.model.meta import Session +from rhodecode.model.settings import SettingsModel, VcsSettingsModel +from rhodecode.model.user import UserModel +from rhodecode.tests import ( + login_user_session, logout_user_session, + TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) +from rhodecode.tests.fixture import Fixture +from rhodecode.tests.utils import AssertResponse + +fixture = Fixture() + + +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'repo_summary': '/{repo_name}', + 'repo_creating_check': '/{repo_name}/repo_creating_check', + 'edit_repo': '/{repo_name}/settings', + 'edit_repo_vcs': '/{repo_name}/settings/vcs', + 'edit_repo_vcs_update': '/{repo_name}/settings/vcs/update', + 'edit_repo_vcs_svn_pattern_delete': '/{repo_name}/settings/vcs/svn_pattern/delete' + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + +@pytest.mark.usefixtures("app") +class TestVcsSettings(object): + FORM_DATA = { + 'inherit_global_settings': False, + 'hooks_changegroup_repo_size': False, + 'hooks_changegroup_push_logger': False, + 'hooks_outgoing_pull_logger': False, + 'extensions_largefiles': False, + 'extensions_evolve': False, + 'phases_publish': 'False', + 'rhodecode_pr_merge_enabled': False, + 'rhodecode_use_outdated_comments': False, + 'new_svn_branch': '', + 'new_svn_tag': '' + } + + @pytest.mark.skip_backends('svn') + def test_global_settings_initial_values(self, autologin_user, backend): + repo_name = backend.repo_name + response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name)) + + expected_settings = ( + 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled', + 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger', + 'hooks_outgoing_pull_logger' + ) + for setting in expected_settings: + self.assert_repo_value_equals_global_value(response, setting) + + def test_show_settings_requires_repo_admin_permission( + self, backend, user_util, settings_util): + repo = backend.create_repo() + repo_name = repo.repo_name + user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN) + user_util.grant_user_permission_to_repo(repo, user, 'repository.admin') + login_user_session( + self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) + self.app.get(route_path('edit_repo_vcs', repo_name=repo_name), status=200) + + def test_inherit_global_settings_flag_is_true_by_default( + self, autologin_user, backend): + repo_name = backend.repo_name + response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name)) + + assert_response = AssertResponse(response) + element = assert_response.get_element('#inherit_global_settings') + assert element.checked + + @pytest.mark.parametrize('checked_value', [True, False]) + def test_inherit_global_settings_value( + self, autologin_user, backend, checked_value, settings_util): + repo = backend.create_repo() + repo_name = repo.repo_name + settings_util.create_repo_rhodecode_setting( + repo, 'inherit_vcs_settings', checked_value, 'bool') + response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name)) + + assert_response = AssertResponse(response) + element = assert_response.get_element('#inherit_global_settings') + assert element.checked == checked_value + + @pytest.mark.skip_backends('svn') + def test_hooks_settings_are_created( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + settings = SettingsModel(repo=repo_name) + try: + for section, key in VcsSettingsModel.HOOKS_SETTINGS: + ui = settings.get_ui_by_section_and_key(section, key) + assert ui.ui_active is False + finally: + self._cleanup_repo_settings(settings) + + def test_hooks_settings_are_not_created_for_svn( + self, autologin_user, backend_svn, csrf_token): + repo_name = backend_svn.repo_name + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + settings = SettingsModel(repo=repo_name) + try: + for section, key in VcsSettingsModel.HOOKS_SETTINGS: + ui = settings.get_ui_by_section_and_key(section, key) + assert ui is None + finally: + self._cleanup_repo_settings(settings) + + @pytest.mark.skip_backends('svn') + def test_hooks_settings_are_updated( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + settings = SettingsModel(repo=repo_name) + for section, key in VcsSettingsModel.HOOKS_SETTINGS: + settings.create_ui_section_value(section, '', key=key, active=True) + + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + try: + for section, key in VcsSettingsModel.HOOKS_SETTINGS: + ui = settings.get_ui_by_section_and_key(section, key) + assert ui.ui_active is False + finally: + self._cleanup_repo_settings(settings) + + def test_hooks_settings_are_not_updated_for_svn( + self, autologin_user, backend_svn, csrf_token): + repo_name = backend_svn.repo_name + settings = SettingsModel(repo=repo_name) + for section, key in VcsSettingsModel.HOOKS_SETTINGS: + settings.create_ui_section_value(section, '', key=key, active=True) + + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + try: + for section, key in VcsSettingsModel.HOOKS_SETTINGS: + ui = settings.get_ui_by_section_and_key(section, key) + assert ui.ui_active is True + finally: + self._cleanup_repo_settings(settings) + + @pytest.mark.skip_backends('svn') + def test_pr_settings_are_created( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + settings = SettingsModel(repo=repo_name) + try: + for name in VcsSettingsModel.GENERAL_SETTINGS: + setting = settings.get_setting_by_name(name) + assert setting.app_settings_value is False + finally: + self._cleanup_repo_settings(settings) + + def test_pr_settings_are_not_created_for_svn( + self, autologin_user, backend_svn, csrf_token): + repo_name = backend_svn.repo_name + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + settings = SettingsModel(repo=repo_name) + try: + for name in VcsSettingsModel.GENERAL_SETTINGS: + setting = settings.get_setting_by_name(name) + assert setting is None + finally: + self._cleanup_repo_settings(settings) + + def test_pr_settings_creation_requires_repo_admin_permission( + self, backend, user_util, settings_util, csrf_token): + repo = backend.create_repo() + repo_name = repo.repo_name + + logout_user_session(self.app, csrf_token) + session = login_user_session( + self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) + new_csrf_token = auth.get_csrf_token(session) + + user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN) + repo = Repository.get_by_repo_name(repo_name) + user_util.grant_user_permission_to_repo(repo, user, 'repository.admin') + data = self.FORM_DATA.copy() + data['csrf_token'] = new_csrf_token + settings = SettingsModel(repo=repo_name) + + try: + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, + status=302) + finally: + self._cleanup_repo_settings(settings) + + @pytest.mark.skip_backends('svn') + def test_pr_settings_are_updated( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + settings = SettingsModel(repo=repo_name) + for name in VcsSettingsModel.GENERAL_SETTINGS: + settings.create_or_update_setting(name, True, 'bool') + + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + try: + for name in VcsSettingsModel.GENERAL_SETTINGS: + setting = settings.get_setting_by_name(name) + assert setting.app_settings_value is False + finally: + self._cleanup_repo_settings(settings) + + def test_pr_settings_are_not_updated_for_svn( + self, autologin_user, backend_svn, csrf_token): + repo_name = backend_svn.repo_name + settings = SettingsModel(repo=repo_name) + for name in VcsSettingsModel.GENERAL_SETTINGS: + settings.create_or_update_setting(name, True, 'bool') + + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + try: + for name in VcsSettingsModel.GENERAL_SETTINGS: + setting = settings.get_setting_by_name(name) + assert setting.app_settings_value is True + finally: + self._cleanup_repo_settings(settings) + + def test_svn_settings_are_created( + self, autologin_user, backend_svn, csrf_token, settings_util): + repo_name = backend_svn.repo_name + data = self.FORM_DATA.copy() + data['new_svn_tag'] = 'svn-tag' + data['new_svn_branch'] = 'svn-branch' + data['csrf_token'] = csrf_token + + # Create few global settings to make sure that uniqueness validators + # are not triggered + settings_util.create_rhodecode_ui( + VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch') + settings_util.create_rhodecode_ui( + VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag') + + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + settings = SettingsModel(repo=repo_name) + try: + svn_branches = settings.get_ui_by_section( + VcsSettingsModel.SVN_BRANCH_SECTION) + svn_branch_names = [b.ui_value for b in svn_branches] + svn_tags = settings.get_ui_by_section( + VcsSettingsModel.SVN_TAG_SECTION) + svn_tag_names = [b.ui_value for b in svn_tags] + assert 'svn-branch' in svn_branch_names + assert 'svn-tag' in svn_tag_names + finally: + self._cleanup_repo_settings(settings) + + def test_svn_settings_are_unique( + self, autologin_user, backend_svn, csrf_token, settings_util): + repo = backend_svn.repo + repo_name = repo.repo_name + data = self.FORM_DATA.copy() + data['new_svn_tag'] = 'test_tag' + data['new_svn_branch'] = 'test_branch' + data['csrf_token'] = csrf_token + settings_util.create_repo_rhodecode_ui( + repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch') + settings_util.create_repo_rhodecode_ui( + repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag') + + response = self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=200) + response.mustcontain('Pattern already exists') + + def test_svn_settings_with_empty_values_are_not_created( + self, autologin_user, backend_svn, csrf_token): + repo_name = backend_svn.repo_name + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + settings = SettingsModel(repo=repo_name) + try: + svn_branches = settings.get_ui_by_section( + VcsSettingsModel.SVN_BRANCH_SECTION) + svn_tags = settings.get_ui_by_section( + VcsSettingsModel.SVN_TAG_SECTION) + assert len(svn_branches) == 0 + assert len(svn_tags) == 0 + finally: + self._cleanup_repo_settings(settings) + + def test_svn_settings_are_shown_for_svn_repository( + self, autologin_user, backend_svn, csrf_token): + repo_name = backend_svn.repo_name + response = self.app.get( + route_path('edit_repo_vcs', repo_name=repo_name), status=200) + response.mustcontain('Subversion Settings') + + @pytest.mark.skip_backends('svn') + def test_svn_settings_are_not_created_for_not_svn_repository( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + settings = SettingsModel(repo=repo_name) + try: + svn_branches = settings.get_ui_by_section( + VcsSettingsModel.SVN_BRANCH_SECTION) + svn_tags = settings.get_ui_by_section( + VcsSettingsModel.SVN_TAG_SECTION) + assert len(svn_branches) == 0 + assert len(svn_tags) == 0 + finally: + self._cleanup_repo_settings(settings) + + @pytest.mark.skip_backends('svn') + def test_svn_settings_are_shown_only_for_svn_repository( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + response = self.app.get( + route_path('edit_repo_vcs', repo_name=repo_name), status=200) + response.mustcontain(no='Subversion Settings') + + def test_hg_settings_are_created( + self, autologin_user, backend_hg, csrf_token): + repo_name = backend_hg.repo_name + data = self.FORM_DATA.copy() + data['new_svn_tag'] = 'svn-tag' + data['new_svn_branch'] = 'svn-branch' + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + settings = SettingsModel(repo=repo_name) + try: + largefiles_ui = settings.get_ui_by_section_and_key( + 'extensions', 'largefiles') + assert largefiles_ui.ui_active is False + phases_ui = settings.get_ui_by_section_and_key( + 'phases', 'publish') + assert str2bool(phases_ui.ui_value) is False + finally: + self._cleanup_repo_settings(settings) + + def test_hg_settings_are_updated( + self, autologin_user, backend_hg, csrf_token): + repo_name = backend_hg.repo_name + settings = SettingsModel(repo=repo_name) + settings.create_ui_section_value( + 'extensions', '', key='largefiles', active=True) + settings.create_ui_section_value( + 'phases', '1', key='publish', active=True) + + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + try: + largefiles_ui = settings.get_ui_by_section_and_key( + 'extensions', 'largefiles') + assert largefiles_ui.ui_active is False + phases_ui = settings.get_ui_by_section_and_key( + 'phases', 'publish') + assert str2bool(phases_ui.ui_value) is False + finally: + self._cleanup_repo_settings(settings) + + def test_hg_settings_are_shown_for_hg_repository( + self, autologin_user, backend_hg, csrf_token): + repo_name = backend_hg.repo_name + response = self.app.get( + route_path('edit_repo_vcs', repo_name=repo_name), status=200) + response.mustcontain('Mercurial Settings') + + @pytest.mark.skip_backends('hg') + def test_hg_settings_are_created_only_for_hg_repository( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + settings = SettingsModel(repo=repo_name) + try: + largefiles_ui = settings.get_ui_by_section_and_key( + 'extensions', 'largefiles') + assert largefiles_ui is None + phases_ui = settings.get_ui_by_section_and_key( + 'phases', 'publish') + assert phases_ui is None + finally: + self._cleanup_repo_settings(settings) + + @pytest.mark.skip_backends('hg') + def test_hg_settings_are_shown_only_for_hg_repository( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + response = self.app.get( + route_path('edit_repo_vcs', repo_name=repo_name), status=200) + response.mustcontain(no='Mercurial Settings') + + @pytest.mark.skip_backends('hg') + def test_hg_settings_are_updated_only_for_hg_repository( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + settings = SettingsModel(repo=repo_name) + settings.create_ui_section_value( + 'extensions', '', key='largefiles', active=True) + settings.create_ui_section_value( + 'phases', '1', key='publish', active=True) + + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + try: + largefiles_ui = settings.get_ui_by_section_and_key( + 'extensions', 'largefiles') + assert largefiles_ui.ui_active is True + phases_ui = settings.get_ui_by_section_and_key( + 'phases', 'publish') + assert phases_ui.ui_value == '1' + finally: + self._cleanup_repo_settings(settings) + + def test_per_repo_svn_settings_are_displayed( + self, autologin_user, backend_svn, settings_util): + repo = backend_svn.create_repo() + repo_name = repo.repo_name + branches = [ + settings_util.create_repo_rhodecode_ui( + repo, VcsSettingsModel.SVN_BRANCH_SECTION, + 'branch_{}'.format(i)) + for i in range(10)] + tags = [ + settings_util.create_repo_rhodecode_ui( + repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i)) + for i in range(10)] + + response = self.app.get( + route_path('edit_repo_vcs', repo_name=repo_name), status=200) + assert_response = AssertResponse(response) + for branch in branches: + css_selector = '[name=branch_value_{}]'.format(branch.ui_id) + element = assert_response.get_element(css_selector) + assert element.value == branch.ui_value + for tag in tags: + css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id) + element = assert_response.get_element(css_selector) + assert element.value == tag.ui_value + + def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn( + self, autologin_user, backend_svn, settings_util): + repo = backend_svn.create_repo() + repo_name = repo.repo_name + response = self.app.get( + route_path('edit_repo_vcs', repo_name=repo_name), status=200) + response.mustcontain(no='') + response.mustcontain(no='') + + def test_inherit_global_settings_value_is_saved( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + data['inherit_global_settings'] = True + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + + settings = SettingsModel(repo=repo_name) + vcs_settings = VcsSettingsModel(repo=repo_name) + try: + assert vcs_settings.inherit_global_settings is True + finally: + self._cleanup_repo_settings(settings) + + def test_repo_cache_is_invalidated_when_settings_are_updated( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + data['inherit_global_settings'] = True + settings = SettingsModel(repo=repo_name) + + invalidation_patcher = mock.patch( + 'rhodecode.model.scm.ScmModel.mark_for_invalidation') + with invalidation_patcher as invalidation_mock: + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, + status=302) + try: + invalidation_mock.assert_called_once_with(repo_name, delete=True) + finally: + self._cleanup_repo_settings(settings) + + def test_other_settings_not_saved_inherit_global_settings_is_true( + self, autologin_user, backend, csrf_token): + repo_name = backend.repo_name + data = self.FORM_DATA.copy() + data['csrf_token'] = csrf_token + data['inherit_global_settings'] = True + self.app.post( + route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302) + + settings = SettingsModel(repo=repo_name) + ui_settings = ( + VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS) + + vcs_settings = [] + try: + for section, key in ui_settings: + ui = settings.get_ui_by_section_and_key(section, key) + if ui: + vcs_settings.append(ui) + vcs_settings.extend(settings.get_ui_by_section( + VcsSettingsModel.SVN_BRANCH_SECTION)) + vcs_settings.extend(settings.get_ui_by_section( + VcsSettingsModel.SVN_TAG_SECTION)) + for name in VcsSettingsModel.GENERAL_SETTINGS: + setting = settings.get_setting_by_name(name) + if setting: + vcs_settings.append(setting) + assert vcs_settings == [] + finally: + self._cleanup_repo_settings(settings) + + def test_delete_svn_branch_and_tag_patterns( + self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header): + repo = backend_svn.create_repo() + repo_name = repo.repo_name + branch = settings_util.create_repo_rhodecode_ui( + repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch', + cleanup=False) + tag = settings_util.create_repo_rhodecode_ui( + repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False) + data = { + 'csrf_token': csrf_token + } + for id_ in (branch.ui_id, tag.ui_id): + data['delete_svn_pattern'] = id_, + self.app.post( + route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name), + data, extra_environ=xhr_header, status=200) + settings = VcsSettingsModel(repo=repo_name) + assert settings.get_repo_svn_branch_patterns() == [] + + def test_delete_svn_branch_requires_repo_admin_permission( + self, backend_svn, user_util, settings_util, csrf_token, xhr_header): + repo = backend_svn.create_repo() + repo_name = repo.repo_name + + logout_user_session(self.app, csrf_token) + session = login_user_session( + self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) + csrf_token = auth.get_csrf_token(session) + + repo = Repository.get_by_repo_name(repo_name) + user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN) + user_util.grant_user_permission_to_repo(repo, user, 'repository.admin') + branch = settings_util.create_repo_rhodecode_ui( + repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch', + cleanup=False) + data = { + 'csrf_token': csrf_token, + 'delete_svn_pattern': branch.ui_id + } + self.app.post( + route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name), + data, extra_environ=xhr_header, status=200) + + def test_delete_svn_branch_raises_400_when_not_found( + self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header): + repo_name = backend_svn.repo_name + data = { + 'delete_svn_pattern': 123, + 'csrf_token': csrf_token + } + self.app.post( + route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name), + data, extra_environ=xhr_header, status=400) + + def test_delete_svn_branch_raises_400_when_no_id_specified( + self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header): + repo_name = backend_svn.repo_name + data = { + 'csrf_token': csrf_token + } + self.app.post( + route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name), + data, extra_environ=xhr_header, status=400) + + def _cleanup_repo_settings(self, settings_model): + cleanup = [] + ui_settings = ( + VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS) + + for section, key in ui_settings: + ui = settings_model.get_ui_by_section_and_key(section, key) + if ui: + cleanup.append(ui) + + cleanup.extend(settings_model.get_ui_by_section( + VcsSettingsModel.INHERIT_SETTINGS)) + cleanup.extend(settings_model.get_ui_by_section( + VcsSettingsModel.SVN_BRANCH_SECTION)) + cleanup.extend(settings_model.get_ui_by_section( + VcsSettingsModel.SVN_TAG_SECTION)) + + for name in VcsSettingsModel.GENERAL_SETTINGS: + setting = settings_model.get_setting_by_name(name) + if setting: + cleanup.append(setting) + + for object_ in cleanup: + Session().delete(object_) + Session().commit() + + def assert_repo_value_equals_global_value(self, response, setting): + assert_response = AssertResponse(response) + global_css_selector = '[name={}_inherited]'.format(setting) + repo_css_selector = '[name={}]'.format(setting) + repo_element = assert_response.get_element(repo_css_selector) + global_element = assert_response.get_element(global_css_selector) + assert repo_element.value == global_element.value + + +def _get_permission_for_user(user, repo): + perm = UserRepoToPerm.query()\ + .filter(UserRepoToPerm.repository == + Repository.get_by_repo_name(repo))\ + .filter(UserRepoToPerm.user == User.get_by_username(user))\ + .all() + return perm diff --git a/rhodecode/apps/repository/tests/test_vcs_settings.py b/rhodecode/apps/repository/tests/test_vcs_settings.py --- a/rhodecode/apps/repository/tests/test_vcs_settings.py +++ b/rhodecode/apps/repository/tests/test_vcs_settings.py @@ -24,7 +24,6 @@ import pytest import rhodecode from rhodecode.model.db import Repository from rhodecode.model.settings import SettingsModel -from rhodecode.tests import url from rhodecode.tests.utils import AssertResponse @@ -33,6 +32,8 @@ def route_path(name, params=None, **kwar base_url = { 'edit_repo': '/{repo_name}/settings', + 'edit_repo_vcs': '/{repo_name}/settings/vcs', + 'edit_repo_vcs_update': '/{repo_name}/settings/vcs/update', }[name].format(**kwargs) if params: @@ -51,8 +52,8 @@ class TestAdminRepoVcsSettings(object): if backend.alias not in setting_backends: pytest.skip('Setting not available for backend {}'.format(backend)) - vcs_settings_url = url( - 'repo_vcs_settings', repo_name=backend.repo.repo_name) + vcs_settings_url = route_path( + 'edit_repo_vcs', repo_name=backend.repo.repo_name) with mock.patch.dict( rhodecode.CONFIG, {'labs_settings_active': 'true'}): @@ -64,24 +65,6 @@ class TestAdminRepoVcsSettings(object): @pytest.mark.parametrize('setting_name, setting_backends', [ ('hg_use_rebase_for_merging', ['hg']), ]) - def test_labs_settings_not_visible_if_disabled( - self, setting_name, setting_backends, backend): - if backend.alias not in setting_backends: - pytest.skip('Setting not available for backend {}'.format(backend)) - - vcs_settings_url = url( - 'repo_vcs_settings', repo_name=backend.repo.repo_name) - - with mock.patch.dict( - rhodecode.CONFIG, {'labs_settings_active': 'false'}): - response = self.app.get(vcs_settings_url) - - assertr = AssertResponse(response) - assertr.no_element_exists('#rhodecode_{}'.format(setting_name)) - - @pytest.mark.parametrize('setting_name, setting_backends', [ - ('hg_use_rebase_for_merging', ['hg']), - ]) def test_update_boolean_settings( self, csrf_token, setting_name, setting_backends, backend): if backend.alias not in setting_backends: @@ -91,8 +74,8 @@ class TestAdminRepoVcsSettings(object): repo_name = repo.repo_name settings_model = SettingsModel(repo=repo) - vcs_settings_url = url( - 'repo_vcs_settings', repo_name=repo_name) + vcs_settings_url = route_path( + 'edit_repo_vcs_update', repo_name=repo_name) self.app.post( vcs_settings_url, diff --git a/rhodecode/apps/repository/views/repo_commits.py b/rhodecode/apps/repository/views/repo_commits.py --- a/rhodecode/apps/repository/views/repo_commits.py +++ b/rhodecode/apps/repository/views/repo_commits.py @@ -36,7 +36,7 @@ from rhodecode.lib.auth import ( from rhodecode.lib.compat import OrderedDict from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError import rhodecode.lib.helpers as h -from rhodecode.lib.utils2 import safe_unicode, safe_int +from rhodecode.lib.utils2 import safe_unicode from rhodecode.lib.vcs.backends.base import EmptyCommit from rhodecode.lib.vcs.exceptions import ( RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError) diff --git a/rhodecode/apps/repository/views/repo_feed.py b/rhodecode/apps/repository/views/repo_feed.py --- a/rhodecode/apps/repository/views/repo_feed.py +++ b/rhodecode/apps/repository/views/repo_feed.py @@ -29,10 +29,9 @@ from webhelpers.feedgenerator import Rss from rhodecode.apps._base import RepoAppView from rhodecode.lib import audit_logger from rhodecode.lib import helpers as h -from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator, - NotAnonymous, CSRFRequired) +from rhodecode.lib.auth import ( + LoginRequired, HasRepoPermissionAnyDecorator) from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer -from rhodecode.lib.ext_json import json from rhodecode.lib.utils2 import str2bool, safe_int from rhodecode.model.db import UserApiKeys, CacheKey diff --git a/rhodecode/apps/repository/views/repo_maintainance.py b/rhodecode/apps/repository/views/repo_maintainance.py --- a/rhodecode/apps/repository/views/repo_maintainance.py +++ b/rhodecode/apps/repository/views/repo_maintainance.py @@ -23,8 +23,7 @@ import logging from pyramid.view import view_config from rhodecode.apps._base import RepoAppView -from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator, - NotAnonymous) +from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib import repo_maintenance log = logging.getLogger(__name__) @@ -43,7 +42,7 @@ class RepoMaintenanceView(RepoAppView): @LoginRequired() @HasRepoPermissionAnyDecorator('repository.admin') @view_config( - route_name='repo_maintenance', request_method='GET', + route_name='edit_repo_maintenance', request_method='GET', renderer='rhodecode:templates/admin/repos/repo_edit.mako') def repo_maintenance(self): c = self.load_default_context() @@ -55,7 +54,7 @@ class RepoMaintenanceView(RepoAppView): @LoginRequired() @HasRepoPermissionAnyDecorator('repository.admin') @view_config( - route_name='repo_maintenance_execute', request_method='GET', + route_name='edit_repo_maintenance_execute', request_method='GET', renderer='json', xhr=True) def repo_maintenance_execute(self): c = self.load_default_context() diff --git a/rhodecode/apps/repository/views/repo_permissions.py b/rhodecode/apps/repository/views/repo_permissions.py --- a/rhodecode/apps/repository/views/repo_permissions.py +++ b/rhodecode/apps/repository/views/repo_permissions.py @@ -20,23 +20,17 @@ import logging -import deform from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config from rhodecode.apps._base import RepoAppView -from rhodecode.forms import RcForm from rhodecode.lib import helpers as h from rhodecode.lib import audit_logger from rhodecode.lib.auth import ( - LoginRequired, HasRepoPermissionAnyDecorator, - HasRepoPermissionAllDecorator, CSRFRequired) -from rhodecode.model.db import RepositoryField, RepoGroup + LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired) from rhodecode.model.forms import RepoPermsForm from rhodecode.model.meta import Session from rhodecode.model.repo import RepoModel -from rhodecode.model.scm import RepoGroupList, ScmModel -from rhodecode.model.validation_schema.schemas import repo_schema log = logging.getLogger(__name__) @@ -63,7 +57,7 @@ class RepoSettingsPermissionsView(RepoAp return self._get_template_context(c) @LoginRequired() - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') @CSRFRequired() @view_config( route_name='edit_repo_perms', request_method='POST', @@ -74,7 +68,7 @@ class RepoSettingsPermissionsView(RepoAp c.active = 'permissions' data = self.request.POST # store private flag outside of HTML to verify if we can modify - # default user permissions, prevents submition of FAKE post data + # default user permissions, prevents submission of FAKE post data # into the form for private repos data['repo_private'] = self.db_repo.private form = RepoPermsForm()().to_python(data) @@ -95,4 +89,4 @@ class RepoSettingsPermissionsView(RepoAp h.flash(_('Repository permissions updated'), category='success') raise HTTPFound( - self.request.route_path('edit_repo_perms', repo_name=self.db_repo_name)) + h.route_path('edit_repo_perms', repo_name=self.db_repo_name)) diff --git a/rhodecode/apps/repository/views/repo_settings.py b/rhodecode/apps/repository/views/repo_settings.py --- a/rhodecode/apps/repository/views/repo_settings.py +++ b/rhodecode/apps/repository/views/repo_settings.py @@ -29,9 +29,8 @@ from rhodecode.forms import RcForm from rhodecode.lib import helpers as h from rhodecode.lib import audit_logger from rhodecode.lib.auth import ( - LoginRequired, HasRepoPermissionAnyDecorator, - HasRepoPermissionAllDecorator, CSRFRequired) -from rhodecode.model.db import RepositoryField, RepoGroup + LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired) +from rhodecode.model.db import RepositoryField, RepoGroup, Repository from rhodecode.model.meta import Session from rhodecode.model.repo import RepoModel from rhodecode.model.scm import RepoGroupList, ScmModel @@ -109,7 +108,7 @@ class RepoSettingsView(RepoAppView): return self._get_template_context(c) @LoginRequired() - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') @CSRFRequired() @view_config( route_name='edit_repo', request_method='POST', @@ -176,4 +175,80 @@ class RepoSettingsView(RepoAppView): old_repo_name), category='error') raise HTTPFound( - self.request.route_path('edit_repo', repo_name=new_repo_name)) + h.route_path('edit_repo', repo_name=new_repo_name)) + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') + @view_config( + route_name='repo_edit_toggle_locking', request_method='GET', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def toggle_locking(self): + """ + Toggle locking of repository by simple GET call to url + """ + _ = self.request.translate + repo = self.db_repo + + try: + if repo.enable_locking: + if repo.locked[0]: + Repository.unlock(repo) + action = _('Unlocked') + else: + Repository.lock( + repo, self._rhodecode_user.user_id, + lock_reason=Repository.LOCK_WEB) + action = _('Locked') + + h.flash(_('Repository has been %s') % action, + category='success') + except Exception: + log.exception("Exception during unlocking") + h.flash(_('An error occurred during unlocking'), + category='error') + raise HTTPFound( + h.route_path('repo_summary', repo_name=self.db_repo_name)) + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @view_config( + route_name='edit_repo_statistics', request_method='GET', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def edit_statistics_form(self): + c = self.load_default_context() + + if self.db_repo.stats: + # this is on what revision we ended up so we add +1 for count + last_rev = self.db_repo.stats.stat_on_revision + 1 + else: + last_rev = 0 + + c.active = 'statistics' + c.stats_revision = last_rev + c.repo_last_rev = self.rhodecode_vcs_repo.count() + + if last_rev == 0 or c.repo_last_rev == 0: + c.stats_percentage = 0 + else: + c.stats_percentage = '%.2f' % ( + (float((last_rev)) / c.repo_last_rev) * 100) + return self._get_template_context(c) + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_statistics_reset', request_method='POST', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def repo_statistics_reset(self): + _ = self.request.translate + + try: + RepoModel().delete_stats(self.db_repo_name) + Session().commit() + except Exception: + log.exception('Edit statistics failure') + h.flash(_('An error occurred during deletion of repository stats'), + category='error') + raise HTTPFound( + h.route_path('edit_repo_statistics', repo_name=self.db_repo_name)) diff --git a/rhodecode/apps/repository/views/repo_settings_advanced.py b/rhodecode/apps/repository/views/repo_settings_advanced.py --- a/rhodecode/apps/repository/views/repo_settings_advanced.py +++ b/rhodecode/apps/repository/views/repo_settings_advanced.py @@ -61,7 +61,7 @@ class RepoSettingsView(RepoAppView): c.default_user_id = User.get_default_user().user_id c.in_public_journal = UserFollowing.query() \ .filter(UserFollowing.user_id == c.default_user_id) \ - .filter(UserFollowing.follows_repository == c.repo_info).scalar() + .filter(UserFollowing.follows_repository == self.db_repo).scalar() c.has_origin_repo_read_perm = False if self.db_repo.fork: diff --git a/rhodecode/apps/repository/views/repo_settings_fields.py b/rhodecode/apps/repository/views/repo_settings_fields.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repository/views/repo_settings_fields.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2017-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 formencode + +from pyramid.httpexceptions import HTTPFound +from pyramid.view import view_config + +from rhodecode.apps._base import RepoAppView +from rhodecode.lib import audit_logger +from rhodecode.lib import helpers as h +from rhodecode.lib.auth import ( + LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired) +from rhodecode.model.db import RepositoryField +from rhodecode.model.forms import RepoFieldForm +from rhodecode.model.meta import Session +from rhodecode.model.repo import RepoModel + +log = logging.getLogger(__name__) + + +class RepoSettingsFieldsView(RepoAppView): + 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 + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @view_config( + route_name='edit_repo_fields', request_method='GET', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def repo_field_edit(self): + c = self.load_default_context() + + c.active = 'fields' + c.repo_fields = RepositoryField.query() \ + .filter(RepositoryField.repository == self.db_repo).all() + + return self._get_template_context(c) + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_fields_create', request_method='POST', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def repo_field_create(self): + _ = self.request.translate + + try: + form_result = RepoFieldForm()().to_python(dict(self.request.POST)) + RepoModel().add_repo_field( + self.db_repo_name, + form_result['new_field_key'], + field_type=form_result['new_field_type'], + field_value=form_result['new_field_value'], + field_label=form_result['new_field_label'], + field_desc=form_result['new_field_desc']) + + Session().commit() + except Exception as e: + log.exception("Exception creating field") + msg = _('An error occurred during creation of field') + if isinstance(e, formencode.Invalid): + msg += ". " + e.msg + h.flash(msg, category='error') + + raise HTTPFound( + h.route_path('edit_repo_fields', repo_name=self.db_repo_name)) + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_fields_delete', request_method='POST', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def repo_field_delete(self): + _ = self.request.translate + field = RepositoryField.get_or_404(self.request.matchdict['field_id']) + try: + RepoModel().delete_repo_field(self.db_repo_name, field.field_key) + Session().commit() + except Exception: + log.exception('Exception during removal of field') + msg = _('An error occurred during removal of field') + h.flash(msg, category='error') + + raise HTTPFound( + h.route_path('edit_repo_fields', repo_name=self.db_repo_name)) diff --git a/rhodecode/apps/repository/views/repo_settings_issue_trackers.py b/rhodecode/apps/repository/views/repo_settings_issue_trackers.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repository/views/repo_settings_issue_trackers.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2017-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 HTTPFound +from pyramid.view import view_config + +from rhodecode.apps._base import RepoAppView +from rhodecode.lib import audit_logger +from rhodecode.lib import helpers as h +from rhodecode.lib.auth import ( + LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired) +from rhodecode.model.forms import IssueTrackerPatternsForm +from rhodecode.model.meta import Session +from rhodecode.model.settings import IssueTrackerSettingsModel + +log = logging.getLogger(__name__) + + +class RepoSettingsIssueTrackersView(RepoAppView): + 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 + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @view_config( + route_name='edit_repo_issuetracker', request_method='GET', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def repo_issuetracker(self): + c = self.load_default_context() + c.active = 'issuetracker' + c.data = 'data' + + c.settings_model = IssueTrackerSettingsModel(repo=self.db_repo) + c.global_patterns = c.settings_model.get_global_settings() + c.repo_patterns = c.settings_model.get_repo_settings() + + return self._get_template_context(c) + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_issuetracker_test', request_method='POST', + xhr=True, renderer='string') + def repo_issuetracker_test(self): + return h.urlify_commit_message( + self.request.POST.get('test_text', ''), + self.db_repo_name) + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_issuetracker_delete', request_method='POST', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def repo_issuetracker_delete(self): + _ = self.request.translate + uid = self.request.POST.get('uid') + repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name) + try: + repo_settings.delete_entries(uid) + except Exception: + h.flash(_('Error occurred during deleting issue tracker entry'), + category='error') + else: + h.flash(_('Removed issue tracker entry'), category='success') + raise HTTPFound( + h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name)) + + def _update_patterns(self, form, repo_settings): + for uid in form['delete_patterns']: + repo_settings.delete_entries(uid) + + for pattern_data in form['patterns']: + for setting_key, pattern, type_ in pattern_data: + sett = repo_settings.create_or_update_setting( + setting_key, pattern.strip(), type_) + Session().add(sett) + + Session().commit() + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_issuetracker_update', request_method='POST', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def repo_issuetracker_update(self): + _ = self.request.translate + # Save inheritance + repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name) + inherited = ( + self.request.POST.get('inherit_global_issuetracker') == "inherited") + repo_settings.inherit_global_settings = inherited + Session().commit() + + form = IssueTrackerPatternsForm()().to_python(self.request.POST) + if form: + self._update_patterns(form, repo_settings) + + h.flash(_('Updated issue tracker entries'), category='success') + raise HTTPFound( + h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name)) + diff --git a/rhodecode/apps/repository/views/repo_settings_remote.py b/rhodecode/apps/repository/views/repo_settings_remote.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repository/views/repo_settings_remote.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2017-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 HTTPFound +from pyramid.view import view_config + +from rhodecode.apps._base import RepoAppView +from rhodecode.lib import helpers as h +from rhodecode.lib.auth import ( + LoginRequired, CSRFRequired, HasRepoPermissionAnyDecorator) +from rhodecode.model.scm import ScmModel + +log = logging.getLogger(__name__) + + +class RepoSettingsRemoteView(RepoAppView): + 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 + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @view_config( + route_name='edit_repo_remote', request_method='GET', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def repo_remote_edit_form(self): + c = self.load_default_context() + c.active = 'remote' + + return self._get_template_context(c) + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_remote_pull', request_method='POST', + renderer=None) + def repo_remote_pull_changes(self): + _ = self.request.translate + self.load_default_context() + + try: + ScmModel().pull_changes( + self.db_repo_name, self._rhodecode_user.username) + h.flash(_('Pulled from remote location'), category='success') + except Exception: + log.exception("Exception during pull from remote") + h.flash(_('An error occurred during pull from remote location'), + category='error') + raise HTTPFound( + h.route_path('edit_repo_remote', repo_name=self.db_repo_name)) diff --git a/rhodecode/apps/repository/views/repo_settings_vcs.py b/rhodecode/apps/repository/views/repo_settings_vcs.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repository/views/repo_settings_vcs.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2017-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 formencode +from pyramid.httpexceptions import HTTPFound, HTTPBadRequest +from pyramid.response import Response +from pyramid.renderers import render +from pyramid.view import view_config + +from rhodecode.apps._base import RepoAppView +from rhodecode.lib import audit_logger +from rhodecode.lib import helpers as h +from rhodecode.lib.auth import ( + LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired) +from rhodecode.model.forms import RepoVcsSettingsForm +from rhodecode.model.meta import Session +from rhodecode.model.settings import VcsSettingsModel, SettingNotFound + +log = logging.getLogger(__name__) + + +class RepoSettingsVcsView(RepoAppView): + 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 _vcs_form_defaults(self, repo_name): + model = VcsSettingsModel(repo=repo_name) + global_defaults = model.get_global_settings() + + repo_defaults = {} + repo_defaults.update(global_defaults) + repo_defaults.update(model.get_repo_settings()) + + global_defaults = { + '{}_inherited'.format(k): global_defaults[k] + for k in global_defaults} + + defaults = { + 'inherit_global_settings': model.inherit_global_settings + } + defaults.update(global_defaults) + defaults.update(repo_defaults) + defaults.update({ + 'new_svn_branch': '', + 'new_svn_tag': '', + }) + return defaults + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @view_config( + route_name='edit_repo_vcs', request_method='GET', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def repo_vcs_settings(self): + c = self.load_default_context() + model = VcsSettingsModel(repo=self.db_repo_name) + + c.active = 'vcs' + c.global_svn_branch_patterns = model.get_global_svn_branch_patterns() + c.global_svn_tag_patterns = model.get_global_svn_tag_patterns() + c.svn_branch_patterns = model.get_repo_svn_branch_patterns() + c.svn_tag_patterns = model.get_repo_svn_tag_patterns() + + defaults = self._vcs_form_defaults(self.db_repo_name) + c.inherit_global_settings = defaults['inherit_global_settings'] + + data = render('rhodecode:templates/admin/repos/repo_edit.mako', + self._get_template_context(c), self.request) + html = formencode.htmlfill.render( + data, + defaults=defaults, + encoding="UTF-8", + force_defaults=False + ) + return Response(html) + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_vcs_update', request_method='POST', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') + def repo_settings_vcs_update(self): + _ = self.request.translate + c = self.load_default_context() + c.active = 'vcs' + + model = VcsSettingsModel(repo=self.db_repo_name) + c.global_svn_branch_patterns = model.get_global_svn_branch_patterns() + c.global_svn_tag_patterns = model.get_global_svn_tag_patterns() + c.svn_branch_patterns = model.get_repo_svn_branch_patterns() + c.svn_tag_patterns = model.get_repo_svn_tag_patterns() + + defaults = self._vcs_form_defaults(self.db_repo_name) + c.inherit_global_settings = defaults['inherit_global_settings'] + + application_form = RepoVcsSettingsForm(self.db_repo_name)() + try: + form_result = application_form.to_python(dict(self.request.POST)) + except formencode.Invalid as errors: + h.flash(_("Some form inputs contain invalid data."), + category='error') + + data = render('rhodecode:templates/admin/repos/repo_edit.mako', + self._get_template_context(c), self.request) + html = formencode.htmlfill.render( + data, + defaults=errors.value, + errors=errors.error_dict or {}, + encoding="UTF-8", + force_defaults=False + ) + return Response(html) + + try: + inherit_global_settings = form_result['inherit_global_settings'] + model.create_or_update_repo_settings( + form_result, inherit_global_settings=inherit_global_settings) + Session().commit() + h.flash(_('Updated VCS settings'), category='success') + except Exception: + log.exception("Exception while updating settings") + h.flash( + _('Error occurred during updating repository VCS settings'), + category='error') + + raise HTTPFound( + h.route_path('edit_repo_vcs', repo_name=self.db_repo_name)) + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.admin') + @CSRFRequired() + @view_config( + route_name='edit_repo_vcs_svn_pattern_delete', request_method='POST', + renderer='json_ext', xhr=True) + def repo_settings_delete_svn_pattern(self): + self.load_default_context() + delete_pattern_id = self.request.POST.get('delete_svn_pattern') + model = VcsSettingsModel(repo=self.db_repo_name) + try: + model.delete_repo_svn_pattern(delete_pattern_id) + except SettingNotFound: + log.exception('Failed to delete SVN pattern') + raise HTTPBadRequest() + + Session().commit() + return True diff --git a/rhodecode/apps/repository/views/repo_strip.py b/rhodecode/apps/repository/views/repo_strip.py --- a/rhodecode/apps/repository/views/repo_strip.py +++ b/rhodecode/apps/repository/views/repo_strip.py @@ -24,8 +24,8 @@ from pyramid.view import view_config from rhodecode.apps._base import RepoAppView from rhodecode.lib import audit_logger from rhodecode.lib import helpers as h -from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator, - NotAnonymous, CSRFRequired) +from rhodecode.lib.auth import ( + LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired) from rhodecode.lib.ext_json import json log = logging.getLogger(__name__) @@ -44,7 +44,7 @@ class StripView(RepoAppView): @LoginRequired() @HasRepoPermissionAnyDecorator('repository.admin') @view_config( - route_name='strip', request_method='GET', + route_name='edit_repo_strip', request_method='GET', renderer='rhodecode:templates/admin/repos/repo_edit.mako') def strip(self): c = self.load_default_context() @@ -99,10 +99,10 @@ class StripView(RepoAppView): continue try: ScmModel().strip( - repo=c.repo_info, + repo=self.db_repo, commit_id=commit['rev'], branch=commit['branch']) log.info('Stripped commit %s from repo `%s` by %s' % ( - commit['rev'], c.repo_info.repo_name, user)) + commit['rev'], self.db_repo_name, user)) data[commit['rev']] = True audit_logger.store_web( diff --git a/rhodecode/apps/repository/views/repo_summary.py b/rhodecode/apps/repository/views/repo_summary.py --- a/rhodecode/apps/repository/views/repo_summary.py +++ b/rhodecode/apps/repository/views/repo_summary.py @@ -22,12 +22,9 @@ 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 @@ -74,11 +71,16 @@ class RepoSummaryView(RepoAppView): log.debug("Searching for a README file.") readme_node = ReadmeFinder(default_renderer).search(commit) if readme_node: - relative_url = h.route_path( - 'repo_file_raw', repo_name=repo_name, - commit_id=commit.raw_id, f_path=readme_node.path) + relative_urls = { + 'raw': h.route_path( + 'repo_file_raw', repo_name=repo_name, + commit_id=commit.raw_id, f_path=readme_node.path), + 'standard': h.route_path( + 'repo_files', repo_name=repo_name, + commit_id=commit.raw_id, f_path=readme_node.path), + } readme_data = self._render_readme_or_none( - commit, readme_node, relative_url) + commit, readme_node, relative_urls) readme_filename = readme_node.path return readme_data, readme_filename @@ -103,15 +105,15 @@ class RepoSummaryView(RepoAppView): log.exception( "Problem getting commit when trying to render the README.") - def _render_readme_or_none(self, commit, readme_node, relative_url): + def _render_readme_or_none(self, commit, readme_node, relative_urls): 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) + if relative_urls: + return relative_links(html_source, relative_urls) return html_source except Exception: log.exception( diff --git a/rhodecode/config/environment.py b/rhodecode/config/environment.py --- a/rhodecode/config/environment.py +++ b/rhodecode/config/environment.py @@ -143,6 +143,8 @@ def load_pyramid_environment(global_conf # Store the settings to make them available to other modules. rhodecode.PYRAMID_SETTINGS = settings_merged + # NOTE(marcink): needs to be enabled after full port to pyramid + # rhodecode.CONFIG = config # If this is a test run we prepare the test environment like # creating a test database, test search index and test repositories. diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -176,23 +176,6 @@ def make_map(config): rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping') rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test') - # ADMIN REPOSITORY ROUTES - with rmap.submapper(path_prefix=ADMIN_PREFIX, - controller='admin/repos') as m: - m.connect('repos', '/repos', - action='create', conditions={'method': ['POST']}) - m.connect('repos', '/repos', - action='index', conditions={'method': ['GET']}) - m.connect('new_repo', '/create_repository', jsroute=True, - action='create_repository', conditions={'method': ['GET']}) - m.connect('delete_repo', '/repos/{repo_name}', - action='delete', conditions={'method': ['DELETE']}, - requirements=URL_NAME_REQUIREMENTS) - m.connect('repo', '/repos/{repo_name}', - action='show', conditions={'method': ['GET'], - 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - # ADMIN REPOSITORY GROUPS ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/repo_groups') as m: @@ -406,81 +389,5 @@ def make_map(config): m.connect('my_account_password', '/my_account/password', action='my_account_password', conditions={'method': ['GET']}) - #========================================================================== - # REPOSITORY ROUTES - #========================================================================== - - # repo edit options - rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields', - controller='admin/repos', action='edit_fields', - conditions={'method': ['GET'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new', - controller='admin/repos', action='create_repo_field', - conditions={'method': ['PUT'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}', - controller='admin/repos', action='delete_repo_field', - conditions={'method': ['DELETE'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - - rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle', - controller='admin/repos', action='toggle_locking', - conditions={'method': ['GET'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - - rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote', - controller='admin/repos', action='edit_remote_form', - conditions={'method': ['GET'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote', - controller='admin/repos', action='edit_remote', - conditions={'method': ['PUT'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - - rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics', - controller='admin/repos', action='edit_statistics_form', - conditions={'method': ['GET'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics', - controller='admin/repos', action='edit_statistics', - conditions={'method': ['PUT'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('repo_settings_issuetracker', - '/{repo_name}/settings/issue-tracker', - controller='admin/repos', action='repo_issuetracker', - conditions={'method': ['GET'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('repo_issuetracker_test', - '/{repo_name}/settings/issue-tracker/test', - controller='admin/repos', action='repo_issuetracker_test', - conditions={'method': ['POST'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('repo_issuetracker_delete', - '/{repo_name}/settings/issue-tracker/delete', - controller='admin/repos', action='repo_issuetracker_delete', - conditions={'method': ['DELETE'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('repo_issuetracker_save', - '/{repo_name}/settings/issue-tracker/save', - controller='admin/repos', action='repo_issuetracker_save', - conditions={'method': ['POST'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', - controller='admin/repos', action='repo_settings_vcs_update', - conditions={'method': ['POST'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', - controller='admin/repos', action='repo_settings_vcs', - conditions={'method': ['GET'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs', - controller='admin/repos', action='repo_delete_svn_pattern', - conditions={'method': ['DELETE'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest', - controller='admin/repos', action='repo_settings_pullrequest', - conditions={'method': ['GET', 'POST'], 'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) return rmap diff --git a/rhodecode/controllers/admin/repos.py b/rhodecode/controllers/admin/repos.py deleted file mode 100644 --- a/rhodecode/controllers/admin/repos.py +++ /dev/null @@ -1,563 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2013-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/ - - -""" -Repositories controller for RhodeCode -""" - -import logging -import traceback - -import formencode -from formencode import htmlfill -from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect -from pylons.i18n.translation import _ -from webob.exc import HTTPForbidden, HTTPBadRequest - -from pyramid.httpexceptions import HTTPFound -import rhodecode -from rhodecode.lib import auth, helpers as h -from rhodecode.lib.auth import ( - LoginRequired, HasPermissionAllDecorator, - HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny, - HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator) -from rhodecode.lib.base import BaseRepoController, render -from rhodecode.lib.ext_json import json -from rhodecode.lib.utils import repo_name_slug, jsonify -from rhodecode.lib.utils2 import safe_int, str2bool -from rhodecode.model.db import (Repository, RepoGroup, RepositoryField) -from rhodecode.model.forms import ( - RepoForm, RepoFieldForm, RepoVcsSettingsForm, IssueTrackerPatternsForm) -from rhodecode.model.meta import Session -from rhodecode.model.repo import RepoModel -from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList -from rhodecode.model.settings import ( - SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel, - SettingNotFound) - -log = logging.getLogger(__name__) - - -class ReposController(BaseRepoController): - """ - REST Controller styled on the Atom Publishing Protocol""" - # To properly map this controller, ensure your config/routing.py - # file has a resource setup: - # map.resource('repo', 'repos') - - @LoginRequired() - def __before__(self): - super(ReposController, self).__before__() - - def _load_repo(self, repo_name): - repo_obj = Repository.get_by_repo_name(repo_name) - - if repo_obj is None: - h.not_mapped_error(repo_name) - return redirect(url('repos')) - - return repo_obj - - def __load_defaults(self, repo=None): - acl_groups = RepoGroupList(RepoGroup.query().all(), - perm_set=['group.write', 'group.admin']) - c.repo_groups = RepoGroup.groups_choices(groups=acl_groups) - c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) - - # in case someone no longer have a group.write access to a repository - # pre fill the list with this entry, we don't care if this is the same - # but it will allow saving repo data properly. - - repo_group = None - if repo: - repo_group = repo.group - if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices: - c.repo_groups_choices.append(unicode(repo_group.group_id)) - c.repo_groups.append(RepoGroup._generate_choice(repo_group)) - - choices, c.landing_revs = ScmModel().get_repo_landing_revs() - c.landing_revs_choices = choices - - def __load_data(self, repo_name=None): - """ - Load defaults settings for edit, and update - - :param repo_name: - """ - c.repo_info = self._load_repo(repo_name) - self.__load_defaults(c.repo_info) - - # override defaults for exact repo info here git/hg etc - if not c.repository_requirements_missing: - choices, c.landing_revs = ScmModel().get_repo_landing_revs( - c.repo_info) - c.landing_revs_choices = choices - defaults = RepoModel()._get_defaults(repo_name) - - return defaults - - def _log_creation_exception(self, e, repo_name): - reason = None - if len(e.args) == 2: - reason = e.args[1] - - if reason == 'INVALID_CERTIFICATE': - log.exception( - 'Exception creating a repository: invalid certificate') - msg = (_('Error creating repository %s: invalid certificate') - % repo_name) - else: - log.exception("Exception creating a repository") - msg = (_('Error creating repository %s') - % repo_name) - - return msg - - @NotAnonymous() - def index(self, format='html'): - """GET /repos: All items in the collection""" - # url('repos') - - repo_list = Repository.get_all_repos() - c.repo_list = RepoList(repo_list, perm_set=['repository.admin']) - repos_data = RepoModel().get_repos_as_dict( - repo_list=c.repo_list, admin=True, super_user_actions=True) - # json used to render the grid - c.data = json.dumps(repos_data) - - return render('admin/repos/repos.mako') - - # perms check inside - @NotAnonymous() - @auth.CSRFRequired() - def create(self): - """ - POST /repos: Create a new item""" - # url('repos') - - self.__load_defaults() - form_result = {} - task_id = None - c.personal_repo_group = c.rhodecode_user.personal_repo_group - try: - # CanWriteToGroup validators checks permissions of this POST - form_result = RepoForm(repo_groups=c.repo_groups_choices, - landing_revs=c.landing_revs_choices)()\ - .to_python(dict(request.POST)) - - # create is done sometimes async on celery, db transaction - # management is handled there. - task = RepoModel().create(form_result, c.rhodecode_user.user_id) - from celery.result import BaseAsyncResult - if isinstance(task, BaseAsyncResult): - task_id = task.task_id - except formencode.Invalid as errors: - return htmlfill.render( - render('admin/repos/repo_add.mako'), - defaults=errors.value, - errors=errors.error_dict or {}, - prefix_error=False, - encoding="UTF-8", - force_defaults=False) - - except Exception as e: - msg = self._log_creation_exception(e, form_result.get('repo_name')) - h.flash(msg, category='error') - return redirect(h.route_path('home')) - - raise HTTPFound( - h.route_path('repo_creating', - repo_name=form_result['repo_name_full'], - _query=dict(task_id=task_id))) - - # perms check inside - @NotAnonymous() - def create_repository(self): - """GET /_admin/create_repository: Form to create a new item""" - new_repo = request.GET.get('repo', '') - parent_group = safe_int(request.GET.get('parent_group')) - _gr = RepoGroup.get(parent_group) - - if not HasPermissionAny('hg.admin', 'hg.create.repository')(): - # you're not super admin nor have global create permissions, - # but maybe you have at least write permission to a parent group ? - - gr_name = _gr.group_name if _gr else None - # create repositories with write permission on group is set to true - create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')() - group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name) - group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name) - if not (group_admin or (group_write and create_on_write)): - raise HTTPForbidden - - acl_groups = RepoGroupList(RepoGroup.query().all(), - perm_set=['group.write', 'group.admin']) - c.repo_groups = RepoGroup.groups_choices(groups=acl_groups) - c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) - choices, c.landing_revs = ScmModel().get_repo_landing_revs() - c.personal_repo_group = c.rhodecode_user.personal_repo_group - c.new_repo = repo_name_slug(new_repo) - - # apply the defaults from defaults page - defaults = SettingsModel().get_default_repo_settings(strip_prefix=True) - # set checkbox to autochecked - defaults['repo_copy_permissions'] = True - - parent_group_choice = '-1' - if not c.rhodecode_user.is_admin and c.rhodecode_user.personal_repo_group: - parent_group_choice = c.rhodecode_user.personal_repo_group - - if parent_group and _gr: - if parent_group in [x[0] for x in c.repo_groups]: - parent_group_choice = unicode(parent_group) - - defaults.update({'repo_group': parent_group_choice}) - - return htmlfill.render( - render('admin/repos/repo_add.mako'), - defaults=defaults, - errors={}, - prefix_error=False, - encoding="UTF-8", - force_defaults=False - ) - - @HasPermissionAllDecorator('hg.admin') - def show(self, repo_name, format='html'): - """GET /repos/repo_name: Show a specific item""" - # url('repo', repo_name=ID) - - @HasRepoPermissionAllDecorator('repository.admin') - def edit_fields(self, repo_name): - """GET /repo_name/settings: Form to edit an existing item""" - c.repo_info = self._load_repo(repo_name) - c.repo_fields = RepositoryField.query()\ - .filter(RepositoryField.repository == c.repo_info).all() - c.active = 'fields' - if request.POST: - - return redirect(url('repo_edit_fields')) - return render('admin/repos/repo_edit.mako') - - @HasRepoPermissionAllDecorator('repository.admin') - @auth.CSRFRequired() - def create_repo_field(self, repo_name): - try: - form_result = RepoFieldForm()().to_python(dict(request.POST)) - RepoModel().add_repo_field( - repo_name, form_result['new_field_key'], - field_type=form_result['new_field_type'], - field_value=form_result['new_field_value'], - field_label=form_result['new_field_label'], - field_desc=form_result['new_field_desc']) - - Session().commit() - except Exception as e: - log.exception("Exception creating field") - msg = _('An error occurred during creation of field') - if isinstance(e, formencode.Invalid): - msg += ". " + e.msg - h.flash(msg, category='error') - return redirect(url('edit_repo_fields', repo_name=repo_name)) - - @HasRepoPermissionAllDecorator('repository.admin') - @auth.CSRFRequired() - def delete_repo_field(self, repo_name, field_id): - field = RepositoryField.get_or_404(field_id) - try: - RepoModel().delete_repo_field(repo_name, field.field_key) - Session().commit() - except Exception as e: - log.exception("Exception during removal of field") - msg = _('An error occurred during removal of field') - h.flash(msg, category='error') - return redirect(url('edit_repo_fields', repo_name=repo_name)) - - @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') - @auth.CSRFRequired() - def toggle_locking(self, repo_name): - """ - Toggle locking of repository by simple GET call to url - - :param repo_name: - """ - - try: - repo = Repository.get_by_repo_name(repo_name) - - if repo.enable_locking: - if repo.locked[0]: - Repository.unlock(repo) - action = _('Unlocked') - else: - Repository.lock(repo, c.rhodecode_user.user_id, - lock_reason=Repository.LOCK_WEB) - action = _('Locked') - - h.flash(_('Repository has been %s') % action, - category='success') - except Exception: - log.exception("Exception during unlocking") - h.flash(_('An error occurred during unlocking'), - category='error') - return redirect(h.route_path('repo_summary', repo_name=repo_name)) - - @HasRepoPermissionAllDecorator('repository.admin') - @auth.CSRFRequired() - def edit_remote(self, repo_name): - """PUT /{repo_name}/settings/remote: edit the repo remote.""" - try: - ScmModel().pull_changes(repo_name, c.rhodecode_user.username) - h.flash(_('Pulled from remote location'), category='success') - except Exception: - log.exception("Exception during pull from remote") - h.flash(_('An error occurred during pull from remote location'), - category='error') - return redirect(url('edit_repo_remote', repo_name=c.repo_name)) - - @HasRepoPermissionAllDecorator('repository.admin') - def edit_remote_form(self, repo_name): - """GET /repo_name/settings: Form to edit an existing item""" - c.repo_info = self._load_repo(repo_name) - c.active = 'remote' - - return render('admin/repos/repo_edit.mako') - - @HasRepoPermissionAllDecorator('repository.admin') - @auth.CSRFRequired() - def edit_statistics(self, repo_name): - """PUT /{repo_name}/settings/statistics: reset the repo statistics.""" - try: - RepoModel().delete_stats(repo_name) - Session().commit() - except Exception as e: - log.error(traceback.format_exc()) - h.flash(_('An error occurred during deletion of repository stats'), - category='error') - return redirect(url('edit_repo_statistics', repo_name=c.repo_name)) - - @HasRepoPermissionAllDecorator('repository.admin') - def edit_statistics_form(self, repo_name): - """GET /repo_name/settings: Form to edit an existing item""" - c.repo_info = self._load_repo(repo_name) - repo = c.repo_info.scm_instance() - - if c.repo_info.stats: - # this is on what revision we ended up so we add +1 for count - last_rev = c.repo_info.stats.stat_on_revision + 1 - else: - last_rev = 0 - c.stats_revision = last_rev - - c.repo_last_rev = repo.count() - - if last_rev == 0 or c.repo_last_rev == 0: - c.stats_percentage = 0 - else: - c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100) - - c.active = 'statistics' - - return render('admin/repos/repo_edit.mako') - - @HasRepoPermissionAllDecorator('repository.admin') - @auth.CSRFRequired() - def repo_issuetracker_test(self, repo_name): - if request.is_xhr: - return h.urlify_commit_message( - request.POST.get('test_text', ''), - repo_name) - else: - raise HTTPBadRequest() - - @HasRepoPermissionAllDecorator('repository.admin') - @auth.CSRFRequired() - def repo_issuetracker_delete(self, repo_name): - uid = request.POST.get('uid') - repo_settings = IssueTrackerSettingsModel(repo=repo_name) - try: - repo_settings.delete_entries(uid) - except Exception: - h.flash(_('Error occurred during deleting issue tracker entry'), - category='error') - else: - h.flash(_('Removed issue tracker entry'), category='success') - return redirect(url('repo_settings_issuetracker', - repo_name=repo_name)) - - def _update_patterns(self, form, repo_settings): - for uid in form['delete_patterns']: - repo_settings.delete_entries(uid) - - for pattern in form['patterns']: - for setting, value, type_ in pattern: - sett = repo_settings.create_or_update_setting( - setting, value, type_) - Session().add(sett) - - Session().commit() - - @HasRepoPermissionAllDecorator('repository.admin') - @auth.CSRFRequired() - def repo_issuetracker_save(self, repo_name): - # Save inheritance - repo_settings = IssueTrackerSettingsModel(repo=repo_name) - inherited = (request.POST.get('inherit_global_issuetracker') - == "inherited") - repo_settings.inherit_global_settings = inherited - Session().commit() - - form = IssueTrackerPatternsForm()().to_python(request.POST) - if form: - self._update_patterns(form, repo_settings) - - h.flash(_('Updated issue tracker entries'), category='success') - return redirect(url('repo_settings_issuetracker', - repo_name=repo_name)) - - @HasRepoPermissionAllDecorator('repository.admin') - def repo_issuetracker(self, repo_name): - """GET /admin/settings/issue-tracker: All items in the collection""" - c.active = 'issuetracker' - c.data = 'data' - c.repo_info = self._load_repo(repo_name) - - repo = Repository.get_by_repo_name(repo_name) - c.settings_model = IssueTrackerSettingsModel(repo=repo) - c.global_patterns = c.settings_model.get_global_settings() - c.repo_patterns = c.settings_model.get_repo_settings() - - return render('admin/repos/repo_edit.mako') - - @HasRepoPermissionAllDecorator('repository.admin') - def repo_settings_vcs(self, repo_name): - """GET /{repo_name}/settings/vcs/: All items in the collection""" - - model = VcsSettingsModel(repo=repo_name) - - c.active = 'vcs' - c.global_svn_branch_patterns = model.get_global_svn_branch_patterns() - c.global_svn_tag_patterns = model.get_global_svn_tag_patterns() - c.svn_branch_patterns = model.get_repo_svn_branch_patterns() - c.svn_tag_patterns = model.get_repo_svn_tag_patterns() - c.repo_info = self._load_repo(repo_name) - defaults = self._vcs_form_defaults(repo_name) - c.inherit_global_settings = defaults['inherit_global_settings'] - c.labs_active = str2bool( - rhodecode.CONFIG.get('labs_settings_active', 'true')) - - return htmlfill.render( - render('admin/repos/repo_edit.mako'), - defaults=defaults, - encoding="UTF-8", - force_defaults=False) - - @HasRepoPermissionAllDecorator('repository.admin') - @auth.CSRFRequired() - def repo_settings_vcs_update(self, repo_name): - """POST /{repo_name}/settings/vcs/: All items in the collection""" - c.active = 'vcs' - - model = VcsSettingsModel(repo=repo_name) - c.global_svn_branch_patterns = model.get_global_svn_branch_patterns() - c.global_svn_tag_patterns = model.get_global_svn_tag_patterns() - c.svn_branch_patterns = model.get_repo_svn_branch_patterns() - c.svn_tag_patterns = model.get_repo_svn_tag_patterns() - c.repo_info = self._load_repo(repo_name) - defaults = self._vcs_form_defaults(repo_name) - c.inherit_global_settings = defaults['inherit_global_settings'] - - application_form = RepoVcsSettingsForm(repo_name)() - try: - form_result = application_form.to_python(dict(request.POST)) - except formencode.Invalid as errors: - h.flash( - _("Some form inputs contain invalid data."), - category='error') - return htmlfill.render( - render('admin/repos/repo_edit.mako'), - defaults=errors.value, - errors=errors.error_dict or {}, - prefix_error=False, - encoding="UTF-8", - force_defaults=False - ) - - try: - inherit_global_settings = form_result['inherit_global_settings'] - model.create_or_update_repo_settings( - form_result, inherit_global_settings=inherit_global_settings) - except Exception: - log.exception("Exception while updating settings") - h.flash( - _('Error occurred during updating repository VCS settings'), - category='error') - else: - Session().commit() - h.flash(_('Updated VCS settings'), category='success') - return redirect(url('repo_vcs_settings', repo_name=repo_name)) - - return htmlfill.render( - render('admin/repos/repo_edit.mako'), - defaults=self._vcs_form_defaults(repo_name), - encoding="UTF-8", - force_defaults=False) - - @HasRepoPermissionAllDecorator('repository.admin') - @auth.CSRFRequired() - @jsonify - def repo_delete_svn_pattern(self, repo_name): - if not request.is_xhr: - return False - - delete_pattern_id = request.POST.get('delete_svn_pattern') - model = VcsSettingsModel(repo=repo_name) - try: - model.delete_repo_svn_pattern(delete_pattern_id) - except SettingNotFound: - raise HTTPBadRequest() - - Session().commit() - return True - - def _vcs_form_defaults(self, repo_name): - model = VcsSettingsModel(repo=repo_name) - global_defaults = model.get_global_settings() - - repo_defaults = {} - repo_defaults.update(global_defaults) - repo_defaults.update(model.get_repo_settings()) - - global_defaults = { - '{}_inherited'.format(k): global_defaults[k] - for k in global_defaults} - - defaults = { - 'inherit_global_settings': model.inherit_global_settings - } - defaults.update(global_defaults) - defaults.update(repo_defaults) - defaults.update({ - 'new_svn_branch': '', - 'new_svn_tag': '', - }) - return defaults diff --git a/rhodecode/controllers/admin/settings.py b/rhodecode/controllers/admin/settings.py --- a/rhodecode/controllers/admin/settings.py +++ b/rhodecode/controllers/admin/settings.py @@ -32,6 +32,7 @@ from formencode import htmlfill from pylons import request, tmpl_context as c, url, config from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from pylons.decorators import jsonify from pyramid.threadlocal import get_current_registry from webob.exc import HTTPBadRequest @@ -47,7 +48,6 @@ from rhodecode.lib.utils import repo2db_ from rhodecode.lib.utils2 import ( str2bool, safe_unicode, AttributeDict, safe_int) from rhodecode.lib.compat import OrderedDict -from rhodecode.lib.utils import jsonify from rhodecode.model.db import RhodeCodeUi, Repository from rhodecode.model.forms import ApplicationSettingsForm, \ diff --git a/rhodecode/controllers/admin/user_groups.py b/rhodecode/controllers/admin/user_groups.py --- a/rhodecode/controllers/admin/user_groups.py +++ b/rhodecode/controllers/admin/user_groups.py @@ -31,25 +31,19 @@ from pylons import request, tmpl_context from pylons.controllers.util import redirect from pylons.i18n.translation import _ -from sqlalchemy.orm import joinedload - from rhodecode.lib import auth from rhodecode.lib import helpers as h from rhodecode.lib import audit_logger -from rhodecode.lib.ext_json import json from rhodecode.lib.exceptions import UserGroupAssignedException,\ RepoGroupAssignmentError -from rhodecode.lib.utils import jsonify from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int from rhodecode.lib.auth import ( LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator, HasPermissionAnyDecorator) from rhodecode.lib.base import BaseController, render from rhodecode.model.permission import PermissionModel -from rhodecode.model.scm import UserGroupList from rhodecode.model.user_group import UserGroupModel -from rhodecode.model.db import ( - User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm) +from rhodecode.model.db import User, UserGroup from rhodecode.model.forms import ( UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm, UserPermissionsForm) diff --git a/rhodecode/lib/base.py b/rhodecode/lib/base.py --- a/rhodecode/lib/base.py +++ b/rhodecode/lib/base.py @@ -34,15 +34,6 @@ import pyramid.threadlocal from paste.auth.basic import AuthBasicAuthenticator from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION -from pylons import tmpl_context as c, request, url -from pylons.controllers import WSGIController -from pylons.controllers.util import redirect -from pylons.i18n import translation -# marcink: don't remove this import -from pylons.templating import render_mako, pylons_globals, literal, cached_template -from pylons.i18n.translation import _ -from webob.exc import HTTPFound - import rhodecode from rhodecode.authentication.base import VCS_TYPE @@ -55,13 +46,15 @@ from rhodecode.lib.utils import ( get_enabled_hook_classes) from rhodecode.lib.utils2 import ( str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist) -from rhodecode.lib.vcs.exceptions import RepositoryRequirementError from rhodecode.model import meta from rhodecode.model.db import Repository, User, ChangesetComment from rhodecode.model.notification import NotificationModel from rhodecode.model.scm import ScmModel from rhodecode.model.settings import VcsSettingsModel, SettingsModel +# NOTE(marcink): remove after base controller is no longer required +from pylons.controllers import WSGIController +from pylons.i18n import translation log = logging.getLogger(__name__) @@ -75,6 +68,9 @@ def render(template_name, extra_vars=Non ``cache_expire``. """ + from pylons.templating import literal + from pylons.templating import cached_template, pylons_globals + # Create a render callable for the cache function def render_template(): # Pull in extra vars if needed @@ -411,10 +407,6 @@ def attach_context_attributes(context, r 'refresh_time': 120 * 1000, 'cutoff_limit': 1000 * 60 * 60 * 24 * 7 }, - 'pylons_dispatch': { - # 'controller': request.environ['pylons.routes_dict']['controller'], - # 'action': request.environ['pylons.routes_dict']['action'], - }, 'pyramid_dispatch': { }, @@ -512,6 +504,7 @@ class BaseController(WSGIController): """ # on each call propagate settings calls into global settings. from pylons import config + from pylons import tmpl_context as c, request, url set_rhodecode_config(config) attach_context_attributes(c, request, self._rhodecode_user.user_id) @@ -531,6 +524,7 @@ class BaseController(WSGIController): user_lang, self._rhodecode_user) def _dispatch_redirect(self, with_url, environ, start_response): + from webob.exc import HTTPFound resp = HTTPFound(with_url) environ['SCRIPT_NAME'] = '' # handle prefix middleware environ['PATH_INFO'] = with_url @@ -542,6 +536,7 @@ class BaseController(WSGIController): # the request is routed to. This routing information is # available in environ['pylons.routes_dict'] from rhodecode.lib import helpers as h + from pylons import tmpl_context as c, request, url # Provide the Pylons context to Pyramid's debugtoolbar if it asks if environ.get('debugtoolbar.wants_pylons_context', False): @@ -620,93 +615,3 @@ def bootstrap_request(**kwargs): config = pyramid.testing.setUp(request=request) add_events_routes(config) - - -class BaseRepoController(BaseController): - """ - Base class for controllers responsible for loading all needed data for - repository loaded items are - - c.rhodecode_repo: instance of scm repository - c.rhodecode_db_repo: instance of db - c.repository_requirements_missing: shows that repository specific data - could not be displayed due to the missing requirements - c.repository_pull_requests: show number of open pull requests - """ - - def __before__(self): - super(BaseRepoController, self).__before__() - if c.repo_name: # extracted from routes - db_repo = Repository.get_by_repo_name(c.repo_name) - if not db_repo: - return - - log.debug( - 'Found repository in database %s with state `%s`', - safe_unicode(db_repo), safe_unicode(db_repo.repo_state)) - route = getattr(request.environ.get('routes.route'), 'name', '') - - # allow to delete repos that are somehow damages in filesystem - if route in ['delete_repo']: - return - - if db_repo.repo_state in [Repository.STATE_PENDING]: - if route in ['repo_creating_home']: - return - check_url = url('repo_creating_home', repo_name=c.repo_name) - return redirect(check_url) - - self.rhodecode_db_repo = db_repo - - missing_requirements = False - try: - self.rhodecode_repo = self.rhodecode_db_repo.scm_instance() - except RepositoryRequirementError as e: - missing_requirements = True - self._handle_missing_requirements(e) - - if self.rhodecode_repo is None and not missing_requirements: - log.error('%s this repository is present in database but it ' - 'cannot be created as an scm instance', c.repo_name) - - h.flash(_( - "The repository at %(repo_name)s cannot be located.") % - {'repo_name': c.repo_name}, - category='error', ignore_duplicate=True) - redirect(h.route_path('home')) - - # update last change according to VCS data - if not missing_requirements: - commit = db_repo.get_commit( - pre_load=["author", "date", "message", "parents"]) - db_repo.update_commit_cache(commit) - - # Prepare context - c.rhodecode_db_repo = db_repo - c.rhodecode_repo = self.rhodecode_repo - c.repository_requirements_missing = missing_requirements - - self._update_global_counters(self.scm_model, db_repo) - - def _update_global_counters(self, scm_model, db_repo): - """ - Base variables that are exposed to every page of repository - """ - c.repository_pull_requests = scm_model.get_pull_requests(db_repo) - - def _handle_missing_requirements(self, error): - self.rhodecode_repo = None - log.error( - 'Requirements are missing for repository %s: %s', - c.repo_name, error.message) - - 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 - should_redirect = ( - path not in (summary_url, settings_update_url) - and '/settings' not in path or path == statistics_url - ) - if should_redirect: - redirect(summary_url) diff --git a/rhodecode/lib/rc_commands/__init__.py b/rhodecode/lib/rc_commands/__init__.py new file mode 100644 diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py --- a/rhodecode/lib/utils.py +++ b/rhodecode/lib/utils.py @@ -852,30 +852,6 @@ class BasePasterCommand(Command): initialize_database(config) -@decorator.decorator -def jsonify(func, *args, **kwargs): - """Action decorator that formats output for JSON - - Given a function that will return content, this decorator will turn - the result into JSON, with a content-type of 'application/json' and - output it. - - """ - from pylons.decorators.util import get_pylons - from rhodecode.lib.ext_json import json - pylons = get_pylons(args) - pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8' - data = func(*args, **kwargs) - if isinstance(data, (list, tuple)): - msg = "JSON responses with Array envelopes are susceptible to " \ - "cross-site data leak attacks, see " \ - "http://wiki.pylonshq.com/display/pylonsfaq/Warnings" - warnings.warn(msg, Warning, 2) - log.warning(msg) - log.debug("Returning JSON wrapped action output") - return json.dumps(data, encoding='utf-8') - - class PartialRenderer(object): """ Partial renderer used to render chunks of html used in datagrids diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -18,10 +18,6 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ -""" -Repository model for rhodecode -""" - import logging import os import re 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 @@ -55,7 +55,7 @@ function setRCMouseBindings(repoName, re window.location = pyroutes.url('gists_new'); }); Mousetrap.bind(['n r'], function(e) { - window.location = pyroutes.url('new_repo'); + window.location = pyroutes.url('repo_new'); }); if (repoName && repoName != '') { 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 @@ -12,7 +12,6 @@ ******************************************************************************/ function registerRCRoutes() { // routes registration - pyroutes.register('new_repo', '/_admin/create_repository', []); pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']); pyroutes.register('favicon', '/favicon.ico', []); pyroutes.register('robots', '/robots.txt', []); @@ -82,6 +81,9 @@ function registerRCRoutes() { pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']); pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']); pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']); + pyroutes.register('repos', '/_admin/repos', []); + pyroutes.register('repo_new', '/_admin/repos/new', []); + pyroutes.register('repo_create', '/_admin/repos/create', []); pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []); pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []); pyroutes.register('channelstream_proxy', '/_channelstream', []); @@ -177,11 +179,26 @@ function registerRCRoutes() { pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['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']); + pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']); + pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']); + pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']); + pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']); + pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']); + pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']); + pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']); + pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']); + pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']); + pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']); + pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']); + pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']); + pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']); + pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']); + pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']); + pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']); + pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']); pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']); pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']); - pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']); - pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']); - pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']); + pyroutes.register('edit_repo_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('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']); diff --git a/rhodecode/templates/admin/repos/repo_add.mako b/rhodecode/templates/admin/repos/repo_add.mako --- a/rhodecode/templates/admin/repos/repo_add.mako +++ b/rhodecode/templates/admin/repos/repo_add.mako @@ -10,9 +10,9 @@ <%def name="breadcrumbs_links()"> %if c.rhodecode_user.is_admin: - ${h.link_to(_('Admin'),h.route_path('admin_home'))} + ${h.link_to(_('Admin'), h.route_path('admin_home'))} » - ${h.link_to(_('Repositories'),h.url('repos'))} + ${h.link_to(_('Repositories'), h.route_path('repos'))} %else: ${_('Admin')} » diff --git a/rhodecode/templates/admin/repos/repo_add_base.mako b/rhodecode/templates/admin/repos/repo_add_base.mako --- a/rhodecode/templates/admin/repos/repo_add_base.mako +++ b/rhodecode/templates/admin/repos/repo_add_base.mako @@ -1,6 +1,6 @@ ## -*- coding: utf-8 -*- -${h.secure_form(h.url('repos'))} +${h.secure_form(h.route_path('repo_create'), method='POST', request=request)}
diff --git a/rhodecode/templates/admin/repos/repo_edit.mako b/rhodecode/templates/admin/repos/repo_edit.mako --- a/rhodecode/templates/admin/repos/repo_edit.mako +++ b/rhodecode/templates/admin/repos/repo_edit.mako @@ -52,24 +52,24 @@ ${_('Advanced')}
  • - ${_('VCS')} + ${_('VCS')}
  • - ${_('Extra Fields')} + ${_('Extra Fields')}
  • - ${_('Issue Tracker')} + ${_('Issue Tracker')}
  • ${_('Caches')}
  • %if c.repo_info.repo_type != 'svn':
  • - ${_('Remote')} + ${_('Remote')}
  • %endif
  • - ${_('Statistics')} + ${_('Statistics')}
  • ${_('Integrations')} @@ -80,10 +80,10 @@
  • %endif
  • - ${_('Maintenance')} + ${_('Maintenance')}
  • - ${_('Strip')} + ${_('Strip')}
  • diff --git a/rhodecode/templates/admin/repos/repo_edit_fields.mako b/rhodecode/templates/admin/repos/repo_edit_fields.mako --- a/rhodecode/templates/admin/repos/repo_edit_fields.mako +++ b/rhodecode/templates/admin/repos/repo_edit_fields.mako @@ -18,7 +18,7 @@ ${field.field_key} ${field.field_type} - ${h.secure_form(h.url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')} + ${h.secure_form(h.route_path('edit_repo_fields_delete', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id), method='POST', request=request)} ${h.hidden('del_repo_field',field.repo_field_id)}
    %endif - ${h.secure_form(h.url('create_repo_fields', repo_name=c.repo_name),method='put')} + ${h.secure_form(h.route_path('edit_repo_fields_create', repo_name=c.repo_name), method='POST', request=request)}
    diff --git a/rhodecode/templates/admin/repos/repo_edit_issuetracker.mako b/rhodecode/templates/admin/repos/repo_edit_issuetracker.mako --- a/rhodecode/templates/admin/repos/repo_edit_issuetracker.mako +++ b/rhodecode/templates/admin/repos/repo_edit_issuetracker.mako @@ -1,7 +1,7 @@ <%namespace name="its" file="/base/issue_tracker_settings.mako"/>
    - ${h.secure_form(h.url('repo_issuetracker_save', repo_name=c.repo_name), method='post', id="inherit-form")} + ${h.secure_form(h.route_path('edit_repo_issuetracker_update', repo_name=c.repo_name), id="inherit-form", method='POST', request=request)}
    @@ -72,8 +72,8 @@
    ${its.issue_tracker_settings_table( patterns=c.repo_patterns.items(), - form_url=h.url('repo_settings_issuetracker', repo_name=c.repo_info.repo_name), - delete_url=h.url('repo_issuetracker_delete', repo_name=c.repo_info.repo_name) + form_url=h.route_path('edit_repo_issuetracker', repo_name=c.repo_info.repo_name), + delete_url=h.route_path('edit_repo_issuetracker_delete', repo_name=c.repo_info.repo_name) )}
    @@ -92,7 +92,7 @@
    ${its.issue_tracker_new_row()} - ${its.issue_tracker_settings_test(test_url=h.url('repo_issuetracker_test', repo_name=c.repo_info.repo_name))} + ${its.issue_tracker_settings_test(test_url=h.route_path('edit_repo_issuetracker_test', repo_name=c.repo_info.repo_name))}
    diff --git a/rhodecode/templates/admin/repos/repo_edit_remote.mako b/rhodecode/templates/admin/repos/repo_edit_remote.mako --- a/rhodecode/templates/admin/repos/repo_edit_remote.mako +++ b/rhodecode/templates/admin/repos/repo_edit_remote.mako @@ -19,7 +19,7 @@

    - ${h.secure_form(h.url('edit_repo_remote', repo_name=c.repo_name), method='put')} + ${h.secure_form(h.route_path('edit_repo_remote_pull', repo_name=c.repo_name), method='POST', request=request)}
    ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")} diff --git a/rhodecode/templates/admin/repos/repo_edit_settings.mako b/rhodecode/templates/admin/repos/repo_edit_settings.mako --- a/rhodecode/templates/admin/repos/repo_edit_settings.mako +++ b/rhodecode/templates/admin/repos/repo_edit_settings.mako @@ -83,7 +83,7 @@ ${h.hidden('repo_clone_uri_change', 'NEW')} %endif

    - <% pull_link = h.literal(h.link_to('remote sync', h.url('edit_repo_remote', repo_name=c.repo_name))) %> + <% pull_link = h.literal(h.link_to('remote sync', h.route_path('edit_repo_remote', repo_name=c.repo_name))) %> ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n}
    ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')}

    diff --git a/rhodecode/templates/admin/repos/repo_edit_statistics.mako b/rhodecode/templates/admin/repos/repo_edit_statistics.mako --- a/rhodecode/templates/admin/repos/repo_edit_statistics.mako +++ b/rhodecode/templates/admin/repos/repo_edit_statistics.mako @@ -3,7 +3,7 @@

    ${_('Repository statistics')}

    - ${h.secure_form(h.url('edit_repo_statistics', repo_name=c.repo_info.repo_name), method='put')} + ${h.secure_form(h.route_path('edit_repo_statistics_reset', repo_name=c.repo_info.repo_name), method='POST', request=request)}
    diff --git a/rhodecode/templates/admin/repos/repo_edit_vcs.mako b/rhodecode/templates/admin/repos/repo_edit_vcs.mako --- a/rhodecode/templates/admin/repos/repo_edit_vcs.mako +++ b/rhodecode/templates/admin/repos/repo_edit_vcs.mako @@ -1,7 +1,7 @@ <%namespace name="vcss" file="/base/vcs_settings.mako"/>
    - ${h.secure_form(h.url('repo_vcs_settings', repo_name=c.repo_info.repo_name), method='post')} + ${h.secure_form(h.route_path('edit_repo_vcs_update', repo_name=c.repo_info.repo_name), method='POST', request=request)}
    @@ -50,13 +50,12 @@