##// END OF EJS Templates
repo-settings: moved advanced setion into pyramid views....
marcink -
r1751:aa1d7f99 default
parent child Browse files
Show More
@@ -0,0 +1,150 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.lib.utils2 import safe_unicode, safe_str
24 from rhodecode.model.db import Repository
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.tests import (
27 HG_REPO, GIT_REPO, assert_session_flash, no_newline_id_generator)
28 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.utils import repo_on_filesystem
30
31 fixture = Fixture()
32
33
34 def route_path(name, params=None, **kwargs):
35 import urllib
36
37 base_url = {
38 'repo_summary_explicit': '/{repo_name}/summary',
39 'repo_summary': '/{repo_name}',
40 'edit_repo_advanced': '/{repo_name}/settings/advanced',
41 'edit_repo_advanced_delete': '/{repo_name}/settings/advanced/delete',
42 'edit_repo_advanced_fork': '/{repo_name}/settings/advanced/fork',
43 'edit_repo_advanced_locking': '/{repo_name}/settings/advanced/locking',
44 'edit_repo_advanced_journal': '/{repo_name}/settings/advanced/journal',
45
46 }[name].format(**kwargs)
47
48 if params:
49 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
50 return base_url
51
52
53 @pytest.mark.usefixtures('autologin_user', 'app')
54 class TestAdminRepoSettingsAdvanced(object):
55
56 def test_set_repo_fork_has_no_self_id(self, autologin_user, backend):
57 repo = backend.repo
58 response = self.app.get(
59 route_path('edit_repo_advanced', repo_name=backend.repo_name))
60 opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
61 response.mustcontain(no=[opt])
62
63 def test_set_fork_of_target_repo(
64 self, autologin_user, backend, csrf_token):
65 target_repo = 'target_%s' % backend.alias
66 fixture.create_repo(target_repo, repo_type=backend.alias)
67 repo2 = Repository.get_by_repo_name(target_repo)
68 response = self.app.post(
69 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
70 params={'id_fork_of': repo2.repo_id,
71 'csrf_token': csrf_token})
72 repo = Repository.get_by_repo_name(backend.repo_name)
73 repo2 = Repository.get_by_repo_name(target_repo)
74 assert_session_flash(
75 response,
76 'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name))
77
78 assert repo.fork == repo2
79 response = response.follow()
80 # check if given repo is selected
81
82 opt = 'This repository is a fork of <a href="%s">%s</a>' % (
83 route_path('repo_summary', repo_name=repo2.repo_name),
84 repo2.repo_name)
85
86 response.mustcontain(opt)
87
88 fixture.destroy_repo(target_repo, forks='detach')
89
90 @pytest.mark.backends("hg", "git")
91 def test_set_fork_of_other_type_repo(
92 self, autologin_user, backend, csrf_token):
93 TARGET_REPO_MAP = {
94 'git': {
95 'type': 'hg',
96 'repo_name': HG_REPO},
97 'hg': {
98 'type': 'git',
99 'repo_name': GIT_REPO},
100 }
101 target_repo = TARGET_REPO_MAP[backend.alias]
102
103 repo2 = Repository.get_by_repo_name(target_repo['repo_name'])
104 response = self.app.post(
105 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
106 params={'id_fork_of': repo2.repo_id,
107 'csrf_token': csrf_token})
108 assert_session_flash(
109 response,
110 'Cannot set repository as fork of repository with other type')
111
112 def test_set_fork_of_none(self, autologin_user, backend, csrf_token):
113 # mark it as None
114 response = self.app.post(
115 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
116 params={'id_fork_of': None, '_method': 'put',
117 'csrf_token': csrf_token})
118 assert_session_flash(
119 response,
120 'Marked repo %s as fork of %s'
121 % (backend.repo_name, "Nothing"))
122 assert backend.repo.fork is None
123
124 def test_set_fork_of_same_repo(self, autologin_user, backend, csrf_token):
125 repo = Repository.get_by_repo_name(backend.repo_name)
126 response = self.app.post(
127 route_path('edit_repo_advanced_fork', repo_name=backend.repo_name),
128 params={'id_fork_of': repo.repo_id, 'csrf_token': csrf_token})
129 assert_session_flash(
130 response, 'An error occurred during this operation')
131
132 @pytest.mark.parametrize(
133 "suffix",
134 ['', u'Δ…Δ™Ε‚' , '123'],
135 ids=no_newline_id_generator)
136 def test_advanced_delete(self, autologin_user, backend, suffix, csrf_token):
137 repo = backend.create_repo(name_suffix=suffix)
138 repo_name = repo.repo_name
139 repo_name_str = safe_str(repo.repo_name)
140
141 response = self.app.post(
142 route_path('edit_repo_advanced_delete', repo_name=repo_name_str),
143 params={'csrf_token': csrf_token})
144 assert_session_flash(response,
145 u'Deleted repository `{}`'.format(repo_name))
146 response.follow()
147
148 # check if repo was deleted from db
149 assert RepoModel().get_by_repo_name(repo_name) is None
150 assert not repo_on_filesystem(repo_name_str)
@@ -0,0 +1,225 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
25
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.lib.exceptions import AttachedForksError
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.vcs import RepositoryError
34 from rhodecode.model.db import Session, UserFollowing, User, Repository
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.scm import ScmModel
37
38 log = logging.getLogger(__name__)
39
40
41 class RepoSettingsView(RepoAppView):
42
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
45
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 c.repo_info = self.db_repo
48
49 self._register_global_c(c)
50 return c
51
52 @LoginRequired()
53 @HasRepoPermissionAnyDecorator('repository.admin')
54 @view_config(
55 route_name='edit_repo_advanced', request_method='GET',
56 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
57 def edit_advanced(self):
58 c = self.load_default_context()
59 c.active = 'advanced'
60
61 c.default_user_id = User.get_default_user().user_id
62 c.in_public_journal = UserFollowing.query() \
63 .filter(UserFollowing.user_id == c.default_user_id) \
64 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
65
66 c.has_origin_repo_read_perm = False
67 if self.db_repo.fork:
68 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
69 'repository.write', 'repository.read', 'repository.admin')(
70 self.db_repo.fork.repo_name, 'repo set as fork page')
71
72 return self._get_template_context(c)
73
74 @LoginRequired()
75 @HasRepoPermissionAnyDecorator('repository.admin')
76 @view_config(
77 route_name='edit_repo_advanced_delete', request_method='POST',
78 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
79 def edit_advanced_delete(self):
80 """
81 Deletes the repository, or shows warnings if deletion is not possible
82 because of attached forks or other errors.
83 """
84 _ = self.request.translate
85 handle_forks = self.request.POST.get('forks', None)
86
87 try:
88 _forks = self.db_repo.forks.count()
89 if _forks and handle_forks:
90 if handle_forks == 'detach_forks':
91 handle_forks = 'detach'
92 h.flash(_('Detached %s forks') % _forks, category='success')
93 elif handle_forks == 'delete_forks':
94 handle_forks = 'delete'
95 h.flash(_('Deleted %s forks') % _forks, category='success')
96
97 repo_data = self.db_repo.get_api_data()
98 RepoModel().delete(self.db_repo, forks=handle_forks)
99
100 repo = audit_logger.RepoWrap(repo_id=self.db_repo.repo_id,
101 repo_name=self.db_repo.repo_name)
102 audit_logger.store(
103 action='repo.delete', action_data={'repo_data': repo_data},
104 user=self._rhodecode_user, repo=repo, commit=False)
105
106 ScmModel().mark_for_invalidation(self.db_repo_name)
107 h.flash(
108 _('Deleted repository `%s`') % self.db_repo_name,
109 category='success')
110 Session().commit()
111 except AttachedForksError:
112 repo_advanced_url = h.route_path(
113 'edit_repo_advanced', repo_name=self.db_repo_name,
114 _anchor='advanced-delete')
115 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
116 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
117 'Try using {delete_or_detach} option.')
118 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
119 category='warning')
120
121 # redirect to advanced for forks handle action ?
122 raise HTTPFound(repo_advanced_url)
123
124 except Exception:
125 log.exception("Exception during deletion of repository")
126 h.flash(_('An error occurred during deletion of `%s`')
127 % self.db_repo_name, category='error')
128 # redirect to advanced for more deletion options
129 raise HTTPFound(
130 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
131 _anchor='advanced-delete')
132
133 raise HTTPFound(h.route_path('home'))
134
135 @LoginRequired()
136 @HasRepoPermissionAnyDecorator('repository.admin')
137 @CSRFRequired()
138 @view_config(
139 route_name='edit_repo_advanced_journal', request_method='POST',
140 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
141 def edit_advanced_journal(self):
142 """
143 Set's this repository to be visible in public journal,
144 in other words making default user to follow this repo
145 """
146 _ = self.request.translate
147
148 try:
149 user_id = User.get_default_user().user_id
150 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
151 h.flash(_('Updated repository visibility in public journal'),
152 category='success')
153 Session().commit()
154 except Exception:
155 h.flash(_('An error occurred during setting this '
156 'repository in public journal'),
157 category='error')
158
159 raise HTTPFound(
160 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
161
162 @LoginRequired()
163 @HasRepoPermissionAnyDecorator('repository.admin')
164 @CSRFRequired()
165 @view_config(
166 route_name='edit_repo_advanced_fork', request_method='POST',
167 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
168 def edit_advanced_fork(self):
169 """
170 Mark given repository as a fork of another
171 """
172 _ = self.request.translate
173
174 new_fork_id = self.request.POST.get('id_fork_of')
175 try:
176
177 if new_fork_id and not new_fork_id.isdigit():
178 log.error('Given fork id %s is not an INT', new_fork_id)
179
180 fork_id = safe_int(new_fork_id)
181 repo = ScmModel().mark_as_fork(
182 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
183 fork = repo.fork.repo_name if repo.fork else _('Nothing')
184 Session().commit()
185 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
186 category='success')
187 except RepositoryError as e:
188 log.exception("Repository Error occurred")
189 h.flash(str(e), category='error')
190 except Exception as e:
191 log.exception("Exception while editing fork")
192 h.flash(_('An error occurred during this operation'),
193 category='error')
194
195 raise HTTPFound(
196 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
197
198 @LoginRequired()
199 @HasRepoPermissionAnyDecorator('repository.admin')
200 @CSRFRequired()
201 @view_config(
202 route_name='edit_repo_advanced_locking', request_method='POST',
203 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
204 def edit_advanced_locking(self):
205 """
206 Toggle locking of repository
207 """
208 _ = self.request.translate
209 set_lock = self.request.POST.get('set_lock')
210 set_unlock = self.request.POST.get('set_unlock')
211
212 try:
213 if set_lock:
214 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
215 lock_reason=Repository.LOCK_WEB)
216 h.flash(_('Locked repository'), category='success')
217 elif set_unlock:
218 Repository.unlock(self.db_repo)
219 h.flash(_('Unlocked repository'), category='success')
220 except Exception as e:
221 log.exception("Exception during unlocking")
222 h.flash(_('An error occurred during unlocking'), category='error')
223
224 raise HTTPFound(
225 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
@@ -1,81 +1,109 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 def includeme(config):
23 23
24 # Summary
25 config.add_route(
26 name='repo_summary_explicit',
27 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
28
24 29 # Tags
25 30 config.add_route(
26 31 name='tags_home',
27 32 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
28 33
29 34 # Branches
30 35 config.add_route(
31 36 name='branches_home',
32 37 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
33 38
34 39 # Bookmarks
35 40 config.add_route(
36 41 name='bookmarks_home',
37 42 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
38 43
39 44 # Settings
40 45 config.add_route(
41 46 name='edit_repo',
42 47 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
43 48
49 # Settings advanced
50 config.add_route(
51 name='edit_repo_advanced',
52 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
53 config.add_route(
54 name='edit_repo_advanced_delete',
55 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
56 config.add_route(
57 name='edit_repo_advanced_locking',
58 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
59 config.add_route(
60 name='edit_repo_advanced_journal',
61 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
62 config.add_route(
63 name='edit_repo_advanced_fork',
64 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
65
44 66 # Caches
45 67 config.add_route(
46 68 name='edit_repo_caches',
47 69 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
48 70
49 71 # Permissions
50 72 config.add_route(
51 73 name='edit_repo_perms',
52 74 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
53 75
54 76 # Repo Review Rules
55 77 config.add_route(
56 78 name='repo_reviewers',
57 79 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
58 80
59 81 # Maintenance
60 82 config.add_route(
61 83 name='repo_maintenance',
62 84 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
63 85
64 86 config.add_route(
65 87 name='repo_maintenance_execute',
66 88 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
67 89
68 90 # Strip
69 91 config.add_route(
70 92 name='strip',
71 93 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
72 94
73 95 config.add_route(
74 96 name='strip_check',
75 97 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
76 98
77 99 config.add_route(
78 100 name='strip_execute',
79 101 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
102
103 # NOTE(marcink): needs to be at the end for catch-all
104 # config.add_route(
105 # name='repo_summary',
106 # pattern='/{repo_name:.*?[^/]}', repo_route=True)
107
80 108 # Scan module for configuration decorators.
81 109 config.scan()
@@ -1,232 +1,233 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.lib.utils2 import str2bool
25 25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 26 from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User
27 27 from rhodecode.model.meta import Session
28 28 from rhodecode.tests import (
29 29 url, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN,
30 30 assert_session_flash)
31 31 from rhodecode.tests.fixture import Fixture
32 32
33 33 fixture = Fixture()
34 34
35 35
36 36 def route_path(name, params=None, **kwargs):
37 37 import urllib
38 38
39 39 base_url = {
40 40 'edit_repo': '/{repo_name}/settings',
41 'edit_repo_advanced': '/{repo_name}/settings/advanced',
41 42 'edit_repo_caches': '/{repo_name}/settings/caches',
42 43 'edit_repo_perms': '/{repo_name}/settings/permissions',
43 44 }[name].format(**kwargs)
44 45
45 46 if params:
46 47 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
47 48 return base_url
48 49
49 50
50 51 def _get_permission_for_user(user, repo):
51 52 perm = UserRepoToPerm.query()\
52 53 .filter(UserRepoToPerm.repository ==
53 54 Repository.get_by_repo_name(repo))\
54 55 .filter(UserRepoToPerm.user == User.get_by_username(user))\
55 56 .all()
56 57 return perm
57 58
58 59
59 60 @pytest.mark.usefixtures('autologin_user', 'app')
60 61 class TestAdminRepoSettings(object):
61 62 @pytest.mark.parametrize('urlname', [
62 63 'edit_repo',
63 64 'edit_repo_caches',
64 65 'edit_repo_perms',
66 'edit_repo_advanced',
65 67 ])
66 68 def test_show_page(self, urlname, app, backend):
67 69 app.get(route_path(urlname, repo_name=backend.repo_name), status=200)
68 70
69 71 def test_edit_accessible_when_missing_requirements(
70 72 self, backend_hg, autologin_user):
71 73 scm_patcher = mock.patch.object(
72 74 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
73 75 with scm_patcher:
74 76 self.app.get(route_path('edit_repo', repo_name=backend_hg.repo_name))
75 77
76 78 @pytest.mark.parametrize('urlname', [
77 'edit_repo_advanced',
78 79 'repo_vcs_settings',
80 'repo_settings_issuetracker',
79 81 'edit_repo_fields',
80 'repo_settings_issuetracker',
81 82 'edit_repo_remote',
82 83 'edit_repo_statistics',
83 84 ])
84 85 def test_show_page_pylons(self, urlname, app):
85 86 app.get(url(urlname, repo_name=HG_REPO))
86 87
87 88 @pytest.mark.parametrize('update_settings', [
88 89 {'repo_description': 'alter-desc'},
89 90 {'repo_owner': TEST_USER_REGULAR_LOGIN},
90 91 {'repo_private': 'true'},
91 92 {'repo_enable_locking': 'true'},
92 93 {'repo_enable_downloads': 'true'},
93 94 ])
94 95 def test_update_repo_settings(self, update_settings, csrf_token, backend, user_util):
95 96 repo = user_util.create_repo(repo_type=backend.alias)
96 97 repo_name = repo.repo_name
97 98
98 99 params = fixture._get_repo_create_params(
99 100 csrf_token=csrf_token,
100 101 repo_name=repo_name,
101 102 repo_type=backend.alias,
102 103 repo_owner=TEST_USER_ADMIN_LOGIN,
103 104 repo_description='DESC',
104 105
105 106 repo_private='false',
106 107 repo_enable_locking='false',
107 108 repo_enable_downloads='false')
108 109 params.update(update_settings)
109 110 self.app.post(
110 111 route_path('edit_repo', repo_name=repo_name),
111 112 params=params, status=302)
112 113
113 114 repo = Repository.get_by_repo_name(repo_name)
114 115 assert repo.user.username == \
115 116 update_settings.get('repo_owner', repo.user.username)
116 117
117 118 assert repo.description == \
118 119 update_settings.get('repo_description', repo.description)
119 120
120 121 assert repo.private == \
121 122 str2bool(update_settings.get(
122 123 'repo_private', repo.private))
123 124
124 125 assert repo.enable_locking == \
125 126 str2bool(update_settings.get(
126 127 'repo_enable_locking', repo.enable_locking))
127 128
128 129 assert repo.enable_downloads == \
129 130 str2bool(update_settings.get(
130 131 'repo_enable_downloads', repo.enable_downloads))
131 132
132 133 def test_update_repo_name_via_settings(self, csrf_token, user_util, backend):
133 134 repo = user_util.create_repo(repo_type=backend.alias)
134 135 repo_name = repo.repo_name
135 136
136 137 repo_group = user_util.create_repo_group()
137 138 repo_group_name = repo_group.group_name
138 139 new_name = repo_group_name + '_' + repo_name
139 140
140 141 params = fixture._get_repo_create_params(
141 142 csrf_token=csrf_token,
142 143 repo_name=new_name,
143 144 repo_type=backend.alias,
144 145 repo_owner=TEST_USER_ADMIN_LOGIN,
145 146 repo_description='DESC',
146 147 repo_private='false',
147 148 repo_enable_locking='false',
148 149 repo_enable_downloads='false')
149 150 self.app.post(
150 151 route_path('edit_repo', repo_name=repo_name),
151 152 params=params, status=302)
152 153 repo = Repository.get_by_repo_name(new_name)
153 154 assert repo.repo_name == new_name
154 155
155 156 def test_update_repo_group_via_settings(self, csrf_token, user_util, backend):
156 157 repo = user_util.create_repo(repo_type=backend.alias)
157 158 repo_name = repo.repo_name
158 159
159 160 repo_group = user_util.create_repo_group()
160 161 repo_group_name = repo_group.group_name
161 162 repo_group_id = repo_group.group_id
162 163
163 164 new_name = repo_group_name + '/' + repo_name
164 165 params = fixture._get_repo_create_params(
165 166 csrf_token=csrf_token,
166 167 repo_name=repo_name,
167 168 repo_type=backend.alias,
168 169 repo_owner=TEST_USER_ADMIN_LOGIN,
169 170 repo_description='DESC',
170 171 repo_group=repo_group_id,
171 172 repo_private='false',
172 173 repo_enable_locking='false',
173 174 repo_enable_downloads='false')
174 175 self.app.post(
175 176 route_path('edit_repo', repo_name=repo_name),
176 177 params=params, status=302)
177 178 repo = Repository.get_by_repo_name(new_name)
178 179 assert repo.repo_name == new_name
179 180
180 181 def test_set_private_flag_sets_default_user_permissions_to_none(
181 182 self, autologin_user, backend, csrf_token):
182 183
183 184 # initially repository perm should be read
184 185 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
185 186 assert len(perm) == 1
186 187 assert perm[0].permission.permission_name == 'repository.read'
187 188 assert not backend.repo.private
188 189
189 190 response = self.app.post(
190 191 route_path('edit_repo', repo_name=backend.repo_name),
191 192 params=fixture._get_repo_create_params(
192 193 repo_private='true',
193 194 repo_name=backend.repo_name,
194 195 repo_type=backend.alias,
195 196 repo_owner=TEST_USER_ADMIN_LOGIN,
196 197 csrf_token=csrf_token), status=302)
197 198
198 199 assert_session_flash(
199 200 response,
200 201 msg='Repository %s updated successfully' % (backend.repo_name))
201 202
202 203 repo = Repository.get_by_repo_name(backend.repo_name)
203 204 assert repo.private is True
204 205
205 206 # now the repo default permission should be None
206 207 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
207 208 assert len(perm) == 1
208 209 assert perm[0].permission.permission_name == 'repository.none'
209 210
210 211 response = self.app.post(
211 212 route_path('edit_repo', repo_name=backend.repo_name),
212 213 params=fixture._get_repo_create_params(
213 214 repo_private='false',
214 215 repo_name=backend.repo_name,
215 216 repo_type=backend.alias,
216 217 repo_owner=TEST_USER_ADMIN_LOGIN,
217 218 csrf_token=csrf_token), status=302)
218 219
219 220 assert_session_flash(
220 221 response,
221 222 msg='Repository %s updated successfully' % (backend.repo_name))
222 223 assert backend.repo.private is False
223 224
224 225 # we turn off private now the repo default permission should stay None
225 226 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
226 227 assert len(perm) == 1
227 228 assert perm[0].permission.permission_name == 'repository.none'
228 229
229 230 # update this permission back
230 231 perm[0].permission = Permission.get_by_key('repository.read')
231 232 Session().add(perm[0])
232 233 Session().commit()
@@ -1,1076 +1,1057 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 # prefix for non repository related links needs to be prefixed with `/`
36 36 ADMIN_PREFIX = '/_admin'
37 37 STATIC_FILE_PREFIX = '/_static'
38 38
39 39 # Default requirements for URL parts
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 'repo_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 def add_route_requirements(route_path, requirements):
55 55 """
56 56 Adds regex requirements to pyramid routes using a mapping dict
57 57
58 58 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
59 59 '/{action}/{id:\d+}'
60 60
61 61 """
62 62 for key, regex in requirements.items():
63 63 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
64 64 return route_path
65 65
66 66
67 67 class JSRoutesMapper(Mapper):
68 68 """
69 69 Wrapper for routes.Mapper to make pyroutes compatible url definitions
70 70 """
71 71 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
72 72 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
73 73 def __init__(self, *args, **kw):
74 74 super(JSRoutesMapper, self).__init__(*args, **kw)
75 75 self._jsroutes = []
76 76
77 77 def connect(self, *args, **kw):
78 78 """
79 79 Wrapper for connect to take an extra argument jsroute=True
80 80
81 81 :param jsroute: boolean, if True will add the route to the pyroutes list
82 82 """
83 83 if kw.pop('jsroute', False):
84 84 if not self._named_route_regex.match(args[0]):
85 85 raise Exception('only named routes can be added to pyroutes')
86 86 self._jsroutes.append(args[0])
87 87
88 88 super(JSRoutesMapper, self).connect(*args, **kw)
89 89
90 90 def _extract_route_information(self, route):
91 91 """
92 92 Convert a route into tuple(name, path, args), eg:
93 93 ('show_user', '/profile/%(username)s', ['username'])
94 94 """
95 95 routepath = route.routepath
96 96 def replace(matchobj):
97 97 if matchobj.group(1):
98 98 return "%%(%s)s" % matchobj.group(1).split(':')[0]
99 99 else:
100 100 return "%%(%s)s" % matchobj.group(2)
101 101
102 102 routepath = self._argument_prog.sub(replace, routepath)
103 103 return (
104 104 route.name,
105 105 routepath,
106 106 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
107 107 for arg in self._argument_prog.findall(route.routepath)]
108 108 )
109 109
110 110 def jsroutes(self):
111 111 """
112 112 Return a list of pyroutes.js compatible routes
113 113 """
114 114 for route_name in self._jsroutes:
115 115 yield self._extract_route_information(self._routenames[route_name])
116 116
117 117
118 118 def make_map(config):
119 119 """Create, configure and return the routes Mapper"""
120 120 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
121 121 always_scan=config['debug'])
122 122 rmap.minimization = False
123 123 rmap.explicit = False
124 124
125 125 from rhodecode.lib.utils2 import str2bool
126 126 from rhodecode.model import repo, repo_group
127 127
128 128 def check_repo(environ, match_dict):
129 129 """
130 130 check for valid repository for proper 404 handling
131 131
132 132 :param environ:
133 133 :param match_dict:
134 134 """
135 135 repo_name = match_dict.get('repo_name')
136 136
137 137 if match_dict.get('f_path'):
138 138 # fix for multiple initial slashes that causes errors
139 139 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
140 140 repo_model = repo.RepoModel()
141 141 by_name_match = repo_model.get_by_repo_name(repo_name)
142 142 # if we match quickly from database, short circuit the operation,
143 143 # and validate repo based on the type.
144 144 if by_name_match:
145 145 return True
146 146
147 147 by_id_match = repo_model.get_repo_by_id(repo_name)
148 148 if by_id_match:
149 149 repo_name = by_id_match.repo_name
150 150 match_dict['repo_name'] = repo_name
151 151 return True
152 152
153 153 return False
154 154
155 155 def check_group(environ, match_dict):
156 156 """
157 157 check for valid repository group path for proper 404 handling
158 158
159 159 :param environ:
160 160 :param match_dict:
161 161 """
162 162 repo_group_name = match_dict.get('group_name')
163 163 repo_group_model = repo_group.RepoGroupModel()
164 164 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
165 165 if by_name_match:
166 166 return True
167 167
168 168 return False
169 169
170 170 def check_user_group(environ, match_dict):
171 171 """
172 172 check for valid user group for proper 404 handling
173 173
174 174 :param environ:
175 175 :param match_dict:
176 176 """
177 177 return True
178 178
179 179 def check_int(environ, match_dict):
180 180 return match_dict.get('id').isdigit()
181 181
182 182
183 183 #==========================================================================
184 184 # CUSTOM ROUTES HERE
185 185 #==========================================================================
186 186
187 187 # MAIN PAGE
188 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
188 rmap.connect('home', '/', controller='home', action='index')
189 189
190 190 # ping and pylons error test
191 191 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
192 192 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
193 193
194 194 # ADMIN REPOSITORY ROUTES
195 195 with rmap.submapper(path_prefix=ADMIN_PREFIX,
196 196 controller='admin/repos') as m:
197 197 m.connect('repos', '/repos',
198 198 action='create', conditions={'method': ['POST']})
199 199 m.connect('repos', '/repos',
200 200 action='index', conditions={'method': ['GET']})
201 201 m.connect('new_repo', '/create_repository', jsroute=True,
202 202 action='create_repository', conditions={'method': ['GET']})
203 203 m.connect('delete_repo', '/repos/{repo_name}',
204 204 action='delete', conditions={'method': ['DELETE']},
205 205 requirements=URL_NAME_REQUIREMENTS)
206 206 m.connect('repo', '/repos/{repo_name}',
207 207 action='show', conditions={'method': ['GET'],
208 208 'function': check_repo},
209 209 requirements=URL_NAME_REQUIREMENTS)
210 210
211 211 # ADMIN REPOSITORY GROUPS ROUTES
212 212 with rmap.submapper(path_prefix=ADMIN_PREFIX,
213 213 controller='admin/repo_groups') as m:
214 214 m.connect('repo_groups', '/repo_groups',
215 215 action='create', conditions={'method': ['POST']})
216 216 m.connect('repo_groups', '/repo_groups',
217 217 action='index', conditions={'method': ['GET']})
218 218 m.connect('new_repo_group', '/repo_groups/new',
219 219 action='new', conditions={'method': ['GET']})
220 220 m.connect('update_repo_group', '/repo_groups/{group_name}',
221 221 action='update', conditions={'method': ['PUT'],
222 222 'function': check_group},
223 223 requirements=URL_NAME_REQUIREMENTS)
224 224
225 225 # EXTRAS REPO GROUP ROUTES
226 226 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
227 227 action='edit',
228 228 conditions={'method': ['GET'], 'function': check_group},
229 229 requirements=URL_NAME_REQUIREMENTS)
230 230 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
231 231 action='edit',
232 232 conditions={'method': ['PUT'], 'function': check_group},
233 233 requirements=URL_NAME_REQUIREMENTS)
234 234
235 235 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
236 236 action='edit_repo_group_advanced',
237 237 conditions={'method': ['GET'], 'function': check_group},
238 238 requirements=URL_NAME_REQUIREMENTS)
239 239 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
240 240 action='edit_repo_group_advanced',
241 241 conditions={'method': ['PUT'], 'function': check_group},
242 242 requirements=URL_NAME_REQUIREMENTS)
243 243
244 244 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
245 245 action='edit_repo_group_perms',
246 246 conditions={'method': ['GET'], 'function': check_group},
247 247 requirements=URL_NAME_REQUIREMENTS)
248 248 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
249 249 action='update_perms',
250 250 conditions={'method': ['PUT'], 'function': check_group},
251 251 requirements=URL_NAME_REQUIREMENTS)
252 252
253 253 m.connect('delete_repo_group', '/repo_groups/{group_name}',
254 254 action='delete', conditions={'method': ['DELETE'],
255 255 'function': check_group},
256 256 requirements=URL_NAME_REQUIREMENTS)
257 257
258 258 # ADMIN USER ROUTES
259 259 with rmap.submapper(path_prefix=ADMIN_PREFIX,
260 260 controller='admin/users') as m:
261 261 m.connect('users', '/users',
262 262 action='create', conditions={'method': ['POST']})
263 263 m.connect('new_user', '/users/new',
264 264 action='new', conditions={'method': ['GET']})
265 265 m.connect('update_user', '/users/{user_id}',
266 266 action='update', conditions={'method': ['PUT']})
267 267 m.connect('delete_user', '/users/{user_id}',
268 268 action='delete', conditions={'method': ['DELETE']})
269 269 m.connect('edit_user', '/users/{user_id}/edit',
270 270 action='edit', conditions={'method': ['GET']}, jsroute=True)
271 271 m.connect('user', '/users/{user_id}',
272 272 action='show', conditions={'method': ['GET']})
273 273 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
274 274 action='reset_password', conditions={'method': ['POST']})
275 275 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
276 276 action='create_personal_repo_group', conditions={'method': ['POST']})
277 277
278 278 # EXTRAS USER ROUTES
279 279 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
280 280 action='edit_advanced', conditions={'method': ['GET']})
281 281 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
282 282 action='update_advanced', conditions={'method': ['PUT']})
283 283
284 284 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
285 285 action='edit_global_perms', conditions={'method': ['GET']})
286 286 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
287 287 action='update_global_perms', conditions={'method': ['PUT']})
288 288
289 289 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
290 290 action='edit_perms_summary', conditions={'method': ['GET']})
291 291
292 292 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
293 293 action='edit_emails', conditions={'method': ['GET']})
294 294 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
295 295 action='add_email', conditions={'method': ['PUT']})
296 296 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
297 297 action='delete_email', conditions={'method': ['DELETE']})
298 298
299 299 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
300 300 action='edit_ips', conditions={'method': ['GET']})
301 301 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
302 302 action='add_ip', conditions={'method': ['PUT']})
303 303 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
304 304 action='delete_ip', conditions={'method': ['DELETE']})
305 305
306 306 # ADMIN USER GROUPS REST ROUTES
307 307 with rmap.submapper(path_prefix=ADMIN_PREFIX,
308 308 controller='admin/user_groups') as m:
309 309 m.connect('users_groups', '/user_groups',
310 310 action='create', conditions={'method': ['POST']})
311 311 m.connect('users_groups', '/user_groups',
312 312 action='index', conditions={'method': ['GET']})
313 313 m.connect('new_users_group', '/user_groups/new',
314 314 action='new', conditions={'method': ['GET']})
315 315 m.connect('update_users_group', '/user_groups/{user_group_id}',
316 316 action='update', conditions={'method': ['PUT']})
317 317 m.connect('delete_users_group', '/user_groups/{user_group_id}',
318 318 action='delete', conditions={'method': ['DELETE']})
319 319 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
320 320 action='edit', conditions={'method': ['GET']},
321 321 function=check_user_group)
322 322
323 323 # EXTRAS USER GROUP ROUTES
324 324 m.connect('edit_user_group_global_perms',
325 325 '/user_groups/{user_group_id}/edit/global_permissions',
326 326 action='edit_global_perms', conditions={'method': ['GET']})
327 327 m.connect('edit_user_group_global_perms',
328 328 '/user_groups/{user_group_id}/edit/global_permissions',
329 329 action='update_global_perms', conditions={'method': ['PUT']})
330 330 m.connect('edit_user_group_perms_summary',
331 331 '/user_groups/{user_group_id}/edit/permissions_summary',
332 332 action='edit_perms_summary', conditions={'method': ['GET']})
333 333
334 334 m.connect('edit_user_group_perms',
335 335 '/user_groups/{user_group_id}/edit/permissions',
336 336 action='edit_perms', conditions={'method': ['GET']})
337 337 m.connect('edit_user_group_perms',
338 338 '/user_groups/{user_group_id}/edit/permissions',
339 339 action='update_perms', conditions={'method': ['PUT']})
340 340
341 341 m.connect('edit_user_group_advanced',
342 342 '/user_groups/{user_group_id}/edit/advanced',
343 343 action='edit_advanced', conditions={'method': ['GET']})
344 344
345 345 m.connect('edit_user_group_advanced_sync',
346 346 '/user_groups/{user_group_id}/edit/advanced/sync',
347 347 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
348 348
349 349 m.connect('edit_user_group_members',
350 350 '/user_groups/{user_group_id}/edit/members', jsroute=True,
351 351 action='user_group_members', conditions={'method': ['GET']})
352 352
353 353 # ADMIN PERMISSIONS ROUTES
354 354 with rmap.submapper(path_prefix=ADMIN_PREFIX,
355 355 controller='admin/permissions') as m:
356 356 m.connect('admin_permissions_application', '/permissions/application',
357 357 action='permission_application_update', conditions={'method': ['POST']})
358 358 m.connect('admin_permissions_application', '/permissions/application',
359 359 action='permission_application', conditions={'method': ['GET']})
360 360
361 361 m.connect('admin_permissions_global', '/permissions/global',
362 362 action='permission_global_update', conditions={'method': ['POST']})
363 363 m.connect('admin_permissions_global', '/permissions/global',
364 364 action='permission_global', conditions={'method': ['GET']})
365 365
366 366 m.connect('admin_permissions_object', '/permissions/object',
367 367 action='permission_objects_update', conditions={'method': ['POST']})
368 368 m.connect('admin_permissions_object', '/permissions/object',
369 369 action='permission_objects', conditions={'method': ['GET']})
370 370
371 371 m.connect('admin_permissions_ips', '/permissions/ips',
372 372 action='permission_ips', conditions={'method': ['POST']})
373 373 m.connect('admin_permissions_ips', '/permissions/ips',
374 374 action='permission_ips', conditions={'method': ['GET']})
375 375
376 376 m.connect('admin_permissions_overview', '/permissions/overview',
377 377 action='permission_perms', conditions={'method': ['GET']})
378 378
379 379 # ADMIN DEFAULTS REST ROUTES
380 380 with rmap.submapper(path_prefix=ADMIN_PREFIX,
381 381 controller='admin/defaults') as m:
382 382 m.connect('admin_defaults_repositories', '/defaults/repositories',
383 383 action='update_repository_defaults', conditions={'method': ['POST']})
384 384 m.connect('admin_defaults_repositories', '/defaults/repositories',
385 385 action='index', conditions={'method': ['GET']})
386 386
387 387 # ADMIN DEBUG STYLE ROUTES
388 388 if str2bool(config.get('debug_style')):
389 389 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
390 390 controller='debug_style') as m:
391 391 m.connect('debug_style_home', '',
392 392 action='index', conditions={'method': ['GET']})
393 393 m.connect('debug_style_template', '/t/{t_path}',
394 394 action='template', conditions={'method': ['GET']})
395 395
396 396 # ADMIN SETTINGS ROUTES
397 397 with rmap.submapper(path_prefix=ADMIN_PREFIX,
398 398 controller='admin/settings') as m:
399 399
400 400 # default
401 401 m.connect('admin_settings', '/settings',
402 402 action='settings_global_update',
403 403 conditions={'method': ['POST']})
404 404 m.connect('admin_settings', '/settings',
405 405 action='settings_global', conditions={'method': ['GET']})
406 406
407 407 m.connect('admin_settings_vcs', '/settings/vcs',
408 408 action='settings_vcs_update',
409 409 conditions={'method': ['POST']})
410 410 m.connect('admin_settings_vcs', '/settings/vcs',
411 411 action='settings_vcs',
412 412 conditions={'method': ['GET']})
413 413 m.connect('admin_settings_vcs', '/settings/vcs',
414 414 action='delete_svn_pattern',
415 415 conditions={'method': ['DELETE']})
416 416
417 417 m.connect('admin_settings_mapping', '/settings/mapping',
418 418 action='settings_mapping_update',
419 419 conditions={'method': ['POST']})
420 420 m.connect('admin_settings_mapping', '/settings/mapping',
421 421 action='settings_mapping', conditions={'method': ['GET']})
422 422
423 423 m.connect('admin_settings_global', '/settings/global',
424 424 action='settings_global_update',
425 425 conditions={'method': ['POST']})
426 426 m.connect('admin_settings_global', '/settings/global',
427 427 action='settings_global', conditions={'method': ['GET']})
428 428
429 429 m.connect('admin_settings_visual', '/settings/visual',
430 430 action='settings_visual_update',
431 431 conditions={'method': ['POST']})
432 432 m.connect('admin_settings_visual', '/settings/visual',
433 433 action='settings_visual', conditions={'method': ['GET']})
434 434
435 435 m.connect('admin_settings_issuetracker',
436 436 '/settings/issue-tracker', action='settings_issuetracker',
437 437 conditions={'method': ['GET']})
438 438 m.connect('admin_settings_issuetracker_save',
439 439 '/settings/issue-tracker/save',
440 440 action='settings_issuetracker_save',
441 441 conditions={'method': ['POST']})
442 442 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
443 443 action='settings_issuetracker_test',
444 444 conditions={'method': ['POST']})
445 445 m.connect('admin_issuetracker_delete',
446 446 '/settings/issue-tracker/delete',
447 447 action='settings_issuetracker_delete',
448 448 conditions={'method': ['DELETE']})
449 449
450 450 m.connect('admin_settings_email', '/settings/email',
451 451 action='settings_email_update',
452 452 conditions={'method': ['POST']})
453 453 m.connect('admin_settings_email', '/settings/email',
454 454 action='settings_email', conditions={'method': ['GET']})
455 455
456 456 m.connect('admin_settings_hooks', '/settings/hooks',
457 457 action='settings_hooks_update',
458 458 conditions={'method': ['POST', 'DELETE']})
459 459 m.connect('admin_settings_hooks', '/settings/hooks',
460 460 action='settings_hooks', conditions={'method': ['GET']})
461 461
462 462 m.connect('admin_settings_search', '/settings/search',
463 463 action='settings_search', conditions={'method': ['GET']})
464 464
465 465 m.connect('admin_settings_supervisor', '/settings/supervisor',
466 466 action='settings_supervisor', conditions={'method': ['GET']})
467 467 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
468 468 action='settings_supervisor_log', conditions={'method': ['GET']})
469 469
470 470 m.connect('admin_settings_labs', '/settings/labs',
471 471 action='settings_labs_update',
472 472 conditions={'method': ['POST']})
473 473 m.connect('admin_settings_labs', '/settings/labs',
474 474 action='settings_labs', conditions={'method': ['GET']})
475 475
476 476 # ADMIN MY ACCOUNT
477 477 with rmap.submapper(path_prefix=ADMIN_PREFIX,
478 478 controller='admin/my_account') as m:
479 479
480 480 m.connect('my_account_edit', '/my_account/edit',
481 481 action='my_account_edit', conditions={'method': ['GET']})
482 482 m.connect('my_account', '/my_account/update',
483 483 action='my_account_update', conditions={'method': ['POST']})
484 484
485 485 # NOTE(marcink): this needs to be kept for password force flag to be
486 486 # handler, remove after migration to pyramid
487 487 m.connect('my_account_password', '/my_account/password',
488 488 action='my_account_password', conditions={'method': ['GET']})
489 489
490 490 m.connect('my_account_repos', '/my_account/repos',
491 491 action='my_account_repos', conditions={'method': ['GET']})
492 492
493 493 m.connect('my_account_watched', '/my_account/watched',
494 494 action='my_account_watched', conditions={'method': ['GET']})
495 495
496 496 m.connect('my_account_pullrequests', '/my_account/pull_requests',
497 497 action='my_account_pullrequests', conditions={'method': ['GET']})
498 498
499 499 m.connect('my_account_perms', '/my_account/perms',
500 500 action='my_account_perms', conditions={'method': ['GET']})
501 501
502 502 m.connect('my_account_emails', '/my_account/emails',
503 503 action='my_account_emails', conditions={'method': ['GET']})
504 504 m.connect('my_account_emails', '/my_account/emails',
505 505 action='my_account_emails_add', conditions={'method': ['POST']})
506 506 m.connect('my_account_emails', '/my_account/emails',
507 507 action='my_account_emails_delete', conditions={'method': ['DELETE']})
508 508
509 509 m.connect('my_account_notifications', '/my_account/notifications',
510 510 action='my_notifications',
511 511 conditions={'method': ['GET']})
512 512 m.connect('my_account_notifications_toggle_visibility',
513 513 '/my_account/toggle_visibility',
514 514 action='my_notifications_toggle_visibility',
515 515 conditions={'method': ['POST']})
516 516 m.connect('my_account_notifications_test_channelstream',
517 517 '/my_account/test_channelstream',
518 518 action='my_account_notifications_test_channelstream',
519 519 conditions={'method': ['POST']})
520 520
521 521 # NOTIFICATION REST ROUTES
522 522 with rmap.submapper(path_prefix=ADMIN_PREFIX,
523 523 controller='admin/notifications') as m:
524 524 m.connect('notifications', '/notifications',
525 525 action='index', conditions={'method': ['GET']})
526 526 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
527 527 action='mark_all_read', conditions={'method': ['POST']})
528 528 m.connect('/notifications/{notification_id}',
529 529 action='update', conditions={'method': ['PUT']})
530 530 m.connect('/notifications/{notification_id}',
531 531 action='delete', conditions={'method': ['DELETE']})
532 532 m.connect('notification', '/notifications/{notification_id}',
533 533 action='show', conditions={'method': ['GET']})
534 534
535 535 # ADMIN GIST
536 536 with rmap.submapper(path_prefix=ADMIN_PREFIX,
537 537 controller='admin/gists') as m:
538 538 m.connect('gists', '/gists',
539 539 action='create', conditions={'method': ['POST']})
540 540 m.connect('gists', '/gists', jsroute=True,
541 541 action='index', conditions={'method': ['GET']})
542 542 m.connect('new_gist', '/gists/new', jsroute=True,
543 543 action='new', conditions={'method': ['GET']})
544 544
545 545 m.connect('/gists/{gist_id}',
546 546 action='delete', conditions={'method': ['DELETE']})
547 547 m.connect('edit_gist', '/gists/{gist_id}/edit',
548 548 action='edit_form', conditions={'method': ['GET']})
549 549 m.connect('edit_gist', '/gists/{gist_id}/edit',
550 550 action='edit', conditions={'method': ['POST']})
551 551 m.connect(
552 552 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
553 553 action='check_revision', conditions={'method': ['GET']})
554 554
555 555 m.connect('gist', '/gists/{gist_id}',
556 556 action='show', conditions={'method': ['GET']})
557 557 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
558 558 revision='tip',
559 559 action='show', conditions={'method': ['GET']})
560 560 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
561 561 revision='tip',
562 562 action='show', conditions={'method': ['GET']})
563 563 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
564 564 revision='tip',
565 565 action='show', conditions={'method': ['GET']},
566 566 requirements=URL_NAME_REQUIREMENTS)
567 567
568 568 # ADMIN MAIN PAGES
569 569 with rmap.submapper(path_prefix=ADMIN_PREFIX,
570 570 controller='admin/admin') as m:
571 571 m.connect('admin_home', '', action='index')
572 572 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
573 573 action='add_repo')
574 574 m.connect(
575 575 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
576 576 action='pull_requests')
577 577 m.connect(
578 578 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
579 579 action='pull_requests')
580 580 m.connect(
581 581 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
582 582 action='pull_requests')
583 583
584 584 # USER JOURNAL
585 585 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
586 586 controller='journal', action='index')
587 587 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
588 588 controller='journal', action='journal_rss')
589 589 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
590 590 controller='journal', action='journal_atom')
591 591
592 592 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
593 593 controller='journal', action='public_journal')
594 594
595 595 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
596 596 controller='journal', action='public_journal_rss')
597 597
598 598 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
599 599 controller='journal', action='public_journal_rss')
600 600
601 601 rmap.connect('public_journal_atom',
602 602 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
603 603 action='public_journal_atom')
604 604
605 605 rmap.connect('public_journal_atom_old',
606 606 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
607 607 action='public_journal_atom')
608 608
609 609 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
610 610 controller='journal', action='toggle_following', jsroute=True,
611 611 conditions={'method': ['POST']})
612 612
613 613 # FEEDS
614 614 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
615 615 controller='feed', action='rss',
616 616 conditions={'function': check_repo},
617 617 requirements=URL_NAME_REQUIREMENTS)
618 618
619 619 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
620 620 controller='feed', action='atom',
621 621 conditions={'function': check_repo},
622 622 requirements=URL_NAME_REQUIREMENTS)
623 623
624 624 #==========================================================================
625 625 # REPOSITORY ROUTES
626 626 #==========================================================================
627 627
628 628 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
629 629 controller='admin/repos', action='repo_creating',
630 630 requirements=URL_NAME_REQUIREMENTS)
631 631 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
632 632 controller='admin/repos', action='repo_check',
633 633 requirements=URL_NAME_REQUIREMENTS)
634 634
635 635 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
636 636 controller='summary', action='repo_stats',
637 637 conditions={'function': check_repo},
638 638 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
639 639
640 640 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
641 641 controller='summary', action='repo_refs_data',
642 642 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
643 643 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
644 644 controller='summary', action='repo_refs_changelog_data',
645 645 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
646 646 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
647 647 controller='summary', action='repo_default_reviewers_data',
648 648 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
649 649
650 650 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
651 651 controller='changeset', revision='tip',
652 652 conditions={'function': check_repo},
653 653 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
654 654 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
655 655 controller='changeset', revision='tip', action='changeset_children',
656 656 conditions={'function': check_repo},
657 657 requirements=URL_NAME_REQUIREMENTS)
658 658 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
659 659 controller='changeset', revision='tip', action='changeset_parents',
660 660 conditions={'function': check_repo},
661 661 requirements=URL_NAME_REQUIREMENTS)
662 662
663 663 # repo edit options
664 664 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
665 665 controller='admin/repos', action='edit_fields',
666 666 conditions={'method': ['GET'], 'function': check_repo},
667 667 requirements=URL_NAME_REQUIREMENTS)
668 668 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
669 669 controller='admin/repos', action='create_repo_field',
670 670 conditions={'method': ['PUT'], 'function': check_repo},
671 671 requirements=URL_NAME_REQUIREMENTS)
672 672 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
673 673 controller='admin/repos', action='delete_repo_field',
674 674 conditions={'method': ['DELETE'], 'function': check_repo},
675 675 requirements=URL_NAME_REQUIREMENTS)
676 676
677 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
678 controller='admin/repos', action='edit_advanced',
679 conditions={'method': ['GET'], 'function': check_repo},
680 requirements=URL_NAME_REQUIREMENTS)
681
682 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
683 controller='admin/repos', action='edit_advanced_locking',
684 conditions={'method': ['PUT'], 'function': check_repo},
685 requirements=URL_NAME_REQUIREMENTS)
686 677 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
687 678 controller='admin/repos', action='toggle_locking',
688 679 conditions={'method': ['GET'], 'function': check_repo},
689 680 requirements=URL_NAME_REQUIREMENTS)
690 681
691 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
692 controller='admin/repos', action='edit_advanced_journal',
693 conditions={'method': ['PUT'], 'function': check_repo},
694 requirements=URL_NAME_REQUIREMENTS)
695
696 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
697 controller='admin/repos', action='edit_advanced_fork',
698 conditions={'method': ['PUT'], 'function': check_repo},
699 requirements=URL_NAME_REQUIREMENTS)
700
701 682 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
702 683 controller='admin/repos', action='edit_remote_form',
703 684 conditions={'method': ['GET'], 'function': check_repo},
704 685 requirements=URL_NAME_REQUIREMENTS)
705 686 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
706 687 controller='admin/repos', action='edit_remote',
707 688 conditions={'method': ['PUT'], 'function': check_repo},
708 689 requirements=URL_NAME_REQUIREMENTS)
709 690
710 691 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
711 692 controller='admin/repos', action='edit_statistics_form',
712 693 conditions={'method': ['GET'], 'function': check_repo},
713 694 requirements=URL_NAME_REQUIREMENTS)
714 695 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
715 696 controller='admin/repos', action='edit_statistics',
716 697 conditions={'method': ['PUT'], 'function': check_repo},
717 698 requirements=URL_NAME_REQUIREMENTS)
718 699 rmap.connect('repo_settings_issuetracker',
719 700 '/{repo_name}/settings/issue-tracker',
720 701 controller='admin/repos', action='repo_issuetracker',
721 702 conditions={'method': ['GET'], 'function': check_repo},
722 703 requirements=URL_NAME_REQUIREMENTS)
723 704 rmap.connect('repo_issuetracker_test',
724 705 '/{repo_name}/settings/issue-tracker/test',
725 706 controller='admin/repos', action='repo_issuetracker_test',
726 707 conditions={'method': ['POST'], 'function': check_repo},
727 708 requirements=URL_NAME_REQUIREMENTS)
728 709 rmap.connect('repo_issuetracker_delete',
729 710 '/{repo_name}/settings/issue-tracker/delete',
730 711 controller='admin/repos', action='repo_issuetracker_delete',
731 712 conditions={'method': ['DELETE'], 'function': check_repo},
732 713 requirements=URL_NAME_REQUIREMENTS)
733 714 rmap.connect('repo_issuetracker_save',
734 715 '/{repo_name}/settings/issue-tracker/save',
735 716 controller='admin/repos', action='repo_issuetracker_save',
736 717 conditions={'method': ['POST'], 'function': check_repo},
737 718 requirements=URL_NAME_REQUIREMENTS)
738 719 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
739 720 controller='admin/repos', action='repo_settings_vcs_update',
740 721 conditions={'method': ['POST'], 'function': check_repo},
741 722 requirements=URL_NAME_REQUIREMENTS)
742 723 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
743 724 controller='admin/repos', action='repo_settings_vcs',
744 725 conditions={'method': ['GET'], 'function': check_repo},
745 726 requirements=URL_NAME_REQUIREMENTS)
746 727 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
747 728 controller='admin/repos', action='repo_delete_svn_pattern',
748 729 conditions={'method': ['DELETE'], 'function': check_repo},
749 730 requirements=URL_NAME_REQUIREMENTS)
750 731 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
751 732 controller='admin/repos', action='repo_settings_pullrequest',
752 733 conditions={'method': ['GET', 'POST'], 'function': check_repo},
753 734 requirements=URL_NAME_REQUIREMENTS)
754 735
755 736 # still working url for backward compat.
756 737 rmap.connect('raw_changeset_home_depraced',
757 738 '/{repo_name}/raw-changeset/{revision}',
758 739 controller='changeset', action='changeset_raw',
759 740 revision='tip', conditions={'function': check_repo},
760 741 requirements=URL_NAME_REQUIREMENTS)
761 742
762 743 # new URLs
763 744 rmap.connect('changeset_raw_home',
764 745 '/{repo_name}/changeset-diff/{revision}',
765 746 controller='changeset', action='changeset_raw',
766 747 revision='tip', conditions={'function': check_repo},
767 748 requirements=URL_NAME_REQUIREMENTS)
768 749
769 750 rmap.connect('changeset_patch_home',
770 751 '/{repo_name}/changeset-patch/{revision}',
771 752 controller='changeset', action='changeset_patch',
772 753 revision='tip', conditions={'function': check_repo},
773 754 requirements=URL_NAME_REQUIREMENTS)
774 755
775 756 rmap.connect('changeset_download_home',
776 757 '/{repo_name}/changeset-download/{revision}',
777 758 controller='changeset', action='changeset_download',
778 759 revision='tip', conditions={'function': check_repo},
779 760 requirements=URL_NAME_REQUIREMENTS)
780 761
781 762 rmap.connect('changeset_comment',
782 763 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
783 764 controller='changeset', revision='tip', action='comment',
784 765 conditions={'function': check_repo},
785 766 requirements=URL_NAME_REQUIREMENTS)
786 767
787 768 rmap.connect('changeset_comment_preview',
788 769 '/{repo_name}/changeset/comment/preview', jsroute=True,
789 770 controller='changeset', action='preview_comment',
790 771 conditions={'function': check_repo, 'method': ['POST']},
791 772 requirements=URL_NAME_REQUIREMENTS)
792 773
793 774 rmap.connect('changeset_comment_delete',
794 775 '/{repo_name}/changeset/comment/{comment_id}/delete',
795 776 controller='changeset', action='delete_comment',
796 777 conditions={'function': check_repo, 'method': ['DELETE']},
797 778 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
798 779
799 780 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
800 781 controller='changeset', action='changeset_info',
801 782 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
802 783
803 784 rmap.connect('compare_home',
804 785 '/{repo_name}/compare',
805 786 controller='compare', action='index',
806 787 conditions={'function': check_repo},
807 788 requirements=URL_NAME_REQUIREMENTS)
808 789
809 790 rmap.connect('compare_url',
810 791 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
811 792 controller='compare', action='compare',
812 793 conditions={'function': check_repo},
813 794 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
814 795
815 796 rmap.connect('pullrequest_home',
816 797 '/{repo_name}/pull-request/new', controller='pullrequests',
817 798 action='index', conditions={'function': check_repo,
818 799 'method': ['GET']},
819 800 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
820 801
821 802 rmap.connect('pullrequest',
822 803 '/{repo_name}/pull-request/new', controller='pullrequests',
823 804 action='create', conditions={'function': check_repo,
824 805 'method': ['POST']},
825 806 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
826 807
827 808 rmap.connect('pullrequest_repo_refs',
828 809 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
829 810 controller='pullrequests',
830 811 action='get_repo_refs',
831 812 conditions={'function': check_repo, 'method': ['GET']},
832 813 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
833 814
834 815 rmap.connect('pullrequest_repo_destinations',
835 816 '/{repo_name}/pull-request/repo-destinations',
836 817 controller='pullrequests',
837 818 action='get_repo_destinations',
838 819 conditions={'function': check_repo, 'method': ['GET']},
839 820 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
840 821
841 822 rmap.connect('pullrequest_show',
842 823 '/{repo_name}/pull-request/{pull_request_id}',
843 824 controller='pullrequests',
844 825 action='show', conditions={'function': check_repo,
845 826 'method': ['GET']},
846 827 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
847 828
848 829 rmap.connect('pullrequest_update',
849 830 '/{repo_name}/pull-request/{pull_request_id}',
850 831 controller='pullrequests',
851 832 action='update', conditions={'function': check_repo,
852 833 'method': ['PUT']},
853 834 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
854 835
855 836 rmap.connect('pullrequest_merge',
856 837 '/{repo_name}/pull-request/{pull_request_id}',
857 838 controller='pullrequests',
858 839 action='merge', conditions={'function': check_repo,
859 840 'method': ['POST']},
860 841 requirements=URL_NAME_REQUIREMENTS)
861 842
862 843 rmap.connect('pullrequest_delete',
863 844 '/{repo_name}/pull-request/{pull_request_id}',
864 845 controller='pullrequests',
865 846 action='delete', conditions={'function': check_repo,
866 847 'method': ['DELETE']},
867 848 requirements=URL_NAME_REQUIREMENTS)
868 849
869 850 rmap.connect('pullrequest_show_all',
870 851 '/{repo_name}/pull-request',
871 852 controller='pullrequests',
872 853 action='show_all', conditions={'function': check_repo,
873 854 'method': ['GET']},
874 855 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
875 856
876 857 rmap.connect('pullrequest_comment',
877 858 '/{repo_name}/pull-request-comment/{pull_request_id}',
878 859 controller='pullrequests',
879 860 action='comment', conditions={'function': check_repo,
880 861 'method': ['POST']},
881 862 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
882 863
883 864 rmap.connect('pullrequest_comment_delete',
884 865 '/{repo_name}/pull-request-comment/{comment_id}/delete',
885 866 controller='pullrequests', action='delete_comment',
886 867 conditions={'function': check_repo, 'method': ['DELETE']},
887 868 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
888 869
889 870 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
890 871 controller='summary', conditions={'function': check_repo},
891 872 requirements=URL_NAME_REQUIREMENTS)
892 873
893 874 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
894 875 controller='changelog', conditions={'function': check_repo},
895 876 requirements=URL_NAME_REQUIREMENTS)
896 877
897 878 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
898 879 controller='changelog', action='changelog_summary',
899 880 conditions={'function': check_repo},
900 881 requirements=URL_NAME_REQUIREMENTS)
901 882
902 883 rmap.connect('changelog_file_home',
903 884 '/{repo_name}/changelog/{revision}/{f_path}',
904 885 controller='changelog', f_path=None,
905 886 conditions={'function': check_repo},
906 887 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
907 888
908 889 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
909 890 controller='changelog', action='changelog_elements',
910 891 conditions={'function': check_repo},
911 892 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
912 893
913 894 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
914 895 controller='files', revision='tip', f_path='',
915 896 conditions={'function': check_repo},
916 897 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
917 898
918 899 rmap.connect('files_home_simple_catchrev',
919 900 '/{repo_name}/files/{revision}',
920 901 controller='files', revision='tip', f_path='',
921 902 conditions={'function': check_repo},
922 903 requirements=URL_NAME_REQUIREMENTS)
923 904
924 905 rmap.connect('files_home_simple_catchall',
925 906 '/{repo_name}/files',
926 907 controller='files', revision='tip', f_path='',
927 908 conditions={'function': check_repo},
928 909 requirements=URL_NAME_REQUIREMENTS)
929 910
930 911 rmap.connect('files_history_home',
931 912 '/{repo_name}/history/{revision}/{f_path}',
932 913 controller='files', action='history', revision='tip', f_path='',
933 914 conditions={'function': check_repo},
934 915 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
935 916
936 917 rmap.connect('files_authors_home',
937 918 '/{repo_name}/authors/{revision}/{f_path}',
938 919 controller='files', action='authors', revision='tip', f_path='',
939 920 conditions={'function': check_repo},
940 921 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
941 922
942 923 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
943 924 controller='files', action='diff', f_path='',
944 925 conditions={'function': check_repo},
945 926 requirements=URL_NAME_REQUIREMENTS)
946 927
947 928 rmap.connect('files_diff_2way_home',
948 929 '/{repo_name}/diff-2way/{f_path}',
949 930 controller='files', action='diff_2way', f_path='',
950 931 conditions={'function': check_repo},
951 932 requirements=URL_NAME_REQUIREMENTS)
952 933
953 934 rmap.connect('files_rawfile_home',
954 935 '/{repo_name}/rawfile/{revision}/{f_path}',
955 936 controller='files', action='rawfile', revision='tip',
956 937 f_path='', conditions={'function': check_repo},
957 938 requirements=URL_NAME_REQUIREMENTS)
958 939
959 940 rmap.connect('files_raw_home',
960 941 '/{repo_name}/raw/{revision}/{f_path}',
961 942 controller='files', action='raw', revision='tip', f_path='',
962 943 conditions={'function': check_repo},
963 944 requirements=URL_NAME_REQUIREMENTS)
964 945
965 946 rmap.connect('files_render_home',
966 947 '/{repo_name}/render/{revision}/{f_path}',
967 948 controller='files', action='index', revision='tip', f_path='',
968 949 rendered=True, conditions={'function': check_repo},
969 950 requirements=URL_NAME_REQUIREMENTS)
970 951
971 952 rmap.connect('files_annotate_home',
972 953 '/{repo_name}/annotate/{revision}/{f_path}',
973 954 controller='files', action='index', revision='tip',
974 955 f_path='', annotate=True, conditions={'function': check_repo},
975 956 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
976 957
977 958 rmap.connect('files_annotate_previous',
978 959 '/{repo_name}/annotate-previous/{revision}/{f_path}',
979 960 controller='files', action='annotate_previous', revision='tip',
980 961 f_path='', annotate=True, conditions={'function': check_repo},
981 962 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
982 963
983 964 rmap.connect('files_edit',
984 965 '/{repo_name}/edit/{revision}/{f_path}',
985 966 controller='files', action='edit', revision='tip',
986 967 f_path='',
987 968 conditions={'function': check_repo, 'method': ['POST']},
988 969 requirements=URL_NAME_REQUIREMENTS)
989 970
990 971 rmap.connect('files_edit_home',
991 972 '/{repo_name}/edit/{revision}/{f_path}',
992 973 controller='files', action='edit_home', revision='tip',
993 974 f_path='', conditions={'function': check_repo},
994 975 requirements=URL_NAME_REQUIREMENTS)
995 976
996 977 rmap.connect('files_add',
997 978 '/{repo_name}/add/{revision}/{f_path}',
998 979 controller='files', action='add', revision='tip',
999 980 f_path='',
1000 981 conditions={'function': check_repo, 'method': ['POST']},
1001 982 requirements=URL_NAME_REQUIREMENTS)
1002 983
1003 984 rmap.connect('files_add_home',
1004 985 '/{repo_name}/add/{revision}/{f_path}',
1005 986 controller='files', action='add_home', revision='tip',
1006 987 f_path='', conditions={'function': check_repo},
1007 988 requirements=URL_NAME_REQUIREMENTS)
1008 989
1009 990 rmap.connect('files_delete',
1010 991 '/{repo_name}/delete/{revision}/{f_path}',
1011 992 controller='files', action='delete', revision='tip',
1012 993 f_path='',
1013 994 conditions={'function': check_repo, 'method': ['POST']},
1014 995 requirements=URL_NAME_REQUIREMENTS)
1015 996
1016 997 rmap.connect('files_delete_home',
1017 998 '/{repo_name}/delete/{revision}/{f_path}',
1018 999 controller='files', action='delete_home', revision='tip',
1019 1000 f_path='', conditions={'function': check_repo},
1020 1001 requirements=URL_NAME_REQUIREMENTS)
1021 1002
1022 1003 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1023 1004 controller='files', action='archivefile',
1024 1005 conditions={'function': check_repo},
1025 1006 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1026 1007
1027 1008 rmap.connect('files_nodelist_home',
1028 1009 '/{repo_name}/nodelist/{revision}/{f_path}',
1029 1010 controller='files', action='nodelist',
1030 1011 conditions={'function': check_repo},
1031 1012 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1032 1013
1033 1014 rmap.connect('files_nodetree_full',
1034 1015 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1035 1016 controller='files', action='nodetree_full',
1036 1017 conditions={'function': check_repo},
1037 1018 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1038 1019
1039 1020 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1040 1021 controller='forks', action='fork_create',
1041 1022 conditions={'function': check_repo, 'method': ['POST']},
1042 1023 requirements=URL_NAME_REQUIREMENTS)
1043 1024
1044 1025 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1045 1026 controller='forks', action='fork',
1046 1027 conditions={'function': check_repo},
1047 1028 requirements=URL_NAME_REQUIREMENTS)
1048 1029
1049 1030 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1050 1031 controller='forks', action='forks',
1051 1032 conditions={'function': check_repo},
1052 1033 requirements=URL_NAME_REQUIREMENTS)
1053 1034
1054 1035 # must be here for proper group/repo catching pattern
1055 1036 _connect_with_slash(
1056 1037 rmap, 'repo_group_home', '/{group_name}',
1057 1038 controller='home', action='index_repo_group',
1058 1039 conditions={'function': check_group},
1059 1040 requirements=URL_NAME_REQUIREMENTS)
1060 1041
1061 1042 # catch all, at the end
1062 1043 _connect_with_slash(
1063 1044 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1064 1045 controller='summary', action='index',
1065 1046 conditions={'function': check_repo},
1066 1047 requirements=URL_NAME_REQUIREMENTS)
1067 1048
1068 1049 return rmap
1069 1050
1070 1051
1071 1052 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1072 1053 """
1073 1054 Connect a route with an optional trailing slash in `path`.
1074 1055 """
1075 1056 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1076 1057 mapper.connect(name, path, *args, **kwargs)
@@ -1,754 +1,610 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Repositories controller for RhodeCode
24 24 """
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 import formencode
30 30 from formencode import htmlfill
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import redirect
33 33 from pylons.i18n.translation import _
34 34 from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
35 35
36 36 import rhodecode
37 37 from rhodecode.lib import auth, helpers as h
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, HasPermissionAllDecorator,
40 40 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
41 41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.ext_json import json
44 44 from rhodecode.lib.exceptions import AttachedForksError
45 45 from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
46 46 from rhodecode.lib.utils2 import safe_int, str2bool
47 47 from rhodecode.lib.vcs import RepositoryError
48 48 from rhodecode.model.db import (
49 49 User, Repository, UserFollowing, RepoGroup, RepositoryField)
50 50 from rhodecode.model.forms import (
51 51 RepoForm, RepoFieldForm, RepoPermsForm, RepoVcsSettingsForm,
52 52 IssueTrackerPatternsForm)
53 53 from rhodecode.model.meta import Session
54 54 from rhodecode.model.repo import RepoModel
55 55 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
56 56 from rhodecode.model.settings import (
57 57 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
58 58 SettingNotFound)
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class ReposController(BaseRepoController):
64 64 """
65 65 REST Controller styled on the Atom Publishing Protocol"""
66 66 # To properly map this controller, ensure your config/routing.py
67 67 # file has a resource setup:
68 68 # map.resource('repo', 'repos')
69 69
70 70 @LoginRequired()
71 71 def __before__(self):
72 72 super(ReposController, self).__before__()
73 73
74 74 def _load_repo(self, repo_name):
75 75 repo_obj = Repository.get_by_repo_name(repo_name)
76 76
77 77 if repo_obj is None:
78 78 h.not_mapped_error(repo_name)
79 79 return redirect(url('repos'))
80 80
81 81 return repo_obj
82 82
83 83 def __load_defaults(self, repo=None):
84 84 acl_groups = RepoGroupList(RepoGroup.query().all(),
85 85 perm_set=['group.write', 'group.admin'])
86 86 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
87 87 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
88 88
89 89 # in case someone no longer have a group.write access to a repository
90 90 # pre fill the list with this entry, we don't care if this is the same
91 91 # but it will allow saving repo data properly.
92 92
93 93 repo_group = None
94 94 if repo:
95 95 repo_group = repo.group
96 96 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
97 97 c.repo_groups_choices.append(unicode(repo_group.group_id))
98 98 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
99 99
100 100 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
101 101 c.landing_revs_choices = choices
102 102
103 103 def __load_data(self, repo_name=None):
104 104 """
105 105 Load defaults settings for edit, and update
106 106
107 107 :param repo_name:
108 108 """
109 109 c.repo_info = self._load_repo(repo_name)
110 110 self.__load_defaults(c.repo_info)
111 111
112 112 # override defaults for exact repo info here git/hg etc
113 113 if not c.repository_requirements_missing:
114 114 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
115 115 c.repo_info)
116 116 c.landing_revs_choices = choices
117 117 defaults = RepoModel()._get_defaults(repo_name)
118 118
119 119 return defaults
120 120
121 121 def _log_creation_exception(self, e, repo_name):
122 122 reason = None
123 123 if len(e.args) == 2:
124 124 reason = e.args[1]
125 125
126 126 if reason == 'INVALID_CERTIFICATE':
127 127 log.exception(
128 128 'Exception creating a repository: invalid certificate')
129 129 msg = (_('Error creating repository %s: invalid certificate')
130 130 % repo_name)
131 131 else:
132 132 log.exception("Exception creating a repository")
133 133 msg = (_('Error creating repository %s')
134 134 % repo_name)
135 135
136 136 return msg
137 137
138 138 @NotAnonymous()
139 139 def index(self, format='html'):
140 140 """GET /repos: All items in the collection"""
141 141 # url('repos')
142 142
143 143 repo_list = Repository.get_all_repos()
144 144 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
145 145 repos_data = RepoModel().get_repos_as_dict(
146 146 repo_list=c.repo_list, admin=True, super_user_actions=True)
147 147 # json used to render the grid
148 148 c.data = json.dumps(repos_data)
149 149
150 150 return render('admin/repos/repos.mako')
151 151
152 152 # perms check inside
153 153 @NotAnonymous()
154 154 @auth.CSRFRequired()
155 155 def create(self):
156 156 """
157 157 POST /repos: Create a new item"""
158 158 # url('repos')
159 159
160 160 self.__load_defaults()
161 161 form_result = {}
162 162 task_id = None
163 163 c.personal_repo_group = c.rhodecode_user.personal_repo_group
164 164 try:
165 165 # CanWriteToGroup validators checks permissions of this POST
166 166 form_result = RepoForm(repo_groups=c.repo_groups_choices,
167 167 landing_revs=c.landing_revs_choices)()\
168 168 .to_python(dict(request.POST))
169 169
170 170 # create is done sometimes async on celery, db transaction
171 171 # management is handled there.
172 172 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
173 173 from celery.result import BaseAsyncResult
174 174 if isinstance(task, BaseAsyncResult):
175 175 task_id = task.task_id
176 176 except formencode.Invalid as errors:
177 177 return htmlfill.render(
178 178 render('admin/repos/repo_add.mako'),
179 179 defaults=errors.value,
180 180 errors=errors.error_dict or {},
181 181 prefix_error=False,
182 182 encoding="UTF-8",
183 183 force_defaults=False)
184 184
185 185 except Exception as e:
186 186 msg = self._log_creation_exception(e, form_result.get('repo_name'))
187 187 h.flash(msg, category='error')
188 188 return redirect(url('home'))
189 189
190 190 return redirect(h.url('repo_creating_home',
191 191 repo_name=form_result['repo_name_full'],
192 192 task_id=task_id))
193 193
194 194 # perms check inside
195 195 @NotAnonymous()
196 196 def create_repository(self):
197 197 """GET /_admin/create_repository: Form to create a new item"""
198 198 new_repo = request.GET.get('repo', '')
199 199 parent_group = safe_int(request.GET.get('parent_group'))
200 200 _gr = RepoGroup.get(parent_group)
201 201
202 202 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
203 203 # you're not super admin nor have global create permissions,
204 204 # but maybe you have at least write permission to a parent group ?
205 205
206 206 gr_name = _gr.group_name if _gr else None
207 207 # create repositories with write permission on group is set to true
208 208 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
209 209 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
210 210 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
211 211 if not (group_admin or (group_write and create_on_write)):
212 212 raise HTTPForbidden
213 213
214 214 acl_groups = RepoGroupList(RepoGroup.query().all(),
215 215 perm_set=['group.write', 'group.admin'])
216 216 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
217 217 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
218 218 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
219 219 c.personal_repo_group = c.rhodecode_user.personal_repo_group
220 220 c.new_repo = repo_name_slug(new_repo)
221 221
222 222 # apply the defaults from defaults page
223 223 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
224 224 # set checkbox to autochecked
225 225 defaults['repo_copy_permissions'] = True
226 226
227 227 parent_group_choice = '-1'
228 228 if not c.rhodecode_user.is_admin and c.rhodecode_user.personal_repo_group:
229 229 parent_group_choice = c.rhodecode_user.personal_repo_group
230 230
231 231 if parent_group and _gr:
232 232 if parent_group in [x[0] for x in c.repo_groups]:
233 233 parent_group_choice = unicode(parent_group)
234 234
235 235 defaults.update({'repo_group': parent_group_choice})
236 236
237 237 return htmlfill.render(
238 238 render('admin/repos/repo_add.mako'),
239 239 defaults=defaults,
240 240 errors={},
241 241 prefix_error=False,
242 242 encoding="UTF-8",
243 243 force_defaults=False
244 244 )
245 245
246 246 @NotAnonymous()
247 247 def repo_creating(self, repo_name):
248 248 c.repo = repo_name
249 249 c.task_id = request.GET.get('task_id')
250 250 if not c.repo:
251 251 raise HTTPNotFound()
252 252 return render('admin/repos/repo_creating.mako')
253 253
254 254 @NotAnonymous()
255 255 @jsonify
256 256 def repo_check(self, repo_name):
257 257 c.repo = repo_name
258 258 task_id = request.GET.get('task_id')
259 259
260 260 if task_id and task_id not in ['None']:
261 261 import rhodecode
262 262 from celery.result import AsyncResult
263 263 if rhodecode.CELERY_ENABLED:
264 264 task = AsyncResult(task_id)
265 265 if task.failed():
266 266 msg = self._log_creation_exception(task.result, c.repo)
267 267 h.flash(msg, category='error')
268 268 return redirect(url('home'), code=501)
269 269
270 270 repo = Repository.get_by_repo_name(repo_name)
271 271 if repo and repo.repo_state == Repository.STATE_CREATED:
272 272 if repo.clone_uri:
273 273 clone_uri = repo.clone_uri_hidden
274 274 h.flash(_('Created repository %s from %s')
275 275 % (repo.repo_name, clone_uri), category='success')
276 276 else:
277 277 repo_url = h.link_to(repo.repo_name,
278 278 h.url('summary_home',
279 279 repo_name=repo.repo_name))
280 280 fork = repo.fork
281 281 if fork:
282 282 fork_name = fork.repo_name
283 283 h.flash(h.literal(_('Forked repository %s as %s')
284 284 % (fork_name, repo_url)), category='success')
285 285 else:
286 286 h.flash(h.literal(_('Created repository %s') % repo_url),
287 287 category='success')
288 288 return {'result': True}
289 289 return {'result': False}
290 290
291 @HasRepoPermissionAllDecorator('repository.admin')
292 @auth.CSRFRequired()
293 def delete(self, repo_name):
294 """
295 DELETE /repos/repo_name: Delete an existing item"""
296 # Forms posted to this method should contain a hidden field:
297 # <input type="hidden" name="_method" value="DELETE" />
298 # Or using helpers:
299 # h.form(url('repo', repo_name=ID),
300 # method='delete')
301 # url('repo', repo_name=ID)
302
303 repo_model = RepoModel()
304 repo = repo_model.get_by_repo_name(repo_name)
305 if not repo:
306 h.not_mapped_error(repo_name)
307 return redirect(url('repos'))
308 try:
309 _forks = repo.forks.count()
310 handle_forks = None
311 if _forks and request.POST.get('forks'):
312 do = request.POST['forks']
313 if do == 'detach_forks':
314 handle_forks = 'detach'
315 h.flash(_('Detached %s forks') % _forks, category='success')
316 elif do == 'delete_forks':
317 handle_forks = 'delete'
318 h.flash(_('Deleted %s forks') % _forks, category='success')
319 repo_model.delete(repo, forks=handle_forks)
320 action_logger(c.rhodecode_user, 'admin_deleted_repo',
321 repo_name, self.ip_addr, self.sa)
322 ScmModel().mark_for_invalidation(repo_name)
323 h.flash(_('Deleted repository %s') % repo_name, category='success')
324 Session().commit()
325 except AttachedForksError:
326 h.flash(_('Cannot delete %s it still contains attached forks')
327 % repo_name, category='warning')
328
329 except Exception:
330 log.exception("Exception during deletion of repository")
331 h.flash(_('An error occurred during deletion of %s') % repo_name,
332 category='error')
333
334 return redirect(url('repos'))
335
336 291 @HasPermissionAllDecorator('hg.admin')
337 292 def show(self, repo_name, format='html'):
338 293 """GET /repos/repo_name: Show a specific item"""
339 294 # url('repo', repo_name=ID)
340 295
341 296 @HasRepoPermissionAllDecorator('repository.admin')
342 297 def edit_fields(self, repo_name):
343 298 """GET /repo_name/settings: Form to edit an existing item"""
344 299 c.repo_info = self._load_repo(repo_name)
345 300 c.repo_fields = RepositoryField.query()\
346 301 .filter(RepositoryField.repository == c.repo_info).all()
347 302 c.active = 'fields'
348 303 if request.POST:
349 304
350 305 return redirect(url('repo_edit_fields'))
351 306 return render('admin/repos/repo_edit.mako')
352 307
353 308 @HasRepoPermissionAllDecorator('repository.admin')
354 309 @auth.CSRFRequired()
355 310 def create_repo_field(self, repo_name):
356 311 try:
357 312 form_result = RepoFieldForm()().to_python(dict(request.POST))
358 313 RepoModel().add_repo_field(
359 314 repo_name, form_result['new_field_key'],
360 315 field_type=form_result['new_field_type'],
361 316 field_value=form_result['new_field_value'],
362 317 field_label=form_result['new_field_label'],
363 318 field_desc=form_result['new_field_desc'])
364 319
365 320 Session().commit()
366 321 except Exception as e:
367 322 log.exception("Exception creating field")
368 323 msg = _('An error occurred during creation of field')
369 324 if isinstance(e, formencode.Invalid):
370 325 msg += ". " + e.msg
371 326 h.flash(msg, category='error')
372 327 return redirect(url('edit_repo_fields', repo_name=repo_name))
373 328
374 329 @HasRepoPermissionAllDecorator('repository.admin')
375 330 @auth.CSRFRequired()
376 331 def delete_repo_field(self, repo_name, field_id):
377 332 field = RepositoryField.get_or_404(field_id)
378 333 try:
379 334 RepoModel().delete_repo_field(repo_name, field.field_key)
380 335 Session().commit()
381 336 except Exception as e:
382 337 log.exception("Exception during removal of field")
383 338 msg = _('An error occurred during removal of field')
384 339 h.flash(msg, category='error')
385 340 return redirect(url('edit_repo_fields', repo_name=repo_name))
386 341
387 @HasRepoPermissionAllDecorator('repository.admin')
388 def edit_advanced(self, repo_name):
389 """GET /repo_name/settings: Form to edit an existing item"""
390 c.repo_info = self._load_repo(repo_name)
391 c.default_user_id = User.get_default_user().user_id
392 c.in_public_journal = UserFollowing.query()\
393 .filter(UserFollowing.user_id == c.default_user_id)\
394 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
395
396 c.active = 'advanced'
397 c.has_origin_repo_read_perm = False
398 if c.repo_info.fork:
399 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
400 'repository.write', 'repository.read', 'repository.admin')(
401 c.repo_info.fork.repo_name, 'repo set as fork page')
402
403 if request.POST:
404 return redirect(url('repo_edit_advanced'))
405 return render('admin/repos/repo_edit.mako')
406
407 @HasRepoPermissionAllDecorator('repository.admin')
408 @auth.CSRFRequired()
409 def edit_advanced_journal(self, repo_name):
410 """
411 Set's this repository to be visible in public journal,
412 in other words assing default user to follow this repo
413
414 :param repo_name:
415 """
416
417 try:
418 repo_id = Repository.get_by_repo_name(repo_name).repo_id
419 user_id = User.get_default_user().user_id
420 self.scm_model.toggle_following_repo(repo_id, user_id)
421 h.flash(_('Updated repository visibility in public journal'),
422 category='success')
423 Session().commit()
424 except Exception:
425 h.flash(_('An error occurred during setting this'
426 ' repository in public journal'),
427 category='error')
428
429 return redirect(url('edit_repo_advanced', repo_name=repo_name))
430
431 @HasRepoPermissionAllDecorator('repository.admin')
432 @auth.CSRFRequired()
433 def edit_advanced_fork(self, repo_name):
434 """
435 Mark given repository as a fork of another
436
437 :param repo_name:
438 """
439
440 new_fork_id = request.POST.get('id_fork_of')
441 try:
442
443 if new_fork_id and not new_fork_id.isdigit():
444 log.error('Given fork id %s is not an INT', new_fork_id)
445
446 fork_id = safe_int(new_fork_id)
447 repo = ScmModel().mark_as_fork(repo_name, fork_id,
448 c.rhodecode_user.username)
449 fork = repo.fork.repo_name if repo.fork else _('Nothing')
450 Session().commit()
451 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
452 category='success')
453 except RepositoryError as e:
454 log.exception("Repository Error occurred")
455 h.flash(str(e), category='error')
456 except Exception as e:
457 log.exception("Exception while editing fork")
458 h.flash(_('An error occurred during this operation'),
459 category='error')
460
461 return redirect(url('edit_repo_advanced', repo_name=repo_name))
462
463 @HasRepoPermissionAllDecorator('repository.admin')
464 @auth.CSRFRequired()
465 def edit_advanced_locking(self, repo_name):
466 """
467 Unlock repository when it is locked !
468
469 :param repo_name:
470 """
471 try:
472 repo = Repository.get_by_repo_name(repo_name)
473 if request.POST.get('set_lock'):
474 Repository.lock(repo, c.rhodecode_user.user_id,
475 lock_reason=Repository.LOCK_WEB)
476 h.flash(_('Locked repository'), category='success')
477 elif request.POST.get('set_unlock'):
478 Repository.unlock(repo)
479 h.flash(_('Unlocked repository'), category='success')
480 except Exception as e:
481 log.exception("Exception during unlocking")
482 h.flash(_('An error occurred during unlocking'),
483 category='error')
484 return redirect(url('edit_repo_advanced', repo_name=repo_name))
485
486 342 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
487 343 @auth.CSRFRequired()
488 344 def toggle_locking(self, repo_name):
489 345 """
490 346 Toggle locking of repository by simple GET call to url
491 347
492 348 :param repo_name:
493 349 """
494 350
495 351 try:
496 352 repo = Repository.get_by_repo_name(repo_name)
497 353
498 354 if repo.enable_locking:
499 355 if repo.locked[0]:
500 356 Repository.unlock(repo)
501 357 action = _('Unlocked')
502 358 else:
503 359 Repository.lock(repo, c.rhodecode_user.user_id,
504 360 lock_reason=Repository.LOCK_WEB)
505 361 action = _('Locked')
506 362
507 363 h.flash(_('Repository has been %s') % action,
508 364 category='success')
509 365 except Exception:
510 366 log.exception("Exception during unlocking")
511 367 h.flash(_('An error occurred during unlocking'),
512 368 category='error')
513 369 return redirect(url('summary_home', repo_name=repo_name))
514 370
515 371 @HasRepoPermissionAllDecorator('repository.admin')
516 372 @auth.CSRFRequired()
517 373 def edit_remote(self, repo_name):
518 374 """PUT /{repo_name}/settings/remote: edit the repo remote."""
519 375 try:
520 376 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
521 377 h.flash(_('Pulled from remote location'), category='success')
522 378 except Exception:
523 379 log.exception("Exception during pull from remote")
524 380 h.flash(_('An error occurred during pull from remote location'),
525 381 category='error')
526 382 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
527 383
528 384 @HasRepoPermissionAllDecorator('repository.admin')
529 385 def edit_remote_form(self, repo_name):
530 386 """GET /repo_name/settings: Form to edit an existing item"""
531 387 c.repo_info = self._load_repo(repo_name)
532 388 c.active = 'remote'
533 389
534 390 return render('admin/repos/repo_edit.mako')
535 391
536 392 @HasRepoPermissionAllDecorator('repository.admin')
537 393 @auth.CSRFRequired()
538 394 def edit_statistics(self, repo_name):
539 395 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
540 396 try:
541 397 RepoModel().delete_stats(repo_name)
542 398 Session().commit()
543 399 except Exception as e:
544 400 log.error(traceback.format_exc())
545 401 h.flash(_('An error occurred during deletion of repository stats'),
546 402 category='error')
547 403 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
548 404
549 405 @HasRepoPermissionAllDecorator('repository.admin')
550 406 def edit_statistics_form(self, repo_name):
551 407 """GET /repo_name/settings: Form to edit an existing item"""
552 408 c.repo_info = self._load_repo(repo_name)
553 409 repo = c.repo_info.scm_instance()
554 410
555 411 if c.repo_info.stats:
556 412 # this is on what revision we ended up so we add +1 for count
557 413 last_rev = c.repo_info.stats.stat_on_revision + 1
558 414 else:
559 415 last_rev = 0
560 416 c.stats_revision = last_rev
561 417
562 418 c.repo_last_rev = repo.count()
563 419
564 420 if last_rev == 0 or c.repo_last_rev == 0:
565 421 c.stats_percentage = 0
566 422 else:
567 423 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
568 424
569 425 c.active = 'statistics'
570 426
571 427 return render('admin/repos/repo_edit.mako')
572 428
573 429 @HasRepoPermissionAllDecorator('repository.admin')
574 430 @auth.CSRFRequired()
575 431 def repo_issuetracker_test(self, repo_name):
576 432 if request.is_xhr:
577 433 return h.urlify_commit_message(
578 434 request.POST.get('test_text', ''),
579 435 repo_name)
580 436 else:
581 437 raise HTTPBadRequest()
582 438
583 439 @HasRepoPermissionAllDecorator('repository.admin')
584 440 @auth.CSRFRequired()
585 441 def repo_issuetracker_delete(self, repo_name):
586 442 uid = request.POST.get('uid')
587 443 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
588 444 try:
589 445 repo_settings.delete_entries(uid)
590 446 except Exception:
591 447 h.flash(_('Error occurred during deleting issue tracker entry'),
592 448 category='error')
593 449 else:
594 450 h.flash(_('Removed issue tracker entry'), category='success')
595 451 return redirect(url('repo_settings_issuetracker',
596 452 repo_name=repo_name))
597 453
598 454 def _update_patterns(self, form, repo_settings):
599 455 for uid in form['delete_patterns']:
600 456 repo_settings.delete_entries(uid)
601 457
602 458 for pattern in form['patterns']:
603 459 for setting, value, type_ in pattern:
604 460 sett = repo_settings.create_or_update_setting(
605 461 setting, value, type_)
606 462 Session().add(sett)
607 463
608 464 Session().commit()
609 465
610 466 @HasRepoPermissionAllDecorator('repository.admin')
611 467 @auth.CSRFRequired()
612 468 def repo_issuetracker_save(self, repo_name):
613 469 # Save inheritance
614 470 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
615 471 inherited = (request.POST.get('inherit_global_issuetracker')
616 472 == "inherited")
617 473 repo_settings.inherit_global_settings = inherited
618 474 Session().commit()
619 475
620 476 form = IssueTrackerPatternsForm()().to_python(request.POST)
621 477 if form:
622 478 self._update_patterns(form, repo_settings)
623 479
624 480 h.flash(_('Updated issue tracker entries'), category='success')
625 481 return redirect(url('repo_settings_issuetracker',
626 482 repo_name=repo_name))
627 483
628 484 @HasRepoPermissionAllDecorator('repository.admin')
629 485 def repo_issuetracker(self, repo_name):
630 486 """GET /admin/settings/issue-tracker: All items in the collection"""
631 487 c.active = 'issuetracker'
632 488 c.data = 'data'
633 489 c.repo_info = self._load_repo(repo_name)
634 490
635 491 repo = Repository.get_by_repo_name(repo_name)
636 492 c.settings_model = IssueTrackerSettingsModel(repo=repo)
637 493 c.global_patterns = c.settings_model.get_global_settings()
638 494 c.repo_patterns = c.settings_model.get_repo_settings()
639 495
640 496 return render('admin/repos/repo_edit.mako')
641 497
642 498 @HasRepoPermissionAllDecorator('repository.admin')
643 499 def repo_settings_vcs(self, repo_name):
644 500 """GET /{repo_name}/settings/vcs/: All items in the collection"""
645 501
646 502 model = VcsSettingsModel(repo=repo_name)
647 503
648 504 c.active = 'vcs'
649 505 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
650 506 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
651 507 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
652 508 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
653 509 c.repo_info = self._load_repo(repo_name)
654 510 defaults = self._vcs_form_defaults(repo_name)
655 511 c.inherit_global_settings = defaults['inherit_global_settings']
656 512 c.labs_active = str2bool(
657 513 rhodecode.CONFIG.get('labs_settings_active', 'true'))
658 514
659 515 return htmlfill.render(
660 516 render('admin/repos/repo_edit.mako'),
661 517 defaults=defaults,
662 518 encoding="UTF-8",
663 519 force_defaults=False)
664 520
665 521 @HasRepoPermissionAllDecorator('repository.admin')
666 522 @auth.CSRFRequired()
667 523 def repo_settings_vcs_update(self, repo_name):
668 524 """POST /{repo_name}/settings/vcs/: All items in the collection"""
669 525 c.active = 'vcs'
670 526
671 527 model = VcsSettingsModel(repo=repo_name)
672 528 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
673 529 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
674 530 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
675 531 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
676 532 c.repo_info = self._load_repo(repo_name)
677 533 defaults = self._vcs_form_defaults(repo_name)
678 534 c.inherit_global_settings = defaults['inherit_global_settings']
679 535
680 536 application_form = RepoVcsSettingsForm(repo_name)()
681 537 try:
682 538 form_result = application_form.to_python(dict(request.POST))
683 539 except formencode.Invalid as errors:
684 540 h.flash(
685 541 _("Some form inputs contain invalid data."),
686 542 category='error')
687 543 return htmlfill.render(
688 544 render('admin/repos/repo_edit.mako'),
689 545 defaults=errors.value,
690 546 errors=errors.error_dict or {},
691 547 prefix_error=False,
692 548 encoding="UTF-8",
693 549 force_defaults=False
694 550 )
695 551
696 552 try:
697 553 inherit_global_settings = form_result['inherit_global_settings']
698 554 model.create_or_update_repo_settings(
699 555 form_result, inherit_global_settings=inherit_global_settings)
700 556 except Exception:
701 557 log.exception("Exception while updating settings")
702 558 h.flash(
703 559 _('Error occurred during updating repository VCS settings'),
704 560 category='error')
705 561 else:
706 562 Session().commit()
707 563 h.flash(_('Updated VCS settings'), category='success')
708 564 return redirect(url('repo_vcs_settings', repo_name=repo_name))
709 565
710 566 return htmlfill.render(
711 567 render('admin/repos/repo_edit.mako'),
712 568 defaults=self._vcs_form_defaults(repo_name),
713 569 encoding="UTF-8",
714 570 force_defaults=False)
715 571
716 572 @HasRepoPermissionAllDecorator('repository.admin')
717 573 @auth.CSRFRequired()
718 574 @jsonify
719 575 def repo_delete_svn_pattern(self, repo_name):
720 576 if not request.is_xhr:
721 577 return False
722 578
723 579 delete_pattern_id = request.POST.get('delete_svn_pattern')
724 580 model = VcsSettingsModel(repo=repo_name)
725 581 try:
726 582 model.delete_repo_svn_pattern(delete_pattern_id)
727 583 except SettingNotFound:
728 584 raise HTTPBadRequest()
729 585
730 586 Session().commit()
731 587 return True
732 588
733 589 def _vcs_form_defaults(self, repo_name):
734 590 model = VcsSettingsModel(repo=repo_name)
735 591 global_defaults = model.get_global_settings()
736 592
737 593 repo_defaults = {}
738 594 repo_defaults.update(global_defaults)
739 595 repo_defaults.update(model.get_repo_settings())
740 596
741 597 global_defaults = {
742 598 '{}_inherited'.format(k): global_defaults[k]
743 599 for k in global_defaults}
744 600
745 601 defaults = {
746 602 'inherit_global_settings': model.inherit_global_settings
747 603 }
748 604 defaults.update(global_defaults)
749 605 defaults.update(repo_defaults)
750 606 defaults.update({
751 607 'new_svn_branch': '',
752 608 'new_svn_tag': '',
753 609 })
754 610 return defaults
@@ -1,183 +1,184 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23
24 24 from rhodecode.model import meta
25 25 from rhodecode.model.db import User, UserLog, Repository
26 26
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 ACTIONS = {
32 32 'user.login.success': {},
33 33 'user.login.failure': {},
34 34 'user.logout': {},
35 35 'user.password.reset_request': {},
36 36 'user.push': {},
37 37 'user.pull': {},
38 38
39 'repo.add': {},
39 'repo.create': {},
40 40 'repo.edit': {},
41 41 'repo.edit.permissions': {},
42 'repo.delete': {},
42 43 'repo.commit.strip': {},
43 44 'repo.archive.download': {},
44 45 }
45 46
46 47
47 48 class UserWrap(object):
48 49 """
49 50 Fake object used to imitate AuthUser
50 51 """
51 52
52 53 def __init__(self, user_id=None, username=None, ip_addr=None):
53 54 self.user_id = user_id
54 55 self.username = username
55 56 self.ip_addr = ip_addr
56 57
57 58
58 59 class RepoWrap(object):
59 60 """
60 61 Fake object used to imitate RepoObject that audit logger requires
61 62 """
62 63
63 64 def __init__(self, repo_id=None, repo_name=None):
64 65 self.repo_id = repo_id
65 66 self.repo_name = repo_name
66 67
67 68
68 69 def _store_log(action_name, action_data, user_id, username, user_data,
69 70 ip_address, repository_id, repository_name):
70 71 user_log = UserLog()
71 72 user_log.version = UserLog.VERSION_2
72 73
73 74 user_log.action = action_name
74 75 user_log.action_data = action_data
75 76
76 77 user_log.user_ip = ip_address
77 78
78 79 user_log.user_id = user_id
79 80 user_log.username = username
80 81 user_log.user_data = user_data
81 82
82 83 user_log.repository_id = repository_id
83 84 user_log.repository_name = repository_name
84 85
85 86 user_log.action_date = datetime.datetime.now()
86 87
87 88 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
88 89 action_name, user_id, username, ip_address)
89 90
90 91 return user_log
91 92
92 93
93 94 def store(
94 95 action, user, action_data=None, user_data=None, ip_addr=None,
95 96 repo=None, sa_session=None, commit=False):
96 97 """
97 98 Audit logger for various actions made by users, typically this results in a call such::
98 99
99 100 from rhodecode.lib import audit_logger
100 101
101 102 audit_logger.store(
102 103 action='repo.edit', user=self._rhodecode_user)
103 104 audit_logger.store(
104 action='repo.delete',
105 action='repo.delete', action_data={'repo_data': repo_data},
105 106 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
106 107
107 108 # repo action
108 109 audit_logger.store(
109 110 action='repo.delete',
110 111 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
111 112 repo=audit_logger.RepoWrap(repo_name='some-repo'))
112 113
113 114 # repo action, when we know and have the repository object already
114 115 audit_logger.store(
115 116 action='repo.delete',
116 117 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
117 118 repo=repo_object)
118 119
119 120 # without an user ?
120 121 audit_logger.store(
121 122 action='user.login.failure',
122 123 user=audit_logger.UserWrap(
123 124 username=self.request.params.get('username'),
124 125 ip_addr=self.request.remote_addr))
125 126
126 127 """
127 128 from rhodecode.lib.utils2 import safe_unicode
128 129 from rhodecode.lib.auth import AuthUser
129 130
130 131 if action not in ACTIONS:
131 132 raise ValueError('Action `{}` not in valid actions'.format(action))
132 133
133 134 if not sa_session:
134 135 sa_session = meta.Session()
135 136
136 137 try:
137 138 username = getattr(user, 'username', None)
138 139 if not username:
139 140 pass
140 141
141 142 user_id = getattr(user, 'user_id', None)
142 143 if not user_id:
143 144 # maybe we have username ? Try to figure user_id from username
144 145 if username:
145 146 user_id = getattr(
146 147 User.get_by_username(username), 'user_id', None)
147 148
148 149 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
149 150 if not ip_addr:
150 151 pass
151 152
152 153 if not user_data:
153 154 # try to get this from the auth user
154 155 if isinstance(user, AuthUser):
155 156 user_data = {
156 157 'username': user.username,
157 158 'email': user.email,
158 159 }
159 160
160 161 repository_name = getattr(repo, 'repo_name', None)
161 162 repository_id = getattr(repo, 'repo_id', None)
162 163 if not repository_id:
163 164 # maybe we have repo_name ? Try to figure repo_id from repo_name
164 165 if repository_name:
165 166 repository_id = getattr(
166 167 Repository.get_by_repo_name(repository_name), 'repo_id', None)
167 168
168 169 user_log = _store_log(
169 170 action_name=safe_unicode(action),
170 171 action_data=action_data or {},
171 172 user_id=user_id,
172 173 username=username,
173 174 user_data=user_data or {},
174 175 ip_address=safe_unicode(ip_addr),
175 176 repository_id=repository_id,
176 177 repository_name=repository_name
177 178 )
178 179 sa_session.add(user_log)
179 180 if commit:
180 181 sa_session.commit()
181 182
182 183 except Exception:
183 184 log.exception('AUDIT: failed to store audit log')
@@ -1,121 +1,127 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('home', '/', []);
16 16 pyroutes.register('new_repo', '/_admin/create_repository', []);
17 17 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
18 18 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
19 19 pyroutes.register('gists', '/_admin/gists', []);
20 20 pyroutes.register('new_gist', '/_admin/gists/new', []);
21 21 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
22 22 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
23 23 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
24 24 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
25 25 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
26 26 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
27 27 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
28 28 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
29 29 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
30 30 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
31 31 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
32 32 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
33 33 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
34 34 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
35 35 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
36 36 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
37 37 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
38 38 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
39 39 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
40 40 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
41 41 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
42 42 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
43 43 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
44 44 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 45 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
46 46 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 47 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 48 pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
49 49 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
50 50 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
51 51 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
52 52 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
53 53 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
54 54 pyroutes.register('favicon', '/favicon.ico', []);
55 55 pyroutes.register('robots', '/robots.txt', []);
56 56 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
57 57 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
58 58 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
59 59 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
60 60 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
61 61 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
62 62 pyroutes.register('repo_group_integrations_home', '%(repo_group_name)s/settings/integrations', ['repo_group_name']);
63 63 pyroutes.register('repo_group_integrations_list', '%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
64 64 pyroutes.register('repo_group_integrations_new', '%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
65 65 pyroutes.register('repo_group_integrations_create', '%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
66 66 pyroutes.register('repo_group_integrations_edit', '%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
67 67 pyroutes.register('repo_integrations_home', '%(repo_name)s/settings/integrations', ['repo_name']);
68 68 pyroutes.register('repo_integrations_list', '%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
69 69 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
70 70 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
71 71 pyroutes.register('repo_integrations_edit', '%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
72 72 pyroutes.register('ops_ping', '_admin/ops/ping', []);
73 73 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
74 74 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
75 75 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
76 76 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
77 77 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
78 78 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
79 79 pyroutes.register('users', '_admin/users', []);
80 80 pyroutes.register('users_data', '_admin/users_data', []);
81 81 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
82 82 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
83 83 pyroutes.register('edit_user_auth_tokens_delete', '_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
84 84 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
85 85 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
86 86 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
87 87 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
88 88 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
89 89 pyroutes.register('channelstream_proxy', '/_channelstream', []);
90 90 pyroutes.register('login', '/_admin/login', []);
91 91 pyroutes.register('logout', '/_admin/logout', []);
92 92 pyroutes.register('register', '/_admin/register', []);
93 93 pyroutes.register('reset_password', '/_admin/password_reset', []);
94 94 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
95 95 pyroutes.register('user_autocomplete_data', '/_users', []);
96 96 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
97 97 pyroutes.register('repo_list_data', '/_repos', []);
98 98 pyroutes.register('goto_switcher_data', '/_goto_data', []);
99 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
99 100 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
100 101 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
101 102 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
102 103 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
104 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
105 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
106 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
107 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
108 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
103 109 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
104 110 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
105 111 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
106 112 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
107 113 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
108 114 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
109 115 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
110 116 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
111 117 pyroutes.register('search', '/_admin/search', []);
112 118 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
113 119 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
114 120 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
115 121 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
116 122 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
117 123 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
118 124 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
119 125 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
120 126 pyroutes.register('apiv2', '/_admin/api', []);
121 127 }
@@ -1,95 +1,95 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ##
3 3 ## See also repo_settings.html
4 4 ##
5 5 <%inherit file="/base/base.mako"/>
6 6
7 7 <%def name="title()">
8 8 ${_('%s repository settings') % c.repo_info.repo_name}
9 9 %if c.rhodecode_name:
10 10 &middot; ${h.branding(c.rhodecode_name)}
11 11 %endif
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15 ${_('Settings')}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='repositories')}
20 20 </%def>
21 21
22 22 <%def name="menu_bar_subnav()">
23 23 ${self.repo_menu(active='options')}
24 24 </%def>
25 25
26 26 <%def name="main_content()">
27 27 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
28 28 </%def>
29 29
30 30
31 31 <%def name="main()">
32 32 <div class="box">
33 33 <div class="title">
34 34 ${self.repo_page_title(c.rhodecode_db_repo)}
35 35 ${self.breadcrumbs()}
36 36 </div>
37 37
38 38 <div class="sidebar-col-wrapper scw-small">
39 39 <div class="sidebar">
40 40 <ul class="nav nav-pills nav-stacked">
41 41 <li class="${'active' if c.active=='settings' else ''}">
42 42 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
43 43 </li>
44 44 <li class="${'active' if c.active=='permissions' else ''}">
45 45 <a href="${h.route_path('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
46 46 </li>
47 47 <li class="${'active' if c.active=='advanced' else ''}">
48 <a href="${h.url('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
48 <a href="${h.route_path('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
49 49 </li>
50 50 <li class="${'active' if c.active=='vcs' else ''}">
51 51 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
52 52 </li>
53 53 <li class="${'active' if c.active=='fields' else ''}">
54 54 <a href="${h.url('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
55 55 </li>
56 56 <li class="${'active' if c.active=='issuetracker' else ''}">
57 57 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
58 58 </li>
59 59 <li class="${'active' if c.active=='caches' else ''}">
60 60 <a href="${h.route_path('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
61 61 </li>
62 62 %if c.repo_info.repo_type != 'svn':
63 63 <li class="${'active' if c.active=='remote' else ''}">
64 64 <a href="${h.url('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
65 65 </li>
66 66 %endif
67 67 <li class="${'active' if c.active=='statistics' else ''}">
68 68 <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
69 69 </li>
70 70 <li class="${'active' if c.active=='integrations' else ''}">
71 71 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
72 72 </li>
73 73 %if c.repo_info.repo_type != 'svn':
74 74 <li class="${'active' if c.active=='reviewers' else ''}">
75 75 <a href="${h.route_path('repo_reviewers', repo_name=c.repo_name)}">${_('Reviewer Rules')}</a>
76 76 </li>
77 77 %endif
78 78 <li class="${'active' if c.active=='maintenance' else ''}">
79 79 <a href="${h.route_path('repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
80 80 </li>
81 81 <li class="${'active' if c.active=='strip' else ''}">
82 82 <a href="${h.route_path('strip', repo_name=c.repo_name)}">${_('Strip')}</a>
83 83 </li>
84 84
85 85 </ul>
86 86 </div>
87 87
88 88 <div class="main-content-full-width">
89 89 ${self.main_content()}
90 90 </div>
91 91
92 92 </div>
93 93 </div>
94 94
95 95 </%def> No newline at end of file
@@ -1,211 +1,210 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <%
4 4 elems = [
5 5 (_('Owner'), lambda:base.gravatar_with_user(c.repo_info.user.email), '', ''),
6 6 (_('Created on'), h.format_date(c.repo_info.created_on), '', ''),
7 7 (_('Updated on'), h.format_date(c.repo_info.updated_on), '', ''),
8 8 (_('Cached Commit id'), lambda: h.link_to(c.repo_info.changeset_cache.get('short_id'), h.url('changeset_home',repo_name=c.repo_name,revision=c.repo_info.changeset_cache.get('raw_id'))), '', ''),
9 9 ]
10 10 %>
11 11
12 12 <div class="panel panel-default">
13 <div class="panel-heading">
14 <h3 class="panel-title">${_('Repository: %s') % c.repo_info.repo_name}</h3>
13 <div class="panel-heading" id="advanced-info" >
14 <h3 class="panel-title">${_('Repository: %s') % c.repo_info.repo_name} <a class="permalink" href="#advanced-info"> ΒΆ</a></h3>
15 15 </div>
16 16 <div class="panel-body">
17 17 ${base.dt_info_panel(elems)}
18 18 </div>
19 19 </div>
20 20
21 21
22 22 <div class="panel panel-default">
23 <div class="panel-heading">
24 <h3 class="panel-title">${_('Fork Reference')}</h3>
23 <div class="panel-heading" id="advanced-fork">
24 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"> ΒΆ</a></h3>
25 25 </div>
26 26 <div class="panel-body">
27 ${h.secure_form(url('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='put')}
27 ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='POST')}
28 28
29 29 % if c.repo_info.fork:
30 30 <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.repo_info.fork.repo_name, h.url('summary_home', repo_name=c.repo_info.fork.repo_name))})}
31 31 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
32 32 % endif
33 33
34 34 <div class="field">
35 35 ${h.hidden('id_fork_of')}
36 36 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-small",)}
37 37 </div>
38 38 <div class="field">
39 39 <span class="help-block">${_('Manually set this repository as a fork of another from the list')}</span>
40 40 </div>
41 41 ${h.end_form()}
42 42 </div>
43 43 </div>
44 44
45 45
46 46 <div class="panel panel-default">
47 <div class="panel-heading">
48 <h3 class="panel-title">${_('Public Journal Visibility')}</h3>
47 <div class="panel-heading" id="advanced-journal">
48 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"> ΒΆ</a></h3>
49 49 </div>
50 50 <div class="panel-body">
51 ${h.secure_form(url('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='put')}
51 ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='POST')}
52 52 <div class="field">
53 53 %if c.in_public_journal:
54 54 <button class="btn btn-small" type="submit">
55 <i class="icon-minus"></i>
56 55 ${_('Remove from Public Journal')}
57 56 </button>
58 57 %else:
59 58 <button class="btn btn-small" type="submit">
60 59 ${_('Add to Public Journal')}
61 60 </button>
62 61 %endif
63 62 </div>
64 63 <div class="field" >
65 64 <span class="help-block">${_('All actions made on this repository will be visible to everyone following the public journal.')}</span>
66 65 </div>
67 66 ${h.end_form()}
68 67 </div>
69 68 </div>
70 69
71 70
72 71 <div class="panel panel-default">
73 <div class="panel-heading">
74 <h3 class="panel-title">${_('Locking state')}</h3>
72 <div class="panel-heading" id="advanced-locking">
73 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"> ΒΆ</a></h3>
75 74 </div>
76 75 <div class="panel-body">
77 ${h.secure_form(url('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='put')}
76 ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='POST')}
78 77
79 78 %if c.repo_info.locked[0]:
80 79 <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.repo_info.locked[0]),
81 80 h.format_date(h. time_to_datetime(c.repo_info.locked[1])), c.repo_info.locked[2])}</div>
82 81 %else:
83 82 <div class="panel-body-title-text">${_('This Repository is not currently locked.')}</div>
84 83 %endif
85 84
86 85 <div class="field" >
87 86 %if c.repo_info.locked[0]:
88 87 ${h.hidden('set_unlock', '1')}
89 88 <button class="btn btn-small" type="submit"
90 89 onclick="return confirm('${_('Confirm to unlock repository.')}');">
91 90 <i class="icon-unlock"></i>
92 91 ${_('Unlock repository')}
93 92 </button>
94 93 %else:
95 94 ${h.hidden('set_lock', '1')}
96 95 <button class="btn btn-small" type="submit"
97 96 onclick="return confirm('${_('Confirm to lock repository.')}');">
98 97 <i class="icon-lock"></i>
99 98 ${_('Lock Repository')}
100 99 </button>
101 100 %endif
102 101 </div>
103 102 <div class="field" >
104 103 <span class="help-block">
105 104 ${_('Force repository locking. This only works when anonymous access is disabled. Pulling from the repository locks the repository to that user until the same user pushes to that repository again.')}
106 105 </span>
107 106 </div>
108 107 ${h.end_form()}
109 108 </div>
110 109 </div>
111 110
112 111 <div class="panel panel-danger">
113 <div class="panel-heading">
114 <h3 class="panel-title">${_('Delete repository')}</h3>
112 <div class="panel-heading" id="advanced-delete">
113 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"> ΒΆ</a></h3>
115 114 </div>
116 115 <div class="panel-body">
117 ${h.secure_form(url('repo', repo_name=c.repo_name),method='delete')}
116 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), method='POST')}
118 117 <table class="display">
119 118 <tr>
120 119 <td>
121 ${ungettext('This repository has %s fork.', 'This repository has %s forks.', c.repo_info. forks.count()) % c.repo_info.forks.count()}
120 ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.repo_info.forks.count()) % c.repo_info.forks.count()}
122 121 </td>
123 122 <td>
124 123 %if c.repo_info.forks.count():
125 124 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
126 125 %endif
127 126 </td>
128 127 <td>
129 128 %if c.repo_info.forks.count():
130 129 <input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label>
131 130 %endif
132 131 </td>
133 132 </tr>
134 133 </table>
135 134 <div style="margin: 0 0 20px 0" class="fake-space"></div>
136 135
137 136 <div class="field">
138 137 <button class="btn btn-small btn-danger" type="submit"
139 138 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
140 139 <i class="icon-remove-sign"></i>
141 140 ${_('Delete This Repository')}
142 141 </button>
143 142 </div>
144 143 <div class="field">
145 144 <span class="help-block">
146 ${_('This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command.')}
145 ${_('This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools.')}
147 146 </span>
148 147 </div>
149 148
150 149 ${h.end_form()}
151 150 </div>
152 151 </div>
153 152
154 153
155 154 <script>
156 155
157 156 var currentRepoId = ${c.repo_info.repo_id};
158 157
159 158 var repoTypeFilter = function(data) {
160 159 var results = [];
161 160
162 161 if (!data.results[0]) {
163 162 return data
164 163 }
165 164
166 165 $.each(data.results[0].children, function() {
167 166 // filter out the SAME repo, it cannot be used as fork of itself
168 167 if (this.obj.repo_id != currentRepoId) {
169 168 this.id = this.obj.repo_id;
170 169 results.push(this)
171 170 }
172 171 });
173 172 data.results[0].children = results;
174 173 return data;
175 174 };
176 175
177 176 $("#id_fork_of").select2({
178 177 cachedDataSource: {},
179 178 minimumInputLength: 2,
180 179 placeholder: "${_('Change repository') if c.repo_info.fork else _('Pick repository')}",
181 180 dropdownAutoWidth: true,
182 181 containerCssClass: "drop-menu",
183 182 dropdownCssClass: "drop-menu-dropdown",
184 183 formatResult: formatResult,
185 184 query: $.debounce(250, function(query){
186 185 self = this;
187 186 var cacheKey = query.term;
188 187 var cachedData = self.cachedDataSource[cacheKey];
189 188
190 189 if (cachedData) {
191 190 query.callback({results: cachedData.results});
192 191 } else {
193 192 $.ajax({
194 193 url: pyroutes.url('repo_list_data'),
195 194 data: {'query': query.term, repo_type: '${c.repo_info.repo_type}'},
196 195 dataType: 'json',
197 196 type: 'GET',
198 197 success: function(data) {
199 198 data = repoTypeFilter(data);
200 199 self.cachedDataSource[cacheKey] = data;
201 200 query.callback({results: data.results});
202 201 },
203 202 error: function(data, textStatus, errorThrown) {
204 203 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
205 204 }
206 205 })
207 206 }
208 207 })
209 208 });
210 209 </script>
211 210
@@ -1,317 +1,317 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS
2 2 ## usage:
3 3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 4 <%namespace name="base" file="/base/base.mako"/>
5 5
6 6 ## REPOSITORY RENDERERS
7 7 <%def name="quick_menu(repo_name)">
8 8 <i class="pointer icon-more"></i>
9 9 <div class="menu_items_container hidden">
10 10 <ul class="menu_items">
11 11 <li>
12 12 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
13 13 <span>${_('Summary')}</span>
14 14 </a>
15 15 </li>
16 16 <li>
17 17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
18 18 <span>${_('Changelog')}</span>
19 19 </a>
20 20 </li>
21 21 <li>
22 22 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
23 23 <span>${_('Files')}</span>
24 24 </a>
25 25 </li>
26 26 <li>
27 27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
28 28 <span>${_('Fork')}</span>
29 29 </a>
30 30 </li>
31 31 </ul>
32 32 </div>
33 33 </%def>
34 34
35 35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
36 36 <%
37 37 def get_name(name,short_name=short_name):
38 38 if short_name:
39 39 return name.split('/')[-1]
40 40 else:
41 41 return name
42 42 %>
43 43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 44 ##NAME
45 45 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.url('summary_home',repo_name=name)}">
46 46
47 47 ##TYPE OF REPO
48 48 %if h.is_hg(rtype):
49 49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
50 50 %elif h.is_git(rtype):
51 51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
52 52 %elif h.is_svn(rtype):
53 53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
54 54 %endif
55 55
56 56 ##PRIVATE/PUBLIC
57 57 %if private and c.visual.show_private_icon:
58 58 <i class="icon-lock" title="${_('Private repository')}"></i>
59 59 %elif not private and c.visual.show_public_icon:
60 60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
61 61 %else:
62 62 <span></span>
63 63 %endif
64 64 ${get_name(name)}
65 65 </a>
66 66 %if fork_of:
67 67 <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
68 68 %endif
69 69 %if rstate == 'repo_state_pending':
70 70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
71 71 %endif
72 72 </div>
73 73 </%def>
74 74
75 75 <%def name="repo_desc(description)">
76 76 <div class="truncate-wrap">${description}</div>
77 77 </%def>
78 78
79 79 <%def name="last_change(last_change)">
80 80 ${h.age_component(last_change)}
81 81 </%def>
82 82
83 83 <%def name="revision(name,rev,tip,author,last_msg)">
84 84 <div>
85 85 %if rev >= 0:
86 86 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
87 87 %else:
88 88 ${_('No commits yet')}
89 89 %endif
90 90 </div>
91 91 </%def>
92 92
93 93 <%def name="rss(name)">
94 94 %if c.rhodecode_user.username != h.DEFAULT_USER:
95 95 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
96 96 %else:
97 97 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
98 98 %endif
99 99 </%def>
100 100
101 101 <%def name="atom(name)">
102 102 %if c.rhodecode_user.username != h.DEFAULT_USER:
103 103 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
104 104 %else:
105 105 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
106 106 %endif
107 107 </%def>
108 108
109 109 <%def name="user_gravatar(email, size=16)">
110 110 <div class="rc-user tooltip" title="${h.author_string(email)}">
111 111 ${base.gravatar(email, 16)}
112 112 </div>
113 113 </%def>
114 114
115 115 <%def name="repo_actions(repo_name, super_user=True)">
116 116 <div>
117 117 <div class="grid_edit">
118 118 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
119 119 <i class="icon-pencil"></i>Edit</a>
120 120 </div>
121 121 <div class="grid_delete">
122 ${h.secure_form(h.url('repo', repo_name=repo_name),method='delete')}
122 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST')}
123 123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
124 124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
125 125 ${h.end_form()}
126 126 </div>
127 127 </div>
128 128 </%def>
129 129
130 130 <%def name="repo_state(repo_state)">
131 131 <div>
132 132 %if repo_state == 'repo_state_pending':
133 133 <div class="tag tag4">${_('Creating')}</div>
134 134 %elif repo_state == 'repo_state_created':
135 135 <div class="tag tag1">${_('Created')}</div>
136 136 %else:
137 137 <div class="tag alert2" title="${repo_state}">invalid</div>
138 138 %endif
139 139 </div>
140 140 </%def>
141 141
142 142
143 143 ## REPO GROUP RENDERERS
144 144 <%def name="quick_repo_group_menu(repo_group_name)">
145 145 <i class="pointer icon-more"></i>
146 146 <div class="menu_items_container hidden">
147 147 <ul class="menu_items">
148 148 <li>
149 149 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
150 150 <span class="icon">
151 151 <i class="icon-file-text"></i>
152 152 </span>
153 153 <span>${_('Summary')}</span>
154 154 </a>
155 155 </li>
156 156
157 157 </ul>
158 158 </div>
159 159 </%def>
160 160
161 161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
162 162 <div>
163 163 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
164 164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
165 165 %if children_groups:
166 166 ${h.literal(' &raquo; '.join(children_groups))}
167 167 %else:
168 168 ${repo_group_name}
169 169 %endif
170 170 </a>
171 171 </div>
172 172 </%def>
173 173
174 174 <%def name="repo_group_desc(description)">
175 175 <div class="truncate-wrap">${description}</div>
176 176 </%def>
177 177
178 178 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
179 179 <div class="grid_edit">
180 180 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
181 181 </div>
182 182 <div class="grid_delete">
183 183 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
184 184 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
185 185 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
186 186 ${h.end_form()}
187 187 </div>
188 188 </%def>
189 189
190 190
191 191 <%def name="user_actions(user_id, username)">
192 192 <div class="grid_edit">
193 193 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
194 194 <i class="icon-pencil"></i>Edit</a>
195 195 </div>
196 196 <div class="grid_delete">
197 197 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
198 198 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
199 199 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
200 200 ${h.end_form()}
201 201 </div>
202 202 </%def>
203 203
204 204 <%def name="user_group_actions(user_group_id, user_group_name)">
205 205 <div class="grid_edit">
206 206 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
207 207 </div>
208 208 <div class="grid_delete">
209 209 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
210 210 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
211 211 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
212 212 ${h.end_form()}
213 213 </div>
214 214 </%def>
215 215
216 216
217 217 <%def name="user_name(user_id, username)">
218 218 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
219 219 </%def>
220 220
221 221 <%def name="user_profile(username)">
222 222 ${base.gravatar_with_user(username, 16)}
223 223 </%def>
224 224
225 225 <%def name="user_group_name(user_group_id, user_group_name)">
226 226 <div>
227 227 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
228 228 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
229 229 </div>
230 230 </%def>
231 231
232 232
233 233 ## GISTS
234 234
235 235 <%def name="gist_gravatar(full_contact)">
236 236 <div class="gist_gravatar">
237 237 ${base.gravatar(full_contact, 30)}
238 238 </div>
239 239 </%def>
240 240
241 241 <%def name="gist_access_id(gist_access_id, full_contact)">
242 242 <div>
243 243 <b>
244 244 <a href="${h.url('gist',gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
245 245 </b>
246 246 </div>
247 247 </%def>
248 248
249 249 <%def name="gist_author(full_contact, created_on, expires)">
250 250 ${base.gravatar_with_user(full_contact, 16)}
251 251 </%def>
252 252
253 253
254 254 <%def name="gist_created(created_on)">
255 255 <div class="created">
256 256 ${h.age_component(created_on, time_is_local=True)}
257 257 </div>
258 258 </%def>
259 259
260 260 <%def name="gist_expires(expires)">
261 261 <div class="created">
262 262 %if expires == -1:
263 263 ${_('never')}
264 264 %else:
265 265 ${h.age_component(h.time_to_utcdatetime(expires))}
266 266 %endif
267 267 </div>
268 268 </%def>
269 269
270 270 <%def name="gist_type(gist_type)">
271 271 %if gist_type != 'public':
272 272 <div class="tag">${_('Private')}</div>
273 273 %endif
274 274 </%def>
275 275
276 276 <%def name="gist_description(gist_description)">
277 277 ${gist_description}
278 278 </%def>
279 279
280 280
281 281 ## PULL REQUESTS GRID RENDERERS
282 282
283 283 <%def name="pullrequest_target_repo(repo_name)">
284 284 <div class="truncate">
285 285 ${h.link_to(repo_name,h.url('summary_home',repo_name=repo_name))}
286 286 </div>
287 287 </%def>
288 288 <%def name="pullrequest_status(status)">
289 289 <div class="${'flag_status %s' % status} pull-left"></div>
290 290 </%def>
291 291
292 292 <%def name="pullrequest_title(title, description)">
293 293 ${title} <br/>
294 294 ${h.shorter(description, 40)}
295 295 </%def>
296 296
297 297 <%def name="pullrequest_comments(comments_nr)">
298 298 <i class="icon-comment"></i> ${comments_nr}
299 299 </%def>
300 300
301 301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
302 302 <a href="${h.url('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
303 303 % if short:
304 304 #${pull_request_id}
305 305 % else:
306 306 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
307 307 % endif
308 308 </a>
309 309 </%def>
310 310
311 311 <%def name="pullrequest_updated_on(updated_on)">
312 312 ${h.age_component(h.time_to_utcdatetime(updated_on))}
313 313 </%def>
314 314
315 315 <%def name="pullrequest_author(full_contact)">
316 316 ${base.gravatar_with_user(full_contact, 16)}
317 317 </%def>
@@ -1,1208 +1,1117 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import urllib
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.lib import auth
27 27 from rhodecode.lib.utils2 import safe_str, str2bool, safe_unicode
28 28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 29 from rhodecode.model.db import Repository, RepoGroup, UserRepoToPerm, User,\
30 30 Permission
31 31 from rhodecode.model.meta import Session
32 32 from rhodecode.model.repo import RepoModel
33 33 from rhodecode.model.repo_group import RepoGroupModel
34 34 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
35 35 from rhodecode.model.user import UserModel
36 36 from rhodecode.tests import (
37 37 login_user_session, url, assert_session_flash, TEST_USER_ADMIN_LOGIN,
38 38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, HG_REPO, GIT_REPO,
39 39 logout_user_session)
40 40 from rhodecode.tests.fixture import Fixture, error_function
41 41 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
42 42
43 43 fixture = Fixture()
44 44
45 45
46 46 @pytest.mark.usefixtures("app")
47 47 class TestAdminRepos(object):
48 48
49 49 def test_index(self):
50 50 self.app.get(url('repos'))
51 51
52 52 def test_create_page_restricted(self, autologin_user, backend):
53 53 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
54 54 response = self.app.get(url('new_repo'), status=200)
55 55 assert_response = AssertResponse(response)
56 56 element = assert_response.get_element('#repo_type')
57 57 assert element.text_content() == '\ngit\n'
58 58
59 59 def test_create_page_non_restricted(self, autologin_user, backend):
60 60 response = self.app.get(url('new_repo'), status=200)
61 61 assert_response = AssertResponse(response)
62 62 assert_response.element_contains('#repo_type', 'git')
63 63 assert_response.element_contains('#repo_type', 'svn')
64 64 assert_response.element_contains('#repo_type', 'hg')
65 65
66 66 @pytest.mark.parametrize("suffix",
67 67 [u'', u'xxa'], ids=['', 'non-ascii'])
68 68 def test_create(self, autologin_user, backend, suffix, csrf_token):
69 69 repo_name_unicode = backend.new_repo_name(suffix=suffix)
70 70 repo_name = repo_name_unicode.encode('utf8')
71 71 description_unicode = u'description for newly created repo' + suffix
72 72 description = description_unicode.encode('utf8')
73 73 response = self.app.post(
74 74 url('repos'),
75 75 fixture._get_repo_create_params(
76 76 repo_private=False,
77 77 repo_name=repo_name,
78 78 repo_type=backend.alias,
79 79 repo_description=description,
80 80 csrf_token=csrf_token),
81 81 status=302)
82 82
83 83 self.assert_repository_is_created_correctly(
84 84 repo_name, description, backend)
85 85
86 86 def test_create_numeric(self, autologin_user, backend, csrf_token):
87 87 numeric_repo = '1234'
88 88 repo_name = numeric_repo
89 89 description = 'description for newly created repo' + numeric_repo
90 90 self.app.post(
91 91 url('repos'),
92 92 fixture._get_repo_create_params(
93 93 repo_private=False,
94 94 repo_name=repo_name,
95 95 repo_type=backend.alias,
96 96 repo_description=description,
97 97 csrf_token=csrf_token))
98 98
99 99 self.assert_repository_is_created_correctly(
100 100 repo_name, description, backend)
101 101
102 102 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ‡Δ™'], ids=['', 'non-ascii'])
103 103 def test_create_in_group(
104 104 self, autologin_user, backend, suffix, csrf_token):
105 105 # create GROUP
106 106 group_name = 'sometest_%s' % backend.alias
107 107 gr = RepoGroupModel().create(group_name=group_name,
108 108 group_description='test',
109 109 owner=TEST_USER_ADMIN_LOGIN)
110 110 Session().commit()
111 111
112 112 repo_name = u'ingroup' + suffix
113 113 repo_name_full = RepoGroup.url_sep().join(
114 114 [group_name, repo_name])
115 115 description = u'description for newly created repo'
116 116 self.app.post(
117 117 url('repos'),
118 118 fixture._get_repo_create_params(
119 119 repo_private=False,
120 120 repo_name=safe_str(repo_name),
121 121 repo_type=backend.alias,
122 122 repo_description=description,
123 123 repo_group=gr.group_id,
124 124 csrf_token=csrf_token))
125 125
126 126 # TODO: johbo: Cleanup work to fixture
127 127 try:
128 128 self.assert_repository_is_created_correctly(
129 129 repo_name_full, description, backend)
130 130
131 131 new_repo = RepoModel().get_by_repo_name(repo_name_full)
132 132 inherited_perms = UserRepoToPerm.query().filter(
133 133 UserRepoToPerm.repository_id == new_repo.repo_id).all()
134 134 assert len(inherited_perms) == 1
135 135 finally:
136 136 RepoModel().delete(repo_name_full)
137 137 RepoGroupModel().delete(group_name)
138 138 Session().commit()
139 139
140 140 def test_create_in_group_numeric(
141 141 self, autologin_user, backend, csrf_token):
142 142 # create GROUP
143 143 group_name = 'sometest_%s' % backend.alias
144 144 gr = RepoGroupModel().create(group_name=group_name,
145 145 group_description='test',
146 146 owner=TEST_USER_ADMIN_LOGIN)
147 147 Session().commit()
148 148
149 149 repo_name = '12345'
150 150 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
151 151 description = 'description for newly created repo'
152 152 self.app.post(
153 153 url('repos'),
154 154 fixture._get_repo_create_params(
155 155 repo_private=False,
156 156 repo_name=repo_name,
157 157 repo_type=backend.alias,
158 158 repo_description=description,
159 159 repo_group=gr.group_id,
160 160 csrf_token=csrf_token))
161 161
162 162 # TODO: johbo: Cleanup work to fixture
163 163 try:
164 164 self.assert_repository_is_created_correctly(
165 165 repo_name_full, description, backend)
166 166
167 167 new_repo = RepoModel().get_by_repo_name(repo_name_full)
168 168 inherited_perms = UserRepoToPerm.query()\
169 169 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
170 170 assert len(inherited_perms) == 1
171 171 finally:
172 172 RepoModel().delete(repo_name_full)
173 173 RepoGroupModel().delete(group_name)
174 174 Session().commit()
175 175
176 176 def test_create_in_group_without_needed_permissions(self, backend):
177 177 session = login_user_session(
178 178 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
179 179 csrf_token = auth.get_csrf_token(session)
180 180 # revoke
181 181 user_model = UserModel()
182 182 # disable fork and create on default user
183 183 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
184 184 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
185 185 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
186 186 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
187 187
188 188 # disable on regular user
189 189 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
190 190 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
191 191 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
192 192 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
193 193 Session().commit()
194 194
195 195 # create GROUP
196 196 group_name = 'reg_sometest_%s' % backend.alias
197 197 gr = RepoGroupModel().create(group_name=group_name,
198 198 group_description='test',
199 199 owner=TEST_USER_ADMIN_LOGIN)
200 200 Session().commit()
201 201
202 202 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
203 203 gr_allowed = RepoGroupModel().create(
204 204 group_name=group_name_allowed,
205 205 group_description='test',
206 206 owner=TEST_USER_REGULAR_LOGIN)
207 207 Session().commit()
208 208
209 209 repo_name = 'ingroup'
210 210 description = 'description for newly created repo'
211 211 response = self.app.post(
212 212 url('repos'),
213 213 fixture._get_repo_create_params(
214 214 repo_private=False,
215 215 repo_name=repo_name,
216 216 repo_type=backend.alias,
217 217 repo_description=description,
218 218 repo_group=gr.group_id,
219 219 csrf_token=csrf_token))
220 220
221 221 response.mustcontain('Invalid value')
222 222
223 223 # user is allowed to create in this group
224 224 repo_name = 'ingroup'
225 225 repo_name_full = RepoGroup.url_sep().join(
226 226 [group_name_allowed, repo_name])
227 227 description = 'description for newly created repo'
228 228 response = self.app.post(
229 229 url('repos'),
230 230 fixture._get_repo_create_params(
231 231 repo_private=False,
232 232 repo_name=repo_name,
233 233 repo_type=backend.alias,
234 234 repo_description=description,
235 235 repo_group=gr_allowed.group_id,
236 236 csrf_token=csrf_token))
237 237
238 238 # TODO: johbo: Cleanup in pytest fixture
239 239 try:
240 240 self.assert_repository_is_created_correctly(
241 241 repo_name_full, description, backend)
242 242
243 243 new_repo = RepoModel().get_by_repo_name(repo_name_full)
244 244 inherited_perms = UserRepoToPerm.query().filter(
245 245 UserRepoToPerm.repository_id == new_repo.repo_id).all()
246 246 assert len(inherited_perms) == 1
247 247
248 248 assert repo_on_filesystem(repo_name_full)
249 249 finally:
250 250 RepoModel().delete(repo_name_full)
251 251 RepoGroupModel().delete(group_name)
252 252 RepoGroupModel().delete(group_name_allowed)
253 253 Session().commit()
254 254
255 255 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
256 256 csrf_token):
257 257 # create GROUP
258 258 group_name = 'sometest_%s' % backend.alias
259 259 gr = RepoGroupModel().create(group_name=group_name,
260 260 group_description='test',
261 261 owner=TEST_USER_ADMIN_LOGIN)
262 262 perm = Permission.get_by_key('repository.write')
263 263 RepoGroupModel().grant_user_permission(
264 264 gr, TEST_USER_REGULAR_LOGIN, perm)
265 265
266 266 # add repo permissions
267 267 Session().commit()
268 268
269 269 repo_name = 'ingroup_inherited_%s' % backend.alias
270 270 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
271 271 description = 'description for newly created repo'
272 272 self.app.post(
273 273 url('repos'),
274 274 fixture._get_repo_create_params(
275 275 repo_private=False,
276 276 repo_name=repo_name,
277 277 repo_type=backend.alias,
278 278 repo_description=description,
279 279 repo_group=gr.group_id,
280 280 repo_copy_permissions=True,
281 281 csrf_token=csrf_token))
282 282
283 283 # TODO: johbo: Cleanup to pytest fixture
284 284 try:
285 285 self.assert_repository_is_created_correctly(
286 286 repo_name_full, description, backend)
287 287 except Exception:
288 288 RepoGroupModel().delete(group_name)
289 289 Session().commit()
290 290 raise
291 291
292 292 # check if inherited permissions are applied
293 293 new_repo = RepoModel().get_by_repo_name(repo_name_full)
294 294 inherited_perms = UserRepoToPerm.query().filter(
295 295 UserRepoToPerm.repository_id == new_repo.repo_id).all()
296 296 assert len(inherited_perms) == 2
297 297
298 298 assert TEST_USER_REGULAR_LOGIN in [
299 299 x.user.username for x in inherited_perms]
300 300 assert 'repository.write' in [
301 301 x.permission.permission_name for x in inherited_perms]
302 302
303 303 RepoModel().delete(repo_name_full)
304 304 RepoGroupModel().delete(group_name)
305 305 Session().commit()
306 306
307 307 @pytest.mark.xfail_backends(
308 308 "git", "hg", reason="Missing reposerver support")
309 309 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
310 310 csrf_token):
311 311 source_repo = backend.create_repo(number_of_commits=2)
312 312 source_repo_name = source_repo.repo_name
313 313 reposerver.serve(source_repo.scm_instance())
314 314
315 315 repo_name = backend.new_repo_name()
316 316 response = self.app.post(
317 317 url('repos'),
318 318 fixture._get_repo_create_params(
319 319 repo_private=False,
320 320 repo_name=repo_name,
321 321 repo_type=backend.alias,
322 322 repo_description='',
323 323 clone_uri=reposerver.url,
324 324 csrf_token=csrf_token),
325 325 status=302)
326 326
327 327 # Should be redirected to the creating page
328 328 response.mustcontain('repo_creating')
329 329
330 330 # Expecting that both repositories have same history
331 331 source_repo = RepoModel().get_by_repo_name(source_repo_name)
332 332 source_vcs = source_repo.scm_instance()
333 333 repo = RepoModel().get_by_repo_name(repo_name)
334 334 repo_vcs = repo.scm_instance()
335 335 assert source_vcs[0].message == repo_vcs[0].message
336 336 assert source_vcs.count() == repo_vcs.count()
337 337 assert source_vcs.commit_ids == repo_vcs.commit_ids
338 338
339 339 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
340 340 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
341 341 csrf_token):
342 342 repo_name = backend.new_repo_name()
343 343 description = 'description for newly created repo'
344 344 response = self.app.post(
345 345 url('repos'),
346 346 fixture._get_repo_create_params(
347 347 repo_private=False,
348 348 repo_name=repo_name,
349 349 repo_type=backend.alias,
350 350 repo_description=description,
351 351 clone_uri='http://repo.invalid/repo',
352 352 csrf_token=csrf_token))
353 353 response.mustcontain('invalid clone url')
354 354
355 355 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
356 356 def test_create_remote_repo_wrong_clone_uri_hg_svn(
357 357 self, autologin_user, backend, csrf_token):
358 358 repo_name = backend.new_repo_name()
359 359 description = 'description for newly created repo'
360 360 response = self.app.post(
361 361 url('repos'),
362 362 fixture._get_repo_create_params(
363 363 repo_private=False,
364 364 repo_name=repo_name,
365 365 repo_type=backend.alias,
366 366 repo_description=description,
367 367 clone_uri='svn+http://svn.invalid/repo',
368 368 csrf_token=csrf_token))
369 369 response.mustcontain('invalid clone url')
370 370
371 371 def test_create_with_git_suffix(
372 372 self, autologin_user, backend, csrf_token):
373 373 repo_name = backend.new_repo_name() + ".git"
374 374 description = 'description for newly created repo'
375 375 response = self.app.post(
376 376 url('repos'),
377 377 fixture._get_repo_create_params(
378 378 repo_private=False,
379 379 repo_name=repo_name,
380 380 repo_type=backend.alias,
381 381 repo_description=description,
382 382 csrf_token=csrf_token))
383 383 response.mustcontain('Repository name cannot end with .git')
384 384
385 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
386 def test_delete(self, autologin_user, backend, suffix, csrf_token):
387 repo = backend.create_repo(name_suffix=suffix)
388 repo_name = repo.repo_name
389
390 response = self.app.post(url('repo', repo_name=repo_name),
391 params={'_method': 'delete',
392 'csrf_token': csrf_token})
393 assert_session_flash(response, 'Deleted repository %s' % (repo_name))
394 response.follow()
395
396 # check if repo was deleted from db
397 assert RepoModel().get_by_repo_name(repo_name) is None
398 assert not repo_on_filesystem(repo_name)
399
400 385 def test_show(self, autologin_user, backend):
401 386 self.app.get(url('repo', repo_name=backend.repo_name))
402 387
403 388 def test_default_user_cannot_access_private_repo_in_a_group(
404 389 self, autologin_user, user_util, backend, csrf_token):
405 390
406 391 group = user_util.create_repo_group()
407 392
408 393 repo = backend.create_repo(
409 394 repo_private=True, repo_group=group, repo_copy_permissions=True)
410 395
411 396 permissions = _get_permission_for_user(
412 397 user='default', repo=repo.repo_name)
413 398 assert len(permissions) == 1
414 399 assert permissions[0].permission.permission_name == 'repository.none'
415 400 assert permissions[0].repository.private is True
416 401
417 def test_set_repo_fork_has_no_self_id(self, autologin_user, backend):
418 repo = backend.repo
419 response = self.app.get(
420 url('edit_repo_advanced', repo_name=backend.repo_name))
421 opt = """<option value="%s">vcs_test_git</option>""" % repo.repo_id
422 response.mustcontain(no=[opt])
423
424 def test_set_fork_of_target_repo(
425 self, autologin_user, backend, csrf_token):
426 target_repo = 'target_%s' % backend.alias
427 fixture.create_repo(target_repo, repo_type=backend.alias)
428 repo2 = Repository.get_by_repo_name(target_repo)
429 response = self.app.post(
430 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
431 params={'id_fork_of': repo2.repo_id, '_method': 'put',
432 'csrf_token': csrf_token})
433 repo = Repository.get_by_repo_name(backend.repo_name)
434 repo2 = Repository.get_by_repo_name(target_repo)
435 assert_session_flash(
436 response,
437 'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name))
438
439 assert repo.fork == repo2
440 response = response.follow()
441 # check if given repo is selected
442
443 opt = 'This repository is a fork of <a href="%s">%s</a>' % (
444 url('summary_home', repo_name=repo2.repo_name), repo2.repo_name)
445
446 response.mustcontain(opt)
447
448 fixture.destroy_repo(target_repo, forks='detach')
449
450 @pytest.mark.backends("hg", "git")
451 def test_set_fork_of_other_type_repo(self, autologin_user, backend,
452 csrf_token):
453 TARGET_REPO_MAP = {
454 'git': {
455 'type': 'hg',
456 'repo_name': HG_REPO},
457 'hg': {
458 'type': 'git',
459 'repo_name': GIT_REPO},
460 }
461 target_repo = TARGET_REPO_MAP[backend.alias]
462
463 repo2 = Repository.get_by_repo_name(target_repo['repo_name'])
464 response = self.app.post(
465 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
466 params={'id_fork_of': repo2.repo_id, '_method': 'put',
467 'csrf_token': csrf_token})
468 assert_session_flash(
469 response,
470 'Cannot set repository as fork of repository with other type')
471
472 def test_set_fork_of_none(self, autologin_user, backend, csrf_token):
473 # mark it as None
474 response = self.app.post(
475 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
476 params={'id_fork_of': None, '_method': 'put',
477 'csrf_token': csrf_token})
478 assert_session_flash(
479 response,
480 'Marked repo %s as fork of %s'
481 % (backend.repo_name, "Nothing"))
482 assert backend.repo.fork is None
483
484 def test_set_fork_of_same_repo(self, autologin_user, backend, csrf_token):
485 repo = Repository.get_by_repo_name(backend.repo_name)
486 response = self.app.post(
487 url('edit_repo_advanced_fork', repo_name=backend.repo_name),
488 params={'id_fork_of': repo.repo_id, '_method': 'put',
489 'csrf_token': csrf_token})
490 assert_session_flash(
491 response, 'An error occurred during this operation')
492
493 402 def test_create_on_top_level_without_permissions(self, backend):
494 403 session = login_user_session(
495 404 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
496 405 csrf_token = auth.get_csrf_token(session)
497 406
498 407 # revoke
499 408 user_model = UserModel()
500 409 # disable fork and create on default user
501 410 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
502 411 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
503 412 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
504 413 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
505 414
506 415 # disable on regular user
507 416 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
508 417 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
509 418 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
510 419 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
511 420 Session().commit()
512 421
513 422 repo_name = backend.new_repo_name()
514 423 description = 'description for newly created repo'
515 424 response = self.app.post(
516 425 url('repos'),
517 426 fixture._get_repo_create_params(
518 427 repo_private=False,
519 428 repo_name=repo_name,
520 429 repo_type=backend.alias,
521 430 repo_description=description,
522 431 csrf_token=csrf_token))
523 432
524 433 response.mustcontain(
525 434 u"You do not have the permission to store repositories in "
526 435 u"the root location.")
527 436
528 437 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
529 438 def test_create_repo_when_filesystem_op_fails(
530 439 self, autologin_user, backend, csrf_token):
531 440 repo_name = backend.new_repo_name()
532 441 description = 'description for newly created repo'
533 442
534 443 response = self.app.post(
535 444 url('repos'),
536 445 fixture._get_repo_create_params(
537 446 repo_private=False,
538 447 repo_name=repo_name,
539 448 repo_type=backend.alias,
540 449 repo_description=description,
541 450 csrf_token=csrf_token))
542 451
543 452 assert_session_flash(
544 453 response, 'Error creating repository %s' % repo_name)
545 454 # repo must not be in db
546 455 assert backend.repo is None
547 456 # repo must not be in filesystem !
548 457 assert not repo_on_filesystem(repo_name)
549 458
550 459 def assert_repository_is_created_correctly(
551 460 self, repo_name, description, backend):
552 461 repo_name_utf8 = safe_str(repo_name)
553 462
554 463 # run the check page that triggers the flash message
555 464 response = self.app.get(url('repo_check_home', repo_name=repo_name))
556 465 assert response.json == {u'result': True}
557 466
558 467 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
559 468 urllib.quote(repo_name_utf8), repo_name)
560 469 assert_session_flash(response, flash_msg)
561 470
562 471 # test if the repo was created in the database
563 472 new_repo = RepoModel().get_by_repo_name(repo_name)
564 473
565 474 assert new_repo.repo_name == repo_name
566 475 assert new_repo.description == description
567 476
568 477 # test if the repository is visible in the list ?
569 478 response = self.app.get(url('summary_home', repo_name=repo_name))
570 479 response.mustcontain(repo_name)
571 480 response.mustcontain(backend.alias)
572 481
573 482 assert repo_on_filesystem(repo_name)
574 483
575 484
576 485 @pytest.mark.usefixtures("app")
577 486 class TestVcsSettings(object):
578 487 FORM_DATA = {
579 488 'inherit_global_settings': False,
580 489 'hooks_changegroup_repo_size': False,
581 490 'hooks_changegroup_push_logger': False,
582 491 'hooks_outgoing_pull_logger': False,
583 492 'extensions_largefiles': False,
584 493 'extensions_evolve': False,
585 494 'phases_publish': 'False',
586 495 'rhodecode_pr_merge_enabled': False,
587 496 'rhodecode_use_outdated_comments': False,
588 497 'new_svn_branch': '',
589 498 'new_svn_tag': ''
590 499 }
591 500
592 501 @pytest.mark.skip_backends('svn')
593 502 def test_global_settings_initial_values(self, autologin_user, backend):
594 503 repo_name = backend.repo_name
595 504 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
596 505
597 506 expected_settings = (
598 507 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
599 508 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
600 509 'hooks_outgoing_pull_logger'
601 510 )
602 511 for setting in expected_settings:
603 512 self.assert_repo_value_equals_global_value(response, setting)
604 513
605 514 def test_show_settings_requires_repo_admin_permission(
606 515 self, backend, user_util, settings_util):
607 516 repo = backend.create_repo()
608 517 repo_name = repo.repo_name
609 518 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
610 519 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
611 520 login_user_session(
612 521 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
613 522 self.app.get(url('repo_vcs_settings', repo_name=repo_name), status=200)
614 523
615 524 def test_inherit_global_settings_flag_is_true_by_default(
616 525 self, autologin_user, backend):
617 526 repo_name = backend.repo_name
618 527 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
619 528
620 529 assert_response = AssertResponse(response)
621 530 element = assert_response.get_element('#inherit_global_settings')
622 531 assert element.checked
623 532
624 533 @pytest.mark.parametrize('checked_value', [True, False])
625 534 def test_inherit_global_settings_value(
626 535 self, autologin_user, backend, checked_value, settings_util):
627 536 repo = backend.create_repo()
628 537 repo_name = repo.repo_name
629 538 settings_util.create_repo_rhodecode_setting(
630 539 repo, 'inherit_vcs_settings', checked_value, 'bool')
631 540 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
632 541
633 542 assert_response = AssertResponse(response)
634 543 element = assert_response.get_element('#inherit_global_settings')
635 544 assert element.checked == checked_value
636 545
637 546 @pytest.mark.skip_backends('svn')
638 547 def test_hooks_settings_are_created(
639 548 self, autologin_user, backend, csrf_token):
640 549 repo_name = backend.repo_name
641 550 data = self.FORM_DATA.copy()
642 551 data['csrf_token'] = csrf_token
643 552 self.app.post(
644 553 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
645 554 settings = SettingsModel(repo=repo_name)
646 555 try:
647 556 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
648 557 ui = settings.get_ui_by_section_and_key(section, key)
649 558 assert ui.ui_active is False
650 559 finally:
651 560 self._cleanup_repo_settings(settings)
652 561
653 562 def test_hooks_settings_are_not_created_for_svn(
654 563 self, autologin_user, backend_svn, csrf_token):
655 564 repo_name = backend_svn.repo_name
656 565 data = self.FORM_DATA.copy()
657 566 data['csrf_token'] = csrf_token
658 567 self.app.post(
659 568 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
660 569 settings = SettingsModel(repo=repo_name)
661 570 try:
662 571 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
663 572 ui = settings.get_ui_by_section_and_key(section, key)
664 573 assert ui is None
665 574 finally:
666 575 self._cleanup_repo_settings(settings)
667 576
668 577 @pytest.mark.skip_backends('svn')
669 578 def test_hooks_settings_are_updated(
670 579 self, autologin_user, backend, csrf_token):
671 580 repo_name = backend.repo_name
672 581 settings = SettingsModel(repo=repo_name)
673 582 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
674 583 settings.create_ui_section_value(section, '', key=key, active=True)
675 584
676 585 data = self.FORM_DATA.copy()
677 586 data['csrf_token'] = csrf_token
678 587 self.app.post(
679 588 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
680 589 try:
681 590 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
682 591 ui = settings.get_ui_by_section_and_key(section, key)
683 592 assert ui.ui_active is False
684 593 finally:
685 594 self._cleanup_repo_settings(settings)
686 595
687 596 def test_hooks_settings_are_not_updated_for_svn(
688 597 self, autologin_user, backend_svn, csrf_token):
689 598 repo_name = backend_svn.repo_name
690 599 settings = SettingsModel(repo=repo_name)
691 600 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
692 601 settings.create_ui_section_value(section, '', key=key, active=True)
693 602
694 603 data = self.FORM_DATA.copy()
695 604 data['csrf_token'] = csrf_token
696 605 self.app.post(
697 606 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
698 607 try:
699 608 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
700 609 ui = settings.get_ui_by_section_and_key(section, key)
701 610 assert ui.ui_active is True
702 611 finally:
703 612 self._cleanup_repo_settings(settings)
704 613
705 614 @pytest.mark.skip_backends('svn')
706 615 def test_pr_settings_are_created(
707 616 self, autologin_user, backend, csrf_token):
708 617 repo_name = backend.repo_name
709 618 data = self.FORM_DATA.copy()
710 619 data['csrf_token'] = csrf_token
711 620 self.app.post(
712 621 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
713 622 settings = SettingsModel(repo=repo_name)
714 623 try:
715 624 for name in VcsSettingsModel.GENERAL_SETTINGS:
716 625 setting = settings.get_setting_by_name(name)
717 626 assert setting.app_settings_value is False
718 627 finally:
719 628 self._cleanup_repo_settings(settings)
720 629
721 630 def test_pr_settings_are_not_created_for_svn(
722 631 self, autologin_user, backend_svn, csrf_token):
723 632 repo_name = backend_svn.repo_name
724 633 data = self.FORM_DATA.copy()
725 634 data['csrf_token'] = csrf_token
726 635 self.app.post(
727 636 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
728 637 settings = SettingsModel(repo=repo_name)
729 638 try:
730 639 for name in VcsSettingsModel.GENERAL_SETTINGS:
731 640 setting = settings.get_setting_by_name(name)
732 641 assert setting is None
733 642 finally:
734 643 self._cleanup_repo_settings(settings)
735 644
736 645 def test_pr_settings_creation_requires_repo_admin_permission(
737 646 self, backend, user_util, settings_util, csrf_token):
738 647 repo = backend.create_repo()
739 648 repo_name = repo.repo_name
740 649
741 650 logout_user_session(self.app, csrf_token)
742 651 session = login_user_session(
743 652 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
744 653 new_csrf_token = auth.get_csrf_token(session)
745 654
746 655 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
747 656 repo = Repository.get_by_repo_name(repo_name)
748 657 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
749 658 data = self.FORM_DATA.copy()
750 659 data['csrf_token'] = new_csrf_token
751 660 settings = SettingsModel(repo=repo_name)
752 661
753 662 try:
754 663 self.app.post(
755 664 url('repo_vcs_settings', repo_name=repo_name), data,
756 665 status=302)
757 666 finally:
758 667 self._cleanup_repo_settings(settings)
759 668
760 669 @pytest.mark.skip_backends('svn')
761 670 def test_pr_settings_are_updated(
762 671 self, autologin_user, backend, csrf_token):
763 672 repo_name = backend.repo_name
764 673 settings = SettingsModel(repo=repo_name)
765 674 for name in VcsSettingsModel.GENERAL_SETTINGS:
766 675 settings.create_or_update_setting(name, True, 'bool')
767 676
768 677 data = self.FORM_DATA.copy()
769 678 data['csrf_token'] = csrf_token
770 679 self.app.post(
771 680 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
772 681 try:
773 682 for name in VcsSettingsModel.GENERAL_SETTINGS:
774 683 setting = settings.get_setting_by_name(name)
775 684 assert setting.app_settings_value is False
776 685 finally:
777 686 self._cleanup_repo_settings(settings)
778 687
779 688 def test_pr_settings_are_not_updated_for_svn(
780 689 self, autologin_user, backend_svn, csrf_token):
781 690 repo_name = backend_svn.repo_name
782 691 settings = SettingsModel(repo=repo_name)
783 692 for name in VcsSettingsModel.GENERAL_SETTINGS:
784 693 settings.create_or_update_setting(name, True, 'bool')
785 694
786 695 data = self.FORM_DATA.copy()
787 696 data['csrf_token'] = csrf_token
788 697 self.app.post(
789 698 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
790 699 try:
791 700 for name in VcsSettingsModel.GENERAL_SETTINGS:
792 701 setting = settings.get_setting_by_name(name)
793 702 assert setting.app_settings_value is True
794 703 finally:
795 704 self._cleanup_repo_settings(settings)
796 705
797 706 def test_svn_settings_are_created(
798 707 self, autologin_user, backend_svn, csrf_token, settings_util):
799 708 repo_name = backend_svn.repo_name
800 709 data = self.FORM_DATA.copy()
801 710 data['new_svn_tag'] = 'svn-tag'
802 711 data['new_svn_branch'] = 'svn-branch'
803 712 data['csrf_token'] = csrf_token
804 713
805 714 # Create few global settings to make sure that uniqueness validators
806 715 # are not triggered
807 716 settings_util.create_rhodecode_ui(
808 717 VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
809 718 settings_util.create_rhodecode_ui(
810 719 VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag')
811 720
812 721 self.app.post(
813 722 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
814 723 settings = SettingsModel(repo=repo_name)
815 724 try:
816 725 svn_branches = settings.get_ui_by_section(
817 726 VcsSettingsModel.SVN_BRANCH_SECTION)
818 727 svn_branch_names = [b.ui_value for b in svn_branches]
819 728 svn_tags = settings.get_ui_by_section(
820 729 VcsSettingsModel.SVN_TAG_SECTION)
821 730 svn_tag_names = [b.ui_value for b in svn_tags]
822 731 assert 'svn-branch' in svn_branch_names
823 732 assert 'svn-tag' in svn_tag_names
824 733 finally:
825 734 self._cleanup_repo_settings(settings)
826 735
827 736 def test_svn_settings_are_unique(
828 737 self, autologin_user, backend_svn, csrf_token, settings_util):
829 738 repo = backend_svn.repo
830 739 repo_name = repo.repo_name
831 740 data = self.FORM_DATA.copy()
832 741 data['new_svn_tag'] = 'test_tag'
833 742 data['new_svn_branch'] = 'test_branch'
834 743 data['csrf_token'] = csrf_token
835 744 settings_util.create_repo_rhodecode_ui(
836 745 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch')
837 746 settings_util.create_repo_rhodecode_ui(
838 747 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag')
839 748
840 749 response = self.app.post(
841 750 url('repo_vcs_settings', repo_name=repo_name), data, status=200)
842 751 response.mustcontain('Pattern already exists')
843 752
844 753 def test_svn_settings_with_empty_values_are_not_created(
845 754 self, autologin_user, backend_svn, csrf_token):
846 755 repo_name = backend_svn.repo_name
847 756 data = self.FORM_DATA.copy()
848 757 data['csrf_token'] = csrf_token
849 758 self.app.post(
850 759 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
851 760 settings = SettingsModel(repo=repo_name)
852 761 try:
853 762 svn_branches = settings.get_ui_by_section(
854 763 VcsSettingsModel.SVN_BRANCH_SECTION)
855 764 svn_tags = settings.get_ui_by_section(
856 765 VcsSettingsModel.SVN_TAG_SECTION)
857 766 assert len(svn_branches) == 0
858 767 assert len(svn_tags) == 0
859 768 finally:
860 769 self._cleanup_repo_settings(settings)
861 770
862 771 def test_svn_settings_are_shown_for_svn_repository(
863 772 self, autologin_user, backend_svn, csrf_token):
864 773 repo_name = backend_svn.repo_name
865 774 response = self.app.get(
866 775 url('repo_vcs_settings', repo_name=repo_name), status=200)
867 776 response.mustcontain('Subversion Settings')
868 777
869 778 @pytest.mark.skip_backends('svn')
870 779 def test_svn_settings_are_not_created_for_not_svn_repository(
871 780 self, autologin_user, backend, csrf_token):
872 781 repo_name = backend.repo_name
873 782 data = self.FORM_DATA.copy()
874 783 data['csrf_token'] = csrf_token
875 784 self.app.post(
876 785 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
877 786 settings = SettingsModel(repo=repo_name)
878 787 try:
879 788 svn_branches = settings.get_ui_by_section(
880 789 VcsSettingsModel.SVN_BRANCH_SECTION)
881 790 svn_tags = settings.get_ui_by_section(
882 791 VcsSettingsModel.SVN_TAG_SECTION)
883 792 assert len(svn_branches) == 0
884 793 assert len(svn_tags) == 0
885 794 finally:
886 795 self._cleanup_repo_settings(settings)
887 796
888 797 @pytest.mark.skip_backends('svn')
889 798 def test_svn_settings_are_shown_only_for_svn_repository(
890 799 self, autologin_user, backend, csrf_token):
891 800 repo_name = backend.repo_name
892 801 response = self.app.get(
893 802 url('repo_vcs_settings', repo_name=repo_name), status=200)
894 803 response.mustcontain(no='Subversion Settings')
895 804
896 805 def test_hg_settings_are_created(
897 806 self, autologin_user, backend_hg, csrf_token):
898 807 repo_name = backend_hg.repo_name
899 808 data = self.FORM_DATA.copy()
900 809 data['new_svn_tag'] = 'svn-tag'
901 810 data['new_svn_branch'] = 'svn-branch'
902 811 data['csrf_token'] = csrf_token
903 812 self.app.post(
904 813 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
905 814 settings = SettingsModel(repo=repo_name)
906 815 try:
907 816 largefiles_ui = settings.get_ui_by_section_and_key(
908 817 'extensions', 'largefiles')
909 818 assert largefiles_ui.ui_active is False
910 819 phases_ui = settings.get_ui_by_section_and_key(
911 820 'phases', 'publish')
912 821 assert str2bool(phases_ui.ui_value) is False
913 822 finally:
914 823 self._cleanup_repo_settings(settings)
915 824
916 825 def test_hg_settings_are_updated(
917 826 self, autologin_user, backend_hg, csrf_token):
918 827 repo_name = backend_hg.repo_name
919 828 settings = SettingsModel(repo=repo_name)
920 829 settings.create_ui_section_value(
921 830 'extensions', '', key='largefiles', active=True)
922 831 settings.create_ui_section_value(
923 832 'phases', '1', key='publish', active=True)
924 833
925 834 data = self.FORM_DATA.copy()
926 835 data['csrf_token'] = csrf_token
927 836 self.app.post(
928 837 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
929 838 try:
930 839 largefiles_ui = settings.get_ui_by_section_and_key(
931 840 'extensions', 'largefiles')
932 841 assert largefiles_ui.ui_active is False
933 842 phases_ui = settings.get_ui_by_section_and_key(
934 843 'phases', 'publish')
935 844 assert str2bool(phases_ui.ui_value) is False
936 845 finally:
937 846 self._cleanup_repo_settings(settings)
938 847
939 848 def test_hg_settings_are_shown_for_hg_repository(
940 849 self, autologin_user, backend_hg, csrf_token):
941 850 repo_name = backend_hg.repo_name
942 851 response = self.app.get(
943 852 url('repo_vcs_settings', repo_name=repo_name), status=200)
944 853 response.mustcontain('Mercurial Settings')
945 854
946 855 @pytest.mark.skip_backends('hg')
947 856 def test_hg_settings_are_created_only_for_hg_repository(
948 857 self, autologin_user, backend, csrf_token):
949 858 repo_name = backend.repo_name
950 859 data = self.FORM_DATA.copy()
951 860 data['csrf_token'] = csrf_token
952 861 self.app.post(
953 862 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
954 863 settings = SettingsModel(repo=repo_name)
955 864 try:
956 865 largefiles_ui = settings.get_ui_by_section_and_key(
957 866 'extensions', 'largefiles')
958 867 assert largefiles_ui is None
959 868 phases_ui = settings.get_ui_by_section_and_key(
960 869 'phases', 'publish')
961 870 assert phases_ui is None
962 871 finally:
963 872 self._cleanup_repo_settings(settings)
964 873
965 874 @pytest.mark.skip_backends('hg')
966 875 def test_hg_settings_are_shown_only_for_hg_repository(
967 876 self, autologin_user, backend, csrf_token):
968 877 repo_name = backend.repo_name
969 878 response = self.app.get(
970 879 url('repo_vcs_settings', repo_name=repo_name), status=200)
971 880 response.mustcontain(no='Mercurial Settings')
972 881
973 882 @pytest.mark.skip_backends('hg')
974 883 def test_hg_settings_are_updated_only_for_hg_repository(
975 884 self, autologin_user, backend, csrf_token):
976 885 repo_name = backend.repo_name
977 886 settings = SettingsModel(repo=repo_name)
978 887 settings.create_ui_section_value(
979 888 'extensions', '', key='largefiles', active=True)
980 889 settings.create_ui_section_value(
981 890 'phases', '1', key='publish', active=True)
982 891
983 892 data = self.FORM_DATA.copy()
984 893 data['csrf_token'] = csrf_token
985 894 self.app.post(
986 895 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
987 896 try:
988 897 largefiles_ui = settings.get_ui_by_section_and_key(
989 898 'extensions', 'largefiles')
990 899 assert largefiles_ui.ui_active is True
991 900 phases_ui = settings.get_ui_by_section_and_key(
992 901 'phases', 'publish')
993 902 assert phases_ui.ui_value == '1'
994 903 finally:
995 904 self._cleanup_repo_settings(settings)
996 905
997 906 def test_per_repo_svn_settings_are_displayed(
998 907 self, autologin_user, backend_svn, settings_util):
999 908 repo = backend_svn.create_repo()
1000 909 repo_name = repo.repo_name
1001 910 branches = [
1002 911 settings_util.create_repo_rhodecode_ui(
1003 912 repo, VcsSettingsModel.SVN_BRANCH_SECTION,
1004 913 'branch_{}'.format(i))
1005 914 for i in range(10)]
1006 915 tags = [
1007 916 settings_util.create_repo_rhodecode_ui(
1008 917 repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i))
1009 918 for i in range(10)]
1010 919
1011 920 response = self.app.get(
1012 921 url('repo_vcs_settings', repo_name=repo_name), status=200)
1013 922 assert_response = AssertResponse(response)
1014 923 for branch in branches:
1015 924 css_selector = '[name=branch_value_{}]'.format(branch.ui_id)
1016 925 element = assert_response.get_element(css_selector)
1017 926 assert element.value == branch.ui_value
1018 927 for tag in tags:
1019 928 css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id)
1020 929 element = assert_response.get_element(css_selector)
1021 930 assert element.value == tag.ui_value
1022 931
1023 932 def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn(
1024 933 self, autologin_user, backend_svn, settings_util):
1025 934 repo = backend_svn.create_repo()
1026 935 repo_name = repo.repo_name
1027 936 response = self.app.get(
1028 937 url('repo_vcs_settings', repo_name=repo_name), status=200)
1029 938 response.mustcontain(no='<label>Hooks:</label>')
1030 939 response.mustcontain(no='<label>Pull Request Settings:</label>')
1031 940
1032 941 def test_inherit_global_settings_value_is_saved(
1033 942 self, autologin_user, backend, csrf_token):
1034 943 repo_name = backend.repo_name
1035 944 data = self.FORM_DATA.copy()
1036 945 data['csrf_token'] = csrf_token
1037 946 data['inherit_global_settings'] = True
1038 947 self.app.post(
1039 948 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
1040 949
1041 950 settings = SettingsModel(repo=repo_name)
1042 951 vcs_settings = VcsSettingsModel(repo=repo_name)
1043 952 try:
1044 953 assert vcs_settings.inherit_global_settings is True
1045 954 finally:
1046 955 self._cleanup_repo_settings(settings)
1047 956
1048 957 def test_repo_cache_is_invalidated_when_settings_are_updated(
1049 958 self, autologin_user, backend, csrf_token):
1050 959 repo_name = backend.repo_name
1051 960 data = self.FORM_DATA.copy()
1052 961 data['csrf_token'] = csrf_token
1053 962 data['inherit_global_settings'] = True
1054 963 settings = SettingsModel(repo=repo_name)
1055 964
1056 965 invalidation_patcher = mock.patch(
1057 966 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
1058 967 with invalidation_patcher as invalidation_mock:
1059 968 self.app.post(
1060 969 url('repo_vcs_settings', repo_name=repo_name), data,
1061 970 status=302)
1062 971 try:
1063 972 invalidation_mock.assert_called_once_with(repo_name, delete=True)
1064 973 finally:
1065 974 self._cleanup_repo_settings(settings)
1066 975
1067 976 def test_other_settings_not_saved_inherit_global_settings_is_true(
1068 977 self, autologin_user, backend, csrf_token):
1069 978 repo_name = backend.repo_name
1070 979 data = self.FORM_DATA.copy()
1071 980 data['csrf_token'] = csrf_token
1072 981 data['inherit_global_settings'] = True
1073 982 self.app.post(
1074 983 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
1075 984
1076 985 settings = SettingsModel(repo=repo_name)
1077 986 ui_settings = (
1078 987 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1079 988
1080 989 vcs_settings = []
1081 990 try:
1082 991 for section, key in ui_settings:
1083 992 ui = settings.get_ui_by_section_and_key(section, key)
1084 993 if ui:
1085 994 vcs_settings.append(ui)
1086 995 vcs_settings.extend(settings.get_ui_by_section(
1087 996 VcsSettingsModel.SVN_BRANCH_SECTION))
1088 997 vcs_settings.extend(settings.get_ui_by_section(
1089 998 VcsSettingsModel.SVN_TAG_SECTION))
1090 999 for name in VcsSettingsModel.GENERAL_SETTINGS:
1091 1000 setting = settings.get_setting_by_name(name)
1092 1001 if setting:
1093 1002 vcs_settings.append(setting)
1094 1003 assert vcs_settings == []
1095 1004 finally:
1096 1005 self._cleanup_repo_settings(settings)
1097 1006
1098 1007 def test_delete_svn_branch_and_tag_patterns(
1099 1008 self, autologin_user, backend_svn, settings_util, csrf_token):
1100 1009 repo = backend_svn.create_repo()
1101 1010 repo_name = repo.repo_name
1102 1011 branch = settings_util.create_repo_rhodecode_ui(
1103 1012 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1104 1013 cleanup=False)
1105 1014 tag = settings_util.create_repo_rhodecode_ui(
1106 1015 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False)
1107 1016 data = {
1108 1017 '_method': 'delete',
1109 1018 'csrf_token': csrf_token
1110 1019 }
1111 1020 for id_ in (branch.ui_id, tag.ui_id):
1112 1021 data['delete_svn_pattern'] = id_,
1113 1022 self.app.post(
1114 1023 url('repo_vcs_settings', repo_name=repo_name), data,
1115 1024 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1116 1025 settings = VcsSettingsModel(repo=repo_name)
1117 1026 assert settings.get_repo_svn_branch_patterns() == []
1118 1027
1119 1028 def test_delete_svn_branch_requires_repo_admin_permission(
1120 1029 self, backend_svn, user_util, settings_util, csrf_token):
1121 1030 repo = backend_svn.create_repo()
1122 1031 repo_name = repo.repo_name
1123 1032
1124 1033 logout_user_session(self.app, csrf_token)
1125 1034 session = login_user_session(
1126 1035 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
1127 1036 csrf_token = auth.get_csrf_token(session)
1128 1037
1129 1038 repo = Repository.get_by_repo_name(repo_name)
1130 1039 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1131 1040 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
1132 1041 branch = settings_util.create_repo_rhodecode_ui(
1133 1042 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1134 1043 cleanup=False)
1135 1044 data = {
1136 1045 '_method': 'delete',
1137 1046 'csrf_token': csrf_token,
1138 1047 'delete_svn_pattern': branch.ui_id
1139 1048 }
1140 1049 self.app.post(
1141 1050 url('repo_vcs_settings', repo_name=repo_name), data,
1142 1051 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1143 1052
1144 1053 def test_delete_svn_branch_raises_400_when_not_found(
1145 1054 self, autologin_user, backend_svn, settings_util, csrf_token):
1146 1055 repo_name = backend_svn.repo_name
1147 1056 data = {
1148 1057 '_method': 'delete',
1149 1058 'delete_svn_pattern': 123,
1150 1059 'csrf_token': csrf_token
1151 1060 }
1152 1061 self.app.post(
1153 1062 url('repo_vcs_settings', repo_name=repo_name), data,
1154 1063 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1155 1064
1156 1065 def test_delete_svn_branch_raises_400_when_no_id_specified(
1157 1066 self, autologin_user, backend_svn, settings_util, csrf_token):
1158 1067 repo_name = backend_svn.repo_name
1159 1068 data = {
1160 1069 '_method': 'delete',
1161 1070 'csrf_token': csrf_token
1162 1071 }
1163 1072 self.app.post(
1164 1073 url('repo_vcs_settings', repo_name=repo_name), data,
1165 1074 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1166 1075
1167 1076 def _cleanup_repo_settings(self, settings_model):
1168 1077 cleanup = []
1169 1078 ui_settings = (
1170 1079 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1171 1080
1172 1081 for section, key in ui_settings:
1173 1082 ui = settings_model.get_ui_by_section_and_key(section, key)
1174 1083 if ui:
1175 1084 cleanup.append(ui)
1176 1085
1177 1086 cleanup.extend(settings_model.get_ui_by_section(
1178 1087 VcsSettingsModel.INHERIT_SETTINGS))
1179 1088 cleanup.extend(settings_model.get_ui_by_section(
1180 1089 VcsSettingsModel.SVN_BRANCH_SECTION))
1181 1090 cleanup.extend(settings_model.get_ui_by_section(
1182 1091 VcsSettingsModel.SVN_TAG_SECTION))
1183 1092
1184 1093 for name in VcsSettingsModel.GENERAL_SETTINGS:
1185 1094 setting = settings_model.get_setting_by_name(name)
1186 1095 if setting:
1187 1096 cleanup.append(setting)
1188 1097
1189 1098 for object_ in cleanup:
1190 1099 Session().delete(object_)
1191 1100 Session().commit()
1192 1101
1193 1102 def assert_repo_value_equals_global_value(self, response, setting):
1194 1103 assert_response = AssertResponse(response)
1195 1104 global_css_selector = '[name={}_inherited]'.format(setting)
1196 1105 repo_css_selector = '[name={}]'.format(setting)
1197 1106 repo_element = assert_response.get_element(repo_css_selector)
1198 1107 global_element = assert_response.get_element(global_css_selector)
1199 1108 assert repo_element.value == global_element.value
1200 1109
1201 1110
1202 1111 def _get_permission_for_user(user, repo):
1203 1112 perm = UserRepoToPerm.query()\
1204 1113 .filter(UserRepoToPerm.repository ==
1205 1114 Repository.get_by_repo_name(repo))\
1206 1115 .filter(UserRepoToPerm.user == User.get_by_username(user))\
1207 1116 .all()
1208 1117 return perm
@@ -1,274 +1,272 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.tests import *
24 24 from rhodecode.tests.fixture import Fixture
25 25
26 26 from rhodecode.model.db import Repository
27 27 from rhodecode.model.repo import RepoModel
28 28 from rhodecode.model.user import UserModel
29 29 from rhodecode.model.meta import Session
30 30
31 31 fixture = Fixture()
32 32
33 33
34 34 class _BaseTest(TestController):
35 35
36 36 REPO = None
37 37 REPO_TYPE = None
38 38 NEW_REPO = None
39 39 REPO_FORK = None
40 40
41 41 @pytest.fixture(autouse=True)
42 42 def prepare(self, request, pylonsapp):
43 43 self.username = u'forkuser'
44 44 self.password = u'qweqwe'
45 45 self.u1 = fixture.create_user(self.username, password=self.password,
46 46 email=u'fork_king@rhodecode.org')
47 47 Session().commit()
48 48 self.u1id = self.u1.user_id
49 49 request.addfinalizer(self.cleanup)
50 50
51 51 def cleanup(self):
52 52 u1 = UserModel().get(self.u1id)
53 53 Session().delete(u1)
54 54 Session().commit()
55 55
56 56 def test_index(self):
57 57 self.log_user()
58 58 repo_name = self.REPO
59 59 response = self.app.get(url(controller='forks', action='forks',
60 60 repo_name=repo_name))
61 61
62 62 response.mustcontain("""There are no forks yet""")
63 63
64 64 def test_no_permissions_to_fork(self):
65 65 usr = self.log_user(TEST_USER_REGULAR_LOGIN,
66 66 TEST_USER_REGULAR_PASS)['user_id']
67 67 user_model = UserModel()
68 68 user_model.revoke_perm(usr, 'hg.fork.repository')
69 69 user_model.grant_perm(usr, 'hg.fork.none')
70 70 u = UserModel().get(usr)
71 71 u.inherit_default_permissions = False
72 72 Session().commit()
73 73 # try create a fork
74 74 repo_name = self.REPO
75 75 self.app.post(
76 76 url(controller='forks', action='fork_create', repo_name=repo_name),
77 77 {'csrf_token': self.csrf_token}, status=403)
78 78
79 79 def test_index_with_fork(self):
80 80 self.log_user()
81 81
82 82 # create a fork
83 83 fork_name = self.REPO_FORK
84 84 description = 'fork of vcs test'
85 85 repo_name = self.REPO
86 86 source_repo = Repository.get_by_repo_name(repo_name)
87 87 creation_args = {
88 88 'repo_name': fork_name,
89 89 'repo_group': '',
90 90 'fork_parent_id': source_repo.repo_id,
91 91 'repo_type': self.REPO_TYPE,
92 92 'description': description,
93 93 'private': 'False',
94 94 'landing_rev': 'rev:tip',
95 95 'csrf_token': self.csrf_token,
96 96 }
97 97
98 98 self.app.post(url(controller='forks', action='fork_create',
99 99 repo_name=repo_name), creation_args)
100 100
101 101 response = self.app.get(url(controller='forks', action='forks',
102 102 repo_name=repo_name))
103 103
104 104 response.mustcontain(
105 105 """<a href="/%s">%s</a>""" % (fork_name, fork_name)
106 106 )
107 107
108 108 # remove this fork
109 response = self.app.post(
110 url('repo', repo_name=fork_name),
111 params={'_method': 'delete', 'csrf_token': self.csrf_token})
109 fixture.destroy_repo(fork_name)
112 110
113 111 def test_fork_create_into_group(self):
114 112 self.log_user()
115 113 group = fixture.create_repo_group('vc')
116 114 group_id = group.group_id
117 115 fork_name = self.REPO_FORK
118 116 fork_name_full = 'vc/%s' % fork_name
119 117 description = 'fork of vcs test'
120 118 repo_name = self.REPO
121 119 source_repo = Repository.get_by_repo_name(repo_name)
122 120 creation_args = {
123 121 'repo_name': fork_name,
124 122 'repo_group': group_id,
125 123 'fork_parent_id': source_repo.repo_id,
126 124 'repo_type': self.REPO_TYPE,
127 125 'description': description,
128 126 'private': 'False',
129 127 'landing_rev': 'rev:tip',
130 128 'csrf_token': self.csrf_token,
131 129 }
132 130 self.app.post(url(controller='forks', action='fork_create',
133 131 repo_name=repo_name), creation_args)
134 132 repo = Repository.get_by_repo_name(fork_name_full)
135 133 assert repo.fork.repo_name == self.REPO
136 134
137 135 # run the check page that triggers the flash message
138 136 response = self.app.get(url('repo_check_home', repo_name=fork_name_full))
139 137 # test if we have a message that fork is ok
140 138 assert_session_flash(response,
141 139 'Forked repository %s as <a href="/%s">%s</a>'
142 140 % (repo_name, fork_name_full, fork_name_full))
143 141
144 142 # test if the fork was created in the database
145 143 fork_repo = Session().query(Repository)\
146 144 .filter(Repository.repo_name == fork_name_full).one()
147 145
148 146 assert fork_repo.repo_name == fork_name_full
149 147 assert fork_repo.fork.repo_name == repo_name
150 148
151 149 # test if the repository is visible in the list ?
152 150 response = self.app.get(url('summary_home', repo_name=fork_name_full))
153 151 response.mustcontain(fork_name_full)
154 152 response.mustcontain(self.REPO_TYPE)
155 153
156 154 response.mustcontain('Fork of')
157 155 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
158 156
159 157 fixture.destroy_repo(fork_name_full)
160 158 fixture.destroy_repo_group(group_id)
161 159
162 160 def test_z_fork_create(self):
163 161 self.log_user()
164 162 fork_name = self.REPO_FORK
165 163 description = 'fork of vcs test'
166 164 repo_name = self.REPO
167 165 source_repo = Repository.get_by_repo_name(repo_name)
168 166 creation_args = {
169 167 'repo_name': fork_name,
170 168 'repo_group': '',
171 169 'fork_parent_id': source_repo.repo_id,
172 170 'repo_type': self.REPO_TYPE,
173 171 'description': description,
174 172 'private': 'False',
175 173 'landing_rev': 'rev:tip',
176 174 'csrf_token': self.csrf_token,
177 175 }
178 176 self.app.post(url(controller='forks', action='fork_create',
179 177 repo_name=repo_name), creation_args)
180 178 repo = Repository.get_by_repo_name(self.REPO_FORK)
181 179 assert repo.fork.repo_name == self.REPO
182 180
183 181 # run the check page that triggers the flash message
184 182 response = self.app.get(url('repo_check_home', repo_name=fork_name))
185 183 # test if we have a message that fork is ok
186 184 assert_session_flash(response,
187 185 'Forked repository %s as <a href="/%s">%s</a>'
188 186 % (repo_name, fork_name, fork_name))
189 187
190 188 # test if the fork was created in the database
191 189 fork_repo = Session().query(Repository)\
192 190 .filter(Repository.repo_name == fork_name).one()
193 191
194 192 assert fork_repo.repo_name == fork_name
195 193 assert fork_repo.fork.repo_name == repo_name
196 194
197 195 # test if the repository is visible in the list ?
198 196 response = self.app.get(url('summary_home', repo_name=fork_name))
199 197 response.mustcontain(fork_name)
200 198 response.mustcontain(self.REPO_TYPE)
201 199 response.mustcontain('Fork of')
202 200 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
203 201
204 202 def test_zz_fork_permission_page(self):
205 203 usr = self.log_user(self.username, self.password)['user_id']
206 204 repo_name = self.REPO
207 205
208 206 forks = Repository.query()\
209 207 .filter(Repository.repo_type == self.REPO_TYPE)\
210 208 .filter(Repository.fork_id != None).all()
211 209 assert 1 == len(forks)
212 210
213 211 # set read permissions for this
214 212 RepoModel().grant_user_permission(repo=forks[0],
215 213 user=usr,
216 214 perm='repository.read')
217 215 Session().commit()
218 216
219 217 response = self.app.get(url(controller='forks', action='forks',
220 218 repo_name=repo_name))
221 219
222 220 response.mustcontain('fork of vcs test')
223 221
224 222 def test_zzz_fork_permission_page(self):
225 223 usr = self.log_user(self.username, self.password)['user_id']
226 224 repo_name = self.REPO
227 225
228 226 forks = Repository.query()\
229 227 .filter(Repository.repo_type == self.REPO_TYPE)\
230 228 .filter(Repository.fork_id != None).all()
231 229 assert 1 == len(forks)
232 230
233 231 # set none
234 232 RepoModel().grant_user_permission(repo=forks[0],
235 233 user=usr, perm='repository.none')
236 234 Session().commit()
237 235 # fork shouldn't be there
238 236 response = self.app.get(url(controller='forks', action='forks',
239 237 repo_name=repo_name))
240 238 response.mustcontain('There are no forks yet')
241 239
242 240
243 241 class TestGIT(_BaseTest):
244 242 REPO = GIT_REPO
245 243 NEW_REPO = NEW_GIT_REPO
246 244 REPO_TYPE = 'git'
247 245 REPO_FORK = GIT_FORK
248 246
249 247
250 248 class TestHG(_BaseTest):
251 249 REPO = HG_REPO
252 250 NEW_REPO = NEW_HG_REPO
253 251 REPO_TYPE = 'hg'
254 252 REPO_FORK = HG_FORK
255 253
256 254
257 255 @pytest.mark.usefixtures('app', 'autologin_user')
258 256 @pytest.mark.skip_backends('git','hg')
259 257 class TestSVNFork:
260 258
261 259 def test_fork_redirects(self, backend):
262 260 denied_actions = ['fork','fork_create']
263 261 for action in denied_actions:
264 262 response = self.app.get(url(
265 263 controller='forks', action=action,
266 264 repo_name=backend.repo_name))
267 265 assert response.status_int == 302
268 266
269 267 # Not allowed, redirect to the summary
270 268 redirected = response.follow()
271 269 summary_url = url('summary_home', repo_name=backend.repo_name)
272 270
273 271 # URL adds leading slash and path doesn't have it
274 272 assert redirected.req.path == summary_url
General Comments 0
You need to be logged in to leave comments. Login now