##// END OF EJS Templates
repositories: rewrote whole admin section to pyramid....
marcink -
r2014:b776c5e0 default
parent child Browse files
Show More
@@ -0,0 +1,180 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-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 import formencode
23
24 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.view import view_config
26 from pyramid.renderers import render
27 from pyramid.response import Response
28
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
30
31 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.auth import (
33 LoginRequired, CSRFRequired, NotAnonymous,
34 HasPermissionAny, HasRepoGroupPermissionAny)
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.utils import repo_name_slug
37 from rhodecode.lib.utils2 import safe_int, safe_unicode
38 from rhodecode.model.forms import RepoForm
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
41 from rhodecode.model.settings import SettingsModel
42 from rhodecode.model.db import Repository, RepoGroup
43
44 log = logging.getLogger(__name__)
45
46
47 class AdminReposView(BaseAppView, DataGridAppView):
48
49 def load_default_context(self):
50 c = self._get_local_tmpl_context()
51 self._register_global_c(c)
52 return c
53
54 def _load_form_data(self, c):
55 acl_groups = RepoGroupList(RepoGroup.query().all(),
56 perm_set=['group.write', 'group.admin'])
57 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
58 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
59 c.landing_revs_choices, c.landing_revs = \
60 ScmModel().get_repo_landing_revs()
61 c.personal_repo_group = self._rhodecode_user.personal_repo_group
62
63 @LoginRequired()
64 @NotAnonymous()
65 @view_config(
66 route_name='repos', request_method='GET',
67 renderer='rhodecode:templates/admin/repos/repos.mako')
68 def repository_list(self):
69 c = self.load_default_context()
70
71 repo_list = Repository.get_all_repos()
72 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
73 repos_data = RepoModel().get_repos_as_dict(
74 repo_list=c.repo_list, admin=True, super_user_actions=True)
75 # json used to render the grid
76 c.data = json.dumps(repos_data)
77
78 return self._get_template_context(c)
79
80 @LoginRequired()
81 @NotAnonymous()
82 # perms check inside
83 @view_config(
84 route_name='repo_new', request_method='GET',
85 renderer='rhodecode:templates/admin/repos/repo_add.mako')
86 def repository_new(self):
87 c = self.load_default_context()
88
89 new_repo = self.request.GET.get('repo', '')
90 parent_group = safe_int(self.request.GET.get('parent_group'))
91 _gr = RepoGroup.get(parent_group)
92
93 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
94 # you're not super admin nor have global create permissions,
95 # but maybe you have at least write permission to a parent group ?
96
97 gr_name = _gr.group_name if _gr else None
98 # create repositories with write permission on group is set to true
99 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
100 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
101 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
102 if not (group_admin or (group_write and create_on_write)):
103 raise HTTPForbidden()
104
105 self._load_form_data(c)
106 c.new_repo = repo_name_slug(new_repo)
107
108 # apply the defaults from defaults page
109 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
110 # set checkbox to autochecked
111 defaults['repo_copy_permissions'] = True
112
113 parent_group_choice = '-1'
114 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
115 parent_group_choice = self._rhodecode_user.personal_repo_group
116
117 if parent_group and _gr:
118 if parent_group in [x[0] for x in c.repo_groups]:
119 parent_group_choice = safe_unicode(parent_group)
120
121 defaults.update({'repo_group': parent_group_choice})
122
123 data = render('rhodecode:templates/admin/repos/repo_add.mako',
124 self._get_template_context(c), self.request)
125 html = formencode.htmlfill.render(
126 data,
127 defaults=defaults,
128 encoding="UTF-8",
129 force_defaults=False
130 )
131 return Response(html)
132
133 @LoginRequired()
134 @NotAnonymous()
135 @CSRFRequired()
136 # perms check inside
137 @view_config(
138 route_name='repo_create', request_method='POST',
139 renderer='rhodecode:templates/admin/repos/repos.mako')
140 def repository_create(self):
141 c = self.load_default_context()
142
143 form_result = {}
144 task_id = None
145 self._load_form_data(c)
146
147 try:
148 # CanWriteToGroup validators checks permissions of this POST
149 form_result = RepoForm(repo_groups=c.repo_groups_choices,
150 landing_revs=c.landing_revs_choices)()\
151 .to_python(dict(self.request.POST))
152
153 # create is done sometimes async on celery, db transaction
154 # management is handled there.
155 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
156 from celery.result import BaseAsyncResult
157 if isinstance(task, BaseAsyncResult):
158 task_id = task.task_id
159 except formencode.Invalid as errors:
160 data = render('rhodecode:templates/admin/repos/repo_add.mako',
161 self._get_template_context(c), self.request)
162 html = formencode.htmlfill.render(
163 data,
164 defaults=errors.value,
165 errors=errors.error_dict or {},
166 prefix_error=False,
167 encoding="UTF-8",
168 force_defaults=False
169 )
170 return Response(html)
171
172 except Exception as e:
173 msg = self._log_creation_exception(e, form_result.get('repo_name'))
174 h.flash(msg, category='error')
175 raise HTTPFound(h.route_path('home'))
176
177 raise HTTPFound(
178 h.route_path('repo_creating',
179 repo_name=form_result['repo_name_full'],
180 _query=dict(task_id=task_id)))
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
This diff has been collapsed as it changes many lines, (685 lines changed) Show them Hide them
@@ -0,0 +1,685 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 mock
22 import pytest
23
24 from rhodecode.lib import auth
25 from rhodecode.lib.utils2 import str2bool
26 from rhodecode.model.db import (
27 Repository, UserRepoToPerm, User)
28 from rhodecode.model.meta import Session
29 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
30 from rhodecode.model.user import UserModel
31 from rhodecode.tests import (
32 login_user_session, logout_user_session,
33 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
34 from rhodecode.tests.fixture import Fixture
35 from rhodecode.tests.utils import AssertResponse
36
37 fixture = Fixture()
38
39
40 def route_path(name, params=None, **kwargs):
41 import urllib
42
43 base_url = {
44 'repo_summary': '/{repo_name}',
45 'repo_creating_check': '/{repo_name}/repo_creating_check',
46 'edit_repo': '/{repo_name}/settings',
47 'edit_repo_vcs': '/{repo_name}/settings/vcs',
48 'edit_repo_vcs_update': '/{repo_name}/settings/vcs/update',
49 'edit_repo_vcs_svn_pattern_delete': '/{repo_name}/settings/vcs/svn_pattern/delete'
50 }[name].format(**kwargs)
51
52 if params:
53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
54 return base_url
55
56
57 @pytest.mark.usefixtures("app")
58 class TestVcsSettings(object):
59 FORM_DATA = {
60 'inherit_global_settings': False,
61 'hooks_changegroup_repo_size': False,
62 'hooks_changegroup_push_logger': False,
63 'hooks_outgoing_pull_logger': False,
64 'extensions_largefiles': False,
65 'extensions_evolve': False,
66 'phases_publish': 'False',
67 'rhodecode_pr_merge_enabled': False,
68 'rhodecode_use_outdated_comments': False,
69 'new_svn_branch': '',
70 'new_svn_tag': ''
71 }
72
73 @pytest.mark.skip_backends('svn')
74 def test_global_settings_initial_values(self, autologin_user, backend):
75 repo_name = backend.repo_name
76 response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name))
77
78 expected_settings = (
79 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
80 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
81 'hooks_outgoing_pull_logger'
82 )
83 for setting in expected_settings:
84 self.assert_repo_value_equals_global_value(response, setting)
85
86 def test_show_settings_requires_repo_admin_permission(
87 self, backend, user_util, settings_util):
88 repo = backend.create_repo()
89 repo_name = repo.repo_name
90 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
91 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
92 login_user_session(
93 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
94 self.app.get(route_path('edit_repo_vcs', repo_name=repo_name), status=200)
95
96 def test_inherit_global_settings_flag_is_true_by_default(
97 self, autologin_user, backend):
98 repo_name = backend.repo_name
99 response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name))
100
101 assert_response = AssertResponse(response)
102 element = assert_response.get_element('#inherit_global_settings')
103 assert element.checked
104
105 @pytest.mark.parametrize('checked_value', [True, False])
106 def test_inherit_global_settings_value(
107 self, autologin_user, backend, checked_value, settings_util):
108 repo = backend.create_repo()
109 repo_name = repo.repo_name
110 settings_util.create_repo_rhodecode_setting(
111 repo, 'inherit_vcs_settings', checked_value, 'bool')
112 response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name))
113
114 assert_response = AssertResponse(response)
115 element = assert_response.get_element('#inherit_global_settings')
116 assert element.checked == checked_value
117
118 @pytest.mark.skip_backends('svn')
119 def test_hooks_settings_are_created(
120 self, autologin_user, backend, csrf_token):
121 repo_name = backend.repo_name
122 data = self.FORM_DATA.copy()
123 data['csrf_token'] = csrf_token
124 self.app.post(
125 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
126 settings = SettingsModel(repo=repo_name)
127 try:
128 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
129 ui = settings.get_ui_by_section_and_key(section, key)
130 assert ui.ui_active is False
131 finally:
132 self._cleanup_repo_settings(settings)
133
134 def test_hooks_settings_are_not_created_for_svn(
135 self, autologin_user, backend_svn, csrf_token):
136 repo_name = backend_svn.repo_name
137 data = self.FORM_DATA.copy()
138 data['csrf_token'] = csrf_token
139 self.app.post(
140 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
141 settings = SettingsModel(repo=repo_name)
142 try:
143 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
144 ui = settings.get_ui_by_section_and_key(section, key)
145 assert ui is None
146 finally:
147 self._cleanup_repo_settings(settings)
148
149 @pytest.mark.skip_backends('svn')
150 def test_hooks_settings_are_updated(
151 self, autologin_user, backend, csrf_token):
152 repo_name = backend.repo_name
153 settings = SettingsModel(repo=repo_name)
154 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
155 settings.create_ui_section_value(section, '', key=key, active=True)
156
157 data = self.FORM_DATA.copy()
158 data['csrf_token'] = csrf_token
159 self.app.post(
160 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
161 try:
162 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
163 ui = settings.get_ui_by_section_and_key(section, key)
164 assert ui.ui_active is False
165 finally:
166 self._cleanup_repo_settings(settings)
167
168 def test_hooks_settings_are_not_updated_for_svn(
169 self, autologin_user, backend_svn, csrf_token):
170 repo_name = backend_svn.repo_name
171 settings = SettingsModel(repo=repo_name)
172 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
173 settings.create_ui_section_value(section, '', key=key, active=True)
174
175 data = self.FORM_DATA.copy()
176 data['csrf_token'] = csrf_token
177 self.app.post(
178 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
179 try:
180 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
181 ui = settings.get_ui_by_section_and_key(section, key)
182 assert ui.ui_active is True
183 finally:
184 self._cleanup_repo_settings(settings)
185
186 @pytest.mark.skip_backends('svn')
187 def test_pr_settings_are_created(
188 self, autologin_user, backend, csrf_token):
189 repo_name = backend.repo_name
190 data = self.FORM_DATA.copy()
191 data['csrf_token'] = csrf_token
192 self.app.post(
193 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
194 settings = SettingsModel(repo=repo_name)
195 try:
196 for name in VcsSettingsModel.GENERAL_SETTINGS:
197 setting = settings.get_setting_by_name(name)
198 assert setting.app_settings_value is False
199 finally:
200 self._cleanup_repo_settings(settings)
201
202 def test_pr_settings_are_not_created_for_svn(
203 self, autologin_user, backend_svn, csrf_token):
204 repo_name = backend_svn.repo_name
205 data = self.FORM_DATA.copy()
206 data['csrf_token'] = csrf_token
207 self.app.post(
208 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
209 settings = SettingsModel(repo=repo_name)
210 try:
211 for name in VcsSettingsModel.GENERAL_SETTINGS:
212 setting = settings.get_setting_by_name(name)
213 assert setting is None
214 finally:
215 self._cleanup_repo_settings(settings)
216
217 def test_pr_settings_creation_requires_repo_admin_permission(
218 self, backend, user_util, settings_util, csrf_token):
219 repo = backend.create_repo()
220 repo_name = repo.repo_name
221
222 logout_user_session(self.app, csrf_token)
223 session = login_user_session(
224 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
225 new_csrf_token = auth.get_csrf_token(session)
226
227 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
228 repo = Repository.get_by_repo_name(repo_name)
229 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
230 data = self.FORM_DATA.copy()
231 data['csrf_token'] = new_csrf_token
232 settings = SettingsModel(repo=repo_name)
233
234 try:
235 self.app.post(
236 route_path('edit_repo_vcs_update', repo_name=repo_name), data,
237 status=302)
238 finally:
239 self._cleanup_repo_settings(settings)
240
241 @pytest.mark.skip_backends('svn')
242 def test_pr_settings_are_updated(
243 self, autologin_user, backend, csrf_token):
244 repo_name = backend.repo_name
245 settings = SettingsModel(repo=repo_name)
246 for name in VcsSettingsModel.GENERAL_SETTINGS:
247 settings.create_or_update_setting(name, True, 'bool')
248
249 data = self.FORM_DATA.copy()
250 data['csrf_token'] = csrf_token
251 self.app.post(
252 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
253 try:
254 for name in VcsSettingsModel.GENERAL_SETTINGS:
255 setting = settings.get_setting_by_name(name)
256 assert setting.app_settings_value is False
257 finally:
258 self._cleanup_repo_settings(settings)
259
260 def test_pr_settings_are_not_updated_for_svn(
261 self, autologin_user, backend_svn, csrf_token):
262 repo_name = backend_svn.repo_name
263 settings = SettingsModel(repo=repo_name)
264 for name in VcsSettingsModel.GENERAL_SETTINGS:
265 settings.create_or_update_setting(name, True, 'bool')
266
267 data = self.FORM_DATA.copy()
268 data['csrf_token'] = csrf_token
269 self.app.post(
270 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
271 try:
272 for name in VcsSettingsModel.GENERAL_SETTINGS:
273 setting = settings.get_setting_by_name(name)
274 assert setting.app_settings_value is True
275 finally:
276 self._cleanup_repo_settings(settings)
277
278 def test_svn_settings_are_created(
279 self, autologin_user, backend_svn, csrf_token, settings_util):
280 repo_name = backend_svn.repo_name
281 data = self.FORM_DATA.copy()
282 data['new_svn_tag'] = 'svn-tag'
283 data['new_svn_branch'] = 'svn-branch'
284 data['csrf_token'] = csrf_token
285
286 # Create few global settings to make sure that uniqueness validators
287 # are not triggered
288 settings_util.create_rhodecode_ui(
289 VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
290 settings_util.create_rhodecode_ui(
291 VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag')
292
293 self.app.post(
294 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
295 settings = SettingsModel(repo=repo_name)
296 try:
297 svn_branches = settings.get_ui_by_section(
298 VcsSettingsModel.SVN_BRANCH_SECTION)
299 svn_branch_names = [b.ui_value for b in svn_branches]
300 svn_tags = settings.get_ui_by_section(
301 VcsSettingsModel.SVN_TAG_SECTION)
302 svn_tag_names = [b.ui_value for b in svn_tags]
303 assert 'svn-branch' in svn_branch_names
304 assert 'svn-tag' in svn_tag_names
305 finally:
306 self._cleanup_repo_settings(settings)
307
308 def test_svn_settings_are_unique(
309 self, autologin_user, backend_svn, csrf_token, settings_util):
310 repo = backend_svn.repo
311 repo_name = repo.repo_name
312 data = self.FORM_DATA.copy()
313 data['new_svn_tag'] = 'test_tag'
314 data['new_svn_branch'] = 'test_branch'
315 data['csrf_token'] = csrf_token
316 settings_util.create_repo_rhodecode_ui(
317 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch')
318 settings_util.create_repo_rhodecode_ui(
319 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag')
320
321 response = self.app.post(
322 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=200)
323 response.mustcontain('Pattern already exists')
324
325 def test_svn_settings_with_empty_values_are_not_created(
326 self, autologin_user, backend_svn, csrf_token):
327 repo_name = backend_svn.repo_name
328 data = self.FORM_DATA.copy()
329 data['csrf_token'] = csrf_token
330 self.app.post(
331 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
332 settings = SettingsModel(repo=repo_name)
333 try:
334 svn_branches = settings.get_ui_by_section(
335 VcsSettingsModel.SVN_BRANCH_SECTION)
336 svn_tags = settings.get_ui_by_section(
337 VcsSettingsModel.SVN_TAG_SECTION)
338 assert len(svn_branches) == 0
339 assert len(svn_tags) == 0
340 finally:
341 self._cleanup_repo_settings(settings)
342
343 def test_svn_settings_are_shown_for_svn_repository(
344 self, autologin_user, backend_svn, csrf_token):
345 repo_name = backend_svn.repo_name
346 response = self.app.get(
347 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
348 response.mustcontain('Subversion Settings')
349
350 @pytest.mark.skip_backends('svn')
351 def test_svn_settings_are_not_created_for_not_svn_repository(
352 self, autologin_user, backend, csrf_token):
353 repo_name = backend.repo_name
354 data = self.FORM_DATA.copy()
355 data['csrf_token'] = csrf_token
356 self.app.post(
357 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
358 settings = SettingsModel(repo=repo_name)
359 try:
360 svn_branches = settings.get_ui_by_section(
361 VcsSettingsModel.SVN_BRANCH_SECTION)
362 svn_tags = settings.get_ui_by_section(
363 VcsSettingsModel.SVN_TAG_SECTION)
364 assert len(svn_branches) == 0
365 assert len(svn_tags) == 0
366 finally:
367 self._cleanup_repo_settings(settings)
368
369 @pytest.mark.skip_backends('svn')
370 def test_svn_settings_are_shown_only_for_svn_repository(
371 self, autologin_user, backend, csrf_token):
372 repo_name = backend.repo_name
373 response = self.app.get(
374 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
375 response.mustcontain(no='Subversion Settings')
376
377 def test_hg_settings_are_created(
378 self, autologin_user, backend_hg, csrf_token):
379 repo_name = backend_hg.repo_name
380 data = self.FORM_DATA.copy()
381 data['new_svn_tag'] = 'svn-tag'
382 data['new_svn_branch'] = 'svn-branch'
383 data['csrf_token'] = csrf_token
384 self.app.post(
385 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
386 settings = SettingsModel(repo=repo_name)
387 try:
388 largefiles_ui = settings.get_ui_by_section_and_key(
389 'extensions', 'largefiles')
390 assert largefiles_ui.ui_active is False
391 phases_ui = settings.get_ui_by_section_and_key(
392 'phases', 'publish')
393 assert str2bool(phases_ui.ui_value) is False
394 finally:
395 self._cleanup_repo_settings(settings)
396
397 def test_hg_settings_are_updated(
398 self, autologin_user, backend_hg, csrf_token):
399 repo_name = backend_hg.repo_name
400 settings = SettingsModel(repo=repo_name)
401 settings.create_ui_section_value(
402 'extensions', '', key='largefiles', active=True)
403 settings.create_ui_section_value(
404 'phases', '1', key='publish', active=True)
405
406 data = self.FORM_DATA.copy()
407 data['csrf_token'] = csrf_token
408 self.app.post(
409 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
410 try:
411 largefiles_ui = settings.get_ui_by_section_and_key(
412 'extensions', 'largefiles')
413 assert largefiles_ui.ui_active is False
414 phases_ui = settings.get_ui_by_section_and_key(
415 'phases', 'publish')
416 assert str2bool(phases_ui.ui_value) is False
417 finally:
418 self._cleanup_repo_settings(settings)
419
420 def test_hg_settings_are_shown_for_hg_repository(
421 self, autologin_user, backend_hg, csrf_token):
422 repo_name = backend_hg.repo_name
423 response = self.app.get(
424 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
425 response.mustcontain('Mercurial Settings')
426
427 @pytest.mark.skip_backends('hg')
428 def test_hg_settings_are_created_only_for_hg_repository(
429 self, autologin_user, backend, csrf_token):
430 repo_name = backend.repo_name
431 data = self.FORM_DATA.copy()
432 data['csrf_token'] = csrf_token
433 self.app.post(
434 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
435 settings = SettingsModel(repo=repo_name)
436 try:
437 largefiles_ui = settings.get_ui_by_section_and_key(
438 'extensions', 'largefiles')
439 assert largefiles_ui is None
440 phases_ui = settings.get_ui_by_section_and_key(
441 'phases', 'publish')
442 assert phases_ui is None
443 finally:
444 self._cleanup_repo_settings(settings)
445
446 @pytest.mark.skip_backends('hg')
447 def test_hg_settings_are_shown_only_for_hg_repository(
448 self, autologin_user, backend, csrf_token):
449 repo_name = backend.repo_name
450 response = self.app.get(
451 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
452 response.mustcontain(no='Mercurial Settings')
453
454 @pytest.mark.skip_backends('hg')
455 def test_hg_settings_are_updated_only_for_hg_repository(
456 self, autologin_user, backend, csrf_token):
457 repo_name = backend.repo_name
458 settings = SettingsModel(repo=repo_name)
459 settings.create_ui_section_value(
460 'extensions', '', key='largefiles', active=True)
461 settings.create_ui_section_value(
462 'phases', '1', key='publish', active=True)
463
464 data = self.FORM_DATA.copy()
465 data['csrf_token'] = csrf_token
466 self.app.post(
467 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
468 try:
469 largefiles_ui = settings.get_ui_by_section_and_key(
470 'extensions', 'largefiles')
471 assert largefiles_ui.ui_active is True
472 phases_ui = settings.get_ui_by_section_and_key(
473 'phases', 'publish')
474 assert phases_ui.ui_value == '1'
475 finally:
476 self._cleanup_repo_settings(settings)
477
478 def test_per_repo_svn_settings_are_displayed(
479 self, autologin_user, backend_svn, settings_util):
480 repo = backend_svn.create_repo()
481 repo_name = repo.repo_name
482 branches = [
483 settings_util.create_repo_rhodecode_ui(
484 repo, VcsSettingsModel.SVN_BRANCH_SECTION,
485 'branch_{}'.format(i))
486 for i in range(10)]
487 tags = [
488 settings_util.create_repo_rhodecode_ui(
489 repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i))
490 for i in range(10)]
491
492 response = self.app.get(
493 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
494 assert_response = AssertResponse(response)
495 for branch in branches:
496 css_selector = '[name=branch_value_{}]'.format(branch.ui_id)
497 element = assert_response.get_element(css_selector)
498 assert element.value == branch.ui_value
499 for tag in tags:
500 css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id)
501 element = assert_response.get_element(css_selector)
502 assert element.value == tag.ui_value
503
504 def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn(
505 self, autologin_user, backend_svn, settings_util):
506 repo = backend_svn.create_repo()
507 repo_name = repo.repo_name
508 response = self.app.get(
509 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
510 response.mustcontain(no='<label>Hooks:</label>')
511 response.mustcontain(no='<label>Pull Request Settings:</label>')
512
513 def test_inherit_global_settings_value_is_saved(
514 self, autologin_user, backend, csrf_token):
515 repo_name = backend.repo_name
516 data = self.FORM_DATA.copy()
517 data['csrf_token'] = csrf_token
518 data['inherit_global_settings'] = True
519 self.app.post(
520 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
521
522 settings = SettingsModel(repo=repo_name)
523 vcs_settings = VcsSettingsModel(repo=repo_name)
524 try:
525 assert vcs_settings.inherit_global_settings is True
526 finally:
527 self._cleanup_repo_settings(settings)
528
529 def test_repo_cache_is_invalidated_when_settings_are_updated(
530 self, autologin_user, backend, csrf_token):
531 repo_name = backend.repo_name
532 data = self.FORM_DATA.copy()
533 data['csrf_token'] = csrf_token
534 data['inherit_global_settings'] = True
535 settings = SettingsModel(repo=repo_name)
536
537 invalidation_patcher = mock.patch(
538 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
539 with invalidation_patcher as invalidation_mock:
540 self.app.post(
541 route_path('edit_repo_vcs_update', repo_name=repo_name), data,
542 status=302)
543 try:
544 invalidation_mock.assert_called_once_with(repo_name, delete=True)
545 finally:
546 self._cleanup_repo_settings(settings)
547
548 def test_other_settings_not_saved_inherit_global_settings_is_true(
549 self, autologin_user, backend, csrf_token):
550 repo_name = backend.repo_name
551 data = self.FORM_DATA.copy()
552 data['csrf_token'] = csrf_token
553 data['inherit_global_settings'] = True
554 self.app.post(
555 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
556
557 settings = SettingsModel(repo=repo_name)
558 ui_settings = (
559 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
560
561 vcs_settings = []
562 try:
563 for section, key in ui_settings:
564 ui = settings.get_ui_by_section_and_key(section, key)
565 if ui:
566 vcs_settings.append(ui)
567 vcs_settings.extend(settings.get_ui_by_section(
568 VcsSettingsModel.SVN_BRANCH_SECTION))
569 vcs_settings.extend(settings.get_ui_by_section(
570 VcsSettingsModel.SVN_TAG_SECTION))
571 for name in VcsSettingsModel.GENERAL_SETTINGS:
572 setting = settings.get_setting_by_name(name)
573 if setting:
574 vcs_settings.append(setting)
575 assert vcs_settings == []
576 finally:
577 self._cleanup_repo_settings(settings)
578
579 def test_delete_svn_branch_and_tag_patterns(
580 self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header):
581 repo = backend_svn.create_repo()
582 repo_name = repo.repo_name
583 branch = settings_util.create_repo_rhodecode_ui(
584 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
585 cleanup=False)
586 tag = settings_util.create_repo_rhodecode_ui(
587 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False)
588 data = {
589 'csrf_token': csrf_token
590 }
591 for id_ in (branch.ui_id, tag.ui_id):
592 data['delete_svn_pattern'] = id_,
593 self.app.post(
594 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
595 data, extra_environ=xhr_header, status=200)
596 settings = VcsSettingsModel(repo=repo_name)
597 assert settings.get_repo_svn_branch_patterns() == []
598
599 def test_delete_svn_branch_requires_repo_admin_permission(
600 self, backend_svn, user_util, settings_util, csrf_token, xhr_header):
601 repo = backend_svn.create_repo()
602 repo_name = repo.repo_name
603
604 logout_user_session(self.app, csrf_token)
605 session = login_user_session(
606 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
607 csrf_token = auth.get_csrf_token(session)
608
609 repo = Repository.get_by_repo_name(repo_name)
610 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
611 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
612 branch = settings_util.create_repo_rhodecode_ui(
613 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
614 cleanup=False)
615 data = {
616 'csrf_token': csrf_token,
617 'delete_svn_pattern': branch.ui_id
618 }
619 self.app.post(
620 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
621 data, extra_environ=xhr_header, status=200)
622
623 def test_delete_svn_branch_raises_400_when_not_found(
624 self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header):
625 repo_name = backend_svn.repo_name
626 data = {
627 'delete_svn_pattern': 123,
628 'csrf_token': csrf_token
629 }
630 self.app.post(
631 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
632 data, extra_environ=xhr_header, status=400)
633
634 def test_delete_svn_branch_raises_400_when_no_id_specified(
635 self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header):
636 repo_name = backend_svn.repo_name
637 data = {
638 'csrf_token': csrf_token
639 }
640 self.app.post(
641 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
642 data, extra_environ=xhr_header, status=400)
643
644 def _cleanup_repo_settings(self, settings_model):
645 cleanup = []
646 ui_settings = (
647 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
648
649 for section, key in ui_settings:
650 ui = settings_model.get_ui_by_section_and_key(section, key)
651 if ui:
652 cleanup.append(ui)
653
654 cleanup.extend(settings_model.get_ui_by_section(
655 VcsSettingsModel.INHERIT_SETTINGS))
656 cleanup.extend(settings_model.get_ui_by_section(
657 VcsSettingsModel.SVN_BRANCH_SECTION))
658 cleanup.extend(settings_model.get_ui_by_section(
659 VcsSettingsModel.SVN_TAG_SECTION))
660
661 for name in VcsSettingsModel.GENERAL_SETTINGS:
662 setting = settings_model.get_setting_by_name(name)
663 if setting:
664 cleanup.append(setting)
665
666 for object_ in cleanup:
667 Session().delete(object_)
668 Session().commit()
669
670 def assert_repo_value_equals_global_value(self, response, setting):
671 assert_response = AssertResponse(response)
672 global_css_selector = '[name={}_inherited]'.format(setting)
673 repo_css_selector = '[name={}]'.format(setting)
674 repo_element = assert_response.get_element(repo_css_selector)
675 global_element = assert_response.get_element(global_css_selector)
676 assert repo_element.value == global_element.value
677
678
679 def _get_permission_for_user(user, repo):
680 perm = UserRepoToPerm.query()\
681 .filter(UserRepoToPerm.repository ==
682 Repository.get_by_repo_name(repo))\
683 .filter(UserRepoToPerm.user == User.get_by_username(user))\
684 .all()
685 return perm
@@ -0,0 +1,113 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-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 import formencode
24
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.view import view_config
27
28 from rhodecode.apps._base import RepoAppView
29 from rhodecode.lib import audit_logger
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
33 from rhodecode.model.db import RepositoryField
34 from rhodecode.model.forms import RepoFieldForm
35 from rhodecode.model.meta import Session
36 from rhodecode.model.repo import RepoModel
37
38 log = logging.getLogger(__name__)
39
40
41 class RepoSettingsFieldsView(RepoAppView):
42 def load_default_context(self):
43 c = self._get_local_tmpl_context()
44
45 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
46 c.repo_info = self.db_repo
47
48 self._register_global_c(c)
49 return c
50
51 @LoginRequired()
52 @HasRepoPermissionAnyDecorator('repository.admin')
53 @view_config(
54 route_name='edit_repo_fields', request_method='GET',
55 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
56 def repo_field_edit(self):
57 c = self.load_default_context()
58
59 c.active = 'fields'
60 c.repo_fields = RepositoryField.query() \
61 .filter(RepositoryField.repository == self.db_repo).all()
62
63 return self._get_template_context(c)
64
65 @LoginRequired()
66 @HasRepoPermissionAnyDecorator('repository.admin')
67 @CSRFRequired()
68 @view_config(
69 route_name='edit_repo_fields_create', request_method='POST',
70 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
71 def repo_field_create(self):
72 _ = self.request.translate
73
74 try:
75 form_result = RepoFieldForm()().to_python(dict(self.request.POST))
76 RepoModel().add_repo_field(
77 self.db_repo_name,
78 form_result['new_field_key'],
79 field_type=form_result['new_field_type'],
80 field_value=form_result['new_field_value'],
81 field_label=form_result['new_field_label'],
82 field_desc=form_result['new_field_desc'])
83
84 Session().commit()
85 except Exception as e:
86 log.exception("Exception creating field")
87 msg = _('An error occurred during creation of field')
88 if isinstance(e, formencode.Invalid):
89 msg += ". " + e.msg
90 h.flash(msg, category='error')
91
92 raise HTTPFound(
93 h.route_path('edit_repo_fields', repo_name=self.db_repo_name))
94
95 @LoginRequired()
96 @HasRepoPermissionAnyDecorator('repository.admin')
97 @CSRFRequired()
98 @view_config(
99 route_name='edit_repo_fields_delete', request_method='POST',
100 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
101 def repo_field_delete(self):
102 _ = self.request.translate
103 field = RepositoryField.get_or_404(self.request.matchdict['field_id'])
104 try:
105 RepoModel().delete_repo_field(self.db_repo_name, field.field_key)
106 Session().commit()
107 except Exception:
108 log.exception('Exception during removal of field')
109 msg = _('An error occurred during removal of field')
110 h.flash(msg, category='error')
111
112 raise HTTPFound(
113 h.route_path('edit_repo_fields', repo_name=self.db_repo_name))
@@ -0,0 +1,129 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-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.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import audit_logger
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.model.forms import IssueTrackerPatternsForm
32 from rhodecode.model.meta import Session
33 from rhodecode.model.settings import IssueTrackerSettingsModel
34
35 log = logging.getLogger(__name__)
36
37
38 class RepoSettingsIssueTrackersView(RepoAppView):
39 def load_default_context(self):
40 c = self._get_local_tmpl_context()
41
42 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
43 c.repo_info = self.db_repo
44
45 self._register_global_c(c)
46 return c
47
48 @LoginRequired()
49 @HasRepoPermissionAnyDecorator('repository.admin')
50 @view_config(
51 route_name='edit_repo_issuetracker', request_method='GET',
52 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
53 def repo_issuetracker(self):
54 c = self.load_default_context()
55 c.active = 'issuetracker'
56 c.data = 'data'
57
58 c.settings_model = IssueTrackerSettingsModel(repo=self.db_repo)
59 c.global_patterns = c.settings_model.get_global_settings()
60 c.repo_patterns = c.settings_model.get_repo_settings()
61
62 return self._get_template_context(c)
63
64 @LoginRequired()
65 @HasRepoPermissionAnyDecorator('repository.admin')
66 @CSRFRequired()
67 @view_config(
68 route_name='edit_repo_issuetracker_test', request_method='POST',
69 xhr=True, renderer='string')
70 def repo_issuetracker_test(self):
71 return h.urlify_commit_message(
72 self.request.POST.get('test_text', ''),
73 self.db_repo_name)
74
75 @LoginRequired()
76 @HasRepoPermissionAnyDecorator('repository.admin')
77 @CSRFRequired()
78 @view_config(
79 route_name='edit_repo_issuetracker_delete', request_method='POST',
80 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
81 def repo_issuetracker_delete(self):
82 _ = self.request.translate
83 uid = self.request.POST.get('uid')
84 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
85 try:
86 repo_settings.delete_entries(uid)
87 except Exception:
88 h.flash(_('Error occurred during deleting issue tracker entry'),
89 category='error')
90 else:
91 h.flash(_('Removed issue tracker entry'), category='success')
92 raise HTTPFound(
93 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
94
95 def _update_patterns(self, form, repo_settings):
96 for uid in form['delete_patterns']:
97 repo_settings.delete_entries(uid)
98
99 for pattern_data in form['patterns']:
100 for setting_key, pattern, type_ in pattern_data:
101 sett = repo_settings.create_or_update_setting(
102 setting_key, pattern.strip(), type_)
103 Session().add(sett)
104
105 Session().commit()
106
107 @LoginRequired()
108 @HasRepoPermissionAnyDecorator('repository.admin')
109 @CSRFRequired()
110 @view_config(
111 route_name='edit_repo_issuetracker_update', request_method='POST',
112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
113 def repo_issuetracker_update(self):
114 _ = self.request.translate
115 # Save inheritance
116 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
117 inherited = (
118 self.request.POST.get('inherit_global_issuetracker') == "inherited")
119 repo_settings.inherit_global_settings = inherited
120 Session().commit()
121
122 form = IssueTrackerPatternsForm()().to_python(self.request.POST)
123 if form:
124 self._update_patterns(form, repo_settings)
125
126 h.flash(_('Updated issue tracker entries'), category='success')
127 raise HTTPFound(
128 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
129
@@ -0,0 +1,75 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-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.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.auth import (
29 LoginRequired, CSRFRequired, HasRepoPermissionAnyDecorator)
30 from rhodecode.model.scm import ScmModel
31
32 log = logging.getLogger(__name__)
33
34
35 class RepoSettingsRemoteView(RepoAppView):
36 def load_default_context(self):
37 c = self._get_local_tmpl_context()
38
39 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
40 c.repo_info = self.db_repo
41
42 self._register_global_c(c)
43 return c
44
45 @LoginRequired()
46 @HasRepoPermissionAnyDecorator('repository.admin')
47 @view_config(
48 route_name='edit_repo_remote', request_method='GET',
49 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
50 def repo_remote_edit_form(self):
51 c = self.load_default_context()
52 c.active = 'remote'
53
54 return self._get_template_context(c)
55
56 @LoginRequired()
57 @HasRepoPermissionAnyDecorator('repository.admin')
58 @CSRFRequired()
59 @view_config(
60 route_name='edit_repo_remote_pull', request_method='POST',
61 renderer=None)
62 def repo_remote_pull_changes(self):
63 _ = self.request.translate
64 self.load_default_context()
65
66 try:
67 ScmModel().pull_changes(
68 self.db_repo_name, self._rhodecode_user.username)
69 h.flash(_('Pulled from remote location'), category='success')
70 except Exception:
71 log.exception("Exception during pull from remote")
72 h.flash(_('An error occurred during pull from remote location'),
73 category='error')
74 raise HTTPFound(
75 h.route_path('edit_repo_remote', repo_name=self.db_repo_name))
@@ -0,0 +1,172 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-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 import formencode
24 from pyramid.httpexceptions import HTTPFound, HTTPBadRequest
25 from pyramid.response import Response
26 from pyramid.renderers import render
27 from pyramid.view import view_config
28
29 from rhodecode.apps._base import RepoAppView
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.auth import (
33 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
34 from rhodecode.model.forms import RepoVcsSettingsForm
35 from rhodecode.model.meta import Session
36 from rhodecode.model.settings import VcsSettingsModel, SettingNotFound
37
38 log = logging.getLogger(__name__)
39
40
41 class RepoSettingsVcsView(RepoAppView):
42 def load_default_context(self):
43 c = self._get_local_tmpl_context()
44
45 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
46 c.repo_info = self.db_repo
47
48 self._register_global_c(c)
49 return c
50
51 def _vcs_form_defaults(self, repo_name):
52 model = VcsSettingsModel(repo=repo_name)
53 global_defaults = model.get_global_settings()
54
55 repo_defaults = {}
56 repo_defaults.update(global_defaults)
57 repo_defaults.update(model.get_repo_settings())
58
59 global_defaults = {
60 '{}_inherited'.format(k): global_defaults[k]
61 for k in global_defaults}
62
63 defaults = {
64 'inherit_global_settings': model.inherit_global_settings
65 }
66 defaults.update(global_defaults)
67 defaults.update(repo_defaults)
68 defaults.update({
69 'new_svn_branch': '',
70 'new_svn_tag': '',
71 })
72 return defaults
73
74 @LoginRequired()
75 @HasRepoPermissionAnyDecorator('repository.admin')
76 @view_config(
77 route_name='edit_repo_vcs', request_method='GET',
78 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
79 def repo_vcs_settings(self):
80 c = self.load_default_context()
81 model = VcsSettingsModel(repo=self.db_repo_name)
82
83 c.active = 'vcs'
84 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
85 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
86 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
87 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
88
89 defaults = self._vcs_form_defaults(self.db_repo_name)
90 c.inherit_global_settings = defaults['inherit_global_settings']
91
92 data = render('rhodecode:templates/admin/repos/repo_edit.mako',
93 self._get_template_context(c), self.request)
94 html = formencode.htmlfill.render(
95 data,
96 defaults=defaults,
97 encoding="UTF-8",
98 force_defaults=False
99 )
100 return Response(html)
101
102 @LoginRequired()
103 @HasRepoPermissionAnyDecorator('repository.admin')
104 @CSRFRequired()
105 @view_config(
106 route_name='edit_repo_vcs_update', request_method='POST',
107 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
108 def repo_settings_vcs_update(self):
109 _ = self.request.translate
110 c = self.load_default_context()
111 c.active = 'vcs'
112
113 model = VcsSettingsModel(repo=self.db_repo_name)
114 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
115 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
116 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
117 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
118
119 defaults = self._vcs_form_defaults(self.db_repo_name)
120 c.inherit_global_settings = defaults['inherit_global_settings']
121
122 application_form = RepoVcsSettingsForm(self.db_repo_name)()
123 try:
124 form_result = application_form.to_python(dict(self.request.POST))
125 except formencode.Invalid as errors:
126 h.flash(_("Some form inputs contain invalid data."),
127 category='error')
128
129 data = render('rhodecode:templates/admin/repos/repo_edit.mako',
130 self._get_template_context(c), self.request)
131 html = formencode.htmlfill.render(
132 data,
133 defaults=errors.value,
134 errors=errors.error_dict or {},
135 encoding="UTF-8",
136 force_defaults=False
137 )
138 return Response(html)
139
140 try:
141 inherit_global_settings = form_result['inherit_global_settings']
142 model.create_or_update_repo_settings(
143 form_result, inherit_global_settings=inherit_global_settings)
144 Session().commit()
145 h.flash(_('Updated VCS settings'), category='success')
146 except Exception:
147 log.exception("Exception while updating settings")
148 h.flash(
149 _('Error occurred during updating repository VCS settings'),
150 category='error')
151
152 raise HTTPFound(
153 h.route_path('edit_repo_vcs', repo_name=self.db_repo_name))
154
155 @LoginRequired()
156 @HasRepoPermissionAnyDecorator('repository.admin')
157 @CSRFRequired()
158 @view_config(
159 route_name='edit_repo_vcs_svn_pattern_delete', request_method='POST',
160 renderer='json_ext', xhr=True)
161 def repo_settings_delete_svn_pattern(self):
162 self.load_default_context()
163 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
164 model = VcsSettingsModel(repo=self.db_repo_name)
165 try:
166 model.delete_repo_svn_pattern(delete_pattern_id)
167 except SettingNotFound:
168 log.exception('Failed to delete SVN pattern')
169 raise HTTPBadRequest()
170
171 Session().commit()
172 return True
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -1,222 +1,235 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.apps.admin.navigation import NavigationRegistry
22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25
25
26
26
27 def admin_routes(config):
27 def admin_routes(config):
28 """
28 """
29 Admin prefixed routes
29 Admin prefixed routes
30 """
30 """
31
31
32 config.add_route(
32 config.add_route(
33 name='admin_audit_logs',
33 name='admin_audit_logs',
34 pattern='/audit_logs')
34 pattern='/audit_logs')
35
35
36 config.add_route(
36 config.add_route(
37 name='pull_requests_global_0', # backward compat
37 name='pull_requests_global_0', # backward compat
38 pattern='/pull_requests/{pull_request_id:\d+}')
38 pattern='/pull_requests/{pull_request_id:\d+}')
39 config.add_route(
39 config.add_route(
40 name='pull_requests_global_1', # backward compat
40 name='pull_requests_global_1', # backward compat
41 pattern='/pull-requests/{pull_request_id:\d+}')
41 pattern='/pull-requests/{pull_request_id:\d+}')
42 config.add_route(
42 config.add_route(
43 name='pull_requests_global',
43 name='pull_requests_global',
44 pattern='/pull-request/{pull_request_id:\d+}')
44 pattern='/pull-request/{pull_request_id:\d+}')
45
45
46 config.add_route(
46 config.add_route(
47 name='admin_settings_open_source',
47 name='admin_settings_open_source',
48 pattern='/settings/open_source')
48 pattern='/settings/open_source')
49 config.add_route(
49 config.add_route(
50 name='admin_settings_vcs_svn_generate_cfg',
50 name='admin_settings_vcs_svn_generate_cfg',
51 pattern='/settings/vcs/svn_generate_cfg')
51 pattern='/settings/vcs/svn_generate_cfg')
52
52
53 config.add_route(
53 config.add_route(
54 name='admin_settings_system',
54 name='admin_settings_system',
55 pattern='/settings/system')
55 pattern='/settings/system')
56 config.add_route(
56 config.add_route(
57 name='admin_settings_system_update',
57 name='admin_settings_system_update',
58 pattern='/settings/system/updates')
58 pattern='/settings/system/updates')
59
59
60 config.add_route(
60 config.add_route(
61 name='admin_settings_sessions',
61 name='admin_settings_sessions',
62 pattern='/settings/sessions')
62 pattern='/settings/sessions')
63 config.add_route(
63 config.add_route(
64 name='admin_settings_sessions_cleanup',
64 name='admin_settings_sessions_cleanup',
65 pattern='/settings/sessions/cleanup')
65 pattern='/settings/sessions/cleanup')
66
66
67 config.add_route(
67 config.add_route(
68 name='admin_settings_process_management',
68 name='admin_settings_process_management',
69 pattern='/settings/process_management')
69 pattern='/settings/process_management')
70 config.add_route(
70 config.add_route(
71 name='admin_settings_process_management_signal',
71 name='admin_settings_process_management_signal',
72 pattern='/settings/process_management/signal')
72 pattern='/settings/process_management/signal')
73
73
74 # global permissions
74 # global permissions
75
75
76 config.add_route(
76 config.add_route(
77 name='admin_permissions_application',
77 name='admin_permissions_application',
78 pattern='/permissions/application')
78 pattern='/permissions/application')
79 config.add_route(
79 config.add_route(
80 name='admin_permissions_application_update',
80 name='admin_permissions_application_update',
81 pattern='/permissions/application/update')
81 pattern='/permissions/application/update')
82
82
83 config.add_route(
83 config.add_route(
84 name='admin_permissions_global',
84 name='admin_permissions_global',
85 pattern='/permissions/global')
85 pattern='/permissions/global')
86 config.add_route(
86 config.add_route(
87 name='admin_permissions_global_update',
87 name='admin_permissions_global_update',
88 pattern='/permissions/global/update')
88 pattern='/permissions/global/update')
89
89
90 config.add_route(
90 config.add_route(
91 name='admin_permissions_object',
91 name='admin_permissions_object',
92 pattern='/permissions/object')
92 pattern='/permissions/object')
93 config.add_route(
93 config.add_route(
94 name='admin_permissions_object_update',
94 name='admin_permissions_object_update',
95 pattern='/permissions/object/update')
95 pattern='/permissions/object/update')
96
96
97 config.add_route(
97 config.add_route(
98 name='admin_permissions_ips',
98 name='admin_permissions_ips',
99 pattern='/permissions/ips')
99 pattern='/permissions/ips')
100
100
101 config.add_route(
101 config.add_route(
102 name='admin_permissions_overview',
102 name='admin_permissions_overview',
103 pattern='/permissions/overview')
103 pattern='/permissions/overview')
104
104
105 config.add_route(
105 config.add_route(
106 name='admin_permissions_auth_token_access',
106 name='admin_permissions_auth_token_access',
107 pattern='/permissions/auth_token_access')
107 pattern='/permissions/auth_token_access')
108
108
109 # users admin
109 # users admin
110 config.add_route(
110 config.add_route(
111 name='users',
111 name='users',
112 pattern='/users')
112 pattern='/users')
113
113
114 config.add_route(
114 config.add_route(
115 name='users_data',
115 name='users_data',
116 pattern='/users_data')
116 pattern='/users_data')
117
117
118 # user auth tokens
118 # user auth tokens
119 config.add_route(
119 config.add_route(
120 name='edit_user_auth_tokens',
120 name='edit_user_auth_tokens',
121 pattern='/users/{user_id:\d+}/edit/auth_tokens')
121 pattern='/users/{user_id:\d+}/edit/auth_tokens')
122 config.add_route(
122 config.add_route(
123 name='edit_user_auth_tokens_add',
123 name='edit_user_auth_tokens_add',
124 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
124 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
125 config.add_route(
125 config.add_route(
126 name='edit_user_auth_tokens_delete',
126 name='edit_user_auth_tokens_delete',
127 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
127 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
128
128
129 # user ssh keys
129 # user ssh keys
130 config.add_route(
130 config.add_route(
131 name='edit_user_ssh_keys',
131 name='edit_user_ssh_keys',
132 pattern='/users/{user_id:\d+}/edit/ssh_keys')
132 pattern='/users/{user_id:\d+}/edit/ssh_keys')
133 config.add_route(
133 config.add_route(
134 name='edit_user_ssh_keys_generate_keypair',
134 name='edit_user_ssh_keys_generate_keypair',
135 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate')
135 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate')
136 config.add_route(
136 config.add_route(
137 name='edit_user_ssh_keys_add',
137 name='edit_user_ssh_keys_add',
138 pattern='/users/{user_id:\d+}/edit/ssh_keys/new')
138 pattern='/users/{user_id:\d+}/edit/ssh_keys/new')
139 config.add_route(
139 config.add_route(
140 name='edit_user_ssh_keys_delete',
140 name='edit_user_ssh_keys_delete',
141 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete')
141 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete')
142
142
143 # user emails
143 # user emails
144 config.add_route(
144 config.add_route(
145 name='edit_user_emails',
145 name='edit_user_emails',
146 pattern='/users/{user_id:\d+}/edit/emails')
146 pattern='/users/{user_id:\d+}/edit/emails')
147 config.add_route(
147 config.add_route(
148 name='edit_user_emails_add',
148 name='edit_user_emails_add',
149 pattern='/users/{user_id:\d+}/edit/emails/new')
149 pattern='/users/{user_id:\d+}/edit/emails/new')
150 config.add_route(
150 config.add_route(
151 name='edit_user_emails_delete',
151 name='edit_user_emails_delete',
152 pattern='/users/{user_id:\d+}/edit/emails/delete')
152 pattern='/users/{user_id:\d+}/edit/emails/delete')
153
153
154 # user IPs
154 # user IPs
155 config.add_route(
155 config.add_route(
156 name='edit_user_ips',
156 name='edit_user_ips',
157 pattern='/users/{user_id:\d+}/edit/ips')
157 pattern='/users/{user_id:\d+}/edit/ips')
158 config.add_route(
158 config.add_route(
159 name='edit_user_ips_add',
159 name='edit_user_ips_add',
160 pattern='/users/{user_id:\d+}/edit/ips/new')
160 pattern='/users/{user_id:\d+}/edit/ips/new')
161 config.add_route(
161 config.add_route(
162 name='edit_user_ips_delete',
162 name='edit_user_ips_delete',
163 pattern='/users/{user_id:\d+}/edit/ips/delete')
163 pattern='/users/{user_id:\d+}/edit/ips/delete')
164
164
165 # user perms
165 # user perms
166 config.add_route(
166 config.add_route(
167 name='edit_user_perms_summary',
167 name='edit_user_perms_summary',
168 pattern='/users/{user_id:\d+}/edit/permissions_summary')
168 pattern='/users/{user_id:\d+}/edit/permissions_summary')
169 config.add_route(
169 config.add_route(
170 name='edit_user_perms_summary_json',
170 name='edit_user_perms_summary_json',
171 pattern='/users/{user_id:\d+}/edit/permissions_summary/json')
171 pattern='/users/{user_id:\d+}/edit/permissions_summary/json')
172
172
173 # user groups management
173 # user groups management
174 config.add_route(
174 config.add_route(
175 name='edit_user_groups_management',
175 name='edit_user_groups_management',
176 pattern='/users/{user_id:\d+}/edit/groups_management')
176 pattern='/users/{user_id:\d+}/edit/groups_management')
177
177
178 config.add_route(
178 config.add_route(
179 name='edit_user_groups_management_updates',
179 name='edit_user_groups_management_updates',
180 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
180 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
181
181
182 # user audit logs
182 # user audit logs
183 config.add_route(
183 config.add_route(
184 name='edit_user_audit_logs',
184 name='edit_user_audit_logs',
185 pattern='/users/{user_id:\d+}/edit/audit')
185 pattern='/users/{user_id:\d+}/edit/audit')
186
186
187 # user groups admin
187 # user groups admin
188 config.add_route(
188 config.add_route(
189 name='user_groups',
189 name='user_groups',
190 pattern='/user_groups')
190 pattern='/user_groups')
191
191
192 config.add_route(
192 config.add_route(
193 name='user_groups_data',
193 name='user_groups_data',
194 pattern='/user_groups_data')
194 pattern='/user_groups_data')
195
195
196 config.add_route(
196 config.add_route(
197 name='user_group_members_data',
197 name='user_group_members_data',
198 pattern='/user_groups/{user_group_id:\d+}/members')
198 pattern='/user_groups/{user_group_id:\d+}/members')
199
199
200 # user groups perms
200 # user groups perms
201 config.add_route(
201 config.add_route(
202 name='edit_user_group_perms_summary',
202 name='edit_user_group_perms_summary',
203 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary')
203 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary')
204 config.add_route(
204 config.add_route(
205 name='edit_user_group_perms_summary_json',
205 name='edit_user_group_perms_summary_json',
206 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary/json')
206 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary/json')
207
207
208 # repos admin
209 config.add_route(
210 name='repos',
211 pattern='/repos')
212
213 config.add_route(
214 name='repo_new',
215 pattern='/repos/new')
216
217 config.add_route(
218 name='repo_create',
219 pattern='/repos/create')
220
208
221
209 def includeme(config):
222 def includeme(config):
210 settings = config.get_settings()
223 settings = config.get_settings()
211
224
212 # Create admin navigation registry and add it to the pyramid registry.
225 # Create admin navigation registry and add it to the pyramid registry.
213 labs_active = str2bool(settings.get('labs_settings_active', False))
226 labs_active = str2bool(settings.get('labs_settings_active', False))
214 navigation_registry = NavigationRegistry(labs_active=labs_active)
227 navigation_registry = NavigationRegistry(labs_active=labs_active)
215 config.registry.registerUtility(navigation_registry)
228 config.registry.registerUtility(navigation_registry)
216
229
217 # main admin routes
230 # main admin routes
218 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
231 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
219 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
232 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
220
233
221 # Scan module for configuration decorators.
234 # Scan module for configuration decorators.
222 config.scan('.views', ignore='.tests')
235 config.scan('.views', ignore='.tests')
This diff has been collapsed as it changes many lines, (712 lines changed) Show them Hide them
@@ -1,1131 +1,509 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import urllib
21 import urllib
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.apps._base import ADMIN_PREFIX
26 from rhodecode.lib import auth
27 from rhodecode.lib import auth
27 from rhodecode.lib.utils2 import safe_str, str2bool
28 from rhodecode.lib.utils2 import safe_str
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
29 from rhodecode.model.db import (
30 from rhodecode.model.db import (
30 Repository, RepoGroup, UserRepoToPerm, User, Permission)
31 Repository, RepoGroup, UserRepoToPerm, User, Permission)
31 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
32 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
35 from rhodecode.model.user import UserModel
35 from rhodecode.model.user import UserModel
36 from rhodecode.tests import (
36 from rhodecode.tests import (
37 login_user_session, url, assert_session_flash, TEST_USER_ADMIN_LOGIN,
37 login_user_session, assert_session_flash, TEST_USER_ADMIN_LOGIN,
38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, logout_user_session)
38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
39 from rhodecode.tests.fixture import Fixture, error_function
39 from rhodecode.tests.fixture import Fixture, error_function
40 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
40 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
41
41
42 fixture = Fixture()
42 fixture = Fixture()
43
43
44
44
45 def route_path(name, params=None, **kwargs):
45 def route_path(name, params=None, **kwargs):
46 import urllib
46 import urllib
47
47
48 base_url = {
48 base_url = {
49 'repo_summary': '/{repo_name}',
49 'repos': ADMIN_PREFIX + '/repos',
50 'repo_new': ADMIN_PREFIX + '/repos/new',
51 'repo_create': ADMIN_PREFIX + '/repos/create',
52
50 'repo_creating_check': '/{repo_name}/repo_creating_check',
53 'repo_creating_check': '/{repo_name}/repo_creating_check',
51 }[name].format(**kwargs)
54 }[name].format(**kwargs)
52
55
53 if params:
56 if params:
54 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
57 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
55 return base_url
58 return base_url
56
59
57
60
61 def _get_permission_for_user(user, repo):
62 perm = UserRepoToPerm.query()\
63 .filter(UserRepoToPerm.repository ==
64 Repository.get_by_repo_name(repo))\
65 .filter(UserRepoToPerm.user == User.get_by_username(user))\
66 .all()
67 return perm
68
69
58 @pytest.mark.usefixtures("app")
70 @pytest.mark.usefixtures("app")
59 class TestAdminRepos(object):
71 class TestAdminRepos(object):
60
72
61 def test_index(self):
73 def test_repo_list(self, autologin_user, user_util):
62 self.app.get(url('repos'))
74 repo = user_util.create_repo()
75 response = self.app.get(
76 route_path('repos'), status=200)
63
77
64 def test_create_page_restricted(self, autologin_user, backend):
78 response.mustcontain(repo.repo_name)
79
80 def test_create_page_restricted_to_single_backend(self, autologin_user, backend):
65 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
81 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
66 response = self.app.get(url('new_repo'), status=200)
82 response = self.app.get(route_path('repo_new'), status=200)
67 assert_response = AssertResponse(response)
83 assert_response = AssertResponse(response)
68 element = assert_response.get_element('#repo_type')
84 element = assert_response.get_element('#repo_type')
69 assert element.text_content() == '\ngit\n'
85 assert element.text_content() == '\ngit\n'
70
86
71 def test_create_page_non_restricted(self, autologin_user, backend):
87 def test_create_page_non_restricted_backends(self, autologin_user, backend):
72 response = self.app.get(url('new_repo'), status=200)
88 response = self.app.get(route_path('repo_new'), status=200)
73 assert_response = AssertResponse(response)
89 assert_response = AssertResponse(response)
74 assert_response.element_contains('#repo_type', 'git')
90 assert_response.element_contains('#repo_type', 'git')
75 assert_response.element_contains('#repo_type', 'svn')
91 assert_response.element_contains('#repo_type', 'svn')
76 assert_response.element_contains('#repo_type', 'hg')
92 assert_response.element_contains('#repo_type', 'hg')
77
93
78 @pytest.mark.parametrize("suffix",
94 @pytest.mark.parametrize(
79 [u'', u'xxa'], ids=['', 'non-ascii'])
95 "suffix", [u'', u'xxa'], ids=['', 'non-ascii'])
80 def test_create(self, autologin_user, backend, suffix, csrf_token):
96 def test_create(self, autologin_user, backend, suffix, csrf_token):
81 repo_name_unicode = backend.new_repo_name(suffix=suffix)
97 repo_name_unicode = backend.new_repo_name(suffix=suffix)
82 repo_name = repo_name_unicode.encode('utf8')
98 repo_name = repo_name_unicode.encode('utf8')
83 description_unicode = u'description for newly created repo' + suffix
99 description_unicode = u'description for newly created repo' + suffix
84 description = description_unicode.encode('utf8')
100 description = description_unicode.encode('utf8')
85 response = self.app.post(
101 response = self.app.post(
86 url('repos'),
102 route_path('repo_create'),
87 fixture._get_repo_create_params(
103 fixture._get_repo_create_params(
88 repo_private=False,
104 repo_private=False,
89 repo_name=repo_name,
105 repo_name=repo_name,
90 repo_type=backend.alias,
106 repo_type=backend.alias,
91 repo_description=description,
107 repo_description=description,
92 csrf_token=csrf_token),
108 csrf_token=csrf_token),
93 status=302)
109 status=302)
94
110
95 self.assert_repository_is_created_correctly(
111 self.assert_repository_is_created_correctly(
96 repo_name, description, backend)
112 repo_name, description, backend)
97
113
98 def test_create_numeric(self, autologin_user, backend, csrf_token):
114 def test_create_numeric_name(self, autologin_user, backend, csrf_token):
99 numeric_repo = '1234'
115 numeric_repo = '1234'
100 repo_name = numeric_repo
116 repo_name = numeric_repo
101 description = 'description for newly created repo' + numeric_repo
117 description = 'description for newly created repo' + numeric_repo
102 self.app.post(
118 self.app.post(
103 url('repos'),
119 route_path('repo_create'),
104 fixture._get_repo_create_params(
120 fixture._get_repo_create_params(
105 repo_private=False,
121 repo_private=False,
106 repo_name=repo_name,
122 repo_name=repo_name,
107 repo_type=backend.alias,
123 repo_type=backend.alias,
108 repo_description=description,
124 repo_description=description,
109 csrf_token=csrf_token))
125 csrf_token=csrf_token))
110
126
111 self.assert_repository_is_created_correctly(
127 self.assert_repository_is_created_correctly(
112 repo_name, description, backend)
128 repo_name, description, backend)
113
129
114 @pytest.mark.parametrize("suffix", [u'', u'ąćę'], ids=['', 'non-ascii'])
130 @pytest.mark.parametrize("suffix", [u'', u'ąćę'], ids=['', 'non-ascii'])
115 def test_create_in_group(
131 def test_create_in_group(
116 self, autologin_user, backend, suffix, csrf_token):
132 self, autologin_user, backend, suffix, csrf_token):
117 # create GROUP
133 # create GROUP
118 group_name = 'sometest_%s' % backend.alias
134 group_name = 'sometest_%s' % backend.alias
119 gr = RepoGroupModel().create(group_name=group_name,
135 gr = RepoGroupModel().create(group_name=group_name,
120 group_description='test',
136 group_description='test',
121 owner=TEST_USER_ADMIN_LOGIN)
137 owner=TEST_USER_ADMIN_LOGIN)
122 Session().commit()
138 Session().commit()
123
139
124 repo_name = u'ingroup' + suffix
140 repo_name = u'ingroup' + suffix
125 repo_name_full = RepoGroup.url_sep().join(
141 repo_name_full = RepoGroup.url_sep().join(
126 [group_name, repo_name])
142 [group_name, repo_name])
127 description = u'description for newly created repo'
143 description = u'description for newly created repo'
128 self.app.post(
144 self.app.post(
129 url('repos'),
145 route_path('repo_create'),
130 fixture._get_repo_create_params(
146 fixture._get_repo_create_params(
131 repo_private=False,
147 repo_private=False,
132 repo_name=safe_str(repo_name),
148 repo_name=safe_str(repo_name),
133 repo_type=backend.alias,
149 repo_type=backend.alias,
134 repo_description=description,
150 repo_description=description,
135 repo_group=gr.group_id,
151 repo_group=gr.group_id,
136 csrf_token=csrf_token))
152 csrf_token=csrf_token))
137
153
138 # TODO: johbo: Cleanup work to fixture
154 # TODO: johbo: Cleanup work to fixture
139 try:
155 try:
140 self.assert_repository_is_created_correctly(
156 self.assert_repository_is_created_correctly(
141 repo_name_full, description, backend)
157 repo_name_full, description, backend)
142
158
143 new_repo = RepoModel().get_by_repo_name(repo_name_full)
159 new_repo = RepoModel().get_by_repo_name(repo_name_full)
144 inherited_perms = UserRepoToPerm.query().filter(
160 inherited_perms = UserRepoToPerm.query().filter(
145 UserRepoToPerm.repository_id == new_repo.repo_id).all()
161 UserRepoToPerm.repository_id == new_repo.repo_id).all()
146 assert len(inherited_perms) == 1
162 assert len(inherited_perms) == 1
147 finally:
163 finally:
148 RepoModel().delete(repo_name_full)
164 RepoModel().delete(repo_name_full)
149 RepoGroupModel().delete(group_name)
165 RepoGroupModel().delete(group_name)
150 Session().commit()
166 Session().commit()
151
167
152 def test_create_in_group_numeric(
168 def test_create_in_group_numeric_name(
153 self, autologin_user, backend, csrf_token):
169 self, autologin_user, backend, csrf_token):
154 # create GROUP
170 # create GROUP
155 group_name = 'sometest_%s' % backend.alias
171 group_name = 'sometest_%s' % backend.alias
156 gr = RepoGroupModel().create(group_name=group_name,
172 gr = RepoGroupModel().create(group_name=group_name,
157 group_description='test',
173 group_description='test',
158 owner=TEST_USER_ADMIN_LOGIN)
174 owner=TEST_USER_ADMIN_LOGIN)
159 Session().commit()
175 Session().commit()
160
176
161 repo_name = '12345'
177 repo_name = '12345'
162 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
178 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
163 description = 'description for newly created repo'
179 description = 'description for newly created repo'
164 self.app.post(
180 self.app.post(
165 url('repos'),
181 route_path('repo_create'),
166 fixture._get_repo_create_params(
182 fixture._get_repo_create_params(
167 repo_private=False,
183 repo_private=False,
168 repo_name=repo_name,
184 repo_name=repo_name,
169 repo_type=backend.alias,
185 repo_type=backend.alias,
170 repo_description=description,
186 repo_description=description,
171 repo_group=gr.group_id,
187 repo_group=gr.group_id,
172 csrf_token=csrf_token))
188 csrf_token=csrf_token))
173
189
174 # TODO: johbo: Cleanup work to fixture
190 # TODO: johbo: Cleanup work to fixture
175 try:
191 try:
176 self.assert_repository_is_created_correctly(
192 self.assert_repository_is_created_correctly(
177 repo_name_full, description, backend)
193 repo_name_full, description, backend)
178
194
179 new_repo = RepoModel().get_by_repo_name(repo_name_full)
195 new_repo = RepoModel().get_by_repo_name(repo_name_full)
180 inherited_perms = UserRepoToPerm.query()\
196 inherited_perms = UserRepoToPerm.query()\
181 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
197 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
182 assert len(inherited_perms) == 1
198 assert len(inherited_perms) == 1
183 finally:
199 finally:
184 RepoModel().delete(repo_name_full)
200 RepoModel().delete(repo_name_full)
185 RepoGroupModel().delete(group_name)
201 RepoGroupModel().delete(group_name)
186 Session().commit()
202 Session().commit()
187
203
188 def test_create_in_group_without_needed_permissions(self, backend):
204 def test_create_in_group_without_needed_permissions(self, backend):
189 session = login_user_session(
205 session = login_user_session(
190 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
206 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
191 csrf_token = auth.get_csrf_token(session)
207 csrf_token = auth.get_csrf_token(session)
192 # revoke
208 # revoke
193 user_model = UserModel()
209 user_model = UserModel()
194 # disable fork and create on default user
210 # disable fork and create on default user
195 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
211 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
196 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
212 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
197 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
213 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
198 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
214 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
199
215
200 # disable on regular user
216 # disable on regular user
201 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
217 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
202 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
218 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
203 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
219 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
204 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
220 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
205 Session().commit()
221 Session().commit()
206
222
207 # create GROUP
223 # create GROUP
208 group_name = 'reg_sometest_%s' % backend.alias
224 group_name = 'reg_sometest_%s' % backend.alias
209 gr = RepoGroupModel().create(group_name=group_name,
225 gr = RepoGroupModel().create(group_name=group_name,
210 group_description='test',
226 group_description='test',
211 owner=TEST_USER_ADMIN_LOGIN)
227 owner=TEST_USER_ADMIN_LOGIN)
212 Session().commit()
228 Session().commit()
213
229
214 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
230 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
215 gr_allowed = RepoGroupModel().create(
231 gr_allowed = RepoGroupModel().create(
216 group_name=group_name_allowed,
232 group_name=group_name_allowed,
217 group_description='test',
233 group_description='test',
218 owner=TEST_USER_REGULAR_LOGIN)
234 owner=TEST_USER_REGULAR_LOGIN)
219 Session().commit()
235 Session().commit()
220
236
221 repo_name = 'ingroup'
237 repo_name = 'ingroup'
222 description = 'description for newly created repo'
238 description = 'description for newly created repo'
223 response = self.app.post(
239 response = self.app.post(
224 url('repos'),
240 route_path('repo_create'),
225 fixture._get_repo_create_params(
241 fixture._get_repo_create_params(
226 repo_private=False,
242 repo_private=False,
227 repo_name=repo_name,
243 repo_name=repo_name,
228 repo_type=backend.alias,
244 repo_type=backend.alias,
229 repo_description=description,
245 repo_description=description,
230 repo_group=gr.group_id,
246 repo_group=gr.group_id,
231 csrf_token=csrf_token))
247 csrf_token=csrf_token))
232
248
233 response.mustcontain('Invalid value')
249 response.mustcontain('Invalid value')
234
250
235 # user is allowed to create in this group
251 # user is allowed to create in this group
236 repo_name = 'ingroup'
252 repo_name = 'ingroup'
237 repo_name_full = RepoGroup.url_sep().join(
253 repo_name_full = RepoGroup.url_sep().join(
238 [group_name_allowed, repo_name])
254 [group_name_allowed, repo_name])
239 description = 'description for newly created repo'
255 description = 'description for newly created repo'
240 response = self.app.post(
256 response = self.app.post(
241 url('repos'),
257 route_path('repo_create'),
242 fixture._get_repo_create_params(
258 fixture._get_repo_create_params(
243 repo_private=False,
259 repo_private=False,
244 repo_name=repo_name,
260 repo_name=repo_name,
245 repo_type=backend.alias,
261 repo_type=backend.alias,
246 repo_description=description,
262 repo_description=description,
247 repo_group=gr_allowed.group_id,
263 repo_group=gr_allowed.group_id,
248 csrf_token=csrf_token))
264 csrf_token=csrf_token))
249
265
250 # TODO: johbo: Cleanup in pytest fixture
266 # TODO: johbo: Cleanup in pytest fixture
251 try:
267 try:
252 self.assert_repository_is_created_correctly(
268 self.assert_repository_is_created_correctly(
253 repo_name_full, description, backend)
269 repo_name_full, description, backend)
254
270
255 new_repo = RepoModel().get_by_repo_name(repo_name_full)
271 new_repo = RepoModel().get_by_repo_name(repo_name_full)
256 inherited_perms = UserRepoToPerm.query().filter(
272 inherited_perms = UserRepoToPerm.query().filter(
257 UserRepoToPerm.repository_id == new_repo.repo_id).all()
273 UserRepoToPerm.repository_id == new_repo.repo_id).all()
258 assert len(inherited_perms) == 1
274 assert len(inherited_perms) == 1
259
275
260 assert repo_on_filesystem(repo_name_full)
276 assert repo_on_filesystem(repo_name_full)
261 finally:
277 finally:
262 RepoModel().delete(repo_name_full)
278 RepoModel().delete(repo_name_full)
263 RepoGroupModel().delete(group_name)
279 RepoGroupModel().delete(group_name)
264 RepoGroupModel().delete(group_name_allowed)
280 RepoGroupModel().delete(group_name_allowed)
265 Session().commit()
281 Session().commit()
266
282
267 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
283 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
268 csrf_token):
284 csrf_token):
269 # create GROUP
285 # create GROUP
270 group_name = 'sometest_%s' % backend.alias
286 group_name = 'sometest_%s' % backend.alias
271 gr = RepoGroupModel().create(group_name=group_name,
287 gr = RepoGroupModel().create(group_name=group_name,
272 group_description='test',
288 group_description='test',
273 owner=TEST_USER_ADMIN_LOGIN)
289 owner=TEST_USER_ADMIN_LOGIN)
274 perm = Permission.get_by_key('repository.write')
290 perm = Permission.get_by_key('repository.write')
275 RepoGroupModel().grant_user_permission(
291 RepoGroupModel().grant_user_permission(
276 gr, TEST_USER_REGULAR_LOGIN, perm)
292 gr, TEST_USER_REGULAR_LOGIN, perm)
277
293
278 # add repo permissions
294 # add repo permissions
279 Session().commit()
295 Session().commit()
280
296
281 repo_name = 'ingroup_inherited_%s' % backend.alias
297 repo_name = 'ingroup_inherited_%s' % backend.alias
282 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
298 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
283 description = 'description for newly created repo'
299 description = 'description for newly created repo'
284 self.app.post(
300 self.app.post(
285 url('repos'),
301 route_path('repo_create'),
286 fixture._get_repo_create_params(
302 fixture._get_repo_create_params(
287 repo_private=False,
303 repo_private=False,
288 repo_name=repo_name,
304 repo_name=repo_name,
289 repo_type=backend.alias,
305 repo_type=backend.alias,
290 repo_description=description,
306 repo_description=description,
291 repo_group=gr.group_id,
307 repo_group=gr.group_id,
292 repo_copy_permissions=True,
308 repo_copy_permissions=True,
293 csrf_token=csrf_token))
309 csrf_token=csrf_token))
294
310
295 # TODO: johbo: Cleanup to pytest fixture
311 # TODO: johbo: Cleanup to pytest fixture
296 try:
312 try:
297 self.assert_repository_is_created_correctly(
313 self.assert_repository_is_created_correctly(
298 repo_name_full, description, backend)
314 repo_name_full, description, backend)
299 except Exception:
315 except Exception:
300 RepoGroupModel().delete(group_name)
316 RepoGroupModel().delete(group_name)
301 Session().commit()
317 Session().commit()
302 raise
318 raise
303
319
304 # check if inherited permissions are applied
320 # check if inherited permissions are applied
305 new_repo = RepoModel().get_by_repo_name(repo_name_full)
321 new_repo = RepoModel().get_by_repo_name(repo_name_full)
306 inherited_perms = UserRepoToPerm.query().filter(
322 inherited_perms = UserRepoToPerm.query().filter(
307 UserRepoToPerm.repository_id == new_repo.repo_id).all()
323 UserRepoToPerm.repository_id == new_repo.repo_id).all()
308 assert len(inherited_perms) == 2
324 assert len(inherited_perms) == 2
309
325
310 assert TEST_USER_REGULAR_LOGIN in [
326 assert TEST_USER_REGULAR_LOGIN in [
311 x.user.username for x in inherited_perms]
327 x.user.username for x in inherited_perms]
312 assert 'repository.write' in [
328 assert 'repository.write' in [
313 x.permission.permission_name for x in inherited_perms]
329 x.permission.permission_name for x in inherited_perms]
314
330
315 RepoModel().delete(repo_name_full)
331 RepoModel().delete(repo_name_full)
316 RepoGroupModel().delete(group_name)
332 RepoGroupModel().delete(group_name)
317 Session().commit()
333 Session().commit()
318
334
319 @pytest.mark.xfail_backends(
335 @pytest.mark.xfail_backends(
320 "git", "hg", reason="Missing reposerver support")
336 "git", "hg", reason="Missing reposerver support")
321 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
337 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
322 csrf_token):
338 csrf_token):
323 source_repo = backend.create_repo(number_of_commits=2)
339 source_repo = backend.create_repo(number_of_commits=2)
324 source_repo_name = source_repo.repo_name
340 source_repo_name = source_repo.repo_name
325 reposerver.serve(source_repo.scm_instance())
341 reposerver.serve(source_repo.scm_instance())
326
342
327 repo_name = backend.new_repo_name()
343 repo_name = backend.new_repo_name()
328 response = self.app.post(
344 response = self.app.post(
329 url('repos'),
345 route_path('repo_create'),
330 fixture._get_repo_create_params(
346 fixture._get_repo_create_params(
331 repo_private=False,
347 repo_private=False,
332 repo_name=repo_name,
348 repo_name=repo_name,
333 repo_type=backend.alias,
349 repo_type=backend.alias,
334 repo_description='',
350 repo_description='',
335 clone_uri=reposerver.url,
351 clone_uri=reposerver.url,
336 csrf_token=csrf_token),
352 csrf_token=csrf_token),
337 status=302)
353 status=302)
338
354
339 # Should be redirected to the creating page
355 # Should be redirected to the creating page
340 response.mustcontain('repo_creating')
356 response.mustcontain('repo_creating')
341
357
342 # Expecting that both repositories have same history
358 # Expecting that both repositories have same history
343 source_repo = RepoModel().get_by_repo_name(source_repo_name)
359 source_repo = RepoModel().get_by_repo_name(source_repo_name)
344 source_vcs = source_repo.scm_instance()
360 source_vcs = source_repo.scm_instance()
345 repo = RepoModel().get_by_repo_name(repo_name)
361 repo = RepoModel().get_by_repo_name(repo_name)
346 repo_vcs = repo.scm_instance()
362 repo_vcs = repo.scm_instance()
347 assert source_vcs[0].message == repo_vcs[0].message
363 assert source_vcs[0].message == repo_vcs[0].message
348 assert source_vcs.count() == repo_vcs.count()
364 assert source_vcs.count() == repo_vcs.count()
349 assert source_vcs.commit_ids == repo_vcs.commit_ids
365 assert source_vcs.commit_ids == repo_vcs.commit_ids
350
366
351 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
367 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
352 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
368 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
353 csrf_token):
369 csrf_token):
354 repo_name = backend.new_repo_name()
370 repo_name = backend.new_repo_name()
355 description = 'description for newly created repo'
371 description = 'description for newly created repo'
356 response = self.app.post(
372 response = self.app.post(
357 url('repos'),
373 route_path('repo_create'),
358 fixture._get_repo_create_params(
374 fixture._get_repo_create_params(
359 repo_private=False,
375 repo_private=False,
360 repo_name=repo_name,
376 repo_name=repo_name,
361 repo_type=backend.alias,
377 repo_type=backend.alias,
362 repo_description=description,
378 repo_description=description,
363 clone_uri='http://repo.invalid/repo',
379 clone_uri='http://repo.invalid/repo',
364 csrf_token=csrf_token))
380 csrf_token=csrf_token))
365 response.mustcontain('invalid clone url')
381 response.mustcontain('invalid clone url')
366
382
367 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
383 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
368 def test_create_remote_repo_wrong_clone_uri_hg_svn(
384 def test_create_remote_repo_wrong_clone_uri_hg_svn(
369 self, autologin_user, backend, csrf_token):
385 self, autologin_user, backend, csrf_token):
370 repo_name = backend.new_repo_name()
386 repo_name = backend.new_repo_name()
371 description = 'description for newly created repo'
387 description = 'description for newly created repo'
372 response = self.app.post(
388 response = self.app.post(
373 url('repos'),
389 route_path('repo_create'),
374 fixture._get_repo_create_params(
390 fixture._get_repo_create_params(
375 repo_private=False,
391 repo_private=False,
376 repo_name=repo_name,
392 repo_name=repo_name,
377 repo_type=backend.alias,
393 repo_type=backend.alias,
378 repo_description=description,
394 repo_description=description,
379 clone_uri='svn+http://svn.invalid/repo',
395 clone_uri='svn+http://svn.invalid/repo',
380 csrf_token=csrf_token))
396 csrf_token=csrf_token))
381 response.mustcontain('invalid clone url')
397 response.mustcontain('invalid clone url')
382
398
383 def test_create_with_git_suffix(
399 def test_create_with_git_suffix(
384 self, autologin_user, backend, csrf_token):
400 self, autologin_user, backend, csrf_token):
385 repo_name = backend.new_repo_name() + ".git"
401 repo_name = backend.new_repo_name() + ".git"
386 description = 'description for newly created repo'
402 description = 'description for newly created repo'
387 response = self.app.post(
403 response = self.app.post(
388 url('repos'),
404 route_path('repo_create'),
389 fixture._get_repo_create_params(
405 fixture._get_repo_create_params(
390 repo_private=False,
406 repo_private=False,
391 repo_name=repo_name,
407 repo_name=repo_name,
392 repo_type=backend.alias,
408 repo_type=backend.alias,
393 repo_description=description,
409 repo_description=description,
394 csrf_token=csrf_token))
410 csrf_token=csrf_token))
395 response.mustcontain('Repository name cannot end with .git')
411 response.mustcontain('Repository name cannot end with .git')
396
412
397 def test_show(self, autologin_user, backend):
398 self.app.get(url('repo', repo_name=backend.repo_name))
399
400 def test_default_user_cannot_access_private_repo_in_a_group(
413 def test_default_user_cannot_access_private_repo_in_a_group(
401 self, autologin_user, user_util, backend, csrf_token):
414 self, autologin_user, user_util, backend):
402
415
403 group = user_util.create_repo_group()
416 group = user_util.create_repo_group()
404
417
405 repo = backend.create_repo(
418 repo = backend.create_repo(
406 repo_private=True, repo_group=group, repo_copy_permissions=True)
419 repo_private=True, repo_group=group, repo_copy_permissions=True)
407
420
408 permissions = _get_permission_for_user(
421 permissions = _get_permission_for_user(
409 user='default', repo=repo.repo_name)
422 user='default', repo=repo.repo_name)
410 assert len(permissions) == 1
423 assert len(permissions) == 1
411 assert permissions[0].permission.permission_name == 'repository.none'
424 assert permissions[0].permission.permission_name == 'repository.none'
412 assert permissions[0].repository.private is True
425 assert permissions[0].repository.private is True
413
426
414 def test_create_on_top_level_without_permissions(self, backend):
427 def test_create_on_top_level_without_permissions(self, backend):
415 session = login_user_session(
428 session = login_user_session(
416 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
429 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
417 csrf_token = auth.get_csrf_token(session)
430 csrf_token = auth.get_csrf_token(session)
418
431
419 # revoke
432 # revoke
420 user_model = UserModel()
433 user_model = UserModel()
421 # disable fork and create on default user
434 # disable fork and create on default user
422 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
435 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
423 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
436 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
424 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
437 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
425 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
438 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
426
439
427 # disable on regular user
440 # disable on regular user
428 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
441 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
429 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
442 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
430 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
443 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
431 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
444 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
432 Session().commit()
445 Session().commit()
433
446
434 repo_name = backend.new_repo_name()
447 repo_name = backend.new_repo_name()
435 description = 'description for newly created repo'
448 description = 'description for newly created repo'
436 response = self.app.post(
449 response = self.app.post(
437 url('repos'),
450 route_path('repo_create'),
438 fixture._get_repo_create_params(
451 fixture._get_repo_create_params(
439 repo_private=False,
452 repo_private=False,
440 repo_name=repo_name,
453 repo_name=repo_name,
441 repo_type=backend.alias,
454 repo_type=backend.alias,
442 repo_description=description,
455 repo_description=description,
443 csrf_token=csrf_token))
456 csrf_token=csrf_token))
444
457
445 response.mustcontain(
458 response.mustcontain(
446 u"You do not have the permission to store repositories in "
459 u"You do not have the permission to store repositories in "
447 u"the root location.")
460 u"the root location.")
448
461
449 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
462 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
450 def test_create_repo_when_filesystem_op_fails(
463 def test_create_repo_when_filesystem_op_fails(
451 self, autologin_user, backend, csrf_token):
464 self, autologin_user, backend, csrf_token):
452 repo_name = backend.new_repo_name()
465 repo_name = backend.new_repo_name()
453 description = 'description for newly created repo'
466 description = 'description for newly created repo'
454
467
455 response = self.app.post(
468 response = self.app.post(
456 url('repos'),
469 route_path('repo_create'),
457 fixture._get_repo_create_params(
470 fixture._get_repo_create_params(
458 repo_private=False,
471 repo_private=False,
459 repo_name=repo_name,
472 repo_name=repo_name,
460 repo_type=backend.alias,
473 repo_type=backend.alias,
461 repo_description=description,
474 repo_description=description,
462 csrf_token=csrf_token))
475 csrf_token=csrf_token))
463
476
464 assert_session_flash(
477 assert_session_flash(
465 response, 'Error creating repository %s' % repo_name)
478 response, 'Error creating repository %s' % repo_name)
466 # repo must not be in db
479 # repo must not be in db
467 assert backend.repo is None
480 assert backend.repo is None
468 # repo must not be in filesystem !
481 # repo must not be in filesystem !
469 assert not repo_on_filesystem(repo_name)
482 assert not repo_on_filesystem(repo_name)
470
483
471 def assert_repository_is_created_correctly(
484 def assert_repository_is_created_correctly(
472 self, repo_name, description, backend):
485 self, repo_name, description, backend):
473 repo_name_utf8 = safe_str(repo_name)
486 repo_name_utf8 = safe_str(repo_name)
474
487
475 # run the check page that triggers the flash message
488 # run the check page that triggers the flash message
476 response = self.app.get(
489 response = self.app.get(
477 route_path('repo_creating_check', repo_name=safe_str(repo_name)))
490 route_path('repo_creating_check', repo_name=safe_str(repo_name)))
478 assert response.json == {u'result': True}
491 assert response.json == {u'result': True}
479
492
480 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
493 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
481 urllib.quote(repo_name_utf8), repo_name)
494 urllib.quote(repo_name_utf8), repo_name)
482 assert_session_flash(response, flash_msg)
495 assert_session_flash(response, flash_msg)
483
496
484 # test if the repo was created in the database
497 # test if the repo was created in the database
485 new_repo = RepoModel().get_by_repo_name(repo_name)
498 new_repo = RepoModel().get_by_repo_name(repo_name)
486
499
487 assert new_repo.repo_name == repo_name
500 assert new_repo.repo_name == repo_name
488 assert new_repo.description == description
501 assert new_repo.description == description
489
502
490 # test if the repository is visible in the list ?
503 # test if the repository is visible in the list ?
491 response = self.app.get(
504 response = self.app.get(
492 h.route_path('repo_summary', repo_name=safe_str(repo_name)))
505 h.route_path('repo_summary', repo_name=safe_str(repo_name)))
493 response.mustcontain(repo_name)
506 response.mustcontain(repo_name)
494 response.mustcontain(backend.alias)
507 response.mustcontain(backend.alias)
495
508
496 assert repo_on_filesystem(repo_name)
509 assert repo_on_filesystem(repo_name)
497
498
499 @pytest.mark.usefixtures("app")
500 class TestVcsSettings(object):
501 FORM_DATA = {
502 'inherit_global_settings': False,
503 'hooks_changegroup_repo_size': False,
504 'hooks_changegroup_push_logger': False,
505 'hooks_outgoing_pull_logger': False,
506 'extensions_largefiles': False,
507 'extensions_evolve': False,
508 'phases_publish': 'False',
509 'rhodecode_pr_merge_enabled': False,
510 'rhodecode_use_outdated_comments': False,
511 'new_svn_branch': '',
512 'new_svn_tag': ''
513 }
514
515 @pytest.mark.skip_backends('svn')
516 def test_global_settings_initial_values(self, autologin_user, backend):
517 repo_name = backend.repo_name
518 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
519
520 expected_settings = (
521 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
522 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
523 'hooks_outgoing_pull_logger'
524 )
525 for setting in expected_settings:
526 self.assert_repo_value_equals_global_value(response, setting)
527
528 def test_show_settings_requires_repo_admin_permission(
529 self, backend, user_util, settings_util):
530 repo = backend.create_repo()
531 repo_name = repo.repo_name
532 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
533 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
534 login_user_session(
535 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
536 self.app.get(url('repo_vcs_settings', repo_name=repo_name), status=200)
537
538 def test_inherit_global_settings_flag_is_true_by_default(
539 self, autologin_user, backend):
540 repo_name = backend.repo_name
541 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
542
543 assert_response = AssertResponse(response)
544 element = assert_response.get_element('#inherit_global_settings')
545 assert element.checked
546
547 @pytest.mark.parametrize('checked_value', [True, False])
548 def test_inherit_global_settings_value(
549 self, autologin_user, backend, checked_value, settings_util):
550 repo = backend.create_repo()
551 repo_name = repo.repo_name
552 settings_util.create_repo_rhodecode_setting(
553 repo, 'inherit_vcs_settings', checked_value, 'bool')
554 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
555
556 assert_response = AssertResponse(response)
557 element = assert_response.get_element('#inherit_global_settings')
558 assert element.checked == checked_value
559
560 @pytest.mark.skip_backends('svn')
561 def test_hooks_settings_are_created(
562 self, autologin_user, backend, csrf_token):
563 repo_name = backend.repo_name
564 data = self.FORM_DATA.copy()
565 data['csrf_token'] = csrf_token
566 self.app.post(
567 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
568 settings = SettingsModel(repo=repo_name)
569 try:
570 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
571 ui = settings.get_ui_by_section_and_key(section, key)
572 assert ui.ui_active is False
573 finally:
574 self._cleanup_repo_settings(settings)
575
576 def test_hooks_settings_are_not_created_for_svn(
577 self, autologin_user, backend_svn, csrf_token):
578 repo_name = backend_svn.repo_name
579 data = self.FORM_DATA.copy()
580 data['csrf_token'] = csrf_token
581 self.app.post(
582 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
583 settings = SettingsModel(repo=repo_name)
584 try:
585 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
586 ui = settings.get_ui_by_section_and_key(section, key)
587 assert ui is None
588 finally:
589 self._cleanup_repo_settings(settings)
590
591 @pytest.mark.skip_backends('svn')
592 def test_hooks_settings_are_updated(
593 self, autologin_user, backend, csrf_token):
594 repo_name = backend.repo_name
595 settings = SettingsModel(repo=repo_name)
596 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
597 settings.create_ui_section_value(section, '', key=key, active=True)
598
599 data = self.FORM_DATA.copy()
600 data['csrf_token'] = csrf_token
601 self.app.post(
602 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
603 try:
604 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
605 ui = settings.get_ui_by_section_and_key(section, key)
606 assert ui.ui_active is False
607 finally:
608 self._cleanup_repo_settings(settings)
609
610 def test_hooks_settings_are_not_updated_for_svn(
611 self, autologin_user, backend_svn, csrf_token):
612 repo_name = backend_svn.repo_name
613 settings = SettingsModel(repo=repo_name)
614 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
615 settings.create_ui_section_value(section, '', key=key, active=True)
616
617 data = self.FORM_DATA.copy()
618 data['csrf_token'] = csrf_token
619 self.app.post(
620 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
621 try:
622 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
623 ui = settings.get_ui_by_section_and_key(section, key)
624 assert ui.ui_active is True
625 finally:
626 self._cleanup_repo_settings(settings)
627
628 @pytest.mark.skip_backends('svn')
629 def test_pr_settings_are_created(
630 self, autologin_user, backend, csrf_token):
631 repo_name = backend.repo_name
632 data = self.FORM_DATA.copy()
633 data['csrf_token'] = csrf_token
634 self.app.post(
635 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
636 settings = SettingsModel(repo=repo_name)
637 try:
638 for name in VcsSettingsModel.GENERAL_SETTINGS:
639 setting = settings.get_setting_by_name(name)
640 assert setting.app_settings_value is False
641 finally:
642 self._cleanup_repo_settings(settings)
643
644 def test_pr_settings_are_not_created_for_svn(
645 self, autologin_user, backend_svn, csrf_token):
646 repo_name = backend_svn.repo_name
647 data = self.FORM_DATA.copy()
648 data['csrf_token'] = csrf_token
649 self.app.post(
650 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
651 settings = SettingsModel(repo=repo_name)
652 try:
653 for name in VcsSettingsModel.GENERAL_SETTINGS:
654 setting = settings.get_setting_by_name(name)
655 assert setting is None
656 finally:
657 self._cleanup_repo_settings(settings)
658
659 def test_pr_settings_creation_requires_repo_admin_permission(
660 self, backend, user_util, settings_util, csrf_token):
661 repo = backend.create_repo()
662 repo_name = repo.repo_name
663
664 logout_user_session(self.app, csrf_token)
665 session = login_user_session(
666 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
667 new_csrf_token = auth.get_csrf_token(session)
668
669 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
670 repo = Repository.get_by_repo_name(repo_name)
671 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
672 data = self.FORM_DATA.copy()
673 data['csrf_token'] = new_csrf_token
674 settings = SettingsModel(repo=repo_name)
675
676 try:
677 self.app.post(
678 url('repo_vcs_settings', repo_name=repo_name), data,
679 status=302)
680 finally:
681 self._cleanup_repo_settings(settings)
682
683 @pytest.mark.skip_backends('svn')
684 def test_pr_settings_are_updated(
685 self, autologin_user, backend, csrf_token):
686 repo_name = backend.repo_name
687 settings = SettingsModel(repo=repo_name)
688 for name in VcsSettingsModel.GENERAL_SETTINGS:
689 settings.create_or_update_setting(name, True, 'bool')
690
691 data = self.FORM_DATA.copy()
692 data['csrf_token'] = csrf_token
693 self.app.post(
694 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
695 try:
696 for name in VcsSettingsModel.GENERAL_SETTINGS:
697 setting = settings.get_setting_by_name(name)
698 assert setting.app_settings_value is False
699 finally:
700 self._cleanup_repo_settings(settings)
701
702 def test_pr_settings_are_not_updated_for_svn(
703 self, autologin_user, backend_svn, csrf_token):
704 repo_name = backend_svn.repo_name
705 settings = SettingsModel(repo=repo_name)
706 for name in VcsSettingsModel.GENERAL_SETTINGS:
707 settings.create_or_update_setting(name, True, 'bool')
708
709 data = self.FORM_DATA.copy()
710 data['csrf_token'] = csrf_token
711 self.app.post(
712 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
713 try:
714 for name in VcsSettingsModel.GENERAL_SETTINGS:
715 setting = settings.get_setting_by_name(name)
716 assert setting.app_settings_value is True
717 finally:
718 self._cleanup_repo_settings(settings)
719
720 def test_svn_settings_are_created(
721 self, autologin_user, backend_svn, csrf_token, settings_util):
722 repo_name = backend_svn.repo_name
723 data = self.FORM_DATA.copy()
724 data['new_svn_tag'] = 'svn-tag'
725 data['new_svn_branch'] = 'svn-branch'
726 data['csrf_token'] = csrf_token
727
728 # Create few global settings to make sure that uniqueness validators
729 # are not triggered
730 settings_util.create_rhodecode_ui(
731 VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
732 settings_util.create_rhodecode_ui(
733 VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag')
734
735 self.app.post(
736 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
737 settings = SettingsModel(repo=repo_name)
738 try:
739 svn_branches = settings.get_ui_by_section(
740 VcsSettingsModel.SVN_BRANCH_SECTION)
741 svn_branch_names = [b.ui_value for b in svn_branches]
742 svn_tags = settings.get_ui_by_section(
743 VcsSettingsModel.SVN_TAG_SECTION)
744 svn_tag_names = [b.ui_value for b in svn_tags]
745 assert 'svn-branch' in svn_branch_names
746 assert 'svn-tag' in svn_tag_names
747 finally:
748 self._cleanup_repo_settings(settings)
749
750 def test_svn_settings_are_unique(
751 self, autologin_user, backend_svn, csrf_token, settings_util):
752 repo = backend_svn.repo
753 repo_name = repo.repo_name
754 data = self.FORM_DATA.copy()
755 data['new_svn_tag'] = 'test_tag'
756 data['new_svn_branch'] = 'test_branch'
757 data['csrf_token'] = csrf_token
758 settings_util.create_repo_rhodecode_ui(
759 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch')
760 settings_util.create_repo_rhodecode_ui(
761 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag')
762
763 response = self.app.post(
764 url('repo_vcs_settings', repo_name=repo_name), data, status=200)
765 response.mustcontain('Pattern already exists')
766
767 def test_svn_settings_with_empty_values_are_not_created(
768 self, autologin_user, backend_svn, csrf_token):
769 repo_name = backend_svn.repo_name
770 data = self.FORM_DATA.copy()
771 data['csrf_token'] = csrf_token
772 self.app.post(
773 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
774 settings = SettingsModel(repo=repo_name)
775 try:
776 svn_branches = settings.get_ui_by_section(
777 VcsSettingsModel.SVN_BRANCH_SECTION)
778 svn_tags = settings.get_ui_by_section(
779 VcsSettingsModel.SVN_TAG_SECTION)
780 assert len(svn_branches) == 0
781 assert len(svn_tags) == 0
782 finally:
783 self._cleanup_repo_settings(settings)
784
785 def test_svn_settings_are_shown_for_svn_repository(
786 self, autologin_user, backend_svn, csrf_token):
787 repo_name = backend_svn.repo_name
788 response = self.app.get(
789 url('repo_vcs_settings', repo_name=repo_name), status=200)
790 response.mustcontain('Subversion Settings')
791
792 @pytest.mark.skip_backends('svn')
793 def test_svn_settings_are_not_created_for_not_svn_repository(
794 self, autologin_user, backend, csrf_token):
795 repo_name = backend.repo_name
796 data = self.FORM_DATA.copy()
797 data['csrf_token'] = csrf_token
798 self.app.post(
799 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
800 settings = SettingsModel(repo=repo_name)
801 try:
802 svn_branches = settings.get_ui_by_section(
803 VcsSettingsModel.SVN_BRANCH_SECTION)
804 svn_tags = settings.get_ui_by_section(
805 VcsSettingsModel.SVN_TAG_SECTION)
806 assert len(svn_branches) == 0
807 assert len(svn_tags) == 0
808 finally:
809 self._cleanup_repo_settings(settings)
810
811 @pytest.mark.skip_backends('svn')
812 def test_svn_settings_are_shown_only_for_svn_repository(
813 self, autologin_user, backend, csrf_token):
814 repo_name = backend.repo_name
815 response = self.app.get(
816 url('repo_vcs_settings', repo_name=repo_name), status=200)
817 response.mustcontain(no='Subversion Settings')
818
819 def test_hg_settings_are_created(
820 self, autologin_user, backend_hg, csrf_token):
821 repo_name = backend_hg.repo_name
822 data = self.FORM_DATA.copy()
823 data['new_svn_tag'] = 'svn-tag'
824 data['new_svn_branch'] = 'svn-branch'
825 data['csrf_token'] = csrf_token
826 self.app.post(
827 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
828 settings = SettingsModel(repo=repo_name)
829 try:
830 largefiles_ui = settings.get_ui_by_section_and_key(
831 'extensions', 'largefiles')
832 assert largefiles_ui.ui_active is False
833 phases_ui = settings.get_ui_by_section_and_key(
834 'phases', 'publish')
835 assert str2bool(phases_ui.ui_value) is False
836 finally:
837 self._cleanup_repo_settings(settings)
838
839 def test_hg_settings_are_updated(
840 self, autologin_user, backend_hg, csrf_token):
841 repo_name = backend_hg.repo_name
842 settings = SettingsModel(repo=repo_name)
843 settings.create_ui_section_value(
844 'extensions', '', key='largefiles', active=True)
845 settings.create_ui_section_value(
846 'phases', '1', key='publish', active=True)
847
848 data = self.FORM_DATA.copy()
849 data['csrf_token'] = csrf_token
850 self.app.post(
851 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
852 try:
853 largefiles_ui = settings.get_ui_by_section_and_key(
854 'extensions', 'largefiles')
855 assert largefiles_ui.ui_active is False
856 phases_ui = settings.get_ui_by_section_and_key(
857 'phases', 'publish')
858 assert str2bool(phases_ui.ui_value) is False
859 finally:
860 self._cleanup_repo_settings(settings)
861
862 def test_hg_settings_are_shown_for_hg_repository(
863 self, autologin_user, backend_hg, csrf_token):
864 repo_name = backend_hg.repo_name
865 response = self.app.get(
866 url('repo_vcs_settings', repo_name=repo_name), status=200)
867 response.mustcontain('Mercurial Settings')
868
869 @pytest.mark.skip_backends('hg')
870 def test_hg_settings_are_created_only_for_hg_repository(
871 self, autologin_user, backend, csrf_token):
872 repo_name = backend.repo_name
873 data = self.FORM_DATA.copy()
874 data['csrf_token'] = csrf_token
875 self.app.post(
876 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
877 settings = SettingsModel(repo=repo_name)
878 try:
879 largefiles_ui = settings.get_ui_by_section_and_key(
880 'extensions', 'largefiles')
881 assert largefiles_ui is None
882 phases_ui = settings.get_ui_by_section_and_key(
883 'phases', 'publish')
884 assert phases_ui is None
885 finally:
886 self._cleanup_repo_settings(settings)
887
888 @pytest.mark.skip_backends('hg')
889 def test_hg_settings_are_shown_only_for_hg_repository(
890 self, autologin_user, backend, csrf_token):
891 repo_name = backend.repo_name
892 response = self.app.get(
893 url('repo_vcs_settings', repo_name=repo_name), status=200)
894 response.mustcontain(no='Mercurial Settings')
895
896 @pytest.mark.skip_backends('hg')
897 def test_hg_settings_are_updated_only_for_hg_repository(
898 self, autologin_user, backend, csrf_token):
899 repo_name = backend.repo_name
900 settings = SettingsModel(repo=repo_name)
901 settings.create_ui_section_value(
902 'extensions', '', key='largefiles', active=True)
903 settings.create_ui_section_value(
904 'phases', '1', key='publish', active=True)
905
906 data = self.FORM_DATA.copy()
907 data['csrf_token'] = csrf_token
908 self.app.post(
909 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
910 try:
911 largefiles_ui = settings.get_ui_by_section_and_key(
912 'extensions', 'largefiles')
913 assert largefiles_ui.ui_active is True
914 phases_ui = settings.get_ui_by_section_and_key(
915 'phases', 'publish')
916 assert phases_ui.ui_value == '1'
917 finally:
918 self._cleanup_repo_settings(settings)
919
920 def test_per_repo_svn_settings_are_displayed(
921 self, autologin_user, backend_svn, settings_util):
922 repo = backend_svn.create_repo()
923 repo_name = repo.repo_name
924 branches = [
925 settings_util.create_repo_rhodecode_ui(
926 repo, VcsSettingsModel.SVN_BRANCH_SECTION,
927 'branch_{}'.format(i))
928 for i in range(10)]
929 tags = [
930 settings_util.create_repo_rhodecode_ui(
931 repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i))
932 for i in range(10)]
933
934 response = self.app.get(
935 url('repo_vcs_settings', repo_name=repo_name), status=200)
936 assert_response = AssertResponse(response)
937 for branch in branches:
938 css_selector = '[name=branch_value_{}]'.format(branch.ui_id)
939 element = assert_response.get_element(css_selector)
940 assert element.value == branch.ui_value
941 for tag in tags:
942 css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id)
943 element = assert_response.get_element(css_selector)
944 assert element.value == tag.ui_value
945
946 def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn(
947 self, autologin_user, backend_svn, settings_util):
948 repo = backend_svn.create_repo()
949 repo_name = repo.repo_name
950 response = self.app.get(
951 url('repo_vcs_settings', repo_name=repo_name), status=200)
952 response.mustcontain(no='<label>Hooks:</label>')
953 response.mustcontain(no='<label>Pull Request Settings:</label>')
954
955 def test_inherit_global_settings_value_is_saved(
956 self, autologin_user, backend, csrf_token):
957 repo_name = backend.repo_name
958 data = self.FORM_DATA.copy()
959 data['csrf_token'] = csrf_token
960 data['inherit_global_settings'] = True
961 self.app.post(
962 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
963
964 settings = SettingsModel(repo=repo_name)
965 vcs_settings = VcsSettingsModel(repo=repo_name)
966 try:
967 assert vcs_settings.inherit_global_settings is True
968 finally:
969 self._cleanup_repo_settings(settings)
970
971 def test_repo_cache_is_invalidated_when_settings_are_updated(
972 self, autologin_user, backend, csrf_token):
973 repo_name = backend.repo_name
974 data = self.FORM_DATA.copy()
975 data['csrf_token'] = csrf_token
976 data['inherit_global_settings'] = True
977 settings = SettingsModel(repo=repo_name)
978
979 invalidation_patcher = mock.patch(
980 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
981 with invalidation_patcher as invalidation_mock:
982 self.app.post(
983 url('repo_vcs_settings', repo_name=repo_name), data,
984 status=302)
985 try:
986 invalidation_mock.assert_called_once_with(repo_name, delete=True)
987 finally:
988 self._cleanup_repo_settings(settings)
989
990 def test_other_settings_not_saved_inherit_global_settings_is_true(
991 self, autologin_user, backend, csrf_token):
992 repo_name = backend.repo_name
993 data = self.FORM_DATA.copy()
994 data['csrf_token'] = csrf_token
995 data['inherit_global_settings'] = True
996 self.app.post(
997 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
998
999 settings = SettingsModel(repo=repo_name)
1000 ui_settings = (
1001 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1002
1003 vcs_settings = []
1004 try:
1005 for section, key in ui_settings:
1006 ui = settings.get_ui_by_section_and_key(section, key)
1007 if ui:
1008 vcs_settings.append(ui)
1009 vcs_settings.extend(settings.get_ui_by_section(
1010 VcsSettingsModel.SVN_BRANCH_SECTION))
1011 vcs_settings.extend(settings.get_ui_by_section(
1012 VcsSettingsModel.SVN_TAG_SECTION))
1013 for name in VcsSettingsModel.GENERAL_SETTINGS:
1014 setting = settings.get_setting_by_name(name)
1015 if setting:
1016 vcs_settings.append(setting)
1017 assert vcs_settings == []
1018 finally:
1019 self._cleanup_repo_settings(settings)
1020
1021 def test_delete_svn_branch_and_tag_patterns(
1022 self, autologin_user, backend_svn, settings_util, csrf_token):
1023 repo = backend_svn.create_repo()
1024 repo_name = repo.repo_name
1025 branch = settings_util.create_repo_rhodecode_ui(
1026 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1027 cleanup=False)
1028 tag = settings_util.create_repo_rhodecode_ui(
1029 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False)
1030 data = {
1031 '_method': 'delete',
1032 'csrf_token': csrf_token
1033 }
1034 for id_ in (branch.ui_id, tag.ui_id):
1035 data['delete_svn_pattern'] = id_,
1036 self.app.post(
1037 url('repo_vcs_settings', repo_name=repo_name), data,
1038 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1039 settings = VcsSettingsModel(repo=repo_name)
1040 assert settings.get_repo_svn_branch_patterns() == []
1041
1042 def test_delete_svn_branch_requires_repo_admin_permission(
1043 self, backend_svn, user_util, settings_util, csrf_token):
1044 repo = backend_svn.create_repo()
1045 repo_name = repo.repo_name
1046
1047 logout_user_session(self.app, csrf_token)
1048 session = login_user_session(
1049 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
1050 csrf_token = auth.get_csrf_token(session)
1051
1052 repo = Repository.get_by_repo_name(repo_name)
1053 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1054 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
1055 branch = settings_util.create_repo_rhodecode_ui(
1056 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1057 cleanup=False)
1058 data = {
1059 '_method': 'delete',
1060 'csrf_token': csrf_token,
1061 'delete_svn_pattern': branch.ui_id
1062 }
1063 self.app.post(
1064 url('repo_vcs_settings', repo_name=repo_name), data,
1065 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1066
1067 def test_delete_svn_branch_raises_400_when_not_found(
1068 self, autologin_user, backend_svn, settings_util, csrf_token):
1069 repo_name = backend_svn.repo_name
1070 data = {
1071 '_method': 'delete',
1072 'delete_svn_pattern': 123,
1073 'csrf_token': csrf_token
1074 }
1075 self.app.post(
1076 url('repo_vcs_settings', repo_name=repo_name), data,
1077 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1078
1079 def test_delete_svn_branch_raises_400_when_no_id_specified(
1080 self, autologin_user, backend_svn, settings_util, csrf_token):
1081 repo_name = backend_svn.repo_name
1082 data = {
1083 '_method': 'delete',
1084 'csrf_token': csrf_token
1085 }
1086 self.app.post(
1087 url('repo_vcs_settings', repo_name=repo_name), data,
1088 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1089
1090 def _cleanup_repo_settings(self, settings_model):
1091 cleanup = []
1092 ui_settings = (
1093 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1094
1095 for section, key in ui_settings:
1096 ui = settings_model.get_ui_by_section_and_key(section, key)
1097 if ui:
1098 cleanup.append(ui)
1099
1100 cleanup.extend(settings_model.get_ui_by_section(
1101 VcsSettingsModel.INHERIT_SETTINGS))
1102 cleanup.extend(settings_model.get_ui_by_section(
1103 VcsSettingsModel.SVN_BRANCH_SECTION))
1104 cleanup.extend(settings_model.get_ui_by_section(
1105 VcsSettingsModel.SVN_TAG_SECTION))
1106
1107 for name in VcsSettingsModel.GENERAL_SETTINGS:
1108 setting = settings_model.get_setting_by_name(name)
1109 if setting:
1110 cleanup.append(setting)
1111
1112 for object_ in cleanup:
1113 Session().delete(object_)
1114 Session().commit()
1115
1116 def assert_repo_value_equals_global_value(self, response, setting):
1117 assert_response = AssertResponse(response)
1118 global_css_selector = '[name={}_inherited]'.format(setting)
1119 repo_css_selector = '[name={}]'.format(setting)
1120 repo_element = assert_response.get_element(repo_css_selector)
1121 global_element = assert_response.get_element(global_css_selector)
1122 assert repo_element.value == global_element.value
1123
1124
1125 def _get_permission_for_user(user, repo):
1126 perm = UserRepoToPerm.query()\
1127 .filter(UserRepoToPerm.repository ==
1128 Repository.get_by_repo_name(repo))\
1129 .filter(UserRepoToPerm.user == User.get_by_username(user))\
1130 .all()
1131 return perm
@@ -1,54 +1,54 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import collections
21 import collections
22 import logging
22 import logging
23
23
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps.admin.navigation import navigation_list
28 from rhodecode.apps.admin.navigation import navigation_list
29 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
29 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
30 from rhodecode.lib.utils import read_opensource_licenses
30 from rhodecode.lib.utils import read_opensource_licenses
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class OpenSourceLicensesAdminSettingsView(BaseAppView):
35 class OpenSourceLicensesAdminSettingsView(BaseAppView):
36
36
37 def load_default_context(self):
37 def load_default_context(self):
38 c = self._get_local_tmpl_context()
38 c = self._get_local_tmpl_context()
39 self._register_global_c(c)
39 self._register_global_c(c)
40 return c
40 return c
41
41
42 @LoginRequired()
42 @LoginRequired()
43 @HasPermissionAllDecorator('hg.admin')
43 @HasPermissionAllDecorator('hg.admin')
44 @view_config(
44 @view_config(
45 route_name='admin_settings_open_source', request_method='GET',
45 route_name='admin_settings_open_source', request_method='GET',
46 renderer='rhodecode:templates/admin/settings/settings.mako')
46 renderer='rhodecode:templates/admin/settings/settings.mako')
47 def open_source_licenses(self):
47 def open_source_licenses(self):
48 c = self.load_default_context()
48 c = self.load_default_context()
49 c.active = 'open_source'
49 c.active = 'open_source'
50 c.navlist = navigation_list(self.request)
50 c.navlist = navigation_list(self.request)
51 c.opensource_licenses = collections.OrderedDict(
51 items = sorted(read_opensource_licenses().items(), key=lambda t: t[0])
52 sorted(read_opensource_licenses().items(), key=lambda t: t[0]))
52 c.opensource_licenses = collections.OrderedDict(items)
53
53
54 return self._get_template_context(c)
54 return self._get_template_context(c)
@@ -1,258 +1,256 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
23
22
24 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
24 from pyramid.view import view_config
26
25
27 from rhodecode.model.scm import UserGroupList
26 from rhodecode.model.scm import UserGroupList
28
27
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
28 from rhodecode.apps._base import BaseAppView, DataGridAppView
30 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
31 LoginRequired, HasPermissionAllDecorator, CSRFRequired, NotAnonymous,
30 LoginRequired, NotAnonymous,
32 HasUserGroupPermissionAnyDecorator)
31 HasUserGroupPermissionAnyDecorator)
33 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
34 from rhodecode.lib.utils import PartialRenderer
33 from rhodecode.lib.utils import PartialRenderer
35 from rhodecode.lib.utils2 import safe_int, safe_unicode
34 from rhodecode.lib.utils2 import safe_unicode
36 from rhodecode.model.user_group import UserGroupModel
37 from rhodecode.model.db import (
35 from rhodecode.model.db import (
38 joinedload, or_, count, User, UserGroup, UserGroupMember,
36 joinedload, or_, count, User, UserGroup, UserGroupMember,
39 UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
37 UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
40 from rhodecode.model.meta import Session
38 from rhodecode.model.meta import Session
41
39
42 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
43
41
44
42
45 class AdminUserGroupsView(BaseAppView, DataGridAppView):
43 class AdminUserGroupsView(BaseAppView, DataGridAppView):
46
44
47 def load_default_context(self):
45 def load_default_context(self):
48 c = self._get_local_tmpl_context()
46 c = self._get_local_tmpl_context()
49 self._register_global_c(c)
47 self._register_global_c(c)
50 return c
48 return c
51
49
52 # permission check in data loading of
50 # permission check in data loading of
53 # `user_groups_list_data` via UserGroupList
51 # `user_groups_list_data` via UserGroupList
54 @LoginRequired()
52 @LoginRequired()
55 @NotAnonymous()
53 @NotAnonymous()
56 @view_config(
54 @view_config(
57 route_name='user_groups', request_method='GET',
55 route_name='user_groups', request_method='GET',
58 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
56 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
59 def user_groups_list(self):
57 def user_groups_list(self):
60 c = self.load_default_context()
58 c = self.load_default_context()
61 return self._get_template_context(c)
59 return self._get_template_context(c)
62
60
63 # permission check inside
61 # permission check inside
64 @LoginRequired()
62 @LoginRequired()
65 @NotAnonymous()
63 @NotAnonymous()
66 @view_config(
64 @view_config(
67 route_name='user_groups_data', request_method='GET',
65 route_name='user_groups_data', request_method='GET',
68 renderer='json_ext', xhr=True)
66 renderer='json_ext', xhr=True)
69 def user_groups_list_data(self):
67 def user_groups_list_data(self):
70 column_map = {
68 column_map = {
71 'active': 'users_group_active',
69 'active': 'users_group_active',
72 'description': 'user_group_description',
70 'description': 'user_group_description',
73 'members': 'members_total',
71 'members': 'members_total',
74 'owner': 'user_username',
72 'owner': 'user_username',
75 'sync': 'group_data'
73 'sync': 'group_data'
76 }
74 }
77 draw, start, limit = self._extract_chunk(self.request)
75 draw, start, limit = self._extract_chunk(self.request)
78 search_q, order_by, order_dir = self._extract_ordering(
76 search_q, order_by, order_dir = self._extract_ordering(
79 self.request, column_map=column_map)
77 self.request, column_map=column_map)
80
78
81 _render = PartialRenderer('data_table/_dt_elements.mako')
79 _render = PartialRenderer('data_table/_dt_elements.mako')
82
80
83 def user_group_name(user_group_id, user_group_name):
81 def user_group_name(user_group_id, user_group_name):
84 return _render("user_group_name", user_group_id, user_group_name)
82 return _render("user_group_name", user_group_id, user_group_name)
85
83
86 def user_group_actions(user_group_id, user_group_name):
84 def user_group_actions(user_group_id, user_group_name):
87 return _render("user_group_actions", user_group_id, user_group_name)
85 return _render("user_group_actions", user_group_id, user_group_name)
88
86
89 def user_profile(username):
87 def user_profile(username):
90 return _render('user_profile', username)
88 return _render('user_profile', username)
91
89
92 auth_user_group_list = UserGroupList(
90 auth_user_group_list = UserGroupList(
93 UserGroup.query().all(), perm_set=['usergroup.admin'])
91 UserGroup.query().all(), perm_set=['usergroup.admin'])
94
92
95 allowed_ids = []
93 allowed_ids = []
96 for user_group in auth_user_group_list:
94 for user_group in auth_user_group_list:
97 allowed_ids.append(user_group.users_group_id)
95 allowed_ids.append(user_group.users_group_id)
98
96
99 user_groups_data_total_count = UserGroup.query()\
97 user_groups_data_total_count = UserGroup.query()\
100 .filter(UserGroup.users_group_id.in_(allowed_ids))\
98 .filter(UserGroup.users_group_id.in_(allowed_ids))\
101 .count()
99 .count()
102
100
103 member_count = count(UserGroupMember.user_id)
101 member_count = count(UserGroupMember.user_id)
104 base_q = Session.query(
102 base_q = Session.query(
105 UserGroup.users_group_name,
103 UserGroup.users_group_name,
106 UserGroup.user_group_description,
104 UserGroup.user_group_description,
107 UserGroup.users_group_active,
105 UserGroup.users_group_active,
108 UserGroup.users_group_id,
106 UserGroup.users_group_id,
109 UserGroup.group_data,
107 UserGroup.group_data,
110 User,
108 User,
111 member_count.label('member_count')
109 member_count.label('member_count')
112 ) \
110 ) \
113 .filter(UserGroup.users_group_id.in_(allowed_ids)) \
111 .filter(UserGroup.users_group_id.in_(allowed_ids)) \
114 .outerjoin(UserGroupMember) \
112 .outerjoin(UserGroupMember) \
115 .join(User, User.user_id == UserGroup.user_id) \
113 .join(User, User.user_id == UserGroup.user_id) \
116 .group_by(UserGroup, User)
114 .group_by(UserGroup, User)
117
115
118 if search_q:
116 if search_q:
119 like_expression = u'%{}%'.format(safe_unicode(search_q))
117 like_expression = u'%{}%'.format(safe_unicode(search_q))
120 base_q = base_q.filter(or_(
118 base_q = base_q.filter(or_(
121 UserGroup.users_group_name.ilike(like_expression),
119 UserGroup.users_group_name.ilike(like_expression),
122 ))
120 ))
123
121
124 user_groups_data_total_filtered_count = base_q.count()
122 user_groups_data_total_filtered_count = base_q.count()
125
123
126 if order_by == 'members_total':
124 if order_by == 'members_total':
127 sort_col = member_count
125 sort_col = member_count
128 elif order_by == 'user_username':
126 elif order_by == 'user_username':
129 sort_col = User.username
127 sort_col = User.username
130 else:
128 else:
131 sort_col = getattr(UserGroup, order_by, None)
129 sort_col = getattr(UserGroup, order_by, None)
132
130
133 if isinstance(sort_col, count) or sort_col:
131 if isinstance(sort_col, count) or sort_col:
134 if order_dir == 'asc':
132 if order_dir == 'asc':
135 sort_col = sort_col.asc()
133 sort_col = sort_col.asc()
136 else:
134 else:
137 sort_col = sort_col.desc()
135 sort_col = sort_col.desc()
138
136
139 base_q = base_q.order_by(sort_col)
137 base_q = base_q.order_by(sort_col)
140 base_q = base_q.offset(start).limit(limit)
138 base_q = base_q.offset(start).limit(limit)
141
139
142 # authenticated access to user groups
140 # authenticated access to user groups
143 auth_user_group_list = base_q.all()
141 auth_user_group_list = base_q.all()
144
142
145 user_groups_data = []
143 user_groups_data = []
146 for user_gr in auth_user_group_list:
144 for user_gr in auth_user_group_list:
147 user_groups_data.append({
145 user_groups_data.append({
148 "users_group_name": user_group_name(
146 "users_group_name": user_group_name(
149 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
147 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
150 "name_raw": h.escape(user_gr.users_group_name),
148 "name_raw": h.escape(user_gr.users_group_name),
151 "description": h.escape(user_gr.user_group_description),
149 "description": h.escape(user_gr.user_group_description),
152 "members": user_gr.member_count,
150 "members": user_gr.member_count,
153 # NOTE(marcink): because of advanced query we
151 # NOTE(marcink): because of advanced query we
154 # need to load it like that
152 # need to load it like that
155 "sync": UserGroup._load_group_data(
153 "sync": UserGroup._load_group_data(
156 user_gr.group_data).get('extern_type'),
154 user_gr.group_data).get('extern_type'),
157 "active": h.bool2icon(user_gr.users_group_active),
155 "active": h.bool2icon(user_gr.users_group_active),
158 "owner": user_profile(user_gr.User.username),
156 "owner": user_profile(user_gr.User.username),
159 "action": user_group_actions(
157 "action": user_group_actions(
160 user_gr.users_group_id, user_gr.users_group_name)
158 user_gr.users_group_id, user_gr.users_group_name)
161 })
159 })
162
160
163 data = ({
161 data = ({
164 'draw': draw,
162 'draw': draw,
165 'data': user_groups_data,
163 'data': user_groups_data,
166 'recordsTotal': user_groups_data_total_count,
164 'recordsTotal': user_groups_data_total_count,
167 'recordsFiltered': user_groups_data_total_filtered_count,
165 'recordsFiltered': user_groups_data_total_filtered_count,
168 })
166 })
169
167
170 return data
168 return data
171
169
172 @LoginRequired()
170 @LoginRequired()
173 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
171 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
174 @view_config(
172 @view_config(
175 route_name='user_group_members_data', request_method='GET',
173 route_name='user_group_members_data', request_method='GET',
176 renderer='json_ext', xhr=True)
174 renderer='json_ext', xhr=True)
177 def user_group_members(self):
175 def user_group_members(self):
178 """
176 """
179 Return members of given user group
177 Return members of given user group
180 """
178 """
181 user_group_id = self.request.matchdict['user_group_id']
179 user_group_id = self.request.matchdict['user_group_id']
182 user_group = UserGroup.get_or_404(user_group_id)
180 user_group = UserGroup.get_or_404(user_group_id)
183 group_members_obj = sorted((x.user for x in user_group.members),
181 group_members_obj = sorted((x.user for x in user_group.members),
184 key=lambda u: u.username.lower())
182 key=lambda u: u.username.lower())
185
183
186 group_members = [
184 group_members = [
187 {
185 {
188 'id': user.user_id,
186 'id': user.user_id,
189 'first_name': user.first_name,
187 'first_name': user.first_name,
190 'last_name': user.last_name,
188 'last_name': user.last_name,
191 'username': user.username,
189 'username': user.username,
192 'icon_link': h.gravatar_url(user.email, 30),
190 'icon_link': h.gravatar_url(user.email, 30),
193 'value_display': h.person(user.email),
191 'value_display': h.person(user.email),
194 'value': user.username,
192 'value': user.username,
195 'value_type': 'user',
193 'value_type': 'user',
196 'active': user.active,
194 'active': user.active,
197 }
195 }
198 for user in group_members_obj
196 for user in group_members_obj
199 ]
197 ]
200
198
201 return {
199 return {
202 'members': group_members
200 'members': group_members
203 }
201 }
204
202
205 def _get_perms_summary(self, user_group_id):
203 def _get_perms_summary(self, user_group_id):
206 permissions = {
204 permissions = {
207 'repositories': {},
205 'repositories': {},
208 'repositories_groups': {},
206 'repositories_groups': {},
209 }
207 }
210 ugroup_repo_perms = UserGroupRepoToPerm.query()\
208 ugroup_repo_perms = UserGroupRepoToPerm.query()\
211 .options(joinedload(UserGroupRepoToPerm.permission))\
209 .options(joinedload(UserGroupRepoToPerm.permission))\
212 .options(joinedload(UserGroupRepoToPerm.repository))\
210 .options(joinedload(UserGroupRepoToPerm.repository))\
213 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
211 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
214 .all()
212 .all()
215
213
216 for gr in ugroup_repo_perms:
214 for gr in ugroup_repo_perms:
217 permissions['repositories'][gr.repository.repo_name] \
215 permissions['repositories'][gr.repository.repo_name] \
218 = gr.permission.permission_name
216 = gr.permission.permission_name
219
217
220 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
218 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
221 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
219 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
222 .options(joinedload(UserGroupRepoGroupToPerm.group))\
220 .options(joinedload(UserGroupRepoGroupToPerm.group))\
223 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
221 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
224 .all()
222 .all()
225
223
226 for gr in ugroup_group_perms:
224 for gr in ugroup_group_perms:
227 permissions['repositories_groups'][gr.group.group_name] \
225 permissions['repositories_groups'][gr.group.group_name] \
228 = gr.permission.permission_name
226 = gr.permission.permission_name
229 return permissions
227 return permissions
230
228
231 @LoginRequired()
229 @LoginRequired()
232 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
230 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
233 @view_config(
231 @view_config(
234 route_name='edit_user_group_perms_summary', request_method='GET',
232 route_name='edit_user_group_perms_summary', request_method='GET',
235 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
233 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
236 def user_group_perms_summary(self):
234 def user_group_perms_summary(self):
237 c = self.load_default_context()
235 c = self.load_default_context()
238
236
239 user_group_id = self.request.matchdict.get('user_group_id')
237 user_group_id = self.request.matchdict.get('user_group_id')
240 c.user_group = UserGroup.get_or_404(user_group_id)
238 c.user_group = UserGroup.get_or_404(user_group_id)
241
239
242 c.active = 'perms_summary'
240 c.active = 'perms_summary'
243
241
244 c.permissions = self._get_perms_summary(c.user_group.users_group_id)
242 c.permissions = self._get_perms_summary(c.user_group.users_group_id)
245 return self._get_template_context(c)
243 return self._get_template_context(c)
246
244
247 @LoginRequired()
245 @LoginRequired()
248 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
246 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
249 @view_config(
247 @view_config(
250 route_name='edit_user_group_perms_summary_json', request_method='GET',
248 route_name='edit_user_group_perms_summary_json', request_method='GET',
251 renderer='json_ext')
249 renderer='json_ext')
252 def user_group_perms_summary(self):
250 def user_group_perms_summary(self):
253 self.load_default_context()
251 self.load_default_context()
254
252
255 user_group_id = self.request.matchdict.get('user_group_id')
253 user_group_id = self.request.matchdict.get('user_group_id')
256 user_group = UserGroup.get_or_404(user_group_id)
254 user_group = UserGroup.get_or_404(user_group_id)
257
255
258 return self._get_perms_summary(user_group.users_group_id)
256 return self._get_perms_summary(user_group.users_group_id)
@@ -1,387 +1,447 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import add_route_with_slash
20 from rhodecode.apps._base import add_route_with_slash
21
21
22
22
23 def includeme(config):
23 def includeme(config):
24
24
25 # repo creating checks, special cases that aren't repo routes
25 # repo creating checks, special cases that aren't repo routes
26 config.add_route(
26 config.add_route(
27 name='repo_creating',
27 name='repo_creating',
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29
29
30 config.add_route(
30 config.add_route(
31 name='repo_creating_check',
31 name='repo_creating_check',
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33
33
34 # Summary
34 # Summary
35 # NOTE(marcink): one additional route is defined in very bottom, catch
35 # NOTE(marcink): one additional route is defined in very bottom, catch
36 # all pattern
36 # all pattern
37 config.add_route(
37 config.add_route(
38 name='repo_summary_explicit',
38 name='repo_summary_explicit',
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
40 config.add_route(
40 config.add_route(
41 name='repo_summary_commits',
41 name='repo_summary_commits',
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
43
43
44 # Commits
44 # Commits
45 config.add_route(
45 config.add_route(
46 name='repo_commit',
46 name='repo_commit',
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
48
48
49 config.add_route(
49 config.add_route(
50 name='repo_commit_children',
50 name='repo_commit_children',
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
52
52
53 config.add_route(
53 config.add_route(
54 name='repo_commit_parents',
54 name='repo_commit_parents',
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
56
56
57 config.add_route(
57 config.add_route(
58 name='repo_commit_raw',
58 name='repo_commit_raw',
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
60
60
61 config.add_route(
61 config.add_route(
62 name='repo_commit_patch',
62 name='repo_commit_patch',
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
64
64
65 config.add_route(
65 config.add_route(
66 name='repo_commit_download',
66 name='repo_commit_download',
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
68
68
69 config.add_route(
69 config.add_route(
70 name='repo_commit_data',
70 name='repo_commit_data',
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
72
72
73 config.add_route(
73 config.add_route(
74 name='repo_commit_comment_create',
74 name='repo_commit_comment_create',
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
76
76
77 config.add_route(
77 config.add_route(
78 name='repo_commit_comment_preview',
78 name='repo_commit_comment_preview',
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
80
80
81 config.add_route(
81 config.add_route(
82 name='repo_commit_comment_delete',
82 name='repo_commit_comment_delete',
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
83 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True)
84
84
85 # still working url for backward compat.
85 # still working url for backward compat.
86 config.add_route(
86 config.add_route(
87 name='repo_commit_raw_deprecated',
87 name='repo_commit_raw_deprecated',
88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
89
89
90 # Files
90 # Files
91 config.add_route(
91 config.add_route(
92 name='repo_archivefile',
92 name='repo_archivefile',
93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
94
94
95 config.add_route(
95 config.add_route(
96 name='repo_files_diff',
96 name='repo_files_diff',
97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
98 config.add_route( # legacy route to make old links work
98 config.add_route( # legacy route to make old links work
99 name='repo_files_diff_2way_redirect',
99 name='repo_files_diff_2way_redirect',
100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
101
101
102 config.add_route(
102 config.add_route(
103 name='repo_files',
103 name='repo_files',
104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
105 config.add_route(
105 config.add_route(
106 name='repo_files:default_path',
106 name='repo_files:default_path',
107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
108 config.add_route(
108 config.add_route(
109 name='repo_files:default_commit',
109 name='repo_files:default_commit',
110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
111
111
112 config.add_route(
112 config.add_route(
113 name='repo_files:rendered',
113 name='repo_files:rendered',
114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
115
115
116 config.add_route(
116 config.add_route(
117 name='repo_files:annotated',
117 name='repo_files:annotated',
118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
119 config.add_route(
119 config.add_route(
120 name='repo_files:annotated_previous',
120 name='repo_files:annotated_previous',
121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
121 pattern='/{repo_name:.*?[^/]}/annotate-previous/{commit_id}/{f_path:.*}', repo_route=True)
122
122
123 config.add_route(
123 config.add_route(
124 name='repo_nodetree_full',
124 name='repo_nodetree_full',
125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
125 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/{f_path:.*}', repo_route=True)
126 config.add_route(
126 config.add_route(
127 name='repo_nodetree_full:default_path',
127 name='repo_nodetree_full:default_path',
128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
129
129
130 config.add_route(
130 config.add_route(
131 name='repo_files_nodelist',
131 name='repo_files_nodelist',
132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
133
133
134 config.add_route(
134 config.add_route(
135 name='repo_file_raw',
135 name='repo_file_raw',
136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
137
137
138 config.add_route(
138 config.add_route(
139 name='repo_file_download',
139 name='repo_file_download',
140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
141 config.add_route( # backward compat to keep old links working
141 config.add_route( # backward compat to keep old links working
142 name='repo_file_download:legacy',
142 name='repo_file_download:legacy',
143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
144 repo_route=True)
144 repo_route=True)
145
145
146 config.add_route(
146 config.add_route(
147 name='repo_file_history',
147 name='repo_file_history',
148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
149
149
150 config.add_route(
150 config.add_route(
151 name='repo_file_authors',
151 name='repo_file_authors',
152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
153
153
154 config.add_route(
154 config.add_route(
155 name='repo_files_remove_file',
155 name='repo_files_remove_file',
156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
157 repo_route=True)
157 repo_route=True)
158 config.add_route(
158 config.add_route(
159 name='repo_files_delete_file',
159 name='repo_files_delete_file',
160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
161 repo_route=True)
161 repo_route=True)
162 config.add_route(
162 config.add_route(
163 name='repo_files_edit_file',
163 name='repo_files_edit_file',
164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
165 repo_route=True)
165 repo_route=True)
166 config.add_route(
166 config.add_route(
167 name='repo_files_update_file',
167 name='repo_files_update_file',
168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
169 repo_route=True)
169 repo_route=True)
170 config.add_route(
170 config.add_route(
171 name='repo_files_add_file',
171 name='repo_files_add_file',
172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
173 repo_route=True)
173 repo_route=True)
174 config.add_route(
174 config.add_route(
175 name='repo_files_create_file',
175 name='repo_files_create_file',
176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
177 repo_route=True)
177 repo_route=True)
178
178
179 # Refs data
179 # Refs data
180 config.add_route(
180 config.add_route(
181 name='repo_refs_data',
181 name='repo_refs_data',
182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
183
183
184 config.add_route(
184 config.add_route(
185 name='repo_refs_changelog_data',
185 name='repo_refs_changelog_data',
186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
187
187
188 config.add_route(
188 config.add_route(
189 name='repo_stats',
189 name='repo_stats',
190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
191
191
192 # Changelog
192 # Changelog
193 config.add_route(
193 config.add_route(
194 name='repo_changelog',
194 name='repo_changelog',
195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
196 config.add_route(
196 config.add_route(
197 name='repo_changelog_file',
197 name='repo_changelog_file',
198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
199 config.add_route(
199 config.add_route(
200 name='repo_changelog_elements',
200 name='repo_changelog_elements',
201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
202
202
203 # Compare
203 # Compare
204 config.add_route(
204 config.add_route(
205 name='repo_compare_select',
205 name='repo_compare_select',
206 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
206 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
207
207
208 config.add_route(
208 config.add_route(
209 name='repo_compare',
209 name='repo_compare',
210 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
210 pattern='/{repo_name:.*?[^/]}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}', repo_route=True)
211
211
212 # Tags
212 # Tags
213 config.add_route(
213 config.add_route(
214 name='tags_home',
214 name='tags_home',
215 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
215 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
216
216
217 # Branches
217 # Branches
218 config.add_route(
218 config.add_route(
219 name='branches_home',
219 name='branches_home',
220 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
220 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
221
221
222 # Bookmarks
222 # Bookmarks
223 config.add_route(
223 config.add_route(
224 name='bookmarks_home',
224 name='bookmarks_home',
225 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
225 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
226
226
227 # Forks
227 # Forks
228 config.add_route(
228 config.add_route(
229 name='repo_fork_new',
229 name='repo_fork_new',
230 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
230 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
231 repo_accepted_types=['hg', 'git'])
231 repo_accepted_types=['hg', 'git'])
232
232
233 config.add_route(
233 config.add_route(
234 name='repo_fork_create',
234 name='repo_fork_create',
235 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
235 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
236 repo_accepted_types=['hg', 'git'])
236 repo_accepted_types=['hg', 'git'])
237
237
238 config.add_route(
238 config.add_route(
239 name='repo_forks_show_all',
239 name='repo_forks_show_all',
240 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
240 pattern='/{repo_name:.*?[^/]}/forks', repo_route=True,
241 repo_accepted_types=['hg', 'git'])
241 repo_accepted_types=['hg', 'git'])
242 config.add_route(
242 config.add_route(
243 name='repo_forks_data',
243 name='repo_forks_data',
244 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
244 pattern='/{repo_name:.*?[^/]}/forks/data', repo_route=True,
245 repo_accepted_types=['hg', 'git'])
245 repo_accepted_types=['hg', 'git'])
246
246
247 # Pull Requests
247 # Pull Requests
248 config.add_route(
248 config.add_route(
249 name='pullrequest_show',
249 name='pullrequest_show',
250 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
250 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
251 repo_route=True)
251 repo_route=True)
252
252
253 config.add_route(
253 config.add_route(
254 name='pullrequest_show_all',
254 name='pullrequest_show_all',
255 pattern='/{repo_name:.*?[^/]}/pull-request',
255 pattern='/{repo_name:.*?[^/]}/pull-request',
256 repo_route=True, repo_accepted_types=['hg', 'git'])
256 repo_route=True, repo_accepted_types=['hg', 'git'])
257
257
258 config.add_route(
258 config.add_route(
259 name='pullrequest_show_all_data',
259 name='pullrequest_show_all_data',
260 pattern='/{repo_name:.*?[^/]}/pull-request-data',
260 pattern='/{repo_name:.*?[^/]}/pull-request-data',
261 repo_route=True, repo_accepted_types=['hg', 'git'])
261 repo_route=True, repo_accepted_types=['hg', 'git'])
262
262
263 config.add_route(
263 config.add_route(
264 name='pullrequest_repo_refs',
264 name='pullrequest_repo_refs',
265 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
265 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
266 repo_route=True)
266 repo_route=True)
267
267
268 config.add_route(
268 config.add_route(
269 name='pullrequest_repo_destinations',
269 name='pullrequest_repo_destinations',
270 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
270 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
271 repo_route=True)
271 repo_route=True)
272
272
273 config.add_route(
273 config.add_route(
274 name='pullrequest_new',
274 name='pullrequest_new',
275 pattern='/{repo_name:.*?[^/]}/pull-request/new',
275 pattern='/{repo_name:.*?[^/]}/pull-request/new',
276 repo_route=True, repo_accepted_types=['hg', 'git'])
276 repo_route=True, repo_accepted_types=['hg', 'git'])
277
277
278 config.add_route(
278 config.add_route(
279 name='pullrequest_create',
279 name='pullrequest_create',
280 pattern='/{repo_name:.*?[^/]}/pull-request/create',
280 pattern='/{repo_name:.*?[^/]}/pull-request/create',
281 repo_route=True, repo_accepted_types=['hg', 'git'])
281 repo_route=True, repo_accepted_types=['hg', 'git'])
282
282
283 config.add_route(
283 config.add_route(
284 name='pullrequest_update',
284 name='pullrequest_update',
285 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
285 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
286 repo_route=True)
286 repo_route=True)
287
287
288 config.add_route(
288 config.add_route(
289 name='pullrequest_merge',
289 name='pullrequest_merge',
290 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
290 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
291 repo_route=True)
291 repo_route=True)
292
292
293 config.add_route(
293 config.add_route(
294 name='pullrequest_delete',
294 name='pullrequest_delete',
295 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
295 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
296 repo_route=True)
296 repo_route=True)
297
297
298 config.add_route(
298 config.add_route(
299 name='pullrequest_comment_create',
299 name='pullrequest_comment_create',
300 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
300 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
301 repo_route=True)
301 repo_route=True)
302
302
303 config.add_route(
303 config.add_route(
304 name='pullrequest_comment_delete',
304 name='pullrequest_comment_delete',
305 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
305 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
306 repo_route=True, repo_accepted_types=['hg', 'git'])
306 repo_route=True, repo_accepted_types=['hg', 'git'])
307
307
308 # Settings
308 # Settings
309 config.add_route(
309 config.add_route(
310 name='edit_repo',
310 name='edit_repo',
311 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
311 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
312
312
313 # Settings advanced
313 # Settings advanced
314 config.add_route(
314 config.add_route(
315 name='edit_repo_advanced',
315 name='edit_repo_advanced',
316 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
316 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
317 config.add_route(
317 config.add_route(
318 name='edit_repo_advanced_delete',
318 name='edit_repo_advanced_delete',
319 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
319 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
320 config.add_route(
320 config.add_route(
321 name='edit_repo_advanced_locking',
321 name='edit_repo_advanced_locking',
322 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
322 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
323 config.add_route(
323 config.add_route(
324 name='edit_repo_advanced_journal',
324 name='edit_repo_advanced_journal',
325 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
325 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
326 config.add_route(
326 config.add_route(
327 name='edit_repo_advanced_fork',
327 name='edit_repo_advanced_fork',
328 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
328 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
329
329
330 # Caches
330 # Caches
331 config.add_route(
331 config.add_route(
332 name='edit_repo_caches',
332 name='edit_repo_caches',
333 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
333 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
334
334
335 # Permissions
335 # Permissions
336 config.add_route(
336 config.add_route(
337 name='edit_repo_perms',
337 name='edit_repo_perms',
338 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
338 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
339
339
340 # Repo Review Rules
340 # Maintenance
341 config.add_route(
342 name='edit_repo_maintenance',
343 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
344
345 config.add_route(
346 name='edit_repo_maintenance_execute',
347 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
348
349 # Fields
350 config.add_route(
351 name='edit_repo_fields',
352 pattern='/{repo_name:.*?[^/]}/settings/fields', repo_route=True)
353 config.add_route(
354 name='edit_repo_fields_create',
355 pattern='/{repo_name:.*?[^/]}/settings/fields/create', repo_route=True)
356 config.add_route(
357 name='edit_repo_fields_delete',
358 pattern='/{repo_name:.*?[^/]}/settings/fields/{field_id}/delete', repo_route=True)
359
360 # Locking
361 config.add_route(
362 name='repo_edit_toggle_locking',
363 pattern='/{repo_name:.*?[^/]}/settings/toggle_locking', repo_route=True)
364
365 # Remote
366 config.add_route(
367 name='edit_repo_remote',
368 pattern='/{repo_name:.*?[^/]}/settings/remote', repo_route=True)
369 config.add_route(
370 name='edit_repo_remote_pull',
371 pattern='/{repo_name:.*?[^/]}/settings/remote/pull', repo_route=True)
372
373
374 # Statistics
375 config.add_route(
376 name='edit_repo_statistics',
377 pattern='/{repo_name:.*?[^/]}/settings/statistics', repo_route=True)
378 config.add_route(
379 name='edit_repo_statistics_reset',
380 pattern='/{repo_name:.*?[^/]}/settings/statistics/update', repo_route=True)
381
382 # Issue trackers
383 config.add_route(
384 name='edit_repo_issuetracker',
385 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers', repo_route=True)
386 config.add_route(
387 name='edit_repo_issuetracker_test',
388 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/test', repo_route=True)
389 config.add_route(
390 name='edit_repo_issuetracker_delete',
391 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/delete', repo_route=True)
392 config.add_route(
393 name='edit_repo_issuetracker_update',
394 pattern='/{repo_name:.*?[^/]}/settings/issue_trackers/update', repo_route=True)
395
396 # VCS Settings
397 config.add_route(
398 name='edit_repo_vcs',
399 pattern='/{repo_name:.*?[^/]}/settings/vcs', repo_route=True)
400 config.add_route(
401 name='edit_repo_vcs_update',
402 pattern='/{repo_name:.*?[^/]}/settings/vcs/update', repo_route=True)
403
404 # svn pattern
405 config.add_route(
406 name='edit_repo_vcs_svn_pattern_delete',
407 pattern='/{repo_name:.*?[^/]}/settings/vcs/svn_pattern/delete', repo_route=True)
408
409 # Repo Review Rules (EE feature)
341 config.add_route(
410 config.add_route(
342 name='repo_reviewers',
411 name='repo_reviewers',
343 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
412 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
344
413
345 config.add_route(
414 config.add_route(
346 name='repo_default_reviewers_data',
415 name='repo_default_reviewers_data',
347 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
416 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
348
417
349 # Maintenance
350 config.add_route(
351 name='repo_maintenance',
352 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
353
354 config.add_route(
355 name='repo_maintenance_execute',
356 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
357
358 # Strip
418 # Strip
359 config.add_route(
419 config.add_route(
360 name='strip',
420 name='edit_repo_strip',
361 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
421 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
362
422
363 config.add_route(
423 config.add_route(
364 name='strip_check',
424 name='strip_check',
365 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
425 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
366
426
367 config.add_route(
427 config.add_route(
368 name='strip_execute',
428 name='strip_execute',
369 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
429 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
370
430
371 # ATOM/RSS Feed
431 # ATOM/RSS Feed
372 config.add_route(
432 config.add_route(
373 name='rss_feed_home',
433 name='rss_feed_home',
374 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
434 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
375
435
376 config.add_route(
436 config.add_route(
377 name='atom_feed_home',
437 name='atom_feed_home',
378 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
438 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
379
439
380 # NOTE(marcink): needs to be at the end for catch-all
440 # NOTE(marcink): needs to be at the end for catch-all
381 add_route_with_slash(
441 add_route_with_slash(
382 config,
442 config,
383 name='repo_summary',
443 name='repo_summary',
384 pattern='/{repo_name:.*?[^/]}', repo_route=True)
444 pattern='/{repo_name:.*?[^/]}', repo_route=True)
385
445
386 # Scan module for configuration decorators.
446 # Scan module for configuration decorators.
387 config.scan('.views', ignore='.tests')
447 config.scan('.views', ignore='.tests')
@@ -1,122 +1,148 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.utils2 import md5
23 from rhodecode.lib.utils2 import md5
24 from rhodecode.model.db import Repository
24 from rhodecode.model.db import Repository
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
26 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
27 from rhodecode.tests import url
27
28
29 def route_path(name, params=None, **kwargs):
30 import urllib
31
32 base_url = {
33 'repo_summary': '/{repo_name}',
34 'edit_repo_issuetracker': '/{repo_name}/settings/issue_trackers',
35 'edit_repo_issuetracker_test': '/{repo_name}/settings/issue_trackers/test',
36 'edit_repo_issuetracker_delete': '/{repo_name}/settings/issue_trackers/delete',
37 'edit_repo_issuetracker_update': '/{repo_name}/settings/issue_trackers/update',
38 }[name].format(**kwargs)
39
40 if params:
41 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
42 return base_url
28
43
29
44
30 @pytest.mark.usefixtures("app")
45 @pytest.mark.usefixtures("app")
31 class TestAdminRepos:
46 class TestRepoIssueTracker(object):
32 def test_issuetracker_index(self, autologin_user, backend):
47 def test_issuetracker_index(self, autologin_user, backend):
33 repo = backend.create_repo()
48 repo = backend.create_repo()
34 response = self.app.get(url('repo_settings_issuetracker',
49 response = self.app.get(route_path('edit_repo_issuetracker',
35 repo_name=repo.repo_name))
50 repo_name=repo.repo_name))
36 assert response.status_code == 200
51 assert response.status_code == 200
37
52
38 def test_add_issuetracker_patterns(
53 def test_add_and_test_issuetracker_patterns(
39 self, autologin_user, backend, csrf_token, request):
54 self, autologin_user, backend, csrf_token, request, xhr_header):
40 pattern = 'issuetracker_pat'
55 pattern = 'issuetracker_pat'
41 another_pattern = pattern+'1'
56 another_pattern = pattern+'1'
42 post_url = url('repo_issuetracker_save',
57 post_url = route_path(
43 repo_name=backend.repo.repo_name)
58 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name)
44 post_data = {
59 post_data = {
45 'new_pattern_pattern_0': pattern,
60 'new_pattern_pattern_0': pattern,
46 'new_pattern_url_0': 'url',
61 'new_pattern_url_0': 'url',
47 'new_pattern_prefix_0': 'prefix',
62 'new_pattern_prefix_0': 'prefix',
48 'new_pattern_description_0': 'description',
63 'new_pattern_description_0': 'description',
49 'new_pattern_pattern_1': another_pattern,
64 'new_pattern_pattern_1': another_pattern,
50 'new_pattern_url_1': 'url1',
65 'new_pattern_url_1': 'url1',
51 'new_pattern_prefix_1': 'prefix1',
66 'new_pattern_prefix_1': 'prefix1',
52 'new_pattern_description_1': 'description1',
67 'new_pattern_description_1': 'description1',
53 'csrf_token': csrf_token
68 'csrf_token': csrf_token
54 }
69 }
55 self.app.post(post_url, post_data, status=302)
70 self.app.post(post_url, post_data, status=302)
56 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
71 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
57 settings = self.settings_model.get_repo_settings()
72 settings = self.settings_model.get_repo_settings()
58 self.uid = md5(pattern)
73 self.uid = md5(pattern)
59 assert settings[self.uid]['pat'] == pattern
74 assert settings[self.uid]['pat'] == pattern
60 self.another_uid = md5(another_pattern)
75 self.another_uid = md5(another_pattern)
61 assert settings[self.another_uid]['pat'] == another_pattern
76 assert settings[self.another_uid]['pat'] == another_pattern
62
77
78 # test pattern
79 data = {'test_text': 'example of issuetracker_pat replacement',
80 'csrf_token': csrf_token}
81 response = self.app.post(
82 route_path('edit_repo_issuetracker_test',
83 repo_name=backend.repo.repo_name),
84 extra_environ=xhr_header, params=data)
85
86 assert response.body == \
87 'example of <a class="issue-tracker-link" href="url">prefix</a> replacement'
88
63 @request.addfinalizer
89 @request.addfinalizer
64 def cleanup():
90 def cleanup():
65 self.settings_model.delete_entries(self.uid)
91 self.settings_model.delete_entries(self.uid)
66 self.settings_model.delete_entries(self.another_uid)
92 self.settings_model.delete_entries(self.another_uid)
67
93
68 def test_edit_issuetracker_pattern(
94 def test_edit_issuetracker_pattern(
69 self, autologin_user, backend, csrf_token, request):
95 self, autologin_user, backend, csrf_token, request):
70 entry_key = 'issuetracker_pat_'
96 entry_key = 'issuetracker_pat_'
71 pattern = 'issuetracker_pat2'
97 pattern = 'issuetracker_pat2'
72 old_pattern = 'issuetracker_pat'
98 old_pattern = 'issuetracker_pat'
73 old_uid = md5(old_pattern)
99 old_uid = md5(old_pattern)
74
100
75 sett = SettingsModel(repo=backend.repo).create_or_update_setting(
101 sett = SettingsModel(repo=backend.repo).create_or_update_setting(
76 entry_key+old_uid, old_pattern, 'unicode')
102 entry_key+old_uid, old_pattern, 'unicode')
77 Session().add(sett)
103 Session().add(sett)
78 Session().commit()
104 Session().commit()
79 post_url = url('repo_issuetracker_save',
105 post_url = route_path(
80 repo_name=backend.repo.repo_name)
106 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name)
81 post_data = {
107 post_data = {
82 'new_pattern_pattern_0': pattern,
108 'new_pattern_pattern_0': pattern,
83 'new_pattern_url_0': 'url',
109 'new_pattern_url_0': 'url',
84 'new_pattern_prefix_0': 'prefix',
110 'new_pattern_prefix_0': 'prefix',
85 'new_pattern_description_0': 'description',
111 'new_pattern_description_0': 'description',
86 'uid': old_uid,
112 'uid': old_uid,
87 'csrf_token': csrf_token
113 'csrf_token': csrf_token
88 }
114 }
89 self.app.post(post_url, post_data, status=302)
115 self.app.post(post_url, post_data, status=302)
90 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
116 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
91 settings = self.settings_model.get_repo_settings()
117 settings = self.settings_model.get_repo_settings()
92 self.uid = md5(pattern)
118 self.uid = md5(pattern)
93 assert settings[self.uid]['pat'] == pattern
119 assert settings[self.uid]['pat'] == pattern
94 with pytest.raises(KeyError):
120 with pytest.raises(KeyError):
95 settings[old_uid]
121 key = settings[old_uid]
96
122
97 @request.addfinalizer
123 @request.addfinalizer
98 def cleanup():
124 def cleanup():
99 self.settings_model.delete_entries(self.uid)
125 self.settings_model.delete_entries(self.uid)
100
126
101 def test_delete_issuetracker_pattern(
127 def test_delete_issuetracker_pattern(
102 self, autologin_user, backend, csrf_token, settings_util):
128 self, autologin_user, backend, csrf_token, settings_util):
103 repo = backend.create_repo()
129 repo = backend.create_repo()
104 repo_name = repo.repo_name
130 repo_name = repo.repo_name
105 entry_key = 'issuetracker_pat_'
131 entry_key = 'issuetracker_pat_'
106 pattern = 'issuetracker_pat3'
132 pattern = 'issuetracker_pat3'
107 uid = md5(pattern)
133 uid = md5(pattern)
108 settings_util.create_repo_rhodecode_setting(
134 settings_util.create_repo_rhodecode_setting(
109 repo=backend.repo, name=entry_key+uid,
135 repo=backend.repo, name=entry_key+uid,
110 value=entry_key, type_='unicode', cleanup=False)
136 value=entry_key, type_='unicode', cleanup=False)
111
137
112 self.app.post(
138 self.app.post(
113 url('repo_issuetracker_delete',
139 route_path(
140 'edit_repo_issuetracker_delete',
114 repo_name=backend.repo.repo_name),
141 repo_name=backend.repo.repo_name),
115 {
142 {
116 '_method': 'delete',
117 'uid': uid,
143 'uid': uid,
118 'csrf_token': csrf_token
144 'csrf_token': csrf_token
119 }, status=302)
145 }, status=302)
120 settings = IssueTrackerSettingsModel(
146 settings = IssueTrackerSettingsModel(
121 repo=Repository.get_by_repo_name(repo_name)).get_repo_settings()
147 repo=Repository.get_by_repo_name(repo_name)).get_repo_settings()
122 assert 'rhodecode_%s%s' % (entry_key, uid) not in settings
148 assert 'rhodecode_%s%s' % (entry_key, uid) not in settings
@@ -1,233 +1,232 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User
26 from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.tests import (
28 from rhodecode.tests import (
29 url, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN,
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, assert_session_flash)
30 assert_session_flash)
31 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
32
31
33 fixture = Fixture()
32 fixture = Fixture()
34
33
35
34
36 def route_path(name, params=None, **kwargs):
35 def route_path(name, params=None, **kwargs):
37 import urllib
36 import urllib
38
37
39 base_url = {
38 base_url = {
40 'edit_repo': '/{repo_name}/settings',
39 'edit_repo': '/{repo_name}/settings',
41 'edit_repo_advanced': '/{repo_name}/settings/advanced',
40 'edit_repo_advanced': '/{repo_name}/settings/advanced',
42 'edit_repo_caches': '/{repo_name}/settings/caches',
41 'edit_repo_caches': '/{repo_name}/settings/caches',
43 'edit_repo_perms': '/{repo_name}/settings/permissions',
42 'edit_repo_perms': '/{repo_name}/settings/permissions',
43 'edit_repo_vcs': '/{repo_name}/settings/vcs',
44 'edit_repo_issuetracker': '/{repo_name}/settings/issue_trackers',
45 'edit_repo_fields': '/{repo_name}/settings/fields',
46 'edit_repo_remote': '/{repo_name}/settings/remote',
47 'edit_repo_statistics': '/{repo_name}/settings/statistics',
44 }[name].format(**kwargs)
48 }[name].format(**kwargs)
45
49
46 if params:
50 if params:
47 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
51 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
48 return base_url
52 return base_url
49
53
50
54
51 def _get_permission_for_user(user, repo):
55 def _get_permission_for_user(user, repo):
52 perm = UserRepoToPerm.query()\
56 perm = UserRepoToPerm.query()\
53 .filter(UserRepoToPerm.repository ==
57 .filter(UserRepoToPerm.repository ==
54 Repository.get_by_repo_name(repo))\
58 Repository.get_by_repo_name(repo))\
55 .filter(UserRepoToPerm.user == User.get_by_username(user))\
59 .filter(UserRepoToPerm.user == User.get_by_username(user))\
56 .all()
60 .all()
57 return perm
61 return perm
58
62
59
63
60 @pytest.mark.usefixtures('autologin_user', 'app')
64 @pytest.mark.usefixtures('autologin_user', 'app')
61 class TestAdminRepoSettings(object):
65 class TestAdminRepoSettings(object):
62 @pytest.mark.parametrize('urlname', [
66 @pytest.mark.parametrize('urlname', [
63 'edit_repo',
67 'edit_repo',
64 'edit_repo_caches',
68 'edit_repo_caches',
65 'edit_repo_perms',
69 'edit_repo_perms',
66 'edit_repo_advanced',
70 'edit_repo_advanced',
71 'edit_repo_vcs',
72 'edit_repo_issuetracker',
73 'edit_repo_fields',
74 'edit_repo_remote',
75 'edit_repo_statistics',
67 ])
76 ])
68 def test_show_page(self, urlname, app, backend):
77 def test_show_page(self, urlname, app, backend):
69 app.get(route_path(urlname, repo_name=backend.repo_name), status=200)
78 app.get(route_path(urlname, repo_name=backend.repo_name), status=200)
70
79
71 def test_edit_accessible_when_missing_requirements(
80 def test_edit_accessible_when_missing_requirements(
72 self, backend_hg, autologin_user):
81 self, backend_hg, autologin_user):
73 scm_patcher = mock.patch.object(
82 scm_patcher = mock.patch.object(
74 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
83 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
75 with scm_patcher:
84 with scm_patcher:
76 self.app.get(route_path('edit_repo', repo_name=backend_hg.repo_name))
85 self.app.get(route_path('edit_repo', repo_name=backend_hg.repo_name))
77
86
78 @pytest.mark.parametrize('urlname', [
79 'repo_vcs_settings',
80 'repo_settings_issuetracker',
81 'edit_repo_fields',
82 'edit_repo_remote',
83 'edit_repo_statistics',
84 ])
85 def test_show_page_pylons(self, urlname, app):
86 app.get(url(urlname, repo_name=HG_REPO))
87
88 @pytest.mark.parametrize('update_settings', [
87 @pytest.mark.parametrize('update_settings', [
89 {'repo_description': 'alter-desc'},
88 {'repo_description': 'alter-desc'},
90 {'repo_owner': TEST_USER_REGULAR_LOGIN},
89 {'repo_owner': TEST_USER_REGULAR_LOGIN},
91 {'repo_private': 'true'},
90 {'repo_private': 'true'},
92 {'repo_enable_locking': 'true'},
91 {'repo_enable_locking': 'true'},
93 {'repo_enable_downloads': 'true'},
92 {'repo_enable_downloads': 'true'},
94 ])
93 ])
95 def test_update_repo_settings(self, update_settings, csrf_token, backend, user_util):
94 def test_update_repo_settings(self, update_settings, csrf_token, backend, user_util):
96 repo = user_util.create_repo(repo_type=backend.alias)
95 repo = user_util.create_repo(repo_type=backend.alias)
97 repo_name = repo.repo_name
96 repo_name = repo.repo_name
98
97
99 params = fixture._get_repo_create_params(
98 params = fixture._get_repo_create_params(
100 csrf_token=csrf_token,
99 csrf_token=csrf_token,
101 repo_name=repo_name,
100 repo_name=repo_name,
102 repo_type=backend.alias,
101 repo_type=backend.alias,
103 repo_owner=TEST_USER_ADMIN_LOGIN,
102 repo_owner=TEST_USER_ADMIN_LOGIN,
104 repo_description='DESC',
103 repo_description='DESC',
105
104
106 repo_private='false',
105 repo_private='false',
107 repo_enable_locking='false',
106 repo_enable_locking='false',
108 repo_enable_downloads='false')
107 repo_enable_downloads='false')
109 params.update(update_settings)
108 params.update(update_settings)
110 self.app.post(
109 self.app.post(
111 route_path('edit_repo', repo_name=repo_name),
110 route_path('edit_repo', repo_name=repo_name),
112 params=params, status=302)
111 params=params, status=302)
113
112
114 repo = Repository.get_by_repo_name(repo_name)
113 repo = Repository.get_by_repo_name(repo_name)
115 assert repo.user.username == \
114 assert repo.user.username == \
116 update_settings.get('repo_owner', repo.user.username)
115 update_settings.get('repo_owner', repo.user.username)
117
116
118 assert repo.description == \
117 assert repo.description == \
119 update_settings.get('repo_description', repo.description)
118 update_settings.get('repo_description', repo.description)
120
119
121 assert repo.private == \
120 assert repo.private == \
122 str2bool(update_settings.get(
121 str2bool(update_settings.get(
123 'repo_private', repo.private))
122 'repo_private', repo.private))
124
123
125 assert repo.enable_locking == \
124 assert repo.enable_locking == \
126 str2bool(update_settings.get(
125 str2bool(update_settings.get(
127 'repo_enable_locking', repo.enable_locking))
126 'repo_enable_locking', repo.enable_locking))
128
127
129 assert repo.enable_downloads == \
128 assert repo.enable_downloads == \
130 str2bool(update_settings.get(
129 str2bool(update_settings.get(
131 'repo_enable_downloads', repo.enable_downloads))
130 'repo_enable_downloads', repo.enable_downloads))
132
131
133 def test_update_repo_name_via_settings(self, csrf_token, user_util, backend):
132 def test_update_repo_name_via_settings(self, csrf_token, user_util, backend):
134 repo = user_util.create_repo(repo_type=backend.alias)
133 repo = user_util.create_repo(repo_type=backend.alias)
135 repo_name = repo.repo_name
134 repo_name = repo.repo_name
136
135
137 repo_group = user_util.create_repo_group()
136 repo_group = user_util.create_repo_group()
138 repo_group_name = repo_group.group_name
137 repo_group_name = repo_group.group_name
139 new_name = repo_group_name + '_' + repo_name
138 new_name = repo_group_name + '_' + repo_name
140
139
141 params = fixture._get_repo_create_params(
140 params = fixture._get_repo_create_params(
142 csrf_token=csrf_token,
141 csrf_token=csrf_token,
143 repo_name=new_name,
142 repo_name=new_name,
144 repo_type=backend.alias,
143 repo_type=backend.alias,
145 repo_owner=TEST_USER_ADMIN_LOGIN,
144 repo_owner=TEST_USER_ADMIN_LOGIN,
146 repo_description='DESC',
145 repo_description='DESC',
147 repo_private='false',
146 repo_private='false',
148 repo_enable_locking='false',
147 repo_enable_locking='false',
149 repo_enable_downloads='false')
148 repo_enable_downloads='false')
150 self.app.post(
149 self.app.post(
151 route_path('edit_repo', repo_name=repo_name),
150 route_path('edit_repo', repo_name=repo_name),
152 params=params, status=302)
151 params=params, status=302)
153 repo = Repository.get_by_repo_name(new_name)
152 repo = Repository.get_by_repo_name(new_name)
154 assert repo.repo_name == new_name
153 assert repo.repo_name == new_name
155
154
156 def test_update_repo_group_via_settings(self, csrf_token, user_util, backend):
155 def test_update_repo_group_via_settings(self, csrf_token, user_util, backend):
157 repo = user_util.create_repo(repo_type=backend.alias)
156 repo = user_util.create_repo(repo_type=backend.alias)
158 repo_name = repo.repo_name
157 repo_name = repo.repo_name
159
158
160 repo_group = user_util.create_repo_group()
159 repo_group = user_util.create_repo_group()
161 repo_group_name = repo_group.group_name
160 repo_group_name = repo_group.group_name
162 repo_group_id = repo_group.group_id
161 repo_group_id = repo_group.group_id
163
162
164 new_name = repo_group_name + '/' + repo_name
163 new_name = repo_group_name + '/' + repo_name
165 params = fixture._get_repo_create_params(
164 params = fixture._get_repo_create_params(
166 csrf_token=csrf_token,
165 csrf_token=csrf_token,
167 repo_name=repo_name,
166 repo_name=repo_name,
168 repo_type=backend.alias,
167 repo_type=backend.alias,
169 repo_owner=TEST_USER_ADMIN_LOGIN,
168 repo_owner=TEST_USER_ADMIN_LOGIN,
170 repo_description='DESC',
169 repo_description='DESC',
171 repo_group=repo_group_id,
170 repo_group=repo_group_id,
172 repo_private='false',
171 repo_private='false',
173 repo_enable_locking='false',
172 repo_enable_locking='false',
174 repo_enable_downloads='false')
173 repo_enable_downloads='false')
175 self.app.post(
174 self.app.post(
176 route_path('edit_repo', repo_name=repo_name),
175 route_path('edit_repo', repo_name=repo_name),
177 params=params, status=302)
176 params=params, status=302)
178 repo = Repository.get_by_repo_name(new_name)
177 repo = Repository.get_by_repo_name(new_name)
179 assert repo.repo_name == new_name
178 assert repo.repo_name == new_name
180
179
181 def test_set_private_flag_sets_default_user_permissions_to_none(
180 def test_set_private_flag_sets_default_user_permissions_to_none(
182 self, autologin_user, backend, csrf_token):
181 self, autologin_user, backend, csrf_token):
183
182
184 # initially repository perm should be read
183 # initially repository perm should be read
185 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
184 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
186 assert len(perm) == 1
185 assert len(perm) == 1
187 assert perm[0].permission.permission_name == 'repository.read'
186 assert perm[0].permission.permission_name == 'repository.read'
188 assert not backend.repo.private
187 assert not backend.repo.private
189
188
190 response = self.app.post(
189 response = self.app.post(
191 route_path('edit_repo', repo_name=backend.repo_name),
190 route_path('edit_repo', repo_name=backend.repo_name),
192 params=fixture._get_repo_create_params(
191 params=fixture._get_repo_create_params(
193 repo_private='true',
192 repo_private='true',
194 repo_name=backend.repo_name,
193 repo_name=backend.repo_name,
195 repo_type=backend.alias,
194 repo_type=backend.alias,
196 repo_owner=TEST_USER_ADMIN_LOGIN,
195 repo_owner=TEST_USER_ADMIN_LOGIN,
197 csrf_token=csrf_token), status=302)
196 csrf_token=csrf_token), status=302)
198
197
199 assert_session_flash(
198 assert_session_flash(
200 response,
199 response,
201 msg='Repository %s updated successfully' % (backend.repo_name))
200 msg='Repository %s updated successfully' % (backend.repo_name))
202
201
203 repo = Repository.get_by_repo_name(backend.repo_name)
202 repo = Repository.get_by_repo_name(backend.repo_name)
204 assert repo.private is True
203 assert repo.private is True
205
204
206 # now the repo default permission should be None
205 # now the repo default permission should be None
207 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
206 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
208 assert len(perm) == 1
207 assert len(perm) == 1
209 assert perm[0].permission.permission_name == 'repository.none'
208 assert perm[0].permission.permission_name == 'repository.none'
210
209
211 response = self.app.post(
210 response = self.app.post(
212 route_path('edit_repo', repo_name=backend.repo_name),
211 route_path('edit_repo', repo_name=backend.repo_name),
213 params=fixture._get_repo_create_params(
212 params=fixture._get_repo_create_params(
214 repo_private='false',
213 repo_private='false',
215 repo_name=backend.repo_name,
214 repo_name=backend.repo_name,
216 repo_type=backend.alias,
215 repo_type=backend.alias,
217 repo_owner=TEST_USER_ADMIN_LOGIN,
216 repo_owner=TEST_USER_ADMIN_LOGIN,
218 csrf_token=csrf_token), status=302)
217 csrf_token=csrf_token), status=302)
219
218
220 assert_session_flash(
219 assert_session_flash(
221 response,
220 response,
222 msg='Repository %s updated successfully' % (backend.repo_name))
221 msg='Repository %s updated successfully' % (backend.repo_name))
223 assert backend.repo.private is False
222 assert backend.repo.private is False
224
223
225 # we turn off private now the repo default permission should stay None
224 # we turn off private now the repo default permission should stay None
226 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
225 perm = _get_permission_for_user(user='default', repo=backend.repo_name)
227 assert len(perm) == 1
226 assert len(perm) == 1
228 assert perm[0].permission.permission_name == 'repository.none'
227 assert perm[0].permission.permission_name == 'repository.none'
229
228
230 # update this permission back
229 # update this permission back
231 perm[0].permission = Permission.get_by_key('repository.read')
230 perm[0].permission = Permission.get_by_key('repository.read')
232 Session().add(perm[0])
231 Session().add(perm[0])
233 Session().commit()
232 Session().commit()
@@ -1,121 +1,104 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.model.db import Repository
25 from rhodecode.model.db import Repository
26 from rhodecode.model.settings import SettingsModel
26 from rhodecode.model.settings import SettingsModel
27 from rhodecode.tests import url
28 from rhodecode.tests.utils import AssertResponse
27 from rhodecode.tests.utils import AssertResponse
29
28
30
29
31 def route_path(name, params=None, **kwargs):
30 def route_path(name, params=None, **kwargs):
32 import urllib
31 import urllib
33
32
34 base_url = {
33 base_url = {
35 'edit_repo': '/{repo_name}/settings',
34 'edit_repo': '/{repo_name}/settings',
35 'edit_repo_vcs': '/{repo_name}/settings/vcs',
36 'edit_repo_vcs_update': '/{repo_name}/settings/vcs/update',
36 }[name].format(**kwargs)
37 }[name].format(**kwargs)
37
38
38 if params:
39 if params:
39 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
40 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
40 return base_url
41 return base_url
41
42
42
43
43 @pytest.mark.usefixtures('autologin_user', 'app')
44 @pytest.mark.usefixtures('autologin_user', 'app')
44 class TestAdminRepoVcsSettings(object):
45 class TestAdminRepoVcsSettings(object):
45
46
46 @pytest.mark.parametrize('setting_name, setting_backends', [
47 @pytest.mark.parametrize('setting_name, setting_backends', [
47 ('hg_use_rebase_for_merging', ['hg']),
48 ('hg_use_rebase_for_merging', ['hg']),
48 ])
49 ])
49 def test_labs_settings_visible_if_enabled(
50 def test_labs_settings_visible_if_enabled(
50 self, setting_name, setting_backends, backend):
51 self, setting_name, setting_backends, backend):
51 if backend.alias not in setting_backends:
52 if backend.alias not in setting_backends:
52 pytest.skip('Setting not available for backend {}'.format(backend))
53 pytest.skip('Setting not available for backend {}'.format(backend))
53
54
54 vcs_settings_url = url(
55 vcs_settings_url = route_path(
55 'repo_vcs_settings', repo_name=backend.repo.repo_name)
56 'edit_repo_vcs', repo_name=backend.repo.repo_name)
56
57
57 with mock.patch.dict(
58 with mock.patch.dict(
58 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
59 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
59 response = self.app.get(vcs_settings_url)
60 response = self.app.get(vcs_settings_url)
60
61
61 assertr = AssertResponse(response)
62 assertr = AssertResponse(response)
62 assertr.one_element_exists('#rhodecode_{}'.format(setting_name))
63 assertr.one_element_exists('#rhodecode_{}'.format(setting_name))
63
64
64 @pytest.mark.parametrize('setting_name, setting_backends', [
65 @pytest.mark.parametrize('setting_name, setting_backends', [
65 ('hg_use_rebase_for_merging', ['hg']),
66 ('hg_use_rebase_for_merging', ['hg']),
66 ])
67 ])
67 def test_labs_settings_not_visible_if_disabled(
68 self, setting_name, setting_backends, backend):
69 if backend.alias not in setting_backends:
70 pytest.skip('Setting not available for backend {}'.format(backend))
71
72 vcs_settings_url = url(
73 'repo_vcs_settings', repo_name=backend.repo.repo_name)
74
75 with mock.patch.dict(
76 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
77 response = self.app.get(vcs_settings_url)
78
79 assertr = AssertResponse(response)
80 assertr.no_element_exists('#rhodecode_{}'.format(setting_name))
81
82 @pytest.mark.parametrize('setting_name, setting_backends', [
83 ('hg_use_rebase_for_merging', ['hg']),
84 ])
85 def test_update_boolean_settings(
68 def test_update_boolean_settings(
86 self, csrf_token, setting_name, setting_backends, backend):
69 self, csrf_token, setting_name, setting_backends, backend):
87 if backend.alias not in setting_backends:
70 if backend.alias not in setting_backends:
88 pytest.skip('Setting not available for backend {}'.format(backend))
71 pytest.skip('Setting not available for backend {}'.format(backend))
89
72
90 repo = backend.create_repo()
73 repo = backend.create_repo()
91 repo_name = repo.repo_name
74 repo_name = repo.repo_name
92
75
93 settings_model = SettingsModel(repo=repo)
76 settings_model = SettingsModel(repo=repo)
94 vcs_settings_url = url(
77 vcs_settings_url = route_path(
95 'repo_vcs_settings', repo_name=repo_name)
78 'edit_repo_vcs_update', repo_name=repo_name)
96
79
97 self.app.post(
80 self.app.post(
98 vcs_settings_url,
81 vcs_settings_url,
99 params={
82 params={
100 'inherit_global_settings': False,
83 'inherit_global_settings': False,
101 'new_svn_branch': 'dummy-value-for-testing',
84 'new_svn_branch': 'dummy-value-for-testing',
102 'new_svn_tag': 'dummy-value-for-testing',
85 'new_svn_tag': 'dummy-value-for-testing',
103 'rhodecode_{}'.format(setting_name): 'true',
86 'rhodecode_{}'.format(setting_name): 'true',
104 'csrf_token': csrf_token,
87 'csrf_token': csrf_token,
105 })
88 })
106 settings_model = SettingsModel(repo=Repository.get_by_repo_name(repo_name))
89 settings_model = SettingsModel(repo=Repository.get_by_repo_name(repo_name))
107 setting = settings_model.get_setting_by_name(setting_name)
90 setting = settings_model.get_setting_by_name(setting_name)
108 assert setting.app_settings_value
91 assert setting.app_settings_value
109
92
110 self.app.post(
93 self.app.post(
111 vcs_settings_url,
94 vcs_settings_url,
112 params={
95 params={
113 'inherit_global_settings': False,
96 'inherit_global_settings': False,
114 'new_svn_branch': 'dummy-value-for-testing',
97 'new_svn_branch': 'dummy-value-for-testing',
115 'new_svn_tag': 'dummy-value-for-testing',
98 'new_svn_tag': 'dummy-value-for-testing',
116 'rhodecode_{}'.format(setting_name): 'false',
99 'rhodecode_{}'.format(setting_name): 'false',
117 'csrf_token': csrf_token,
100 'csrf_token': csrf_token,
118 })
101 })
119 settings_model = SettingsModel(repo=Repository.get_by_repo_name(repo_name))
102 settings_model = SettingsModel(repo=Repository.get_by_repo_name(repo_name))
120 setting = settings_model.get_setting_by_name(setting_name)
103 setting = settings_model.get_setting_by_name(setting_name)
121 assert not setting.app_settings_value
104 assert not setting.app_settings_value
@@ -1,557 +1,557 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
25 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode.apps._base import RepoAppView
30 from rhodecode.apps._base import RepoAppView
31
31
32 from rhodecode.lib import diffs, codeblocks
32 from rhodecode.lib import diffs, codeblocks
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
35
35
36 from rhodecode.lib.compat import OrderedDict
36 from rhodecode.lib.compat import OrderedDict
37 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
37 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 import rhodecode.lib.helpers as h
38 import rhodecode.lib.helpers as h
39 from rhodecode.lib.utils2 import safe_unicode, safe_int
39 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.lib.vcs.backends.base import EmptyCommit
40 from rhodecode.lib.vcs.backends.base import EmptyCommit
41 from rhodecode.lib.vcs.exceptions import (
41 from rhodecode.lib.vcs.exceptions import (
42 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
42 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
43 from rhodecode.model.db import ChangesetComment, ChangesetStatus
43 from rhodecode.model.db import ChangesetComment, ChangesetStatus
44 from rhodecode.model.changeset_status import ChangesetStatusModel
44 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.comment import CommentsModel
45 from rhodecode.model.comment import CommentsModel
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 def _update_with_GET(params, request):
52 def _update_with_GET(params, request):
53 for k in ['diff1', 'diff2', 'diff']:
53 for k in ['diff1', 'diff2', 'diff']:
54 params[k] += request.GET.getall(k)
54 params[k] += request.GET.getall(k)
55
55
56
56
57 def get_ignore_ws(fid, request):
57 def get_ignore_ws(fid, request):
58 ig_ws_global = request.GET.get('ignorews')
58 ig_ws_global = request.GET.get('ignorews')
59 ig_ws = filter(lambda k: k.startswith('WS'), request.GET.getall(fid))
59 ig_ws = filter(lambda k: k.startswith('WS'), request.GET.getall(fid))
60 if ig_ws:
60 if ig_ws:
61 try:
61 try:
62 return int(ig_ws[0].split(':')[-1])
62 return int(ig_ws[0].split(':')[-1])
63 except Exception:
63 except Exception:
64 pass
64 pass
65 return ig_ws_global
65 return ig_ws_global
66
66
67
67
68 def _ignorews_url(request, fileid=None):
68 def _ignorews_url(request, fileid=None):
69 _ = request.translate
69 _ = request.translate
70 fileid = str(fileid) if fileid else None
70 fileid = str(fileid) if fileid else None
71 params = collections.defaultdict(list)
71 params = collections.defaultdict(list)
72 _update_with_GET(params, request)
72 _update_with_GET(params, request)
73 label = _('Show whitespace')
73 label = _('Show whitespace')
74 tooltiplbl = _('Show whitespace for all diffs')
74 tooltiplbl = _('Show whitespace for all diffs')
75 ig_ws = get_ignore_ws(fileid, request)
75 ig_ws = get_ignore_ws(fileid, request)
76 ln_ctx = get_line_ctx(fileid, request)
76 ln_ctx = get_line_ctx(fileid, request)
77
77
78 if ig_ws is None:
78 if ig_ws is None:
79 params['ignorews'] += [1]
79 params['ignorews'] += [1]
80 label = _('Ignore whitespace')
80 label = _('Ignore whitespace')
81 tooltiplbl = _('Ignore whitespace for all diffs')
81 tooltiplbl = _('Ignore whitespace for all diffs')
82 ctx_key = 'context'
82 ctx_key = 'context'
83 ctx_val = ln_ctx
83 ctx_val = ln_ctx
84
84
85 # if we have passed in ln_ctx pass it along to our params
85 # if we have passed in ln_ctx pass it along to our params
86 if ln_ctx:
86 if ln_ctx:
87 params[ctx_key] += [ctx_val]
87 params[ctx_key] += [ctx_val]
88
88
89 if fileid:
89 if fileid:
90 params['anchor'] = 'a_' + fileid
90 params['anchor'] = 'a_' + fileid
91 return h.link_to(label, request.current_route_path(_query=params),
91 return h.link_to(label, request.current_route_path(_query=params),
92 title=tooltiplbl, class_='tooltip')
92 title=tooltiplbl, class_='tooltip')
93
93
94
94
95 def get_line_ctx(fid, request):
95 def get_line_ctx(fid, request):
96 ln_ctx_global = request.GET.get('context')
96 ln_ctx_global = request.GET.get('context')
97 if fid:
97 if fid:
98 ln_ctx = filter(lambda k: k.startswith('C'), request.GET.getall(fid))
98 ln_ctx = filter(lambda k: k.startswith('C'), request.GET.getall(fid))
99 else:
99 else:
100 _ln_ctx = filter(lambda k: k.startswith('C'), request.GET)
100 _ln_ctx = filter(lambda k: k.startswith('C'), request.GET)
101 ln_ctx = request.GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
101 ln_ctx = request.GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
102 if ln_ctx:
102 if ln_ctx:
103 ln_ctx = [ln_ctx]
103 ln_ctx = [ln_ctx]
104
104
105 if ln_ctx:
105 if ln_ctx:
106 retval = ln_ctx[0].split(':')[-1]
106 retval = ln_ctx[0].split(':')[-1]
107 else:
107 else:
108 retval = ln_ctx_global
108 retval = ln_ctx_global
109
109
110 try:
110 try:
111 return int(retval)
111 return int(retval)
112 except Exception:
112 except Exception:
113 return 3
113 return 3
114
114
115
115
116 def _context_url(request, fileid=None):
116 def _context_url(request, fileid=None):
117 """
117 """
118 Generates a url for context lines.
118 Generates a url for context lines.
119
119
120 :param fileid:
120 :param fileid:
121 """
121 """
122
122
123 _ = request.translate
123 _ = request.translate
124 fileid = str(fileid) if fileid else None
124 fileid = str(fileid) if fileid else None
125 ig_ws = get_ignore_ws(fileid, request)
125 ig_ws = get_ignore_ws(fileid, request)
126 ln_ctx = (get_line_ctx(fileid, request) or 3) * 2
126 ln_ctx = (get_line_ctx(fileid, request) or 3) * 2
127
127
128 params = collections.defaultdict(list)
128 params = collections.defaultdict(list)
129 _update_with_GET(params, request)
129 _update_with_GET(params, request)
130
130
131 if ln_ctx > 0:
131 if ln_ctx > 0:
132 params['context'] += [ln_ctx]
132 params['context'] += [ln_ctx]
133
133
134 if ig_ws:
134 if ig_ws:
135 ig_ws_key = 'ignorews'
135 ig_ws_key = 'ignorews'
136 ig_ws_val = 1
136 ig_ws_val = 1
137 params[ig_ws_key] += [ig_ws_val]
137 params[ig_ws_key] += [ig_ws_val]
138
138
139 lbl = _('Increase context')
139 lbl = _('Increase context')
140 tooltiplbl = _('Increase context for all diffs')
140 tooltiplbl = _('Increase context for all diffs')
141
141
142 if fileid:
142 if fileid:
143 params['anchor'] = 'a_' + fileid
143 params['anchor'] = 'a_' + fileid
144 return h.link_to(lbl, request.current_route_path(_query=params),
144 return h.link_to(lbl, request.current_route_path(_query=params),
145 title=tooltiplbl, class_='tooltip')
145 title=tooltiplbl, class_='tooltip')
146
146
147
147
148 class RepoCommitsView(RepoAppView):
148 class RepoCommitsView(RepoAppView):
149 def load_default_context(self):
149 def load_default_context(self):
150 c = self._get_local_tmpl_context(include_app_defaults=True)
150 c = self._get_local_tmpl_context(include_app_defaults=True)
151
151
152 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
152 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
153 c.repo_info = self.db_repo
153 c.repo_info = self.db_repo
154 c.rhodecode_repo = self.rhodecode_vcs_repo
154 c.rhodecode_repo = self.rhodecode_vcs_repo
155
155
156 self._register_global_c(c)
156 self._register_global_c(c)
157 return c
157 return c
158
158
159 def _commit(self, commit_id_range, method):
159 def _commit(self, commit_id_range, method):
160 _ = self.request.translate
160 _ = self.request.translate
161 c = self.load_default_context()
161 c = self.load_default_context()
162 c.ignorews_url = _ignorews_url
162 c.ignorews_url = _ignorews_url
163 c.context_url = _context_url
163 c.context_url = _context_url
164 c.fulldiff = self.request.GET.get('fulldiff')
164 c.fulldiff = self.request.GET.get('fulldiff')
165
165
166 # fetch global flags of ignore ws or context lines
166 # fetch global flags of ignore ws or context lines
167 context_lcl = get_line_ctx('', self.request)
167 context_lcl = get_line_ctx('', self.request)
168 ign_whitespace_lcl = get_ignore_ws('', self.request)
168 ign_whitespace_lcl = get_ignore_ws('', self.request)
169
169
170 # diff_limit will cut off the whole diff if the limit is applied
170 # diff_limit will cut off the whole diff if the limit is applied
171 # otherwise it will just hide the big files from the front-end
171 # otherwise it will just hide the big files from the front-end
172 diff_limit = c.visual.cut_off_limit_diff
172 diff_limit = c.visual.cut_off_limit_diff
173 file_limit = c.visual.cut_off_limit_file
173 file_limit = c.visual.cut_off_limit_file
174
174
175 # get ranges of commit ids if preset
175 # get ranges of commit ids if preset
176 commit_range = commit_id_range.split('...')[:2]
176 commit_range = commit_id_range.split('...')[:2]
177
177
178 try:
178 try:
179 pre_load = ['affected_files', 'author', 'branch', 'date',
179 pre_load = ['affected_files', 'author', 'branch', 'date',
180 'message', 'parents']
180 'message', 'parents']
181
181
182 if len(commit_range) == 2:
182 if len(commit_range) == 2:
183 commits = self.rhodecode_vcs_repo.get_commits(
183 commits = self.rhodecode_vcs_repo.get_commits(
184 start_id=commit_range[0], end_id=commit_range[1],
184 start_id=commit_range[0], end_id=commit_range[1],
185 pre_load=pre_load)
185 pre_load=pre_load)
186 commits = list(commits)
186 commits = list(commits)
187 else:
187 else:
188 commits = [self.rhodecode_vcs_repo.get_commit(
188 commits = [self.rhodecode_vcs_repo.get_commit(
189 commit_id=commit_id_range, pre_load=pre_load)]
189 commit_id=commit_id_range, pre_load=pre_load)]
190
190
191 c.commit_ranges = commits
191 c.commit_ranges = commits
192 if not c.commit_ranges:
192 if not c.commit_ranges:
193 raise RepositoryError(
193 raise RepositoryError(
194 'The commit range returned an empty result')
194 'The commit range returned an empty result')
195 except CommitDoesNotExistError:
195 except CommitDoesNotExistError:
196 msg = _('No such commit exists for this repository')
196 msg = _('No such commit exists for this repository')
197 h.flash(msg, category='error')
197 h.flash(msg, category='error')
198 raise HTTPNotFound()
198 raise HTTPNotFound()
199 except Exception:
199 except Exception:
200 log.exception("General failure")
200 log.exception("General failure")
201 raise HTTPNotFound()
201 raise HTTPNotFound()
202
202
203 c.changes = OrderedDict()
203 c.changes = OrderedDict()
204 c.lines_added = 0
204 c.lines_added = 0
205 c.lines_deleted = 0
205 c.lines_deleted = 0
206
206
207 # auto collapse if we have more than limit
207 # auto collapse if we have more than limit
208 collapse_limit = diffs.DiffProcessor._collapse_commits_over
208 collapse_limit = diffs.DiffProcessor._collapse_commits_over
209 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
209 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
210
210
211 c.commit_statuses = ChangesetStatus.STATUSES
211 c.commit_statuses = ChangesetStatus.STATUSES
212 c.inline_comments = []
212 c.inline_comments = []
213 c.files = []
213 c.files = []
214
214
215 c.statuses = []
215 c.statuses = []
216 c.comments = []
216 c.comments = []
217 c.unresolved_comments = []
217 c.unresolved_comments = []
218 if len(c.commit_ranges) == 1:
218 if len(c.commit_ranges) == 1:
219 commit = c.commit_ranges[0]
219 commit = c.commit_ranges[0]
220 c.comments = CommentsModel().get_comments(
220 c.comments = CommentsModel().get_comments(
221 self.db_repo.repo_id,
221 self.db_repo.repo_id,
222 revision=commit.raw_id)
222 revision=commit.raw_id)
223 c.statuses.append(ChangesetStatusModel().get_status(
223 c.statuses.append(ChangesetStatusModel().get_status(
224 self.db_repo.repo_id, commit.raw_id))
224 self.db_repo.repo_id, commit.raw_id))
225 # comments from PR
225 # comments from PR
226 statuses = ChangesetStatusModel().get_statuses(
226 statuses = ChangesetStatusModel().get_statuses(
227 self.db_repo.repo_id, commit.raw_id,
227 self.db_repo.repo_id, commit.raw_id,
228 with_revisions=True)
228 with_revisions=True)
229 prs = set(st.pull_request for st in statuses
229 prs = set(st.pull_request for st in statuses
230 if st.pull_request is not None)
230 if st.pull_request is not None)
231 # from associated statuses, check the pull requests, and
231 # from associated statuses, check the pull requests, and
232 # show comments from them
232 # show comments from them
233 for pr in prs:
233 for pr in prs:
234 c.comments.extend(pr.comments)
234 c.comments.extend(pr.comments)
235
235
236 c.unresolved_comments = CommentsModel()\
236 c.unresolved_comments = CommentsModel()\
237 .get_commit_unresolved_todos(commit.raw_id)
237 .get_commit_unresolved_todos(commit.raw_id)
238
238
239 diff = None
239 diff = None
240 # Iterate over ranges (default commit view is always one commit)
240 # Iterate over ranges (default commit view is always one commit)
241 for commit in c.commit_ranges:
241 for commit in c.commit_ranges:
242 c.changes[commit.raw_id] = []
242 c.changes[commit.raw_id] = []
243
243
244 commit2 = commit
244 commit2 = commit
245 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
245 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
246
246
247 _diff = self.rhodecode_vcs_repo.get_diff(
247 _diff = self.rhodecode_vcs_repo.get_diff(
248 commit1, commit2,
248 commit1, commit2,
249 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
249 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
250 diff_processor = diffs.DiffProcessor(
250 diff_processor = diffs.DiffProcessor(
251 _diff, format='newdiff', diff_limit=diff_limit,
251 _diff, format='newdiff', diff_limit=diff_limit,
252 file_limit=file_limit, show_full_diff=c.fulldiff)
252 file_limit=file_limit, show_full_diff=c.fulldiff)
253
253
254 commit_changes = OrderedDict()
254 commit_changes = OrderedDict()
255 if method == 'show':
255 if method == 'show':
256 _parsed = diff_processor.prepare()
256 _parsed = diff_processor.prepare()
257 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
257 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
258
258
259 _parsed = diff_processor.prepare()
259 _parsed = diff_processor.prepare()
260
260
261 def _node_getter(commit):
261 def _node_getter(commit):
262 def get_node(fname):
262 def get_node(fname):
263 try:
263 try:
264 return commit.get_node(fname)
264 return commit.get_node(fname)
265 except NodeDoesNotExistError:
265 except NodeDoesNotExistError:
266 return None
266 return None
267 return get_node
267 return get_node
268
268
269 inline_comments = CommentsModel().get_inline_comments(
269 inline_comments = CommentsModel().get_inline_comments(
270 self.db_repo.repo_id, revision=commit.raw_id)
270 self.db_repo.repo_id, revision=commit.raw_id)
271 c.inline_cnt = CommentsModel().get_inline_comments_count(
271 c.inline_cnt = CommentsModel().get_inline_comments_count(
272 inline_comments)
272 inline_comments)
273
273
274 diffset = codeblocks.DiffSet(
274 diffset = codeblocks.DiffSet(
275 repo_name=self.db_repo_name,
275 repo_name=self.db_repo_name,
276 source_node_getter=_node_getter(commit1),
276 source_node_getter=_node_getter(commit1),
277 target_node_getter=_node_getter(commit2),
277 target_node_getter=_node_getter(commit2),
278 comments=inline_comments)
278 comments=inline_comments)
279 diffset = diffset.render_patchset(
279 diffset = diffset.render_patchset(
280 _parsed, commit1.raw_id, commit2.raw_id)
280 _parsed, commit1.raw_id, commit2.raw_id)
281
281
282 c.changes[commit.raw_id] = diffset
282 c.changes[commit.raw_id] = diffset
283 else:
283 else:
284 # downloads/raw we only need RAW diff nothing else
284 # downloads/raw we only need RAW diff nothing else
285 diff = diff_processor.as_raw()
285 diff = diff_processor.as_raw()
286 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
286 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
287
287
288 # sort comments by how they were generated
288 # sort comments by how they were generated
289 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
289 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
290
290
291 if len(c.commit_ranges) == 1:
291 if len(c.commit_ranges) == 1:
292 c.commit = c.commit_ranges[0]
292 c.commit = c.commit_ranges[0]
293 c.parent_tmpl = ''.join(
293 c.parent_tmpl = ''.join(
294 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
294 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
295
295
296 if method == 'download':
296 if method == 'download':
297 response = Response(diff)
297 response = Response(diff)
298 response.content_type = 'text/plain'
298 response.content_type = 'text/plain'
299 response.content_disposition = (
299 response.content_disposition = (
300 'attachment; filename=%s.diff' % commit_id_range[:12])
300 'attachment; filename=%s.diff' % commit_id_range[:12])
301 return response
301 return response
302 elif method == 'patch':
302 elif method == 'patch':
303 c.diff = safe_unicode(diff)
303 c.diff = safe_unicode(diff)
304 patch = render(
304 patch = render(
305 'rhodecode:templates/changeset/patch_changeset.mako',
305 'rhodecode:templates/changeset/patch_changeset.mako',
306 self._get_template_context(c), self.request)
306 self._get_template_context(c), self.request)
307 response = Response(patch)
307 response = Response(patch)
308 response.content_type = 'text/plain'
308 response.content_type = 'text/plain'
309 return response
309 return response
310 elif method == 'raw':
310 elif method == 'raw':
311 response = Response(diff)
311 response = Response(diff)
312 response.content_type = 'text/plain'
312 response.content_type = 'text/plain'
313 return response
313 return response
314 elif method == 'show':
314 elif method == 'show':
315 if len(c.commit_ranges) == 1:
315 if len(c.commit_ranges) == 1:
316 html = render(
316 html = render(
317 'rhodecode:templates/changeset/changeset.mako',
317 'rhodecode:templates/changeset/changeset.mako',
318 self._get_template_context(c), self.request)
318 self._get_template_context(c), self.request)
319 return Response(html)
319 return Response(html)
320 else:
320 else:
321 c.ancestor = None
321 c.ancestor = None
322 c.target_repo = self.db_repo
322 c.target_repo = self.db_repo
323 html = render(
323 html = render(
324 'rhodecode:templates/changeset/changeset_range.mako',
324 'rhodecode:templates/changeset/changeset_range.mako',
325 self._get_template_context(c), self.request)
325 self._get_template_context(c), self.request)
326 return Response(html)
326 return Response(html)
327
327
328 raise HTTPBadRequest()
328 raise HTTPBadRequest()
329
329
330 @LoginRequired()
330 @LoginRequired()
331 @HasRepoPermissionAnyDecorator(
331 @HasRepoPermissionAnyDecorator(
332 'repository.read', 'repository.write', 'repository.admin')
332 'repository.read', 'repository.write', 'repository.admin')
333 @view_config(
333 @view_config(
334 route_name='repo_commit', request_method='GET',
334 route_name='repo_commit', request_method='GET',
335 renderer=None)
335 renderer=None)
336 def repo_commit_show(self):
336 def repo_commit_show(self):
337 commit_id = self.request.matchdict['commit_id']
337 commit_id = self.request.matchdict['commit_id']
338 return self._commit(commit_id, method='show')
338 return self._commit(commit_id, method='show')
339
339
340 @LoginRequired()
340 @LoginRequired()
341 @HasRepoPermissionAnyDecorator(
341 @HasRepoPermissionAnyDecorator(
342 'repository.read', 'repository.write', 'repository.admin')
342 'repository.read', 'repository.write', 'repository.admin')
343 @view_config(
343 @view_config(
344 route_name='repo_commit_raw', request_method='GET',
344 route_name='repo_commit_raw', request_method='GET',
345 renderer=None)
345 renderer=None)
346 @view_config(
346 @view_config(
347 route_name='repo_commit_raw_deprecated', request_method='GET',
347 route_name='repo_commit_raw_deprecated', request_method='GET',
348 renderer=None)
348 renderer=None)
349 def repo_commit_raw(self):
349 def repo_commit_raw(self):
350 commit_id = self.request.matchdict['commit_id']
350 commit_id = self.request.matchdict['commit_id']
351 return self._commit(commit_id, method='raw')
351 return self._commit(commit_id, method='raw')
352
352
353 @LoginRequired()
353 @LoginRequired()
354 @HasRepoPermissionAnyDecorator(
354 @HasRepoPermissionAnyDecorator(
355 'repository.read', 'repository.write', 'repository.admin')
355 'repository.read', 'repository.write', 'repository.admin')
356 @view_config(
356 @view_config(
357 route_name='repo_commit_patch', request_method='GET',
357 route_name='repo_commit_patch', request_method='GET',
358 renderer=None)
358 renderer=None)
359 def repo_commit_patch(self):
359 def repo_commit_patch(self):
360 commit_id = self.request.matchdict['commit_id']
360 commit_id = self.request.matchdict['commit_id']
361 return self._commit(commit_id, method='patch')
361 return self._commit(commit_id, method='patch')
362
362
363 @LoginRequired()
363 @LoginRequired()
364 @HasRepoPermissionAnyDecorator(
364 @HasRepoPermissionAnyDecorator(
365 'repository.read', 'repository.write', 'repository.admin')
365 'repository.read', 'repository.write', 'repository.admin')
366 @view_config(
366 @view_config(
367 route_name='repo_commit_download', request_method='GET',
367 route_name='repo_commit_download', request_method='GET',
368 renderer=None)
368 renderer=None)
369 def repo_commit_download(self):
369 def repo_commit_download(self):
370 commit_id = self.request.matchdict['commit_id']
370 commit_id = self.request.matchdict['commit_id']
371 return self._commit(commit_id, method='download')
371 return self._commit(commit_id, method='download')
372
372
373 @LoginRequired()
373 @LoginRequired()
374 @NotAnonymous()
374 @NotAnonymous()
375 @HasRepoPermissionAnyDecorator(
375 @HasRepoPermissionAnyDecorator(
376 'repository.read', 'repository.write', 'repository.admin')
376 'repository.read', 'repository.write', 'repository.admin')
377 @CSRFRequired()
377 @CSRFRequired()
378 @view_config(
378 @view_config(
379 route_name='repo_commit_comment_create', request_method='POST',
379 route_name='repo_commit_comment_create', request_method='POST',
380 renderer='json_ext')
380 renderer='json_ext')
381 def repo_commit_comment_create(self):
381 def repo_commit_comment_create(self):
382 _ = self.request.translate
382 _ = self.request.translate
383 commit_id = self.request.matchdict['commit_id']
383 commit_id = self.request.matchdict['commit_id']
384
384
385 c = self.load_default_context()
385 c = self.load_default_context()
386 status = self.request.POST.get('changeset_status', None)
386 status = self.request.POST.get('changeset_status', None)
387 text = self.request.POST.get('text')
387 text = self.request.POST.get('text')
388 comment_type = self.request.POST.get('comment_type')
388 comment_type = self.request.POST.get('comment_type')
389 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
389 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
390
390
391 if status:
391 if status:
392 text = text or (_('Status change %(transition_icon)s %(status)s')
392 text = text or (_('Status change %(transition_icon)s %(status)s')
393 % {'transition_icon': '>',
393 % {'transition_icon': '>',
394 'status': ChangesetStatus.get_status_lbl(status)})
394 'status': ChangesetStatus.get_status_lbl(status)})
395
395
396 multi_commit_ids = []
396 multi_commit_ids = []
397 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
397 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
398 if _commit_id not in ['', None, EmptyCommit.raw_id]:
398 if _commit_id not in ['', None, EmptyCommit.raw_id]:
399 if _commit_id not in multi_commit_ids:
399 if _commit_id not in multi_commit_ids:
400 multi_commit_ids.append(_commit_id)
400 multi_commit_ids.append(_commit_id)
401
401
402 commit_ids = multi_commit_ids or [commit_id]
402 commit_ids = multi_commit_ids or [commit_id]
403
403
404 comment = None
404 comment = None
405 for current_id in filter(None, commit_ids):
405 for current_id in filter(None, commit_ids):
406 comment = CommentsModel().create(
406 comment = CommentsModel().create(
407 text=text,
407 text=text,
408 repo=self.db_repo.repo_id,
408 repo=self.db_repo.repo_id,
409 user=self._rhodecode_db_user.user_id,
409 user=self._rhodecode_db_user.user_id,
410 commit_id=current_id,
410 commit_id=current_id,
411 f_path=self.request.POST.get('f_path'),
411 f_path=self.request.POST.get('f_path'),
412 line_no=self.request.POST.get('line'),
412 line_no=self.request.POST.get('line'),
413 status_change=(ChangesetStatus.get_status_lbl(status)
413 status_change=(ChangesetStatus.get_status_lbl(status)
414 if status else None),
414 if status else None),
415 status_change_type=status,
415 status_change_type=status,
416 comment_type=comment_type,
416 comment_type=comment_type,
417 resolves_comment_id=resolves_comment_id
417 resolves_comment_id=resolves_comment_id
418 )
418 )
419
419
420 # get status if set !
420 # get status if set !
421 if status:
421 if status:
422 # if latest status was from pull request and it's closed
422 # if latest status was from pull request and it's closed
423 # disallow changing status !
423 # disallow changing status !
424 # dont_allow_on_closed_pull_request = True !
424 # dont_allow_on_closed_pull_request = True !
425
425
426 try:
426 try:
427 ChangesetStatusModel().set_status(
427 ChangesetStatusModel().set_status(
428 self.db_repo.repo_id,
428 self.db_repo.repo_id,
429 status,
429 status,
430 self._rhodecode_db_user.user_id,
430 self._rhodecode_db_user.user_id,
431 comment,
431 comment,
432 revision=current_id,
432 revision=current_id,
433 dont_allow_on_closed_pull_request=True
433 dont_allow_on_closed_pull_request=True
434 )
434 )
435 except StatusChangeOnClosedPullRequestError:
435 except StatusChangeOnClosedPullRequestError:
436 msg = _('Changing the status of a commit associated with '
436 msg = _('Changing the status of a commit associated with '
437 'a closed pull request is not allowed')
437 'a closed pull request is not allowed')
438 log.exception(msg)
438 log.exception(msg)
439 h.flash(msg, category='warning')
439 h.flash(msg, category='warning')
440 raise HTTPFound(h.route_path(
440 raise HTTPFound(h.route_path(
441 'repo_commit', repo_name=self.db_repo_name,
441 'repo_commit', repo_name=self.db_repo_name,
442 commit_id=current_id))
442 commit_id=current_id))
443
443
444 # finalize, commit and redirect
444 # finalize, commit and redirect
445 Session().commit()
445 Session().commit()
446
446
447 data = {
447 data = {
448 'target_id': h.safeid(h.safe_unicode(
448 'target_id': h.safeid(h.safe_unicode(
449 self.request.POST.get('f_path'))),
449 self.request.POST.get('f_path'))),
450 }
450 }
451 if comment:
451 if comment:
452 c.co = comment
452 c.co = comment
453 rendered_comment = render(
453 rendered_comment = render(
454 'rhodecode:templates/changeset/changeset_comment_block.mako',
454 'rhodecode:templates/changeset/changeset_comment_block.mako',
455 self._get_template_context(c), self.request)
455 self._get_template_context(c), self.request)
456
456
457 data.update(comment.get_dict())
457 data.update(comment.get_dict())
458 data.update({'rendered_text': rendered_comment})
458 data.update({'rendered_text': rendered_comment})
459
459
460 return data
460 return data
461
461
462 @LoginRequired()
462 @LoginRequired()
463 @NotAnonymous()
463 @NotAnonymous()
464 @HasRepoPermissionAnyDecorator(
464 @HasRepoPermissionAnyDecorator(
465 'repository.read', 'repository.write', 'repository.admin')
465 'repository.read', 'repository.write', 'repository.admin')
466 @CSRFRequired()
466 @CSRFRequired()
467 @view_config(
467 @view_config(
468 route_name='repo_commit_comment_preview', request_method='POST',
468 route_name='repo_commit_comment_preview', request_method='POST',
469 renderer='string', xhr=True)
469 renderer='string', xhr=True)
470 def repo_commit_comment_preview(self):
470 def repo_commit_comment_preview(self):
471 # Technically a CSRF token is not needed as no state changes with this
471 # Technically a CSRF token is not needed as no state changes with this
472 # call. However, as this is a POST is better to have it, so automated
472 # call. However, as this is a POST is better to have it, so automated
473 # tools don't flag it as potential CSRF.
473 # tools don't flag it as potential CSRF.
474 # Post is required because the payload could be bigger than the maximum
474 # Post is required because the payload could be bigger than the maximum
475 # allowed by GET.
475 # allowed by GET.
476
476
477 text = self.request.POST.get('text')
477 text = self.request.POST.get('text')
478 renderer = self.request.POST.get('renderer') or 'rst'
478 renderer = self.request.POST.get('renderer') or 'rst'
479 if text:
479 if text:
480 return h.render(text, renderer=renderer, mentions=True)
480 return h.render(text, renderer=renderer, mentions=True)
481 return ''
481 return ''
482
482
483 @LoginRequired()
483 @LoginRequired()
484 @NotAnonymous()
484 @NotAnonymous()
485 @HasRepoPermissionAnyDecorator(
485 @HasRepoPermissionAnyDecorator(
486 'repository.read', 'repository.write', 'repository.admin')
486 'repository.read', 'repository.write', 'repository.admin')
487 @CSRFRequired()
487 @CSRFRequired()
488 @view_config(
488 @view_config(
489 route_name='repo_commit_comment_delete', request_method='POST',
489 route_name='repo_commit_comment_delete', request_method='POST',
490 renderer='json_ext')
490 renderer='json_ext')
491 def repo_commit_comment_delete(self):
491 def repo_commit_comment_delete(self):
492 commit_id = self.request.matchdict['commit_id']
492 commit_id = self.request.matchdict['commit_id']
493 comment_id = self.request.matchdict['comment_id']
493 comment_id = self.request.matchdict['comment_id']
494
494
495 comment = ChangesetComment.get_or_404(comment_id)
495 comment = ChangesetComment.get_or_404(comment_id)
496 if not comment:
496 if not comment:
497 log.debug('Comment with id:%s not found, skipping', comment_id)
497 log.debug('Comment with id:%s not found, skipping', comment_id)
498 # comment already deleted in another call probably
498 # comment already deleted in another call probably
499 return True
499 return True
500
500
501 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
501 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
502 super_admin = h.HasPermissionAny('hg.admin')()
502 super_admin = h.HasPermissionAny('hg.admin')()
503 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
503 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
504 is_repo_comment = comment.repo.repo_name == self.db_repo_name
504 is_repo_comment = comment.repo.repo_name == self.db_repo_name
505 comment_repo_admin = is_repo_admin and is_repo_comment
505 comment_repo_admin = is_repo_admin and is_repo_comment
506
506
507 if super_admin or comment_owner or comment_repo_admin:
507 if super_admin or comment_owner or comment_repo_admin:
508 CommentsModel().delete(comment=comment, user=self._rhodecode_db_user)
508 CommentsModel().delete(comment=comment, user=self._rhodecode_db_user)
509 Session().commit()
509 Session().commit()
510 return True
510 return True
511 else:
511 else:
512 log.warning('No permissions for user %s to delete comment_id: %s',
512 log.warning('No permissions for user %s to delete comment_id: %s',
513 self._rhodecode_db_user, comment_id)
513 self._rhodecode_db_user, comment_id)
514 raise HTTPNotFound()
514 raise HTTPNotFound()
515
515
516 @LoginRequired()
516 @LoginRequired()
517 @HasRepoPermissionAnyDecorator(
517 @HasRepoPermissionAnyDecorator(
518 'repository.read', 'repository.write', 'repository.admin')
518 'repository.read', 'repository.write', 'repository.admin')
519 @view_config(
519 @view_config(
520 route_name='repo_commit_data', request_method='GET',
520 route_name='repo_commit_data', request_method='GET',
521 renderer='json_ext', xhr=True)
521 renderer='json_ext', xhr=True)
522 def repo_commit_data(self):
522 def repo_commit_data(self):
523 commit_id = self.request.matchdict['commit_id']
523 commit_id = self.request.matchdict['commit_id']
524 self.load_default_context()
524 self.load_default_context()
525
525
526 try:
526 try:
527 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
527 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
528 except CommitDoesNotExistError as e:
528 except CommitDoesNotExistError as e:
529 return EmptyCommit(message=str(e))
529 return EmptyCommit(message=str(e))
530
530
531 @LoginRequired()
531 @LoginRequired()
532 @HasRepoPermissionAnyDecorator(
532 @HasRepoPermissionAnyDecorator(
533 'repository.read', 'repository.write', 'repository.admin')
533 'repository.read', 'repository.write', 'repository.admin')
534 @view_config(
534 @view_config(
535 route_name='repo_commit_children', request_method='GET',
535 route_name='repo_commit_children', request_method='GET',
536 renderer='json_ext', xhr=True)
536 renderer='json_ext', xhr=True)
537 def repo_commit_children(self):
537 def repo_commit_children(self):
538 commit_id = self.request.matchdict['commit_id']
538 commit_id = self.request.matchdict['commit_id']
539 self.load_default_context()
539 self.load_default_context()
540
540
541 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
541 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
542 result = {"results": commit.children}
542 result = {"results": commit.children}
543 return result
543 return result
544
544
545 @LoginRequired()
545 @LoginRequired()
546 @HasRepoPermissionAnyDecorator(
546 @HasRepoPermissionAnyDecorator(
547 'repository.read', 'repository.write', 'repository.admin')
547 'repository.read', 'repository.write', 'repository.admin')
548 @view_config(
548 @view_config(
549 route_name='repo_commit_parents', request_method='GET',
549 route_name='repo_commit_parents', request_method='GET',
550 renderer='json_ext')
550 renderer='json_ext')
551 def repo_commit_parents(self):
551 def repo_commit_parents(self):
552 commit_id = self.request.matchdict['commit_id']
552 commit_id = self.request.matchdict['commit_id']
553 self.load_default_context()
553 self.load_default_context()
554
554
555 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
555 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
556 result = {"results": commit.parents}
556 result = {"results": commit.parents}
557 return result
557 return result
@@ -1,203 +1,202 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytz
21 import pytz
22 import logging
22 import logging
23
23
24 from beaker.cache import cache_region
24 from beaker.cache import cache_region
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26 from pyramid.response import Response
26 from pyramid.response import Response
27 from webhelpers.feedgenerator import Rss201rev2Feed, Atom1Feed
27 from webhelpers.feedgenerator import Rss201rev2Feed, Atom1Feed
28
28
29 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps._base import RepoAppView
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
32 from rhodecode.lib.auth import (
33 NotAnonymous, CSRFRequired)
33 LoginRequired, HasRepoPermissionAnyDecorator)
34 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
34 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.utils2 import str2bool, safe_int
35 from rhodecode.lib.utils2 import str2bool, safe_int
37 from rhodecode.model.db import UserApiKeys, CacheKey
36 from rhodecode.model.db import UserApiKeys, CacheKey
38
37
39 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
40
39
41
40
42 class RepoFeedView(RepoAppView):
41 class RepoFeedView(RepoAppView):
43 def load_default_context(self):
42 def load_default_context(self):
44 c = self._get_local_tmpl_context()
43 c = self._get_local_tmpl_context()
45
44
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
45 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 c.repo_info = self.db_repo
46 c.repo_info = self.db_repo
48
47
49 self._register_global_c(c)
48 self._register_global_c(c)
50 self._load_defaults()
49 self._load_defaults()
51 return c
50 return c
52
51
53 def _get_config(self):
52 def _get_config(self):
54 import rhodecode
53 import rhodecode
55 config = rhodecode.CONFIG
54 config = rhodecode.CONFIG
56
55
57 return {
56 return {
58 'language': 'en-us',
57 'language': 'en-us',
59 'feed_ttl': '5', # TTL of feed,
58 'feed_ttl': '5', # TTL of feed,
60 'feed_include_diff':
59 'feed_include_diff':
61 str2bool(config.get('rss_include_diff', False)),
60 str2bool(config.get('rss_include_diff', False)),
62 'feed_items_per_page':
61 'feed_items_per_page':
63 safe_int(config.get('rss_items_per_page', 20)),
62 safe_int(config.get('rss_items_per_page', 20)),
64 'feed_diff_limit':
63 'feed_diff_limit':
65 # we need to protect from parsing huge diffs here other way
64 # we need to protect from parsing huge diffs here other way
66 # we can kill the server
65 # we can kill the server
67 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
66 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
68 }
67 }
69
68
70 def _load_defaults(self):
69 def _load_defaults(self):
71 _ = self.request.translate
70 _ = self.request.translate
72 config = self._get_config()
71 config = self._get_config()
73 # common values for feeds
72 # common values for feeds
74 self.description = _('Changes on %s repository')
73 self.description = _('Changes on %s repository')
75 self.title = self.title = _('%s %s feed') % (self.db_repo_name, '%s')
74 self.title = self.title = _('%s %s feed') % (self.db_repo_name, '%s')
76 self.language = config["language"]
75 self.language = config["language"]
77 self.ttl = config["feed_ttl"]
76 self.ttl = config["feed_ttl"]
78 self.feed_include_diff = config['feed_include_diff']
77 self.feed_include_diff = config['feed_include_diff']
79 self.feed_diff_limit = config['feed_diff_limit']
78 self.feed_diff_limit = config['feed_diff_limit']
80 self.feed_items_per_page = config['feed_items_per_page']
79 self.feed_items_per_page = config['feed_items_per_page']
81
80
82 def _changes(self, commit):
81 def _changes(self, commit):
83 diff_processor = DiffProcessor(
82 diff_processor = DiffProcessor(
84 commit.diff(), diff_limit=self.feed_diff_limit)
83 commit.diff(), diff_limit=self.feed_diff_limit)
85 _parsed = diff_processor.prepare(inline_diff=False)
84 _parsed = diff_processor.prepare(inline_diff=False)
86 limited_diff = isinstance(_parsed, LimitedDiffContainer)
85 limited_diff = isinstance(_parsed, LimitedDiffContainer)
87
86
88 return _parsed, limited_diff
87 return _parsed, limited_diff
89
88
90 def _get_title(self, commit):
89 def _get_title(self, commit):
91 return h.shorter(commit.message, 160)
90 return h.shorter(commit.message, 160)
92
91
93 def _get_description(self, commit):
92 def _get_description(self, commit):
94 _renderer = self.request.get_partial_renderer(
93 _renderer = self.request.get_partial_renderer(
95 'feed/atom_feed_entry.mako')
94 'feed/atom_feed_entry.mako')
96 parsed_diff, limited_diff = self._changes(commit)
95 parsed_diff, limited_diff = self._changes(commit)
97 return _renderer(
96 return _renderer(
98 'body',
97 'body',
99 commit=commit,
98 commit=commit,
100 parsed_diff=parsed_diff,
99 parsed_diff=parsed_diff,
101 limited_diff=limited_diff,
100 limited_diff=limited_diff,
102 feed_include_diff=self.feed_include_diff,
101 feed_include_diff=self.feed_include_diff,
103 )
102 )
104
103
105 def _set_timezone(self, date, tzinfo=pytz.utc):
104 def _set_timezone(self, date, tzinfo=pytz.utc):
106 if not getattr(date, "tzinfo", None):
105 if not getattr(date, "tzinfo", None):
107 date.replace(tzinfo=tzinfo)
106 date.replace(tzinfo=tzinfo)
108 return date
107 return date
109
108
110 def _get_commits(self):
109 def _get_commits(self):
111 return list(self.rhodecode_vcs_repo[-self.feed_items_per_page:])
110 return list(self.rhodecode_vcs_repo[-self.feed_items_per_page:])
112
111
113 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
112 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
114 @HasRepoPermissionAnyDecorator(
113 @HasRepoPermissionAnyDecorator(
115 'repository.read', 'repository.write', 'repository.admin')
114 'repository.read', 'repository.write', 'repository.admin')
116 @view_config(
115 @view_config(
117 route_name='atom_feed_home', request_method='GET',
116 route_name='atom_feed_home', request_method='GET',
118 renderer=None)
117 renderer=None)
119 def atom(self):
118 def atom(self):
120 """
119 """
121 Produce an atom-1.0 feed via feedgenerator module
120 Produce an atom-1.0 feed via feedgenerator module
122 """
121 """
123 self.load_default_context()
122 self.load_default_context()
124
123
125 @cache_region('long_term')
124 @cache_region('long_term')
126 def _generate_feed(cache_key):
125 def _generate_feed(cache_key):
127 feed = Atom1Feed(
126 feed = Atom1Feed(
128 title=self.title % self.db_repo_name,
127 title=self.title % self.db_repo_name,
129 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
128 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
130 description=self.description % self.db_repo_name,
129 description=self.description % self.db_repo_name,
131 language=self.language,
130 language=self.language,
132 ttl=self.ttl
131 ttl=self.ttl
133 )
132 )
134
133
135 for commit in reversed(self._get_commits()):
134 for commit in reversed(self._get_commits()):
136 date = self._set_timezone(commit.date)
135 date = self._set_timezone(commit.date)
137 feed.add_item(
136 feed.add_item(
138 title=self._get_title(commit),
137 title=self._get_title(commit),
139 author_name=commit.author,
138 author_name=commit.author,
140 description=self._get_description(commit),
139 description=self._get_description(commit),
141 link=h.route_url(
140 link=h.route_url(
142 'repo_commit', repo_name=self.db_repo_name,
141 'repo_commit', repo_name=self.db_repo_name,
143 commit_id=commit.raw_id),
142 commit_id=commit.raw_id),
144 pubdate=date,)
143 pubdate=date,)
145
144
146 return feed.mime_type, feed.writeString('utf-8')
145 return feed.mime_type, feed.writeString('utf-8')
147
146
148 invalidator_context = CacheKey.repo_context_cache(
147 invalidator_context = CacheKey.repo_context_cache(
149 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM)
148 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM)
150
149
151 with invalidator_context as context:
150 with invalidator_context as context:
152 context.invalidate()
151 context.invalidate()
153 mime_type, feed = context.compute()
152 mime_type, feed = context.compute()
154
153
155 response = Response(feed)
154 response = Response(feed)
156 response.content_type = mime_type
155 response.content_type = mime_type
157 return response
156 return response
158
157
159 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
158 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
160 @HasRepoPermissionAnyDecorator(
159 @HasRepoPermissionAnyDecorator(
161 'repository.read', 'repository.write', 'repository.admin')
160 'repository.read', 'repository.write', 'repository.admin')
162 @view_config(
161 @view_config(
163 route_name='rss_feed_home', request_method='GET',
162 route_name='rss_feed_home', request_method='GET',
164 renderer=None)
163 renderer=None)
165 def rss(self):
164 def rss(self):
166 """
165 """
167 Produce an rss2 feed via feedgenerator module
166 Produce an rss2 feed via feedgenerator module
168 """
167 """
169 self.load_default_context()
168 self.load_default_context()
170
169
171 @cache_region('long_term')
170 @cache_region('long_term')
172 def _generate_feed(cache_key):
171 def _generate_feed(cache_key):
173 feed = Rss201rev2Feed(
172 feed = Rss201rev2Feed(
174 title=self.title % self.db_repo_name,
173 title=self.title % self.db_repo_name,
175 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
174 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
176 description=self.description % self.db_repo_name,
175 description=self.description % self.db_repo_name,
177 language=self.language,
176 language=self.language,
178 ttl=self.ttl
177 ttl=self.ttl
179 )
178 )
180
179
181 for commit in reversed(self._get_commits()):
180 for commit in reversed(self._get_commits()):
182 date = self._set_timezone(commit.date)
181 date = self._set_timezone(commit.date)
183 feed.add_item(
182 feed.add_item(
184 title=self._get_title(commit),
183 title=self._get_title(commit),
185 author_name=commit.author,
184 author_name=commit.author,
186 description=self._get_description(commit),
185 description=self._get_description(commit),
187 link=h.route_url(
186 link=h.route_url(
188 'repo_commit', repo_name=self.db_repo_name,
187 'repo_commit', repo_name=self.db_repo_name,
189 commit_id=commit.raw_id),
188 commit_id=commit.raw_id),
190 pubdate=date,)
189 pubdate=date,)
191
190
192 return feed.mime_type, feed.writeString('utf-8')
191 return feed.mime_type, feed.writeString('utf-8')
193
192
194 invalidator_context = CacheKey.repo_context_cache(
193 invalidator_context = CacheKey.repo_context_cache(
195 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_RSS)
194 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_RSS)
196
195
197 with invalidator_context as context:
196 with invalidator_context as context:
198 context.invalidate()
197 context.invalidate()
199 mime_type, feed = context.compute()
198 mime_type, feed = context.compute()
200
199
201 response = Response(feed)
200 response = Response(feed)
202 response.content_type = mime_type
201 response.content_type = mime_type
203 return response
202 return response
@@ -1,68 +1,67 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24
24
25 from rhodecode.apps._base import RepoAppView
25 from rhodecode.apps._base import RepoAppView
26 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
26 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 NotAnonymous)
28 from rhodecode.lib import repo_maintenance
27 from rhodecode.lib import repo_maintenance
29
28
30 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
31
30
32
31
33 class RepoMaintenanceView(RepoAppView):
32 class RepoMaintenanceView(RepoAppView):
34 def load_default_context(self):
33 def load_default_context(self):
35 c = self._get_local_tmpl_context()
34 c = self._get_local_tmpl_context()
36
35
37 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
36 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
38 c.repo_info = self.db_repo
37 c.repo_info = self.db_repo
39
38
40 self._register_global_c(c)
39 self._register_global_c(c)
41 return c
40 return c
42
41
43 @LoginRequired()
42 @LoginRequired()
44 @HasRepoPermissionAnyDecorator('repository.admin')
43 @HasRepoPermissionAnyDecorator('repository.admin')
45 @view_config(
44 @view_config(
46 route_name='repo_maintenance', request_method='GET',
45 route_name='edit_repo_maintenance', request_method='GET',
47 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
46 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
48 def repo_maintenance(self):
47 def repo_maintenance(self):
49 c = self.load_default_context()
48 c = self.load_default_context()
50 c.active = 'maintenance'
49 c.active = 'maintenance'
51 maintenance = repo_maintenance.RepoMaintenance()
50 maintenance = repo_maintenance.RepoMaintenance()
52 c.executable_tasks = maintenance.get_tasks_for_repo(self.db_repo)
51 c.executable_tasks = maintenance.get_tasks_for_repo(self.db_repo)
53 return self._get_template_context(c)
52 return self._get_template_context(c)
54
53
55 @LoginRequired()
54 @LoginRequired()
56 @HasRepoPermissionAnyDecorator('repository.admin')
55 @HasRepoPermissionAnyDecorator('repository.admin')
57 @view_config(
56 @view_config(
58 route_name='repo_maintenance_execute', request_method='GET',
57 route_name='edit_repo_maintenance_execute', request_method='GET',
59 renderer='json', xhr=True)
58 renderer='json', xhr=True)
60 def repo_maintenance_execute(self):
59 def repo_maintenance_execute(self):
61 c = self.load_default_context()
60 c = self.load_default_context()
62 c.active = 'maintenance'
61 c.active = 'maintenance'
63 _ = self.request.translate
62 _ = self.request.translate
64
63
65 maintenance = repo_maintenance.RepoMaintenance()
64 maintenance = repo_maintenance.RepoMaintenance()
66 executed_types = maintenance.execute(self.db_repo)
65 executed_types = maintenance.execute(self.db_repo)
67
66
68 return executed_types
67 return executed_types
@@ -1,98 +1,92 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import deform
24 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
24 from pyramid.view import view_config
26
25
27 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps._base import RepoAppView
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator,
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
33 HasRepoPermissionAllDecorator, CSRFRequired)
34 from rhodecode.model.db import RepositoryField, RepoGroup
35 from rhodecode.model.forms import RepoPermsForm
31 from rhodecode.model.forms import RepoPermsForm
36 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
37 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.scm import RepoGroupList, ScmModel
39 from rhodecode.model.validation_schema.schemas import repo_schema
40
34
41 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
42
36
43
37
44 class RepoSettingsPermissionsView(RepoAppView):
38 class RepoSettingsPermissionsView(RepoAppView):
45
39
46 def load_default_context(self):
40 def load_default_context(self):
47 c = self._get_local_tmpl_context()
41 c = self._get_local_tmpl_context()
48
42
49 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
43 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
50 c.repo_info = self.db_repo
44 c.repo_info = self.db_repo
51
45
52 self._register_global_c(c)
46 self._register_global_c(c)
53 return c
47 return c
54
48
55 @LoginRequired()
49 @LoginRequired()
56 @HasRepoPermissionAnyDecorator('repository.admin')
50 @HasRepoPermissionAnyDecorator('repository.admin')
57 @view_config(
51 @view_config(
58 route_name='edit_repo_perms', request_method='GET',
52 route_name='edit_repo_perms', request_method='GET',
59 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
53 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
60 def edit_permissions(self):
54 def edit_permissions(self):
61 c = self.load_default_context()
55 c = self.load_default_context()
62 c.active = 'permissions'
56 c.active = 'permissions'
63 return self._get_template_context(c)
57 return self._get_template_context(c)
64
58
65 @LoginRequired()
59 @LoginRequired()
66 @HasRepoPermissionAllDecorator('repository.admin')
60 @HasRepoPermissionAnyDecorator('repository.admin')
67 @CSRFRequired()
61 @CSRFRequired()
68 @view_config(
62 @view_config(
69 route_name='edit_repo_perms', request_method='POST',
63 route_name='edit_repo_perms', request_method='POST',
70 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
64 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
71 def edit_permissions_update(self):
65 def edit_permissions_update(self):
72 _ = self.request.translate
66 _ = self.request.translate
73 c = self.load_default_context()
67 c = self.load_default_context()
74 c.active = 'permissions'
68 c.active = 'permissions'
75 data = self.request.POST
69 data = self.request.POST
76 # store private flag outside of HTML to verify if we can modify
70 # store private flag outside of HTML to verify if we can modify
77 # default user permissions, prevents submition of FAKE post data
71 # default user permissions, prevents submission of FAKE post data
78 # into the form for private repos
72 # into the form for private repos
79 data['repo_private'] = self.db_repo.private
73 data['repo_private'] = self.db_repo.private
80 form = RepoPermsForm()().to_python(data)
74 form = RepoPermsForm()().to_python(data)
81 changes = RepoModel().update_permissions(
75 changes = RepoModel().update_permissions(
82 self.db_repo_name, form['perm_additions'], form['perm_updates'],
76 self.db_repo_name, form['perm_additions'], form['perm_updates'],
83 form['perm_deletions'])
77 form['perm_deletions'])
84
78
85 action_data = {
79 action_data = {
86 'added': changes['added'],
80 'added': changes['added'],
87 'updated': changes['updated'],
81 'updated': changes['updated'],
88 'deleted': changes['deleted'],
82 'deleted': changes['deleted'],
89 }
83 }
90 audit_logger.store_web(
84 audit_logger.store_web(
91 'repo.edit.permissions', action_data=action_data,
85 'repo.edit.permissions', action_data=action_data,
92 user=self._rhodecode_user, repo=self.db_repo)
86 user=self._rhodecode_user, repo=self.db_repo)
93
87
94 Session().commit()
88 Session().commit()
95 h.flash(_('Repository permissions updated'), category='success')
89 h.flash(_('Repository permissions updated'), category='success')
96
90
97 raise HTTPFound(
91 raise HTTPFound(
98 self.request.route_path('edit_repo_perms', repo_name=self.db_repo_name))
92 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
@@ -1,179 +1,254 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import deform
23 import deform
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.forms import RcForm
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator,
32 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
33 HasRepoPermissionAllDecorator, CSRFRequired)
33 from rhodecode.model.db import RepositoryField, RepoGroup, Repository
34 from rhodecode.model.db import RepositoryField, RepoGroup
35 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
36 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.scm import RepoGroupList, ScmModel
36 from rhodecode.model.scm import RepoGroupList, ScmModel
38 from rhodecode.model.validation_schema.schemas import repo_schema
37 from rhodecode.model.validation_schema.schemas import repo_schema
39
38
40 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
41
40
42
41
43 class RepoSettingsView(RepoAppView):
42 class RepoSettingsView(RepoAppView):
44
43
45 def load_default_context(self):
44 def load_default_context(self):
46 c = self._get_local_tmpl_context()
45 c = self._get_local_tmpl_context()
47
46
48 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
49 c.repo_info = self.db_repo
48 c.repo_info = self.db_repo
50
49
51 acl_groups = RepoGroupList(
50 acl_groups = RepoGroupList(
52 RepoGroup.query().all(),
51 RepoGroup.query().all(),
53 perm_set=['group.write', 'group.admin'])
52 perm_set=['group.write', 'group.admin'])
54 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
53 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
55 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
54 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
56
55
57 # in case someone no longer have a group.write access to a repository
56 # in case someone no longer have a group.write access to a repository
58 # pre fill the list with this entry, we don't care if this is the same
57 # pre fill the list with this entry, we don't care if this is the same
59 # but it will allow saving repo data properly.
58 # but it will allow saving repo data properly.
60 repo_group = self.db_repo.group
59 repo_group = self.db_repo.group
61 if repo_group and repo_group.group_id not in c.repo_groups_choices:
60 if repo_group and repo_group.group_id not in c.repo_groups_choices:
62 c.repo_groups_choices.append(repo_group.group_id)
61 c.repo_groups_choices.append(repo_group.group_id)
63 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
62 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
64
63
65 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
64 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
66 # we might be in missing requirement state, so we load things
65 # we might be in missing requirement state, so we load things
67 # without touching scm_instance()
66 # without touching scm_instance()
68 c.landing_revs_choices, c.landing_revs = \
67 c.landing_revs_choices, c.landing_revs = \
69 ScmModel().get_repo_landing_revs()
68 ScmModel().get_repo_landing_revs()
70 else:
69 else:
71 c.landing_revs_choices, c.landing_revs = \
70 c.landing_revs_choices, c.landing_revs = \
72 ScmModel().get_repo_landing_revs(self.db_repo)
71 ScmModel().get_repo_landing_revs(self.db_repo)
73
72
74 c.personal_repo_group = c.auth_user.personal_repo_group
73 c.personal_repo_group = c.auth_user.personal_repo_group
75 c.repo_fields = RepositoryField.query()\
74 c.repo_fields = RepositoryField.query()\
76 .filter(RepositoryField.repository == self.db_repo).all()
75 .filter(RepositoryField.repository == self.db_repo).all()
77
76
78 self._register_global_c(c)
77 self._register_global_c(c)
79 return c
78 return c
80
79
81 def _get_schema(self, c, old_values=None):
80 def _get_schema(self, c, old_values=None):
82 return repo_schema.RepoSettingsSchema().bind(
81 return repo_schema.RepoSettingsSchema().bind(
83 repo_type=self.db_repo.repo_type,
82 repo_type=self.db_repo.repo_type,
84 repo_type_options=[self.db_repo.repo_type],
83 repo_type_options=[self.db_repo.repo_type],
85 repo_ref_options=c.landing_revs_choices,
84 repo_ref_options=c.landing_revs_choices,
86 repo_ref_items=c.landing_revs,
85 repo_ref_items=c.landing_revs,
87 repo_repo_group_options=c.repo_groups_choices,
86 repo_repo_group_options=c.repo_groups_choices,
88 repo_repo_group_items=c.repo_groups,
87 repo_repo_group_items=c.repo_groups,
89 # user caller
88 # user caller
90 user=self._rhodecode_user,
89 user=self._rhodecode_user,
91 old_values=old_values
90 old_values=old_values
92 )
91 )
93
92
94 @LoginRequired()
93 @LoginRequired()
95 @HasRepoPermissionAnyDecorator('repository.admin')
94 @HasRepoPermissionAnyDecorator('repository.admin')
96 @view_config(
95 @view_config(
97 route_name='edit_repo', request_method='GET',
96 route_name='edit_repo', request_method='GET',
98 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
97 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
99 def edit_settings(self):
98 def edit_settings(self):
100 c = self.load_default_context()
99 c = self.load_default_context()
101 c.active = 'settings'
100 c.active = 'settings'
102
101
103 defaults = RepoModel()._get_defaults(self.db_repo_name)
102 defaults = RepoModel()._get_defaults(self.db_repo_name)
104 defaults['repo_owner'] = defaults['user']
103 defaults['repo_owner'] = defaults['user']
105 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
104 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
106
105
107 schema = self._get_schema(c)
106 schema = self._get_schema(c)
108 c.form = RcForm(schema, appstruct=defaults)
107 c.form = RcForm(schema, appstruct=defaults)
109 return self._get_template_context(c)
108 return self._get_template_context(c)
110
109
111 @LoginRequired()
110 @LoginRequired()
112 @HasRepoPermissionAllDecorator('repository.admin')
111 @HasRepoPermissionAnyDecorator('repository.admin')
113 @CSRFRequired()
112 @CSRFRequired()
114 @view_config(
113 @view_config(
115 route_name='edit_repo', request_method='POST',
114 route_name='edit_repo', request_method='POST',
116 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
115 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
117 def edit_settings_update(self):
116 def edit_settings_update(self):
118 _ = self.request.translate
117 _ = self.request.translate
119 c = self.load_default_context()
118 c = self.load_default_context()
120 c.active = 'settings'
119 c.active = 'settings'
121 old_repo_name = self.db_repo_name
120 old_repo_name = self.db_repo_name
122
121
123 old_values = self.db_repo.get_api_data()
122 old_values = self.db_repo.get_api_data()
124 schema = self._get_schema(c, old_values=old_values)
123 schema = self._get_schema(c, old_values=old_values)
125
124
126 c.form = RcForm(schema)
125 c.form = RcForm(schema)
127 pstruct = self.request.POST.items()
126 pstruct = self.request.POST.items()
128 pstruct.append(('repo_type', self.db_repo.repo_type))
127 pstruct.append(('repo_type', self.db_repo.repo_type))
129 try:
128 try:
130 schema_data = c.form.validate(pstruct)
129 schema_data = c.form.validate(pstruct)
131 except deform.ValidationFailure as err_form:
130 except deform.ValidationFailure as err_form:
132 return self._get_template_context(c)
131 return self._get_template_context(c)
133
132
134 # data is now VALID, proceed with updates
133 # data is now VALID, proceed with updates
135 # save validated data back into the updates dict
134 # save validated data back into the updates dict
136 validated_updates = dict(
135 validated_updates = dict(
137 repo_name=schema_data['repo_group']['repo_name_without_group'],
136 repo_name=schema_data['repo_group']['repo_name_without_group'],
138 repo_group=schema_data['repo_group']['repo_group_id'],
137 repo_group=schema_data['repo_group']['repo_group_id'],
139
138
140 user=schema_data['repo_owner'],
139 user=schema_data['repo_owner'],
141 repo_description=schema_data['repo_description'],
140 repo_description=schema_data['repo_description'],
142 repo_private=schema_data['repo_private'],
141 repo_private=schema_data['repo_private'],
143 clone_uri=schema_data['repo_clone_uri'],
142 clone_uri=schema_data['repo_clone_uri'],
144 repo_landing_rev=schema_data['repo_landing_commit_ref'],
143 repo_landing_rev=schema_data['repo_landing_commit_ref'],
145 repo_enable_statistics=schema_data['repo_enable_statistics'],
144 repo_enable_statistics=schema_data['repo_enable_statistics'],
146 repo_enable_locking=schema_data['repo_enable_locking'],
145 repo_enable_locking=schema_data['repo_enable_locking'],
147 repo_enable_downloads=schema_data['repo_enable_downloads'],
146 repo_enable_downloads=schema_data['repo_enable_downloads'],
148 )
147 )
149 # detect if CLONE URI changed, if we get OLD means we keep old values
148 # detect if CLONE URI changed, if we get OLD means we keep old values
150 if schema_data['repo_clone_uri_change'] == 'OLD':
149 if schema_data['repo_clone_uri_change'] == 'OLD':
151 validated_updates['clone_uri'] = self.db_repo.clone_uri
150 validated_updates['clone_uri'] = self.db_repo.clone_uri
152
151
153 # use the new full name for redirect
152 # use the new full name for redirect
154 new_repo_name = schema_data['repo_group']['repo_name_with_group']
153 new_repo_name = schema_data['repo_group']['repo_name_with_group']
155
154
156 # save extra fields into our validated data
155 # save extra fields into our validated data
157 for key, value in pstruct:
156 for key, value in pstruct:
158 if key.startswith(RepositoryField.PREFIX):
157 if key.startswith(RepositoryField.PREFIX):
159 validated_updates[key] = value
158 validated_updates[key] = value
160
159
161 try:
160 try:
162 RepoModel().update(self.db_repo, **validated_updates)
161 RepoModel().update(self.db_repo, **validated_updates)
163 ScmModel().mark_for_invalidation(new_repo_name)
162 ScmModel().mark_for_invalidation(new_repo_name)
164
163
165 audit_logger.store_web(
164 audit_logger.store_web(
166 'repo.edit', action_data={'old_data': old_values},
165 'repo.edit', action_data={'old_data': old_values},
167 user=self._rhodecode_user, repo=self.db_repo)
166 user=self._rhodecode_user, repo=self.db_repo)
168
167
169 Session().commit()
168 Session().commit()
170
169
171 h.flash(_('Repository {} updated successfully').format(
170 h.flash(_('Repository {} updated successfully').format(
172 old_repo_name), category='success')
171 old_repo_name), category='success')
173 except Exception:
172 except Exception:
174 log.exception("Exception during update of repository")
173 log.exception("Exception during update of repository")
175 h.flash(_('Error occurred during update of repository {}').format(
174 h.flash(_('Error occurred during update of repository {}').format(
176 old_repo_name), category='error')
175 old_repo_name), category='error')
177
176
178 raise HTTPFound(
177 raise HTTPFound(
179 self.request.route_path('edit_repo', repo_name=new_repo_name))
178 h.route_path('edit_repo', repo_name=new_repo_name))
179
180 @LoginRequired()
181 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
182 @view_config(
183 route_name='repo_edit_toggle_locking', request_method='GET',
184 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
185 def toggle_locking(self):
186 """
187 Toggle locking of repository by simple GET call to url
188 """
189 _ = self.request.translate
190 repo = self.db_repo
191
192 try:
193 if repo.enable_locking:
194 if repo.locked[0]:
195 Repository.unlock(repo)
196 action = _('Unlocked')
197 else:
198 Repository.lock(
199 repo, self._rhodecode_user.user_id,
200 lock_reason=Repository.LOCK_WEB)
201 action = _('Locked')
202
203 h.flash(_('Repository has been %s') % action,
204 category='success')
205 except Exception:
206 log.exception("Exception during unlocking")
207 h.flash(_('An error occurred during unlocking'),
208 category='error')
209 raise HTTPFound(
210 h.route_path('repo_summary', repo_name=self.db_repo_name))
211
212 @LoginRequired()
213 @HasRepoPermissionAnyDecorator('repository.admin')
214 @view_config(
215 route_name='edit_repo_statistics', request_method='GET',
216 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
217 def edit_statistics_form(self):
218 c = self.load_default_context()
219
220 if self.db_repo.stats:
221 # this is on what revision we ended up so we add +1 for count
222 last_rev = self.db_repo.stats.stat_on_revision + 1
223 else:
224 last_rev = 0
225
226 c.active = 'statistics'
227 c.stats_revision = last_rev
228 c.repo_last_rev = self.rhodecode_vcs_repo.count()
229
230 if last_rev == 0 or c.repo_last_rev == 0:
231 c.stats_percentage = 0
232 else:
233 c.stats_percentage = '%.2f' % (
234 (float((last_rev)) / c.repo_last_rev) * 100)
235 return self._get_template_context(c)
236
237 @LoginRequired()
238 @HasRepoPermissionAnyDecorator('repository.admin')
239 @CSRFRequired()
240 @view_config(
241 route_name='edit_repo_statistics_reset', request_method='POST',
242 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
243 def repo_statistics_reset(self):
244 _ = self.request.translate
245
246 try:
247 RepoModel().delete_stats(self.db_repo_name)
248 Session().commit()
249 except Exception:
250 log.exception('Edit statistics failure')
251 h.flash(_('An error occurred during deletion of repository stats'),
252 category='error')
253 raise HTTPFound(
254 h.route_path('edit_repo_statistics', repo_name=self.db_repo_name))
@@ -1,226 +1,226 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.lib.exceptions import AttachedForksError
31 from rhodecode.lib.exceptions import AttachedForksError
32 from rhodecode.lib.utils2 import safe_int
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.vcs import RepositoryError
33 from rhodecode.lib.vcs import RepositoryError
34 from rhodecode.model.db import Session, UserFollowing, User, Repository
34 from rhodecode.model.db import Session, UserFollowing, User, Repository
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.scm import ScmModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class RepoSettingsView(RepoAppView):
41 class RepoSettingsView(RepoAppView):
42
42
43 def load_default_context(self):
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
44 c = self._get_local_tmpl_context()
45
45
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 c.repo_info = self.db_repo
47 c.repo_info = self.db_repo
48
48
49 self._register_global_c(c)
49 self._register_global_c(c)
50 return c
50 return c
51
51
52 @LoginRequired()
52 @LoginRequired()
53 @HasRepoPermissionAnyDecorator('repository.admin')
53 @HasRepoPermissionAnyDecorator('repository.admin')
54 @view_config(
54 @view_config(
55 route_name='edit_repo_advanced', request_method='GET',
55 route_name='edit_repo_advanced', request_method='GET',
56 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
56 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
57 def edit_advanced(self):
57 def edit_advanced(self):
58 c = self.load_default_context()
58 c = self.load_default_context()
59 c.active = 'advanced'
59 c.active = 'advanced'
60
60
61 c.default_user_id = User.get_default_user().user_id
61 c.default_user_id = User.get_default_user().user_id
62 c.in_public_journal = UserFollowing.query() \
62 c.in_public_journal = UserFollowing.query() \
63 .filter(UserFollowing.user_id == c.default_user_id) \
63 .filter(UserFollowing.user_id == c.default_user_id) \
64 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
64 .filter(UserFollowing.follows_repository == self.db_repo).scalar()
65
65
66 c.has_origin_repo_read_perm = False
66 c.has_origin_repo_read_perm = False
67 if self.db_repo.fork:
67 if self.db_repo.fork:
68 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
68 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
69 'repository.write', 'repository.read', 'repository.admin')(
69 'repository.write', 'repository.read', 'repository.admin')(
70 self.db_repo.fork.repo_name, 'repo set as fork page')
70 self.db_repo.fork.repo_name, 'repo set as fork page')
71
71
72 return self._get_template_context(c)
72 return self._get_template_context(c)
73
73
74 @LoginRequired()
74 @LoginRequired()
75 @HasRepoPermissionAnyDecorator('repository.admin')
75 @HasRepoPermissionAnyDecorator('repository.admin')
76 @CSRFRequired()
76 @CSRFRequired()
77 @view_config(
77 @view_config(
78 route_name='edit_repo_advanced_delete', request_method='POST',
78 route_name='edit_repo_advanced_delete', request_method='POST',
79 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
79 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
80 def edit_advanced_delete(self):
80 def edit_advanced_delete(self):
81 """
81 """
82 Deletes the repository, or shows warnings if deletion is not possible
82 Deletes the repository, or shows warnings if deletion is not possible
83 because of attached forks or other errors.
83 because of attached forks or other errors.
84 """
84 """
85 _ = self.request.translate
85 _ = self.request.translate
86 handle_forks = self.request.POST.get('forks', None)
86 handle_forks = self.request.POST.get('forks', None)
87
87
88 try:
88 try:
89 _forks = self.db_repo.forks.count()
89 _forks = self.db_repo.forks.count()
90 if _forks and handle_forks:
90 if _forks and handle_forks:
91 if handle_forks == 'detach_forks':
91 if handle_forks == 'detach_forks':
92 handle_forks = 'detach'
92 handle_forks = 'detach'
93 h.flash(_('Detached %s forks') % _forks, category='success')
93 h.flash(_('Detached %s forks') % _forks, category='success')
94 elif handle_forks == 'delete_forks':
94 elif handle_forks == 'delete_forks':
95 handle_forks = 'delete'
95 handle_forks = 'delete'
96 h.flash(_('Deleted %s forks') % _forks, category='success')
96 h.flash(_('Deleted %s forks') % _forks, category='success')
97
97
98 old_data = self.db_repo.get_api_data()
98 old_data = self.db_repo.get_api_data()
99 RepoModel().delete(self.db_repo, forks=handle_forks)
99 RepoModel().delete(self.db_repo, forks=handle_forks)
100
100
101 repo = audit_logger.RepoWrap(repo_id=None,
101 repo = audit_logger.RepoWrap(repo_id=None,
102 repo_name=self.db_repo.repo_name)
102 repo_name=self.db_repo.repo_name)
103 audit_logger.store_web(
103 audit_logger.store_web(
104 'repo.delete', action_data={'old_data': old_data},
104 'repo.delete', action_data={'old_data': old_data},
105 user=self._rhodecode_user, repo=repo)
105 user=self._rhodecode_user, repo=repo)
106
106
107 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
107 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
108 h.flash(
108 h.flash(
109 _('Deleted repository `%s`') % self.db_repo_name,
109 _('Deleted repository `%s`') % self.db_repo_name,
110 category='success')
110 category='success')
111 Session().commit()
111 Session().commit()
112 except AttachedForksError:
112 except AttachedForksError:
113 repo_advanced_url = h.route_path(
113 repo_advanced_url = h.route_path(
114 'edit_repo_advanced', repo_name=self.db_repo_name,
114 'edit_repo_advanced', repo_name=self.db_repo_name,
115 _anchor='advanced-delete')
115 _anchor='advanced-delete')
116 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
116 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
117 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
117 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
118 'Try using {delete_or_detach} option.')
118 'Try using {delete_or_detach} option.')
119 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
119 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
120 category='warning')
120 category='warning')
121
121
122 # redirect to advanced for forks handle action ?
122 # redirect to advanced for forks handle action ?
123 raise HTTPFound(repo_advanced_url)
123 raise HTTPFound(repo_advanced_url)
124
124
125 except Exception:
125 except Exception:
126 log.exception("Exception during deletion of repository")
126 log.exception("Exception during deletion of repository")
127 h.flash(_('An error occurred during deletion of `%s`')
127 h.flash(_('An error occurred during deletion of `%s`')
128 % self.db_repo_name, category='error')
128 % self.db_repo_name, category='error')
129 # redirect to advanced for more deletion options
129 # redirect to advanced for more deletion options
130 raise HTTPFound(
130 raise HTTPFound(
131 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
131 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
132 _anchor='advanced-delete')
132 _anchor='advanced-delete')
133
133
134 raise HTTPFound(h.route_path('home'))
134 raise HTTPFound(h.route_path('home'))
135
135
136 @LoginRequired()
136 @LoginRequired()
137 @HasRepoPermissionAnyDecorator('repository.admin')
137 @HasRepoPermissionAnyDecorator('repository.admin')
138 @CSRFRequired()
138 @CSRFRequired()
139 @view_config(
139 @view_config(
140 route_name='edit_repo_advanced_journal', request_method='POST',
140 route_name='edit_repo_advanced_journal', request_method='POST',
141 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
141 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
142 def edit_advanced_journal(self):
142 def edit_advanced_journal(self):
143 """
143 """
144 Set's this repository to be visible in public journal,
144 Set's this repository to be visible in public journal,
145 in other words making default user to follow this repo
145 in other words making default user to follow this repo
146 """
146 """
147 _ = self.request.translate
147 _ = self.request.translate
148
148
149 try:
149 try:
150 user_id = User.get_default_user().user_id
150 user_id = User.get_default_user().user_id
151 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
151 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
152 h.flash(_('Updated repository visibility in public journal'),
152 h.flash(_('Updated repository visibility in public journal'),
153 category='success')
153 category='success')
154 Session().commit()
154 Session().commit()
155 except Exception:
155 except Exception:
156 h.flash(_('An error occurred during setting this '
156 h.flash(_('An error occurred during setting this '
157 'repository in public journal'),
157 'repository in public journal'),
158 category='error')
158 category='error')
159
159
160 raise HTTPFound(
160 raise HTTPFound(
161 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
161 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
162
162
163 @LoginRequired()
163 @LoginRequired()
164 @HasRepoPermissionAnyDecorator('repository.admin')
164 @HasRepoPermissionAnyDecorator('repository.admin')
165 @CSRFRequired()
165 @CSRFRequired()
166 @view_config(
166 @view_config(
167 route_name='edit_repo_advanced_fork', request_method='POST',
167 route_name='edit_repo_advanced_fork', request_method='POST',
168 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
168 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
169 def edit_advanced_fork(self):
169 def edit_advanced_fork(self):
170 """
170 """
171 Mark given repository as a fork of another
171 Mark given repository as a fork of another
172 """
172 """
173 _ = self.request.translate
173 _ = self.request.translate
174
174
175 new_fork_id = self.request.POST.get('id_fork_of')
175 new_fork_id = self.request.POST.get('id_fork_of')
176 try:
176 try:
177
177
178 if new_fork_id and not new_fork_id.isdigit():
178 if new_fork_id and not new_fork_id.isdigit():
179 log.error('Given fork id %s is not an INT', new_fork_id)
179 log.error('Given fork id %s is not an INT', new_fork_id)
180
180
181 fork_id = safe_int(new_fork_id)
181 fork_id = safe_int(new_fork_id)
182 repo = ScmModel().mark_as_fork(
182 repo = ScmModel().mark_as_fork(
183 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
183 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
184 fork = repo.fork.repo_name if repo.fork else _('Nothing')
184 fork = repo.fork.repo_name if repo.fork else _('Nothing')
185 Session().commit()
185 Session().commit()
186 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
186 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
187 category='success')
187 category='success')
188 except RepositoryError as e:
188 except RepositoryError as e:
189 log.exception("Repository Error occurred")
189 log.exception("Repository Error occurred")
190 h.flash(str(e), category='error')
190 h.flash(str(e), category='error')
191 except Exception as e:
191 except Exception as e:
192 log.exception("Exception while editing fork")
192 log.exception("Exception while editing fork")
193 h.flash(_('An error occurred during this operation'),
193 h.flash(_('An error occurred during this operation'),
194 category='error')
194 category='error')
195
195
196 raise HTTPFound(
196 raise HTTPFound(
197 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
197 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
198
198
199 @LoginRequired()
199 @LoginRequired()
200 @HasRepoPermissionAnyDecorator('repository.admin')
200 @HasRepoPermissionAnyDecorator('repository.admin')
201 @CSRFRequired()
201 @CSRFRequired()
202 @view_config(
202 @view_config(
203 route_name='edit_repo_advanced_locking', request_method='POST',
203 route_name='edit_repo_advanced_locking', request_method='POST',
204 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
204 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
205 def edit_advanced_locking(self):
205 def edit_advanced_locking(self):
206 """
206 """
207 Toggle locking of repository
207 Toggle locking of repository
208 """
208 """
209 _ = self.request.translate
209 _ = self.request.translate
210 set_lock = self.request.POST.get('set_lock')
210 set_lock = self.request.POST.get('set_lock')
211 set_unlock = self.request.POST.get('set_unlock')
211 set_unlock = self.request.POST.get('set_unlock')
212
212
213 try:
213 try:
214 if set_lock:
214 if set_lock:
215 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
215 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
216 lock_reason=Repository.LOCK_WEB)
216 lock_reason=Repository.LOCK_WEB)
217 h.flash(_('Locked repository'), category='success')
217 h.flash(_('Locked repository'), category='success')
218 elif set_unlock:
218 elif set_unlock:
219 Repository.unlock(self.db_repo)
219 Repository.unlock(self.db_repo)
220 h.flash(_('Unlocked repository'), category='success')
220 h.flash(_('Unlocked repository'), category='success')
221 except Exception as e:
221 except Exception as e:
222 log.exception("Exception during unlocking")
222 log.exception("Exception during unlocking")
223 h.flash(_('An error occurred during unlocking'), category='error')
223 h.flash(_('An error occurred during unlocking'), category='error')
224
224
225 raise HTTPFound(
225 raise HTTPFound(
226 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
226 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
@@ -1,116 +1,116 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 from pyramid.view import view_config
22 from pyramid.view import view_config
23
23
24 from rhodecode.apps._base import RepoAppView
24 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib import audit_logger
25 from rhodecode.lib import audit_logger
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
27 from rhodecode.lib.auth import (
28 NotAnonymous, CSRFRequired)
28 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
29 from rhodecode.lib.ext_json import json
29 from rhodecode.lib.ext_json import json
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class StripView(RepoAppView):
34 class StripView(RepoAppView):
35 def load_default_context(self):
35 def load_default_context(self):
36 c = self._get_local_tmpl_context()
36 c = self._get_local_tmpl_context()
37
37
38 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
38 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
39 c.repo_info = self.db_repo
39 c.repo_info = self.db_repo
40
40
41 self._register_global_c(c)
41 self._register_global_c(c)
42 return c
42 return c
43
43
44 @LoginRequired()
44 @LoginRequired()
45 @HasRepoPermissionAnyDecorator('repository.admin')
45 @HasRepoPermissionAnyDecorator('repository.admin')
46 @view_config(
46 @view_config(
47 route_name='strip', request_method='GET',
47 route_name='edit_repo_strip', request_method='GET',
48 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
48 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
49 def strip(self):
49 def strip(self):
50 c = self.load_default_context()
50 c = self.load_default_context()
51 c.active = 'strip'
51 c.active = 'strip'
52 c.strip_limit = 10
52 c.strip_limit = 10
53
53
54 return self._get_template_context(c)
54 return self._get_template_context(c)
55
55
56 @LoginRequired()
56 @LoginRequired()
57 @HasRepoPermissionAnyDecorator('repository.admin')
57 @HasRepoPermissionAnyDecorator('repository.admin')
58 @CSRFRequired()
58 @CSRFRequired()
59 @view_config(
59 @view_config(
60 route_name='strip_check', request_method='POST',
60 route_name='strip_check', request_method='POST',
61 renderer='json', xhr=True)
61 renderer='json', xhr=True)
62 def strip_check(self):
62 def strip_check(self):
63 from rhodecode.lib.vcs.backends.base import EmptyCommit
63 from rhodecode.lib.vcs.backends.base import EmptyCommit
64 data = {}
64 data = {}
65 rp = self.request.POST
65 rp = self.request.POST
66 for i in range(1, 11):
66 for i in range(1, 11):
67 chset = 'changeset_id-%d' % (i,)
67 chset = 'changeset_id-%d' % (i,)
68 check = rp.get(chset)
68 check = rp.get(chset)
69
69
70 if check:
70 if check:
71 data[i] = self.db_repo.get_changeset(rp[chset])
71 data[i] = self.db_repo.get_changeset(rp[chset])
72 if isinstance(data[i], EmptyCommit):
72 if isinstance(data[i], EmptyCommit):
73 data[i] = {'rev': None, 'commit': h.escape(rp[chset])}
73 data[i] = {'rev': None, 'commit': h.escape(rp[chset])}
74 else:
74 else:
75 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch,
75 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch,
76 'author': data[i].author,
76 'author': data[i].author,
77 'comment': data[i].message}
77 'comment': data[i].message}
78 else:
78 else:
79 break
79 break
80 return data
80 return data
81
81
82 @LoginRequired()
82 @LoginRequired()
83 @HasRepoPermissionAnyDecorator('repository.admin')
83 @HasRepoPermissionAnyDecorator('repository.admin')
84 @CSRFRequired()
84 @CSRFRequired()
85 @view_config(
85 @view_config(
86 route_name='strip_execute', request_method='POST',
86 route_name='strip_execute', request_method='POST',
87 renderer='json', xhr=True)
87 renderer='json', xhr=True)
88 def strip_execute(self):
88 def strip_execute(self):
89 from rhodecode.model.scm import ScmModel
89 from rhodecode.model.scm import ScmModel
90
90
91 c = self.load_default_context()
91 c = self.load_default_context()
92 user = self._rhodecode_user
92 user = self._rhodecode_user
93 rp = self.request.POST
93 rp = self.request.POST
94 data = {}
94 data = {}
95 for idx in rp:
95 for idx in rp:
96 commit = json.loads(rp[idx])
96 commit = json.loads(rp[idx])
97 # If someone put two times the same branch
97 # If someone put two times the same branch
98 if commit['branch'] in data.keys():
98 if commit['branch'] in data.keys():
99 continue
99 continue
100 try:
100 try:
101 ScmModel().strip(
101 ScmModel().strip(
102 repo=c.repo_info,
102 repo=self.db_repo,
103 commit_id=commit['rev'], branch=commit['branch'])
103 commit_id=commit['rev'], branch=commit['branch'])
104 log.info('Stripped commit %s from repo `%s` by %s' % (
104 log.info('Stripped commit %s from repo `%s` by %s' % (
105 commit['rev'], c.repo_info.repo_name, user))
105 commit['rev'], self.db_repo_name, user))
106 data[commit['rev']] = True
106 data[commit['rev']] = True
107
107
108 audit_logger.store_web(
108 audit_logger.store_web(
109 'repo.commit.strip', action_data={'commit_id': commit['rev']},
109 'repo.commit.strip', action_data={'commit_id': commit['rev']},
110 repo=self.db_repo, user=self._rhodecode_user, commit=True)
110 repo=self.db_repo, user=self._rhodecode_user, commit=True)
111
111
112 except Exception as e:
112 except Exception as e:
113 data[commit['rev']] = False
113 data[commit['rev']] = False
114 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (
114 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (
115 commit['rev'], self.db_repo_name, user, e.message))
115 commit['rev'], self.db_repo_name, user, e.message))
116 return data
116 return data
@@ -1,370 +1,372 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import string
22 import string
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
26 from beaker.cache import cache_region
25 from beaker.cache import cache_region
27
26
28
29 from rhodecode.controllers import utils
27 from rhodecode.controllers import utils
30
31 from rhodecode.apps._base import RepoAppView
28 from rhodecode.apps._base import RepoAppView
32 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
33 from rhodecode.lib import caches, helpers as h
30 from rhodecode.lib import caches, helpers as h
34 from rhodecode.lib.helpers import RepoPage
31 from rhodecode.lib.helpers import RepoPage
35 from rhodecode.lib.utils2 import safe_str, safe_int
32 from rhodecode.lib.utils2 import safe_str, safe_int
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
38 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
40 from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError
37 from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError
41 from rhodecode.model.db import Statistics, CacheKey, User
38 from rhodecode.model.db import Statistics, CacheKey, User
42 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
43 from rhodecode.model.repo import ReadmeFinder
40 from rhodecode.model.repo import ReadmeFinder
44 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
45
42
46 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
47
44
48
45
49 class RepoSummaryView(RepoAppView):
46 class RepoSummaryView(RepoAppView):
50
47
51 def load_default_context(self):
48 def load_default_context(self):
52 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
53
50
54 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
51 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
55 c.repo_info = self.db_repo
52 c.repo_info = self.db_repo
56 c.rhodecode_repo = None
53 c.rhodecode_repo = None
57 if not c.repository_requirements_missing:
54 if not c.repository_requirements_missing:
58 c.rhodecode_repo = self.rhodecode_vcs_repo
55 c.rhodecode_repo = self.rhodecode_vcs_repo
59
56
60 self._register_global_c(c)
57 self._register_global_c(c)
61 return c
58 return c
62
59
63 def _get_readme_data(self, db_repo, default_renderer):
60 def _get_readme_data(self, db_repo, default_renderer):
64 repo_name = db_repo.repo_name
61 repo_name = db_repo.repo_name
65 log.debug('Looking for README file')
62 log.debug('Looking for README file')
66
63
67 @cache_region('long_term')
64 @cache_region('long_term')
68 def _generate_readme(cache_key):
65 def _generate_readme(cache_key):
69 readme_data = None
66 readme_data = None
70 readme_node = None
67 readme_node = None
71 readme_filename = None
68 readme_filename = None
72 commit = self._get_landing_commit_or_none(db_repo)
69 commit = self._get_landing_commit_or_none(db_repo)
73 if commit:
70 if commit:
74 log.debug("Searching for a README file.")
71 log.debug("Searching for a README file.")
75 readme_node = ReadmeFinder(default_renderer).search(commit)
72 readme_node = ReadmeFinder(default_renderer).search(commit)
76 if readme_node:
73 if readme_node:
77 relative_url = h.route_path(
74 relative_urls = {
78 'repo_file_raw', repo_name=repo_name,
75 'raw': h.route_path(
79 commit_id=commit.raw_id, f_path=readme_node.path)
76 'repo_file_raw', repo_name=repo_name,
77 commit_id=commit.raw_id, f_path=readme_node.path),
78 'standard': h.route_path(
79 'repo_files', repo_name=repo_name,
80 commit_id=commit.raw_id, f_path=readme_node.path),
81 }
80 readme_data = self._render_readme_or_none(
82 readme_data = self._render_readme_or_none(
81 commit, readme_node, relative_url)
83 commit, readme_node, relative_urls)
82 readme_filename = readme_node.path
84 readme_filename = readme_node.path
83 return readme_data, readme_filename
85 return readme_data, readme_filename
84
86
85 invalidator_context = CacheKey.repo_context_cache(
87 invalidator_context = CacheKey.repo_context_cache(
86 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
88 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
87
89
88 with invalidator_context as context:
90 with invalidator_context as context:
89 context.invalidate()
91 context.invalidate()
90 computed = context.compute()
92 computed = context.compute()
91
93
92 return computed
94 return computed
93
95
94 def _get_landing_commit_or_none(self, db_repo):
96 def _get_landing_commit_or_none(self, db_repo):
95 log.debug("Getting the landing commit.")
97 log.debug("Getting the landing commit.")
96 try:
98 try:
97 commit = db_repo.get_landing_commit()
99 commit = db_repo.get_landing_commit()
98 if not isinstance(commit, EmptyCommit):
100 if not isinstance(commit, EmptyCommit):
99 return commit
101 return commit
100 else:
102 else:
101 log.debug("Repository is empty, no README to render.")
103 log.debug("Repository is empty, no README to render.")
102 except CommitError:
104 except CommitError:
103 log.exception(
105 log.exception(
104 "Problem getting commit when trying to render the README.")
106 "Problem getting commit when trying to render the README.")
105
107
106 def _render_readme_or_none(self, commit, readme_node, relative_url):
108 def _render_readme_or_none(self, commit, readme_node, relative_urls):
107 log.debug(
109 log.debug(
108 'Found README file `%s` rendering...', readme_node.path)
110 'Found README file `%s` rendering...', readme_node.path)
109 renderer = MarkupRenderer()
111 renderer = MarkupRenderer()
110 try:
112 try:
111 html_source = renderer.render(
113 html_source = renderer.render(
112 readme_node.content, filename=readme_node.path)
114 readme_node.content, filename=readme_node.path)
113 if relative_url:
115 if relative_urls:
114 return relative_links(html_source, relative_url)
116 return relative_links(html_source, relative_urls)
115 return html_source
117 return html_source
116 except Exception:
118 except Exception:
117 log.exception(
119 log.exception(
118 "Exception while trying to render the README")
120 "Exception while trying to render the README")
119
121
120 def _load_commits_context(self, c):
122 def _load_commits_context(self, c):
121 p = safe_int(self.request.GET.get('page'), 1)
123 p = safe_int(self.request.GET.get('page'), 1)
122 size = safe_int(self.request.GET.get('size'), 10)
124 size = safe_int(self.request.GET.get('size'), 10)
123
125
124 def url_generator(**kw):
126 def url_generator(**kw):
125 query_params = {
127 query_params = {
126 'size': size
128 'size': size
127 }
129 }
128 query_params.update(kw)
130 query_params.update(kw)
129 return h.route_path(
131 return h.route_path(
130 'repo_summary_commits',
132 'repo_summary_commits',
131 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
133 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
132
134
133 pre_load = ['author', 'branch', 'date', 'message']
135 pre_load = ['author', 'branch', 'date', 'message']
134 try:
136 try:
135 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
137 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
136 except EmptyRepositoryError:
138 except EmptyRepositoryError:
137 collection = self.rhodecode_vcs_repo
139 collection = self.rhodecode_vcs_repo
138
140
139 c.repo_commits = RepoPage(
141 c.repo_commits = RepoPage(
140 collection, page=p, items_per_page=size, url=url_generator)
142 collection, page=p, items_per_page=size, url=url_generator)
141 page_ids = [x.raw_id for x in c.repo_commits]
143 page_ids = [x.raw_id for x in c.repo_commits]
142 c.comments = self.db_repo.get_comments(page_ids)
144 c.comments = self.db_repo.get_comments(page_ids)
143 c.statuses = self.db_repo.statuses(page_ids)
145 c.statuses = self.db_repo.statuses(page_ids)
144
146
145 @LoginRequired()
147 @LoginRequired()
146 @HasRepoPermissionAnyDecorator(
148 @HasRepoPermissionAnyDecorator(
147 'repository.read', 'repository.write', 'repository.admin')
149 'repository.read', 'repository.write', 'repository.admin')
148 @view_config(
150 @view_config(
149 route_name='repo_summary_commits', request_method='GET',
151 route_name='repo_summary_commits', request_method='GET',
150 renderer='rhodecode:templates/summary/summary_commits.mako')
152 renderer='rhodecode:templates/summary/summary_commits.mako')
151 def summary_commits(self):
153 def summary_commits(self):
152 c = self.load_default_context()
154 c = self.load_default_context()
153 self._load_commits_context(c)
155 self._load_commits_context(c)
154 return self._get_template_context(c)
156 return self._get_template_context(c)
155
157
156 @LoginRequired()
158 @LoginRequired()
157 @HasRepoPermissionAnyDecorator(
159 @HasRepoPermissionAnyDecorator(
158 'repository.read', 'repository.write', 'repository.admin')
160 'repository.read', 'repository.write', 'repository.admin')
159 @view_config(
161 @view_config(
160 route_name='repo_summary', request_method='GET',
162 route_name='repo_summary', request_method='GET',
161 renderer='rhodecode:templates/summary/summary.mako')
163 renderer='rhodecode:templates/summary/summary.mako')
162 @view_config(
164 @view_config(
163 route_name='repo_summary_slash', request_method='GET',
165 route_name='repo_summary_slash', request_method='GET',
164 renderer='rhodecode:templates/summary/summary.mako')
166 renderer='rhodecode:templates/summary/summary.mako')
165 @view_config(
167 @view_config(
166 route_name='repo_summary_explicit', request_method='GET',
168 route_name='repo_summary_explicit', request_method='GET',
167 renderer='rhodecode:templates/summary/summary.mako')
169 renderer='rhodecode:templates/summary/summary.mako')
168 def summary(self):
170 def summary(self):
169 c = self.load_default_context()
171 c = self.load_default_context()
170
172
171 # Prepare the clone URL
173 # Prepare the clone URL
172 username = ''
174 username = ''
173 if self._rhodecode_user.username != User.DEFAULT_USER:
175 if self._rhodecode_user.username != User.DEFAULT_USER:
174 username = safe_str(self._rhodecode_user.username)
176 username = safe_str(self._rhodecode_user.username)
175
177
176 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
178 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
177 if '{repo}' in _def_clone_uri:
179 if '{repo}' in _def_clone_uri:
178 _def_clone_uri_by_id = _def_clone_uri.replace(
180 _def_clone_uri_by_id = _def_clone_uri.replace(
179 '{repo}', '_{repoid}')
181 '{repo}', '_{repoid}')
180 elif '{repoid}' in _def_clone_uri:
182 elif '{repoid}' in _def_clone_uri:
181 _def_clone_uri_by_id = _def_clone_uri.replace(
183 _def_clone_uri_by_id = _def_clone_uri.replace(
182 '_{repoid}', '{repo}')
184 '_{repoid}', '{repo}')
183
185
184 c.clone_repo_url = self.db_repo.clone_url(
186 c.clone_repo_url = self.db_repo.clone_url(
185 user=username, uri_tmpl=_def_clone_uri)
187 user=username, uri_tmpl=_def_clone_uri)
186 c.clone_repo_url_id = self.db_repo.clone_url(
188 c.clone_repo_url_id = self.db_repo.clone_url(
187 user=username, uri_tmpl=_def_clone_uri_by_id)
189 user=username, uri_tmpl=_def_clone_uri_by_id)
188
190
189 # If enabled, get statistics data
191 # If enabled, get statistics data
190
192
191 c.show_stats = bool(self.db_repo.enable_statistics)
193 c.show_stats = bool(self.db_repo.enable_statistics)
192
194
193 stats = Session().query(Statistics) \
195 stats = Session().query(Statistics) \
194 .filter(Statistics.repository == self.db_repo) \
196 .filter(Statistics.repository == self.db_repo) \
195 .scalar()
197 .scalar()
196
198
197 c.stats_percentage = 0
199 c.stats_percentage = 0
198
200
199 if stats and stats.languages:
201 if stats and stats.languages:
200 c.no_data = False is self.db_repo.enable_statistics
202 c.no_data = False is self.db_repo.enable_statistics
201 lang_stats_d = json.loads(stats.languages)
203 lang_stats_d = json.loads(stats.languages)
202
204
203 # Sort first by decreasing count and second by the file extension,
205 # Sort first by decreasing count and second by the file extension,
204 # so we have a consistent output.
206 # so we have a consistent output.
205 lang_stats_items = sorted(lang_stats_d.iteritems(),
207 lang_stats_items = sorted(lang_stats_d.iteritems(),
206 key=lambda k: (-k[1], k[0]))[:10]
208 key=lambda k: (-k[1], k[0]))[:10]
207 lang_stats = [(x, {"count": y,
209 lang_stats = [(x, {"count": y,
208 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
210 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
209 for x, y in lang_stats_items]
211 for x, y in lang_stats_items]
210
212
211 c.trending_languages = json.dumps(lang_stats)
213 c.trending_languages = json.dumps(lang_stats)
212 else:
214 else:
213 c.no_data = True
215 c.no_data = True
214 c.trending_languages = json.dumps({})
216 c.trending_languages = json.dumps({})
215
217
216 scm_model = ScmModel()
218 scm_model = ScmModel()
217 c.enable_downloads = self.db_repo.enable_downloads
219 c.enable_downloads = self.db_repo.enable_downloads
218 c.repository_followers = scm_model.get_followers(self.db_repo)
220 c.repository_followers = scm_model.get_followers(self.db_repo)
219 c.repository_forks = scm_model.get_forks(self.db_repo)
221 c.repository_forks = scm_model.get_forks(self.db_repo)
220 c.repository_is_user_following = scm_model.is_following_repo(
222 c.repository_is_user_following = scm_model.is_following_repo(
221 self.db_repo_name, self._rhodecode_user.user_id)
223 self.db_repo_name, self._rhodecode_user.user_id)
222
224
223 # first interaction with the VCS instance after here...
225 # first interaction with the VCS instance after here...
224 if c.repository_requirements_missing:
226 if c.repository_requirements_missing:
225 self.request.override_renderer = \
227 self.request.override_renderer = \
226 'rhodecode:templates/summary/missing_requirements.mako'
228 'rhodecode:templates/summary/missing_requirements.mako'
227 return self._get_template_context(c)
229 return self._get_template_context(c)
228
230
229 c.readme_data, c.readme_file = \
231 c.readme_data, c.readme_file = \
230 self._get_readme_data(self.db_repo, c.visual.default_renderer)
232 self._get_readme_data(self.db_repo, c.visual.default_renderer)
231
233
232 # loads the summary commits template context
234 # loads the summary commits template context
233 self._load_commits_context(c)
235 self._load_commits_context(c)
234
236
235 return self._get_template_context(c)
237 return self._get_template_context(c)
236
238
237 def get_request_commit_id(self):
239 def get_request_commit_id(self):
238 return self.request.matchdict['commit_id']
240 return self.request.matchdict['commit_id']
239
241
240 @LoginRequired()
242 @LoginRequired()
241 @HasRepoPermissionAnyDecorator(
243 @HasRepoPermissionAnyDecorator(
242 'repository.read', 'repository.write', 'repository.admin')
244 'repository.read', 'repository.write', 'repository.admin')
243 @view_config(
245 @view_config(
244 route_name='repo_stats', request_method='GET',
246 route_name='repo_stats', request_method='GET',
245 renderer='json_ext')
247 renderer='json_ext')
246 def repo_stats(self):
248 def repo_stats(self):
247 commit_id = self.get_request_commit_id()
249 commit_id = self.get_request_commit_id()
248
250
249 _namespace = caches.get_repo_namespace_key(
251 _namespace = caches.get_repo_namespace_key(
250 caches.SUMMARY_STATS, self.db_repo_name)
252 caches.SUMMARY_STATS, self.db_repo_name)
251 show_stats = bool(self.db_repo.enable_statistics)
253 show_stats = bool(self.db_repo.enable_statistics)
252 cache_manager = caches.get_cache_manager(
254 cache_manager = caches.get_cache_manager(
253 'repo_cache_long', _namespace)
255 'repo_cache_long', _namespace)
254 _cache_key = caches.compute_key_from_params(
256 _cache_key = caches.compute_key_from_params(
255 self.db_repo_name, commit_id, show_stats)
257 self.db_repo_name, commit_id, show_stats)
256
258
257 def compute_stats():
259 def compute_stats():
258 code_stats = {}
260 code_stats = {}
259 size = 0
261 size = 0
260 try:
262 try:
261 scm_instance = self.db_repo.scm_instance()
263 scm_instance = self.db_repo.scm_instance()
262 commit = scm_instance.get_commit(commit_id)
264 commit = scm_instance.get_commit(commit_id)
263
265
264 for node in commit.get_filenodes_generator():
266 for node in commit.get_filenodes_generator():
265 size += node.size
267 size += node.size
266 if not show_stats:
268 if not show_stats:
267 continue
269 continue
268 ext = string.lower(node.extension)
270 ext = string.lower(node.extension)
269 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
271 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
270 if ext_info:
272 if ext_info:
271 if ext in code_stats:
273 if ext in code_stats:
272 code_stats[ext]['count'] += 1
274 code_stats[ext]['count'] += 1
273 else:
275 else:
274 code_stats[ext] = {"count": 1, "desc": ext_info}
276 code_stats[ext] = {"count": 1, "desc": ext_info}
275 except EmptyRepositoryError:
277 except EmptyRepositoryError:
276 pass
278 pass
277 return {'size': h.format_byte_size_binary(size),
279 return {'size': h.format_byte_size_binary(size),
278 'code_stats': code_stats}
280 'code_stats': code_stats}
279
281
280 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
282 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
281 return stats
283 return stats
282
284
283 @LoginRequired()
285 @LoginRequired()
284 @HasRepoPermissionAnyDecorator(
286 @HasRepoPermissionAnyDecorator(
285 'repository.read', 'repository.write', 'repository.admin')
287 'repository.read', 'repository.write', 'repository.admin')
286 @view_config(
288 @view_config(
287 route_name='repo_refs_data', request_method='GET',
289 route_name='repo_refs_data', request_method='GET',
288 renderer='json_ext')
290 renderer='json_ext')
289 def repo_refs_data(self):
291 def repo_refs_data(self):
290 _ = self.request.translate
292 _ = self.request.translate
291 self.load_default_context()
293 self.load_default_context()
292
294
293 repo = self.rhodecode_vcs_repo
295 repo = self.rhodecode_vcs_repo
294 refs_to_create = [
296 refs_to_create = [
295 (_("Branch"), repo.branches, 'branch'),
297 (_("Branch"), repo.branches, 'branch'),
296 (_("Tag"), repo.tags, 'tag'),
298 (_("Tag"), repo.tags, 'tag'),
297 (_("Bookmark"), repo.bookmarks, 'book'),
299 (_("Bookmark"), repo.bookmarks, 'book'),
298 ]
300 ]
299 res = self._create_reference_data(
301 res = self._create_reference_data(
300 repo, self.db_repo_name, refs_to_create)
302 repo, self.db_repo_name, refs_to_create)
301 data = {
303 data = {
302 'more': False,
304 'more': False,
303 'results': res
305 'results': res
304 }
306 }
305 return data
307 return data
306
308
307 @LoginRequired()
309 @LoginRequired()
308 @HasRepoPermissionAnyDecorator(
310 @HasRepoPermissionAnyDecorator(
309 'repository.read', 'repository.write', 'repository.admin')
311 'repository.read', 'repository.write', 'repository.admin')
310 @view_config(
312 @view_config(
311 route_name='repo_refs_changelog_data', request_method='GET',
313 route_name='repo_refs_changelog_data', request_method='GET',
312 renderer='json_ext')
314 renderer='json_ext')
313 def repo_refs_changelog_data(self):
315 def repo_refs_changelog_data(self):
314 _ = self.request.translate
316 _ = self.request.translate
315 self.load_default_context()
317 self.load_default_context()
316
318
317 repo = self.rhodecode_vcs_repo
319 repo = self.rhodecode_vcs_repo
318
320
319 refs_to_create = [
321 refs_to_create = [
320 (_("Branches"), repo.branches, 'branch'),
322 (_("Branches"), repo.branches, 'branch'),
321 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
323 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
322 # TODO: enable when vcs can handle bookmarks filters
324 # TODO: enable when vcs can handle bookmarks filters
323 # (_("Bookmarks"), repo.bookmarks, "book"),
325 # (_("Bookmarks"), repo.bookmarks, "book"),
324 ]
326 ]
325 res = self._create_reference_data(
327 res = self._create_reference_data(
326 repo, self.db_repo_name, refs_to_create)
328 repo, self.db_repo_name, refs_to_create)
327 data = {
329 data = {
328 'more': False,
330 'more': False,
329 'results': res
331 'results': res
330 }
332 }
331 return data
333 return data
332
334
333 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
335 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
334 format_ref_id = utils.get_format_ref_id(repo)
336 format_ref_id = utils.get_format_ref_id(repo)
335
337
336 result = []
338 result = []
337 for title, refs, ref_type in refs_to_create:
339 for title, refs, ref_type in refs_to_create:
338 if refs:
340 if refs:
339 result.append({
341 result.append({
340 'text': title,
342 'text': title,
341 'children': self._create_reference_items(
343 'children': self._create_reference_items(
342 repo, full_repo_name, refs, ref_type,
344 repo, full_repo_name, refs, ref_type,
343 format_ref_id),
345 format_ref_id),
344 })
346 })
345 return result
347 return result
346
348
347 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
349 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
348 format_ref_id):
350 format_ref_id):
349 result = []
351 result = []
350 is_svn = h.is_svn(repo)
352 is_svn = h.is_svn(repo)
351 for ref_name, raw_id in refs.iteritems():
353 for ref_name, raw_id in refs.iteritems():
352 files_url = self._create_files_url(
354 files_url = self._create_files_url(
353 repo, full_repo_name, ref_name, raw_id, is_svn)
355 repo, full_repo_name, ref_name, raw_id, is_svn)
354 result.append({
356 result.append({
355 'text': ref_name,
357 'text': ref_name,
356 'id': format_ref_id(ref_name, raw_id),
358 'id': format_ref_id(ref_name, raw_id),
357 'raw_id': raw_id,
359 'raw_id': raw_id,
358 'type': ref_type,
360 'type': ref_type,
359 'files_url': files_url,
361 'files_url': files_url,
360 })
362 })
361 return result
363 return result
362
364
363 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
365 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
364 use_commit_id = '/' in ref_name or is_svn
366 use_commit_id = '/' in ref_name or is_svn
365 return h.route_path(
367 return h.route_path(
366 'repo_files',
368 'repo_files',
367 repo_name=full_repo_name,
369 repo_name=full_repo_name,
368 f_path=ref_name if is_svn else '',
370 f_path=ref_name if is_svn else '',
369 commit_id=raw_id if use_commit_id else ref_name,
371 commit_id=raw_id if use_commit_id else ref_name,
370 _query=dict(at=ref_name))
372 _query=dict(at=ref_name))
@@ -1,180 +1,182 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Pylons environment configuration
22 Pylons environment configuration
23 """
23 """
24
24
25 import os
25 import os
26 import logging
26 import logging
27 import rhodecode
27 import rhodecode
28 import platform
28 import platform
29 import re
29 import re
30 import io
30 import io
31
31
32 from mako.lookup import TemplateLookup
32 from mako.lookup import TemplateLookup
33 from pylons.configuration import PylonsConfig
33 from pylons.configuration import PylonsConfig
34 from pylons.error import handle_mako_error
34 from pylons.error import handle_mako_error
35 from pyramid.settings import asbool
35 from pyramid.settings import asbool
36
36
37 # ------------------------------------------------------------------------------
37 # ------------------------------------------------------------------------------
38 # CELERY magic until refactor - issue #4163 - import order matters here:
38 # CELERY magic until refactor - issue #4163 - import order matters here:
39 from rhodecode.lib import celerypylons # this must be first, celerypylons
39 from rhodecode.lib import celerypylons # this must be first, celerypylons
40 # sets config settings upon import
40 # sets config settings upon import
41
41
42 import rhodecode.integrations # any modules using celery task
42 import rhodecode.integrations # any modules using celery task
43 # decorators should be added afterwards:
43 # decorators should be added afterwards:
44 # ------------------------------------------------------------------------------
44 # ------------------------------------------------------------------------------
45
45
46 from rhodecode.lib import app_globals
46 from rhodecode.lib import app_globals
47 from rhodecode.config import utils
47 from rhodecode.config import utils
48 from rhodecode.config.routing import make_map
48 from rhodecode.config.routing import make_map
49 from rhodecode.config.jsroutes import generate_jsroutes_content
49 from rhodecode.config.jsroutes import generate_jsroutes_content
50
50
51 from rhodecode.lib import helpers
51 from rhodecode.lib import helpers
52 from rhodecode.lib.auth import set_available_permissions
52 from rhodecode.lib.auth import set_available_permissions
53 from rhodecode.lib.utils import (
53 from rhodecode.lib.utils import (
54 repo2db_mapper, make_db_config, set_rhodecode_config,
54 repo2db_mapper, make_db_config, set_rhodecode_config,
55 load_rcextensions)
55 load_rcextensions)
56 from rhodecode.lib.utils2 import str2bool, aslist
56 from rhodecode.lib.utils2 import str2bool, aslist
57 from rhodecode.lib.vcs import connect_vcs, start_vcs_server
57 from rhodecode.lib.vcs import connect_vcs, start_vcs_server
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62 def load_environment(global_conf, app_conf, initial=False,
62 def load_environment(global_conf, app_conf, initial=False,
63 test_env=None, test_index=None):
63 test_env=None, test_index=None):
64 """
64 """
65 Configure the Pylons environment via the ``pylons.config``
65 Configure the Pylons environment via the ``pylons.config``
66 object
66 object
67 """
67 """
68 config = PylonsConfig()
68 config = PylonsConfig()
69
69
70
70
71 # Pylons paths
71 # Pylons paths
72 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
72 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
73 paths = {
73 paths = {
74 'root': root,
74 'root': root,
75 'controllers': os.path.join(root, 'controllers'),
75 'controllers': os.path.join(root, 'controllers'),
76 'static_files': os.path.join(root, 'public'),
76 'static_files': os.path.join(root, 'public'),
77 'templates': [os.path.join(root, 'templates')],
77 'templates': [os.path.join(root, 'templates')],
78 }
78 }
79
79
80 # Initialize config with the basic options
80 # Initialize config with the basic options
81 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
81 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
82
82
83 # store some globals into rhodecode
83 # store some globals into rhodecode
84 rhodecode.CELERY_ENABLED = str2bool(config['app_conf'].get('use_celery'))
84 rhodecode.CELERY_ENABLED = str2bool(config['app_conf'].get('use_celery'))
85 rhodecode.CELERY_EAGER = str2bool(
85 rhodecode.CELERY_EAGER = str2bool(
86 config['app_conf'].get('celery.always.eager'))
86 config['app_conf'].get('celery.always.eager'))
87
87
88 config['routes.map'] = make_map(config)
88 config['routes.map'] = make_map(config)
89
89
90 config['pylons.app_globals'] = app_globals.Globals(config)
90 config['pylons.app_globals'] = app_globals.Globals(config)
91 config['pylons.h'] = helpers
91 config['pylons.h'] = helpers
92 rhodecode.CONFIG = config
92 rhodecode.CONFIG = config
93
93
94 load_rcextensions(root_path=config['here'])
94 load_rcextensions(root_path=config['here'])
95
95
96 # Setup cache object as early as possible
96 # Setup cache object as early as possible
97 import pylons
97 import pylons
98 pylons.cache._push_object(config['pylons.app_globals'].cache)
98 pylons.cache._push_object(config['pylons.app_globals'].cache)
99
99
100 # Create the Mako TemplateLookup, with the default auto-escaping
100 # Create the Mako TemplateLookup, with the default auto-escaping
101 config['pylons.app_globals'].mako_lookup = TemplateLookup(
101 config['pylons.app_globals'].mako_lookup = TemplateLookup(
102 directories=paths['templates'],
102 directories=paths['templates'],
103 error_handler=handle_mako_error,
103 error_handler=handle_mako_error,
104 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
104 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
105 input_encoding='utf-8', default_filters=['escape'],
105 input_encoding='utf-8', default_filters=['escape'],
106 imports=['from webhelpers.html import escape'])
106 imports=['from webhelpers.html import escape'])
107
107
108 # sets the c attribute access when don't existing attribute are accessed
108 # sets the c attribute access when don't existing attribute are accessed
109 config['pylons.strict_tmpl_context'] = True
109 config['pylons.strict_tmpl_context'] = True
110
110
111 # configure channelstream
111 # configure channelstream
112 config['channelstream_config'] = {
112 config['channelstream_config'] = {
113 'enabled': asbool(config.get('channelstream.enabled', False)),
113 'enabled': asbool(config.get('channelstream.enabled', False)),
114 'server': config.get('channelstream.server'),
114 'server': config.get('channelstream.server'),
115 'secret': config.get('channelstream.secret')
115 'secret': config.get('channelstream.secret')
116 }
116 }
117
117
118 set_available_permissions(config)
118 set_available_permissions(config)
119 db_cfg = make_db_config(clear_session=True)
119 db_cfg = make_db_config(clear_session=True)
120
120
121 repos_path = list(db_cfg.items('paths'))[0][1]
121 repos_path = list(db_cfg.items('paths'))[0][1]
122 config['base_path'] = repos_path
122 config['base_path'] = repos_path
123
123
124 # store db config also in main global CONFIG
124 # store db config also in main global CONFIG
125 set_rhodecode_config(config)
125 set_rhodecode_config(config)
126
126
127 # configure instance id
127 # configure instance id
128 utils.set_instance_id(config)
128 utils.set_instance_id(config)
129
129
130 # CONFIGURATION OPTIONS HERE (note: all config options will override
130 # CONFIGURATION OPTIONS HERE (note: all config options will override
131 # any Pylons config options)
131 # any Pylons config options)
132
132
133 # store config reference into our module to skip import magic of pylons
133 # store config reference into our module to skip import magic of pylons
134 rhodecode.CONFIG.update(config)
134 rhodecode.CONFIG.update(config)
135
135
136 return config
136 return config
137
137
138
138
139 def load_pyramid_environment(global_config, settings):
139 def load_pyramid_environment(global_config, settings):
140 # Some parts of the code expect a merge of global and app settings.
140 # Some parts of the code expect a merge of global and app settings.
141 settings_merged = global_config.copy()
141 settings_merged = global_config.copy()
142 settings_merged.update(settings)
142 settings_merged.update(settings)
143
143
144 # Store the settings to make them available to other modules.
144 # Store the settings to make them available to other modules.
145 rhodecode.PYRAMID_SETTINGS = settings_merged
145 rhodecode.PYRAMID_SETTINGS = settings_merged
146 # NOTE(marcink): needs to be enabled after full port to pyramid
147 # rhodecode.CONFIG = config
146
148
147 # If this is a test run we prepare the test environment like
149 # If this is a test run we prepare the test environment like
148 # creating a test database, test search index and test repositories.
150 # creating a test database, test search index and test repositories.
149 # This has to be done before the database connection is initialized.
151 # This has to be done before the database connection is initialized.
150 if settings['is_test']:
152 if settings['is_test']:
151 rhodecode.is_test = True
153 rhodecode.is_test = True
152 rhodecode.disable_error_handler = True
154 rhodecode.disable_error_handler = True
153
155
154 utils.initialize_test_environment(settings_merged)
156 utils.initialize_test_environment(settings_merged)
155
157
156 # Initialize the database connection.
158 # Initialize the database connection.
157 utils.initialize_database(settings_merged)
159 utils.initialize_database(settings_merged)
158
160
159 # Limit backends to `vcs.backends` from configuration
161 # Limit backends to `vcs.backends` from configuration
160 for alias in rhodecode.BACKENDS.keys():
162 for alias in rhodecode.BACKENDS.keys():
161 if alias not in settings['vcs.backends']:
163 if alias not in settings['vcs.backends']:
162 del rhodecode.BACKENDS[alias]
164 del rhodecode.BACKENDS[alias]
163 log.info('Enabled VCS backends: %s', rhodecode.BACKENDS.keys())
165 log.info('Enabled VCS backends: %s', rhodecode.BACKENDS.keys())
164
166
165 # initialize vcs client and optionally run the server if enabled
167 # initialize vcs client and optionally run the server if enabled
166 vcs_server_uri = settings['vcs.server']
168 vcs_server_uri = settings['vcs.server']
167 vcs_server_enabled = settings['vcs.server.enable']
169 vcs_server_enabled = settings['vcs.server.enable']
168 start_server = (
170 start_server = (
169 settings['vcs.start_server'] and
171 settings['vcs.start_server'] and
170 not int(os.environ.get('RC_VCSSERVER_TEST_DISABLE', '0')))
172 not int(os.environ.get('RC_VCSSERVER_TEST_DISABLE', '0')))
171
173
172 if vcs_server_enabled and start_server:
174 if vcs_server_enabled and start_server:
173 log.info("Starting vcsserver")
175 log.info("Starting vcsserver")
174 start_vcs_server(server_and_port=vcs_server_uri,
176 start_vcs_server(server_and_port=vcs_server_uri,
175 protocol=utils.get_vcs_server_protocol(settings),
177 protocol=utils.get_vcs_server_protocol(settings),
176 log_level=settings['vcs.server.log_level'])
178 log_level=settings['vcs.server.log_level'])
177
179
178 utils.configure_vcs(settings)
180 utils.configure_vcs(settings)
179 if vcs_server_enabled:
181 if vcs_server_enabled:
180 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(settings))
182 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(settings))
@@ -1,486 +1,393 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 # prefix for non repository related links needs to be prefixed with `/`
35 # prefix for non repository related links needs to be prefixed with `/`
36 ADMIN_PREFIX = '/_admin'
36 ADMIN_PREFIX = '/_admin'
37 STATIC_FILE_PREFIX = '/_static'
37 STATIC_FILE_PREFIX = '/_static'
38
38
39 # Default requirements for URL parts
39 # Default requirements for URL parts
40 URL_NAME_REQUIREMENTS = {
40 URL_NAME_REQUIREMENTS = {
41 # group name can have a slash in them, but they must not end with a slash
41 # group name can have a slash in them, but they must not end with a slash
42 'group_name': r'.*?[^/]',
42 'group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
44 # repo names can have a slash in them, but they must not end with a slash
44 # repo names can have a slash in them, but they must not end with a slash
45 'repo_name': r'.*?[^/]',
45 'repo_name': r'.*?[^/]',
46 # file path eats up everything at the end
46 # file path eats up everything at the end
47 'f_path': r'.*',
47 'f_path': r'.*',
48 # reference types
48 # reference types
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 }
51 }
52
52
53
53
54 class JSRoutesMapper(Mapper):
54 class JSRoutesMapper(Mapper):
55 """
55 """
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 """
57 """
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 def __init__(self, *args, **kw):
60 def __init__(self, *args, **kw):
61 super(JSRoutesMapper, self).__init__(*args, **kw)
61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 self._jsroutes = []
62 self._jsroutes = []
63
63
64 def connect(self, *args, **kw):
64 def connect(self, *args, **kw):
65 """
65 """
66 Wrapper for connect to take an extra argument jsroute=True
66 Wrapper for connect to take an extra argument jsroute=True
67
67
68 :param jsroute: boolean, if True will add the route to the pyroutes list
68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 """
69 """
70 if kw.pop('jsroute', False):
70 if kw.pop('jsroute', False):
71 if not self._named_route_regex.match(args[0]):
71 if not self._named_route_regex.match(args[0]):
72 raise Exception('only named routes can be added to pyroutes')
72 raise Exception('only named routes can be added to pyroutes')
73 self._jsroutes.append(args[0])
73 self._jsroutes.append(args[0])
74
74
75 super(JSRoutesMapper, self).connect(*args, **kw)
75 super(JSRoutesMapper, self).connect(*args, **kw)
76
76
77 def _extract_route_information(self, route):
77 def _extract_route_information(self, route):
78 """
78 """
79 Convert a route into tuple(name, path, args), eg:
79 Convert a route into tuple(name, path, args), eg:
80 ('show_user', '/profile/%(username)s', ['username'])
80 ('show_user', '/profile/%(username)s', ['username'])
81 """
81 """
82 routepath = route.routepath
82 routepath = route.routepath
83 def replace(matchobj):
83 def replace(matchobj):
84 if matchobj.group(1):
84 if matchobj.group(1):
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 else:
86 else:
87 return "%%(%s)s" % matchobj.group(2)
87 return "%%(%s)s" % matchobj.group(2)
88
88
89 routepath = self._argument_prog.sub(replace, routepath)
89 routepath = self._argument_prog.sub(replace, routepath)
90 return (
90 return (
91 route.name,
91 route.name,
92 routepath,
92 routepath,
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 for arg in self._argument_prog.findall(route.routepath)]
94 for arg in self._argument_prog.findall(route.routepath)]
95 )
95 )
96
96
97 def jsroutes(self):
97 def jsroutes(self):
98 """
98 """
99 Return a list of pyroutes.js compatible routes
99 Return a list of pyroutes.js compatible routes
100 """
100 """
101 for route_name in self._jsroutes:
101 for route_name in self._jsroutes:
102 yield self._extract_route_information(self._routenames[route_name])
102 yield self._extract_route_information(self._routenames[route_name])
103
103
104
104
105 def make_map(config):
105 def make_map(config):
106 """Create, configure and return the routes Mapper"""
106 """Create, configure and return the routes Mapper"""
107 rmap = JSRoutesMapper(
107 rmap = JSRoutesMapper(
108 directory=config['pylons.paths']['controllers'],
108 directory=config['pylons.paths']['controllers'],
109 always_scan=config['debug'])
109 always_scan=config['debug'])
110 rmap.minimization = False
110 rmap.minimization = False
111 rmap.explicit = False
111 rmap.explicit = False
112
112
113 from rhodecode.lib.utils2 import str2bool
113 from rhodecode.lib.utils2 import str2bool
114 from rhodecode.model import repo, repo_group
114 from rhodecode.model import repo, repo_group
115
115
116 def check_repo(environ, match_dict):
116 def check_repo(environ, match_dict):
117 """
117 """
118 check for valid repository for proper 404 handling
118 check for valid repository for proper 404 handling
119
119
120 :param environ:
120 :param environ:
121 :param match_dict:
121 :param match_dict:
122 """
122 """
123 repo_name = match_dict.get('repo_name')
123 repo_name = match_dict.get('repo_name')
124
124
125 if match_dict.get('f_path'):
125 if match_dict.get('f_path'):
126 # fix for multiple initial slashes that causes errors
126 # fix for multiple initial slashes that causes errors
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 repo_model = repo.RepoModel()
128 repo_model = repo.RepoModel()
129 by_name_match = repo_model.get_by_repo_name(repo_name)
129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 # if we match quickly from database, short circuit the operation,
130 # if we match quickly from database, short circuit the operation,
131 # and validate repo based on the type.
131 # and validate repo based on the type.
132 if by_name_match:
132 if by_name_match:
133 return True
133 return True
134
134
135 by_id_match = repo_model.get_repo_by_id(repo_name)
135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 if by_id_match:
136 if by_id_match:
137 repo_name = by_id_match.repo_name
137 repo_name = by_id_match.repo_name
138 match_dict['repo_name'] = repo_name
138 match_dict['repo_name'] = repo_name
139 return True
139 return True
140
140
141 return False
141 return False
142
142
143 def check_group(environ, match_dict):
143 def check_group(environ, match_dict):
144 """
144 """
145 check for valid repository group path for proper 404 handling
145 check for valid repository group path for proper 404 handling
146
146
147 :param environ:
147 :param environ:
148 :param match_dict:
148 :param match_dict:
149 """
149 """
150 repo_group_name = match_dict.get('group_name')
150 repo_group_name = match_dict.get('group_name')
151 repo_group_model = repo_group.RepoGroupModel()
151 repo_group_model = repo_group.RepoGroupModel()
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 if by_name_match:
153 if by_name_match:
154 return True
154 return True
155
155
156 return False
156 return False
157
157
158 def check_user_group(environ, match_dict):
158 def check_user_group(environ, match_dict):
159 """
159 """
160 check for valid user group for proper 404 handling
160 check for valid user group for proper 404 handling
161
161
162 :param environ:
162 :param environ:
163 :param match_dict:
163 :param match_dict:
164 """
164 """
165 return True
165 return True
166
166
167 def check_int(environ, match_dict):
167 def check_int(environ, match_dict):
168 return match_dict.get('id').isdigit()
168 return match_dict.get('id').isdigit()
169
169
170
170
171 #==========================================================================
171 #==========================================================================
172 # CUSTOM ROUTES HERE
172 # CUSTOM ROUTES HERE
173 #==========================================================================
173 #==========================================================================
174
174
175 # ping and pylons error test
175 # ping and pylons error test
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
178
178
179 # ADMIN REPOSITORY ROUTES
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 controller='admin/repos') as m:
182 m.connect('repos', '/repos',
183 action='create', conditions={'method': ['POST']})
184 m.connect('repos', '/repos',
185 action='index', conditions={'method': ['GET']})
186 m.connect('new_repo', '/create_repository', jsroute=True,
187 action='create_repository', conditions={'method': ['GET']})
188 m.connect('delete_repo', '/repos/{repo_name}',
189 action='delete', conditions={'method': ['DELETE']},
190 requirements=URL_NAME_REQUIREMENTS)
191 m.connect('repo', '/repos/{repo_name}',
192 action='show', conditions={'method': ['GET'],
193 'function': check_repo},
194 requirements=URL_NAME_REQUIREMENTS)
195
196 # ADMIN REPOSITORY GROUPS ROUTES
179 # ADMIN REPOSITORY GROUPS ROUTES
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
198 controller='admin/repo_groups') as m:
181 controller='admin/repo_groups') as m:
199 m.connect('repo_groups', '/repo_groups',
182 m.connect('repo_groups', '/repo_groups',
200 action='create', conditions={'method': ['POST']})
183 action='create', conditions={'method': ['POST']})
201 m.connect('repo_groups', '/repo_groups',
184 m.connect('repo_groups', '/repo_groups',
202 action='index', conditions={'method': ['GET']})
185 action='index', conditions={'method': ['GET']})
203 m.connect('new_repo_group', '/repo_groups/new',
186 m.connect('new_repo_group', '/repo_groups/new',
204 action='new', conditions={'method': ['GET']})
187 action='new', conditions={'method': ['GET']})
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
188 m.connect('update_repo_group', '/repo_groups/{group_name}',
206 action='update', conditions={'method': ['PUT'],
189 action='update', conditions={'method': ['PUT'],
207 'function': check_group},
190 'function': check_group},
208 requirements=URL_NAME_REQUIREMENTS)
191 requirements=URL_NAME_REQUIREMENTS)
209
192
210 # EXTRAS REPO GROUP ROUTES
193 # EXTRAS REPO GROUP ROUTES
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
194 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
212 action='edit',
195 action='edit',
213 conditions={'method': ['GET'], 'function': check_group},
196 conditions={'method': ['GET'], 'function': check_group},
214 requirements=URL_NAME_REQUIREMENTS)
197 requirements=URL_NAME_REQUIREMENTS)
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
198 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
216 action='edit',
199 action='edit',
217 conditions={'method': ['PUT'], 'function': check_group},
200 conditions={'method': ['PUT'], 'function': check_group},
218 requirements=URL_NAME_REQUIREMENTS)
201 requirements=URL_NAME_REQUIREMENTS)
219
202
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
203 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
221 action='edit_repo_group_advanced',
204 action='edit_repo_group_advanced',
222 conditions={'method': ['GET'], 'function': check_group},
205 conditions={'method': ['GET'], 'function': check_group},
223 requirements=URL_NAME_REQUIREMENTS)
206 requirements=URL_NAME_REQUIREMENTS)
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
207 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
225 action='edit_repo_group_advanced',
208 action='edit_repo_group_advanced',
226 conditions={'method': ['PUT'], 'function': check_group},
209 conditions={'method': ['PUT'], 'function': check_group},
227 requirements=URL_NAME_REQUIREMENTS)
210 requirements=URL_NAME_REQUIREMENTS)
228
211
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
212 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
230 action='edit_repo_group_perms',
213 action='edit_repo_group_perms',
231 conditions={'method': ['GET'], 'function': check_group},
214 conditions={'method': ['GET'], 'function': check_group},
232 requirements=URL_NAME_REQUIREMENTS)
215 requirements=URL_NAME_REQUIREMENTS)
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
216 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
234 action='update_perms',
217 action='update_perms',
235 conditions={'method': ['PUT'], 'function': check_group},
218 conditions={'method': ['PUT'], 'function': check_group},
236 requirements=URL_NAME_REQUIREMENTS)
219 requirements=URL_NAME_REQUIREMENTS)
237
220
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
221 m.connect('delete_repo_group', '/repo_groups/{group_name}',
239 action='delete', conditions={'method': ['DELETE'],
222 action='delete', conditions={'method': ['DELETE'],
240 'function': check_group},
223 'function': check_group},
241 requirements=URL_NAME_REQUIREMENTS)
224 requirements=URL_NAME_REQUIREMENTS)
242
225
243 # ADMIN USER ROUTES
226 # ADMIN USER ROUTES
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 controller='admin/users') as m:
228 controller='admin/users') as m:
246 m.connect('users', '/users',
229 m.connect('users', '/users',
247 action='create', conditions={'method': ['POST']})
230 action='create', conditions={'method': ['POST']})
248 m.connect('new_user', '/users/new',
231 m.connect('new_user', '/users/new',
249 action='new', conditions={'method': ['GET']})
232 action='new', conditions={'method': ['GET']})
250 m.connect('update_user', '/users/{user_id}',
233 m.connect('update_user', '/users/{user_id}',
251 action='update', conditions={'method': ['PUT']})
234 action='update', conditions={'method': ['PUT']})
252 m.connect('delete_user', '/users/{user_id}',
235 m.connect('delete_user', '/users/{user_id}',
253 action='delete', conditions={'method': ['DELETE']})
236 action='delete', conditions={'method': ['DELETE']})
254 m.connect('edit_user', '/users/{user_id}/edit',
237 m.connect('edit_user', '/users/{user_id}/edit',
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
238 action='edit', conditions={'method': ['GET']}, jsroute=True)
256 m.connect('user', '/users/{user_id}',
239 m.connect('user', '/users/{user_id}',
257 action='show', conditions={'method': ['GET']})
240 action='show', conditions={'method': ['GET']})
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
241 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
259 action='reset_password', conditions={'method': ['POST']})
242 action='reset_password', conditions={'method': ['POST']})
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
243 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
261 action='create_personal_repo_group', conditions={'method': ['POST']})
244 action='create_personal_repo_group', conditions={'method': ['POST']})
262
245
263 # EXTRAS USER ROUTES
246 # EXTRAS USER ROUTES
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
247 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
265 action='edit_advanced', conditions={'method': ['GET']})
248 action='edit_advanced', conditions={'method': ['GET']})
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
249 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
267 action='update_advanced', conditions={'method': ['PUT']})
250 action='update_advanced', conditions={'method': ['PUT']})
268
251
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
252 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
270 action='edit_global_perms', conditions={'method': ['GET']})
253 action='edit_global_perms', conditions={'method': ['GET']})
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
254 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
272 action='update_global_perms', conditions={'method': ['PUT']})
255 action='update_global_perms', conditions={'method': ['PUT']})
273
256
274 # ADMIN USER GROUPS REST ROUTES
257 # ADMIN USER GROUPS REST ROUTES
275 with rmap.submapper(path_prefix=ADMIN_PREFIX,
258 with rmap.submapper(path_prefix=ADMIN_PREFIX,
276 controller='admin/user_groups') as m:
259 controller='admin/user_groups') as m:
277 m.connect('users_groups', '/user_groups',
260 m.connect('users_groups', '/user_groups',
278 action='create', conditions={'method': ['POST']})
261 action='create', conditions={'method': ['POST']})
279 m.connect('new_users_group', '/user_groups/new',
262 m.connect('new_users_group', '/user_groups/new',
280 action='new', conditions={'method': ['GET']})
263 action='new', conditions={'method': ['GET']})
281 m.connect('update_users_group', '/user_groups/{user_group_id}',
264 m.connect('update_users_group', '/user_groups/{user_group_id}',
282 action='update', conditions={'method': ['PUT']})
265 action='update', conditions={'method': ['PUT']})
283 m.connect('delete_users_group', '/user_groups/{user_group_id}',
266 m.connect('delete_users_group', '/user_groups/{user_group_id}',
284 action='delete', conditions={'method': ['DELETE']})
267 action='delete', conditions={'method': ['DELETE']})
285 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
268 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
286 action='edit', conditions={'method': ['GET']},
269 action='edit', conditions={'method': ['GET']},
287 function=check_user_group)
270 function=check_user_group)
288
271
289 # EXTRAS USER GROUP ROUTES
272 # EXTRAS USER GROUP ROUTES
290 m.connect('edit_user_group_global_perms',
273 m.connect('edit_user_group_global_perms',
291 '/user_groups/{user_group_id}/edit/global_permissions',
274 '/user_groups/{user_group_id}/edit/global_permissions',
292 action='edit_global_perms', conditions={'method': ['GET']})
275 action='edit_global_perms', conditions={'method': ['GET']})
293 m.connect('edit_user_group_global_perms',
276 m.connect('edit_user_group_global_perms',
294 '/user_groups/{user_group_id}/edit/global_permissions',
277 '/user_groups/{user_group_id}/edit/global_permissions',
295 action='update_global_perms', conditions={'method': ['PUT']})
278 action='update_global_perms', conditions={'method': ['PUT']})
296
279
297 m.connect('edit_user_group_perms',
280 m.connect('edit_user_group_perms',
298 '/user_groups/{user_group_id}/edit/permissions',
281 '/user_groups/{user_group_id}/edit/permissions',
299 action='edit_perms', conditions={'method': ['GET']})
282 action='edit_perms', conditions={'method': ['GET']})
300 m.connect('edit_user_group_perms',
283 m.connect('edit_user_group_perms',
301 '/user_groups/{user_group_id}/edit/permissions',
284 '/user_groups/{user_group_id}/edit/permissions',
302 action='update_perms', conditions={'method': ['PUT']})
285 action='update_perms', conditions={'method': ['PUT']})
303
286
304 m.connect('edit_user_group_advanced',
287 m.connect('edit_user_group_advanced',
305 '/user_groups/{user_group_id}/edit/advanced',
288 '/user_groups/{user_group_id}/edit/advanced',
306 action='edit_advanced', conditions={'method': ['GET']})
289 action='edit_advanced', conditions={'method': ['GET']})
307
290
308 m.connect('edit_user_group_advanced_sync',
291 m.connect('edit_user_group_advanced_sync',
309 '/user_groups/{user_group_id}/edit/advanced/sync',
292 '/user_groups/{user_group_id}/edit/advanced/sync',
310 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
293 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
311
294
312 # ADMIN DEFAULTS REST ROUTES
295 # ADMIN DEFAULTS REST ROUTES
313 with rmap.submapper(path_prefix=ADMIN_PREFIX,
296 with rmap.submapper(path_prefix=ADMIN_PREFIX,
314 controller='admin/defaults') as m:
297 controller='admin/defaults') as m:
315 m.connect('admin_defaults_repositories', '/defaults/repositories',
298 m.connect('admin_defaults_repositories', '/defaults/repositories',
316 action='update_repository_defaults', conditions={'method': ['POST']})
299 action='update_repository_defaults', conditions={'method': ['POST']})
317 m.connect('admin_defaults_repositories', '/defaults/repositories',
300 m.connect('admin_defaults_repositories', '/defaults/repositories',
318 action='index', conditions={'method': ['GET']})
301 action='index', conditions={'method': ['GET']})
319
302
320 # ADMIN SETTINGS ROUTES
303 # ADMIN SETTINGS ROUTES
321 with rmap.submapper(path_prefix=ADMIN_PREFIX,
304 with rmap.submapper(path_prefix=ADMIN_PREFIX,
322 controller='admin/settings') as m:
305 controller='admin/settings') as m:
323
306
324 # default
307 # default
325 m.connect('admin_settings', '/settings',
308 m.connect('admin_settings', '/settings',
326 action='settings_global_update',
309 action='settings_global_update',
327 conditions={'method': ['POST']})
310 conditions={'method': ['POST']})
328 m.connect('admin_settings', '/settings',
311 m.connect('admin_settings', '/settings',
329 action='settings_global', conditions={'method': ['GET']})
312 action='settings_global', conditions={'method': ['GET']})
330
313
331 m.connect('admin_settings_vcs', '/settings/vcs',
314 m.connect('admin_settings_vcs', '/settings/vcs',
332 action='settings_vcs_update',
315 action='settings_vcs_update',
333 conditions={'method': ['POST']})
316 conditions={'method': ['POST']})
334 m.connect('admin_settings_vcs', '/settings/vcs',
317 m.connect('admin_settings_vcs', '/settings/vcs',
335 action='settings_vcs',
318 action='settings_vcs',
336 conditions={'method': ['GET']})
319 conditions={'method': ['GET']})
337 m.connect('admin_settings_vcs', '/settings/vcs',
320 m.connect('admin_settings_vcs', '/settings/vcs',
338 action='delete_svn_pattern',
321 action='delete_svn_pattern',
339 conditions={'method': ['DELETE']})
322 conditions={'method': ['DELETE']})
340
323
341 m.connect('admin_settings_mapping', '/settings/mapping',
324 m.connect('admin_settings_mapping', '/settings/mapping',
342 action='settings_mapping_update',
325 action='settings_mapping_update',
343 conditions={'method': ['POST']})
326 conditions={'method': ['POST']})
344 m.connect('admin_settings_mapping', '/settings/mapping',
327 m.connect('admin_settings_mapping', '/settings/mapping',
345 action='settings_mapping', conditions={'method': ['GET']})
328 action='settings_mapping', conditions={'method': ['GET']})
346
329
347 m.connect('admin_settings_global', '/settings/global',
330 m.connect('admin_settings_global', '/settings/global',
348 action='settings_global_update',
331 action='settings_global_update',
349 conditions={'method': ['POST']})
332 conditions={'method': ['POST']})
350 m.connect('admin_settings_global', '/settings/global',
333 m.connect('admin_settings_global', '/settings/global',
351 action='settings_global', conditions={'method': ['GET']})
334 action='settings_global', conditions={'method': ['GET']})
352
335
353 m.connect('admin_settings_visual', '/settings/visual',
336 m.connect('admin_settings_visual', '/settings/visual',
354 action='settings_visual_update',
337 action='settings_visual_update',
355 conditions={'method': ['POST']})
338 conditions={'method': ['POST']})
356 m.connect('admin_settings_visual', '/settings/visual',
339 m.connect('admin_settings_visual', '/settings/visual',
357 action='settings_visual', conditions={'method': ['GET']})
340 action='settings_visual', conditions={'method': ['GET']})
358
341
359 m.connect('admin_settings_issuetracker',
342 m.connect('admin_settings_issuetracker',
360 '/settings/issue-tracker', action='settings_issuetracker',
343 '/settings/issue-tracker', action='settings_issuetracker',
361 conditions={'method': ['GET']})
344 conditions={'method': ['GET']})
362 m.connect('admin_settings_issuetracker_save',
345 m.connect('admin_settings_issuetracker_save',
363 '/settings/issue-tracker/save',
346 '/settings/issue-tracker/save',
364 action='settings_issuetracker_save',
347 action='settings_issuetracker_save',
365 conditions={'method': ['POST']})
348 conditions={'method': ['POST']})
366 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
349 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
367 action='settings_issuetracker_test',
350 action='settings_issuetracker_test',
368 conditions={'method': ['POST']})
351 conditions={'method': ['POST']})
369 m.connect('admin_issuetracker_delete',
352 m.connect('admin_issuetracker_delete',
370 '/settings/issue-tracker/delete',
353 '/settings/issue-tracker/delete',
371 action='settings_issuetracker_delete',
354 action='settings_issuetracker_delete',
372 conditions={'method': ['DELETE']})
355 conditions={'method': ['DELETE']})
373
356
374 m.connect('admin_settings_email', '/settings/email',
357 m.connect('admin_settings_email', '/settings/email',
375 action='settings_email_update',
358 action='settings_email_update',
376 conditions={'method': ['POST']})
359 conditions={'method': ['POST']})
377 m.connect('admin_settings_email', '/settings/email',
360 m.connect('admin_settings_email', '/settings/email',
378 action='settings_email', conditions={'method': ['GET']})
361 action='settings_email', conditions={'method': ['GET']})
379
362
380 m.connect('admin_settings_hooks', '/settings/hooks',
363 m.connect('admin_settings_hooks', '/settings/hooks',
381 action='settings_hooks_update',
364 action='settings_hooks_update',
382 conditions={'method': ['POST', 'DELETE']})
365 conditions={'method': ['POST', 'DELETE']})
383 m.connect('admin_settings_hooks', '/settings/hooks',
366 m.connect('admin_settings_hooks', '/settings/hooks',
384 action='settings_hooks', conditions={'method': ['GET']})
367 action='settings_hooks', conditions={'method': ['GET']})
385
368
386 m.connect('admin_settings_search', '/settings/search',
369 m.connect('admin_settings_search', '/settings/search',
387 action='settings_search', conditions={'method': ['GET']})
370 action='settings_search', conditions={'method': ['GET']})
388
371
389 m.connect('admin_settings_supervisor', '/settings/supervisor',
372 m.connect('admin_settings_supervisor', '/settings/supervisor',
390 action='settings_supervisor', conditions={'method': ['GET']})
373 action='settings_supervisor', conditions={'method': ['GET']})
391 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
374 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
392 action='settings_supervisor_log', conditions={'method': ['GET']})
375 action='settings_supervisor_log', conditions={'method': ['GET']})
393
376
394 m.connect('admin_settings_labs', '/settings/labs',
377 m.connect('admin_settings_labs', '/settings/labs',
395 action='settings_labs_update',
378 action='settings_labs_update',
396 conditions={'method': ['POST']})
379 conditions={'method': ['POST']})
397 m.connect('admin_settings_labs', '/settings/labs',
380 m.connect('admin_settings_labs', '/settings/labs',
398 action='settings_labs', conditions={'method': ['GET']})
381 action='settings_labs', conditions={'method': ['GET']})
399
382
400 # ADMIN MY ACCOUNT
383 # ADMIN MY ACCOUNT
401 with rmap.submapper(path_prefix=ADMIN_PREFIX,
384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
402 controller='admin/my_account') as m:
385 controller='admin/my_account') as m:
403
386
404 # NOTE(marcink): this needs to be kept for password force flag to be
387 # NOTE(marcink): this needs to be kept for password force flag to be
405 # handled in pylons controllers, remove after full migration to pyramid
388 # handled in pylons controllers, remove after full migration to pyramid
406 m.connect('my_account_password', '/my_account/password',
389 m.connect('my_account_password', '/my_account/password',
407 action='my_account_password', conditions={'method': ['GET']})
390 action='my_account_password', conditions={'method': ['GET']})
408
391
409 #==========================================================================
410 # REPOSITORY ROUTES
411 #==========================================================================
412
413 # repo edit options
414 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
415 controller='admin/repos', action='edit_fields',
416 conditions={'method': ['GET'], 'function': check_repo},
417 requirements=URL_NAME_REQUIREMENTS)
418 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
419 controller='admin/repos', action='create_repo_field',
420 conditions={'method': ['PUT'], 'function': check_repo},
421 requirements=URL_NAME_REQUIREMENTS)
422 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
423 controller='admin/repos', action='delete_repo_field',
424 conditions={'method': ['DELETE'], 'function': check_repo},
425 requirements=URL_NAME_REQUIREMENTS)
426
427 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
428 controller='admin/repos', action='toggle_locking',
429 conditions={'method': ['GET'], 'function': check_repo},
430 requirements=URL_NAME_REQUIREMENTS)
431
432 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
433 controller='admin/repos', action='edit_remote_form',
434 conditions={'method': ['GET'], 'function': check_repo},
435 requirements=URL_NAME_REQUIREMENTS)
436 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
437 controller='admin/repos', action='edit_remote',
438 conditions={'method': ['PUT'], 'function': check_repo},
439 requirements=URL_NAME_REQUIREMENTS)
440
441 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
442 controller='admin/repos', action='edit_statistics_form',
443 conditions={'method': ['GET'], 'function': check_repo},
444 requirements=URL_NAME_REQUIREMENTS)
445 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
446 controller='admin/repos', action='edit_statistics',
447 conditions={'method': ['PUT'], 'function': check_repo},
448 requirements=URL_NAME_REQUIREMENTS)
449 rmap.connect('repo_settings_issuetracker',
450 '/{repo_name}/settings/issue-tracker',
451 controller='admin/repos', action='repo_issuetracker',
452 conditions={'method': ['GET'], 'function': check_repo},
453 requirements=URL_NAME_REQUIREMENTS)
454 rmap.connect('repo_issuetracker_test',
455 '/{repo_name}/settings/issue-tracker/test',
456 controller='admin/repos', action='repo_issuetracker_test',
457 conditions={'method': ['POST'], 'function': check_repo},
458 requirements=URL_NAME_REQUIREMENTS)
459 rmap.connect('repo_issuetracker_delete',
460 '/{repo_name}/settings/issue-tracker/delete',
461 controller='admin/repos', action='repo_issuetracker_delete',
462 conditions={'method': ['DELETE'], 'function': check_repo},
463 requirements=URL_NAME_REQUIREMENTS)
464 rmap.connect('repo_issuetracker_save',
465 '/{repo_name}/settings/issue-tracker/save',
466 controller='admin/repos', action='repo_issuetracker_save',
467 conditions={'method': ['POST'], 'function': check_repo},
468 requirements=URL_NAME_REQUIREMENTS)
469 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
470 controller='admin/repos', action='repo_settings_vcs_update',
471 conditions={'method': ['POST'], 'function': check_repo},
472 requirements=URL_NAME_REQUIREMENTS)
473 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
474 controller='admin/repos', action='repo_settings_vcs',
475 conditions={'method': ['GET'], 'function': check_repo},
476 requirements=URL_NAME_REQUIREMENTS)
477 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
478 controller='admin/repos', action='repo_delete_svn_pattern',
479 conditions={'method': ['DELETE'], 'function': check_repo},
480 requirements=URL_NAME_REQUIREMENTS)
481 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
482 controller='admin/repos', action='repo_settings_pullrequest',
483 conditions={'method': ['GET', 'POST'], 'function': check_repo},
484 requirements=URL_NAME_REQUIREMENTS)
485
392
486 return rmap
393 return rmap
@@ -1,697 +1,697 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 settings controller for rhodecode admin
23 settings controller for rhodecode admin
24 """
24 """
25
25
26 import collections
26 import collections
27 import logging
27 import logging
28
28
29 import datetime
29 import datetime
30 import formencode
30 import formencode
31 from formencode import htmlfill
31 from formencode import htmlfill
32 from pylons import request, tmpl_context as c, url, config
32 from pylons import request, tmpl_context as c, url, config
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from pylons.decorators import jsonify
35 from pyramid.threadlocal import get_current_registry
36 from pyramid.threadlocal import get_current_registry
36 from webob.exc import HTTPBadRequest
37 from webob.exc import HTTPBadRequest
37
38
38 import rhodecode
39 import rhodecode
39 from rhodecode.apps.admin.navigation import navigation_list
40 from rhodecode.apps.admin.navigation import navigation_list
40 from rhodecode.apps.svn_support.config_keys import generate_config
41 from rhodecode.apps.svn_support.config_keys import generate_config
41 from rhodecode.lib import auth
42 from rhodecode.lib import auth
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib import helpers as h
43 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
44 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
44 from rhodecode.lib.base import BaseController, render
45 from rhodecode.lib.base import BaseController, render
45 from rhodecode.lib.celerylib import tasks, run_task
46 from rhodecode.lib.celerylib import tasks, run_task
46 from rhodecode.lib.utils import repo2db_mapper
47 from rhodecode.lib.utils import repo2db_mapper
47 from rhodecode.lib.utils2 import (
48 from rhodecode.lib.utils2 import (
48 str2bool, safe_unicode, AttributeDict, safe_int)
49 str2bool, safe_unicode, AttributeDict, safe_int)
49 from rhodecode.lib.compat import OrderedDict
50 from rhodecode.lib.compat import OrderedDict
50 from rhodecode.lib.utils import jsonify
51
51
52 from rhodecode.model.db import RhodeCodeUi, Repository
52 from rhodecode.model.db import RhodeCodeUi, Repository
53 from rhodecode.model.forms import ApplicationSettingsForm, \
53 from rhodecode.model.forms import ApplicationSettingsForm, \
54 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
54 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
55 LabsSettingsForm, IssueTrackerPatternsForm
55 LabsSettingsForm, IssueTrackerPatternsForm
56 from rhodecode.model.repo_group import RepoGroupModel
56 from rhodecode.model.repo_group import RepoGroupModel
57
57
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
59 from rhodecode.model.notification import EmailNotificationModel
59 from rhodecode.model.notification import EmailNotificationModel
60 from rhodecode.model.meta import Session
60 from rhodecode.model.meta import Session
61 from rhodecode.model.settings import (
61 from rhodecode.model.settings import (
62 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
62 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
63 SettingsModel)
63 SettingsModel)
64
64
65 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
65 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
66
66
67
67
68 log = logging.getLogger(__name__)
68 log = logging.getLogger(__name__)
69
69
70
70
71 class SettingsController(BaseController):
71 class SettingsController(BaseController):
72 """REST Controller styled on the Atom Publishing Protocol"""
72 """REST Controller styled on the Atom Publishing Protocol"""
73 # To properly map this controller, ensure your config/routing.py
73 # To properly map this controller, ensure your config/routing.py
74 # file has a resource setup:
74 # file has a resource setup:
75 # map.resource('setting', 'settings', controller='admin/settings',
75 # map.resource('setting', 'settings', controller='admin/settings',
76 # path_prefix='/admin', name_prefix='admin_')
76 # path_prefix='/admin', name_prefix='admin_')
77
77
78 @LoginRequired()
78 @LoginRequired()
79 def __before__(self):
79 def __before__(self):
80 super(SettingsController, self).__before__()
80 super(SettingsController, self).__before__()
81 c.labs_active = str2bool(
81 c.labs_active = str2bool(
82 rhodecode.CONFIG.get('labs_settings_active', 'true'))
82 rhodecode.CONFIG.get('labs_settings_active', 'true'))
83 c.navlist = navigation_list(request)
83 c.navlist = navigation_list(request)
84
84
85 def _get_ui_settings(self):
85 def _get_ui_settings(self):
86 ret = RhodeCodeUi.query().all()
86 ret = RhodeCodeUi.query().all()
87
87
88 if not ret:
88 if not ret:
89 raise Exception('Could not get application ui settings !')
89 raise Exception('Could not get application ui settings !')
90 settings = {}
90 settings = {}
91 for each in ret:
91 for each in ret:
92 k = each.ui_key
92 k = each.ui_key
93 v = each.ui_value
93 v = each.ui_value
94 if k == '/':
94 if k == '/':
95 k = 'root_path'
95 k = 'root_path'
96
96
97 if k in ['push_ssl', 'publish', 'enabled']:
97 if k in ['push_ssl', 'publish', 'enabled']:
98 v = str2bool(v)
98 v = str2bool(v)
99
99
100 if k.find('.') != -1:
100 if k.find('.') != -1:
101 k = k.replace('.', '_')
101 k = k.replace('.', '_')
102
102
103 if each.ui_section in ['hooks', 'extensions']:
103 if each.ui_section in ['hooks', 'extensions']:
104 v = each.ui_active
104 v = each.ui_active
105
105
106 settings[each.ui_section + '_' + k] = v
106 settings[each.ui_section + '_' + k] = v
107 return settings
107 return settings
108
108
109 @HasPermissionAllDecorator('hg.admin')
109 @HasPermissionAllDecorator('hg.admin')
110 @auth.CSRFRequired()
110 @auth.CSRFRequired()
111 @jsonify
111 @jsonify
112 def delete_svn_pattern(self):
112 def delete_svn_pattern(self):
113 if not request.is_xhr:
113 if not request.is_xhr:
114 raise HTTPBadRequest()
114 raise HTTPBadRequest()
115
115
116 delete_pattern_id = request.POST.get('delete_svn_pattern')
116 delete_pattern_id = request.POST.get('delete_svn_pattern')
117 model = VcsSettingsModel()
117 model = VcsSettingsModel()
118 try:
118 try:
119 model.delete_global_svn_pattern(delete_pattern_id)
119 model.delete_global_svn_pattern(delete_pattern_id)
120 except SettingNotFound:
120 except SettingNotFound:
121 raise HTTPBadRequest()
121 raise HTTPBadRequest()
122
122
123 Session().commit()
123 Session().commit()
124 return True
124 return True
125
125
126 @HasPermissionAllDecorator('hg.admin')
126 @HasPermissionAllDecorator('hg.admin')
127 @auth.CSRFRequired()
127 @auth.CSRFRequired()
128 def settings_vcs_update(self):
128 def settings_vcs_update(self):
129 """POST /admin/settings: All items in the collection"""
129 """POST /admin/settings: All items in the collection"""
130 # url('admin_settings_vcs')
130 # url('admin_settings_vcs')
131 c.active = 'vcs'
131 c.active = 'vcs'
132
132
133 model = VcsSettingsModel()
133 model = VcsSettingsModel()
134 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
134 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
135 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
135 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
136
136
137 # TODO: Replace with request.registry after migrating to pyramid.
137 # TODO: Replace with request.registry after migrating to pyramid.
138 pyramid_settings = get_current_registry().settings
138 pyramid_settings = get_current_registry().settings
139 c.svn_proxy_generate_config = pyramid_settings[generate_config]
139 c.svn_proxy_generate_config = pyramid_settings[generate_config]
140
140
141 application_form = ApplicationUiSettingsForm()()
141 application_form = ApplicationUiSettingsForm()()
142
142
143 try:
143 try:
144 form_result = application_form.to_python(dict(request.POST))
144 form_result = application_form.to_python(dict(request.POST))
145 except formencode.Invalid as errors:
145 except formencode.Invalid as errors:
146 h.flash(
146 h.flash(
147 _("Some form inputs contain invalid data."),
147 _("Some form inputs contain invalid data."),
148 category='error')
148 category='error')
149 return htmlfill.render(
149 return htmlfill.render(
150 render('admin/settings/settings.mako'),
150 render('admin/settings/settings.mako'),
151 defaults=errors.value,
151 defaults=errors.value,
152 errors=errors.error_dict or {},
152 errors=errors.error_dict or {},
153 prefix_error=False,
153 prefix_error=False,
154 encoding="UTF-8",
154 encoding="UTF-8",
155 force_defaults=False
155 force_defaults=False
156 )
156 )
157
157
158 try:
158 try:
159 if c.visual.allow_repo_location_change:
159 if c.visual.allow_repo_location_change:
160 model.update_global_path_setting(
160 model.update_global_path_setting(
161 form_result['paths_root_path'])
161 form_result['paths_root_path'])
162
162
163 model.update_global_ssl_setting(form_result['web_push_ssl'])
163 model.update_global_ssl_setting(form_result['web_push_ssl'])
164 model.update_global_hook_settings(form_result)
164 model.update_global_hook_settings(form_result)
165
165
166 model.create_or_update_global_svn_settings(form_result)
166 model.create_or_update_global_svn_settings(form_result)
167 model.create_or_update_global_hg_settings(form_result)
167 model.create_or_update_global_hg_settings(form_result)
168 model.create_or_update_global_git_settings(form_result)
168 model.create_or_update_global_git_settings(form_result)
169 model.create_or_update_global_pr_settings(form_result)
169 model.create_or_update_global_pr_settings(form_result)
170 except Exception:
170 except Exception:
171 log.exception("Exception while updating settings")
171 log.exception("Exception while updating settings")
172 h.flash(_('Error occurred during updating '
172 h.flash(_('Error occurred during updating '
173 'application settings'), category='error')
173 'application settings'), category='error')
174 else:
174 else:
175 Session().commit()
175 Session().commit()
176 h.flash(_('Updated VCS settings'), category='success')
176 h.flash(_('Updated VCS settings'), category='success')
177 return redirect(url('admin_settings_vcs'))
177 return redirect(url('admin_settings_vcs'))
178
178
179 return htmlfill.render(
179 return htmlfill.render(
180 render('admin/settings/settings.mako'),
180 render('admin/settings/settings.mako'),
181 defaults=self._form_defaults(),
181 defaults=self._form_defaults(),
182 encoding="UTF-8",
182 encoding="UTF-8",
183 force_defaults=False)
183 force_defaults=False)
184
184
185 @HasPermissionAllDecorator('hg.admin')
185 @HasPermissionAllDecorator('hg.admin')
186 def settings_vcs(self):
186 def settings_vcs(self):
187 """GET /admin/settings: All items in the collection"""
187 """GET /admin/settings: All items in the collection"""
188 # url('admin_settings_vcs')
188 # url('admin_settings_vcs')
189 c.active = 'vcs'
189 c.active = 'vcs'
190 model = VcsSettingsModel()
190 model = VcsSettingsModel()
191 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
191 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
192 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
192 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
193
193
194 # TODO: Replace with request.registry after migrating to pyramid.
194 # TODO: Replace with request.registry after migrating to pyramid.
195 pyramid_settings = get_current_registry().settings
195 pyramid_settings = get_current_registry().settings
196 c.svn_proxy_generate_config = pyramid_settings[generate_config]
196 c.svn_proxy_generate_config = pyramid_settings[generate_config]
197
197
198 defaults = self._form_defaults()
198 defaults = self._form_defaults()
199
199
200 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
200 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
201 return htmlfill.render(
201 return htmlfill.render(
202 render('admin/settings/settings.mako'),
202 render('admin/settings/settings.mako'),
203 defaults=defaults,
203 defaults=defaults,
204 encoding="UTF-8",
204 encoding="UTF-8",
205 force_defaults=False)
205 force_defaults=False)
206
206
207 @HasPermissionAllDecorator('hg.admin')
207 @HasPermissionAllDecorator('hg.admin')
208 @auth.CSRFRequired()
208 @auth.CSRFRequired()
209 def settings_mapping_update(self):
209 def settings_mapping_update(self):
210 """POST /admin/settings/mapping: All items in the collection"""
210 """POST /admin/settings/mapping: All items in the collection"""
211 # url('admin_settings_mapping')
211 # url('admin_settings_mapping')
212 c.active = 'mapping'
212 c.active = 'mapping'
213 rm_obsolete = request.POST.get('destroy', False)
213 rm_obsolete = request.POST.get('destroy', False)
214 invalidate_cache = request.POST.get('invalidate', False)
214 invalidate_cache = request.POST.get('invalidate', False)
215 log.debug(
215 log.debug(
216 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
216 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
217
217
218 if invalidate_cache:
218 if invalidate_cache:
219 log.debug('invalidating all repositories cache')
219 log.debug('invalidating all repositories cache')
220 for repo in Repository.get_all():
220 for repo in Repository.get_all():
221 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
221 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
222
222
223 filesystem_repos = ScmModel().repo_scan()
223 filesystem_repos = ScmModel().repo_scan()
224 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
224 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
225 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
225 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
226 h.flash(_('Repositories successfully '
226 h.flash(_('Repositories successfully '
227 'rescanned added: %s ; removed: %s') %
227 'rescanned added: %s ; removed: %s') %
228 (_repr(added), _repr(removed)),
228 (_repr(added), _repr(removed)),
229 category='success')
229 category='success')
230 return redirect(url('admin_settings_mapping'))
230 return redirect(url('admin_settings_mapping'))
231
231
232 @HasPermissionAllDecorator('hg.admin')
232 @HasPermissionAllDecorator('hg.admin')
233 def settings_mapping(self):
233 def settings_mapping(self):
234 """GET /admin/settings/mapping: All items in the collection"""
234 """GET /admin/settings/mapping: All items in the collection"""
235 # url('admin_settings_mapping')
235 # url('admin_settings_mapping')
236 c.active = 'mapping'
236 c.active = 'mapping'
237
237
238 return htmlfill.render(
238 return htmlfill.render(
239 render('admin/settings/settings.mako'),
239 render('admin/settings/settings.mako'),
240 defaults=self._form_defaults(),
240 defaults=self._form_defaults(),
241 encoding="UTF-8",
241 encoding="UTF-8",
242 force_defaults=False)
242 force_defaults=False)
243
243
244 @HasPermissionAllDecorator('hg.admin')
244 @HasPermissionAllDecorator('hg.admin')
245 @auth.CSRFRequired()
245 @auth.CSRFRequired()
246 def settings_global_update(self):
246 def settings_global_update(self):
247 """POST /admin/settings/global: All items in the collection"""
247 """POST /admin/settings/global: All items in the collection"""
248 # url('admin_settings_global')
248 # url('admin_settings_global')
249 c.active = 'global'
249 c.active = 'global'
250 c.personal_repo_group_default_pattern = RepoGroupModel()\
250 c.personal_repo_group_default_pattern = RepoGroupModel()\
251 .get_personal_group_name_pattern()
251 .get_personal_group_name_pattern()
252 application_form = ApplicationSettingsForm()()
252 application_form = ApplicationSettingsForm()()
253 try:
253 try:
254 form_result = application_form.to_python(dict(request.POST))
254 form_result = application_form.to_python(dict(request.POST))
255 except formencode.Invalid as errors:
255 except formencode.Invalid as errors:
256 return htmlfill.render(
256 return htmlfill.render(
257 render('admin/settings/settings.mako'),
257 render('admin/settings/settings.mako'),
258 defaults=errors.value,
258 defaults=errors.value,
259 errors=errors.error_dict or {},
259 errors=errors.error_dict or {},
260 prefix_error=False,
260 prefix_error=False,
261 encoding="UTF-8",
261 encoding="UTF-8",
262 force_defaults=False)
262 force_defaults=False)
263
263
264 try:
264 try:
265 settings = [
265 settings = [
266 ('title', 'rhodecode_title', 'unicode'),
266 ('title', 'rhodecode_title', 'unicode'),
267 ('realm', 'rhodecode_realm', 'unicode'),
267 ('realm', 'rhodecode_realm', 'unicode'),
268 ('pre_code', 'rhodecode_pre_code', 'unicode'),
268 ('pre_code', 'rhodecode_pre_code', 'unicode'),
269 ('post_code', 'rhodecode_post_code', 'unicode'),
269 ('post_code', 'rhodecode_post_code', 'unicode'),
270 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
270 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
271 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
271 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
272 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
272 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
273 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
273 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
274 ]
274 ]
275 for setting, form_key, type_ in settings:
275 for setting, form_key, type_ in settings:
276 sett = SettingsModel().create_or_update_setting(
276 sett = SettingsModel().create_or_update_setting(
277 setting, form_result[form_key], type_)
277 setting, form_result[form_key], type_)
278 Session().add(sett)
278 Session().add(sett)
279
279
280 Session().commit()
280 Session().commit()
281 SettingsModel().invalidate_settings_cache()
281 SettingsModel().invalidate_settings_cache()
282 h.flash(_('Updated application settings'), category='success')
282 h.flash(_('Updated application settings'), category='success')
283 except Exception:
283 except Exception:
284 log.exception("Exception while updating application settings")
284 log.exception("Exception while updating application settings")
285 h.flash(
285 h.flash(
286 _('Error occurred during updating application settings'),
286 _('Error occurred during updating application settings'),
287 category='error')
287 category='error')
288
288
289 return redirect(url('admin_settings_global'))
289 return redirect(url('admin_settings_global'))
290
290
291 @HasPermissionAllDecorator('hg.admin')
291 @HasPermissionAllDecorator('hg.admin')
292 def settings_global(self):
292 def settings_global(self):
293 """GET /admin/settings/global: All items in the collection"""
293 """GET /admin/settings/global: All items in the collection"""
294 # url('admin_settings_global')
294 # url('admin_settings_global')
295 c.active = 'global'
295 c.active = 'global'
296 c.personal_repo_group_default_pattern = RepoGroupModel()\
296 c.personal_repo_group_default_pattern = RepoGroupModel()\
297 .get_personal_group_name_pattern()
297 .get_personal_group_name_pattern()
298
298
299 return htmlfill.render(
299 return htmlfill.render(
300 render('admin/settings/settings.mako'),
300 render('admin/settings/settings.mako'),
301 defaults=self._form_defaults(),
301 defaults=self._form_defaults(),
302 encoding="UTF-8",
302 encoding="UTF-8",
303 force_defaults=False)
303 force_defaults=False)
304
304
305 @HasPermissionAllDecorator('hg.admin')
305 @HasPermissionAllDecorator('hg.admin')
306 @auth.CSRFRequired()
306 @auth.CSRFRequired()
307 def settings_visual_update(self):
307 def settings_visual_update(self):
308 """POST /admin/settings/visual: All items in the collection"""
308 """POST /admin/settings/visual: All items in the collection"""
309 # url('admin_settings_visual')
309 # url('admin_settings_visual')
310 c.active = 'visual'
310 c.active = 'visual'
311 application_form = ApplicationVisualisationForm()()
311 application_form = ApplicationVisualisationForm()()
312 try:
312 try:
313 form_result = application_form.to_python(dict(request.POST))
313 form_result = application_form.to_python(dict(request.POST))
314 except formencode.Invalid as errors:
314 except formencode.Invalid as errors:
315 return htmlfill.render(
315 return htmlfill.render(
316 render('admin/settings/settings.mako'),
316 render('admin/settings/settings.mako'),
317 defaults=errors.value,
317 defaults=errors.value,
318 errors=errors.error_dict or {},
318 errors=errors.error_dict or {},
319 prefix_error=False,
319 prefix_error=False,
320 encoding="UTF-8",
320 encoding="UTF-8",
321 force_defaults=False
321 force_defaults=False
322 )
322 )
323
323
324 try:
324 try:
325 settings = [
325 settings = [
326 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
326 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
327 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
327 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
328 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
328 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
329 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
329 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
330 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
330 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
331 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
331 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
332 ('show_version', 'rhodecode_show_version', 'bool'),
332 ('show_version', 'rhodecode_show_version', 'bool'),
333 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
333 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
334 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
334 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
335 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
335 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
336 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
336 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
337 ('support_url', 'rhodecode_support_url', 'unicode'),
337 ('support_url', 'rhodecode_support_url', 'unicode'),
338 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
338 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
339 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
339 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
340 ]
340 ]
341 for setting, form_key, type_ in settings:
341 for setting, form_key, type_ in settings:
342 sett = SettingsModel().create_or_update_setting(
342 sett = SettingsModel().create_or_update_setting(
343 setting, form_result[form_key], type_)
343 setting, form_result[form_key], type_)
344 Session().add(sett)
344 Session().add(sett)
345
345
346 Session().commit()
346 Session().commit()
347 SettingsModel().invalidate_settings_cache()
347 SettingsModel().invalidate_settings_cache()
348 h.flash(_('Updated visualisation settings'), category='success')
348 h.flash(_('Updated visualisation settings'), category='success')
349 except Exception:
349 except Exception:
350 log.exception("Exception updating visualization settings")
350 log.exception("Exception updating visualization settings")
351 h.flash(_('Error occurred during updating '
351 h.flash(_('Error occurred during updating '
352 'visualisation settings'),
352 'visualisation settings'),
353 category='error')
353 category='error')
354
354
355 return redirect(url('admin_settings_visual'))
355 return redirect(url('admin_settings_visual'))
356
356
357 @HasPermissionAllDecorator('hg.admin')
357 @HasPermissionAllDecorator('hg.admin')
358 def settings_visual(self):
358 def settings_visual(self):
359 """GET /admin/settings/visual: All items in the collection"""
359 """GET /admin/settings/visual: All items in the collection"""
360 # url('admin_settings_visual')
360 # url('admin_settings_visual')
361 c.active = 'visual'
361 c.active = 'visual'
362
362
363 return htmlfill.render(
363 return htmlfill.render(
364 render('admin/settings/settings.mako'),
364 render('admin/settings/settings.mako'),
365 defaults=self._form_defaults(),
365 defaults=self._form_defaults(),
366 encoding="UTF-8",
366 encoding="UTF-8",
367 force_defaults=False)
367 force_defaults=False)
368
368
369 @HasPermissionAllDecorator('hg.admin')
369 @HasPermissionAllDecorator('hg.admin')
370 @auth.CSRFRequired()
370 @auth.CSRFRequired()
371 def settings_issuetracker_test(self):
371 def settings_issuetracker_test(self):
372 if request.is_xhr:
372 if request.is_xhr:
373 return h.urlify_commit_message(
373 return h.urlify_commit_message(
374 request.POST.get('test_text', ''),
374 request.POST.get('test_text', ''),
375 'repo_group/test_repo1')
375 'repo_group/test_repo1')
376 else:
376 else:
377 raise HTTPBadRequest()
377 raise HTTPBadRequest()
378
378
379 @HasPermissionAllDecorator('hg.admin')
379 @HasPermissionAllDecorator('hg.admin')
380 @auth.CSRFRequired()
380 @auth.CSRFRequired()
381 def settings_issuetracker_delete(self):
381 def settings_issuetracker_delete(self):
382 uid = request.POST.get('uid')
382 uid = request.POST.get('uid')
383 IssueTrackerSettingsModel().delete_entries(uid)
383 IssueTrackerSettingsModel().delete_entries(uid)
384 h.flash(_('Removed issue tracker entry'), category='success')
384 h.flash(_('Removed issue tracker entry'), category='success')
385 return redirect(url('admin_settings_issuetracker'))
385 return redirect(url('admin_settings_issuetracker'))
386
386
387 @HasPermissionAllDecorator('hg.admin')
387 @HasPermissionAllDecorator('hg.admin')
388 def settings_issuetracker(self):
388 def settings_issuetracker(self):
389 """GET /admin/settings/issue-tracker: All items in the collection"""
389 """GET /admin/settings/issue-tracker: All items in the collection"""
390 # url('admin_settings_issuetracker')
390 # url('admin_settings_issuetracker')
391 c.active = 'issuetracker'
391 c.active = 'issuetracker'
392 defaults = SettingsModel().get_all_settings()
392 defaults = SettingsModel().get_all_settings()
393
393
394 entry_key = 'rhodecode_issuetracker_pat_'
394 entry_key = 'rhodecode_issuetracker_pat_'
395
395
396 c.issuetracker_entries = {}
396 c.issuetracker_entries = {}
397 for k, v in defaults.items():
397 for k, v in defaults.items():
398 if k.startswith(entry_key):
398 if k.startswith(entry_key):
399 uid = k[len(entry_key):]
399 uid = k[len(entry_key):]
400 c.issuetracker_entries[uid] = None
400 c.issuetracker_entries[uid] = None
401
401
402 for uid in c.issuetracker_entries:
402 for uid in c.issuetracker_entries:
403 c.issuetracker_entries[uid] = AttributeDict({
403 c.issuetracker_entries[uid] = AttributeDict({
404 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
404 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
405 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
405 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
406 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
406 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
407 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
407 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
408 })
408 })
409
409
410 return render('admin/settings/settings.mako')
410 return render('admin/settings/settings.mako')
411
411
412 @HasPermissionAllDecorator('hg.admin')
412 @HasPermissionAllDecorator('hg.admin')
413 @auth.CSRFRequired()
413 @auth.CSRFRequired()
414 def settings_issuetracker_save(self):
414 def settings_issuetracker_save(self):
415 settings_model = IssueTrackerSettingsModel()
415 settings_model = IssueTrackerSettingsModel()
416
416
417 form = IssueTrackerPatternsForm()().to_python(request.POST)
417 form = IssueTrackerPatternsForm()().to_python(request.POST)
418 if form:
418 if form:
419 for uid in form.get('delete_patterns', []):
419 for uid in form.get('delete_patterns', []):
420 settings_model.delete_entries(uid)
420 settings_model.delete_entries(uid)
421
421
422 for pattern in form.get('patterns', []):
422 for pattern in form.get('patterns', []):
423 for setting, value, type_ in pattern:
423 for setting, value, type_ in pattern:
424 sett = settings_model.create_or_update_setting(
424 sett = settings_model.create_or_update_setting(
425 setting, value, type_)
425 setting, value, type_)
426 Session().add(sett)
426 Session().add(sett)
427
427
428 Session().commit()
428 Session().commit()
429
429
430 SettingsModel().invalidate_settings_cache()
430 SettingsModel().invalidate_settings_cache()
431 h.flash(_('Updated issue tracker entries'), category='success')
431 h.flash(_('Updated issue tracker entries'), category='success')
432 return redirect(url('admin_settings_issuetracker'))
432 return redirect(url('admin_settings_issuetracker'))
433
433
434 @HasPermissionAllDecorator('hg.admin')
434 @HasPermissionAllDecorator('hg.admin')
435 @auth.CSRFRequired()
435 @auth.CSRFRequired()
436 def settings_email_update(self):
436 def settings_email_update(self):
437 """POST /admin/settings/email: All items in the collection"""
437 """POST /admin/settings/email: All items in the collection"""
438 # url('admin_settings_email')
438 # url('admin_settings_email')
439 c.active = 'email'
439 c.active = 'email'
440
440
441 test_email = request.POST.get('test_email')
441 test_email = request.POST.get('test_email')
442
442
443 if not test_email:
443 if not test_email:
444 h.flash(_('Please enter email address'), category='error')
444 h.flash(_('Please enter email address'), category='error')
445 return redirect(url('admin_settings_email'))
445 return redirect(url('admin_settings_email'))
446
446
447 email_kwargs = {
447 email_kwargs = {
448 'date': datetime.datetime.now(),
448 'date': datetime.datetime.now(),
449 'user': c.rhodecode_user,
449 'user': c.rhodecode_user,
450 'rhodecode_version': c.rhodecode_version
450 'rhodecode_version': c.rhodecode_version
451 }
451 }
452
452
453 (subject, headers, email_body,
453 (subject, headers, email_body,
454 email_body_plaintext) = EmailNotificationModel().render_email(
454 email_body_plaintext) = EmailNotificationModel().render_email(
455 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
455 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
456
456
457 recipients = [test_email] if test_email else None
457 recipients = [test_email] if test_email else None
458
458
459 run_task(tasks.send_email, recipients, subject,
459 run_task(tasks.send_email, recipients, subject,
460 email_body_plaintext, email_body)
460 email_body_plaintext, email_body)
461
461
462 h.flash(_('Send email task created'), category='success')
462 h.flash(_('Send email task created'), category='success')
463 return redirect(url('admin_settings_email'))
463 return redirect(url('admin_settings_email'))
464
464
465 @HasPermissionAllDecorator('hg.admin')
465 @HasPermissionAllDecorator('hg.admin')
466 def settings_email(self):
466 def settings_email(self):
467 """GET /admin/settings/email: All items in the collection"""
467 """GET /admin/settings/email: All items in the collection"""
468 # url('admin_settings_email')
468 # url('admin_settings_email')
469 c.active = 'email'
469 c.active = 'email'
470 c.rhodecode_ini = rhodecode.CONFIG
470 c.rhodecode_ini = rhodecode.CONFIG
471
471
472 return htmlfill.render(
472 return htmlfill.render(
473 render('admin/settings/settings.mako'),
473 render('admin/settings/settings.mako'),
474 defaults=self._form_defaults(),
474 defaults=self._form_defaults(),
475 encoding="UTF-8",
475 encoding="UTF-8",
476 force_defaults=False)
476 force_defaults=False)
477
477
478 @HasPermissionAllDecorator('hg.admin')
478 @HasPermissionAllDecorator('hg.admin')
479 @auth.CSRFRequired()
479 @auth.CSRFRequired()
480 def settings_hooks_update(self):
480 def settings_hooks_update(self):
481 """POST or DELETE /admin/settings/hooks: All items in the collection"""
481 """POST or DELETE /admin/settings/hooks: All items in the collection"""
482 # url('admin_settings_hooks')
482 # url('admin_settings_hooks')
483 c.active = 'hooks'
483 c.active = 'hooks'
484 if c.visual.allow_custom_hooks_settings:
484 if c.visual.allow_custom_hooks_settings:
485 ui_key = request.POST.get('new_hook_ui_key')
485 ui_key = request.POST.get('new_hook_ui_key')
486 ui_value = request.POST.get('new_hook_ui_value')
486 ui_value = request.POST.get('new_hook_ui_value')
487
487
488 hook_id = request.POST.get('hook_id')
488 hook_id = request.POST.get('hook_id')
489 new_hook = False
489 new_hook = False
490
490
491 model = SettingsModel()
491 model = SettingsModel()
492 try:
492 try:
493 if ui_value and ui_key:
493 if ui_value and ui_key:
494 model.create_or_update_hook(ui_key, ui_value)
494 model.create_or_update_hook(ui_key, ui_value)
495 h.flash(_('Added new hook'), category='success')
495 h.flash(_('Added new hook'), category='success')
496 new_hook = True
496 new_hook = True
497 elif hook_id:
497 elif hook_id:
498 RhodeCodeUi.delete(hook_id)
498 RhodeCodeUi.delete(hook_id)
499 Session().commit()
499 Session().commit()
500
500
501 # check for edits
501 # check for edits
502 update = False
502 update = False
503 _d = request.POST.dict_of_lists()
503 _d = request.POST.dict_of_lists()
504 for k, v in zip(_d.get('hook_ui_key', []),
504 for k, v in zip(_d.get('hook_ui_key', []),
505 _d.get('hook_ui_value_new', [])):
505 _d.get('hook_ui_value_new', [])):
506 model.create_or_update_hook(k, v)
506 model.create_or_update_hook(k, v)
507 update = True
507 update = True
508
508
509 if update and not new_hook:
509 if update and not new_hook:
510 h.flash(_('Updated hooks'), category='success')
510 h.flash(_('Updated hooks'), category='success')
511 Session().commit()
511 Session().commit()
512 except Exception:
512 except Exception:
513 log.exception("Exception during hook creation")
513 log.exception("Exception during hook creation")
514 h.flash(_('Error occurred during hook creation'),
514 h.flash(_('Error occurred during hook creation'),
515 category='error')
515 category='error')
516
516
517 return redirect(url('admin_settings_hooks'))
517 return redirect(url('admin_settings_hooks'))
518
518
519 @HasPermissionAllDecorator('hg.admin')
519 @HasPermissionAllDecorator('hg.admin')
520 def settings_hooks(self):
520 def settings_hooks(self):
521 """GET /admin/settings/hooks: All items in the collection"""
521 """GET /admin/settings/hooks: All items in the collection"""
522 # url('admin_settings_hooks')
522 # url('admin_settings_hooks')
523 c.active = 'hooks'
523 c.active = 'hooks'
524
524
525 model = SettingsModel()
525 model = SettingsModel()
526 c.hooks = model.get_builtin_hooks()
526 c.hooks = model.get_builtin_hooks()
527 c.custom_hooks = model.get_custom_hooks()
527 c.custom_hooks = model.get_custom_hooks()
528
528
529 return htmlfill.render(
529 return htmlfill.render(
530 render('admin/settings/settings.mako'),
530 render('admin/settings/settings.mako'),
531 defaults=self._form_defaults(),
531 defaults=self._form_defaults(),
532 encoding="UTF-8",
532 encoding="UTF-8",
533 force_defaults=False)
533 force_defaults=False)
534
534
535 @HasPermissionAllDecorator('hg.admin')
535 @HasPermissionAllDecorator('hg.admin')
536 def settings_search(self):
536 def settings_search(self):
537 """GET /admin/settings/search: All items in the collection"""
537 """GET /admin/settings/search: All items in the collection"""
538 # url('admin_settings_search')
538 # url('admin_settings_search')
539 c.active = 'search'
539 c.active = 'search'
540
540
541 from rhodecode.lib.index import searcher_from_config
541 from rhodecode.lib.index import searcher_from_config
542 searcher = searcher_from_config(config)
542 searcher = searcher_from_config(config)
543 c.statistics = searcher.statistics()
543 c.statistics = searcher.statistics()
544
544
545 return render('admin/settings/settings.mako')
545 return render('admin/settings/settings.mako')
546
546
547 @HasPermissionAllDecorator('hg.admin')
547 @HasPermissionAllDecorator('hg.admin')
548 def settings_supervisor(self):
548 def settings_supervisor(self):
549 c.rhodecode_ini = rhodecode.CONFIG
549 c.rhodecode_ini = rhodecode.CONFIG
550 c.active = 'supervisor'
550 c.active = 'supervisor'
551
551
552 c.supervisor_procs = OrderedDict([
552 c.supervisor_procs = OrderedDict([
553 (SUPERVISOR_MASTER, {}),
553 (SUPERVISOR_MASTER, {}),
554 ])
554 ])
555
555
556 c.log_size = 10240
556 c.log_size = 10240
557 supervisor = SupervisorModel()
557 supervisor = SupervisorModel()
558
558
559 _connection = supervisor.get_connection(
559 _connection = supervisor.get_connection(
560 c.rhodecode_ini.get('supervisor.uri'))
560 c.rhodecode_ini.get('supervisor.uri'))
561 c.connection_error = None
561 c.connection_error = None
562 try:
562 try:
563 _connection.supervisor.getAllProcessInfo()
563 _connection.supervisor.getAllProcessInfo()
564 except Exception as e:
564 except Exception as e:
565 c.connection_error = str(e)
565 c.connection_error = str(e)
566 log.exception("Exception reading supervisor data")
566 log.exception("Exception reading supervisor data")
567 return render('admin/settings/settings.mako')
567 return render('admin/settings/settings.mako')
568
568
569 groupid = c.rhodecode_ini.get('supervisor.group_id')
569 groupid = c.rhodecode_ini.get('supervisor.group_id')
570
570
571 # feed our group processes to the main
571 # feed our group processes to the main
572 for proc in supervisor.get_group_processes(_connection, groupid):
572 for proc in supervisor.get_group_processes(_connection, groupid):
573 c.supervisor_procs[proc['name']] = {}
573 c.supervisor_procs[proc['name']] = {}
574
574
575 for k in c.supervisor_procs.keys():
575 for k in c.supervisor_procs.keys():
576 try:
576 try:
577 # master process info
577 # master process info
578 if k == SUPERVISOR_MASTER:
578 if k == SUPERVISOR_MASTER:
579 _data = supervisor.get_master_state(_connection)
579 _data = supervisor.get_master_state(_connection)
580 _data['name'] = 'supervisor master'
580 _data['name'] = 'supervisor master'
581 _data['description'] = 'pid %s, id: %s, ver: %s' % (
581 _data['description'] = 'pid %s, id: %s, ver: %s' % (
582 _data['pid'], _data['id'], _data['ver'])
582 _data['pid'], _data['id'], _data['ver'])
583 c.supervisor_procs[k] = _data
583 c.supervisor_procs[k] = _data
584 else:
584 else:
585 procid = groupid + ":" + k
585 procid = groupid + ":" + k
586 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
586 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
587 except Exception as e:
587 except Exception as e:
588 log.exception("Exception reading supervisor data")
588 log.exception("Exception reading supervisor data")
589 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
589 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
590
590
591 return render('admin/settings/settings.mako')
591 return render('admin/settings/settings.mako')
592
592
593 @HasPermissionAllDecorator('hg.admin')
593 @HasPermissionAllDecorator('hg.admin')
594 def settings_supervisor_log(self, procid):
594 def settings_supervisor_log(self, procid):
595 import rhodecode
595 import rhodecode
596 c.rhodecode_ini = rhodecode.CONFIG
596 c.rhodecode_ini = rhodecode.CONFIG
597 c.active = 'supervisor_tail'
597 c.active = 'supervisor_tail'
598
598
599 supervisor = SupervisorModel()
599 supervisor = SupervisorModel()
600 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
600 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
601 groupid = c.rhodecode_ini.get('supervisor.group_id')
601 groupid = c.rhodecode_ini.get('supervisor.group_id')
602 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
602 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
603
603
604 c.log_size = 10240
604 c.log_size = 10240
605 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
605 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
606 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
606 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
607
607
608 return render('admin/settings/settings.mako')
608 return render('admin/settings/settings.mako')
609
609
610 @HasPermissionAllDecorator('hg.admin')
610 @HasPermissionAllDecorator('hg.admin')
611 @auth.CSRFRequired()
611 @auth.CSRFRequired()
612 def settings_labs_update(self):
612 def settings_labs_update(self):
613 """POST /admin/settings/labs: All items in the collection"""
613 """POST /admin/settings/labs: All items in the collection"""
614 # url('admin_settings/labs', method={'POST'})
614 # url('admin_settings/labs', method={'POST'})
615 c.active = 'labs'
615 c.active = 'labs'
616
616
617 application_form = LabsSettingsForm()()
617 application_form = LabsSettingsForm()()
618 try:
618 try:
619 form_result = application_form.to_python(dict(request.POST))
619 form_result = application_form.to_python(dict(request.POST))
620 except formencode.Invalid as errors:
620 except formencode.Invalid as errors:
621 h.flash(
621 h.flash(
622 _('Some form inputs contain invalid data.'),
622 _('Some form inputs contain invalid data.'),
623 category='error')
623 category='error')
624 return htmlfill.render(
624 return htmlfill.render(
625 render('admin/settings/settings.mako'),
625 render('admin/settings/settings.mako'),
626 defaults=errors.value,
626 defaults=errors.value,
627 errors=errors.error_dict or {},
627 errors=errors.error_dict or {},
628 prefix_error=False,
628 prefix_error=False,
629 encoding='UTF-8',
629 encoding='UTF-8',
630 force_defaults=False
630 force_defaults=False
631 )
631 )
632
632
633 try:
633 try:
634 session = Session()
634 session = Session()
635 for setting in _LAB_SETTINGS:
635 for setting in _LAB_SETTINGS:
636 setting_name = setting.key[len('rhodecode_'):]
636 setting_name = setting.key[len('rhodecode_'):]
637 sett = SettingsModel().create_or_update_setting(
637 sett = SettingsModel().create_or_update_setting(
638 setting_name, form_result[setting.key], setting.type)
638 setting_name, form_result[setting.key], setting.type)
639 session.add(sett)
639 session.add(sett)
640
640
641 except Exception:
641 except Exception:
642 log.exception('Exception while updating lab settings')
642 log.exception('Exception while updating lab settings')
643 h.flash(_('Error occurred during updating labs settings'),
643 h.flash(_('Error occurred during updating labs settings'),
644 category='error')
644 category='error')
645 else:
645 else:
646 Session().commit()
646 Session().commit()
647 SettingsModel().invalidate_settings_cache()
647 SettingsModel().invalidate_settings_cache()
648 h.flash(_('Updated Labs settings'), category='success')
648 h.flash(_('Updated Labs settings'), category='success')
649 return redirect(url('admin_settings_labs'))
649 return redirect(url('admin_settings_labs'))
650
650
651 return htmlfill.render(
651 return htmlfill.render(
652 render('admin/settings/settings.mako'),
652 render('admin/settings/settings.mako'),
653 defaults=self._form_defaults(),
653 defaults=self._form_defaults(),
654 encoding='UTF-8',
654 encoding='UTF-8',
655 force_defaults=False)
655 force_defaults=False)
656
656
657 @HasPermissionAllDecorator('hg.admin')
657 @HasPermissionAllDecorator('hg.admin')
658 def settings_labs(self):
658 def settings_labs(self):
659 """GET /admin/settings/labs: All items in the collection"""
659 """GET /admin/settings/labs: All items in the collection"""
660 # url('admin_settings_labs')
660 # url('admin_settings_labs')
661 if not c.labs_active:
661 if not c.labs_active:
662 redirect(url('admin_settings'))
662 redirect(url('admin_settings'))
663
663
664 c.active = 'labs'
664 c.active = 'labs'
665 c.lab_settings = _LAB_SETTINGS
665 c.lab_settings = _LAB_SETTINGS
666
666
667 return htmlfill.render(
667 return htmlfill.render(
668 render('admin/settings/settings.mako'),
668 render('admin/settings/settings.mako'),
669 defaults=self._form_defaults(),
669 defaults=self._form_defaults(),
670 encoding='UTF-8',
670 encoding='UTF-8',
671 force_defaults=False)
671 force_defaults=False)
672
672
673 def _form_defaults(self):
673 def _form_defaults(self):
674 defaults = SettingsModel().get_all_settings()
674 defaults = SettingsModel().get_all_settings()
675 defaults.update(self._get_ui_settings())
675 defaults.update(self._get_ui_settings())
676
676
677 defaults.update({
677 defaults.update({
678 'new_svn_branch': '',
678 'new_svn_branch': '',
679 'new_svn_tag': '',
679 'new_svn_tag': '',
680 })
680 })
681 return defaults
681 return defaults
682
682
683
683
684 # :param key: name of the setting including the 'rhodecode_' prefix
684 # :param key: name of the setting including the 'rhodecode_' prefix
685 # :param type: the RhodeCodeSetting type to use.
685 # :param type: the RhodeCodeSetting type to use.
686 # :param group: the i18ned group in which we should dispaly this setting
686 # :param group: the i18ned group in which we should dispaly this setting
687 # :param label: the i18ned label we should display for this setting
687 # :param label: the i18ned label we should display for this setting
688 # :param help: the i18ned help we should dispaly for this setting
688 # :param help: the i18ned help we should dispaly for this setting
689 LabSetting = collections.namedtuple(
689 LabSetting = collections.namedtuple(
690 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
690 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
691
691
692
692
693 # This list has to be kept in sync with the form
693 # This list has to be kept in sync with the form
694 # rhodecode.model.forms.LabsSettingsForm.
694 # rhodecode.model.forms.LabsSettingsForm.
695 _LAB_SETTINGS = [
695 _LAB_SETTINGS = [
696
696
697 ]
697 ]
@@ -1,419 +1,413 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 User Groups crud controller for pylons
22 User Groups crud controller for pylons
23 """
23 """
24
24
25 import logging
25 import logging
26 import formencode
26 import formencode
27
27
28 import peppercorn
28 import peppercorn
29 from formencode import htmlfill
29 from formencode import htmlfill
30 from pylons import request, tmpl_context as c, url, config
30 from pylons import request, tmpl_context as c, url, config
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 from sqlalchemy.orm import joinedload
35
36 from rhodecode.lib import auth
34 from rhodecode.lib import auth
37 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
38 from rhodecode.lib import audit_logger
36 from rhodecode.lib import audit_logger
39 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.exceptions import UserGroupAssignedException,\
37 from rhodecode.lib.exceptions import UserGroupAssignedException,\
41 RepoGroupAssignmentError
38 RepoGroupAssignmentError
42 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
39 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
44 from rhodecode.lib.auth import (
40 from rhodecode.lib.auth import (
45 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
41 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
46 HasPermissionAnyDecorator)
42 HasPermissionAnyDecorator)
47 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.base import BaseController, render
48 from rhodecode.model.permission import PermissionModel
44 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.scm import UserGroupList
50 from rhodecode.model.user_group import UserGroupModel
45 from rhodecode.model.user_group import UserGroupModel
51 from rhodecode.model.db import (
46 from rhodecode.model.db import User, UserGroup
52 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
53 from rhodecode.model.forms import (
47 from rhodecode.model.forms import (
54 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
48 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
55 UserPermissionsForm)
49 UserPermissionsForm)
56 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
57
51
58
52
59 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
60
54
61
55
62 class UserGroupsController(BaseController):
56 class UserGroupsController(BaseController):
63 """REST Controller styled on the Atom Publishing Protocol"""
57 """REST Controller styled on the Atom Publishing Protocol"""
64
58
65 @LoginRequired()
59 @LoginRequired()
66 def __before__(self):
60 def __before__(self):
67 super(UserGroupsController, self).__before__()
61 super(UserGroupsController, self).__before__()
68 c.available_permissions = config['available_permissions']
62 c.available_permissions = config['available_permissions']
69 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
63 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
70
64
71 def __load_data(self, user_group_id):
65 def __load_data(self, user_group_id):
72 c.group_members_obj = [x.user for x in c.user_group.members]
66 c.group_members_obj = [x.user for x in c.user_group.members]
73 c.group_members_obj.sort(key=lambda u: u.username.lower())
67 c.group_members_obj.sort(key=lambda u: u.username.lower())
74 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
68 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
75
69
76 def __load_defaults(self, user_group_id):
70 def __load_defaults(self, user_group_id):
77 """
71 """
78 Load defaults settings for edit, and update
72 Load defaults settings for edit, and update
79
73
80 :param user_group_id:
74 :param user_group_id:
81 """
75 """
82 user_group = UserGroup.get_or_404(user_group_id)
76 user_group = UserGroup.get_or_404(user_group_id)
83 data = user_group.get_dict()
77 data = user_group.get_dict()
84 # fill owner
78 # fill owner
85 if user_group.user:
79 if user_group.user:
86 data.update({'user': user_group.user.username})
80 data.update({'user': user_group.user.username})
87 else:
81 else:
88 replacement_user = User.get_first_super_admin().username
82 replacement_user = User.get_first_super_admin().username
89 data.update({'user': replacement_user})
83 data.update({'user': replacement_user})
90 return data
84 return data
91
85
92 def _revoke_perms_on_yourself(self, form_result):
86 def _revoke_perms_on_yourself(self, form_result):
93 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
87 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
94 form_result['perm_updates'])
88 form_result['perm_updates'])
95 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
89 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
96 form_result['perm_additions'])
90 form_result['perm_additions'])
97 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
91 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
98 form_result['perm_deletions'])
92 form_result['perm_deletions'])
99 admin_perm = 'usergroup.admin'
93 admin_perm = 'usergroup.admin'
100 if _updates and _updates[0][1] != admin_perm or \
94 if _updates and _updates[0][1] != admin_perm or \
101 _additions and _additions[0][1] != admin_perm or \
95 _additions and _additions[0][1] != admin_perm or \
102 _deletions and _deletions[0][1] != admin_perm:
96 _deletions and _deletions[0][1] != admin_perm:
103 return True
97 return True
104 return False
98 return False
105
99
106 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
100 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
107 @auth.CSRFRequired()
101 @auth.CSRFRequired()
108 def create(self):
102 def create(self):
109
103
110 users_group_form = UserGroupForm()()
104 users_group_form = UserGroupForm()()
111 try:
105 try:
112 form_result = users_group_form.to_python(dict(request.POST))
106 form_result = users_group_form.to_python(dict(request.POST))
113 user_group = UserGroupModel().create(
107 user_group = UserGroupModel().create(
114 name=form_result['users_group_name'],
108 name=form_result['users_group_name'],
115 description=form_result['user_group_description'],
109 description=form_result['user_group_description'],
116 owner=c.rhodecode_user.user_id,
110 owner=c.rhodecode_user.user_id,
117 active=form_result['users_group_active'])
111 active=form_result['users_group_active'])
118 Session().flush()
112 Session().flush()
119 creation_data = user_group.get_api_data()
113 creation_data = user_group.get_api_data()
120 user_group_name = form_result['users_group_name']
114 user_group_name = form_result['users_group_name']
121
115
122 audit_logger.store_web(
116 audit_logger.store_web(
123 'user_group.create', action_data={'data': creation_data},
117 'user_group.create', action_data={'data': creation_data},
124 user=c.rhodecode_user)
118 user=c.rhodecode_user)
125
119
126 user_group_link = h.link_to(
120 user_group_link = h.link_to(
127 h.escape(user_group_name),
121 h.escape(user_group_name),
128 url('edit_users_group', user_group_id=user_group.users_group_id))
122 url('edit_users_group', user_group_id=user_group.users_group_id))
129 h.flash(h.literal(_('Created user group %(user_group_link)s')
123 h.flash(h.literal(_('Created user group %(user_group_link)s')
130 % {'user_group_link': user_group_link}),
124 % {'user_group_link': user_group_link}),
131 category='success')
125 category='success')
132 Session().commit()
126 Session().commit()
133 except formencode.Invalid as errors:
127 except formencode.Invalid as errors:
134 return htmlfill.render(
128 return htmlfill.render(
135 render('admin/user_groups/user_group_add.mako'),
129 render('admin/user_groups/user_group_add.mako'),
136 defaults=errors.value,
130 defaults=errors.value,
137 errors=errors.error_dict or {},
131 errors=errors.error_dict or {},
138 prefix_error=False,
132 prefix_error=False,
139 encoding="UTF-8",
133 encoding="UTF-8",
140 force_defaults=False)
134 force_defaults=False)
141 except Exception:
135 except Exception:
142 log.exception("Exception creating user group")
136 log.exception("Exception creating user group")
143 h.flash(_('Error occurred during creation of user group %s') \
137 h.flash(_('Error occurred during creation of user group %s') \
144 % request.POST.get('users_group_name'), category='error')
138 % request.POST.get('users_group_name'), category='error')
145
139
146 return redirect(
140 return redirect(
147 url('edit_users_group', user_group_id=user_group.users_group_id))
141 url('edit_users_group', user_group_id=user_group.users_group_id))
148
142
149 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
143 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
150 def new(self):
144 def new(self):
151 """GET /user_groups/new: Form to create a new item"""
145 """GET /user_groups/new: Form to create a new item"""
152 # url('new_users_group')
146 # url('new_users_group')
153 return render('admin/user_groups/user_group_add.mako')
147 return render('admin/user_groups/user_group_add.mako')
154
148
155 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
149 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
156 @auth.CSRFRequired()
150 @auth.CSRFRequired()
157 def update(self, user_group_id):
151 def update(self, user_group_id):
158
152
159 user_group_id = safe_int(user_group_id)
153 user_group_id = safe_int(user_group_id)
160 c.user_group = UserGroup.get_or_404(user_group_id)
154 c.user_group = UserGroup.get_or_404(user_group_id)
161 c.active = 'settings'
155 c.active = 'settings'
162 self.__load_data(user_group_id)
156 self.__load_data(user_group_id)
163
157
164 users_group_form = UserGroupForm(
158 users_group_form = UserGroupForm(
165 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
159 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
166
160
167 old_values = c.user_group.get_api_data()
161 old_values = c.user_group.get_api_data()
168 try:
162 try:
169 form_result = users_group_form.to_python(request.POST)
163 form_result = users_group_form.to_python(request.POST)
170 pstruct = peppercorn.parse(request.POST.items())
164 pstruct = peppercorn.parse(request.POST.items())
171 form_result['users_group_members'] = pstruct['user_group_members']
165 form_result['users_group_members'] = pstruct['user_group_members']
172
166
173 user_group, added_members, removed_members = \
167 user_group, added_members, removed_members = \
174 UserGroupModel().update(c.user_group, form_result)
168 UserGroupModel().update(c.user_group, form_result)
175 updated_user_group = form_result['users_group_name']
169 updated_user_group = form_result['users_group_name']
176
170
177 audit_logger.store_web(
171 audit_logger.store_web(
178 'user_group.edit', action_data={'old_data': old_values},
172 'user_group.edit', action_data={'old_data': old_values},
179 user=c.rhodecode_user)
173 user=c.rhodecode_user)
180
174
181 # TODO(marcink): use added/removed to set user_group.edit.member.add
175 # TODO(marcink): use added/removed to set user_group.edit.member.add
182
176
183 h.flash(_('Updated user group %s') % updated_user_group,
177 h.flash(_('Updated user group %s') % updated_user_group,
184 category='success')
178 category='success')
185 Session().commit()
179 Session().commit()
186 except formencode.Invalid as errors:
180 except formencode.Invalid as errors:
187 defaults = errors.value
181 defaults = errors.value
188 e = errors.error_dict or {}
182 e = errors.error_dict or {}
189
183
190 return htmlfill.render(
184 return htmlfill.render(
191 render('admin/user_groups/user_group_edit.mako'),
185 render('admin/user_groups/user_group_edit.mako'),
192 defaults=defaults,
186 defaults=defaults,
193 errors=e,
187 errors=e,
194 prefix_error=False,
188 prefix_error=False,
195 encoding="UTF-8",
189 encoding="UTF-8",
196 force_defaults=False)
190 force_defaults=False)
197 except Exception:
191 except Exception:
198 log.exception("Exception during update of user group")
192 log.exception("Exception during update of user group")
199 h.flash(_('Error occurred during update of user group %s')
193 h.flash(_('Error occurred during update of user group %s')
200 % request.POST.get('users_group_name'), category='error')
194 % request.POST.get('users_group_name'), category='error')
201
195
202 return redirect(url('edit_users_group', user_group_id=user_group_id))
196 return redirect(url('edit_users_group', user_group_id=user_group_id))
203
197
204 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
198 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
205 @auth.CSRFRequired()
199 @auth.CSRFRequired()
206 def delete(self, user_group_id):
200 def delete(self, user_group_id):
207 user_group_id = safe_int(user_group_id)
201 user_group_id = safe_int(user_group_id)
208 c.user_group = UserGroup.get_or_404(user_group_id)
202 c.user_group = UserGroup.get_or_404(user_group_id)
209 force = str2bool(request.POST.get('force'))
203 force = str2bool(request.POST.get('force'))
210
204
211 old_values = c.user_group.get_api_data()
205 old_values = c.user_group.get_api_data()
212 try:
206 try:
213 UserGroupModel().delete(c.user_group, force=force)
207 UserGroupModel().delete(c.user_group, force=force)
214 audit_logger.store_web(
208 audit_logger.store_web(
215 'user.delete', action_data={'old_data': old_values},
209 'user.delete', action_data={'old_data': old_values},
216 user=c.rhodecode_user)
210 user=c.rhodecode_user)
217 Session().commit()
211 Session().commit()
218 h.flash(_('Successfully deleted user group'), category='success')
212 h.flash(_('Successfully deleted user group'), category='success')
219 except UserGroupAssignedException as e:
213 except UserGroupAssignedException as e:
220 h.flash(str(e), category='error')
214 h.flash(str(e), category='error')
221 except Exception:
215 except Exception:
222 log.exception("Exception during deletion of user group")
216 log.exception("Exception during deletion of user group")
223 h.flash(_('An error occurred during deletion of user group'),
217 h.flash(_('An error occurred during deletion of user group'),
224 category='error')
218 category='error')
225 return redirect(url('users_groups'))
219 return redirect(url('users_groups'))
226
220
227 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
221 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
228 def edit(self, user_group_id):
222 def edit(self, user_group_id):
229 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
223 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
230 # url('edit_users_group', user_group_id=ID)
224 # url('edit_users_group', user_group_id=ID)
231
225
232 user_group_id = safe_int(user_group_id)
226 user_group_id = safe_int(user_group_id)
233 c.user_group = UserGroup.get_or_404(user_group_id)
227 c.user_group = UserGroup.get_or_404(user_group_id)
234 c.active = 'settings'
228 c.active = 'settings'
235 self.__load_data(user_group_id)
229 self.__load_data(user_group_id)
236
230
237 defaults = self.__load_defaults(user_group_id)
231 defaults = self.__load_defaults(user_group_id)
238
232
239 return htmlfill.render(
233 return htmlfill.render(
240 render('admin/user_groups/user_group_edit.mako'),
234 render('admin/user_groups/user_group_edit.mako'),
241 defaults=defaults,
235 defaults=defaults,
242 encoding="UTF-8",
236 encoding="UTF-8",
243 force_defaults=False
237 force_defaults=False
244 )
238 )
245
239
246 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
240 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
247 def edit_perms(self, user_group_id):
241 def edit_perms(self, user_group_id):
248 user_group_id = safe_int(user_group_id)
242 user_group_id = safe_int(user_group_id)
249 c.user_group = UserGroup.get_or_404(user_group_id)
243 c.user_group = UserGroup.get_or_404(user_group_id)
250 c.active = 'perms'
244 c.active = 'perms'
251
245
252 defaults = {}
246 defaults = {}
253 # fill user group users
247 # fill user group users
254 for p in c.user_group.user_user_group_to_perm:
248 for p in c.user_group.user_user_group_to_perm:
255 defaults.update({'u_perm_%s' % p.user.user_id:
249 defaults.update({'u_perm_%s' % p.user.user_id:
256 p.permission.permission_name})
250 p.permission.permission_name})
257
251
258 for p in c.user_group.user_group_user_group_to_perm:
252 for p in c.user_group.user_group_user_group_to_perm:
259 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
253 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
260 p.permission.permission_name})
254 p.permission.permission_name})
261
255
262 return htmlfill.render(
256 return htmlfill.render(
263 render('admin/user_groups/user_group_edit.mako'),
257 render('admin/user_groups/user_group_edit.mako'),
264 defaults=defaults,
258 defaults=defaults,
265 encoding="UTF-8",
259 encoding="UTF-8",
266 force_defaults=False
260 force_defaults=False
267 )
261 )
268
262
269 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
263 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
270 @auth.CSRFRequired()
264 @auth.CSRFRequired()
271 def update_perms(self, user_group_id):
265 def update_perms(self, user_group_id):
272 """
266 """
273 grant permission for given usergroup
267 grant permission for given usergroup
274
268
275 :param user_group_id:
269 :param user_group_id:
276 """
270 """
277 user_group_id = safe_int(user_group_id)
271 user_group_id = safe_int(user_group_id)
278 c.user_group = UserGroup.get_or_404(user_group_id)
272 c.user_group = UserGroup.get_or_404(user_group_id)
279 form = UserGroupPermsForm()().to_python(request.POST)
273 form = UserGroupPermsForm()().to_python(request.POST)
280
274
281 if not c.rhodecode_user.is_admin:
275 if not c.rhodecode_user.is_admin:
282 if self._revoke_perms_on_yourself(form):
276 if self._revoke_perms_on_yourself(form):
283 msg = _('Cannot change permission for yourself as admin')
277 msg = _('Cannot change permission for yourself as admin')
284 h.flash(msg, category='warning')
278 h.flash(msg, category='warning')
285 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
279 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
286
280
287 try:
281 try:
288 UserGroupModel().update_permissions(user_group_id,
282 UserGroupModel().update_permissions(user_group_id,
289 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
283 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
290 except RepoGroupAssignmentError:
284 except RepoGroupAssignmentError:
291 h.flash(_('Target group cannot be the same'), category='error')
285 h.flash(_('Target group cannot be the same'), category='error')
292 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
286 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
293
287
294 # TODO(marcink): implement global permissions
288 # TODO(marcink): implement global permissions
295 # audit_log.store_web('user_group.edit.permissions')
289 # audit_log.store_web('user_group.edit.permissions')
296 Session().commit()
290 Session().commit()
297 h.flash(_('User Group permissions updated'), category='success')
291 h.flash(_('User Group permissions updated'), category='success')
298 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
292 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
299
293
300
294
301
295
302 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
296 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
303 def edit_global_perms(self, user_group_id):
297 def edit_global_perms(self, user_group_id):
304 user_group_id = safe_int(user_group_id)
298 user_group_id = safe_int(user_group_id)
305 c.user_group = UserGroup.get_or_404(user_group_id)
299 c.user_group = UserGroup.get_or_404(user_group_id)
306 c.active = 'global_perms'
300 c.active = 'global_perms'
307
301
308 c.default_user = User.get_default_user()
302 c.default_user = User.get_default_user()
309 defaults = c.user_group.get_dict()
303 defaults = c.user_group.get_dict()
310 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
304 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
311 defaults.update(c.user_group.get_default_perms())
305 defaults.update(c.user_group.get_default_perms())
312
306
313 return htmlfill.render(
307 return htmlfill.render(
314 render('admin/user_groups/user_group_edit.mako'),
308 render('admin/user_groups/user_group_edit.mako'),
315 defaults=defaults,
309 defaults=defaults,
316 encoding="UTF-8",
310 encoding="UTF-8",
317 force_defaults=False
311 force_defaults=False
318 )
312 )
319
313
320 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
314 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
321 @auth.CSRFRequired()
315 @auth.CSRFRequired()
322 def update_global_perms(self, user_group_id):
316 def update_global_perms(self, user_group_id):
323 user_group_id = safe_int(user_group_id)
317 user_group_id = safe_int(user_group_id)
324 user_group = UserGroup.get_or_404(user_group_id)
318 user_group = UserGroup.get_or_404(user_group_id)
325 c.active = 'global_perms'
319 c.active = 'global_perms'
326
320
327 try:
321 try:
328 # first stage that verifies the checkbox
322 # first stage that verifies the checkbox
329 _form = UserIndividualPermissionsForm()
323 _form = UserIndividualPermissionsForm()
330 form_result = _form.to_python(dict(request.POST))
324 form_result = _form.to_python(dict(request.POST))
331 inherit_perms = form_result['inherit_default_permissions']
325 inherit_perms = form_result['inherit_default_permissions']
332 user_group.inherit_default_permissions = inherit_perms
326 user_group.inherit_default_permissions = inherit_perms
333 Session().add(user_group)
327 Session().add(user_group)
334
328
335 if not inherit_perms:
329 if not inherit_perms:
336 # only update the individual ones if we un check the flag
330 # only update the individual ones if we un check the flag
337 _form = UserPermissionsForm(
331 _form = UserPermissionsForm(
338 [x[0] for x in c.repo_create_choices],
332 [x[0] for x in c.repo_create_choices],
339 [x[0] for x in c.repo_create_on_write_choices],
333 [x[0] for x in c.repo_create_on_write_choices],
340 [x[0] for x in c.repo_group_create_choices],
334 [x[0] for x in c.repo_group_create_choices],
341 [x[0] for x in c.user_group_create_choices],
335 [x[0] for x in c.user_group_create_choices],
342 [x[0] for x in c.fork_choices],
336 [x[0] for x in c.fork_choices],
343 [x[0] for x in c.inherit_default_permission_choices])()
337 [x[0] for x in c.inherit_default_permission_choices])()
344
338
345 form_result = _form.to_python(dict(request.POST))
339 form_result = _form.to_python(dict(request.POST))
346 form_result.update({'perm_user_group_id': user_group.users_group_id})
340 form_result.update({'perm_user_group_id': user_group.users_group_id})
347
341
348 PermissionModel().update_user_group_permissions(form_result)
342 PermissionModel().update_user_group_permissions(form_result)
349
343
350 Session().commit()
344 Session().commit()
351 h.flash(_('User Group global permissions updated successfully'),
345 h.flash(_('User Group global permissions updated successfully'),
352 category='success')
346 category='success')
353
347
354 except formencode.Invalid as errors:
348 except formencode.Invalid as errors:
355 defaults = errors.value
349 defaults = errors.value
356 c.user_group = user_group
350 c.user_group = user_group
357 return htmlfill.render(
351 return htmlfill.render(
358 render('admin/user_groups/user_group_edit.mako'),
352 render('admin/user_groups/user_group_edit.mako'),
359 defaults=defaults,
353 defaults=defaults,
360 errors=errors.error_dict or {},
354 errors=errors.error_dict or {},
361 prefix_error=False,
355 prefix_error=False,
362 encoding="UTF-8",
356 encoding="UTF-8",
363 force_defaults=False)
357 force_defaults=False)
364 except Exception:
358 except Exception:
365 log.exception("Exception during permissions saving")
359 log.exception("Exception during permissions saving")
366 h.flash(_('An error occurred during permissions saving'),
360 h.flash(_('An error occurred during permissions saving'),
367 category='error')
361 category='error')
368
362
369 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
363 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
370
364
371 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
365 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
372 def edit_advanced(self, user_group_id):
366 def edit_advanced(self, user_group_id):
373 user_group_id = safe_int(user_group_id)
367 user_group_id = safe_int(user_group_id)
374 c.user_group = UserGroup.get_or_404(user_group_id)
368 c.user_group = UserGroup.get_or_404(user_group_id)
375 c.active = 'advanced'
369 c.active = 'advanced'
376 c.group_members_obj = sorted(
370 c.group_members_obj = sorted(
377 (x.user for x in c.user_group.members),
371 (x.user for x in c.user_group.members),
378 key=lambda u: u.username.lower())
372 key=lambda u: u.username.lower())
379
373
380 c.group_to_repos = sorted(
374 c.group_to_repos = sorted(
381 (x.repository for x in c.user_group.users_group_repo_to_perm),
375 (x.repository for x in c.user_group.users_group_repo_to_perm),
382 key=lambda u: u.repo_name.lower())
376 key=lambda u: u.repo_name.lower())
383
377
384 c.group_to_repo_groups = sorted(
378 c.group_to_repo_groups = sorted(
385 (x.group for x in c.user_group.users_group_repo_group_to_perm),
379 (x.group for x in c.user_group.users_group_repo_group_to_perm),
386 key=lambda u: u.group_name.lower())
380 key=lambda u: u.group_name.lower())
387
381
388 return render('admin/user_groups/user_group_edit.mako')
382 return render('admin/user_groups/user_group_edit.mako')
389
383
390 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
384 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
391 def edit_advanced_set_synchronization(self, user_group_id):
385 def edit_advanced_set_synchronization(self, user_group_id):
392 user_group_id = safe_int(user_group_id)
386 user_group_id = safe_int(user_group_id)
393 user_group = UserGroup.get_or_404(user_group_id)
387 user_group = UserGroup.get_or_404(user_group_id)
394
388
395 existing = user_group.group_data.get('extern_type')
389 existing = user_group.group_data.get('extern_type')
396
390
397 if existing:
391 if existing:
398 new_state = user_group.group_data
392 new_state = user_group.group_data
399 new_state['extern_type'] = None
393 new_state['extern_type'] = None
400 else:
394 else:
401 new_state = user_group.group_data
395 new_state = user_group.group_data
402 new_state['extern_type'] = 'manual'
396 new_state['extern_type'] = 'manual'
403 new_state['extern_type_set_by'] = c.rhodecode_user.username
397 new_state['extern_type_set_by'] = c.rhodecode_user.username
404
398
405 try:
399 try:
406 user_group.group_data = new_state
400 user_group.group_data = new_state
407 Session().add(user_group)
401 Session().add(user_group)
408 Session().commit()
402 Session().commit()
409
403
410 h.flash(_('User Group synchronization updated successfully'),
404 h.flash(_('User Group synchronization updated successfully'),
411 category='success')
405 category='success')
412 except Exception:
406 except Exception:
413 log.exception("Exception during sync settings saving")
407 log.exception("Exception during sync settings saving")
414 h.flash(_('An error occurred during synchronization update'),
408 h.flash(_('An error occurred during synchronization update'),
415 category='error')
409 category='error')
416
410
417 return redirect(
411 return redirect(
418 url('edit_user_group_advanced', user_group_id=user_group_id))
412 url('edit_user_group_advanced', user_group_id=user_group_id))
419
413
@@ -1,712 +1,617 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32 import pyramid.threadlocal
32 import pyramid.threadlocal
33
33
34 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.auth.basic import AuthBasicAuthenticator
35 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
36 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
37 from pylons import tmpl_context as c, request, url
38 from pylons.controllers import WSGIController
39 from pylons.controllers.util import redirect
40 from pylons.i18n import translation
41 # marcink: don't remove this import
42 from pylons.templating import render_mako, pylons_globals, literal, cached_template
43 from pylons.i18n.translation import _
44 from webob.exc import HTTPFound
45
46
37
47 import rhodecode
38 import rhodecode
48 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.authentication.base import VCS_TYPE
49 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import auth, utils2
50 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
51 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
52 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.exceptions import UserCreationError
53 from rhodecode.lib.utils import (
44 from rhodecode.lib.utils import (
54 get_repo_slug, set_rhodecode_config, password_changed,
45 get_repo_slug, set_rhodecode_config, password_changed,
55 get_enabled_hook_classes)
46 get_enabled_hook_classes)
56 from rhodecode.lib.utils2 import (
47 from rhodecode.lib.utils2 import (
57 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
48 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
58 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
59 from rhodecode.model import meta
49 from rhodecode.model import meta
60 from rhodecode.model.db import Repository, User, ChangesetComment
50 from rhodecode.model.db import Repository, User, ChangesetComment
61 from rhodecode.model.notification import NotificationModel
51 from rhodecode.model.notification import NotificationModel
62 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
63 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
53 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
64
54
55 # NOTE(marcink): remove after base controller is no longer required
56 from pylons.controllers import WSGIController
57 from pylons.i18n import translation
65
58
66 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
67
60
68
61
69 # hack to make the migration to pyramid easier
62 # hack to make the migration to pyramid easier
70 def render(template_name, extra_vars=None, cache_key=None,
63 def render(template_name, extra_vars=None, cache_key=None,
71 cache_type=None, cache_expire=None):
64 cache_type=None, cache_expire=None):
72 """Render a template with Mako
65 """Render a template with Mako
73
66
74 Accepts the cache options ``cache_key``, ``cache_type``, and
67 Accepts the cache options ``cache_key``, ``cache_type``, and
75 ``cache_expire``.
68 ``cache_expire``.
76
69
77 """
70 """
71 from pylons.templating import literal
72 from pylons.templating import cached_template, pylons_globals
73
78 # Create a render callable for the cache function
74 # Create a render callable for the cache function
79 def render_template():
75 def render_template():
80 # Pull in extra vars if needed
76 # Pull in extra vars if needed
81 globs = extra_vars or {}
77 globs = extra_vars or {}
82
78
83 # Second, get the globals
79 # Second, get the globals
84 globs.update(pylons_globals())
80 globs.update(pylons_globals())
85
81
86 globs['_ungettext'] = globs['ungettext']
82 globs['_ungettext'] = globs['ungettext']
87 # Grab a template reference
83 # Grab a template reference
88 template = globs['app_globals'].mako_lookup.get_template(template_name)
84 template = globs['app_globals'].mako_lookup.get_template(template_name)
89
85
90 return literal(template.render_unicode(**globs))
86 return literal(template.render_unicode(**globs))
91
87
92 return cached_template(template_name, render_template, cache_key=cache_key,
88 return cached_template(template_name, render_template, cache_key=cache_key,
93 cache_type=cache_type, cache_expire=cache_expire)
89 cache_type=cache_type, cache_expire=cache_expire)
94
90
95 def _filter_proxy(ip):
91 def _filter_proxy(ip):
96 """
92 """
97 Passed in IP addresses in HEADERS can be in a special format of multiple
93 Passed in IP addresses in HEADERS can be in a special format of multiple
98 ips. Those comma separated IPs are passed from various proxies in the
94 ips. Those comma separated IPs are passed from various proxies in the
99 chain of request processing. The left-most being the original client.
95 chain of request processing. The left-most being the original client.
100 We only care about the first IP which came from the org. client.
96 We only care about the first IP which came from the org. client.
101
97
102 :param ip: ip string from headers
98 :param ip: ip string from headers
103 """
99 """
104 if ',' in ip:
100 if ',' in ip:
105 _ips = ip.split(',')
101 _ips = ip.split(',')
106 _first_ip = _ips[0].strip()
102 _first_ip = _ips[0].strip()
107 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
103 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
108 return _first_ip
104 return _first_ip
109 return ip
105 return ip
110
106
111
107
112 def _filter_port(ip):
108 def _filter_port(ip):
113 """
109 """
114 Removes a port from ip, there are 4 main cases to handle here.
110 Removes a port from ip, there are 4 main cases to handle here.
115 - ipv4 eg. 127.0.0.1
111 - ipv4 eg. 127.0.0.1
116 - ipv6 eg. ::1
112 - ipv6 eg. ::1
117 - ipv4+port eg. 127.0.0.1:8080
113 - ipv4+port eg. 127.0.0.1:8080
118 - ipv6+port eg. [::1]:8080
114 - ipv6+port eg. [::1]:8080
119
115
120 :param ip:
116 :param ip:
121 """
117 """
122 def is_ipv6(ip_addr):
118 def is_ipv6(ip_addr):
123 if hasattr(socket, 'inet_pton'):
119 if hasattr(socket, 'inet_pton'):
124 try:
120 try:
125 socket.inet_pton(socket.AF_INET6, ip_addr)
121 socket.inet_pton(socket.AF_INET6, ip_addr)
126 except socket.error:
122 except socket.error:
127 return False
123 return False
128 else:
124 else:
129 # fallback to ipaddress
125 # fallback to ipaddress
130 try:
126 try:
131 ipaddress.IPv6Address(safe_unicode(ip_addr))
127 ipaddress.IPv6Address(safe_unicode(ip_addr))
132 except Exception:
128 except Exception:
133 return False
129 return False
134 return True
130 return True
135
131
136 if ':' not in ip: # must be ipv4 pure ip
132 if ':' not in ip: # must be ipv4 pure ip
137 return ip
133 return ip
138
134
139 if '[' in ip and ']' in ip: # ipv6 with port
135 if '[' in ip and ']' in ip: # ipv6 with port
140 return ip.split(']')[0][1:].lower()
136 return ip.split(']')[0][1:].lower()
141
137
142 # must be ipv6 or ipv4 with port
138 # must be ipv6 or ipv4 with port
143 if is_ipv6(ip):
139 if is_ipv6(ip):
144 return ip
140 return ip
145 else:
141 else:
146 ip, _port = ip.split(':')[:2] # means ipv4+port
142 ip, _port = ip.split(':')[:2] # means ipv4+port
147 return ip
143 return ip
148
144
149
145
150 def get_ip_addr(environ):
146 def get_ip_addr(environ):
151 proxy_key = 'HTTP_X_REAL_IP'
147 proxy_key = 'HTTP_X_REAL_IP'
152 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
148 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
153 def_key = 'REMOTE_ADDR'
149 def_key = 'REMOTE_ADDR'
154 _filters = lambda x: _filter_port(_filter_proxy(x))
150 _filters = lambda x: _filter_port(_filter_proxy(x))
155
151
156 ip = environ.get(proxy_key)
152 ip = environ.get(proxy_key)
157 if ip:
153 if ip:
158 return _filters(ip)
154 return _filters(ip)
159
155
160 ip = environ.get(proxy_key2)
156 ip = environ.get(proxy_key2)
161 if ip:
157 if ip:
162 return _filters(ip)
158 return _filters(ip)
163
159
164 ip = environ.get(def_key, '0.0.0.0')
160 ip = environ.get(def_key, '0.0.0.0')
165 return _filters(ip)
161 return _filters(ip)
166
162
167
163
168 def get_server_ip_addr(environ, log_errors=True):
164 def get_server_ip_addr(environ, log_errors=True):
169 hostname = environ.get('SERVER_NAME')
165 hostname = environ.get('SERVER_NAME')
170 try:
166 try:
171 return socket.gethostbyname(hostname)
167 return socket.gethostbyname(hostname)
172 except Exception as e:
168 except Exception as e:
173 if log_errors:
169 if log_errors:
174 # in some cases this lookup is not possible, and we don't want to
170 # in some cases this lookup is not possible, and we don't want to
175 # make it an exception in logs
171 # make it an exception in logs
176 log.exception('Could not retrieve server ip address: %s', e)
172 log.exception('Could not retrieve server ip address: %s', e)
177 return hostname
173 return hostname
178
174
179
175
180 def get_server_port(environ):
176 def get_server_port(environ):
181 return environ.get('SERVER_PORT')
177 return environ.get('SERVER_PORT')
182
178
183
179
184 def get_access_path(environ):
180 def get_access_path(environ):
185 path = environ.get('PATH_INFO')
181 path = environ.get('PATH_INFO')
186 org_req = environ.get('pylons.original_request')
182 org_req = environ.get('pylons.original_request')
187 if org_req:
183 if org_req:
188 path = org_req.environ.get('PATH_INFO')
184 path = org_req.environ.get('PATH_INFO')
189 return path
185 return path
190
186
191
187
192 def get_user_agent(environ):
188 def get_user_agent(environ):
193 return environ.get('HTTP_USER_AGENT')
189 return environ.get('HTTP_USER_AGENT')
194
190
195
191
196 def vcs_operation_context(
192 def vcs_operation_context(
197 environ, repo_name, username, action, scm, check_locking=True,
193 environ, repo_name, username, action, scm, check_locking=True,
198 is_shadow_repo=False):
194 is_shadow_repo=False):
199 """
195 """
200 Generate the context for a vcs operation, e.g. push or pull.
196 Generate the context for a vcs operation, e.g. push or pull.
201
197
202 This context is passed over the layers so that hooks triggered by the
198 This context is passed over the layers so that hooks triggered by the
203 vcs operation know details like the user, the user's IP address etc.
199 vcs operation know details like the user, the user's IP address etc.
204
200
205 :param check_locking: Allows to switch of the computation of the locking
201 :param check_locking: Allows to switch of the computation of the locking
206 data. This serves mainly the need of the simplevcs middleware to be
202 data. This serves mainly the need of the simplevcs middleware to be
207 able to disable this for certain operations.
203 able to disable this for certain operations.
208
204
209 """
205 """
210 # Tri-state value: False: unlock, None: nothing, True: lock
206 # Tri-state value: False: unlock, None: nothing, True: lock
211 make_lock = None
207 make_lock = None
212 locked_by = [None, None, None]
208 locked_by = [None, None, None]
213 is_anonymous = username == User.DEFAULT_USER
209 is_anonymous = username == User.DEFAULT_USER
214 if not is_anonymous and check_locking:
210 if not is_anonymous and check_locking:
215 log.debug('Checking locking on repository "%s"', repo_name)
211 log.debug('Checking locking on repository "%s"', repo_name)
216 user = User.get_by_username(username)
212 user = User.get_by_username(username)
217 repo = Repository.get_by_repo_name(repo_name)
213 repo = Repository.get_by_repo_name(repo_name)
218 make_lock, __, locked_by = repo.get_locking_state(
214 make_lock, __, locked_by = repo.get_locking_state(
219 action, user.user_id)
215 action, user.user_id)
220
216
221 settings_model = VcsSettingsModel(repo=repo_name)
217 settings_model = VcsSettingsModel(repo=repo_name)
222 ui_settings = settings_model.get_ui_settings()
218 ui_settings = settings_model.get_ui_settings()
223
219
224 extras = {
220 extras = {
225 'ip': get_ip_addr(environ),
221 'ip': get_ip_addr(environ),
226 'username': username,
222 'username': username,
227 'action': action,
223 'action': action,
228 'repository': repo_name,
224 'repository': repo_name,
229 'scm': scm,
225 'scm': scm,
230 'config': rhodecode.CONFIG['__file__'],
226 'config': rhodecode.CONFIG['__file__'],
231 'make_lock': make_lock,
227 'make_lock': make_lock,
232 'locked_by': locked_by,
228 'locked_by': locked_by,
233 'server_url': utils2.get_server_url(environ),
229 'server_url': utils2.get_server_url(environ),
234 'user_agent': get_user_agent(environ),
230 'user_agent': get_user_agent(environ),
235 'hooks': get_enabled_hook_classes(ui_settings),
231 'hooks': get_enabled_hook_classes(ui_settings),
236 'is_shadow_repo': is_shadow_repo,
232 'is_shadow_repo': is_shadow_repo,
237 }
233 }
238 return extras
234 return extras
239
235
240
236
241 class BasicAuth(AuthBasicAuthenticator):
237 class BasicAuth(AuthBasicAuthenticator):
242
238
243 def __init__(self, realm, authfunc, registry, auth_http_code=None,
239 def __init__(self, realm, authfunc, registry, auth_http_code=None,
244 initial_call_detection=False, acl_repo_name=None):
240 initial_call_detection=False, acl_repo_name=None):
245 self.realm = realm
241 self.realm = realm
246 self.initial_call = initial_call_detection
242 self.initial_call = initial_call_detection
247 self.authfunc = authfunc
243 self.authfunc = authfunc
248 self.registry = registry
244 self.registry = registry
249 self.acl_repo_name = acl_repo_name
245 self.acl_repo_name = acl_repo_name
250 self._rc_auth_http_code = auth_http_code
246 self._rc_auth_http_code = auth_http_code
251
247
252 def _get_response_from_code(self, http_code):
248 def _get_response_from_code(self, http_code):
253 try:
249 try:
254 return get_exception(safe_int(http_code))
250 return get_exception(safe_int(http_code))
255 except Exception:
251 except Exception:
256 log.exception('Failed to fetch response for code %s' % http_code)
252 log.exception('Failed to fetch response for code %s' % http_code)
257 return HTTPForbidden
253 return HTTPForbidden
258
254
259 def build_authentication(self):
255 def build_authentication(self):
260 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
256 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
261 if self._rc_auth_http_code and not self.initial_call:
257 if self._rc_auth_http_code and not self.initial_call:
262 # return alternative HTTP code if alternative http return code
258 # return alternative HTTP code if alternative http return code
263 # is specified in RhodeCode config, but ONLY if it's not the
259 # is specified in RhodeCode config, but ONLY if it's not the
264 # FIRST call
260 # FIRST call
265 custom_response_klass = self._get_response_from_code(
261 custom_response_klass = self._get_response_from_code(
266 self._rc_auth_http_code)
262 self._rc_auth_http_code)
267 return custom_response_klass(headers=head)
263 return custom_response_klass(headers=head)
268 return HTTPUnauthorized(headers=head)
264 return HTTPUnauthorized(headers=head)
269
265
270 def authenticate(self, environ):
266 def authenticate(self, environ):
271 authorization = AUTHORIZATION(environ)
267 authorization = AUTHORIZATION(environ)
272 if not authorization:
268 if not authorization:
273 return self.build_authentication()
269 return self.build_authentication()
274 (authmeth, auth) = authorization.split(' ', 1)
270 (authmeth, auth) = authorization.split(' ', 1)
275 if 'basic' != authmeth.lower():
271 if 'basic' != authmeth.lower():
276 return self.build_authentication()
272 return self.build_authentication()
277 auth = auth.strip().decode('base64')
273 auth = auth.strip().decode('base64')
278 _parts = auth.split(':', 1)
274 _parts = auth.split(':', 1)
279 if len(_parts) == 2:
275 if len(_parts) == 2:
280 username, password = _parts
276 username, password = _parts
281 if self.authfunc(
277 if self.authfunc(
282 username, password, environ, VCS_TYPE,
278 username, password, environ, VCS_TYPE,
283 registry=self.registry, acl_repo_name=self.acl_repo_name):
279 registry=self.registry, acl_repo_name=self.acl_repo_name):
284 return username
280 return username
285 if username and password:
281 if username and password:
286 # we mark that we actually executed authentication once, at
282 # we mark that we actually executed authentication once, at
287 # that point we can use the alternative auth code
283 # that point we can use the alternative auth code
288 self.initial_call = False
284 self.initial_call = False
289
285
290 return self.build_authentication()
286 return self.build_authentication()
291
287
292 __call__ = authenticate
288 __call__ = authenticate
293
289
294
290
295 def calculate_version_hash(config):
291 def calculate_version_hash(config):
296 return md5(
292 return md5(
297 config.get('beaker.session.secret', '') +
293 config.get('beaker.session.secret', '') +
298 rhodecode.__version__)[:8]
294 rhodecode.__version__)[:8]
299
295
300
296
301 def get_current_lang(request):
297 def get_current_lang(request):
302 # NOTE(marcink): remove after pyramid move
298 # NOTE(marcink): remove after pyramid move
303 try:
299 try:
304 return translation.get_lang()[0]
300 return translation.get_lang()[0]
305 except:
301 except:
306 pass
302 pass
307
303
308 return getattr(request, '_LOCALE_', request.locale_name)
304 return getattr(request, '_LOCALE_', request.locale_name)
309
305
310
306
311 def attach_context_attributes(context, request, user_id):
307 def attach_context_attributes(context, request, user_id):
312 """
308 """
313 Attach variables into template context called `c`, please note that
309 Attach variables into template context called `c`, please note that
314 request could be pylons or pyramid request in here.
310 request could be pylons or pyramid request in here.
315 """
311 """
316 # NOTE(marcink): remove check after pyramid migration
312 # NOTE(marcink): remove check after pyramid migration
317 if hasattr(request, 'registry'):
313 if hasattr(request, 'registry'):
318 config = request.registry.settings
314 config = request.registry.settings
319 else:
315 else:
320 from pylons import config
316 from pylons import config
321
317
322 rc_config = SettingsModel().get_all_settings(cache=True)
318 rc_config = SettingsModel().get_all_settings(cache=True)
323
319
324 context.rhodecode_version = rhodecode.__version__
320 context.rhodecode_version = rhodecode.__version__
325 context.rhodecode_edition = config.get('rhodecode.edition')
321 context.rhodecode_edition = config.get('rhodecode.edition')
326 # unique secret + version does not leak the version but keep consistency
322 # unique secret + version does not leak the version but keep consistency
327 context.rhodecode_version_hash = calculate_version_hash(config)
323 context.rhodecode_version_hash = calculate_version_hash(config)
328
324
329 # Default language set for the incoming request
325 # Default language set for the incoming request
330 context.language = get_current_lang(request)
326 context.language = get_current_lang(request)
331
327
332 # Visual options
328 # Visual options
333 context.visual = AttributeDict({})
329 context.visual = AttributeDict({})
334
330
335 # DB stored Visual Items
331 # DB stored Visual Items
336 context.visual.show_public_icon = str2bool(
332 context.visual.show_public_icon = str2bool(
337 rc_config.get('rhodecode_show_public_icon'))
333 rc_config.get('rhodecode_show_public_icon'))
338 context.visual.show_private_icon = str2bool(
334 context.visual.show_private_icon = str2bool(
339 rc_config.get('rhodecode_show_private_icon'))
335 rc_config.get('rhodecode_show_private_icon'))
340 context.visual.stylify_metatags = str2bool(
336 context.visual.stylify_metatags = str2bool(
341 rc_config.get('rhodecode_stylify_metatags'))
337 rc_config.get('rhodecode_stylify_metatags'))
342 context.visual.dashboard_items = safe_int(
338 context.visual.dashboard_items = safe_int(
343 rc_config.get('rhodecode_dashboard_items', 100))
339 rc_config.get('rhodecode_dashboard_items', 100))
344 context.visual.admin_grid_items = safe_int(
340 context.visual.admin_grid_items = safe_int(
345 rc_config.get('rhodecode_admin_grid_items', 100))
341 rc_config.get('rhodecode_admin_grid_items', 100))
346 context.visual.repository_fields = str2bool(
342 context.visual.repository_fields = str2bool(
347 rc_config.get('rhodecode_repository_fields'))
343 rc_config.get('rhodecode_repository_fields'))
348 context.visual.show_version = str2bool(
344 context.visual.show_version = str2bool(
349 rc_config.get('rhodecode_show_version'))
345 rc_config.get('rhodecode_show_version'))
350 context.visual.use_gravatar = str2bool(
346 context.visual.use_gravatar = str2bool(
351 rc_config.get('rhodecode_use_gravatar'))
347 rc_config.get('rhodecode_use_gravatar'))
352 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
348 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
353 context.visual.default_renderer = rc_config.get(
349 context.visual.default_renderer = rc_config.get(
354 'rhodecode_markup_renderer', 'rst')
350 'rhodecode_markup_renderer', 'rst')
355 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
351 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
356 context.visual.rhodecode_support_url = \
352 context.visual.rhodecode_support_url = \
357 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
353 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
358
354
359 context.visual.affected_files_cut_off = 60
355 context.visual.affected_files_cut_off = 60
360
356
361 context.pre_code = rc_config.get('rhodecode_pre_code')
357 context.pre_code = rc_config.get('rhodecode_pre_code')
362 context.post_code = rc_config.get('rhodecode_post_code')
358 context.post_code = rc_config.get('rhodecode_post_code')
363 context.rhodecode_name = rc_config.get('rhodecode_title')
359 context.rhodecode_name = rc_config.get('rhodecode_title')
364 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
360 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
365 # if we have specified default_encoding in the request, it has more
361 # if we have specified default_encoding in the request, it has more
366 # priority
362 # priority
367 if request.GET.get('default_encoding'):
363 if request.GET.get('default_encoding'):
368 context.default_encodings.insert(0, request.GET.get('default_encoding'))
364 context.default_encodings.insert(0, request.GET.get('default_encoding'))
369 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
365 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
370
366
371 # INI stored
367 # INI stored
372 context.labs_active = str2bool(
368 context.labs_active = str2bool(
373 config.get('labs_settings_active', 'false'))
369 config.get('labs_settings_active', 'false'))
374 context.visual.allow_repo_location_change = str2bool(
370 context.visual.allow_repo_location_change = str2bool(
375 config.get('allow_repo_location_change', True))
371 config.get('allow_repo_location_change', True))
376 context.visual.allow_custom_hooks_settings = str2bool(
372 context.visual.allow_custom_hooks_settings = str2bool(
377 config.get('allow_custom_hooks_settings', True))
373 config.get('allow_custom_hooks_settings', True))
378 context.debug_style = str2bool(config.get('debug_style', False))
374 context.debug_style = str2bool(config.get('debug_style', False))
379
375
380 context.rhodecode_instanceid = config.get('instance_id')
376 context.rhodecode_instanceid = config.get('instance_id')
381
377
382 context.visual.cut_off_limit_diff = safe_int(
378 context.visual.cut_off_limit_diff = safe_int(
383 config.get('cut_off_limit_diff'))
379 config.get('cut_off_limit_diff'))
384 context.visual.cut_off_limit_file = safe_int(
380 context.visual.cut_off_limit_file = safe_int(
385 config.get('cut_off_limit_file'))
381 config.get('cut_off_limit_file'))
386
382
387 # AppEnlight
383 # AppEnlight
388 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
384 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
389 context.appenlight_api_public_key = config.get(
385 context.appenlight_api_public_key = config.get(
390 'appenlight.api_public_key', '')
386 'appenlight.api_public_key', '')
391 context.appenlight_server_url = config.get('appenlight.server_url', '')
387 context.appenlight_server_url = config.get('appenlight.server_url', '')
392
388
393 # JS template context
389 # JS template context
394 context.template_context = {
390 context.template_context = {
395 'repo_name': None,
391 'repo_name': None,
396 'repo_type': None,
392 'repo_type': None,
397 'repo_landing_commit': None,
393 'repo_landing_commit': None,
398 'rhodecode_user': {
394 'rhodecode_user': {
399 'username': None,
395 'username': None,
400 'email': None,
396 'email': None,
401 'notification_status': False
397 'notification_status': False
402 },
398 },
403 'visual': {
399 'visual': {
404 'default_renderer': None
400 'default_renderer': None
405 },
401 },
406 'commit_data': {
402 'commit_data': {
407 'commit_id': None
403 'commit_id': None
408 },
404 },
409 'pull_request_data': {'pull_request_id': None},
405 'pull_request_data': {'pull_request_id': None},
410 'timeago': {
406 'timeago': {
411 'refresh_time': 120 * 1000,
407 'refresh_time': 120 * 1000,
412 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
408 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
413 },
409 },
414 'pylons_dispatch': {
415 # 'controller': request.environ['pylons.routes_dict']['controller'],
416 # 'action': request.environ['pylons.routes_dict']['action'],
417 },
418 'pyramid_dispatch': {
410 'pyramid_dispatch': {
419
411
420 },
412 },
421 'extra': {'plugins': {}}
413 'extra': {'plugins': {}}
422 }
414 }
423 # END CONFIG VARS
415 # END CONFIG VARS
424
416
425 # TODO: This dosn't work when called from pylons compatibility tween.
417 # TODO: This dosn't work when called from pylons compatibility tween.
426 # Fix this and remove it from base controller.
418 # Fix this and remove it from base controller.
427 # context.repo_name = get_repo_slug(request) # can be empty
419 # context.repo_name = get_repo_slug(request) # can be empty
428
420
429 diffmode = 'sideside'
421 diffmode = 'sideside'
430 if request.GET.get('diffmode'):
422 if request.GET.get('diffmode'):
431 if request.GET['diffmode'] == 'unified':
423 if request.GET['diffmode'] == 'unified':
432 diffmode = 'unified'
424 diffmode = 'unified'
433 elif request.session.get('diffmode'):
425 elif request.session.get('diffmode'):
434 diffmode = request.session['diffmode']
426 diffmode = request.session['diffmode']
435
427
436 context.diffmode = diffmode
428 context.diffmode = diffmode
437
429
438 if request.session.get('diffmode') != diffmode:
430 if request.session.get('diffmode') != diffmode:
439 request.session['diffmode'] = diffmode
431 request.session['diffmode'] = diffmode
440
432
441 context.csrf_token = auth.get_csrf_token(session=request.session)
433 context.csrf_token = auth.get_csrf_token(session=request.session)
442 context.backends = rhodecode.BACKENDS.keys()
434 context.backends = rhodecode.BACKENDS.keys()
443 context.backends.sort()
435 context.backends.sort()
444 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
436 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
445
437
446 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
438 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
447 # given request will ALWAYS be pyramid one
439 # given request will ALWAYS be pyramid one
448 pyramid_request = pyramid.threadlocal.get_current_request()
440 pyramid_request = pyramid.threadlocal.get_current_request()
449 context.pyramid_request = pyramid_request
441 context.pyramid_request = pyramid_request
450
442
451 # web case
443 # web case
452 if hasattr(pyramid_request, 'user'):
444 if hasattr(pyramid_request, 'user'):
453 context.auth_user = pyramid_request.user
445 context.auth_user = pyramid_request.user
454 context.rhodecode_user = pyramid_request.user
446 context.rhodecode_user = pyramid_request.user
455
447
456 # api case
448 # api case
457 if hasattr(pyramid_request, 'rpc_user'):
449 if hasattr(pyramid_request, 'rpc_user'):
458 context.auth_user = pyramid_request.rpc_user
450 context.auth_user = pyramid_request.rpc_user
459 context.rhodecode_user = pyramid_request.rpc_user
451 context.rhodecode_user = pyramid_request.rpc_user
460
452
461 # attach the whole call context to the request
453 # attach the whole call context to the request
462 request.call_context = context
454 request.call_context = context
463
455
464
456
465 def get_auth_user(request):
457 def get_auth_user(request):
466 environ = request.environ
458 environ = request.environ
467 session = request.session
459 session = request.session
468
460
469 ip_addr = get_ip_addr(environ)
461 ip_addr = get_ip_addr(environ)
470 # make sure that we update permissions each time we call controller
462 # make sure that we update permissions each time we call controller
471 _auth_token = (request.GET.get('auth_token', '') or
463 _auth_token = (request.GET.get('auth_token', '') or
472 request.GET.get('api_key', ''))
464 request.GET.get('api_key', ''))
473
465
474 if _auth_token:
466 if _auth_token:
475 # when using API_KEY we assume user exists, and
467 # when using API_KEY we assume user exists, and
476 # doesn't need auth based on cookies.
468 # doesn't need auth based on cookies.
477 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
469 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
478 authenticated = False
470 authenticated = False
479 else:
471 else:
480 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
472 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
481 try:
473 try:
482 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
474 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
483 ip_addr=ip_addr)
475 ip_addr=ip_addr)
484 except UserCreationError as e:
476 except UserCreationError as e:
485 h.flash(e, 'error')
477 h.flash(e, 'error')
486 # container auth or other auth functions that create users
478 # container auth or other auth functions that create users
487 # on the fly can throw this exception signaling that there's
479 # on the fly can throw this exception signaling that there's
488 # issue with user creation, explanation should be provided
480 # issue with user creation, explanation should be provided
489 # in Exception itself. We then create a simple blank
481 # in Exception itself. We then create a simple blank
490 # AuthUser
482 # AuthUser
491 auth_user = AuthUser(ip_addr=ip_addr)
483 auth_user = AuthUser(ip_addr=ip_addr)
492
484
493 if password_changed(auth_user, session):
485 if password_changed(auth_user, session):
494 session.invalidate()
486 session.invalidate()
495 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
487 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
496 auth_user = AuthUser(ip_addr=ip_addr)
488 auth_user = AuthUser(ip_addr=ip_addr)
497
489
498 authenticated = cookie_store.get('is_authenticated')
490 authenticated = cookie_store.get('is_authenticated')
499
491
500 if not auth_user.is_authenticated and auth_user.is_user_object:
492 if not auth_user.is_authenticated and auth_user.is_user_object:
501 # user is not authenticated and not empty
493 # user is not authenticated and not empty
502 auth_user.set_authenticated(authenticated)
494 auth_user.set_authenticated(authenticated)
503
495
504 return auth_user
496 return auth_user
505
497
506
498
507 class BaseController(WSGIController):
499 class BaseController(WSGIController):
508
500
509 def __before__(self):
501 def __before__(self):
510 """
502 """
511 __before__ is called before controller methods and after __call__
503 __before__ is called before controller methods and after __call__
512 """
504 """
513 # on each call propagate settings calls into global settings.
505 # on each call propagate settings calls into global settings.
514 from pylons import config
506 from pylons import config
507 from pylons import tmpl_context as c, request, url
515 set_rhodecode_config(config)
508 set_rhodecode_config(config)
516 attach_context_attributes(c, request, self._rhodecode_user.user_id)
509 attach_context_attributes(c, request, self._rhodecode_user.user_id)
517
510
518 # TODO: Remove this when fixed in attach_context_attributes()
511 # TODO: Remove this when fixed in attach_context_attributes()
519 c.repo_name = get_repo_slug(request) # can be empty
512 c.repo_name = get_repo_slug(request) # can be empty
520
513
521 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
514 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
522 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
515 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
523 self.sa = meta.Session
516 self.sa = meta.Session
524 self.scm_model = ScmModel(self.sa)
517 self.scm_model = ScmModel(self.sa)
525
518
526 # set user language
519 # set user language
527 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
520 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
528 if user_lang:
521 if user_lang:
529 translation.set_lang(user_lang)
522 translation.set_lang(user_lang)
530 log.debug('set language to %s for user %s',
523 log.debug('set language to %s for user %s',
531 user_lang, self._rhodecode_user)
524 user_lang, self._rhodecode_user)
532
525
533 def _dispatch_redirect(self, with_url, environ, start_response):
526 def _dispatch_redirect(self, with_url, environ, start_response):
527 from webob.exc import HTTPFound
534 resp = HTTPFound(with_url)
528 resp = HTTPFound(with_url)
535 environ['SCRIPT_NAME'] = '' # handle prefix middleware
529 environ['SCRIPT_NAME'] = '' # handle prefix middleware
536 environ['PATH_INFO'] = with_url
530 environ['PATH_INFO'] = with_url
537 return resp(environ, start_response)
531 return resp(environ, start_response)
538
532
539 def __call__(self, environ, start_response):
533 def __call__(self, environ, start_response):
540 """Invoke the Controller"""
534 """Invoke the Controller"""
541 # WSGIController.__call__ dispatches to the Controller method
535 # WSGIController.__call__ dispatches to the Controller method
542 # the request is routed to. This routing information is
536 # the request is routed to. This routing information is
543 # available in environ['pylons.routes_dict']
537 # available in environ['pylons.routes_dict']
544 from rhodecode.lib import helpers as h
538 from rhodecode.lib import helpers as h
539 from pylons import tmpl_context as c, request, url
545
540
546 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
541 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
547 if environ.get('debugtoolbar.wants_pylons_context', False):
542 if environ.get('debugtoolbar.wants_pylons_context', False):
548 environ['debugtoolbar.pylons_context'] = c._current_obj()
543 environ['debugtoolbar.pylons_context'] = c._current_obj()
549
544
550 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
545 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
551 environ['pylons.routes_dict']['action']])
546 environ['pylons.routes_dict']['action']])
552
547
553 self.rc_config = SettingsModel().get_all_settings(cache=True)
548 self.rc_config = SettingsModel().get_all_settings(cache=True)
554 self.ip_addr = get_ip_addr(environ)
549 self.ip_addr = get_ip_addr(environ)
555
550
556 # The rhodecode auth user is looked up and passed through the
551 # The rhodecode auth user is looked up and passed through the
557 # environ by the pylons compatibility tween in pyramid.
552 # environ by the pylons compatibility tween in pyramid.
558 # So we can just grab it from there.
553 # So we can just grab it from there.
559 auth_user = environ['rc_auth_user']
554 auth_user = environ['rc_auth_user']
560
555
561 # set globals for auth user
556 # set globals for auth user
562 request.user = auth_user
557 request.user = auth_user
563 self._rhodecode_user = auth_user
558 self._rhodecode_user = auth_user
564
559
565 log.info('IP: %s User: %s accessed %s [%s]' % (
560 log.info('IP: %s User: %s accessed %s [%s]' % (
566 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
561 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
567 _route_name)
562 _route_name)
568 )
563 )
569
564
570 user_obj = auth_user.get_instance()
565 user_obj = auth_user.get_instance()
571 if user_obj and user_obj.user_data.get('force_password_change'):
566 if user_obj and user_obj.user_data.get('force_password_change'):
572 h.flash('You are required to change your password', 'warning',
567 h.flash('You are required to change your password', 'warning',
573 ignore_duplicate=True)
568 ignore_duplicate=True)
574 return self._dispatch_redirect(
569 return self._dispatch_redirect(
575 url('my_account_password'), environ, start_response)
570 url('my_account_password'), environ, start_response)
576
571
577 return WSGIController.__call__(self, environ, start_response)
572 return WSGIController.__call__(self, environ, start_response)
578
573
579
574
580 def h_filter(s):
575 def h_filter(s):
581 """
576 """
582 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
577 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
583 we wrap this with additional functionality that converts None to empty
578 we wrap this with additional functionality that converts None to empty
584 strings
579 strings
585 """
580 """
586 if s is None:
581 if s is None:
587 return markupsafe.Markup()
582 return markupsafe.Markup()
588 return markupsafe.escape(s)
583 return markupsafe.escape(s)
589
584
590
585
591 def add_events_routes(config):
586 def add_events_routes(config):
592 """
587 """
593 Adds routing that can be used in events. Because some events are triggered
588 Adds routing that can be used in events. Because some events are triggered
594 outside of pyramid context, we need to bootstrap request with some
589 outside of pyramid context, we need to bootstrap request with some
595 routing registered
590 routing registered
596 """
591 """
597 config.add_route(name='home', pattern='/')
592 config.add_route(name='home', pattern='/')
598
593
599 config.add_route(name='repo_summary', pattern='/{repo_name}')
594 config.add_route(name='repo_summary', pattern='/{repo_name}')
600 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
595 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
601 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
596 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
602
597
603 config.add_route(name='pullrequest_show',
598 config.add_route(name='pullrequest_show',
604 pattern='/{repo_name}/pull-request/{pull_request_id}')
599 pattern='/{repo_name}/pull-request/{pull_request_id}')
605 config.add_route(name='pull_requests_global',
600 config.add_route(name='pull_requests_global',
606 pattern='/pull-request/{pull_request_id}')
601 pattern='/pull-request/{pull_request_id}')
607
602
608 config.add_route(name='repo_commit',
603 config.add_route(name='repo_commit',
609 pattern='/{repo_name}/changeset/{commit_id}')
604 pattern='/{repo_name}/changeset/{commit_id}')
610 config.add_route(name='repo_files',
605 config.add_route(name='repo_files',
611 pattern='/{repo_name}/files/{commit_id}/{f_path}')
606 pattern='/{repo_name}/files/{commit_id}/{f_path}')
612
607
613
608
614 def bootstrap_request(**kwargs):
609 def bootstrap_request(**kwargs):
615 import pyramid.testing
610 import pyramid.testing
616 request = pyramid.testing.DummyRequest(**kwargs)
611 request = pyramid.testing.DummyRequest(**kwargs)
617 request.application_url = kwargs.pop('application_url', 'http://example.com')
612 request.application_url = kwargs.pop('application_url', 'http://example.com')
618 request.host = kwargs.pop('host', 'example.com:80')
613 request.host = kwargs.pop('host', 'example.com:80')
619 request.domain = kwargs.pop('domain', 'example.com')
614 request.domain = kwargs.pop('domain', 'example.com')
620
615
621 config = pyramid.testing.setUp(request=request)
616 config = pyramid.testing.setUp(request=request)
622 add_events_routes(config)
617 add_events_routes(config)
623
624
625 class BaseRepoController(BaseController):
626 """
627 Base class for controllers responsible for loading all needed data for
628 repository loaded items are
629
630 c.rhodecode_repo: instance of scm repository
631 c.rhodecode_db_repo: instance of db
632 c.repository_requirements_missing: shows that repository specific data
633 could not be displayed due to the missing requirements
634 c.repository_pull_requests: show number of open pull requests
635 """
636
637 def __before__(self):
638 super(BaseRepoController, self).__before__()
639 if c.repo_name: # extracted from routes
640 db_repo = Repository.get_by_repo_name(c.repo_name)
641 if not db_repo:
642 return
643
644 log.debug(
645 'Found repository in database %s with state `%s`',
646 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
647 route = getattr(request.environ.get('routes.route'), 'name', '')
648
649 # allow to delete repos that are somehow damages in filesystem
650 if route in ['delete_repo']:
651 return
652
653 if db_repo.repo_state in [Repository.STATE_PENDING]:
654 if route in ['repo_creating_home']:
655 return
656 check_url = url('repo_creating_home', repo_name=c.repo_name)
657 return redirect(check_url)
658
659 self.rhodecode_db_repo = db_repo
660
661 missing_requirements = False
662 try:
663 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
664 except RepositoryRequirementError as e:
665 missing_requirements = True
666 self._handle_missing_requirements(e)
667
668 if self.rhodecode_repo is None and not missing_requirements:
669 log.error('%s this repository is present in database but it '
670 'cannot be created as an scm instance', c.repo_name)
671
672 h.flash(_(
673 "The repository at %(repo_name)s cannot be located.") %
674 {'repo_name': c.repo_name},
675 category='error', ignore_duplicate=True)
676 redirect(h.route_path('home'))
677
678 # update last change according to VCS data
679 if not missing_requirements:
680 commit = db_repo.get_commit(
681 pre_load=["author", "date", "message", "parents"])
682 db_repo.update_commit_cache(commit)
683
684 # Prepare context
685 c.rhodecode_db_repo = db_repo
686 c.rhodecode_repo = self.rhodecode_repo
687 c.repository_requirements_missing = missing_requirements
688
689 self._update_global_counters(self.scm_model, db_repo)
690
691 def _update_global_counters(self, scm_model, db_repo):
692 """
693 Base variables that are exposed to every page of repository
694 """
695 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
696
697 def _handle_missing_requirements(self, error):
698 self.rhodecode_repo = None
699 log.error(
700 'Requirements are missing for repository %s: %s',
701 c.repo_name, error.message)
702
703 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
704 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
705 settings_update_url = url('repo', repo_name=c.repo_name)
706 path = request.path
707 should_redirect = (
708 path not in (summary_url, settings_update_url)
709 and '/settings' not in path or path == statistics_url
710 )
711 if should_redirect:
712 redirect(summary_url)
@@ -1,997 +1,973 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Utilities library for RhodeCode
22 Utilities library for RhodeCode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import decorator
26 import decorator
27 import json
27 import json
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import shutil
31 import shutil
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35 import warnings
35 import warnings
36 import hashlib
36 import hashlib
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39 import paste
39 import paste
40 import pkg_resources
40 import pkg_resources
41 from paste.script.command import Command, BadCommand
41 from paste.script.command import Command, BadCommand
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 from mako import exceptions
43 from mako import exceptions
44 from pyramid.threadlocal import get_current_registry
44 from pyramid.threadlocal import get_current_registry
45 from pyramid.request import Request
45 from pyramid.request import Request
46
46
47 from rhodecode.lib.fakemod import create_module
47 from rhodecode.lib.fakemod import create_module
48 from rhodecode.lib.vcs.backends.base import Config
48 from rhodecode.lib.vcs.backends.base import Config
49 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 from rhodecode.lib.utils2 import (
51 from rhodecode.lib.utils2 import (
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
53 from rhodecode.model import meta
53 from rhodecode.model import meta
54 from rhodecode.model.db import (
54 from rhodecode.model.db import (
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62
62
63 # String which contains characters that are not allowed in slug names for
63 # String which contains characters that are not allowed in slug names for
64 # repositories or repository groups. It is properly escaped to use it in
64 # repositories or repository groups. It is properly escaped to use it in
65 # regular expressions.
65 # regular expressions.
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
67
67
68 # Regex that matches forbidden characters in repo/group slugs.
68 # Regex that matches forbidden characters in repo/group slugs.
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
70
70
71 # Regex that matches allowed characters in repo/group slugs.
71 # Regex that matches allowed characters in repo/group slugs.
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
73
73
74 # Regex that matches whole repo/group slugs.
74 # Regex that matches whole repo/group slugs.
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
76
76
77 _license_cache = None
77 _license_cache = None
78
78
79
79
80 def repo_name_slug(value):
80 def repo_name_slug(value):
81 """
81 """
82 Return slug of name of repository
82 Return slug of name of repository
83 This function is called on each creation/modification
83 This function is called on each creation/modification
84 of repository to prevent bad names in repo
84 of repository to prevent bad names in repo
85 """
85 """
86 replacement_char = '-'
86 replacement_char = '-'
87
87
88 slug = remove_formatting(value)
88 slug = remove_formatting(value)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
90 slug = re.sub('[\s]+', '-', slug)
90 slug = re.sub('[\s]+', '-', slug)
91 slug = collapse(slug, replacement_char)
91 slug = collapse(slug, replacement_char)
92 return slug
92 return slug
93
93
94
94
95 #==============================================================================
95 #==============================================================================
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 #==============================================================================
97 #==============================================================================
98 def get_repo_slug(request):
98 def get_repo_slug(request):
99 _repo = ''
99 _repo = ''
100 if isinstance(request, Request):
100 if isinstance(request, Request):
101 if hasattr(request, 'db_repo'):
101 if hasattr(request, 'db_repo'):
102 # if our requests has set db reference use it for name, this
102 # if our requests has set db reference use it for name, this
103 # translates the example.com/_<id> into proper repo names
103 # translates the example.com/_<id> into proper repo names
104 _repo = request.db_repo.repo_name
104 _repo = request.db_repo.repo_name
105 elif getattr(request, 'matchdict', None):
105 elif getattr(request, 'matchdict', None):
106 # pyramid
106 # pyramid
107 _repo = request.matchdict.get('repo_name')
107 _repo = request.matchdict.get('repo_name')
108
108
109 # TODO(marcink): remove after pylons migration...
109 # TODO(marcink): remove after pylons migration...
110 if not _repo:
110 if not _repo:
111 _repo = request.environ['pylons.routes_dict'].get('repo_name')
111 _repo = request.environ['pylons.routes_dict'].get('repo_name')
112
112
113 if _repo:
113 if _repo:
114 _repo = _repo.rstrip('/')
114 _repo = _repo.rstrip('/')
115 return _repo
115 return _repo
116
116
117
117
118 def get_repo_group_slug(request):
118 def get_repo_group_slug(request):
119 _group = ''
119 _group = ''
120 if isinstance(request, Request):
120 if isinstance(request, Request):
121 if hasattr(request, 'db_repo_group'):
121 if hasattr(request, 'db_repo_group'):
122 # if our requests has set db reference use it for name, this
122 # if our requests has set db reference use it for name, this
123 # translates the example.com/_<id> into proper repo group names
123 # translates the example.com/_<id> into proper repo group names
124 _group = request.db_repo_group.group_name
124 _group = request.db_repo_group.group_name
125 elif getattr(request, 'matchdict', None):
125 elif getattr(request, 'matchdict', None):
126 # pyramid
126 # pyramid
127 _group = request.matchdict.get('repo_group_name')
127 _group = request.matchdict.get('repo_group_name')
128
128
129 # TODO(marcink): remove after pylons migration...
129 # TODO(marcink): remove after pylons migration...
130 if not _group:
130 if not _group:
131 _group = request.environ['pylons.routes_dict'].get('group_name')
131 _group = request.environ['pylons.routes_dict'].get('group_name')
132
132
133 if _group:
133 if _group:
134 _group = _group.rstrip('/')
134 _group = _group.rstrip('/')
135 return _group
135 return _group
136
136
137
137
138 def get_user_group_slug(request):
138 def get_user_group_slug(request):
139 if isinstance(request, Request) and getattr(request, 'matchdict', None):
139 if isinstance(request, Request) and getattr(request, 'matchdict', None):
140 # pyramid
140 # pyramid
141 _group = request.matchdict.get('user_group_id')
141 _group = request.matchdict.get('user_group_id')
142 else:
142 else:
143 _group = request.environ['pylons.routes_dict'].get('user_group_id')
143 _group = request.environ['pylons.routes_dict'].get('user_group_id')
144
144
145 try:
145 try:
146 _group = UserGroup.get(_group)
146 _group = UserGroup.get(_group)
147 if _group:
147 if _group:
148 _group = _group.users_group_name
148 _group = _group.users_group_name
149 except Exception:
149 except Exception:
150 log.exception('Failed to get user group by id')
150 log.exception('Failed to get user group by id')
151 # catch all failures here
151 # catch all failures here
152 return None
152 return None
153
153
154 return _group
154 return _group
155
155
156
156
157 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
157 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
158 """
158 """
159 Scans given path for repos and return (name,(type,path)) tuple
159 Scans given path for repos and return (name,(type,path)) tuple
160
160
161 :param path: path to scan for repositories
161 :param path: path to scan for repositories
162 :param recursive: recursive search and return names with subdirs in front
162 :param recursive: recursive search and return names with subdirs in front
163 """
163 """
164
164
165 # remove ending slash for better results
165 # remove ending slash for better results
166 path = path.rstrip(os.sep)
166 path = path.rstrip(os.sep)
167 log.debug('now scanning in %s location recursive:%s...', path, recursive)
167 log.debug('now scanning in %s location recursive:%s...', path, recursive)
168
168
169 def _get_repos(p):
169 def _get_repos(p):
170 dirpaths = _get_dirpaths(p)
170 dirpaths = _get_dirpaths(p)
171 if not _is_dir_writable(p):
171 if not _is_dir_writable(p):
172 log.warning('repo path without write access: %s', p)
172 log.warning('repo path without write access: %s', p)
173
173
174 for dirpath in dirpaths:
174 for dirpath in dirpaths:
175 if os.path.isfile(os.path.join(p, dirpath)):
175 if os.path.isfile(os.path.join(p, dirpath)):
176 continue
176 continue
177 cur_path = os.path.join(p, dirpath)
177 cur_path = os.path.join(p, dirpath)
178
178
179 # skip removed repos
179 # skip removed repos
180 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
180 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
181 continue
181 continue
182
182
183 #skip .<somethin> dirs
183 #skip .<somethin> dirs
184 if dirpath.startswith('.'):
184 if dirpath.startswith('.'):
185 continue
185 continue
186
186
187 try:
187 try:
188 scm_info = get_scm(cur_path)
188 scm_info = get_scm(cur_path)
189 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
189 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
190 except VCSError:
190 except VCSError:
191 if not recursive:
191 if not recursive:
192 continue
192 continue
193 #check if this dir containts other repos for recursive scan
193 #check if this dir containts other repos for recursive scan
194 rec_path = os.path.join(p, dirpath)
194 rec_path = os.path.join(p, dirpath)
195 if os.path.isdir(rec_path):
195 if os.path.isdir(rec_path):
196 for inner_scm in _get_repos(rec_path):
196 for inner_scm in _get_repos(rec_path):
197 yield inner_scm
197 yield inner_scm
198
198
199 return _get_repos(path)
199 return _get_repos(path)
200
200
201
201
202 def _get_dirpaths(p):
202 def _get_dirpaths(p):
203 try:
203 try:
204 # OS-independable way of checking if we have at least read-only
204 # OS-independable way of checking if we have at least read-only
205 # access or not.
205 # access or not.
206 dirpaths = os.listdir(p)
206 dirpaths = os.listdir(p)
207 except OSError:
207 except OSError:
208 log.warning('ignoring repo path without read access: %s', p)
208 log.warning('ignoring repo path without read access: %s', p)
209 return []
209 return []
210
210
211 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
211 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
212 # decode paths and suddenly returns unicode objects itself. The items it
212 # decode paths and suddenly returns unicode objects itself. The items it
213 # cannot decode are returned as strings and cause issues.
213 # cannot decode are returned as strings and cause issues.
214 #
214 #
215 # Those paths are ignored here until a solid solution for path handling has
215 # Those paths are ignored here until a solid solution for path handling has
216 # been built.
216 # been built.
217 expected_type = type(p)
217 expected_type = type(p)
218
218
219 def _has_correct_type(item):
219 def _has_correct_type(item):
220 if type(item) is not expected_type:
220 if type(item) is not expected_type:
221 log.error(
221 log.error(
222 u"Ignoring path %s since it cannot be decoded into unicode.",
222 u"Ignoring path %s since it cannot be decoded into unicode.",
223 # Using "repr" to make sure that we see the byte value in case
223 # Using "repr" to make sure that we see the byte value in case
224 # of support.
224 # of support.
225 repr(item))
225 repr(item))
226 return False
226 return False
227 return True
227 return True
228
228
229 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
229 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
230
230
231 return dirpaths
231 return dirpaths
232
232
233
233
234 def _is_dir_writable(path):
234 def _is_dir_writable(path):
235 """
235 """
236 Probe if `path` is writable.
236 Probe if `path` is writable.
237
237
238 Due to trouble on Cygwin / Windows, this is actually probing if it is
238 Due to trouble on Cygwin / Windows, this is actually probing if it is
239 possible to create a file inside of `path`, stat does not produce reliable
239 possible to create a file inside of `path`, stat does not produce reliable
240 results in this case.
240 results in this case.
241 """
241 """
242 try:
242 try:
243 with tempfile.TemporaryFile(dir=path):
243 with tempfile.TemporaryFile(dir=path):
244 pass
244 pass
245 except OSError:
245 except OSError:
246 return False
246 return False
247 return True
247 return True
248
248
249
249
250 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
250 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
251 """
251 """
252 Returns True if given path is a valid repository False otherwise.
252 Returns True if given path is a valid repository False otherwise.
253 If expect_scm param is given also, compare if given scm is the same
253 If expect_scm param is given also, compare if given scm is the same
254 as expected from scm parameter. If explicit_scm is given don't try to
254 as expected from scm parameter. If explicit_scm is given don't try to
255 detect the scm, just use the given one to check if repo is valid
255 detect the scm, just use the given one to check if repo is valid
256
256
257 :param repo_name:
257 :param repo_name:
258 :param base_path:
258 :param base_path:
259 :param expect_scm:
259 :param expect_scm:
260 :param explicit_scm:
260 :param explicit_scm:
261
261
262 :return True: if given path is a valid repository
262 :return True: if given path is a valid repository
263 """
263 """
264 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
264 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
265 log.debug('Checking if `%s` is a valid path for repository. '
265 log.debug('Checking if `%s` is a valid path for repository. '
266 'Explicit type: %s', repo_name, explicit_scm)
266 'Explicit type: %s', repo_name, explicit_scm)
267
267
268 try:
268 try:
269 if explicit_scm:
269 if explicit_scm:
270 detected_scms = [get_scm_backend(explicit_scm)]
270 detected_scms = [get_scm_backend(explicit_scm)]
271 else:
271 else:
272 detected_scms = get_scm(full_path)
272 detected_scms = get_scm(full_path)
273
273
274 if expect_scm:
274 if expect_scm:
275 return detected_scms[0] == expect_scm
275 return detected_scms[0] == expect_scm
276 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
276 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
277 return True
277 return True
278 except VCSError:
278 except VCSError:
279 log.debug('path: %s is not a valid repo !', full_path)
279 log.debug('path: %s is not a valid repo !', full_path)
280 return False
280 return False
281
281
282
282
283 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
283 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
284 """
284 """
285 Returns True if given path is a repository group, False otherwise
285 Returns True if given path is a repository group, False otherwise
286
286
287 :param repo_name:
287 :param repo_name:
288 :param base_path:
288 :param base_path:
289 """
289 """
290 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
290 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
291 log.debug('Checking if `%s` is a valid path for repository group',
291 log.debug('Checking if `%s` is a valid path for repository group',
292 repo_group_name)
292 repo_group_name)
293
293
294 # check if it's not a repo
294 # check if it's not a repo
295 if is_valid_repo(repo_group_name, base_path):
295 if is_valid_repo(repo_group_name, base_path):
296 log.debug('Repo called %s exist, it is not a valid '
296 log.debug('Repo called %s exist, it is not a valid '
297 'repo group' % repo_group_name)
297 'repo group' % repo_group_name)
298 return False
298 return False
299
299
300 try:
300 try:
301 # we need to check bare git repos at higher level
301 # we need to check bare git repos at higher level
302 # since we might match branches/hooks/info/objects or possible
302 # since we might match branches/hooks/info/objects or possible
303 # other things inside bare git repo
303 # other things inside bare git repo
304 scm_ = get_scm(os.path.dirname(full_path))
304 scm_ = get_scm(os.path.dirname(full_path))
305 log.debug('path: %s is a vcs object:%s, not valid '
305 log.debug('path: %s is a vcs object:%s, not valid '
306 'repo group' % (full_path, scm_))
306 'repo group' % (full_path, scm_))
307 return False
307 return False
308 except VCSError:
308 except VCSError:
309 pass
309 pass
310
310
311 # check if it's a valid path
311 # check if it's a valid path
312 if skip_path_check or os.path.isdir(full_path):
312 if skip_path_check or os.path.isdir(full_path):
313 log.debug('path: %s is a valid repo group !', full_path)
313 log.debug('path: %s is a valid repo group !', full_path)
314 return True
314 return True
315
315
316 log.debug('path: %s is not a valid repo group !', full_path)
316 log.debug('path: %s is not a valid repo group !', full_path)
317 return False
317 return False
318
318
319
319
320 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
320 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
321 while True:
321 while True:
322 ok = raw_input(prompt)
322 ok = raw_input(prompt)
323 if ok.lower() in ('y', 'ye', 'yes'):
323 if ok.lower() in ('y', 'ye', 'yes'):
324 return True
324 return True
325 if ok.lower() in ('n', 'no', 'nop', 'nope'):
325 if ok.lower() in ('n', 'no', 'nop', 'nope'):
326 return False
326 return False
327 retries = retries - 1
327 retries = retries - 1
328 if retries < 0:
328 if retries < 0:
329 raise IOError
329 raise IOError
330 print(complaint)
330 print(complaint)
331
331
332 # propagated from mercurial documentation
332 # propagated from mercurial documentation
333 ui_sections = [
333 ui_sections = [
334 'alias', 'auth',
334 'alias', 'auth',
335 'decode/encode', 'defaults',
335 'decode/encode', 'defaults',
336 'diff', 'email',
336 'diff', 'email',
337 'extensions', 'format',
337 'extensions', 'format',
338 'merge-patterns', 'merge-tools',
338 'merge-patterns', 'merge-tools',
339 'hooks', 'http_proxy',
339 'hooks', 'http_proxy',
340 'smtp', 'patch',
340 'smtp', 'patch',
341 'paths', 'profiling',
341 'paths', 'profiling',
342 'server', 'trusted',
342 'server', 'trusted',
343 'ui', 'web', ]
343 'ui', 'web', ]
344
344
345
345
346 def config_data_from_db(clear_session=True, repo=None):
346 def config_data_from_db(clear_session=True, repo=None):
347 """
347 """
348 Read the configuration data from the database and return configuration
348 Read the configuration data from the database and return configuration
349 tuples.
349 tuples.
350 """
350 """
351 from rhodecode.model.settings import VcsSettingsModel
351 from rhodecode.model.settings import VcsSettingsModel
352
352
353 config = []
353 config = []
354
354
355 sa = meta.Session()
355 sa = meta.Session()
356 settings_model = VcsSettingsModel(repo=repo, sa=sa)
356 settings_model = VcsSettingsModel(repo=repo, sa=sa)
357
357
358 ui_settings = settings_model.get_ui_settings()
358 ui_settings = settings_model.get_ui_settings()
359
359
360 for setting in ui_settings:
360 for setting in ui_settings:
361 if setting.active:
361 if setting.active:
362 log.debug(
362 log.debug(
363 'settings ui from db: [%s] %s=%s',
363 'settings ui from db: [%s] %s=%s',
364 setting.section, setting.key, setting.value)
364 setting.section, setting.key, setting.value)
365 config.append((
365 config.append((
366 safe_str(setting.section), safe_str(setting.key),
366 safe_str(setting.section), safe_str(setting.key),
367 safe_str(setting.value)))
367 safe_str(setting.value)))
368 if setting.key == 'push_ssl':
368 if setting.key == 'push_ssl':
369 # force set push_ssl requirement to False, rhodecode
369 # force set push_ssl requirement to False, rhodecode
370 # handles that
370 # handles that
371 config.append((
371 config.append((
372 safe_str(setting.section), safe_str(setting.key), False))
372 safe_str(setting.section), safe_str(setting.key), False))
373 if clear_session:
373 if clear_session:
374 meta.Session.remove()
374 meta.Session.remove()
375
375
376 # TODO: mikhail: probably it makes no sense to re-read hooks information.
376 # TODO: mikhail: probably it makes no sense to re-read hooks information.
377 # It's already there and activated/deactivated
377 # It's already there and activated/deactivated
378 skip_entries = []
378 skip_entries = []
379 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
379 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
380 if 'pull' not in enabled_hook_classes:
380 if 'pull' not in enabled_hook_classes:
381 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
381 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
382 if 'push' not in enabled_hook_classes:
382 if 'push' not in enabled_hook_classes:
383 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
383 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
384 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
384 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
385 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
385 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
386
386
387 config = [entry for entry in config if entry[:2] not in skip_entries]
387 config = [entry for entry in config if entry[:2] not in skip_entries]
388
388
389 return config
389 return config
390
390
391
391
392 def make_db_config(clear_session=True, repo=None):
392 def make_db_config(clear_session=True, repo=None):
393 """
393 """
394 Create a :class:`Config` instance based on the values in the database.
394 Create a :class:`Config` instance based on the values in the database.
395 """
395 """
396 config = Config()
396 config = Config()
397 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
397 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
398 for section, option, value in config_data:
398 for section, option, value in config_data:
399 config.set(section, option, value)
399 config.set(section, option, value)
400 return config
400 return config
401
401
402
402
403 def get_enabled_hook_classes(ui_settings):
403 def get_enabled_hook_classes(ui_settings):
404 """
404 """
405 Return the enabled hook classes.
405 Return the enabled hook classes.
406
406
407 :param ui_settings: List of ui_settings as returned
407 :param ui_settings: List of ui_settings as returned
408 by :meth:`VcsSettingsModel.get_ui_settings`
408 by :meth:`VcsSettingsModel.get_ui_settings`
409
409
410 :return: a list with the enabled hook classes. The order is not guaranteed.
410 :return: a list with the enabled hook classes. The order is not guaranteed.
411 :rtype: list
411 :rtype: list
412 """
412 """
413 enabled_hooks = []
413 enabled_hooks = []
414 active_hook_keys = [
414 active_hook_keys = [
415 key for section, key, value, active in ui_settings
415 key for section, key, value, active in ui_settings
416 if section == 'hooks' and active]
416 if section == 'hooks' and active]
417
417
418 hook_names = {
418 hook_names = {
419 RhodeCodeUi.HOOK_PUSH: 'push',
419 RhodeCodeUi.HOOK_PUSH: 'push',
420 RhodeCodeUi.HOOK_PULL: 'pull',
420 RhodeCodeUi.HOOK_PULL: 'pull',
421 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
421 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
422 }
422 }
423
423
424 for key in active_hook_keys:
424 for key in active_hook_keys:
425 hook = hook_names.get(key)
425 hook = hook_names.get(key)
426 if hook:
426 if hook:
427 enabled_hooks.append(hook)
427 enabled_hooks.append(hook)
428
428
429 return enabled_hooks
429 return enabled_hooks
430
430
431
431
432 def set_rhodecode_config(config):
432 def set_rhodecode_config(config):
433 """
433 """
434 Updates pylons config with new settings from database
434 Updates pylons config with new settings from database
435
435
436 :param config:
436 :param config:
437 """
437 """
438 from rhodecode.model.settings import SettingsModel
438 from rhodecode.model.settings import SettingsModel
439 app_settings = SettingsModel().get_all_settings()
439 app_settings = SettingsModel().get_all_settings()
440
440
441 for k, v in app_settings.items():
441 for k, v in app_settings.items():
442 config[k] = v
442 config[k] = v
443
443
444
444
445 def get_rhodecode_realm():
445 def get_rhodecode_realm():
446 """
446 """
447 Return the rhodecode realm from database.
447 Return the rhodecode realm from database.
448 """
448 """
449 from rhodecode.model.settings import SettingsModel
449 from rhodecode.model.settings import SettingsModel
450 realm = SettingsModel().get_setting_by_name('realm')
450 realm = SettingsModel().get_setting_by_name('realm')
451 return safe_str(realm.app_settings_value)
451 return safe_str(realm.app_settings_value)
452
452
453
453
454 def get_rhodecode_base_path():
454 def get_rhodecode_base_path():
455 """
455 """
456 Returns the base path. The base path is the filesystem path which points
456 Returns the base path. The base path is the filesystem path which points
457 to the repository store.
457 to the repository store.
458 """
458 """
459 from rhodecode.model.settings import SettingsModel
459 from rhodecode.model.settings import SettingsModel
460 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
460 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
461 return safe_str(paths_ui.ui_value)
461 return safe_str(paths_ui.ui_value)
462
462
463
463
464 def map_groups(path):
464 def map_groups(path):
465 """
465 """
466 Given a full path to a repository, create all nested groups that this
466 Given a full path to a repository, create all nested groups that this
467 repo is inside. This function creates parent-child relationships between
467 repo is inside. This function creates parent-child relationships between
468 groups and creates default perms for all new groups.
468 groups and creates default perms for all new groups.
469
469
470 :param paths: full path to repository
470 :param paths: full path to repository
471 """
471 """
472 from rhodecode.model.repo_group import RepoGroupModel
472 from rhodecode.model.repo_group import RepoGroupModel
473 sa = meta.Session()
473 sa = meta.Session()
474 groups = path.split(Repository.NAME_SEP)
474 groups = path.split(Repository.NAME_SEP)
475 parent = None
475 parent = None
476 group = None
476 group = None
477
477
478 # last element is repo in nested groups structure
478 # last element is repo in nested groups structure
479 groups = groups[:-1]
479 groups = groups[:-1]
480 rgm = RepoGroupModel(sa)
480 rgm = RepoGroupModel(sa)
481 owner = User.get_first_super_admin()
481 owner = User.get_first_super_admin()
482 for lvl, group_name in enumerate(groups):
482 for lvl, group_name in enumerate(groups):
483 group_name = '/'.join(groups[:lvl] + [group_name])
483 group_name = '/'.join(groups[:lvl] + [group_name])
484 group = RepoGroup.get_by_group_name(group_name)
484 group = RepoGroup.get_by_group_name(group_name)
485 desc = '%s group' % group_name
485 desc = '%s group' % group_name
486
486
487 # skip folders that are now removed repos
487 # skip folders that are now removed repos
488 if REMOVED_REPO_PAT.match(group_name):
488 if REMOVED_REPO_PAT.match(group_name):
489 break
489 break
490
490
491 if group is None:
491 if group is None:
492 log.debug('creating group level: %s group_name: %s',
492 log.debug('creating group level: %s group_name: %s',
493 lvl, group_name)
493 lvl, group_name)
494 group = RepoGroup(group_name, parent)
494 group = RepoGroup(group_name, parent)
495 group.group_description = desc
495 group.group_description = desc
496 group.user = owner
496 group.user = owner
497 sa.add(group)
497 sa.add(group)
498 perm_obj = rgm._create_default_perms(group)
498 perm_obj = rgm._create_default_perms(group)
499 sa.add(perm_obj)
499 sa.add(perm_obj)
500 sa.flush()
500 sa.flush()
501
501
502 parent = group
502 parent = group
503 return group
503 return group
504
504
505
505
506 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
506 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
507 """
507 """
508 maps all repos given in initial_repo_list, non existing repositories
508 maps all repos given in initial_repo_list, non existing repositories
509 are created, if remove_obsolete is True it also checks for db entries
509 are created, if remove_obsolete is True it also checks for db entries
510 that are not in initial_repo_list and removes them.
510 that are not in initial_repo_list and removes them.
511
511
512 :param initial_repo_list: list of repositories found by scanning methods
512 :param initial_repo_list: list of repositories found by scanning methods
513 :param remove_obsolete: check for obsolete entries in database
513 :param remove_obsolete: check for obsolete entries in database
514 """
514 """
515 from rhodecode.model.repo import RepoModel
515 from rhodecode.model.repo import RepoModel
516 from rhodecode.model.scm import ScmModel
516 from rhodecode.model.scm import ScmModel
517 from rhodecode.model.repo_group import RepoGroupModel
517 from rhodecode.model.repo_group import RepoGroupModel
518 from rhodecode.model.settings import SettingsModel
518 from rhodecode.model.settings import SettingsModel
519
519
520 sa = meta.Session()
520 sa = meta.Session()
521 repo_model = RepoModel()
521 repo_model = RepoModel()
522 user = User.get_first_super_admin()
522 user = User.get_first_super_admin()
523 added = []
523 added = []
524
524
525 # creation defaults
525 # creation defaults
526 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
526 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
527 enable_statistics = defs.get('repo_enable_statistics')
527 enable_statistics = defs.get('repo_enable_statistics')
528 enable_locking = defs.get('repo_enable_locking')
528 enable_locking = defs.get('repo_enable_locking')
529 enable_downloads = defs.get('repo_enable_downloads')
529 enable_downloads = defs.get('repo_enable_downloads')
530 private = defs.get('repo_private')
530 private = defs.get('repo_private')
531
531
532 for name, repo in initial_repo_list.items():
532 for name, repo in initial_repo_list.items():
533 group = map_groups(name)
533 group = map_groups(name)
534 unicode_name = safe_unicode(name)
534 unicode_name = safe_unicode(name)
535 db_repo = repo_model.get_by_repo_name(unicode_name)
535 db_repo = repo_model.get_by_repo_name(unicode_name)
536 # found repo that is on filesystem not in RhodeCode database
536 # found repo that is on filesystem not in RhodeCode database
537 if not db_repo:
537 if not db_repo:
538 log.info('repository %s not found, creating now', name)
538 log.info('repository %s not found, creating now', name)
539 added.append(name)
539 added.append(name)
540 desc = (repo.description
540 desc = (repo.description
541 if repo.description != 'unknown'
541 if repo.description != 'unknown'
542 else '%s repository' % name)
542 else '%s repository' % name)
543
543
544 db_repo = repo_model._create_repo(
544 db_repo = repo_model._create_repo(
545 repo_name=name,
545 repo_name=name,
546 repo_type=repo.alias,
546 repo_type=repo.alias,
547 description=desc,
547 description=desc,
548 repo_group=getattr(group, 'group_id', None),
548 repo_group=getattr(group, 'group_id', None),
549 owner=user,
549 owner=user,
550 enable_locking=enable_locking,
550 enable_locking=enable_locking,
551 enable_downloads=enable_downloads,
551 enable_downloads=enable_downloads,
552 enable_statistics=enable_statistics,
552 enable_statistics=enable_statistics,
553 private=private,
553 private=private,
554 state=Repository.STATE_CREATED
554 state=Repository.STATE_CREATED
555 )
555 )
556 sa.commit()
556 sa.commit()
557 # we added that repo just now, and make sure we updated server info
557 # we added that repo just now, and make sure we updated server info
558 if db_repo.repo_type == 'git':
558 if db_repo.repo_type == 'git':
559 git_repo = db_repo.scm_instance()
559 git_repo = db_repo.scm_instance()
560 # update repository server-info
560 # update repository server-info
561 log.debug('Running update server info')
561 log.debug('Running update server info')
562 git_repo._update_server_info()
562 git_repo._update_server_info()
563
563
564 db_repo.update_commit_cache()
564 db_repo.update_commit_cache()
565
565
566 config = db_repo._config
566 config = db_repo._config
567 config.set('extensions', 'largefiles', '')
567 config.set('extensions', 'largefiles', '')
568 ScmModel().install_hooks(
568 ScmModel().install_hooks(
569 db_repo.scm_instance(config=config),
569 db_repo.scm_instance(config=config),
570 repo_type=db_repo.repo_type)
570 repo_type=db_repo.repo_type)
571
571
572 removed = []
572 removed = []
573 if remove_obsolete:
573 if remove_obsolete:
574 # remove from database those repositories that are not in the filesystem
574 # remove from database those repositories that are not in the filesystem
575 for repo in sa.query(Repository).all():
575 for repo in sa.query(Repository).all():
576 if repo.repo_name not in initial_repo_list.keys():
576 if repo.repo_name not in initial_repo_list.keys():
577 log.debug("Removing non-existing repository found in db `%s`",
577 log.debug("Removing non-existing repository found in db `%s`",
578 repo.repo_name)
578 repo.repo_name)
579 try:
579 try:
580 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
580 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
581 sa.commit()
581 sa.commit()
582 removed.append(repo.repo_name)
582 removed.append(repo.repo_name)
583 except Exception:
583 except Exception:
584 # don't hold further removals on error
584 # don't hold further removals on error
585 log.error(traceback.format_exc())
585 log.error(traceback.format_exc())
586 sa.rollback()
586 sa.rollback()
587
587
588 def splitter(full_repo_name):
588 def splitter(full_repo_name):
589 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
589 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
590 gr_name = None
590 gr_name = None
591 if len(_parts) == 2:
591 if len(_parts) == 2:
592 gr_name = _parts[0]
592 gr_name = _parts[0]
593 return gr_name
593 return gr_name
594
594
595 initial_repo_group_list = [splitter(x) for x in
595 initial_repo_group_list = [splitter(x) for x in
596 initial_repo_list.keys() if splitter(x)]
596 initial_repo_list.keys() if splitter(x)]
597
597
598 # remove from database those repository groups that are not in the
598 # remove from database those repository groups that are not in the
599 # filesystem due to parent child relationships we need to delete them
599 # filesystem due to parent child relationships we need to delete them
600 # in a specific order of most nested first
600 # in a specific order of most nested first
601 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
601 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
602 nested_sort = lambda gr: len(gr.split('/'))
602 nested_sort = lambda gr: len(gr.split('/'))
603 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
603 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
604 if group_name not in initial_repo_group_list:
604 if group_name not in initial_repo_group_list:
605 repo_group = RepoGroup.get_by_group_name(group_name)
605 repo_group = RepoGroup.get_by_group_name(group_name)
606 if (repo_group.children.all() or
606 if (repo_group.children.all() or
607 not RepoGroupModel().check_exist_filesystem(
607 not RepoGroupModel().check_exist_filesystem(
608 group_name=group_name, exc_on_failure=False)):
608 group_name=group_name, exc_on_failure=False)):
609 continue
609 continue
610
610
611 log.info(
611 log.info(
612 'Removing non-existing repository group found in db `%s`',
612 'Removing non-existing repository group found in db `%s`',
613 group_name)
613 group_name)
614 try:
614 try:
615 RepoGroupModel(sa).delete(group_name, fs_remove=False)
615 RepoGroupModel(sa).delete(group_name, fs_remove=False)
616 sa.commit()
616 sa.commit()
617 removed.append(group_name)
617 removed.append(group_name)
618 except Exception:
618 except Exception:
619 # don't hold further removals on error
619 # don't hold further removals on error
620 log.exception(
620 log.exception(
621 'Unable to remove repository group `%s`',
621 'Unable to remove repository group `%s`',
622 group_name)
622 group_name)
623 sa.rollback()
623 sa.rollback()
624 raise
624 raise
625
625
626 return added, removed
626 return added, removed
627
627
628
628
629 def get_default_cache_settings(settings):
629 def get_default_cache_settings(settings):
630 cache_settings = {}
630 cache_settings = {}
631 for key in settings.keys():
631 for key in settings.keys():
632 for prefix in ['beaker.cache.', 'cache.']:
632 for prefix in ['beaker.cache.', 'cache.']:
633 if key.startswith(prefix):
633 if key.startswith(prefix):
634 name = key.split(prefix)[1].strip()
634 name = key.split(prefix)[1].strip()
635 cache_settings[name] = settings[key].strip()
635 cache_settings[name] = settings[key].strip()
636 return cache_settings
636 return cache_settings
637
637
638
638
639 # set cache regions for beaker so celery can utilise it
639 # set cache regions for beaker so celery can utilise it
640 def add_cache(settings):
640 def add_cache(settings):
641 from rhodecode.lib import caches
641 from rhodecode.lib import caches
642 cache_settings = {'regions': None}
642 cache_settings = {'regions': None}
643 # main cache settings used as default ...
643 # main cache settings used as default ...
644 cache_settings.update(get_default_cache_settings(settings))
644 cache_settings.update(get_default_cache_settings(settings))
645
645
646 if cache_settings['regions']:
646 if cache_settings['regions']:
647 for region in cache_settings['regions'].split(','):
647 for region in cache_settings['regions'].split(','):
648 region = region.strip()
648 region = region.strip()
649 region_settings = {}
649 region_settings = {}
650 for key, value in cache_settings.items():
650 for key, value in cache_settings.items():
651 if key.startswith(region):
651 if key.startswith(region):
652 region_settings[key.split('.')[1]] = value
652 region_settings[key.split('.')[1]] = value
653
653
654 caches.configure_cache_region(
654 caches.configure_cache_region(
655 region, region_settings, cache_settings)
655 region, region_settings, cache_settings)
656
656
657
657
658 def load_rcextensions(root_path):
658 def load_rcextensions(root_path):
659 import rhodecode
659 import rhodecode
660 from rhodecode.config import conf
660 from rhodecode.config import conf
661
661
662 path = os.path.join(root_path, 'rcextensions', '__init__.py')
662 path = os.path.join(root_path, 'rcextensions', '__init__.py')
663 if os.path.isfile(path):
663 if os.path.isfile(path):
664 rcext = create_module('rc', path)
664 rcext = create_module('rc', path)
665 EXT = rhodecode.EXTENSIONS = rcext
665 EXT = rhodecode.EXTENSIONS = rcext
666 log.debug('Found rcextensions now loading %s...', rcext)
666 log.debug('Found rcextensions now loading %s...', rcext)
667
667
668 # Additional mappings that are not present in the pygments lexers
668 # Additional mappings that are not present in the pygments lexers
669 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
669 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
670
670
671 # auto check if the module is not missing any data, set to default if is
671 # auto check if the module is not missing any data, set to default if is
672 # this will help autoupdate new feature of rcext module
672 # this will help autoupdate new feature of rcext module
673 #from rhodecode.config import rcextensions
673 #from rhodecode.config import rcextensions
674 #for k in dir(rcextensions):
674 #for k in dir(rcextensions):
675 # if not k.startswith('_') and not hasattr(EXT, k):
675 # if not k.startswith('_') and not hasattr(EXT, k):
676 # setattr(EXT, k, getattr(rcextensions, k))
676 # setattr(EXT, k, getattr(rcextensions, k))
677
677
678
678
679 def get_custom_lexer(extension):
679 def get_custom_lexer(extension):
680 """
680 """
681 returns a custom lexer if it is defined in rcextensions module, or None
681 returns a custom lexer if it is defined in rcextensions module, or None
682 if there's no custom lexer defined
682 if there's no custom lexer defined
683 """
683 """
684 import rhodecode
684 import rhodecode
685 from pygments import lexers
685 from pygments import lexers
686
686
687 # custom override made by RhodeCode
687 # custom override made by RhodeCode
688 if extension in ['mako']:
688 if extension in ['mako']:
689 return lexers.get_lexer_by_name('html+mako')
689 return lexers.get_lexer_by_name('html+mako')
690
690
691 # check if we didn't define this extension as other lexer
691 # check if we didn't define this extension as other lexer
692 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
692 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
693 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
693 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
694 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
694 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
695 return lexers.get_lexer_by_name(_lexer_name)
695 return lexers.get_lexer_by_name(_lexer_name)
696
696
697
697
698 #==============================================================================
698 #==============================================================================
699 # TEST FUNCTIONS AND CREATORS
699 # TEST FUNCTIONS AND CREATORS
700 #==============================================================================
700 #==============================================================================
701 def create_test_index(repo_location, config):
701 def create_test_index(repo_location, config):
702 """
702 """
703 Makes default test index.
703 Makes default test index.
704 """
704 """
705 import rc_testdata
705 import rc_testdata
706
706
707 rc_testdata.extract_search_index(
707 rc_testdata.extract_search_index(
708 'vcs_search_index', os.path.dirname(config['search.location']))
708 'vcs_search_index', os.path.dirname(config['search.location']))
709
709
710
710
711 def create_test_directory(test_path):
711 def create_test_directory(test_path):
712 """
712 """
713 Create test directory if it doesn't exist.
713 Create test directory if it doesn't exist.
714 """
714 """
715 if not os.path.isdir(test_path):
715 if not os.path.isdir(test_path):
716 log.debug('Creating testdir %s', test_path)
716 log.debug('Creating testdir %s', test_path)
717 os.makedirs(test_path)
717 os.makedirs(test_path)
718
718
719
719
720 def create_test_database(test_path, config):
720 def create_test_database(test_path, config):
721 """
721 """
722 Makes a fresh database.
722 Makes a fresh database.
723 """
723 """
724 from rhodecode.lib.db_manage import DbManage
724 from rhodecode.lib.db_manage import DbManage
725
725
726 # PART ONE create db
726 # PART ONE create db
727 dbconf = config['sqlalchemy.db1.url']
727 dbconf = config['sqlalchemy.db1.url']
728 log.debug('making test db %s', dbconf)
728 log.debug('making test db %s', dbconf)
729
729
730 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
730 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
731 tests=True, cli_args={'force_ask': True})
731 tests=True, cli_args={'force_ask': True})
732 dbmanage.create_tables(override=True)
732 dbmanage.create_tables(override=True)
733 dbmanage.set_db_version()
733 dbmanage.set_db_version()
734 # for tests dynamically set new root paths based on generated content
734 # for tests dynamically set new root paths based on generated content
735 dbmanage.create_settings(dbmanage.config_prompt(test_path))
735 dbmanage.create_settings(dbmanage.config_prompt(test_path))
736 dbmanage.create_default_user()
736 dbmanage.create_default_user()
737 dbmanage.create_test_admin_and_users()
737 dbmanage.create_test_admin_and_users()
738 dbmanage.create_permissions()
738 dbmanage.create_permissions()
739 dbmanage.populate_default_permissions()
739 dbmanage.populate_default_permissions()
740 Session().commit()
740 Session().commit()
741
741
742
742
743 def create_test_repositories(test_path, config):
743 def create_test_repositories(test_path, config):
744 """
744 """
745 Creates test repositories in the temporary directory. Repositories are
745 Creates test repositories in the temporary directory. Repositories are
746 extracted from archives within the rc_testdata package.
746 extracted from archives within the rc_testdata package.
747 """
747 """
748 import rc_testdata
748 import rc_testdata
749 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
749 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
750
750
751 log.debug('making test vcs repositories')
751 log.debug('making test vcs repositories')
752
752
753 idx_path = config['search.location']
753 idx_path = config['search.location']
754 data_path = config['cache_dir']
754 data_path = config['cache_dir']
755
755
756 # clean index and data
756 # clean index and data
757 if idx_path and os.path.exists(idx_path):
757 if idx_path and os.path.exists(idx_path):
758 log.debug('remove %s', idx_path)
758 log.debug('remove %s', idx_path)
759 shutil.rmtree(idx_path)
759 shutil.rmtree(idx_path)
760
760
761 if data_path and os.path.exists(data_path):
761 if data_path and os.path.exists(data_path):
762 log.debug('remove %s', data_path)
762 log.debug('remove %s', data_path)
763 shutil.rmtree(data_path)
763 shutil.rmtree(data_path)
764
764
765 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
765 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
766 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
766 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
767
767
768 # Note: Subversion is in the process of being integrated with the system,
768 # Note: Subversion is in the process of being integrated with the system,
769 # until we have a properly packed version of the test svn repository, this
769 # until we have a properly packed version of the test svn repository, this
770 # tries to copy over the repo from a package "rc_testdata"
770 # tries to copy over the repo from a package "rc_testdata"
771 svn_repo_path = rc_testdata.get_svn_repo_archive()
771 svn_repo_path = rc_testdata.get_svn_repo_archive()
772 with tarfile.open(svn_repo_path) as tar:
772 with tarfile.open(svn_repo_path) as tar:
773 tar.extractall(jn(test_path, SVN_REPO))
773 tar.extractall(jn(test_path, SVN_REPO))
774
774
775
775
776 #==============================================================================
776 #==============================================================================
777 # PASTER COMMANDS
777 # PASTER COMMANDS
778 #==============================================================================
778 #==============================================================================
779 class BasePasterCommand(Command):
779 class BasePasterCommand(Command):
780 """
780 """
781 Abstract Base Class for paster commands.
781 Abstract Base Class for paster commands.
782
782
783 The celery commands are somewhat aggressive about loading
783 The celery commands are somewhat aggressive about loading
784 celery.conf, and since our module sets the `CELERY_LOADER`
784 celery.conf, and since our module sets the `CELERY_LOADER`
785 environment variable to our loader, we have to bootstrap a bit and
785 environment variable to our loader, we have to bootstrap a bit and
786 make sure we've had a chance to load the pylons config off of the
786 make sure we've had a chance to load the pylons config off of the
787 command line, otherwise everything fails.
787 command line, otherwise everything fails.
788 """
788 """
789 min_args = 1
789 min_args = 1
790 min_args_error = "Please provide a paster config file as an argument."
790 min_args_error = "Please provide a paster config file as an argument."
791 takes_config_file = 1
791 takes_config_file = 1
792 requires_config_file = True
792 requires_config_file = True
793
793
794 def notify_msg(self, msg, log=False):
794 def notify_msg(self, msg, log=False):
795 """Make a notification to user, additionally if logger is passed
795 """Make a notification to user, additionally if logger is passed
796 it logs this action using given logger
796 it logs this action using given logger
797
797
798 :param msg: message that will be printed to user
798 :param msg: message that will be printed to user
799 :param log: logging instance, to use to additionally log this message
799 :param log: logging instance, to use to additionally log this message
800
800
801 """
801 """
802 if log and isinstance(log, logging):
802 if log and isinstance(log, logging):
803 log(msg)
803 log(msg)
804
804
805 def run(self, args):
805 def run(self, args):
806 """
806 """
807 Overrides Command.run
807 Overrides Command.run
808
808
809 Checks for a config file argument and loads it.
809 Checks for a config file argument and loads it.
810 """
810 """
811 if len(args) < self.min_args:
811 if len(args) < self.min_args:
812 raise BadCommand(
812 raise BadCommand(
813 self.min_args_error % {'min_args': self.min_args,
813 self.min_args_error % {'min_args': self.min_args,
814 'actual_args': len(args)})
814 'actual_args': len(args)})
815
815
816 # Decrement because we're going to lob off the first argument.
816 # Decrement because we're going to lob off the first argument.
817 # @@ This is hacky
817 # @@ This is hacky
818 self.min_args -= 1
818 self.min_args -= 1
819 self.bootstrap_config(args[0])
819 self.bootstrap_config(args[0])
820 self.update_parser()
820 self.update_parser()
821 return super(BasePasterCommand, self).run(args[1:])
821 return super(BasePasterCommand, self).run(args[1:])
822
822
823 def update_parser(self):
823 def update_parser(self):
824 """
824 """
825 Abstract method. Allows for the class' parser to be updated
825 Abstract method. Allows for the class' parser to be updated
826 before the superclass' `run` method is called. Necessary to
826 before the superclass' `run` method is called. Necessary to
827 allow options/arguments to be passed through to the underlying
827 allow options/arguments to be passed through to the underlying
828 celery command.
828 celery command.
829 """
829 """
830 raise NotImplementedError("Abstract Method.")
830 raise NotImplementedError("Abstract Method.")
831
831
832 def bootstrap_config(self, conf):
832 def bootstrap_config(self, conf):
833 """
833 """
834 Loads the pylons configuration.
834 Loads the pylons configuration.
835 """
835 """
836 from pylons import config as pylonsconfig
836 from pylons import config as pylonsconfig
837
837
838 self.path_to_ini_file = os.path.realpath(conf)
838 self.path_to_ini_file = os.path.realpath(conf)
839 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
839 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
840 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
840 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
841
841
842 def _init_session(self):
842 def _init_session(self):
843 """
843 """
844 Inits SqlAlchemy Session
844 Inits SqlAlchemy Session
845 """
845 """
846 logging.config.fileConfig(self.path_to_ini_file)
846 logging.config.fileConfig(self.path_to_ini_file)
847 from pylons import config
847 from pylons import config
848 from rhodecode.config.utils import initialize_database
848 from rhodecode.config.utils import initialize_database
849
849
850 # get to remove repos !!
850 # get to remove repos !!
851 add_cache(config)
851 add_cache(config)
852 initialize_database(config)
852 initialize_database(config)
853
853
854
854
855 @decorator.decorator
856 def jsonify(func, *args, **kwargs):
857 """Action decorator that formats output for JSON
858
859 Given a function that will return content, this decorator will turn
860 the result into JSON, with a content-type of 'application/json' and
861 output it.
862
863 """
864 from pylons.decorators.util import get_pylons
865 from rhodecode.lib.ext_json import json
866 pylons = get_pylons(args)
867 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
868 data = func(*args, **kwargs)
869 if isinstance(data, (list, tuple)):
870 msg = "JSON responses with Array envelopes are susceptible to " \
871 "cross-site data leak attacks, see " \
872 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
873 warnings.warn(msg, Warning, 2)
874 log.warning(msg)
875 log.debug("Returning JSON wrapped action output")
876 return json.dumps(data, encoding='utf-8')
877
878
879 class PartialRenderer(object):
855 class PartialRenderer(object):
880 """
856 """
881 Partial renderer used to render chunks of html used in datagrids
857 Partial renderer used to render chunks of html used in datagrids
882 use like::
858 use like::
883
859
884 _render = PartialRenderer('data_table/_dt_elements.mako')
860 _render = PartialRenderer('data_table/_dt_elements.mako')
885 _render('quick_menu', args, kwargs)
861 _render('quick_menu', args, kwargs)
886 PartialRenderer.h,
862 PartialRenderer.h,
887 c,
863 c,
888 _,
864 _,
889 ungettext
865 ungettext
890 are the template stuff initialized inside and can be re-used later
866 are the template stuff initialized inside and can be re-used later
891
867
892 :param tmpl_name: template path relate to /templates/ dir
868 :param tmpl_name: template path relate to /templates/ dir
893 """
869 """
894
870
895 def __init__(self, tmpl_name):
871 def __init__(self, tmpl_name):
896 import rhodecode
872 import rhodecode
897 from pylons import request, tmpl_context as c
873 from pylons import request, tmpl_context as c
898 from pylons.i18n.translation import _, ungettext
874 from pylons.i18n.translation import _, ungettext
899 from rhodecode.lib import helpers as h
875 from rhodecode.lib import helpers as h
900
876
901 self.tmpl_name = tmpl_name
877 self.tmpl_name = tmpl_name
902 self.rhodecode = rhodecode
878 self.rhodecode = rhodecode
903 self.c = c
879 self.c = c
904 self._ = _
880 self._ = _
905 self.ungettext = ungettext
881 self.ungettext = ungettext
906 self.h = h
882 self.h = h
907 self.request = request
883 self.request = request
908
884
909 def _mako_lookup(self):
885 def _mako_lookup(self):
910 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
886 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
911 return _tmpl_lookup.get_template(self.tmpl_name)
887 return _tmpl_lookup.get_template(self.tmpl_name)
912
888
913 def _update_kwargs_for_render(self, kwargs):
889 def _update_kwargs_for_render(self, kwargs):
914 """
890 """
915 Inject params required for Mako rendering
891 Inject params required for Mako rendering
916 """
892 """
917 _kwargs = {
893 _kwargs = {
918 '_': self._,
894 '_': self._,
919 'h': self.h,
895 'h': self.h,
920 'c': self.c,
896 'c': self.c,
921 'request': self.request,
897 'request': self.request,
922 '_ungettext': self.ungettext,
898 '_ungettext': self.ungettext,
923 }
899 }
924 _kwargs.update(kwargs)
900 _kwargs.update(kwargs)
925 return _kwargs
901 return _kwargs
926
902
927 def _render_with_exc(self, render_func, args, kwargs):
903 def _render_with_exc(self, render_func, args, kwargs):
928 try:
904 try:
929 return render_func.render(*args, **kwargs)
905 return render_func.render(*args, **kwargs)
930 except:
906 except:
931 log.error(exceptions.text_error_template().render())
907 log.error(exceptions.text_error_template().render())
932 raise
908 raise
933
909
934 def _get_template(self, template_obj, def_name):
910 def _get_template(self, template_obj, def_name):
935 if def_name:
911 if def_name:
936 tmpl = template_obj.get_def(def_name)
912 tmpl = template_obj.get_def(def_name)
937 else:
913 else:
938 tmpl = template_obj
914 tmpl = template_obj
939 return tmpl
915 return tmpl
940
916
941 def render(self, def_name, *args, **kwargs):
917 def render(self, def_name, *args, **kwargs):
942 lookup_obj = self._mako_lookup()
918 lookup_obj = self._mako_lookup()
943 tmpl = self._get_template(lookup_obj, def_name=def_name)
919 tmpl = self._get_template(lookup_obj, def_name=def_name)
944 kwargs = self._update_kwargs_for_render(kwargs)
920 kwargs = self._update_kwargs_for_render(kwargs)
945 return self._render_with_exc(tmpl, args, kwargs)
921 return self._render_with_exc(tmpl, args, kwargs)
946
922
947 def __call__(self, tmpl, *args, **kwargs):
923 def __call__(self, tmpl, *args, **kwargs):
948 return self.render(tmpl, *args, **kwargs)
924 return self.render(tmpl, *args, **kwargs)
949
925
950
926
951 def password_changed(auth_user, session):
927 def password_changed(auth_user, session):
952 # Never report password change in case of default user or anonymous user.
928 # Never report password change in case of default user or anonymous user.
953 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
929 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
954 return False
930 return False
955
931
956 password_hash = md5(auth_user.password) if auth_user.password else None
932 password_hash = md5(auth_user.password) if auth_user.password else None
957 rhodecode_user = session.get('rhodecode_user', {})
933 rhodecode_user = session.get('rhodecode_user', {})
958 session_password_hash = rhodecode_user.get('password', '')
934 session_password_hash = rhodecode_user.get('password', '')
959 return password_hash != session_password_hash
935 return password_hash != session_password_hash
960
936
961
937
962 def read_opensource_licenses():
938 def read_opensource_licenses():
963 global _license_cache
939 global _license_cache
964
940
965 if not _license_cache:
941 if not _license_cache:
966 licenses = pkg_resources.resource_string(
942 licenses = pkg_resources.resource_string(
967 'rhodecode', 'config/licenses.json')
943 'rhodecode', 'config/licenses.json')
968 _license_cache = json.loads(licenses)
944 _license_cache = json.loads(licenses)
969
945
970 return _license_cache
946 return _license_cache
971
947
972
948
973 def get_registry(request):
949 def get_registry(request):
974 """
950 """
975 Utility to get the pyramid registry from a request. During migration to
951 Utility to get the pyramid registry from a request. During migration to
976 pyramid we sometimes want to use the pyramid registry from pylons context.
952 pyramid we sometimes want to use the pyramid registry from pylons context.
977 Therefore this utility returns `request.registry` for pyramid requests and
953 Therefore this utility returns `request.registry` for pyramid requests and
978 uses `get_current_registry()` for pylons requests.
954 uses `get_current_registry()` for pylons requests.
979 """
955 """
980 try:
956 try:
981 return request.registry
957 return request.registry
982 except AttributeError:
958 except AttributeError:
983 return get_current_registry()
959 return get_current_registry()
984
960
985
961
986 def generate_platform_uuid():
962 def generate_platform_uuid():
987 """
963 """
988 Generates platform UUID based on it's name
964 Generates platform UUID based on it's name
989 """
965 """
990 import platform
966 import platform
991
967
992 try:
968 try:
993 uuid_list = [platform.platform()]
969 uuid_list = [platform.platform()]
994 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
970 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
995 except Exception as e:
971 except Exception as e:
996 log.error('Failed to generate host uuid: %s' % e)
972 log.error('Failed to generate host uuid: %s' % e)
997 return 'UNDEFINED'
973 return 'UNDEFINED'
@@ -1,1029 +1,1025 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
22 Repository model for rhodecode
23 """
24
25 import logging
21 import logging
26 import os
22 import os
27 import re
23 import re
28 import shutil
24 import shutil
29 import time
25 import time
30 import traceback
26 import traceback
31 import datetime
27 import datetime
32
28
33 from pyramid.threadlocal import get_current_request
29 from pyramid.threadlocal import get_current_request
34 from zope.cachedescriptors.property import Lazy as LazyProperty
30 from zope.cachedescriptors.property import Lazy as LazyProperty
35
31
36 from rhodecode import events
32 from rhodecode import events
37 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 from rhodecode.lib.auth import HasUserGroupPermissionAny
39 from rhodecode.lib.caching_query import FromCache
35 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.lib.exceptions import AttachedForksError
36 from rhodecode.lib.exceptions import AttachedForksError
41 from rhodecode.lib.hooks_base import log_delete_repository
37 from rhodecode.lib.hooks_base import log_delete_repository
42 from rhodecode.lib.utils import make_db_config
38 from rhodecode.lib.utils import make_db_config
43 from rhodecode.lib.utils2 import (
39 from rhodecode.lib.utils2 import (
44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
41 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
46 from rhodecode.lib.vcs.backends import get_backend
42 from rhodecode.lib.vcs.backends import get_backend
47 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
48 from rhodecode.model.db import (_hash_key,
44 from rhodecode.model.db import (_hash_key,
49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
45 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
46 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
51 RepoGroup, RepositoryField)
47 RepoGroup, RepositoryField)
52
48
53 from rhodecode.model.settings import VcsSettingsModel
49 from rhodecode.model.settings import VcsSettingsModel
54
50
55
51
56 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
57
53
58
54
59 class RepoModel(BaseModel):
55 class RepoModel(BaseModel):
60
56
61 cls = Repository
57 cls = Repository
62
58
63 def _get_user_group(self, users_group):
59 def _get_user_group(self, users_group):
64 return self._get_instance(UserGroup, users_group,
60 return self._get_instance(UserGroup, users_group,
65 callback=UserGroup.get_by_group_name)
61 callback=UserGroup.get_by_group_name)
66
62
67 def _get_repo_group(self, repo_group):
63 def _get_repo_group(self, repo_group):
68 return self._get_instance(RepoGroup, repo_group,
64 return self._get_instance(RepoGroup, repo_group,
69 callback=RepoGroup.get_by_group_name)
65 callback=RepoGroup.get_by_group_name)
70
66
71 def _create_default_perms(self, repository, private):
67 def _create_default_perms(self, repository, private):
72 # create default permission
68 # create default permission
73 default = 'repository.read'
69 default = 'repository.read'
74 def_user = User.get_default_user()
70 def_user = User.get_default_user()
75 for p in def_user.user_perms:
71 for p in def_user.user_perms:
76 if p.permission.permission_name.startswith('repository.'):
72 if p.permission.permission_name.startswith('repository.'):
77 default = p.permission.permission_name
73 default = p.permission.permission_name
78 break
74 break
79
75
80 default_perm = 'repository.none' if private else default
76 default_perm = 'repository.none' if private else default
81
77
82 repo_to_perm = UserRepoToPerm()
78 repo_to_perm = UserRepoToPerm()
83 repo_to_perm.permission = Permission.get_by_key(default_perm)
79 repo_to_perm.permission = Permission.get_by_key(default_perm)
84
80
85 repo_to_perm.repository = repository
81 repo_to_perm.repository = repository
86 repo_to_perm.user_id = def_user.user_id
82 repo_to_perm.user_id = def_user.user_id
87
83
88 return repo_to_perm
84 return repo_to_perm
89
85
90 @LazyProperty
86 @LazyProperty
91 def repos_path(self):
87 def repos_path(self):
92 """
88 """
93 Gets the repositories root path from database
89 Gets the repositories root path from database
94 """
90 """
95 settings_model = VcsSettingsModel(sa=self.sa)
91 settings_model = VcsSettingsModel(sa=self.sa)
96 return settings_model.get_repos_location()
92 return settings_model.get_repos_location()
97
93
98 def get(self, repo_id, cache=False):
94 def get(self, repo_id, cache=False):
99 repo = self.sa.query(Repository) \
95 repo = self.sa.query(Repository) \
100 .filter(Repository.repo_id == repo_id)
96 .filter(Repository.repo_id == repo_id)
101
97
102 if cache:
98 if cache:
103 repo = repo.options(
99 repo = repo.options(
104 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
100 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
105 return repo.scalar()
101 return repo.scalar()
106
102
107 def get_repo(self, repository):
103 def get_repo(self, repository):
108 return self._get_repo(repository)
104 return self._get_repo(repository)
109
105
110 def get_by_repo_name(self, repo_name, cache=False):
106 def get_by_repo_name(self, repo_name, cache=False):
111 repo = self.sa.query(Repository) \
107 repo = self.sa.query(Repository) \
112 .filter(Repository.repo_name == repo_name)
108 .filter(Repository.repo_name == repo_name)
113
109
114 if cache:
110 if cache:
115 name_key = _hash_key(repo_name)
111 name_key = _hash_key(repo_name)
116 repo = repo.options(
112 repo = repo.options(
117 FromCache("sql_cache_short", "get_repo_%s" % name_key))
113 FromCache("sql_cache_short", "get_repo_%s" % name_key))
118 return repo.scalar()
114 return repo.scalar()
119
115
120 def _extract_id_from_repo_name(self, repo_name):
116 def _extract_id_from_repo_name(self, repo_name):
121 if repo_name.startswith('/'):
117 if repo_name.startswith('/'):
122 repo_name = repo_name.lstrip('/')
118 repo_name = repo_name.lstrip('/')
123 by_id_match = re.match(r'^_(\d{1,})', repo_name)
119 by_id_match = re.match(r'^_(\d{1,})', repo_name)
124 if by_id_match:
120 if by_id_match:
125 return by_id_match.groups()[0]
121 return by_id_match.groups()[0]
126
122
127 def get_repo_by_id(self, repo_name):
123 def get_repo_by_id(self, repo_name):
128 """
124 """
129 Extracts repo_name by id from special urls.
125 Extracts repo_name by id from special urls.
130 Example url is _11/repo_name
126 Example url is _11/repo_name
131
127
132 :param repo_name:
128 :param repo_name:
133 :return: repo object if matched else None
129 :return: repo object if matched else None
134 """
130 """
135
131
136 try:
132 try:
137 _repo_id = self._extract_id_from_repo_name(repo_name)
133 _repo_id = self._extract_id_from_repo_name(repo_name)
138 if _repo_id:
134 if _repo_id:
139 return self.get(_repo_id)
135 return self.get(_repo_id)
140 except Exception:
136 except Exception:
141 log.exception('Failed to extract repo_name from URL')
137 log.exception('Failed to extract repo_name from URL')
142
138
143 return None
139 return None
144
140
145 def get_repos_for_root(self, root, traverse=False):
141 def get_repos_for_root(self, root, traverse=False):
146 if traverse:
142 if traverse:
147 like_expression = u'{}%'.format(safe_unicode(root))
143 like_expression = u'{}%'.format(safe_unicode(root))
148 repos = Repository.query().filter(
144 repos = Repository.query().filter(
149 Repository.repo_name.like(like_expression)).all()
145 Repository.repo_name.like(like_expression)).all()
150 else:
146 else:
151 if root and not isinstance(root, RepoGroup):
147 if root and not isinstance(root, RepoGroup):
152 raise ValueError(
148 raise ValueError(
153 'Root must be an instance '
149 'Root must be an instance '
154 'of RepoGroup, got:{} instead'.format(type(root)))
150 'of RepoGroup, got:{} instead'.format(type(root)))
155 repos = Repository.query().filter(Repository.group == root).all()
151 repos = Repository.query().filter(Repository.group == root).all()
156 return repos
152 return repos
157
153
158 def get_url(self, repo, request=None, permalink=False):
154 def get_url(self, repo, request=None, permalink=False):
159 if not request:
155 if not request:
160 request = get_current_request()
156 request = get_current_request()
161
157
162 if not request:
158 if not request:
163 return
159 return
164
160
165 if permalink:
161 if permalink:
166 return request.route_url(
162 return request.route_url(
167 'repo_summary', repo_name=safe_str(repo.repo_id))
163 'repo_summary', repo_name=safe_str(repo.repo_id))
168 else:
164 else:
169 return request.route_url(
165 return request.route_url(
170 'repo_summary', repo_name=safe_str(repo.repo_name))
166 'repo_summary', repo_name=safe_str(repo.repo_name))
171
167
172 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
168 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
173 if not request:
169 if not request:
174 request = get_current_request()
170 request = get_current_request()
175
171
176 if not request:
172 if not request:
177 return
173 return
178
174
179 if permalink:
175 if permalink:
180 return request.route_url(
176 return request.route_url(
181 'repo_commit', repo_name=safe_str(repo.repo_id),
177 'repo_commit', repo_name=safe_str(repo.repo_id),
182 commit_id=commit_id)
178 commit_id=commit_id)
183
179
184 else:
180 else:
185 return request.route_url(
181 return request.route_url(
186 'repo_commit', repo_name=safe_str(repo.repo_name),
182 'repo_commit', repo_name=safe_str(repo.repo_name),
187 commit_id=commit_id)
183 commit_id=commit_id)
188
184
189 @classmethod
185 @classmethod
190 def update_repoinfo(cls, repositories=None):
186 def update_repoinfo(cls, repositories=None):
191 if not repositories:
187 if not repositories:
192 repositories = Repository.getAll()
188 repositories = Repository.getAll()
193 for repo in repositories:
189 for repo in repositories:
194 repo.update_commit_cache()
190 repo.update_commit_cache()
195
191
196 def get_repos_as_dict(self, repo_list=None, admin=False,
192 def get_repos_as_dict(self, repo_list=None, admin=False,
197 super_user_actions=False):
193 super_user_actions=False):
198 _render = get_current_request().get_partial_renderer(
194 _render = get_current_request().get_partial_renderer(
199 'data_table/_dt_elements.mako')
195 'data_table/_dt_elements.mako')
200 c = _render.get_call_context()
196 c = _render.get_call_context()
201
197
202 def quick_menu(repo_name):
198 def quick_menu(repo_name):
203 return _render('quick_menu', repo_name)
199 return _render('quick_menu', repo_name)
204
200
205 def repo_lnk(name, rtype, rstate, private, fork_of):
201 def repo_lnk(name, rtype, rstate, private, fork_of):
206 return _render('repo_name', name, rtype, rstate, private, fork_of,
202 return _render('repo_name', name, rtype, rstate, private, fork_of,
207 short_name=not admin, admin=False)
203 short_name=not admin, admin=False)
208
204
209 def last_change(last_change):
205 def last_change(last_change):
210 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
206 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
211 last_change = last_change + datetime.timedelta(seconds=
207 last_change = last_change + datetime.timedelta(seconds=
212 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
208 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
213 return _render("last_change", last_change)
209 return _render("last_change", last_change)
214
210
215 def rss_lnk(repo_name):
211 def rss_lnk(repo_name):
216 return _render("rss", repo_name)
212 return _render("rss", repo_name)
217
213
218 def atom_lnk(repo_name):
214 def atom_lnk(repo_name):
219 return _render("atom", repo_name)
215 return _render("atom", repo_name)
220
216
221 def last_rev(repo_name, cs_cache):
217 def last_rev(repo_name, cs_cache):
222 return _render('revision', repo_name, cs_cache.get('revision'),
218 return _render('revision', repo_name, cs_cache.get('revision'),
223 cs_cache.get('raw_id'), cs_cache.get('author'),
219 cs_cache.get('raw_id'), cs_cache.get('author'),
224 cs_cache.get('message'))
220 cs_cache.get('message'))
225
221
226 def desc(desc):
222 def desc(desc):
227 if c.visual.stylify_metatags:
223 if c.visual.stylify_metatags:
228 desc = h.urlify_text(h.escaped_stylize(desc))
224 desc = h.urlify_text(h.escaped_stylize(desc))
229 else:
225 else:
230 desc = h.urlify_text(h.html_escape(desc))
226 desc = h.urlify_text(h.html_escape(desc))
231
227
232 return _render('repo_desc', desc)
228 return _render('repo_desc', desc)
233
229
234 def state(repo_state):
230 def state(repo_state):
235 return _render("repo_state", repo_state)
231 return _render("repo_state", repo_state)
236
232
237 def repo_actions(repo_name):
233 def repo_actions(repo_name):
238 return _render('repo_actions', repo_name, super_user_actions)
234 return _render('repo_actions', repo_name, super_user_actions)
239
235
240 def user_profile(username):
236 def user_profile(username):
241 return _render('user_profile', username)
237 return _render('user_profile', username)
242
238
243 repos_data = []
239 repos_data = []
244 for repo in repo_list:
240 for repo in repo_list:
245 cs_cache = repo.changeset_cache
241 cs_cache = repo.changeset_cache
246 row = {
242 row = {
247 "menu": quick_menu(repo.repo_name),
243 "menu": quick_menu(repo.repo_name),
248
244
249 "name": repo_lnk(repo.repo_name, repo.repo_type,
245 "name": repo_lnk(repo.repo_name, repo.repo_type,
250 repo.repo_state, repo.private, repo.fork),
246 repo.repo_state, repo.private, repo.fork),
251 "name_raw": repo.repo_name.lower(),
247 "name_raw": repo.repo_name.lower(),
252
248
253 "last_change": last_change(repo.last_db_change),
249 "last_change": last_change(repo.last_db_change),
254 "last_change_raw": datetime_to_time(repo.last_db_change),
250 "last_change_raw": datetime_to_time(repo.last_db_change),
255
251
256 "last_changeset": last_rev(repo.repo_name, cs_cache),
252 "last_changeset": last_rev(repo.repo_name, cs_cache),
257 "last_changeset_raw": cs_cache.get('revision'),
253 "last_changeset_raw": cs_cache.get('revision'),
258
254
259 "desc": desc(repo.description_safe),
255 "desc": desc(repo.description_safe),
260 "owner": user_profile(repo.user.username),
256 "owner": user_profile(repo.user.username),
261
257
262 "state": state(repo.repo_state),
258 "state": state(repo.repo_state),
263 "rss": rss_lnk(repo.repo_name),
259 "rss": rss_lnk(repo.repo_name),
264
260
265 "atom": atom_lnk(repo.repo_name),
261 "atom": atom_lnk(repo.repo_name),
266 }
262 }
267 if admin:
263 if admin:
268 row.update({
264 row.update({
269 "action": repo_actions(repo.repo_name),
265 "action": repo_actions(repo.repo_name),
270 })
266 })
271 repos_data.append(row)
267 repos_data.append(row)
272
268
273 return repos_data
269 return repos_data
274
270
275 def _get_defaults(self, repo_name):
271 def _get_defaults(self, repo_name):
276 """
272 """
277 Gets information about repository, and returns a dict for
273 Gets information about repository, and returns a dict for
278 usage in forms
274 usage in forms
279
275
280 :param repo_name:
276 :param repo_name:
281 """
277 """
282
278
283 repo_info = Repository.get_by_repo_name(repo_name)
279 repo_info = Repository.get_by_repo_name(repo_name)
284
280
285 if repo_info is None:
281 if repo_info is None:
286 return None
282 return None
287
283
288 defaults = repo_info.get_dict()
284 defaults = repo_info.get_dict()
289 defaults['repo_name'] = repo_info.just_name
285 defaults['repo_name'] = repo_info.just_name
290
286
291 groups = repo_info.groups_with_parents
287 groups = repo_info.groups_with_parents
292 parent_group = groups[-1] if groups else None
288 parent_group = groups[-1] if groups else None
293
289
294 # we use -1 as this is how in HTML, we mark an empty group
290 # we use -1 as this is how in HTML, we mark an empty group
295 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
291 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
296
292
297 keys_to_process = (
293 keys_to_process = (
298 {'k': 'repo_type', 'strip': False},
294 {'k': 'repo_type', 'strip': False},
299 {'k': 'repo_enable_downloads', 'strip': True},
295 {'k': 'repo_enable_downloads', 'strip': True},
300 {'k': 'repo_description', 'strip': True},
296 {'k': 'repo_description', 'strip': True},
301 {'k': 'repo_enable_locking', 'strip': True},
297 {'k': 'repo_enable_locking', 'strip': True},
302 {'k': 'repo_landing_rev', 'strip': True},
298 {'k': 'repo_landing_rev', 'strip': True},
303 {'k': 'clone_uri', 'strip': False},
299 {'k': 'clone_uri', 'strip': False},
304 {'k': 'repo_private', 'strip': True},
300 {'k': 'repo_private', 'strip': True},
305 {'k': 'repo_enable_statistics', 'strip': True}
301 {'k': 'repo_enable_statistics', 'strip': True}
306 )
302 )
307
303
308 for item in keys_to_process:
304 for item in keys_to_process:
309 attr = item['k']
305 attr = item['k']
310 if item['strip']:
306 if item['strip']:
311 attr = remove_prefix(item['k'], 'repo_')
307 attr = remove_prefix(item['k'], 'repo_')
312
308
313 val = defaults[attr]
309 val = defaults[attr]
314 if item['k'] == 'repo_landing_rev':
310 if item['k'] == 'repo_landing_rev':
315 val = ':'.join(defaults[attr])
311 val = ':'.join(defaults[attr])
316 defaults[item['k']] = val
312 defaults[item['k']] = val
317 if item['k'] == 'clone_uri':
313 if item['k'] == 'clone_uri':
318 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
314 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
319
315
320 # fill owner
316 # fill owner
321 if repo_info.user:
317 if repo_info.user:
322 defaults.update({'user': repo_info.user.username})
318 defaults.update({'user': repo_info.user.username})
323 else:
319 else:
324 replacement_user = User.get_first_super_admin().username
320 replacement_user = User.get_first_super_admin().username
325 defaults.update({'user': replacement_user})
321 defaults.update({'user': replacement_user})
326
322
327 return defaults
323 return defaults
328
324
329 def update(self, repo, **kwargs):
325 def update(self, repo, **kwargs):
330 try:
326 try:
331 cur_repo = self._get_repo(repo)
327 cur_repo = self._get_repo(repo)
332 source_repo_name = cur_repo.repo_name
328 source_repo_name = cur_repo.repo_name
333 if 'user' in kwargs:
329 if 'user' in kwargs:
334 cur_repo.user = User.get_by_username(kwargs['user'])
330 cur_repo.user = User.get_by_username(kwargs['user'])
335
331
336 if 'repo_group' in kwargs:
332 if 'repo_group' in kwargs:
337 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
333 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
338 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
334 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
339
335
340 update_keys = [
336 update_keys = [
341 (1, 'repo_description'),
337 (1, 'repo_description'),
342 (1, 'repo_landing_rev'),
338 (1, 'repo_landing_rev'),
343 (1, 'repo_private'),
339 (1, 'repo_private'),
344 (1, 'repo_enable_downloads'),
340 (1, 'repo_enable_downloads'),
345 (1, 'repo_enable_locking'),
341 (1, 'repo_enable_locking'),
346 (1, 'repo_enable_statistics'),
342 (1, 'repo_enable_statistics'),
347 (0, 'clone_uri'),
343 (0, 'clone_uri'),
348 (0, 'fork_id')
344 (0, 'fork_id')
349 ]
345 ]
350 for strip, k in update_keys:
346 for strip, k in update_keys:
351 if k in kwargs:
347 if k in kwargs:
352 val = kwargs[k]
348 val = kwargs[k]
353 if strip:
349 if strip:
354 k = remove_prefix(k, 'repo_')
350 k = remove_prefix(k, 'repo_')
355
351
356 setattr(cur_repo, k, val)
352 setattr(cur_repo, k, val)
357
353
358 new_name = cur_repo.get_new_name(kwargs['repo_name'])
354 new_name = cur_repo.get_new_name(kwargs['repo_name'])
359 cur_repo.repo_name = new_name
355 cur_repo.repo_name = new_name
360
356
361 # if private flag is set, reset default permission to NONE
357 # if private flag is set, reset default permission to NONE
362 if kwargs.get('repo_private'):
358 if kwargs.get('repo_private'):
363 EMPTY_PERM = 'repository.none'
359 EMPTY_PERM = 'repository.none'
364 RepoModel().grant_user_permission(
360 RepoModel().grant_user_permission(
365 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
361 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
366 )
362 )
367
363
368 # handle extra fields
364 # handle extra fields
369 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
365 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
370 kwargs):
366 kwargs):
371 k = RepositoryField.un_prefix_key(field)
367 k = RepositoryField.un_prefix_key(field)
372 ex_field = RepositoryField.get_by_key_name(
368 ex_field = RepositoryField.get_by_key_name(
373 key=k, repo=cur_repo)
369 key=k, repo=cur_repo)
374 if ex_field:
370 if ex_field:
375 ex_field.field_value = kwargs[field]
371 ex_field.field_value = kwargs[field]
376 self.sa.add(ex_field)
372 self.sa.add(ex_field)
377 cur_repo.updated_on = datetime.datetime.now()
373 cur_repo.updated_on = datetime.datetime.now()
378 self.sa.add(cur_repo)
374 self.sa.add(cur_repo)
379
375
380 if source_repo_name != new_name:
376 if source_repo_name != new_name:
381 # rename repository
377 # rename repository
382 self._rename_filesystem_repo(
378 self._rename_filesystem_repo(
383 old=source_repo_name, new=new_name)
379 old=source_repo_name, new=new_name)
384
380
385 return cur_repo
381 return cur_repo
386 except Exception:
382 except Exception:
387 log.error(traceback.format_exc())
383 log.error(traceback.format_exc())
388 raise
384 raise
389
385
390 def _create_repo(self, repo_name, repo_type, description, owner,
386 def _create_repo(self, repo_name, repo_type, description, owner,
391 private=False, clone_uri=None, repo_group=None,
387 private=False, clone_uri=None, repo_group=None,
392 landing_rev='rev:tip', fork_of=None,
388 landing_rev='rev:tip', fork_of=None,
393 copy_fork_permissions=False, enable_statistics=False,
389 copy_fork_permissions=False, enable_statistics=False,
394 enable_locking=False, enable_downloads=False,
390 enable_locking=False, enable_downloads=False,
395 copy_group_permissions=False,
391 copy_group_permissions=False,
396 state=Repository.STATE_PENDING):
392 state=Repository.STATE_PENDING):
397 """
393 """
398 Create repository inside database with PENDING state, this should be
394 Create repository inside database with PENDING state, this should be
399 only executed by create() repo. With exception of importing existing
395 only executed by create() repo. With exception of importing existing
400 repos
396 repos
401 """
397 """
402 from rhodecode.model.scm import ScmModel
398 from rhodecode.model.scm import ScmModel
403
399
404 owner = self._get_user(owner)
400 owner = self._get_user(owner)
405 fork_of = self._get_repo(fork_of)
401 fork_of = self._get_repo(fork_of)
406 repo_group = self._get_repo_group(safe_int(repo_group))
402 repo_group = self._get_repo_group(safe_int(repo_group))
407
403
408 try:
404 try:
409 repo_name = safe_unicode(repo_name)
405 repo_name = safe_unicode(repo_name)
410 description = safe_unicode(description)
406 description = safe_unicode(description)
411 # repo name is just a name of repository
407 # repo name is just a name of repository
412 # while repo_name_full is a full qualified name that is combined
408 # while repo_name_full is a full qualified name that is combined
413 # with name and path of group
409 # with name and path of group
414 repo_name_full = repo_name
410 repo_name_full = repo_name
415 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
411 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
416
412
417 new_repo = Repository()
413 new_repo = Repository()
418 new_repo.repo_state = state
414 new_repo.repo_state = state
419 new_repo.enable_statistics = False
415 new_repo.enable_statistics = False
420 new_repo.repo_name = repo_name_full
416 new_repo.repo_name = repo_name_full
421 new_repo.repo_type = repo_type
417 new_repo.repo_type = repo_type
422 new_repo.user = owner
418 new_repo.user = owner
423 new_repo.group = repo_group
419 new_repo.group = repo_group
424 new_repo.description = description or repo_name
420 new_repo.description = description or repo_name
425 new_repo.private = private
421 new_repo.private = private
426 new_repo.clone_uri = clone_uri
422 new_repo.clone_uri = clone_uri
427 new_repo.landing_rev = landing_rev
423 new_repo.landing_rev = landing_rev
428
424
429 new_repo.enable_statistics = enable_statistics
425 new_repo.enable_statistics = enable_statistics
430 new_repo.enable_locking = enable_locking
426 new_repo.enable_locking = enable_locking
431 new_repo.enable_downloads = enable_downloads
427 new_repo.enable_downloads = enable_downloads
432
428
433 if repo_group:
429 if repo_group:
434 new_repo.enable_locking = repo_group.enable_locking
430 new_repo.enable_locking = repo_group.enable_locking
435
431
436 if fork_of:
432 if fork_of:
437 parent_repo = fork_of
433 parent_repo = fork_of
438 new_repo.fork = parent_repo
434 new_repo.fork = parent_repo
439
435
440 events.trigger(events.RepoPreCreateEvent(new_repo))
436 events.trigger(events.RepoPreCreateEvent(new_repo))
441
437
442 self.sa.add(new_repo)
438 self.sa.add(new_repo)
443
439
444 EMPTY_PERM = 'repository.none'
440 EMPTY_PERM = 'repository.none'
445 if fork_of and copy_fork_permissions:
441 if fork_of and copy_fork_permissions:
446 repo = fork_of
442 repo = fork_of
447 user_perms = UserRepoToPerm.query() \
443 user_perms = UserRepoToPerm.query() \
448 .filter(UserRepoToPerm.repository == repo).all()
444 .filter(UserRepoToPerm.repository == repo).all()
449 group_perms = UserGroupRepoToPerm.query() \
445 group_perms = UserGroupRepoToPerm.query() \
450 .filter(UserGroupRepoToPerm.repository == repo).all()
446 .filter(UserGroupRepoToPerm.repository == repo).all()
451
447
452 for perm in user_perms:
448 for perm in user_perms:
453 UserRepoToPerm.create(
449 UserRepoToPerm.create(
454 perm.user, new_repo, perm.permission)
450 perm.user, new_repo, perm.permission)
455
451
456 for perm in group_perms:
452 for perm in group_perms:
457 UserGroupRepoToPerm.create(
453 UserGroupRepoToPerm.create(
458 perm.users_group, new_repo, perm.permission)
454 perm.users_group, new_repo, perm.permission)
459 # in case we copy permissions and also set this repo to private
455 # in case we copy permissions and also set this repo to private
460 # override the default user permission to make it a private
456 # override the default user permission to make it a private
461 # repo
457 # repo
462 if private:
458 if private:
463 RepoModel(self.sa).grant_user_permission(
459 RepoModel(self.sa).grant_user_permission(
464 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
460 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
465
461
466 elif repo_group and copy_group_permissions:
462 elif repo_group and copy_group_permissions:
467 user_perms = UserRepoGroupToPerm.query() \
463 user_perms = UserRepoGroupToPerm.query() \
468 .filter(UserRepoGroupToPerm.group == repo_group).all()
464 .filter(UserRepoGroupToPerm.group == repo_group).all()
469
465
470 group_perms = UserGroupRepoGroupToPerm.query() \
466 group_perms = UserGroupRepoGroupToPerm.query() \
471 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
467 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
472
468
473 for perm in user_perms:
469 for perm in user_perms:
474 perm_name = perm.permission.permission_name.replace(
470 perm_name = perm.permission.permission_name.replace(
475 'group.', 'repository.')
471 'group.', 'repository.')
476 perm_obj = Permission.get_by_key(perm_name)
472 perm_obj = Permission.get_by_key(perm_name)
477 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
473 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
478
474
479 for perm in group_perms:
475 for perm in group_perms:
480 perm_name = perm.permission.permission_name.replace(
476 perm_name = perm.permission.permission_name.replace(
481 'group.', 'repository.')
477 'group.', 'repository.')
482 perm_obj = Permission.get_by_key(perm_name)
478 perm_obj = Permission.get_by_key(perm_name)
483 UserGroupRepoToPerm.create(
479 UserGroupRepoToPerm.create(
484 perm.users_group, new_repo, perm_obj)
480 perm.users_group, new_repo, perm_obj)
485
481
486 if private:
482 if private:
487 RepoModel(self.sa).grant_user_permission(
483 RepoModel(self.sa).grant_user_permission(
488 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
484 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
489
485
490 else:
486 else:
491 perm_obj = self._create_default_perms(new_repo, private)
487 perm_obj = self._create_default_perms(new_repo, private)
492 self.sa.add(perm_obj)
488 self.sa.add(perm_obj)
493
489
494 # now automatically start following this repository as owner
490 # now automatically start following this repository as owner
495 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
491 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
496 owner.user_id)
492 owner.user_id)
497
493
498 # we need to flush here, in order to check if database won't
494 # we need to flush here, in order to check if database won't
499 # throw any exceptions, create filesystem dirs at the very end
495 # throw any exceptions, create filesystem dirs at the very end
500 self.sa.flush()
496 self.sa.flush()
501 events.trigger(events.RepoCreateEvent(new_repo))
497 events.trigger(events.RepoCreateEvent(new_repo))
502 return new_repo
498 return new_repo
503
499
504 except Exception:
500 except Exception:
505 log.error(traceback.format_exc())
501 log.error(traceback.format_exc())
506 raise
502 raise
507
503
508 def create(self, form_data, cur_user):
504 def create(self, form_data, cur_user):
509 """
505 """
510 Create repository using celery tasks
506 Create repository using celery tasks
511
507
512 :param form_data:
508 :param form_data:
513 :param cur_user:
509 :param cur_user:
514 """
510 """
515 from rhodecode.lib.celerylib import tasks, run_task
511 from rhodecode.lib.celerylib import tasks, run_task
516 return run_task(tasks.create_repo, form_data, cur_user)
512 return run_task(tasks.create_repo, form_data, cur_user)
517
513
518 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
514 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
519 perm_deletions=None, check_perms=True,
515 perm_deletions=None, check_perms=True,
520 cur_user=None):
516 cur_user=None):
521 if not perm_additions:
517 if not perm_additions:
522 perm_additions = []
518 perm_additions = []
523 if not perm_updates:
519 if not perm_updates:
524 perm_updates = []
520 perm_updates = []
525 if not perm_deletions:
521 if not perm_deletions:
526 perm_deletions = []
522 perm_deletions = []
527
523
528 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
524 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
529
525
530 changes = {
526 changes = {
531 'added': [],
527 'added': [],
532 'updated': [],
528 'updated': [],
533 'deleted': []
529 'deleted': []
534 }
530 }
535 # update permissions
531 # update permissions
536 for member_id, perm, member_type in perm_updates:
532 for member_id, perm, member_type in perm_updates:
537 member_id = int(member_id)
533 member_id = int(member_id)
538 if member_type == 'user':
534 if member_type == 'user':
539 member_name = User.get(member_id).username
535 member_name = User.get(member_id).username
540 # this updates also current one if found
536 # this updates also current one if found
541 self.grant_user_permission(
537 self.grant_user_permission(
542 repo=repo, user=member_id, perm=perm)
538 repo=repo, user=member_id, perm=perm)
543 else: # set for user group
539 else: # set for user group
544 # check if we have permissions to alter this usergroup
540 # check if we have permissions to alter this usergroup
545 member_name = UserGroup.get(member_id).users_group_name
541 member_name = UserGroup.get(member_id).users_group_name
546 if not check_perms or HasUserGroupPermissionAny(
542 if not check_perms or HasUserGroupPermissionAny(
547 *req_perms)(member_name, user=cur_user):
543 *req_perms)(member_name, user=cur_user):
548 self.grant_user_group_permission(
544 self.grant_user_group_permission(
549 repo=repo, group_name=member_id, perm=perm)
545 repo=repo, group_name=member_id, perm=perm)
550
546
551 changes['updated'].append({'type': member_type, 'id': member_id,
547 changes['updated'].append({'type': member_type, 'id': member_id,
552 'name': member_name, 'new_perm': perm})
548 'name': member_name, 'new_perm': perm})
553
549
554 # set new permissions
550 # set new permissions
555 for member_id, perm, member_type in perm_additions:
551 for member_id, perm, member_type in perm_additions:
556 member_id = int(member_id)
552 member_id = int(member_id)
557 if member_type == 'user':
553 if member_type == 'user':
558 member_name = User.get(member_id).username
554 member_name = User.get(member_id).username
559 self.grant_user_permission(
555 self.grant_user_permission(
560 repo=repo, user=member_id, perm=perm)
556 repo=repo, user=member_id, perm=perm)
561 else: # set for user group
557 else: # set for user group
562 # check if we have permissions to alter this usergroup
558 # check if we have permissions to alter this usergroup
563 member_name = UserGroup.get(member_id).users_group_name
559 member_name = UserGroup.get(member_id).users_group_name
564 if not check_perms or HasUserGroupPermissionAny(
560 if not check_perms or HasUserGroupPermissionAny(
565 *req_perms)(member_name, user=cur_user):
561 *req_perms)(member_name, user=cur_user):
566 self.grant_user_group_permission(
562 self.grant_user_group_permission(
567 repo=repo, group_name=member_id, perm=perm)
563 repo=repo, group_name=member_id, perm=perm)
568 changes['added'].append({'type': member_type, 'id': member_id,
564 changes['added'].append({'type': member_type, 'id': member_id,
569 'name': member_name, 'new_perm': perm})
565 'name': member_name, 'new_perm': perm})
570 # delete permissions
566 # delete permissions
571 for member_id, perm, member_type in perm_deletions:
567 for member_id, perm, member_type in perm_deletions:
572 member_id = int(member_id)
568 member_id = int(member_id)
573 if member_type == 'user':
569 if member_type == 'user':
574 member_name = User.get(member_id).username
570 member_name = User.get(member_id).username
575 self.revoke_user_permission(repo=repo, user=member_id)
571 self.revoke_user_permission(repo=repo, user=member_id)
576 else: # set for user group
572 else: # set for user group
577 # check if we have permissions to alter this usergroup
573 # check if we have permissions to alter this usergroup
578 member_name = UserGroup.get(member_id).users_group_name
574 member_name = UserGroup.get(member_id).users_group_name
579 if not check_perms or HasUserGroupPermissionAny(
575 if not check_perms or HasUserGroupPermissionAny(
580 *req_perms)(member_name, user=cur_user):
576 *req_perms)(member_name, user=cur_user):
581 self.revoke_user_group_permission(
577 self.revoke_user_group_permission(
582 repo=repo, group_name=member_id)
578 repo=repo, group_name=member_id)
583
579
584 changes['deleted'].append({'type': member_type, 'id': member_id,
580 changes['deleted'].append({'type': member_type, 'id': member_id,
585 'name': member_name, 'new_perm': perm})
581 'name': member_name, 'new_perm': perm})
586 return changes
582 return changes
587
583
588 def create_fork(self, form_data, cur_user):
584 def create_fork(self, form_data, cur_user):
589 """
585 """
590 Simple wrapper into executing celery task for fork creation
586 Simple wrapper into executing celery task for fork creation
591
587
592 :param form_data:
588 :param form_data:
593 :param cur_user:
589 :param cur_user:
594 """
590 """
595 from rhodecode.lib.celerylib import tasks, run_task
591 from rhodecode.lib.celerylib import tasks, run_task
596 return run_task(tasks.create_repo_fork, form_data, cur_user)
592 return run_task(tasks.create_repo_fork, form_data, cur_user)
597
593
598 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
594 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
599 """
595 """
600 Delete given repository, forks parameter defines what do do with
596 Delete given repository, forks parameter defines what do do with
601 attached forks. Throws AttachedForksError if deleted repo has attached
597 attached forks. Throws AttachedForksError if deleted repo has attached
602 forks
598 forks
603
599
604 :param repo:
600 :param repo:
605 :param forks: str 'delete' or 'detach'
601 :param forks: str 'delete' or 'detach'
606 :param fs_remove: remove(archive) repo from filesystem
602 :param fs_remove: remove(archive) repo from filesystem
607 """
603 """
608 if not cur_user:
604 if not cur_user:
609 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
605 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
610 repo = self._get_repo(repo)
606 repo = self._get_repo(repo)
611 if repo:
607 if repo:
612 if forks == 'detach':
608 if forks == 'detach':
613 for r in repo.forks:
609 for r in repo.forks:
614 r.fork = None
610 r.fork = None
615 self.sa.add(r)
611 self.sa.add(r)
616 elif forks == 'delete':
612 elif forks == 'delete':
617 for r in repo.forks:
613 for r in repo.forks:
618 self.delete(r, forks='delete')
614 self.delete(r, forks='delete')
619 elif [f for f in repo.forks]:
615 elif [f for f in repo.forks]:
620 raise AttachedForksError()
616 raise AttachedForksError()
621
617
622 old_repo_dict = repo.get_dict()
618 old_repo_dict = repo.get_dict()
623 events.trigger(events.RepoPreDeleteEvent(repo))
619 events.trigger(events.RepoPreDeleteEvent(repo))
624 try:
620 try:
625 self.sa.delete(repo)
621 self.sa.delete(repo)
626 if fs_remove:
622 if fs_remove:
627 self._delete_filesystem_repo(repo)
623 self._delete_filesystem_repo(repo)
628 else:
624 else:
629 log.debug('skipping removal from filesystem')
625 log.debug('skipping removal from filesystem')
630 old_repo_dict.update({
626 old_repo_dict.update({
631 'deleted_by': cur_user,
627 'deleted_by': cur_user,
632 'deleted_on': time.time(),
628 'deleted_on': time.time(),
633 })
629 })
634 log_delete_repository(**old_repo_dict)
630 log_delete_repository(**old_repo_dict)
635 events.trigger(events.RepoDeleteEvent(repo))
631 events.trigger(events.RepoDeleteEvent(repo))
636 except Exception:
632 except Exception:
637 log.error(traceback.format_exc())
633 log.error(traceback.format_exc())
638 raise
634 raise
639
635
640 def grant_user_permission(self, repo, user, perm):
636 def grant_user_permission(self, repo, user, perm):
641 """
637 """
642 Grant permission for user on given repository, or update existing one
638 Grant permission for user on given repository, or update existing one
643 if found
639 if found
644
640
645 :param repo: Instance of Repository, repository_id, or repository name
641 :param repo: Instance of Repository, repository_id, or repository name
646 :param user: Instance of User, user_id or username
642 :param user: Instance of User, user_id or username
647 :param perm: Instance of Permission, or permission_name
643 :param perm: Instance of Permission, or permission_name
648 """
644 """
649 user = self._get_user(user)
645 user = self._get_user(user)
650 repo = self._get_repo(repo)
646 repo = self._get_repo(repo)
651 permission = self._get_perm(perm)
647 permission = self._get_perm(perm)
652
648
653 # check if we have that permission already
649 # check if we have that permission already
654 obj = self.sa.query(UserRepoToPerm) \
650 obj = self.sa.query(UserRepoToPerm) \
655 .filter(UserRepoToPerm.user == user) \
651 .filter(UserRepoToPerm.user == user) \
656 .filter(UserRepoToPerm.repository == repo) \
652 .filter(UserRepoToPerm.repository == repo) \
657 .scalar()
653 .scalar()
658 if obj is None:
654 if obj is None:
659 # create new !
655 # create new !
660 obj = UserRepoToPerm()
656 obj = UserRepoToPerm()
661 obj.repository = repo
657 obj.repository = repo
662 obj.user = user
658 obj.user = user
663 obj.permission = permission
659 obj.permission = permission
664 self.sa.add(obj)
660 self.sa.add(obj)
665 log.debug('Granted perm %s to %s on %s', perm, user, repo)
661 log.debug('Granted perm %s to %s on %s', perm, user, repo)
666 action_logger_generic(
662 action_logger_generic(
667 'granted permission: {} to user: {} on repo: {}'.format(
663 'granted permission: {} to user: {} on repo: {}'.format(
668 perm, user, repo), namespace='security.repo')
664 perm, user, repo), namespace='security.repo')
669 return obj
665 return obj
670
666
671 def revoke_user_permission(self, repo, user):
667 def revoke_user_permission(self, repo, user):
672 """
668 """
673 Revoke permission for user on given repository
669 Revoke permission for user on given repository
674
670
675 :param repo: Instance of Repository, repository_id, or repository name
671 :param repo: Instance of Repository, repository_id, or repository name
676 :param user: Instance of User, user_id or username
672 :param user: Instance of User, user_id or username
677 """
673 """
678
674
679 user = self._get_user(user)
675 user = self._get_user(user)
680 repo = self._get_repo(repo)
676 repo = self._get_repo(repo)
681
677
682 obj = self.sa.query(UserRepoToPerm) \
678 obj = self.sa.query(UserRepoToPerm) \
683 .filter(UserRepoToPerm.repository == repo) \
679 .filter(UserRepoToPerm.repository == repo) \
684 .filter(UserRepoToPerm.user == user) \
680 .filter(UserRepoToPerm.user == user) \
685 .scalar()
681 .scalar()
686 if obj:
682 if obj:
687 self.sa.delete(obj)
683 self.sa.delete(obj)
688 log.debug('Revoked perm on %s on %s', repo, user)
684 log.debug('Revoked perm on %s on %s', repo, user)
689 action_logger_generic(
685 action_logger_generic(
690 'revoked permission from user: {} on repo: {}'.format(
686 'revoked permission from user: {} on repo: {}'.format(
691 user, repo), namespace='security.repo')
687 user, repo), namespace='security.repo')
692
688
693 def grant_user_group_permission(self, repo, group_name, perm):
689 def grant_user_group_permission(self, repo, group_name, perm):
694 """
690 """
695 Grant permission for user group on given repository, or update
691 Grant permission for user group on given repository, or update
696 existing one if found
692 existing one if found
697
693
698 :param repo: Instance of Repository, repository_id, or repository name
694 :param repo: Instance of Repository, repository_id, or repository name
699 :param group_name: Instance of UserGroup, users_group_id,
695 :param group_name: Instance of UserGroup, users_group_id,
700 or user group name
696 or user group name
701 :param perm: Instance of Permission, or permission_name
697 :param perm: Instance of Permission, or permission_name
702 """
698 """
703 repo = self._get_repo(repo)
699 repo = self._get_repo(repo)
704 group_name = self._get_user_group(group_name)
700 group_name = self._get_user_group(group_name)
705 permission = self._get_perm(perm)
701 permission = self._get_perm(perm)
706
702
707 # check if we have that permission already
703 # check if we have that permission already
708 obj = self.sa.query(UserGroupRepoToPerm) \
704 obj = self.sa.query(UserGroupRepoToPerm) \
709 .filter(UserGroupRepoToPerm.users_group == group_name) \
705 .filter(UserGroupRepoToPerm.users_group == group_name) \
710 .filter(UserGroupRepoToPerm.repository == repo) \
706 .filter(UserGroupRepoToPerm.repository == repo) \
711 .scalar()
707 .scalar()
712
708
713 if obj is None:
709 if obj is None:
714 # create new
710 # create new
715 obj = UserGroupRepoToPerm()
711 obj = UserGroupRepoToPerm()
716
712
717 obj.repository = repo
713 obj.repository = repo
718 obj.users_group = group_name
714 obj.users_group = group_name
719 obj.permission = permission
715 obj.permission = permission
720 self.sa.add(obj)
716 self.sa.add(obj)
721 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
717 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
722 action_logger_generic(
718 action_logger_generic(
723 'granted permission: {} to usergroup: {} on repo: {}'.format(
719 'granted permission: {} to usergroup: {} on repo: {}'.format(
724 perm, group_name, repo), namespace='security.repo')
720 perm, group_name, repo), namespace='security.repo')
725
721
726 return obj
722 return obj
727
723
728 def revoke_user_group_permission(self, repo, group_name):
724 def revoke_user_group_permission(self, repo, group_name):
729 """
725 """
730 Revoke permission for user group on given repository
726 Revoke permission for user group on given repository
731
727
732 :param repo: Instance of Repository, repository_id, or repository name
728 :param repo: Instance of Repository, repository_id, or repository name
733 :param group_name: Instance of UserGroup, users_group_id,
729 :param group_name: Instance of UserGroup, users_group_id,
734 or user group name
730 or user group name
735 """
731 """
736 repo = self._get_repo(repo)
732 repo = self._get_repo(repo)
737 group_name = self._get_user_group(group_name)
733 group_name = self._get_user_group(group_name)
738
734
739 obj = self.sa.query(UserGroupRepoToPerm) \
735 obj = self.sa.query(UserGroupRepoToPerm) \
740 .filter(UserGroupRepoToPerm.repository == repo) \
736 .filter(UserGroupRepoToPerm.repository == repo) \
741 .filter(UserGroupRepoToPerm.users_group == group_name) \
737 .filter(UserGroupRepoToPerm.users_group == group_name) \
742 .scalar()
738 .scalar()
743 if obj:
739 if obj:
744 self.sa.delete(obj)
740 self.sa.delete(obj)
745 log.debug('Revoked perm to %s on %s', repo, group_name)
741 log.debug('Revoked perm to %s on %s', repo, group_name)
746 action_logger_generic(
742 action_logger_generic(
747 'revoked permission from usergroup: {} on repo: {}'.format(
743 'revoked permission from usergroup: {} on repo: {}'.format(
748 group_name, repo), namespace='security.repo')
744 group_name, repo), namespace='security.repo')
749
745
750 def delete_stats(self, repo_name):
746 def delete_stats(self, repo_name):
751 """
747 """
752 removes stats for given repo
748 removes stats for given repo
753
749
754 :param repo_name:
750 :param repo_name:
755 """
751 """
756 repo = self._get_repo(repo_name)
752 repo = self._get_repo(repo_name)
757 try:
753 try:
758 obj = self.sa.query(Statistics) \
754 obj = self.sa.query(Statistics) \
759 .filter(Statistics.repository == repo).scalar()
755 .filter(Statistics.repository == repo).scalar()
760 if obj:
756 if obj:
761 self.sa.delete(obj)
757 self.sa.delete(obj)
762 except Exception:
758 except Exception:
763 log.error(traceback.format_exc())
759 log.error(traceback.format_exc())
764 raise
760 raise
765
761
766 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
762 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
767 field_type='str', field_desc=''):
763 field_type='str', field_desc=''):
768
764
769 repo = self._get_repo(repo_name)
765 repo = self._get_repo(repo_name)
770
766
771 new_field = RepositoryField()
767 new_field = RepositoryField()
772 new_field.repository = repo
768 new_field.repository = repo
773 new_field.field_key = field_key
769 new_field.field_key = field_key
774 new_field.field_type = field_type # python type
770 new_field.field_type = field_type # python type
775 new_field.field_value = field_value
771 new_field.field_value = field_value
776 new_field.field_desc = field_desc
772 new_field.field_desc = field_desc
777 new_field.field_label = field_label
773 new_field.field_label = field_label
778 self.sa.add(new_field)
774 self.sa.add(new_field)
779 return new_field
775 return new_field
780
776
781 def delete_repo_field(self, repo_name, field_key):
777 def delete_repo_field(self, repo_name, field_key):
782 repo = self._get_repo(repo_name)
778 repo = self._get_repo(repo_name)
783 field = RepositoryField.get_by_key_name(field_key, repo)
779 field = RepositoryField.get_by_key_name(field_key, repo)
784 if field:
780 if field:
785 self.sa.delete(field)
781 self.sa.delete(field)
786
782
787 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
783 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
788 clone_uri=None, repo_store_location=None,
784 clone_uri=None, repo_store_location=None,
789 use_global_config=False):
785 use_global_config=False):
790 """
786 """
791 makes repository on filesystem. It's group aware means it'll create
787 makes repository on filesystem. It's group aware means it'll create
792 a repository within a group, and alter the paths accordingly of
788 a repository within a group, and alter the paths accordingly of
793 group location
789 group location
794
790
795 :param repo_name:
791 :param repo_name:
796 :param alias:
792 :param alias:
797 :param parent:
793 :param parent:
798 :param clone_uri:
794 :param clone_uri:
799 :param repo_store_location:
795 :param repo_store_location:
800 """
796 """
801 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
797 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
802 from rhodecode.model.scm import ScmModel
798 from rhodecode.model.scm import ScmModel
803
799
804 if Repository.NAME_SEP in repo_name:
800 if Repository.NAME_SEP in repo_name:
805 raise ValueError(
801 raise ValueError(
806 'repo_name must not contain groups got `%s`' % repo_name)
802 'repo_name must not contain groups got `%s`' % repo_name)
807
803
808 if isinstance(repo_group, RepoGroup):
804 if isinstance(repo_group, RepoGroup):
809 new_parent_path = os.sep.join(repo_group.full_path_splitted)
805 new_parent_path = os.sep.join(repo_group.full_path_splitted)
810 else:
806 else:
811 new_parent_path = repo_group or ''
807 new_parent_path = repo_group or ''
812
808
813 if repo_store_location:
809 if repo_store_location:
814 _paths = [repo_store_location]
810 _paths = [repo_store_location]
815 else:
811 else:
816 _paths = [self.repos_path, new_parent_path, repo_name]
812 _paths = [self.repos_path, new_parent_path, repo_name]
817 # we need to make it str for mercurial
813 # we need to make it str for mercurial
818 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
814 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
819
815
820 # check if this path is not a repository
816 # check if this path is not a repository
821 if is_valid_repo(repo_path, self.repos_path):
817 if is_valid_repo(repo_path, self.repos_path):
822 raise Exception('This path %s is a valid repository' % repo_path)
818 raise Exception('This path %s is a valid repository' % repo_path)
823
819
824 # check if this path is a group
820 # check if this path is a group
825 if is_valid_repo_group(repo_path, self.repos_path):
821 if is_valid_repo_group(repo_path, self.repos_path):
826 raise Exception('This path %s is a valid group' % repo_path)
822 raise Exception('This path %s is a valid group' % repo_path)
827
823
828 log.info('creating repo %s in %s from url: `%s`',
824 log.info('creating repo %s in %s from url: `%s`',
829 repo_name, safe_unicode(repo_path),
825 repo_name, safe_unicode(repo_path),
830 obfuscate_url_pw(clone_uri))
826 obfuscate_url_pw(clone_uri))
831
827
832 backend = get_backend(repo_type)
828 backend = get_backend(repo_type)
833
829
834 config_repo = None if use_global_config else repo_name
830 config_repo = None if use_global_config else repo_name
835 if config_repo and new_parent_path:
831 if config_repo and new_parent_path:
836 config_repo = Repository.NAME_SEP.join(
832 config_repo = Repository.NAME_SEP.join(
837 (new_parent_path, config_repo))
833 (new_parent_path, config_repo))
838 config = make_db_config(clear_session=False, repo=config_repo)
834 config = make_db_config(clear_session=False, repo=config_repo)
839 config.set('extensions', 'largefiles', '')
835 config.set('extensions', 'largefiles', '')
840
836
841 # patch and reset hooks section of UI config to not run any
837 # patch and reset hooks section of UI config to not run any
842 # hooks on creating remote repo
838 # hooks on creating remote repo
843 config.clear_section('hooks')
839 config.clear_section('hooks')
844
840
845 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
841 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
846 if repo_type == 'git':
842 if repo_type == 'git':
847 repo = backend(
843 repo = backend(
848 repo_path, config=config, create=True, src_url=clone_uri,
844 repo_path, config=config, create=True, src_url=clone_uri,
849 bare=True)
845 bare=True)
850 else:
846 else:
851 repo = backend(
847 repo = backend(
852 repo_path, config=config, create=True, src_url=clone_uri)
848 repo_path, config=config, create=True, src_url=clone_uri)
853
849
854 ScmModel().install_hooks(repo, repo_type=repo_type)
850 ScmModel().install_hooks(repo, repo_type=repo_type)
855
851
856 log.debug('Created repo %s with %s backend',
852 log.debug('Created repo %s with %s backend',
857 safe_unicode(repo_name), safe_unicode(repo_type))
853 safe_unicode(repo_name), safe_unicode(repo_type))
858 return repo
854 return repo
859
855
860 def _rename_filesystem_repo(self, old, new):
856 def _rename_filesystem_repo(self, old, new):
861 """
857 """
862 renames repository on filesystem
858 renames repository on filesystem
863
859
864 :param old: old name
860 :param old: old name
865 :param new: new name
861 :param new: new name
866 """
862 """
867 log.info('renaming repo from %s to %s', old, new)
863 log.info('renaming repo from %s to %s', old, new)
868
864
869 old_path = os.path.join(self.repos_path, old)
865 old_path = os.path.join(self.repos_path, old)
870 new_path = os.path.join(self.repos_path, new)
866 new_path = os.path.join(self.repos_path, new)
871 if os.path.isdir(new_path):
867 if os.path.isdir(new_path):
872 raise Exception(
868 raise Exception(
873 'Was trying to rename to already existing dir %s' % new_path
869 'Was trying to rename to already existing dir %s' % new_path
874 )
870 )
875 shutil.move(old_path, new_path)
871 shutil.move(old_path, new_path)
876
872
877 def _delete_filesystem_repo(self, repo):
873 def _delete_filesystem_repo(self, repo):
878 """
874 """
879 removes repo from filesystem, the removal is acctually made by
875 removes repo from filesystem, the removal is acctually made by
880 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
876 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
881 repository is no longer valid for rhodecode, can be undeleted later on
877 repository is no longer valid for rhodecode, can be undeleted later on
882 by reverting the renames on this repository
878 by reverting the renames on this repository
883
879
884 :param repo: repo object
880 :param repo: repo object
885 """
881 """
886 rm_path = os.path.join(self.repos_path, repo.repo_name)
882 rm_path = os.path.join(self.repos_path, repo.repo_name)
887 repo_group = repo.group
883 repo_group = repo.group
888 log.info("Removing repository %s", rm_path)
884 log.info("Removing repository %s", rm_path)
889 # disable hg/git internal that it doesn't get detected as repo
885 # disable hg/git internal that it doesn't get detected as repo
890 alias = repo.repo_type
886 alias = repo.repo_type
891
887
892 config = make_db_config(clear_session=False)
888 config = make_db_config(clear_session=False)
893 config.set('extensions', 'largefiles', '')
889 config.set('extensions', 'largefiles', '')
894 bare = getattr(repo.scm_instance(config=config), 'bare', False)
890 bare = getattr(repo.scm_instance(config=config), 'bare', False)
895
891
896 # skip this for bare git repos
892 # skip this for bare git repos
897 if not bare:
893 if not bare:
898 # disable VCS repo
894 # disable VCS repo
899 vcs_path = os.path.join(rm_path, '.%s' % alias)
895 vcs_path = os.path.join(rm_path, '.%s' % alias)
900 if os.path.exists(vcs_path):
896 if os.path.exists(vcs_path):
901 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
897 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
902
898
903 _now = datetime.datetime.now()
899 _now = datetime.datetime.now()
904 _ms = str(_now.microsecond).rjust(6, '0')
900 _ms = str(_now.microsecond).rjust(6, '0')
905 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
901 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
906 repo.just_name)
902 repo.just_name)
907 if repo_group:
903 if repo_group:
908 # if repository is in group, prefix the removal path with the group
904 # if repository is in group, prefix the removal path with the group
909 args = repo_group.full_path_splitted + [_d]
905 args = repo_group.full_path_splitted + [_d]
910 _d = os.path.join(*args)
906 _d = os.path.join(*args)
911
907
912 if os.path.isdir(rm_path):
908 if os.path.isdir(rm_path):
913 shutil.move(rm_path, os.path.join(self.repos_path, _d))
909 shutil.move(rm_path, os.path.join(self.repos_path, _d))
914
910
915
911
916 class ReadmeFinder:
912 class ReadmeFinder:
917 """
913 """
918 Utility which knows how to find a readme for a specific commit.
914 Utility which knows how to find a readme for a specific commit.
919
915
920 The main idea is that this is a configurable algorithm. When creating an
916 The main idea is that this is a configurable algorithm. When creating an
921 instance you can define parameters, currently only the `default_renderer`.
917 instance you can define parameters, currently only the `default_renderer`.
922 Based on this configuration the method :meth:`search` behaves slightly
918 Based on this configuration the method :meth:`search` behaves slightly
923 different.
919 different.
924 """
920 """
925
921
926 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
922 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
927 path_re = re.compile(r'^docs?', re.IGNORECASE)
923 path_re = re.compile(r'^docs?', re.IGNORECASE)
928
924
929 default_priorities = {
925 default_priorities = {
930 None: 0,
926 None: 0,
931 '.text': 2,
927 '.text': 2,
932 '.txt': 3,
928 '.txt': 3,
933 '.rst': 1,
929 '.rst': 1,
934 '.rest': 2,
930 '.rest': 2,
935 '.md': 1,
931 '.md': 1,
936 '.mkdn': 2,
932 '.mkdn': 2,
937 '.mdown': 3,
933 '.mdown': 3,
938 '.markdown': 4,
934 '.markdown': 4,
939 }
935 }
940
936
941 path_priority = {
937 path_priority = {
942 'doc': 0,
938 'doc': 0,
943 'docs': 1,
939 'docs': 1,
944 }
940 }
945
941
946 FALLBACK_PRIORITY = 99
942 FALLBACK_PRIORITY = 99
947
943
948 RENDERER_TO_EXTENSION = {
944 RENDERER_TO_EXTENSION = {
949 'rst': ['.rst', '.rest'],
945 'rst': ['.rst', '.rest'],
950 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
946 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
951 }
947 }
952
948
953 def __init__(self, default_renderer=None):
949 def __init__(self, default_renderer=None):
954 self._default_renderer = default_renderer
950 self._default_renderer = default_renderer
955 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
951 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
956 default_renderer, [])
952 default_renderer, [])
957
953
958 def search(self, commit, path='/'):
954 def search(self, commit, path='/'):
959 """
955 """
960 Find a readme in the given `commit`.
956 Find a readme in the given `commit`.
961 """
957 """
962 nodes = commit.get_nodes(path)
958 nodes = commit.get_nodes(path)
963 matches = self._match_readmes(nodes)
959 matches = self._match_readmes(nodes)
964 matches = self._sort_according_to_priority(matches)
960 matches = self._sort_according_to_priority(matches)
965 if matches:
961 if matches:
966 return matches[0].node
962 return matches[0].node
967
963
968 paths = self._match_paths(nodes)
964 paths = self._match_paths(nodes)
969 paths = self._sort_paths_according_to_priority(paths)
965 paths = self._sort_paths_according_to_priority(paths)
970 for path in paths:
966 for path in paths:
971 match = self.search(commit, path=path)
967 match = self.search(commit, path=path)
972 if match:
968 if match:
973 return match
969 return match
974
970
975 return None
971 return None
976
972
977 def _match_readmes(self, nodes):
973 def _match_readmes(self, nodes):
978 for node in nodes:
974 for node in nodes:
979 if not node.is_file():
975 if not node.is_file():
980 continue
976 continue
981 path = node.path.rsplit('/', 1)[-1]
977 path = node.path.rsplit('/', 1)[-1]
982 match = self.readme_re.match(path)
978 match = self.readme_re.match(path)
983 if match:
979 if match:
984 extension = match.group(1)
980 extension = match.group(1)
985 yield ReadmeMatch(node, match, self._priority(extension))
981 yield ReadmeMatch(node, match, self._priority(extension))
986
982
987 def _match_paths(self, nodes):
983 def _match_paths(self, nodes):
988 for node in nodes:
984 for node in nodes:
989 if not node.is_dir():
985 if not node.is_dir():
990 continue
986 continue
991 match = self.path_re.match(node.path)
987 match = self.path_re.match(node.path)
992 if match:
988 if match:
993 yield node.path
989 yield node.path
994
990
995 def _priority(self, extension):
991 def _priority(self, extension):
996 renderer_priority = (
992 renderer_priority = (
997 0 if extension in self._renderer_extensions else 1)
993 0 if extension in self._renderer_extensions else 1)
998 extension_priority = self.default_priorities.get(
994 extension_priority = self.default_priorities.get(
999 extension, self.FALLBACK_PRIORITY)
995 extension, self.FALLBACK_PRIORITY)
1000 return (renderer_priority, extension_priority)
996 return (renderer_priority, extension_priority)
1001
997
1002 def _sort_according_to_priority(self, matches):
998 def _sort_according_to_priority(self, matches):
1003
999
1004 def priority_and_path(match):
1000 def priority_and_path(match):
1005 return (match.priority, match.path)
1001 return (match.priority, match.path)
1006
1002
1007 return sorted(matches, key=priority_and_path)
1003 return sorted(matches, key=priority_and_path)
1008
1004
1009 def _sort_paths_according_to_priority(self, paths):
1005 def _sort_paths_according_to_priority(self, paths):
1010
1006
1011 def priority_and_path(path):
1007 def priority_and_path(path):
1012 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1008 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1013
1009
1014 return sorted(paths, key=priority_and_path)
1010 return sorted(paths, key=priority_and_path)
1015
1011
1016
1012
1017 class ReadmeMatch:
1013 class ReadmeMatch:
1018
1014
1019 def __init__(self, node, match, priority):
1015 def __init__(self, node, match, priority):
1020 self.node = node
1016 self.node = node
1021 self._match = match
1017 self._match = match
1022 self.priority = priority
1018 self.priority = priority
1023
1019
1024 @property
1020 @property
1025 def path(self):
1021 def path(self):
1026 return self.node.path
1022 return self.node.path
1027
1023
1028 def __repr__(self):
1024 def __repr__(self):
1029 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1025 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,101 +1,101 b''
1 // Global keyboard bindings
1 // Global keyboard bindings
2
2
3 function setRCMouseBindings(repoName, repoLandingRev) {
3 function setRCMouseBindings(repoName, repoLandingRev) {
4
4
5 /** custom callback for supressing mousetrap from firing */
5 /** custom callback for supressing mousetrap from firing */
6 Mousetrap.stopCallback = function(e, element) {
6 Mousetrap.stopCallback = function(e, element) {
7 // if the element has the class "mousetrap" then no need to stop
7 // if the element has the class "mousetrap" then no need to stop
8 if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
8 if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
9 return false;
9 return false;
10 }
10 }
11
11
12 // stop for input, select, and textarea
12 // stop for input, select, and textarea
13 return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
13 return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
14 };
14 };
15
15
16 // general help "?"
16 // general help "?"
17 Mousetrap.bind(['?'], function(e) {
17 Mousetrap.bind(['?'], function(e) {
18 $('#help_kb').modal({});
18 $('#help_kb').modal({});
19 });
19 });
20
20
21 // / open the quick filter
21 // / open the quick filter
22 Mousetrap.bind(['/'], function(e) {
22 Mousetrap.bind(['/'], function(e) {
23 $('#repo_switcher').select2('open');
23 $('#repo_switcher').select2('open');
24
24
25 // return false to prevent default browser behavior
25 // return false to prevent default browser behavior
26 // and stop event from bubbling
26 // and stop event from bubbling
27 return false;
27 return false;
28 });
28 });
29
29
30 // ctrl/command+b, show the the main bar
30 // ctrl/command+b, show the the main bar
31 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
31 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
32 var $headerInner = $('#header-inner'),
32 var $headerInner = $('#header-inner'),
33 $content = $('#content');
33 $content = $('#content');
34 if ($headerInner.hasClass('hover') && $content.hasClass('hover')) {
34 if ($headerInner.hasClass('hover') && $content.hasClass('hover')) {
35 $headerInner.removeClass('hover');
35 $headerInner.removeClass('hover');
36 $content.removeClass('hover');
36 $content.removeClass('hover');
37 } else {
37 } else {
38 $headerInner.addClass('hover');
38 $headerInner.addClass('hover');
39 $content.addClass('hover');
39 $content.addClass('hover');
40 }
40 }
41 return false;
41 return false;
42 });
42 });
43
43
44 // general nav g + action
44 // general nav g + action
45 Mousetrap.bind(['g h'], function(e) {
45 Mousetrap.bind(['g h'], function(e) {
46 window.location = pyroutes.url('home');
46 window.location = pyroutes.url('home');
47 });
47 });
48 Mousetrap.bind(['g g'], function(e) {
48 Mousetrap.bind(['g g'], function(e) {
49 window.location = pyroutes.url('gists_show', {'private': 1});
49 window.location = pyroutes.url('gists_show', {'private': 1});
50 });
50 });
51 Mousetrap.bind(['g G'], function(e) {
51 Mousetrap.bind(['g G'], function(e) {
52 window.location = pyroutes.url('gists_show', {'public': 1});
52 window.location = pyroutes.url('gists_show', {'public': 1});
53 });
53 });
54 Mousetrap.bind(['n g'], function(e) {
54 Mousetrap.bind(['n g'], function(e) {
55 window.location = pyroutes.url('gists_new');
55 window.location = pyroutes.url('gists_new');
56 });
56 });
57 Mousetrap.bind(['n r'], function(e) {
57 Mousetrap.bind(['n r'], function(e) {
58 window.location = pyroutes.url('new_repo');
58 window.location = pyroutes.url('repo_new');
59 });
59 });
60
60
61 if (repoName && repoName != '') {
61 if (repoName && repoName != '') {
62 // nav in repo context
62 // nav in repo context
63 Mousetrap.bind(['g s'], function(e) {
63 Mousetrap.bind(['g s'], function(e) {
64 window.location = pyroutes.url(
64 window.location = pyroutes.url(
65 'repo_summary', {'repo_name': repoName});
65 'repo_summary', {'repo_name': repoName});
66 });
66 });
67 Mousetrap.bind(['g c'], function(e) {
67 Mousetrap.bind(['g c'], function(e) {
68 window.location = pyroutes.url(
68 window.location = pyroutes.url(
69 'repo_changelog', {'repo_name': repoName});
69 'repo_changelog', {'repo_name': repoName});
70 });
70 });
71 Mousetrap.bind(['g F'], function(e) {
71 Mousetrap.bind(['g F'], function(e) {
72 window.location = pyroutes.url(
72 window.location = pyroutes.url(
73 'repo_files',
73 'repo_files',
74 {
74 {
75 'repo_name': repoName,
75 'repo_name': repoName,
76 'commit_id': repoLandingRev,
76 'commit_id': repoLandingRev,
77 'f_path': '',
77 'f_path': '',
78 'search': '1'
78 'search': '1'
79 });
79 });
80 });
80 });
81 Mousetrap.bind(['g f'], function(e) {
81 Mousetrap.bind(['g f'], function(e) {
82 window.location = pyroutes.url(
82 window.location = pyroutes.url(
83 'repo_files',
83 'repo_files',
84 {
84 {
85 'repo_name': repoName,
85 'repo_name': repoName,
86 'commit_id': repoLandingRev,
86 'commit_id': repoLandingRev,
87 'f_path': ''
87 'f_path': ''
88 });
88 });
89 });
89 });
90 Mousetrap.bind(['g o'], function(e) {
90 Mousetrap.bind(['g o'], function(e) {
91 window.location = pyroutes.url(
91 window.location = pyroutes.url(
92 'edit_repo', {'repo_name': repoName});
92 'edit_repo', {'repo_name': repoName});
93 });
93 });
94 Mousetrap.bind(['g O'], function(e) {
94 Mousetrap.bind(['g O'], function(e) {
95 window.location = pyroutes.url(
95 window.location = pyroutes.url(
96 'edit_repo_perms', {'repo_name': repoName});
96 'edit_repo_perms', {'repo_name': repoName});
97 });
97 });
98 }
98 }
99 }
99 }
100
100
101 setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit);
101 setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit);
@@ -1,234 +1,251 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
15 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('favicon', '/favicon.ico', []);
18 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('robots', '/robots.txt', []);
19 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
20 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
21 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
22 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
23 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
24 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
25 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
26 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
27 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
28 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
29 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
30 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
31 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
32 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
33 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
34 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
35 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
36 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
37 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
38 pyroutes.register('admin_home', '/_admin', []);
37 pyroutes.register('admin_home', '/_admin', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
38 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
39 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
40 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
42 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
44 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
43 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
45 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
44 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
46 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
45 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
47 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
46 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
48 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
47 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
49 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
48 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
50 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
49 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
51 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
50 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
52 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
51 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
53 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
52 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
54 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
53 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
55 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
54 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
56 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
55 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
57 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
56 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
58 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
57 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
59 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
58 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
60 pyroutes.register('users', '/_admin/users', []);
59 pyroutes.register('users', '/_admin/users', []);
61 pyroutes.register('users_data', '/_admin/users_data', []);
60 pyroutes.register('users_data', '/_admin/users_data', []);
62 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
61 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
63 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
62 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
64 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
63 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
65 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
64 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
66 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
65 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
67 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
66 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
68 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
67 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
69 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
68 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
70 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
69 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
71 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
70 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
72 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
71 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
73 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
72 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
74 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
73 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
75 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
74 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
76 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
75 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
77 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
76 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
78 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
77 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
79 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
78 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
80 pyroutes.register('user_groups', '/_admin/user_groups', []);
79 pyroutes.register('user_groups', '/_admin/user_groups', []);
81 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
80 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
82 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
81 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
83 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
82 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
84 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
83 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
84 pyroutes.register('repos', '/_admin/repos', []);
85 pyroutes.register('repo_new', '/_admin/repos/new', []);
86 pyroutes.register('repo_create', '/_admin/repos/create', []);
85 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
87 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
86 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
88 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
87 pyroutes.register('channelstream_proxy', '/_channelstream', []);
89 pyroutes.register('channelstream_proxy', '/_channelstream', []);
88 pyroutes.register('login', '/_admin/login', []);
90 pyroutes.register('login', '/_admin/login', []);
89 pyroutes.register('logout', '/_admin/logout', []);
91 pyroutes.register('logout', '/_admin/logout', []);
90 pyroutes.register('register', '/_admin/register', []);
92 pyroutes.register('register', '/_admin/register', []);
91 pyroutes.register('reset_password', '/_admin/password_reset', []);
93 pyroutes.register('reset_password', '/_admin/password_reset', []);
92 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
94 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
93 pyroutes.register('home', '/', []);
95 pyroutes.register('home', '/', []);
94 pyroutes.register('user_autocomplete_data', '/_users', []);
96 pyroutes.register('user_autocomplete_data', '/_users', []);
95 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
97 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
96 pyroutes.register('repo_list_data', '/_repos', []);
98 pyroutes.register('repo_list_data', '/_repos', []);
97 pyroutes.register('goto_switcher_data', '/_goto_data', []);
99 pyroutes.register('goto_switcher_data', '/_goto_data', []);
98 pyroutes.register('journal', '/_admin/journal', []);
100 pyroutes.register('journal', '/_admin/journal', []);
99 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
101 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
100 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
102 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
101 pyroutes.register('journal_public', '/_admin/public_journal', []);
103 pyroutes.register('journal_public', '/_admin/public_journal', []);
102 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
104 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
103 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
105 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
104 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
106 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
105 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
107 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
106 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
108 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
107 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
109 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
108 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
110 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
109 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
111 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
110 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
112 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
111 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
113 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
112 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
114 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
113 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
115 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
114 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
116 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
115 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
117 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
116 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
118 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
117 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
119 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
118 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
120 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
119 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
121 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
120 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
122 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
121 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
123 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
122 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
124 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
123 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
125 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
124 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
126 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
125 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
128 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
127 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
129 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
128 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
134 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
133 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
136 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
137 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
136 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
138 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
137 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
139 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
138 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
139 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
141 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
142 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
141 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
143 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
142 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
144 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
143 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
145 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
144 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
146 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
145 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
147 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
146 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
148 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
147 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
149 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
148 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
150 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
149 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
151 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
150 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
152 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
151 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
153 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
152 pyroutes.register('repo_compare', '/%(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']);
154 pyroutes.register('repo_compare', '/%(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']);
153 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
155 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
154 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
156 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
155 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
157 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
156 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
158 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
157 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
159 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
158 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
160 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
159 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
161 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
160 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
162 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
161 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
163 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
162 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
164 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
163 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
165 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
164 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
166 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
165 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
167 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
166 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
168 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
167 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
169 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
168 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
170 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
169 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
171 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
170 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
172 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
171 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
173 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
172 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
174 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
173 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
175 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
174 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
176 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
175 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
177 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
176 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
178 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
177 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
179 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
178 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
180 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
179 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
181 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
182 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
183 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
184 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
185 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
186 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
187 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
188 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
189 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
190 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
191 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
192 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
193 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
194 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
195 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
196 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
197 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
198 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
180 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
199 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
181 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
200 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
182 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
201 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
183 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
184 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
185 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
202 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
186 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
203 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
187 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
204 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
188 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
205 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
189 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
206 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
190 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
207 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
191 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
208 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
192 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
209 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
193 pyroutes.register('search', '/_admin/search', []);
210 pyroutes.register('search', '/_admin/search', []);
194 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
211 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
195 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
212 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
196 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
213 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
197 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
214 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
198 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
215 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
199 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
216 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
200 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
217 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
201 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
218 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
202 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
219 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
203 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
220 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
204 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
221 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
205 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
222 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
206 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
223 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
207 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
224 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
208 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
225 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
209 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
226 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
210 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
227 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
211 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
228 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
212 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
229 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
213 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
230 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
214 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
231 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
215 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
232 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
216 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
233 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
217 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
234 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
218 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
235 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
219 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
236 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
220 pyroutes.register('gists_show', '/_admin/gists', []);
237 pyroutes.register('gists_show', '/_admin/gists', []);
221 pyroutes.register('gists_new', '/_admin/gists/new', []);
238 pyroutes.register('gists_new', '/_admin/gists/new', []);
222 pyroutes.register('gists_create', '/_admin/gists/create', []);
239 pyroutes.register('gists_create', '/_admin/gists/create', []);
223 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
240 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
224 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
241 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
225 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
242 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
226 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
243 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
227 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
244 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
228 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
245 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
229 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
246 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
230 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
247 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
231 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
248 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
232 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
249 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
233 pyroutes.register('apiv2', '/_admin/api', []);
250 pyroutes.register('apiv2', '/_admin/api', []);
234 }
251 }
@@ -1,37 +1,37 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Add repository')}
5 ${_('Add repository')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 %if c.rhodecode_user.is_admin:
12 %if c.rhodecode_user.is_admin:
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 ${h.link_to(_('Admin'), h.route_path('admin_home'))}
14 &raquo;
14 &raquo;
15 ${h.link_to(_('Repositories'),h.url('repos'))}
15 ${h.link_to(_('Repositories'), h.route_path('repos'))}
16 %else:
16 %else:
17 ${_('Admin')}
17 ${_('Admin')}
18 &raquo;
18 &raquo;
19 ${_('Repositories')}
19 ${_('Repositories')}
20 %endif
20 %endif
21 &raquo;
21 &raquo;
22 ${_('Add Repository')}
22 ${_('Add Repository')}
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_nav()">
25 <%def name="menu_bar_nav()">
26 ${self.menu_items(active='admin')}
26 ${self.menu_items(active='admin')}
27 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30 <div class="box">
30 <div class="box">
31 <!-- box / title -->
31 <!-- box / title -->
32 <div class="title">
32 <div class="title">
33 ${self.breadcrumbs()}
33 ${self.breadcrumbs()}
34 </div>
34 </div>
35 <%include file="repo_add_base.mako"/>
35 <%include file="repo_add_base.mako"/>
36 </div>
36 </div>
37 </%def>
37 </%def>
@@ -1,159 +1,159 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 ${h.secure_form(h.url('repos'))}
3 ${h.secure_form(h.route_path('repo_create'), method='POST', request=request)}
4 <div class="form">
4 <div class="form">
5 <!-- fields -->
5 <!-- fields -->
6 <div class="fields">
6 <div class="fields">
7 <div class="field">
7 <div class="field">
8 <div class="label">
8 <div class="label">
9 <label for="repo_name">${_('Name')}:</label>
9 <label for="repo_name">${_('Name')}:</label>
10 </div>
10 </div>
11 <div class="input">
11 <div class="input">
12 ${h.text('repo_name', class_="medium")}
12 ${h.text('repo_name', class_="medium")}
13 <div class="info-block">
13 <div class="info-block">
14 <a id="remote_clone_toggle" href="#"><i class="icon-download-alt"></i> ${_('Import Existing Repository ?')}</a>
14 <a id="remote_clone_toggle" href="#"><i class="icon-download-alt"></i> ${_('Import Existing Repository ?')}</a>
15 </div>
15 </div>
16 %if not c.rhodecode_user.is_admin:
16 %if not c.rhodecode_user.is_admin:
17 ${h.hidden('user_created',True)}
17 ${h.hidden('user_created',True)}
18 %endif
18 %endif
19 </div>
19 </div>
20 </div>
20 </div>
21 <div id="remote_clone" class="field" style="display: none;">
21 <div id="remote_clone" class="field" style="display: none;">
22 <div class="label">
22 <div class="label">
23 <label for="clone_uri">${_('Clone from')}:</label>
23 <label for="clone_uri">${_('Clone from')}:</label>
24 </div>
24 </div>
25 <div class="input">
25 <div class="input">
26 ${h.text('clone_uri', class_="medium")}
26 ${h.text('clone_uri', class_="medium")}
27 <span class="help-block">
27 <span class="help-block">
28 <pre>
28 <pre>
29 - The repository must be accessible over http:// or https://
29 - The repository must be accessible over http:// or https://
30 - For Git projects it's recommended appending .git to the end of clone url.
30 - For Git projects it's recommended appending .git to the end of clone url.
31 - Make sure to select proper repository type from the below selector before importing it.
31 - Make sure to select proper repository type from the below selector before importing it.
32 - If your HTTP[S] repository is not publicly accessible,
32 - If your HTTP[S] repository is not publicly accessible,
33 add authentication information to the URL: https://username:password@server.company.com/repo-name.
33 add authentication information to the URL: https://username:password@server.company.com/repo-name.
34 - The Git LFS/Mercurial Largefiles objects will not be imported.
34 - The Git LFS/Mercurial Largefiles objects will not be imported.
35 - For very large repositories, it's recommended to manually copy them into the
35 - For very large repositories, it's recommended to manually copy them into the
36 RhodeCode <a href="${h.url('admin_settings_vcs', anchor='vcs-storage-options')}">storage location</a> and run <a href="${h.url('admin_settings_mapping')}">Remap and Rescan</a>.
36 RhodeCode <a href="${h.url('admin_settings_vcs', anchor='vcs-storage-options')}">storage location</a> and run <a href="${h.url('admin_settings_mapping')}">Remap and Rescan</a>.
37 </pre>
37 </pre>
38 </span>
38 </span>
39 </div>
39 </div>
40 </div>
40 </div>
41 <div class="field">
41 <div class="field">
42 <div class="label">
42 <div class="label">
43 <label for="repo_description">${_('Description')}:</label>
43 <label for="repo_description">${_('Description')}:</label>
44 </div>
44 </div>
45 <div class="textarea editor">
45 <div class="textarea editor">
46 ${h.textarea('repo_description')}
46 ${h.textarea('repo_description')}
47 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
47 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
48 </div>
48 </div>
49 </div>
49 </div>
50 <div class="field">
50 <div class="field">
51 <div class="label">
51 <div class="label">
52 <label for="repo_group">${_('Repository Group')}:</label>
52 <label for="repo_group">${_('Repository Group')}:</label>
53 </div>
53 </div>
54 <div class="select">
54 <div class="select">
55 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
55 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
56 % if c.personal_repo_group:
56 % if c.personal_repo_group:
57 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
57 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
58 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
58 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
59 </a>
59 </a>
60 % endif
60 % endif
61 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
61 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
62 </div>
62 </div>
63 </div>
63 </div>
64 <div id="copy_perms" class="field">
64 <div id="copy_perms" class="field">
65 <div class="label label-checkbox">
65 <div class="label label-checkbox">
66 <label for="repo_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
66 <label for="repo_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
67 </div>
67 </div>
68 <div class="checkboxes">
68 <div class="checkboxes">
69 ${h.checkbox('repo_copy_permissions', value="True", checked="checked")}
69 ${h.checkbox('repo_copy_permissions', value="True", checked="checked")}
70 <span class="help-block">${_('Copy permission set from the parent repository group.')}</span>
70 <span class="help-block">${_('Copy permission set from the parent repository group.')}</span>
71 </div>
71 </div>
72 </div>
72 </div>
73 <div class="field">
73 <div class="field">
74 <div class="label">
74 <div class="label">
75 <label for="repo_type">${_('Type')}:</label>
75 <label for="repo_type">${_('Type')}:</label>
76 </div>
76 </div>
77 <div class="select">
77 <div class="select">
78 ${h.select('repo_type','hg',c.backends)}
78 ${h.select('repo_type','hg',c.backends)}
79 <span class="help-block">${_('Set the type of repository to create.')}</span>
79 <span class="help-block">${_('Set the type of repository to create.')}</span>
80 </div>
80 </div>
81 </div>
81 </div>
82 <div class="field">
82 <div class="field">
83 <div class="label">
83 <div class="label">
84 <label for="repo_landing_rev">${_('Landing commit')}:</label>
84 <label for="repo_landing_rev">${_('Landing commit')}:</label>
85 </div>
85 </div>
86 <div class="select">
86 <div class="select">
87 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
87 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
88 <span class="help-block">${_('The default commit for file pages, downloads, full text search index, and README generation.')}</span>
88 <span class="help-block">${_('The default commit for file pages, downloads, full text search index, and README generation.')}</span>
89 </div>
89 </div>
90 </div>
90 </div>
91 <div class="field">
91 <div class="field">
92 <div class="label label-checkbox">
92 <div class="label label-checkbox">
93 <label for="repo_private">${_('Private Repository')}:</label>
93 <label for="repo_private">${_('Private Repository')}:</label>
94 </div>
94 </div>
95 <div class="checkboxes">
95 <div class="checkboxes">
96 ${h.checkbox('repo_private',value="True")}
96 ${h.checkbox('repo_private',value="True")}
97 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
97 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
98 </div>
98 </div>
99 </div>
99 </div>
100 <div class="buttons">
100 <div class="buttons">
101 ${h.submit('save',_('Save'),class_="btn")}
101 ${h.submit('save',_('Save'),class_="btn")}
102 </div>
102 </div>
103 </div>
103 </div>
104 </div>
104 </div>
105 <script>
105 <script>
106 $(document).ready(function(){
106 $(document).ready(function(){
107 var setCopyPermsOption = function(group_val){
107 var setCopyPermsOption = function(group_val){
108 if(group_val != "-1"){
108 if(group_val != "-1"){
109 $('#copy_perms').show()
109 $('#copy_perms').show()
110 }
110 }
111 else{
111 else{
112 $('#copy_perms').hide();
112 $('#copy_perms').hide();
113 }
113 }
114 };
114 };
115
115
116 $('#remote_clone_toggle').on('click', function(e){
116 $('#remote_clone_toggle').on('click', function(e){
117 $('#remote_clone').show();
117 $('#remote_clone').show();
118 e.preventDefault();
118 e.preventDefault();
119 });
119 });
120
120
121 if($('#remote_clone input').hasClass('error')){
121 if($('#remote_clone input').hasClass('error')){
122 $('#remote_clone').show();
122 $('#remote_clone').show();
123 }
123 }
124 if($('#remote_clone input').val()){
124 if($('#remote_clone input').val()){
125 $('#remote_clone').show();
125 $('#remote_clone').show();
126 }
126 }
127
127
128 $("#repo_group").select2({
128 $("#repo_group").select2({
129 'containerCssClass': "drop-menu",
129 'containerCssClass': "drop-menu",
130 'dropdownCssClass': "drop-menu-dropdown",
130 'dropdownCssClass': "drop-menu-dropdown",
131 'dropdownAutoWidth': true,
131 'dropdownAutoWidth': true,
132 'width': "resolve"
132 'width': "resolve"
133 });
133 });
134
134
135 setCopyPermsOption($('#repo_group').val());
135 setCopyPermsOption($('#repo_group').val());
136 $("#repo_group").on("change", function(e) {
136 $("#repo_group").on("change", function(e) {
137 setCopyPermsOption(e.val)
137 setCopyPermsOption(e.val)
138 });
138 });
139
139
140 $("#repo_type").select2({
140 $("#repo_type").select2({
141 'containerCssClass': "drop-menu",
141 'containerCssClass': "drop-menu",
142 'dropdownCssClass': "drop-menu-dropdown",
142 'dropdownCssClass': "drop-menu-dropdown",
143 'minimumResultsForSearch': -1,
143 'minimumResultsForSearch': -1,
144 });
144 });
145 $("#repo_landing_rev").select2({
145 $("#repo_landing_rev").select2({
146 'containerCssClass': "drop-menu",
146 'containerCssClass': "drop-menu",
147 'dropdownCssClass': "drop-menu-dropdown",
147 'dropdownCssClass': "drop-menu-dropdown",
148 'minimumResultsForSearch': -1,
148 'minimumResultsForSearch': -1,
149 });
149 });
150 $('#repo_name').focus();
150 $('#repo_name').focus();
151
151
152 $('#select_my_group').on('click', function(e){
152 $('#select_my_group').on('click', function(e){
153 e.preventDefault();
153 e.preventDefault();
154 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
154 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
155 })
155 })
156
156
157 })
157 })
158 </script>
158 </script>
159 ${h.end_form()}
159 ${h.end_form()}
@@ -1,99 +1,99 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##
2 ##
3 ## See also repo_settings.html
3 ## See also repo_settings.html
4 ##
4 ##
5 <%inherit file="/base/base.mako"/>
5 <%inherit file="/base/base.mako"/>
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('%s repository settings') % c.repo_info.repo_name}
8 ${_('%s repository settings') % c.repo_info.repo_name}
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Settings')}
15 ${_('Settings')}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='repositories')}
19 ${self.menu_items(active='repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_subnav()">
22 <%def name="menu_bar_subnav()">
23 ${self.repo_menu(active='options')}
23 ${self.repo_menu(active='options')}
24 </%def>
24 </%def>
25
25
26 <%def name="main_content()">
26 <%def name="main_content()">
27 % if hasattr(c, 'repo_edit_template'):
27 % if hasattr(c, 'repo_edit_template'):
28 <%include file="${c.repo_edit_template}"/>
28 <%include file="${c.repo_edit_template}"/>
29 % else:
29 % else:
30 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
30 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
31 % endif
31 % endif
32 </%def>
32 </%def>
33
33
34
34
35 <%def name="main()">
35 <%def name="main()">
36 <div class="box">
36 <div class="box">
37 <div class="title">
37 <div class="title">
38 ${self.repo_page_title(c.rhodecode_db_repo)}
38 ${self.repo_page_title(c.rhodecode_db_repo)}
39 ${self.breadcrumbs()}
39 ${self.breadcrumbs()}
40 </div>
40 </div>
41
41
42 <div class="sidebar-col-wrapper scw-small">
42 <div class="sidebar-col-wrapper scw-small">
43 <div class="sidebar">
43 <div class="sidebar">
44 <ul class="nav nav-pills nav-stacked">
44 <ul class="nav nav-pills nav-stacked">
45 <li class="${'active' if c.active=='settings' else ''}">
45 <li class="${'active' if c.active=='settings' else ''}">
46 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
46 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
47 </li>
47 </li>
48 <li class="${'active' if c.active=='permissions' else ''}">
48 <li class="${'active' if c.active=='permissions' else ''}">
49 <a href="${h.route_path('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
49 <a href="${h.route_path('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
50 </li>
50 </li>
51 <li class="${'active' if c.active=='advanced' else ''}">
51 <li class="${'active' if c.active=='advanced' else ''}">
52 <a href="${h.route_path('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
52 <a href="${h.route_path('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
53 </li>
53 </li>
54 <li class="${'active' if c.active=='vcs' else ''}">
54 <li class="${'active' if c.active=='vcs' else ''}">
55 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
55 <a href="${h.route_path('edit_repo_vcs', repo_name=c.repo_name)}">${_('VCS')}</a>
56 </li>
56 </li>
57 <li class="${'active' if c.active=='fields' else ''}">
57 <li class="${'active' if c.active=='fields' else ''}">
58 <a href="${h.url('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
58 <a href="${h.route_path('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
59 </li>
59 </li>
60 <li class="${'active' if c.active=='issuetracker' else ''}">
60 <li class="${'active' if c.active=='issuetracker' else ''}">
61 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
61 <a href="${h.route_path('edit_repo_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
62 </li>
62 </li>
63 <li class="${'active' if c.active=='caches' else ''}">
63 <li class="${'active' if c.active=='caches' else ''}">
64 <a href="${h.route_path('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
64 <a href="${h.route_path('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
65 </li>
65 </li>
66 %if c.repo_info.repo_type != 'svn':
66 %if c.repo_info.repo_type != 'svn':
67 <li class="${'active' if c.active=='remote' else ''}">
67 <li class="${'active' if c.active=='remote' else ''}">
68 <a href="${h.url('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
68 <a href="${h.route_path('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
69 </li>
69 </li>
70 %endif
70 %endif
71 <li class="${'active' if c.active=='statistics' else ''}">
71 <li class="${'active' if c.active=='statistics' else ''}">
72 <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
72 <a href="${h.route_path('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
73 </li>
73 </li>
74 <li class="${'active' if c.active=='integrations' else ''}">
74 <li class="${'active' if c.active=='integrations' else ''}">
75 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
75 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
76 </li>
76 </li>
77 %if c.repo_info.repo_type != 'svn':
77 %if c.repo_info.repo_type != 'svn':
78 <li class="${'active' if c.active=='reviewers' else ''}">
78 <li class="${'active' if c.active=='reviewers' else ''}">
79 <a href="${h.route_path('repo_reviewers', repo_name=c.repo_name)}">${_('Reviewer Rules')}</a>
79 <a href="${h.route_path('repo_reviewers', repo_name=c.repo_name)}">${_('Reviewer Rules')}</a>
80 </li>
80 </li>
81 %endif
81 %endif
82 <li class="${'active' if c.active=='maintenance' else ''}">
82 <li class="${'active' if c.active=='maintenance' else ''}">
83 <a href="${h.route_path('repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
83 <a href="${h.route_path('edit_repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
84 </li>
84 </li>
85 <li class="${'active' if c.active=='strip' else ''}">
85 <li class="${'active' if c.active=='strip' else ''}">
86 <a href="${h.route_path('strip', repo_name=c.repo_name)}">${_('Strip')}</a>
86 <a href="${h.route_path('edit_repo_strip', repo_name=c.repo_name)}">${_('Strip')}</a>
87 </li>
87 </li>
88
88
89 </ul>
89 </ul>
90 </div>
90 </div>
91
91
92 <div class="main-content-full-width">
92 <div class="main-content-full-width">
93 ${self.main_content()}
93 ${self.main_content()}
94 </div>
94 </div>
95
95
96 </div>
96 </div>
97 </div>
97 </div>
98
98
99 </%def> No newline at end of file
99 </%def>
@@ -1,79 +1,79 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Custom extra fields for this repository')}</h3>
3 <h3 class="panel-title">${_('Custom extra fields for this repository')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 %if c.visual.repository_fields:
6 %if c.visual.repository_fields:
7 %if c.repo_fields:
7 %if c.repo_fields:
8 <div class="emails_wrap">
8 <div class="emails_wrap">
9 <table class="rctable edit_fields">
9 <table class="rctable edit_fields">
10 <th>${_('Label')}</th>
10 <th>${_('Label')}</th>
11 <th>${_('Key')}</th>
11 <th>${_('Key')}</th>
12 <th>${_('Type')}</th>
12 <th>${_('Type')}</th>
13 <th>${_('Action')}</th>
13 <th>${_('Action')}</th>
14
14
15 %for field in c.repo_fields:
15 %for field in c.repo_fields:
16 <tr>
16 <tr>
17 <td class="td-tags">${field.field_label}</td>
17 <td class="td-tags">${field.field_label}</td>
18 <td class="td-hash">${field.field_key}</td>
18 <td class="td-hash">${field.field_key}</td>
19 <td class="td-type">${field.field_type}</td>
19 <td class="td-type">${field.field_type}</td>
20 <td class="td-action">
20 <td class="td-action">
21 ${h.secure_form(h.url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
21 ${h.secure_form(h.route_path('edit_repo_fields_delete', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id), method='POST', request=request)}
22 ${h.hidden('del_repo_field',field.repo_field_id)}
22 ${h.hidden('del_repo_field',field.repo_field_id)}
23 <button class="btn btn-link btn-danger" type="submit"
23 <button class="btn btn-link btn-danger" type="submit"
24 onclick="return confirm('${_('Confirm to delete this field: %s') % field.field_key}');">
24 onclick="return confirm('${_('Confirm to delete this field: %s') % field.field_key}');">
25 ${_('Delete')}
25 ${_('Delete')}
26 </button>
26 </button>
27 ${h.end_form()}
27 ${h.end_form()}
28 </td>
28 </td>
29 </tr>
29 </tr>
30 %endfor
30 %endfor
31 </table>
31 </table>
32 </div>
32 </div>
33 %endif
33 %endif
34 ${h.secure_form(h.url('create_repo_fields', repo_name=c.repo_name),method='put')}
34 ${h.secure_form(h.route_path('edit_repo_fields_create', repo_name=c.repo_name), method='POST', request=request)}
35 <div class="form">
35 <div class="form">
36 <!-- fields -->
36 <!-- fields -->
37 <div class="fields">
37 <div class="fields">
38 <div class="field">
38 <div class="field">
39 <div class="label">
39 <div class="label">
40 <label for="new_field_key">${_('New Field Key')}:</label>
40 <label for="new_field_key">${_('New Field Key')}:</label>
41 </div>
41 </div>
42 <div class="input">
42 <div class="input">
43 ${h.text('new_field_key', class_='medium')}
43 ${h.text('new_field_key', class_='medium')}
44 </div>
44 </div>
45 </div>
45 </div>
46 <div class="field">
46 <div class="field">
47 <div class="label">
47 <div class="label">
48 <label for="new_field_label">${_('New Field Label')}:</label>
48 <label for="new_field_label">${_('New Field Label')}:</label>
49 </div>
49 </div>
50 <div class="input">
50 <div class="input">
51 ${h.text('new_field_label', class_='medium', placeholder=_('Enter short label'))}
51 ${h.text('new_field_label', class_='medium', placeholder=_('Enter short label'))}
52 </div>
52 </div>
53 </div>
53 </div>
54
54
55 <div class="field">
55 <div class="field">
56 <div class="label">
56 <div class="label">
57 <label for="new_field_desc">${_('New Field Description')}:</label>
57 <label for="new_field_desc">${_('New Field Description')}:</label>
58 </div>
58 </div>
59 <div class="input">
59 <div class="input">
60 ${h.text('new_field_desc', class_='medium', placeholder=_('Enter a full description for the field'))}
60 ${h.text('new_field_desc', class_='medium', placeholder=_('Enter a full description for the field'))}
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <div class="buttons">
64 <div class="buttons">
65 ${h.submit('save',_('Add'),class_="btn")}
65 ${h.submit('save',_('Add'),class_="btn")}
66 ${h.reset('reset',_('Reset'),class_="btn")}
66 ${h.reset('reset',_('Reset'),class_="btn")}
67 </div>
67 </div>
68 </div>
68 </div>
69 </div>
69 </div>
70 ${h.end_form()}
70 ${h.end_form()}
71 %else:
71 %else:
72 <h2>
72 <h2>
73 ${_('Extra fields are disabled. You can enable them from the Admin/Settings/Visual page.')}
73 ${_('Extra fields are disabled. You can enable them from the Admin/Settings/Visual page.')}
74 </h2>
74 </h2>
75 %endif
75 %endif
76 </div>
76 </div>
77 </div>
77 </div>
78
78
79
79
@@ -1,109 +1,109 b''
1 <%namespace name="its" file="/base/issue_tracker_settings.mako"/>
1 <%namespace name="its" file="/base/issue_tracker_settings.mako"/>
2
2
3 <div id="repo_issue_tracker" class="${'inherited' if c.settings_model.inherit_global_settings else ''}">
3 <div id="repo_issue_tracker" class="${'inherited' if c.settings_model.inherit_global_settings else ''}">
4 ${h.secure_form(h.url('repo_issuetracker_save', repo_name=c.repo_name), method='post', id="inherit-form")}
4 ${h.secure_form(h.route_path('edit_repo_issuetracker_update', repo_name=c.repo_name), id="inherit-form", method='POST', request=request)}
5 <div class="panel panel-default panel-body">
5 <div class="panel panel-default panel-body">
6 <div class="fields">
6 <div class="fields">
7 <div class="field">
7 <div class="field">
8 <div class="label label-checkbox">
8 <div class="label label-checkbox">
9 <label for="inherit_default_permissions">${_('Inherit from global settings')}:</label>
9 <label for="inherit_default_permissions">${_('Inherit from global settings')}:</label>
10 </div>
10 </div>
11 <div class="checkboxes">
11 <div class="checkboxes">
12 ${h.checkbox('inherit_global_issuetracker', value='inherited', checked=c.settings_model.inherit_global_settings)}
12 ${h.checkbox('inherit_global_issuetracker', value='inherited', checked=c.settings_model.inherit_global_settings)}
13 <span class="help-block">
13 <span class="help-block">
14 ${h.literal(_('Select to inherit global patterns for issue tracker.'))}
14 ${h.literal(_('Select to inherit global patterns for issue tracker.'))}
15 </span>
15 </span>
16 </div>
16 </div>
17 </div>
17 </div>
18 </div>
18 </div>
19 </div>
19 </div>
20
20
21 <div id="inherit_overlay">
21 <div id="inherit_overlay">
22 <div class="panel panel-default">
22 <div class="panel panel-default">
23 <div class="panel-heading">
23 <div class="panel-heading">
24 <h3 class="panel-title">${_('Inherited Issue Tracker Patterns')}</h3>
24 <h3 class="panel-title">${_('Inherited Issue Tracker Patterns')}</h3>
25 </div>
25 </div>
26 <div class="panel-body">
26 <div class="panel-body">
27 <table class="rctable issuetracker readonly">
27 <table class="rctable issuetracker readonly">
28 <tr>
28 <tr>
29 <th>${_('Description')}</th>
29 <th>${_('Description')}</th>
30 <th>${_('Pattern')}</th>
30 <th>${_('Pattern')}</th>
31 <th>${_('Url')}</th>
31 <th>${_('Url')}</th>
32 <th>${_('Prefix')}</th>
32 <th>${_('Prefix')}</th>
33 <th ></th>
33 <th ></th>
34 </tr>
34 </tr>
35 %for uid, entry in c.global_patterns.items():
35 %for uid, entry in c.global_patterns.items():
36 <tr id="${uid}">
36 <tr id="${uid}">
37 <td class="td-description issuetracker_desc">
37 <td class="td-description issuetracker_desc">
38 <span class="entry">
38 <span class="entry">
39 ${entry.desc}
39 ${entry.desc}
40 </span>
40 </span>
41 </td>
41 </td>
42 <td class="td-regex issuetracker_pat">
42 <td class="td-regex issuetracker_pat">
43 <span class="entry">
43 <span class="entry">
44 ${entry.pat}
44 ${entry.pat}
45 </span>
45 </span>
46 </td>
46 </td>
47 <td class="td-url issuetracker_url">
47 <td class="td-url issuetracker_url">
48 <span class="entry">
48 <span class="entry">
49 ${entry.url}
49 ${entry.url}
50 </span>
50 </span>
51 </td>
51 </td>
52 <td class="td-prefix issuetracker_pref">
52 <td class="td-prefix issuetracker_pref">
53 <span class="entry">
53 <span class="entry">
54 ${entry.pref}
54 ${entry.pref}
55 </span>
55 </span>
56 </td>
56 </td>
57 <td class="td-action">
57 <td class="td-action">
58 </td>
58 </td>
59 </tr>
59 </tr>
60 %endfor
60 %endfor
61
61
62 </table>
62 </table>
63 </div>
63 </div>
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 <div id="custom_overlay">
67 <div id="custom_overlay">
68 <div class="panel panel-default">
68 <div class="panel panel-default">
69 <div class="panel-heading">
69 <div class="panel-heading">
70 <h3 class="panel-title">${_('Issue Tracker / Wiki Patterns')}</h3>
70 <h3 class="panel-title">${_('Issue Tracker / Wiki Patterns')}</h3>
71 </div>
71 </div>
72 <div class="panel-body">
72 <div class="panel-body">
73 ${its.issue_tracker_settings_table(
73 ${its.issue_tracker_settings_table(
74 patterns=c.repo_patterns.items(),
74 patterns=c.repo_patterns.items(),
75 form_url=h.url('repo_settings_issuetracker', repo_name=c.repo_info.repo_name),
75 form_url=h.route_path('edit_repo_issuetracker', repo_name=c.repo_info.repo_name),
76 delete_url=h.url('repo_issuetracker_delete', repo_name=c.repo_info.repo_name)
76 delete_url=h.route_path('edit_repo_issuetracker_delete', repo_name=c.repo_info.repo_name)
77 )}
77 )}
78 <div class="buttons">
78 <div class="buttons">
79 <button type="submit" class="btn btn-primary save-inheritance" id="save">${_('Save')}</button>
79 <button type="submit" class="btn btn-primary save-inheritance" id="save">${_('Save')}</button>
80 <button type="reset" class="btn reset-inheritance">${_('Reset')}</button>
80 <button type="reset" class="btn reset-inheritance">${_('Reset')}</button>
81 </div>
81 </div>
82 </div>
82 </div>
83 </div>
83 </div>
84 </div>
84 </div>
85
85
86
86
87 ${h.end_form()}
87 ${h.end_form()}
88
88
89 <div class="panel panel-default">
89 <div class="panel panel-default">
90 <div class="panel-heading">
90 <div class="panel-heading">
91 <h3 class="panel-title">${_('Test Patterns')}</h3>
91 <h3 class="panel-title">${_('Test Patterns')}</h3>
92 </div>
92 </div>
93 <div class="panel-body">
93 <div class="panel-body">
94 ${its.issue_tracker_new_row()}
94 ${its.issue_tracker_new_row()}
95 ${its.issue_tracker_settings_test(test_url=h.url('repo_issuetracker_test', repo_name=c.repo_info.repo_name))}
95 ${its.issue_tracker_settings_test(test_url=h.route_path('edit_repo_issuetracker_test', repo_name=c.repo_info.repo_name))}
96 </div>
96 </div>
97 </div>
97 </div>
98
98
99 </div>
99 </div>
100
100
101 <script>
101 <script>
102 $('#inherit_global_issuetracker').on('change', function(e){
102 $('#inherit_global_issuetracker').on('change', function(e){
103 $('#repo_issue_tracker').toggleClass('inherited',this.checked);
103 $('#repo_issue_tracker').toggleClass('inherited',this.checked);
104 });
104 });
105
105
106 $('.reset-inheritance').on('click', function(e){
106 $('.reset-inheritance').on('click', function(e){
107 $('#inherit_global_issuetracker').prop('checked', false).change();
107 $('#inherit_global_issuetracker').prop('checked', false).change();
108 });
108 });
109 </script>
109 </script>
@@ -1,40 +1,40 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Remote url')}</h3>
3 <h3 class="panel-title">${_('Remote url')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6
6
7 <h4>${_('Manually pull changes from external repository.')}</h4>
7 <h4>${_('Manually pull changes from external repository.')}</h4>
8
8
9 %if c.repo_info.clone_uri:
9 %if c.repo_info.clone_uri:
10
10
11 ${_('Remote mirror url')}:
11 ${_('Remote mirror url')}:
12 <a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri_hidden}</a>
12 <a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri_hidden}</a>
13
13
14 <p>
14 <p>
15 ${_('Pull can be automated by such api call. Can be called periodically in crontab etc.')}
15 ${_('Pull can be automated by such api call. Can be called periodically in crontab etc.')}
16 <br/>
16 <br/>
17 <code>
17 <code>
18 ${h.api_call_example(method='pull', args={"repoid": c.repo_info.repo_name})}
18 ${h.api_call_example(method='pull', args={"repoid": c.repo_info.repo_name})}
19 </code>
19 </code>
20 </p>
20 </p>
21
21
22 ${h.secure_form(h.url('edit_repo_remote', repo_name=c.repo_name), method='put')}
22 ${h.secure_form(h.route_path('edit_repo_remote_pull', repo_name=c.repo_name), method='POST', request=request)}
23 <div class="form">
23 <div class="form">
24 <div class="fields">
24 <div class="fields">
25 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
25 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
26 </div>
26 </div>
27 </div>
27 </div>
28 ${h.end_form()}
28 ${h.end_form()}
29 %else:
29 %else:
30
30
31 ${_('This repository does not have any remote mirror url set.')}
31 ${_('This repository does not have any remote mirror url set.')}
32 <a href="${h.route_path('edit_repo', repo_name=c.repo_info.repo_name)}">${_('Set remote url.')}</a>
32 <a href="${h.route_path('edit_repo', repo_name=c.repo_info.repo_name)}">${_('Set remote url.')}</a>
33 <br/>
33 <br/>
34 <br/>
34 <br/>
35 <button class="btn disabled" type="submit" disabled="disabled">
35 <button class="btn disabled" type="submit" disabled="disabled">
36 ${_('Pull changes from remote location')}
36 ${_('Pull changes from remote location')}
37 </button>
37 </button>
38 %endif
38 %endif
39 </div>
39 </div>
40 </div>
40 </div>
@@ -1,260 +1,260 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <div class="panel panel-default">
4 <div class="panel panel-default">
5 <div class="panel-heading">
5 <div class="panel-heading">
6 <h3 class="panel-title">${_('Settings for Repository: %s') % c.rhodecode_db_repo.repo_name}</h3>
6 <h3 class="panel-title">${_('Settings for Repository: %s') % c.rhodecode_db_repo.repo_name}</h3>
7 </div>
7 </div>
8 <div class="panel-body">
8 <div class="panel-body">
9 ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
9 ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
10 <div class="form">
10 <div class="form">
11 <!-- fields -->
11 <!-- fields -->
12 <div class="fields">
12 <div class="fields">
13
13
14 <div class="field">
14 <div class="field">
15 <div class="label">
15 <div class="label">
16 <label for="repo_name">${_('Name')}:</label>
16 <label for="repo_name">${_('Name')}:</label>
17 </div>
17 </div>
18 <div class="input">
18 <div class="input">
19 ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n}
19 ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n}
20 ${c.form.render_error(request, c.form['repo_name'])|n}
20 ${c.form.render_error(request, c.form['repo_name'])|n}
21
21
22 <p class="help-block">${_('Non-changeable id')}: `_${c.rhodecode_db_repo.repo_id}` <span><a href="#" onclick="$('#clone_id').toggle();return false">${_('what is that ?')}</a></span></p>
22 <p class="help-block">${_('Non-changeable id')}: `_${c.rhodecode_db_repo.repo_id}` <span><a href="#" onclick="$('#clone_id').toggle();return false">${_('what is that ?')}</a></span></p>
23 <p id="clone_id" style="display:none;">
23 <p id="clone_id" style="display:none;">
24 ${_('URL by id')}: `${c.rhodecode_db_repo.clone_url(with_id=True)}` <br/>
24 ${_('URL by id')}: `${c.rhodecode_db_repo.clone_url(with_id=True)}` <br/>
25 ${_('''In case this repository is renamed or moved into another group the repository url changes.
25 ${_('''In case this repository is renamed or moved into another group the repository url changes.
26 Using above url guarantees that this repository will always be accessible under such url.
26 Using above url guarantees that this repository will always be accessible under such url.
27 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
27 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
28 </div>
28 </div>
29 </div>
29 </div>
30
30
31 <div class="field">
31 <div class="field">
32 <div class="label">
32 <div class="label">
33 <label for="repo_group">${_('Repository group')}:</label>
33 <label for="repo_group">${_('Repository group')}:</label>
34 </div>
34 </div>
35 <div class="select">
35 <div class="select">
36 ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n}
36 ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n}
37 ${c.form.render_error(request, c.form['repo_group'])|n}
37 ${c.form.render_error(request, c.form['repo_group'])|n}
38
38
39 % if c.personal_repo_group:
39 % if c.personal_repo_group:
40 <a class="btn" href="#" data-personal-group-name="${c.personal_repo_group.group_name}" data-personal-group-id="${c.personal_repo_group.group_id}" onclick="selectMyGroup(this); return false">
40 <a class="btn" href="#" data-personal-group-name="${c.personal_repo_group.group_name}" data-personal-group-id="${c.personal_repo_group.group_id}" onclick="selectMyGroup(this); return false">
41 ${_('Select my personal group (`%(repo_group_name)s`)') % {'repo_group_name': c.personal_repo_group.group_name}}
41 ${_('Select my personal group (`%(repo_group_name)s`)') % {'repo_group_name': c.personal_repo_group.group_name}}
42 </a>
42 </a>
43 % endif
43 % endif
44 <p class="help-block">${_('Optional select a group to put this repository into.')}</p>
44 <p class="help-block">${_('Optional select a group to put this repository into.')}</p>
45 </div>
45 </div>
46 </div>
46 </div>
47
47
48 % if c.rhodecode_db_repo.repo_type != 'svn':
48 % if c.rhodecode_db_repo.repo_type != 'svn':
49 <div class="field">
49 <div class="field">
50 <div class="label">
50 <div class="label">
51 <label for="clone_uri">${_('Remote uri')}:</label>
51 <label for="clone_uri">${_('Remote uri')}:</label>
52 </div>
52 </div>
53 <div class="input">
53 <div class="input">
54 %if c.rhodecode_db_repo.clone_uri:
54 %if c.rhodecode_db_repo.clone_uri:
55 ## display, if we don't have any errors
55 ## display, if we don't have any errors
56 % if not c.form['repo_clone_uri'].error:
56 % if not c.form['repo_clone_uri'].error:
57 <div id="clone_uri_hidden" class='text-as-placeholder'>
57 <div id="clone_uri_hidden" class='text-as-placeholder'>
58 <span id="clone_uri_hidden_value">${c.rhodecode_db_repo.clone_uri_hidden}</span>
58 <span id="clone_uri_hidden_value">${c.rhodecode_db_repo.clone_uri_hidden}</span>
59 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
59 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
60 </div>
60 </div>
61 % endif
61 % endif
62
62
63 ## alter field
63 ## alter field
64 <div id="alter_clone_uri" style="${'' if c.form['repo_clone_uri'].error else 'display: none'}">
64 <div id="alter_clone_uri" style="${'' if c.form['repo_clone_uri'].error else 'display: none'}">
65 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri', placeholder=_('enter new value, or leave empty to remove'))|n}
65 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri', placeholder=_('enter new value, or leave empty to remove'))|n}
66 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
66 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
67 % if c.form['repo_clone_uri'].error:
67 % if c.form['repo_clone_uri'].error:
68 ## we got error from form subit, means we modify the url
68 ## we got error from form subit, means we modify the url
69 ${h.hidden('repo_clone_uri_change', 'MOD')}
69 ${h.hidden('repo_clone_uri_change', 'MOD')}
70 % else:
70 % else:
71 ${h.hidden('repo_clone_uri_change', 'OLD')}
71 ${h.hidden('repo_clone_uri_change', 'OLD')}
72 % endif
72 % endif
73
73
74 % if not c.form['repo_clone_uri'].error:
74 % if not c.form['repo_clone_uri'].error:
75 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
75 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
76 % endif
76 % endif
77
77
78 </div>
78 </div>
79 %else:
79 %else:
80 ## not set yet, display form to set it
80 ## not set yet, display form to set it
81 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n}
81 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n}
82 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
82 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
83 ${h.hidden('repo_clone_uri_change', 'NEW')}
83 ${h.hidden('repo_clone_uri_change', 'NEW')}
84 %endif
84 %endif
85 <p id="alter_clone_uri_help_block" class="help-block">
85 <p id="alter_clone_uri_help_block" class="help-block">
86 <% pull_link = h.literal(h.link_to('remote sync', h.url('edit_repo_remote', repo_name=c.repo_name))) %>
86 <% pull_link = h.literal(h.link_to('remote sync', h.route_path('edit_repo_remote', repo_name=c.repo_name))) %>
87 ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n} <br/>
87 ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n} <br/>
88 ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')}
88 ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')}
89 </p>
89 </p>
90 </div>
90 </div>
91 </div>
91 </div>
92 % else:
92 % else:
93 ${h.hidden('repo_clone_uri', '')}
93 ${h.hidden('repo_clone_uri', '')}
94 % endif
94 % endif
95
95
96 <div class="field">
96 <div class="field">
97 <div class="label">
97 <div class="label">
98 <label for="repo_landing_commit_ref">${_('Landing commit')}:</label>
98 <label for="repo_landing_commit_ref">${_('Landing commit')}:</label>
99 </div>
99 </div>
100 <div class="select">
100 <div class="select">
101 ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n}
101 ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n}
102 ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n}
102 ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n}
103 <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p>
103 <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p>
104 </div>
104 </div>
105 </div>
105 </div>
106
106
107 <div class="field badged-field">
107 <div class="field badged-field">
108 <div class="label">
108 <div class="label">
109 <label for="repo_owner">${_('Owner')}:</label>
109 <label for="repo_owner">${_('Owner')}:</label>
110 </div>
110 </div>
111 <div class="input">
111 <div class="input">
112 <div class="badge-input-container">
112 <div class="badge-input-container">
113 <div class="user-badge">
113 <div class="user-badge">
114 ${base.gravatar_with_user(c.rhodecode_db_repo.user.email or c.rhodecode_db_repo.user.username, show_disabled=not c.rhodecode_db_repo.user.active)}
114 ${base.gravatar_with_user(c.rhodecode_db_repo.user.email or c.rhodecode_db_repo.user.username, show_disabled=not c.rhodecode_db_repo.user.active)}
115 </div>
115 </div>
116 <div class="badge-input-wrap">
116 <div class="badge-input-wrap">
117 ${c.form['repo_owner'].render(css_class='medium', oid='repo_owner')|n}
117 ${c.form['repo_owner'].render(css_class='medium', oid='repo_owner')|n}
118 </div>
118 </div>
119 </div>
119 </div>
120 ${c.form.render_error(request, c.form['repo_owner'])|n}
120 ${c.form.render_error(request, c.form['repo_owner'])|n}
121 <p class="help-block">${_('Change owner of this repository.')}</p>
121 <p class="help-block">${_('Change owner of this repository.')}</p>
122 </div>
122 </div>
123 </div>
123 </div>
124
124
125 <div class="field">
125 <div class="field">
126 <div class="label label-textarea">
126 <div class="label label-textarea">
127 <label for="repo_description">${_('Description')}:</label>
127 <label for="repo_description">${_('Description')}:</label>
128 </div>
128 </div>
129 <div class="textarea text-area editor">
129 <div class="textarea text-area editor">
130 ${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n}
130 ${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n}
131 ${c.form.render_error(request, c.form['repo_description'])|n}
131 ${c.form.render_error(request, c.form['repo_description'])|n}
132 <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
132 <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
133 </div>
133 </div>
134 </div>
134 </div>
135
135
136 <div class="field">
136 <div class="field">
137 <div class="label label-checkbox">
137 <div class="label label-checkbox">
138 <label for="${c.form['repo_private'].oid}">${_('Private repository')}:</label>
138 <label for="${c.form['repo_private'].oid}">${_('Private repository')}:</label>
139 </div>
139 </div>
140 <div class="checkboxes">
140 <div class="checkboxes">
141 ${c.form['repo_private'].render(css_class='medium')|n}
141 ${c.form['repo_private'].render(css_class='medium')|n}
142 ${c.form.render_error(request, c.form['repo_private'])|n}
142 ${c.form.render_error(request, c.form['repo_private'])|n}
143 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
143 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
144 </div>
144 </div>
145 </div>
145 </div>
146 <div class="field">
146 <div class="field">
147 <div class="label label-checkbox">
147 <div class="label label-checkbox">
148 <label for="${c.form['repo_enable_statistics'].oid}">${_('Enable statistics')}:</label>
148 <label for="${c.form['repo_enable_statistics'].oid}">${_('Enable statistics')}:</label>
149 </div>
149 </div>
150 <div class="checkboxes">
150 <div class="checkboxes">
151 ${c.form['repo_enable_statistics'].render(css_class='medium')|n}
151 ${c.form['repo_enable_statistics'].render(css_class='medium')|n}
152 ${c.form.render_error(request, c.form['repo_enable_statistics'])|n}
152 ${c.form.render_error(request, c.form['repo_enable_statistics'])|n}
153 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
153 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
154 </div>
154 </div>
155 </div>
155 </div>
156 <div class="field">
156 <div class="field">
157 <div class="label label-checkbox">
157 <div class="label label-checkbox">
158 <label for="${c.form['repo_enable_downloads'].oid}">${_('Enable downloads')}:</label>
158 <label for="${c.form['repo_enable_downloads'].oid}">${_('Enable downloads')}:</label>
159 </div>
159 </div>
160 <div class="checkboxes">
160 <div class="checkboxes">
161 ${c.form['repo_enable_downloads'].render(css_class='medium')|n}
161 ${c.form['repo_enable_downloads'].render(css_class='medium')|n}
162 ${c.form.render_error(request, c.form['repo_enable_downloads'])|n}
162 ${c.form.render_error(request, c.form['repo_enable_downloads'])|n}
163 <span class="help-block">${_('Enable download menu on summary page.')}</span>
163 <span class="help-block">${_('Enable download menu on summary page.')}</span>
164 </div>
164 </div>
165 </div>
165 </div>
166 <div class="field">
166 <div class="field">
167 <div class="label label-checkbox">
167 <div class="label label-checkbox">
168 <label for="${c.form['repo_enable_locking'].oid}">${_('Enable automatic locking')}:</label>
168 <label for="${c.form['repo_enable_locking'].oid}">${_('Enable automatic locking')}:</label>
169 </div>
169 </div>
170 <div class="checkboxes">
170 <div class="checkboxes">
171 ${c.form['repo_enable_locking'].render(css_class='medium')|n}
171 ${c.form['repo_enable_locking'].render(css_class='medium')|n}
172 ${c.form.render_error(request, c.form['repo_enable_locking'])|n}
172 ${c.form.render_error(request, c.form['repo_enable_locking'])|n}
173 <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span>
173 <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span>
174 </div>
174 </div>
175 </div>
175 </div>
176
176
177 %if c.visual.repository_fields:
177 %if c.visual.repository_fields:
178 ## EXTRA FIELDS
178 ## EXTRA FIELDS
179 %for field in c.repo_fields:
179 %for field in c.repo_fields:
180 <div class="field">
180 <div class="field">
181 <div class="label">
181 <div class="label">
182 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
182 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
183 </div>
183 </div>
184 <div class="input input-medium">
184 <div class="input input-medium">
185 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
185 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
186 %if field.field_desc:
186 %if field.field_desc:
187 <span class="help-block">${field.field_desc}</span>
187 <span class="help-block">${field.field_desc}</span>
188 %endif
188 %endif
189 </div>
189 </div>
190 </div>
190 </div>
191 %endfor
191 %endfor
192 %endif
192 %endif
193 <div class="buttons">
193 <div class="buttons">
194 ${h.submit('save',_('Save'),class_="btn")}
194 ${h.submit('save',_('Save'),class_="btn")}
195 ${h.reset('reset',_('Reset'),class_="btn")}
195 ${h.reset('reset',_('Reset'),class_="btn")}
196 </div>
196 </div>
197 </div>
197 </div>
198 </div>
198 </div>
199 ${h.end_form()}
199 ${h.end_form()}
200 </div>
200 </div>
201 </div>
201 </div>
202
202
203 <script>
203 <script>
204 $(document).ready(function(){
204 $(document).ready(function(){
205 var cloneUrl = function() {
205 var cloneUrl = function() {
206 var alterButton = $('#alter_clone_uri');
206 var alterButton = $('#alter_clone_uri');
207 var editButton = $('#edit_clone_uri');
207 var editButton = $('#edit_clone_uri');
208 var cancelEditButton = $('#cancel_edit_clone_uri');
208 var cancelEditButton = $('#cancel_edit_clone_uri');
209 var hiddenUrl = $('#clone_uri_hidden');
209 var hiddenUrl = $('#clone_uri_hidden');
210 var hiddenUrlValue = $('#clone_uri_hidden_value');
210 var hiddenUrlValue = $('#clone_uri_hidden_value');
211 var input = $('#clone_uri');
211 var input = $('#clone_uri');
212 var helpBlock = $('#alter_clone_uri_help_block');
212 var helpBlock = $('#alter_clone_uri_help_block');
213 var changedFlag = $('#repo_clone_uri_change');
213 var changedFlag = $('#repo_clone_uri_change');
214 var originalText = helpBlock.html();
214 var originalText = helpBlock.html();
215 var obfuscatedUrl = hiddenUrlValue.html();
215 var obfuscatedUrl = hiddenUrlValue.html();
216
216
217 var edit = function(e) {
217 var edit = function(e) {
218 alterButton.show();
218 alterButton.show();
219 editButton.hide();
219 editButton.hide();
220 hiddenUrl.hide();
220 hiddenUrl.hide();
221
221
222 //add the old value next to input for verification
222 //add the old value next to input for verification
223 helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText);
223 helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText);
224 changedFlag.val('MOD');
224 changedFlag.val('MOD');
225 };
225 };
226
226
227 var cancelEdit = function(e) {
227 var cancelEdit = function(e) {
228 alterButton.hide();
228 alterButton.hide();
229 editButton.show();
229 editButton.show();
230 hiddenUrl.show();
230 hiddenUrl.show();
231
231
232 helpBlock.html(originalText);
232 helpBlock.html(originalText);
233 changedFlag.val('OLD');
233 changedFlag.val('OLD');
234 input.val('');
234 input.val('');
235 };
235 };
236
236
237 var initEvents = function() {
237 var initEvents = function() {
238 editButton.on('click', edit);
238 editButton.on('click', edit);
239 cancelEditButton.on('click', cancelEdit);
239 cancelEditButton.on('click', cancelEdit);
240 };
240 };
241
241
242 var setInitialState = function() {
242 var setInitialState = function() {
243 if (input.hasClass('error')) {
243 if (input.hasClass('error')) {
244 alterButton.show();
244 alterButton.show();
245 editButton.hide();
245 editButton.hide();
246 hiddenUrl.hide();
246 hiddenUrl.hide();
247 }
247 }
248 };
248 };
249
249
250 setInitialState();
250 setInitialState();
251 initEvents();
251 initEvents();
252 }();
252 }();
253
253
254 selectMyGroup = function(element) {
254 selectMyGroup = function(element) {
255 $("#repo_group").val($(element).data('personalGroupId')).trigger("change");
255 $("#repo_group").val($(element).data('personalGroupId')).trigger("change");
256 };
256 };
257
257
258 UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}');
258 UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}');
259 });
259 });
260 </script>
260 </script>
@@ -1,22 +1,22 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Repository statistics')}</h3>
3 <h3 class="panel-title">${_('Repository statistics')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 ${h.secure_form(h.url('edit_repo_statistics', repo_name=c.repo_info.repo_name), method='put')}
6 ${h.secure_form(h.route_path('edit_repo_statistics_reset', repo_name=c.repo_info.repo_name), method='POST', request=request)}
7 <div class="form">
7 <div class="form">
8 <div class="fields">
8 <div class="fields">
9 <div class="field" >
9 <div class="field" >
10 <dl class="dl-horizontal settings">
10 <dl class="dl-horizontal settings">
11 <dt>${_('Processed commits')}:</dt><dd>${c.stats_revision}/${c.repo_last_rev}</dd>
11 <dt>${_('Processed commits')}:</dt><dd>${c.stats_revision}/${c.repo_last_rev}</dd>
12 <dt>${_('Processed progress')}:</dt><dd>${c.stats_percentage}%</dd>
12 <dt>${_('Processed progress')}:</dt><dd>${c.stats_percentage}%</dd>
13 </dl>
13 </dl>
14 </div>
14 </div>
15 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset statistics'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
15 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset statistics'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
16 </div>
16 </div>
17 </div>
17 </div>
18 ${h.end_form()}
18 ${h.end_form()}
19 </div>
19 </div>
20 </div>
20 </div>
21
21
22
22
@@ -1,74 +1,73 b''
1 <%namespace name="vcss" file="/base/vcs_settings.mako"/>
1 <%namespace name="vcss" file="/base/vcs_settings.mako"/>
2
2
3 <div id="repo_vcs_settings" class="${'inherited' if c.inherit_global_settings else ''}">
3 <div id="repo_vcs_settings" class="${'inherited' if c.inherit_global_settings else ''}">
4 ${h.secure_form(h.url('repo_vcs_settings', repo_name=c.repo_info.repo_name), method='post')}
4 ${h.secure_form(h.route_path('edit_repo_vcs_update', repo_name=c.repo_info.repo_name), method='POST', request=request)}
5 <div class="form panel panel-default">
5 <div class="form panel panel-default">
6 <div class="fields panel-body">
6 <div class="fields panel-body">
7 <div class="field">
7 <div class="field">
8 <div class="label label-checkbox">
8 <div class="label label-checkbox">
9 <label for="inherit_global_settings">${_('Inherit from global settings')}:</label>
9 <label for="inherit_global_settings">${_('Inherit from global settings')}:</label>
10 </div>
10 </div>
11 <div class="checkboxes">
11 <div class="checkboxes">
12 ${h.checkbox('inherit_global_settings',value=True)}
12 ${h.checkbox('inherit_global_settings',value=True)}
13 <span class="help-block">${h.literal(_('Select to inherit global vcs settings.'))}</span>
13 <span class="help-block">${h.literal(_('Select to inherit global vcs settings.'))}</span>
14 </div>
14 </div>
15 </div>
15 </div>
16 </div>
16 </div>
17 </div>
17 </div>
18
18
19 <div id="inherit_overlay_vcs_default">
19 <div id="inherit_overlay_vcs_default">
20 <div>
20 <div>
21 ${vcss.vcs_settings_fields(
21 ${vcss.vcs_settings_fields(
22 suffix='_inherited',
22 suffix='_inherited',
23 svn_tag_patterns=c.global_svn_tag_patterns,
23 svn_tag_patterns=c.global_svn_tag_patterns,
24 svn_branch_patterns=c.global_svn_branch_patterns,
24 svn_branch_patterns=c.global_svn_branch_patterns,
25 repo_type=c.repo_info.repo_type,
25 repo_type=c.repo_info.repo_type,
26 disabled='disabled'
26 disabled='disabled'
27 )}
27 )}
28 </div>
28 </div>
29 </div>
29 </div>
30
30
31 <div id="inherit_overlay_vcs_custom">
31 <div id="inherit_overlay_vcs_custom">
32 <div>
32 <div>
33 ${vcss.vcs_settings_fields(
33 ${vcss.vcs_settings_fields(
34 suffix='',
34 suffix='',
35 svn_tag_patterns=c.svn_tag_patterns,
35 svn_tag_patterns=c.svn_tag_patterns,
36 svn_branch_patterns=c.svn_branch_patterns,
36 svn_branch_patterns=c.svn_branch_patterns,
37 repo_type=c.repo_info.repo_type
37 repo_type=c.repo_info.repo_type
38 )}
38 )}
39 </div>
39 </div>
40 </div>
40 </div>
41
41
42 <div class="buttons">
42 <div class="buttons">
43 ${h.submit('save',_('Save settings'),class_="btn")}
43 ${h.submit('save',_('Save settings'),class_="btn")}
44 ${h.reset('reset',_('Reset'),class_="btn")}
44 ${h.reset('reset',_('Reset'),class_="btn")}
45 </div>
45 </div>
46
46
47 ${h.end_form()}
47 ${h.end_form()}
48 </div>
48 </div>
49
49
50 <script type="text/javascript">
50 <script type="text/javascript">
51
51
52 function ajaxDeletePattern(pattern_id, field_id) {
52 function ajaxDeletePattern(pattern_id, field_id) {
53 var sUrl = "${h.url('repo_vcs_settings', repo_name=c.repo_info.repo_name)}";
53 var sUrl = "${h.route_path('edit_repo_vcs_svn_pattern_delete', repo_name=c.repo_info.repo_name)}";
54 var callback = function (o) {
54 var callback = function (o) {
55 var elem = $("#"+field_id);
55 var elem = $("#"+field_id);
56 elem.remove();
56 elem.remove();
57 };
57 };
58 var postData = {
58 var postData = {
59 '_method': 'delete',
60 'delete_svn_pattern': pattern_id,
59 'delete_svn_pattern': pattern_id,
61 'csrf_token': CSRF_TOKEN
60 'csrf_token': CSRF_TOKEN
62 };
61 };
63 var request = $.post(sUrl, postData)
62 var request = $.post(sUrl, postData)
64 .done(callback)
63 .done(callback)
65 .fail(function (data, textStatus, errorThrown) {
64 .fail(function (data, textStatus, errorThrown) {
66 alert("Error while deleting hooks.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(this)[0].url));
65 alert("Error while deleting hooks.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(this)[0].url));
67 });
66 });
68 }
67 }
69
68
70 $('#inherit_global_settings').on('change', function(e){
69 $('#inherit_global_settings').on('change', function(e){
71 $('#repo_vcs_settings').toggleClass('inherited', this.checked);
70 $('#repo_vcs_settings').toggleClass('inherited', this.checked);
72 });
71 });
73
72
74 </script>
73 </script>
@@ -1,101 +1,101 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repositories administration')}
5 ${_('Repositories administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('repositories')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('repositories')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
17 ${self.menu_items(active='admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 <ul class="links">
24 <ul class="links">
25 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
25 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
26 <li>
26 <li>
27 <a href="${h.url('new_repo')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
27 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
28 </li>
28 </li>
29 %endif
29 %endif
30 </ul>
30 </ul>
31 </div>
31 </div>
32 <div id="repos_list_wrap">
32 <div id="repos_list_wrap">
33 <table id="repo_list_table" class="display"></table>
33 <table id="repo_list_table" class="display"></table>
34 </div>
34 </div>
35 </div>
35 </div>
36
36
37 <script>
37 <script>
38 $(document).ready(function() {
38 $(document).ready(function() {
39
39
40 var get_datatable_count = function(){
40 var get_datatable_count = function(){
41 var api = $('#repo_list_table').dataTable().api();
41 var api = $('#repo_list_table').dataTable().api();
42 $('#repo_count').text(api.page.info().recordsDisplay);
42 $('#repo_count').text(api.page.info().recordsDisplay);
43 };
43 };
44
44
45
45
46 // repo list
46 // repo list
47 $('#repo_list_table').DataTable({
47 $('#repo_list_table').DataTable({
48 data: ${c.data|n},
48 data: ${c.data|n},
49 dom: 'rtp',
49 dom: 'rtp',
50 pageLength: ${c.visual.admin_grid_items},
50 pageLength: ${c.visual.admin_grid_items},
51 order: [[ 0, "asc" ]],
51 order: [[ 0, "asc" ]],
52 columns: [
52 columns: [
53 { data: {"_": "name",
53 { data: {"_": "name",
54 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
54 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
55 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
55 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
56 { data: {"_": "desc",
56 { data: {"_": "desc",
57 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
57 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
58 { data: {"_": "last_change",
58 { data: {"_": "last_change",
59 "sort": "last_change_raw",
59 "sort": "last_change_raw",
60 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
60 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
61 { data: {"_": "last_changeset",
61 { data: {"_": "last_changeset",
62 "sort": "last_changeset_raw",
62 "sort": "last_changeset_raw",
63 "type": Number}, title: "${_('Commit')}", className: "td-commit" },
63 "type": Number}, title: "${_('Commit')}", className: "td-commit" },
64 { data: {"_": "owner",
64 { data: {"_": "owner",
65 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
65 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
66 { data: {"_": "state",
66 { data: {"_": "state",
67 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
67 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
68 { data: {"_": "action",
68 { data: {"_": "action",
69 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
69 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
70 ],
70 ],
71 language: {
71 language: {
72 paginate: DEFAULT_GRID_PAGINATION,
72 paginate: DEFAULT_GRID_PAGINATION,
73 emptyTable:_gettext("No repositories available yet.")
73 emptyTable:_gettext("No repositories available yet.")
74 },
74 },
75 "initComplete": function( settings, json ) {
75 "initComplete": function( settings, json ) {
76 get_datatable_count();
76 get_datatable_count();
77 quick_repo_menu();
77 quick_repo_menu();
78 }
78 }
79 });
79 });
80
80
81 // update the counter when doing search
81 // update the counter when doing search
82 $('#repo_list_table').on( 'search.dt', function (e,settings) {
82 $('#repo_list_table').on( 'search.dt', function (e,settings) {
83 get_datatable_count();
83 get_datatable_count();
84 });
84 });
85
85
86 // filter, filter both grids
86 // filter, filter both grids
87 $('#q_filter').on( 'keyup', function () {
87 $('#q_filter').on( 'keyup', function () {
88 var repo_api = $('#repo_list_table').dataTable().api();
88 var repo_api = $('#repo_list_table').dataTable().api();
89 repo_api
89 repo_api
90 .columns(0)
90 .columns(0)
91 .search(this.value)
91 .search(this.value)
92 .draw();
92 .draw();
93 });
93 });
94
94
95 // refilter table if page load via back button
95 // refilter table if page load via back button
96 $("#q_filter").trigger('keyup');
96 $("#q_filter").trigger('keyup');
97 });
97 });
98
98
99 </script>
99 </script>
100
100
101 </%def>
101 </%def>
@@ -1,609 +1,609 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 </div>
19 </div>
20 </div>
20 </div>
21 ${self.menu_bar_subnav()}
21 ${self.menu_bar_subnav()}
22 <!-- END HEADER -->
22 <!-- END HEADER -->
23
23
24 <!-- CONTENT -->
24 <!-- CONTENT -->
25 <div id="content" class="wrapper">
25 <div id="content" class="wrapper">
26
26
27 <rhodecode-toast id="notifications"></rhodecode-toast>
27 <rhodecode-toast id="notifications"></rhodecode-toast>
28
28
29 <div class="main">
29 <div class="main">
30 ${next.main()}
30 ${next.main()}
31 </div>
31 </div>
32 </div>
32 </div>
33 <!-- END CONTENT -->
33 <!-- END CONTENT -->
34
34
35 </div>
35 </div>
36 <!-- FOOTER -->
36 <!-- FOOTER -->
37 <div id="footer">
37 <div id="footer">
38 <div id="footer-inner" class="title wrapper">
38 <div id="footer-inner" class="title wrapper">
39 <div>
39 <div>
40 <p class="footer-link-right">
40 <p class="footer-link-right">
41 % if c.visual.show_version:
41 % if c.visual.show_version:
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 % endif
43 % endif
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 % if c.visual.rhodecode_support_url:
45 % if c.visual.rhodecode_support_url:
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 % endif
47 % endif
48 </p>
48 </p>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 <p class="server-instance" style="display:${sid}">
50 <p class="server-instance" style="display:${sid}">
51 ## display hidden instance ID if specially defined
51 ## display hidden instance ID if specially defined
52 % if c.rhodecode_instanceid:
52 % if c.rhodecode_instanceid:
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 % endif
54 % endif
55 </p>
55 </p>
56 </div>
56 </div>
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <!-- END FOOTER -->
60 <!-- END FOOTER -->
61
61
62 ### MAKO DEFS ###
62 ### MAKO DEFS ###
63
63
64 <%def name="menu_bar_subnav()">
64 <%def name="menu_bar_subnav()">
65 </%def>
65 </%def>
66
66
67 <%def name="breadcrumbs(class_='breadcrumbs')">
67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 <div class="${class_}">
68 <div class="${class_}">
69 ${self.breadcrumbs_links()}
69 ${self.breadcrumbs_links()}
70 </div>
70 </div>
71 </%def>
71 </%def>
72
72
73 <%def name="admin_menu()">
73 <%def name="admin_menu()">
74 <ul class="admin_menu submenu">
74 <ul class="admin_menu submenu">
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
76 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
80 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 </ul>
85 </ul>
86 </%def>
86 </%def>
87
87
88
88
89 <%def name="dt_info_panel(elements)">
89 <%def name="dt_info_panel(elements)">
90 <dl class="dl-horizontal">
90 <dl class="dl-horizontal">
91 %for dt, dd, title, show_items in elements:
91 %for dt, dd, title, show_items in elements:
92 <dt>${dt}:</dt>
92 <dt>${dt}:</dt>
93 <dd title="${h.tooltip(title)}">
93 <dd title="${h.tooltip(title)}">
94 %if callable(dd):
94 %if callable(dd):
95 ## allow lazy evaluation of elements
95 ## allow lazy evaluation of elements
96 ${dd()}
96 ${dd()}
97 %else:
97 %else:
98 ${dd}
98 ${dd}
99 %endif
99 %endif
100 %if show_items:
100 %if show_items:
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 %endif
102 %endif
103 </dd>
103 </dd>
104
104
105 %if show_items:
105 %if show_items:
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 %for item in show_items:
107 %for item in show_items:
108 <dt></dt>
108 <dt></dt>
109 <dd>${item}</dd>
109 <dd>${item}</dd>
110 %endfor
110 %endfor
111 </div>
111 </div>
112 %endif
112 %endif
113
113
114 %endfor
114 %endfor
115 </dl>
115 </dl>
116 </%def>
116 </%def>
117
117
118
118
119 <%def name="gravatar(email, size=16)">
119 <%def name="gravatar(email, size=16)">
120 <%
120 <%
121 if (size > 16):
121 if (size > 16):
122 gravatar_class = 'gravatar gravatar-large'
122 gravatar_class = 'gravatar gravatar-large'
123 else:
123 else:
124 gravatar_class = 'gravatar'
124 gravatar_class = 'gravatar'
125 %>
125 %>
126 <%doc>
126 <%doc>
127 TODO: johbo: For now we serve double size images to make it smooth
127 TODO: johbo: For now we serve double size images to make it smooth
128 for retina. This is how it worked until now. Should be replaced
128 for retina. This is how it worked until now. Should be replaced
129 with a better solution at some point.
129 with a better solution at some point.
130 </%doc>
130 </%doc>
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 </%def>
132 </%def>
133
133
134
134
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 <% email = h.email_or_none(contact) %>
136 <% email = h.email_or_none(contact) %>
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 ${self.gravatar(email, size)}
138 ${self.gravatar(email, size)}
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 </div>
140 </div>
141 </%def>
141 </%def>
142
142
143
143
144 ## admin menu used for people that have some admin resources
144 ## admin menu used for people that have some admin resources
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 <ul class="submenu">
146 <ul class="submenu">
147 %if repositories:
147 %if repositories:
148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
148 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
149 %endif
149 %endif
150 %if repository_groups:
150 %if repository_groups:
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 %endif
152 %endif
153 %if user_groups:
153 %if user_groups:
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 %endif
155 %endif
156 </ul>
156 </ul>
157 </%def>
157 </%def>
158
158
159 <%def name="repo_page_title(repo_instance)">
159 <%def name="repo_page_title(repo_instance)">
160 <div class="title-content">
160 <div class="title-content">
161 <div class="title-main">
161 <div class="title-main">
162 ## SVN/HG/GIT icons
162 ## SVN/HG/GIT icons
163 %if h.is_hg(repo_instance):
163 %if h.is_hg(repo_instance):
164 <i class="icon-hg"></i>
164 <i class="icon-hg"></i>
165 %endif
165 %endif
166 %if h.is_git(repo_instance):
166 %if h.is_git(repo_instance):
167 <i class="icon-git"></i>
167 <i class="icon-git"></i>
168 %endif
168 %endif
169 %if h.is_svn(repo_instance):
169 %if h.is_svn(repo_instance):
170 <i class="icon-svn"></i>
170 <i class="icon-svn"></i>
171 %endif
171 %endif
172
172
173 ## public/private
173 ## public/private
174 %if repo_instance.private:
174 %if repo_instance.private:
175 <i class="icon-repo-private"></i>
175 <i class="icon-repo-private"></i>
176 %else:
176 %else:
177 <i class="icon-repo-public"></i>
177 <i class="icon-repo-public"></i>
178 %endif
178 %endif
179
179
180 ## repo name with group name
180 ## repo name with group name
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182
182
183 </div>
183 </div>
184
184
185 ## FORKED
185 ## FORKED
186 %if repo_instance.fork:
186 %if repo_instance.fork:
187 <p>
187 <p>
188 <i class="icon-code-fork"></i> ${_('Fork of')}
188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 </p>
190 </p>
191 %endif
191 %endif
192
192
193 ## IMPORTED FROM REMOTE
193 ## IMPORTED FROM REMOTE
194 %if repo_instance.clone_uri:
194 %if repo_instance.clone_uri:
195 <p>
195 <p>
196 <i class="icon-code-fork"></i> ${_('Clone from')}
196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 </p>
198 </p>
199 %endif
199 %endif
200
200
201 ## LOCKING STATUS
201 ## LOCKING STATUS
202 %if repo_instance.locked[0]:
202 %if repo_instance.locked[0]:
203 <p class="locking_locked">
203 <p class="locking_locked">
204 <i class="icon-repo-lock"></i>
204 <i class="icon-repo-lock"></i>
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 </p>
206 </p>
207 %elif repo_instance.enable_locking:
207 %elif repo_instance.enable_locking:
208 <p class="locking_unlocked">
208 <p class="locking_unlocked">
209 <i class="icon-repo-unlock"></i>
209 <i class="icon-repo-unlock"></i>
210 ${_('Repository not locked. Pull repository to lock it.')}
210 ${_('Repository not locked. Pull repository to lock it.')}
211 </p>
211 </p>
212 %endif
212 %endif
213
213
214 </div>
214 </div>
215 </%def>
215 </%def>
216
216
217 <%def name="repo_menu(active=None)">
217 <%def name="repo_menu(active=None)">
218 <%
218 <%
219 def is_active(selected):
219 def is_active(selected):
220 if selected == active:
220 if selected == active:
221 return "active"
221 return "active"
222 %>
222 %>
223
223
224 <!--- CONTEXT BAR -->
224 <!--- CONTEXT BAR -->
225 <div id="context-bar">
225 <div id="context-bar">
226 <div class="wrapper">
226 <div class="wrapper">
227 <ul id="context-pages" class="horizontal-list navigation">
227 <ul id="context-pages" class="horizontal-list navigation">
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
231 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
231 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
232 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
232 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
233 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
233 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
234 <li class="${is_active('showpullrequest')}">
234 <li class="${is_active('showpullrequest')}">
235 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
235 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
236 %if c.repository_pull_requests:
236 %if c.repository_pull_requests:
237 <span class="pr_notifications">${c.repository_pull_requests}</span>
237 <span class="pr_notifications">${c.repository_pull_requests}</span>
238 %endif
238 %endif
239 <div class="menulabel">${_('Pull Requests')}</div>
239 <div class="menulabel">${_('Pull Requests')}</div>
240 </a>
240 </a>
241 </li>
241 </li>
242 %endif
242 %endif
243 <li class="${is_active('options')}">
243 <li class="${is_active('options')}">
244 <a class="menulink dropdown">
244 <a class="menulink dropdown">
245 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
245 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
246 </a>
246 </a>
247 <ul class="submenu">
247 <ul class="submenu">
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
249 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 %endif
250 %endif
251 %if c.rhodecode_db_repo.fork:
251 %if c.rhodecode_db_repo.fork:
252 <li>
252 <li>
253 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
253 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
254 href="${h.route_path('repo_compare',
254 href="${h.route_path('repo_compare',
255 repo_name=c.rhodecode_db_repo.fork.repo_name,
255 repo_name=c.rhodecode_db_repo.fork.repo_name,
256 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
256 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
257 source_ref=c.rhodecode_db_repo.landing_rev[1],
257 source_ref=c.rhodecode_db_repo.landing_rev[1],
258 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
258 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
259 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
259 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
260 _query=dict(merge=1))}"
260 _query=dict(merge=1))}"
261 >
261 >
262 ${_('Compare fork')}
262 ${_('Compare fork')}
263 </a>
263 </a>
264 </li>
264 </li>
265 %endif
265 %endif
266
266
267 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
267 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
268
268
269 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
269 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
270 %if c.rhodecode_db_repo.locked[0]:
270 %if c.rhodecode_db_repo.locked[0]:
271 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
271 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
272 %else:
272 %else:
273 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
273 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
274 %endif
274 %endif
275 %endif
275 %endif
276 %if c.rhodecode_user.username != h.DEFAULT_USER:
276 %if c.rhodecode_user.username != h.DEFAULT_USER:
277 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
277 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
278 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
278 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
279 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
279 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
280 %endif
280 %endif
281 %endif
281 %endif
282 </ul>
282 </ul>
283 </li>
283 </li>
284 </ul>
284 </ul>
285 </div>
285 </div>
286 <div class="clear"></div>
286 <div class="clear"></div>
287 </div>
287 </div>
288 <!--- END CONTEXT BAR -->
288 <!--- END CONTEXT BAR -->
289
289
290 </%def>
290 </%def>
291
291
292 <%def name="usermenu(active=False)">
292 <%def name="usermenu(active=False)">
293 ## USER MENU
293 ## USER MENU
294 <li id="quick_login_li" class="${'active' if active else ''}">
294 <li id="quick_login_li" class="${'active' if active else ''}">
295 <a id="quick_login_link" class="menulink childs">
295 <a id="quick_login_link" class="menulink childs">
296 ${gravatar(c.rhodecode_user.email, 20)}
296 ${gravatar(c.rhodecode_user.email, 20)}
297 <span class="user">
297 <span class="user">
298 %if c.rhodecode_user.username != h.DEFAULT_USER:
298 %if c.rhodecode_user.username != h.DEFAULT_USER:
299 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
299 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
300 %else:
300 %else:
301 <span>${_('Sign in')}</span>
301 <span>${_('Sign in')}</span>
302 %endif
302 %endif
303 </span>
303 </span>
304 </a>
304 </a>
305
305
306 <div class="user-menu submenu">
306 <div class="user-menu submenu">
307 <div id="quick_login">
307 <div id="quick_login">
308 %if c.rhodecode_user.username == h.DEFAULT_USER:
308 %if c.rhodecode_user.username == h.DEFAULT_USER:
309 <h4>${_('Sign in to your account')}</h4>
309 <h4>${_('Sign in to your account')}</h4>
310 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
310 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
311 <div class="form form-vertical">
311 <div class="form form-vertical">
312 <div class="fields">
312 <div class="fields">
313 <div class="field">
313 <div class="field">
314 <div class="label">
314 <div class="label">
315 <label for="username">${_('Username')}:</label>
315 <label for="username">${_('Username')}:</label>
316 </div>
316 </div>
317 <div class="input">
317 <div class="input">
318 ${h.text('username',class_='focus',tabindex=1)}
318 ${h.text('username',class_='focus',tabindex=1)}
319 </div>
319 </div>
320
320
321 </div>
321 </div>
322 <div class="field">
322 <div class="field">
323 <div class="label">
323 <div class="label">
324 <label for="password">${_('Password')}:</label>
324 <label for="password">${_('Password')}:</label>
325 %if h.HasPermissionAny('hg.password_reset.enabled')():
325 %if h.HasPermissionAny('hg.password_reset.enabled')():
326 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
326 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
327 %endif
327 %endif
328 </div>
328 </div>
329 <div class="input">
329 <div class="input">
330 ${h.password('password',class_='focus',tabindex=2)}
330 ${h.password('password',class_='focus',tabindex=2)}
331 </div>
331 </div>
332 </div>
332 </div>
333 <div class="buttons">
333 <div class="buttons">
334 <div class="register">
334 <div class="register">
335 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
335 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
336 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
336 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
337 %endif
337 %endif
338 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
338 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
339 </div>
339 </div>
340 <div class="submit">
340 <div class="submit">
341 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
341 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
342 </div>
342 </div>
343 </div>
343 </div>
344 </div>
344 </div>
345 </div>
345 </div>
346 ${h.end_form()}
346 ${h.end_form()}
347 %else:
347 %else:
348 <div class="">
348 <div class="">
349 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
349 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
350 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
350 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
351 <div class="email">${c.rhodecode_user.email}</div>
351 <div class="email">${c.rhodecode_user.email}</div>
352 </div>
352 </div>
353 <div class="">
353 <div class="">
354 <ol class="links">
354 <ol class="links">
355 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
355 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
356 % if c.rhodecode_user.personal_repo_group:
356 % if c.rhodecode_user.personal_repo_group:
357 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
357 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
358 % endif
358 % endif
359 <li class="logout">
359 <li class="logout">
360 ${h.secure_form(h.route_path('logout'), request=request)}
360 ${h.secure_form(h.route_path('logout'), request=request)}
361 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
361 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
362 ${h.end_form()}
362 ${h.end_form()}
363 </li>
363 </li>
364 </ol>
364 </ol>
365 </div>
365 </div>
366 %endif
366 %endif
367 </div>
367 </div>
368 </div>
368 </div>
369 %if c.rhodecode_user.username != h.DEFAULT_USER:
369 %if c.rhodecode_user.username != h.DEFAULT_USER:
370 <div class="pill_container">
370 <div class="pill_container">
371 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
371 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
372 </div>
372 </div>
373 % endif
373 % endif
374 </li>
374 </li>
375 </%def>
375 </%def>
376
376
377 <%def name="menu_items(active=None)">
377 <%def name="menu_items(active=None)">
378 <%
378 <%
379 def is_active(selected):
379 def is_active(selected):
380 if selected == active:
380 if selected == active:
381 return "active"
381 return "active"
382 return ""
382 return ""
383 %>
383 %>
384 <ul id="quick" class="main_nav navigation horizontal-list">
384 <ul id="quick" class="main_nav navigation horizontal-list">
385 <!-- repo switcher -->
385 <!-- repo switcher -->
386 <li class="${is_active('repositories')} repo_switcher_li has_select2">
386 <li class="${is_active('repositories')} repo_switcher_li has_select2">
387 <input id="repo_switcher" name="repo_switcher" type="hidden">
387 <input id="repo_switcher" name="repo_switcher" type="hidden">
388 </li>
388 </li>
389
389
390 ## ROOT MENU
390 ## ROOT MENU
391 %if c.rhodecode_user.username != h.DEFAULT_USER:
391 %if c.rhodecode_user.username != h.DEFAULT_USER:
392 <li class="${is_active('journal')}">
392 <li class="${is_active('journal')}">
393 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
393 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
394 <div class="menulabel">${_('Journal')}</div>
394 <div class="menulabel">${_('Journal')}</div>
395 </a>
395 </a>
396 </li>
396 </li>
397 %else:
397 %else:
398 <li class="${is_active('journal')}">
398 <li class="${is_active('journal')}">
399 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
399 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
400 <div class="menulabel">${_('Public journal')}</div>
400 <div class="menulabel">${_('Public journal')}</div>
401 </a>
401 </a>
402 </li>
402 </li>
403 %endif
403 %endif
404 <li class="${is_active('gists')}">
404 <li class="${is_active('gists')}">
405 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
405 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
406 <div class="menulabel">${_('Gists')}</div>
406 <div class="menulabel">${_('Gists')}</div>
407 </a>
407 </a>
408 </li>
408 </li>
409 <li class="${is_active('search')}">
409 <li class="${is_active('search')}">
410 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
410 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
411 <div class="menulabel">${_('Search')}</div>
411 <div class="menulabel">${_('Search')}</div>
412 </a>
412 </a>
413 </li>
413 </li>
414 % if h.HasPermissionAll('hg.admin')('access admin main page'):
414 % if h.HasPermissionAll('hg.admin')('access admin main page'):
415 <li class="${is_active('admin')}">
415 <li class="${is_active('admin')}">
416 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
416 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
417 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
417 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
418 </a>
418 </a>
419 ${admin_menu()}
419 ${admin_menu()}
420 </li>
420 </li>
421 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
421 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
422 <li class="${is_active('admin')}">
422 <li class="${is_active('admin')}">
423 <a class="menulink childs" title="${_('Delegated Admin settings')}">
423 <a class="menulink childs" title="${_('Delegated Admin settings')}">
424 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
424 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
425 </a>
425 </a>
426 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
426 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
427 c.rhodecode_user.repository_groups_admin,
427 c.rhodecode_user.repository_groups_admin,
428 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
428 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
429 </li>
429 </li>
430 % endif
430 % endif
431 % if c.debug_style:
431 % if c.debug_style:
432 <li class="${is_active('debug_style')}">
432 <li class="${is_active('debug_style')}">
433 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
433 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
434 <div class="menulabel">${_('Style')}</div>
434 <div class="menulabel">${_('Style')}</div>
435 </a>
435 </a>
436 </li>
436 </li>
437 % endif
437 % endif
438 ## render extra user menu
438 ## render extra user menu
439 ${usermenu(active=(active=='my_account'))}
439 ${usermenu(active=(active=='my_account'))}
440 </ul>
440 </ul>
441
441
442 <script type="text/javascript">
442 <script type="text/javascript">
443 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
443 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
444
444
445 /*format the look of items in the list*/
445 /*format the look of items in the list*/
446 var format = function(state, escapeMarkup){
446 var format = function(state, escapeMarkup){
447 if (!state.id){
447 if (!state.id){
448 return state.text; // optgroup
448 return state.text; // optgroup
449 }
449 }
450 var obj_dict = state.obj;
450 var obj_dict = state.obj;
451 var tmpl = '';
451 var tmpl = '';
452
452
453 if(obj_dict && state.type == 'repo'){
453 if(obj_dict && state.type == 'repo'){
454 if(obj_dict['repo_type'] === 'hg'){
454 if(obj_dict['repo_type'] === 'hg'){
455 tmpl += '<i class="icon-hg"></i> ';
455 tmpl += '<i class="icon-hg"></i> ';
456 }
456 }
457 else if(obj_dict['repo_type'] === 'git'){
457 else if(obj_dict['repo_type'] === 'git'){
458 tmpl += '<i class="icon-git"></i> ';
458 tmpl += '<i class="icon-git"></i> ';
459 }
459 }
460 else if(obj_dict['repo_type'] === 'svn'){
460 else if(obj_dict['repo_type'] === 'svn'){
461 tmpl += '<i class="icon-svn"></i> ';
461 tmpl += '<i class="icon-svn"></i> ';
462 }
462 }
463 if(obj_dict['private']){
463 if(obj_dict['private']){
464 tmpl += '<i class="icon-lock" ></i> ';
464 tmpl += '<i class="icon-lock" ></i> ';
465 }
465 }
466 else if(visual_show_public_icon){
466 else if(visual_show_public_icon){
467 tmpl += '<i class="icon-unlock-alt"></i> ';
467 tmpl += '<i class="icon-unlock-alt"></i> ';
468 }
468 }
469 }
469 }
470 if(obj_dict && state.type == 'commit') {
470 if(obj_dict && state.type == 'commit') {
471 tmpl += '<i class="icon-tag"></i>';
471 tmpl += '<i class="icon-tag"></i>';
472 }
472 }
473 if(obj_dict && state.type == 'group'){
473 if(obj_dict && state.type == 'group'){
474 tmpl += '<i class="icon-folder-close"></i> ';
474 tmpl += '<i class="icon-folder-close"></i> ';
475 }
475 }
476 tmpl += escapeMarkup(state.text);
476 tmpl += escapeMarkup(state.text);
477 return tmpl;
477 return tmpl;
478 };
478 };
479
479
480 var formatResult = function(result, container, query, escapeMarkup) {
480 var formatResult = function(result, container, query, escapeMarkup) {
481 return format(result, escapeMarkup);
481 return format(result, escapeMarkup);
482 };
482 };
483
483
484 var formatSelection = function(data, container, escapeMarkup) {
484 var formatSelection = function(data, container, escapeMarkup) {
485 return format(data, escapeMarkup);
485 return format(data, escapeMarkup);
486 };
486 };
487
487
488 $("#repo_switcher").select2({
488 $("#repo_switcher").select2({
489 cachedDataSource: {},
489 cachedDataSource: {},
490 minimumInputLength: 2,
490 minimumInputLength: 2,
491 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
491 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
492 dropdownAutoWidth: true,
492 dropdownAutoWidth: true,
493 formatResult: formatResult,
493 formatResult: formatResult,
494 formatSelection: formatSelection,
494 formatSelection: formatSelection,
495 containerCssClass: "repo-switcher",
495 containerCssClass: "repo-switcher",
496 dropdownCssClass: "repo-switcher-dropdown",
496 dropdownCssClass: "repo-switcher-dropdown",
497 escapeMarkup: function(m){
497 escapeMarkup: function(m){
498 // don't escape our custom placeholder
498 // don't escape our custom placeholder
499 if(m.substr(0,23) == '<div class="menulabel">'){
499 if(m.substr(0,23) == '<div class="menulabel">'){
500 return m;
500 return m;
501 }
501 }
502
502
503 return Select2.util.escapeMarkup(m);
503 return Select2.util.escapeMarkup(m);
504 },
504 },
505 query: $.debounce(250, function(query){
505 query: $.debounce(250, function(query){
506 self = this;
506 self = this;
507 var cacheKey = query.term;
507 var cacheKey = query.term;
508 var cachedData = self.cachedDataSource[cacheKey];
508 var cachedData = self.cachedDataSource[cacheKey];
509
509
510 if (cachedData) {
510 if (cachedData) {
511 query.callback({results: cachedData.results});
511 query.callback({results: cachedData.results});
512 } else {
512 } else {
513 $.ajax({
513 $.ajax({
514 url: pyroutes.url('goto_switcher_data'),
514 url: pyroutes.url('goto_switcher_data'),
515 data: {'query': query.term},
515 data: {'query': query.term},
516 dataType: 'json',
516 dataType: 'json',
517 type: 'GET',
517 type: 'GET',
518 success: function(data) {
518 success: function(data) {
519 self.cachedDataSource[cacheKey] = data;
519 self.cachedDataSource[cacheKey] = data;
520 query.callback({results: data.results});
520 query.callback({results: data.results});
521 },
521 },
522 error: function(data, textStatus, errorThrown) {
522 error: function(data, textStatus, errorThrown) {
523 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
523 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
524 }
524 }
525 })
525 })
526 }
526 }
527 })
527 })
528 });
528 });
529
529
530 $("#repo_switcher").on('select2-selecting', function(e){
530 $("#repo_switcher").on('select2-selecting', function(e){
531 e.preventDefault();
531 e.preventDefault();
532 window.location = e.choice.url;
532 window.location = e.choice.url;
533 });
533 });
534
534
535 </script>
535 </script>
536 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
536 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
537 </%def>
537 </%def>
538
538
539 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
539 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
540 <div class="modal-dialog">
540 <div class="modal-dialog">
541 <div class="modal-content">
541 <div class="modal-content">
542 <div class="modal-header">
542 <div class="modal-header">
543 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
543 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
544 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
544 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
545 </div>
545 </div>
546 <div class="modal-body">
546 <div class="modal-body">
547 <div class="block-left">
547 <div class="block-left">
548 <table class="keyboard-mappings">
548 <table class="keyboard-mappings">
549 <tbody>
549 <tbody>
550 <tr>
550 <tr>
551 <th></th>
551 <th></th>
552 <th>${_('Site-wide shortcuts')}</th>
552 <th>${_('Site-wide shortcuts')}</th>
553 </tr>
553 </tr>
554 <%
554 <%
555 elems = [
555 elems = [
556 ('/', 'Open quick search box'),
556 ('/', 'Open quick search box'),
557 ('g h', 'Goto home page'),
557 ('g h', 'Goto home page'),
558 ('g g', 'Goto my private gists page'),
558 ('g g', 'Goto my private gists page'),
559 ('g G', 'Goto my public gists page'),
559 ('g G', 'Goto my public gists page'),
560 ('n r', 'New repository page'),
560 ('n r', 'New repository page'),
561 ('n g', 'New gist page'),
561 ('n g', 'New gist page'),
562 ]
562 ]
563 %>
563 %>
564 %for key, desc in elems:
564 %for key, desc in elems:
565 <tr>
565 <tr>
566 <td class="keys">
566 <td class="keys">
567 <span class="key tag">${key}</span>
567 <span class="key tag">${key}</span>
568 </td>
568 </td>
569 <td>${desc}</td>
569 <td>${desc}</td>
570 </tr>
570 </tr>
571 %endfor
571 %endfor
572 </tbody>
572 </tbody>
573 </table>
573 </table>
574 </div>
574 </div>
575 <div class="block-left">
575 <div class="block-left">
576 <table class="keyboard-mappings">
576 <table class="keyboard-mappings">
577 <tbody>
577 <tbody>
578 <tr>
578 <tr>
579 <th></th>
579 <th></th>
580 <th>${_('Repositories')}</th>
580 <th>${_('Repositories')}</th>
581 </tr>
581 </tr>
582 <%
582 <%
583 elems = [
583 elems = [
584 ('g s', 'Goto summary page'),
584 ('g s', 'Goto summary page'),
585 ('g c', 'Goto changelog page'),
585 ('g c', 'Goto changelog page'),
586 ('g f', 'Goto files page'),
586 ('g f', 'Goto files page'),
587 ('g F', 'Goto files page with file search activated'),
587 ('g F', 'Goto files page with file search activated'),
588 ('g p', 'Goto pull requests page'),
588 ('g p', 'Goto pull requests page'),
589 ('g o', 'Goto repository settings'),
589 ('g o', 'Goto repository settings'),
590 ('g O', 'Goto repository permissions settings'),
590 ('g O', 'Goto repository permissions settings'),
591 ]
591 ]
592 %>
592 %>
593 %for key, desc in elems:
593 %for key, desc in elems:
594 <tr>
594 <tr>
595 <td class="keys">
595 <td class="keys">
596 <span class="key tag">${key}</span>
596 <span class="key tag">${key}</span>
597 </td>
597 </td>
598 <td>${desc}</td>
598 <td>${desc}</td>
599 </tr>
599 </tr>
600 %endfor
600 %endfor
601 </tbody>
601 </tbody>
602 </table>
602 </table>
603 </div>
603 </div>
604 </div>
604 </div>
605 <div class="modal-footer">
605 <div class="modal-footer">
606 </div>
606 </div>
607 </div><!-- /.modal-content -->
607 </div><!-- /.modal-content -->
608 </div><!-- /.modal-dialog -->
608 </div><!-- /.modal-dialog -->
609 </div><!-- /.modal -->
609 </div><!-- /.modal -->
@@ -1,218 +1,218 b''
1 ## snippet for displaying issue tracker settings
1 ## snippet for displaying issue tracker settings
2 ## usage:
2 ## usage:
3 ## <%namespace name="its" file="/base/issue_tracker_settings.mako"/>
3 ## <%namespace name="its" file="/base/issue_tracker_settings.mako"/>
4 ## ${its.issue_tracker_settings_table(patterns, form_url, delete_url)}
4 ## ${its.issue_tracker_settings_table(patterns, form_url, delete_url)}
5 ## ${its.issue_tracker_settings_test(test_url)}
5 ## ${its.issue_tracker_settings_test(test_url)}
6
6
7 <%def name="issue_tracker_settings_table(patterns, form_url, delete_url)">
7 <%def name="issue_tracker_settings_table(patterns, form_url, delete_url)">
8 <table class="rctable issuetracker">
8 <table class="rctable issuetracker">
9 <tr>
9 <tr>
10 <th>${_('Description')}</th>
10 <th>${_('Description')}</th>
11 <th>${_('Pattern')}</th>
11 <th>${_('Pattern')}</th>
12 <th>${_('Url')}</th>
12 <th>${_('Url')}</th>
13 <th>${_('Prefix')}</th>
13 <th>${_('Prefix')}</th>
14 <th ></th>
14 <th ></th>
15 </tr>
15 </tr>
16 <tr>
16 <tr>
17 <td class="td-description issue-tracker-example">Example</td>
17 <td class="td-description issue-tracker-example">Example</td>
18 <td class="td-regex issue-tracker-example">${'(?:#)(?P<issue_id>\d+)'}</td>
18 <td class="td-regex issue-tracker-example">${'(?:#)(?P<issue_id>\d+)'}</td>
19 <td class="td-url issue-tracker-example">${'https://myissueserver.com/${repo}/issue/${issue_id}'}</td>
19 <td class="td-url issue-tracker-example">${'https://myissueserver.com/${repo}/issue/${issue_id}'}</td>
20 <td class="td-prefix issue-tracker-example">#</td>
20 <td class="td-prefix issue-tracker-example">#</td>
21 <td class="issue-tracker-example"><a href="${h.route_url('enterprise_issue_tracker_settings')}" target="_blank">${_('Read more')}</a></td>
21 <td class="issue-tracker-example"><a href="${h.route_url('enterprise_issue_tracker_settings')}" target="_blank">${_('Read more')}</a></td>
22 </tr>
22 </tr>
23 %for uid, entry in patterns:
23 %for uid, entry in patterns:
24 <tr id="entry_${uid}">
24 <tr id="entry_${uid}">
25 <td class="td-description issuetracker_desc">
25 <td class="td-description issuetracker_desc">
26 <span class="entry">
26 <span class="entry">
27 ${entry.desc}
27 ${entry.desc}
28 </span>
28 </span>
29 <span class="edit">
29 <span class="edit">
30 ${h.text('new_pattern_description_'+uid, class_='medium-inline', value=entry.desc or '')}
30 ${h.text('new_pattern_description_'+uid, class_='medium-inline', value=entry.desc or '')}
31 </span>
31 </span>
32 </td>
32 </td>
33 <td class="td-regex issuetracker_pat">
33 <td class="td-regex issuetracker_pat">
34 <span class="entry">
34 <span class="entry">
35 ${entry.pat}
35 ${entry.pat}
36 </span>
36 </span>
37 <span class="edit">
37 <span class="edit">
38 ${h.text('new_pattern_pattern_'+uid, class_='medium-inline', value=entry.pat or '')}
38 ${h.text('new_pattern_pattern_'+uid, class_='medium-inline', value=entry.pat or '')}
39 </span>
39 </span>
40 </td>
40 </td>
41 <td class="td-url issuetracker_url">
41 <td class="td-url issuetracker_url">
42 <span class="entry">
42 <span class="entry">
43 ${entry.url}
43 ${entry.url}
44 </span>
44 </span>
45 <span class="edit">
45 <span class="edit">
46 ${h.text('new_pattern_url_'+uid, class_='medium-inline', value=entry.url or '')}
46 ${h.text('new_pattern_url_'+uid, class_='medium-inline', value=entry.url or '')}
47 </span>
47 </span>
48 </td>
48 </td>
49 <td class="td-prefix issuetracker_pref">
49 <td class="td-prefix issuetracker_pref">
50 <span class="entry">
50 <span class="entry">
51 ${entry.pref}
51 ${entry.pref}
52 </span>
52 </span>
53 <span class="edit">
53 <span class="edit">
54 ${h.text('new_pattern_prefix_'+uid, class_='medium-inline', value=entry.pref or '')}
54 ${h.text('new_pattern_prefix_'+uid, class_='medium-inline', value=entry.pref or '')}
55 </span>
55 </span>
56 </td>
56 </td>
57 <td class="td-action">
57 <td class="td-action">
58 <div class="grid_edit">
58 <div class="grid_edit">
59 <span class="entry">
59 <span class="entry">
60 <a class="edit_issuetracker_entry" href="">${_('Edit')}</a>
60 <a class="edit_issuetracker_entry" href="">${_('Edit')}</a>
61 </span>
61 </span>
62 <span class="edit">
62 <span class="edit">
63 ${h.hidden('uid', uid)}
63 ${h.hidden('uid', uid)}
64 </span>
64 </span>
65 </div>
65 </div>
66 <div class="grid_delete">
66 <div class="grid_delete">
67 <span class="entry">
67 <span class="entry">
68 <a class="btn btn-link btn-danger delete_issuetracker_entry" data-desc="${entry.desc}" data-uid="${uid}">
68 <a class="btn btn-link btn-danger delete_issuetracker_entry" data-desc="${entry.desc}" data-uid="${uid}">
69 ${_('Delete')}
69 ${_('Delete')}
70 </a>
70 </a>
71 </span>
71 </span>
72 <span class="edit">
72 <span class="edit">
73 <a class="btn btn-link btn-danger edit_issuetracker_cancel" data-uid="${uid}">${_('Cancel')}</a>
73 <a class="btn btn-link btn-danger edit_issuetracker_cancel" data-uid="${uid}">${_('Cancel')}</a>
74 </span>
74 </span>
75 </div>
75 </div>
76 </td>
76 </td>
77 </tr>
77 </tr>
78 %endfor
78 %endfor
79 <tr id="last-row"></tr>
79 <tr id="last-row"></tr>
80 </table>
80 </table>
81 <p>
81 <p>
82 <a id="add_pattern" class="link">
82 <a id="add_pattern" class="link">
83 ${_('Add new')}
83 ${_('Add new')}
84 </a>
84 </a>
85 </p>
85 </p>
86
86
87 <script type="text/javascript">
87 <script type="text/javascript">
88 var newEntryLabel = $('label[for="new_entry"]');
88 var newEntryLabel = $('label[for="new_entry"]');
89
89
90 var resetEntry = function() {
90 var resetEntry = function() {
91 newEntryLabel.text("${_('New Entry')}:");
91 newEntryLabel.text("${_('New Entry')}:");
92 };
92 };
93
93
94 var delete_pattern = function(entry) {
94 var delete_pattern = function(entry) {
95 if (confirm("${_('Confirm to remove this pattern:')} "+$(entry).data('desc'))) {
95 if (confirm("${_('Confirm to remove this pattern:')} "+$(entry).data('desc'))) {
96 var request = $.ajax({
96 var request = $.ajax({
97 type: "POST",
97 type: "POST",
98 url: "${delete_url}",
98 url: "${delete_url}",
99 data: {
99 data: {
100 '_method': 'delete',
100 '_method': 'delete',
101 'csrf_token': CSRF_TOKEN,
101 'csrf_token': CSRF_TOKEN,
102 'uid':$(entry).data('uid')
102 'uid':$(entry).data('uid')
103 },
103 },
104 success: function(){
104 success: function(){
105 location.reload();
105 location.reload();
106 },
106 },
107 error: function(data, textStatus, errorThrown){
107 error: function(data, textStatus, errorThrown){
108 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
108 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
109 }
109 }
110 });
110 });
111 };
111 }
112 }
112 };
113
113
114 $('.delete_issuetracker_entry').on('click', function(e){
114 $('.delete_issuetracker_entry').on('click', function(e){
115 e.preventDefault();
115 e.preventDefault();
116 delete_pattern(this);
116 delete_pattern(this);
117 });
117 });
118
118
119 $('.edit_issuetracker_entry').on('click', function(e){
119 $('.edit_issuetracker_entry').on('click', function(e){
120 e.preventDefault();
120 e.preventDefault();
121 $(this).parents('tr').addClass('editopen');
121 $(this).parents('tr').addClass('editopen');
122 });
122 });
123
123
124 $('.edit_issuetracker_cancel').on('click', function(e){
124 $('.edit_issuetracker_cancel').on('click', function(e){
125 e.preventDefault();
125 e.preventDefault();
126 $(this).parents('tr').removeClass('editopen');
126 $(this).parents('tr').removeClass('editopen');
127 // Reset to original value
127 // Reset to original value
128 var uid = $(this).data('uid');
128 var uid = $(this).data('uid');
129 $('#'+uid+' input').each(function(e) {
129 $('#'+uid+' input').each(function(e) {
130 this.value = this.defaultValue;
130 this.value = this.defaultValue;
131 });
131 });
132 });
132 });
133
133
134 $('input#reset').on('click', function(e) {
134 $('input#reset').on('click', function(e) {
135 resetEntry();
135 resetEntry();
136 });
136 });
137
137
138 $('#add_pattern').on('click', function(e) {
138 $('#add_pattern').on('click', function(e) {
139 addNewPatternInput();
139 addNewPatternInput();
140 });
140 });
141 </script>
141 </script>
142 </%def>
142 </%def>
143
143
144 <%def name="issue_tracker_new_row()">
144 <%def name="issue_tracker_new_row()">
145 <table id="add-row-tmpl" style="display: none;">
145 <table id="add-row-tmpl" style="display: none;">
146 <tbody>
146 <tbody>
147 <tr class="new_pattern">
147 <tr class="new_pattern">
148 <td class="td-description issuetracker_desc">
148 <td class="td-description issuetracker_desc">
149 <span class="entry">
149 <span class="entry">
150 <input class="medium-inline" id="description_##UUID##" name="new_pattern_description_##UUID##" value="##DESCRIPTION##" type="text">
150 <input class="medium-inline" id="description_##UUID##" name="new_pattern_description_##UUID##" value="##DESCRIPTION##" type="text">
151 </span>
151 </span>
152 </td>
152 </td>
153 <td class="td-regex issuetracker_pat">
153 <td class="td-regex issuetracker_pat">
154 <span class="entry">
154 <span class="entry">
155 <input class="medium-inline" id="pattern_##UUID##" name="new_pattern_pattern_##UUID##" placeholder="Pattern"
155 <input class="medium-inline" id="pattern_##UUID##" name="new_pattern_pattern_##UUID##" placeholder="Pattern"
156 value="##PATTERN##" type="text">
156 value="##PATTERN##" type="text">
157 </span>
157 </span>
158 </td>
158 </td>
159 <td class="td-url issuetracker_url">
159 <td class="td-url issuetracker_url">
160 <span class="entry">
160 <span class="entry">
161 <input class="medium-inline" id="url_##UUID##" name="new_pattern_url_##UUID##" placeholder="Url" value="##URL##" type="text">
161 <input class="medium-inline" id="url_##UUID##" name="new_pattern_url_##UUID##" placeholder="Url" value="##URL##" type="text">
162 </span>
162 </span>
163 </td>
163 </td>
164 <td class="td-prefix issuetracker_pref">
164 <td class="td-prefix issuetracker_pref">
165 <span class="entry">
165 <span class="entry">
166 <input class="medium-inline" id="prefix_##UUID##" name="new_pattern_prefix_##UUID##" placeholder="Prefix" value="##PREFIX##" type="text">
166 <input class="medium-inline" id="prefix_##UUID##" name="new_pattern_prefix_##UUID##" placeholder="Prefix" value="##PREFIX##" type="text">
167 </span>
167 </span>
168 </td>
168 </td>
169 <td class="td-action">
169 <td class="td-action">
170 </td>
170 </td>
171 <input id="uid_##UUID##" name="uid_##UUID##" type="hidden" value="">
171 <input id="uid_##UUID##" name="uid_##UUID##" type="hidden" value="">
172 </tr>
172 </tr>
173 </tbody>
173 </tbody>
174 </table>
174 </table>
175 </%def>
175 </%def>
176
176
177 <%def name="issue_tracker_settings_test(test_url)">
177 <%def name="issue_tracker_settings_test(test_url)">
178 <div class="form-vertical">
178 <div class="form-vertical">
179 <div class="fields">
179 <div class="fields">
180 <div class="field">
180 <div class="field">
181 <div class='textarea-full'>
181 <div class='textarea-full'>
182 <textarea id="test_pattern_data" >
182 <textarea id="test_pattern_data" >
183 This commit fixes ticket #451.
183 This commit fixes ticket #451.
184 This is an example text for testing issue tracker patterns, add a pattern here and
184 This is an example text for testing issue tracker patterns, add a pattern here and
185 hit preview to see the link
185 hit preview to see the link
186 </textarea>
186 </textarea>
187 </div>
187 </div>
188 </div>
188 </div>
189 </div>
189 </div>
190 <div class="test_pattern_preview">
190 <div class="test_pattern_preview">
191 <div id="test_pattern" class="btn btn-small" >${_('Preview')}</div>
191 <div id="test_pattern" class="btn btn-small" >${_('Preview')}</div>
192 <p>${_('Test Pattern Preview')}</p>
192 <p>${_('Test Pattern Preview')}</p>
193 <div id="test_pattern_result"></div>
193 <div id="test_pattern_result"></div>
194 </div>
194 </div>
195 </div>
195 </div>
196
196
197 <script type="text/javascript">
197 <script type="text/javascript">
198 $('#test_pattern').on('click', function(e) {
198 $('#test_pattern').on('click', function(e) {
199 $.ajax({
199 $.ajax({
200 type: "POST",
200 type: "POST",
201 url: "${test_url}",
201 url: "${test_url}",
202 data: {
202 data: {
203 'test_text': $('#test_pattern_data').val(),
203 'test_text': $('#test_pattern_data').val(),
204 'csrf_token': CSRF_TOKEN
204 'csrf_token': CSRF_TOKEN
205 },
205 },
206 success: function(data){
206 success: function(data){
207 $('#test_pattern_result').html(data);
207 $('#test_pattern_result').html(data);
208 },
208 },
209 error: function(jqXHR, textStatus, errorThrown){
209 error: function(jqXHR, textStatus, errorThrown){
210 $('#test_pattern_result').html('Error: ' + errorThrown);
210 $('#test_pattern_result').html('Error: ' + errorThrown);
211 }
211 }
212 });
212 });
213 $('#test_pattern_result').show();
213 $('#test_pattern_result').show();
214 });
214 });
215 </script>
215 </script>
216 </%def>
216 </%def>
217
217
218
218
@@ -1,104 +1,106 b''
1 <%namespace name="sourceblock" file="/codeblocks/source.mako"/>
1 <%namespace name="sourceblock" file="/codeblocks/source.mako"/>
2
2
3 <div id="codeblock" class="codeblock">
3 <div id="codeblock" class="codeblock">
4 <div class="codeblock-header">
4 <div class="codeblock-header">
5 <div class="stats">
5 <div class="stats">
6 <span>
6 <span>
7 <strong>
7 <strong>
8 <i class="icon-file-text"></i>
8 <i class="icon-file-text"></i>
9 ${c.file}
9 ${c.file}
10 </strong>
10 </strong>
11 </span>
11 </span>
12 % if c.lf_node:
12 % if c.lf_node:
13 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
13 <span title="${_('This file is a pointer to large binary file')}"> | ${_('LargeFile')} ${h.format_byte_size_binary(c.lf_node.size)} </span>
14 % endif
14 % endif
15 <span> | ${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}</span>
15 <span> | ${c.file.lines()[0]} ${_ungettext('line', 'lines', c.file.lines()[0])}</span>
16 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
16 <span> | ${h.format_byte_size_binary(c.file.size)}</span>
17 <span> | ${c.file.mimetype} </span>
17 <span> | ${c.file.mimetype} </span>
18 <span> | ${h.get_lexer_for_filenode(c.file).__class__.__name__}</span>
18 <span> | ${h.get_lexer_for_filenode(c.file).__class__.__name__}</span>
19 <span class="item last"> | <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.file.path}" title="${_('Copy the full path')}"></i></span>
19 <span class="item last"> | <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.file.path}" title="${_('Copy the full path')}"></i></span>
20 </div>
20 </div>
21 <div class="buttons">
21 <div class="buttons">
22 <a id="file_history_overview" href="#">
22 <a id="file_history_overview" href="#">
23 ${_('History')}
23 ${_('History')}
24 </a>
24 </a>
25 <a id="file_history_overview_full" style="display: none" href="${h.route_path('repo_changelog_file',repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}">
25 <a id="file_history_overview_full" style="display: none" href="${h.route_path('repo_changelog_file',repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path)}">
26 ${_('Show Full History')}
26 ${_('Show Full History')}
27 </a> |
27 </a> |
28 %if c.annotate:
28 %if c.annotate:
29 ${h.link_to(_('Source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
29 ${h.link_to(_('Source'), h.route_path('repo_files', repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
30 %else:
30 %else:
31 ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
31 ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
32 %endif
32 %endif
33 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
33 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
34 |
34 |
35 % if c.lf_node:
35 % if c.lf_node:
36 <a href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query=dict(lf=1))}">
36 <a href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path, _query=dict(lf=1))}">
37 ${_('Download largefile')}
37 ${_('Download largefile')}
38 </a>
38 </a>
39 % else:
39 % else:
40 <a href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
40 <a href="${h.route_path('repo_file_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
41 ${_('Download')}
41 ${_('Download')}
42 </a>
42 </a>
43 % endif
43 % endif
44
44
45 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
45 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
46 |
46 |
47 %if c.on_branch_head and c.branch_or_raw_id and not c.file.is_binary:
47 %if c.on_branch_head and c.branch_or_raw_id and not c.file.is_binary:
48 <a href="${h.route_path('repo_files_edit_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">
48 <a href="${h.route_path('repo_files_edit_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">
49 ${_('Edit on Branch:{}').format(c.branch_name)}
49 ${_('Edit on Branch:{}').format(c.branch_name)}
50 </a>
50 </a>
51 | <a class="btn-danger btn-link" href="${h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">${_('Delete')}
51 | <a class="btn-danger btn-link" href="${h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit')}">${_('Delete')}
52 </a>
52 </a>
53 %elif c.on_branch_head and c.branch_or_raw_id and c.file.is_binary:
53 %elif c.on_branch_head and c.branch_or_raw_id and c.file.is_binary:
54 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing binary files not allowed'))}
54 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing binary files not allowed'))}
55 | ${h.link_to(_('Delete'), h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit'),class_="btn-danger btn-link")}
55 | ${h.link_to(_('Delete'), h.route_path('repo_files_remove_file',repo_name=c.repo_name,commit_id=c.branch_or_raw_id,f_path=c.f_path, _anchor='edit'),class_="btn-danger btn-link")}
56 %else:
56 %else:
57 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
57 ${h.link_to(_('Edit'), '#', class_="btn btn-link disabled tooltip", title=_('Editing files allowed only when on branch head commit'))}
58 | ${h.link_to(_('Delete'), '#', class_="btn btn-danger btn-link disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
58 | ${h.link_to(_('Delete'), '#', class_="btn btn-danger btn-link disabled tooltip", title=_('Deleting files allowed only when on branch head commit'))}
59 %endif
59 %endif
60 %endif
60 %endif
61 </div>
61 </div>
62 </div>
62 </div>
63 <div id="file_history_container"></div>
63 <div id="file_history_container"></div>
64 <div class="code-body">
64 <div class="code-body">
65 %if c.file.is_binary:
65 %if c.file.is_binary:
66 <% rendered_binary = h.render_binary(c.repo_name, c.file)%>
66 <% rendered_binary = h.render_binary(c.repo_name, c.file)%>
67 % if rendered_binary:
67 % if rendered_binary:
68 ${rendered_binary}
68 ${rendered_binary}
69 % else:
69 % else:
70 <div>
70 <div>
71 ${_('Binary file (%s)') % c.file.mimetype}
71 ${_('Binary file (%s)') % c.file.mimetype}
72 </div>
72 </div>
73 % endif
73 % endif
74 %else:
74 %else:
75 % if c.file.size < c.visual.cut_off_limit_file:
75 % if c.file.size < c.visual.cut_off_limit_file:
76 %if c.renderer and not c.annotate:
76 %if c.renderer and not c.annotate:
77 ## pick relative url based on renderer
78 ${c.renderer} XXXX
77 <%
79 <%
78 relative_urls = {
80 relative_urls = {
79 'raw': h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
81 'raw': h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
80 'standard': h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
82 'standard': h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path),
81 }
83 }
82 %>
84 %>
83 ${h.render(c.file.content, renderer=c.renderer, relative_urls=relative_urls)}
85 ${h.render(c.file.content, renderer=c.renderer, relative_urls=relative_urls)}
84 %else:
86 %else:
85 <table class="cb codehilite">
87 <table class="cb codehilite">
86 %if c.annotate:
88 %if c.annotate:
87 <% color_hasher = h.color_hasher() %>
89 <% color_hasher = h.color_hasher() %>
88 %for annotation, lines in c.annotated_lines:
90 %for annotation, lines in c.annotated_lines:
89 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
91 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
90 %endfor
92 %endfor
91 %else:
93 %else:
92 %for line_num, tokens in enumerate(c.lines, 1):
94 %for line_num, tokens in enumerate(c.lines, 1):
93 ${sourceblock.render_line(line_num, tokens)}
95 ${sourceblock.render_line(line_num, tokens)}
94 %endfor
96 %endfor
95 %endif
97 %endif
96 </table>
98 </table>
97 %endif
99 %endif
98 %else:
100 %else:
99 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
101 ${_('File size {} is bigger then allowed limit {}. ').format(h.format_byte_size_binary(c.file.size), h.format_byte_size_binary(c.visual.cut_off_limit_file))} ${h.link_to(_('Show as raw'),
100 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
102 h.route_path('repo_file_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path))}
101 %endif
103 %endif
102 %endif
104 %endif
103 </div>
105 </div>
104 </div> No newline at end of file
106 </div>
@@ -1,173 +1,173 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="main()">
3 <%def name="main()">
4 <div class="box">
4 <div class="box">
5 <!-- box / title -->
5 <!-- box / title -->
6 <div class="title">
6 <div class="title">
7 <div class="block-left breadcrumbs">
7 <div class="block-left breadcrumbs">
8 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
8 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
9 ${self.breadcrumbs()}
9 ${self.breadcrumbs()}
10 <span id="match_container" style="display:none">&raquo; <span id="match_count">0</span> ${_('matches')}</span>
10 <span id="match_container" style="display:none">&raquo; <span id="match_count">0</span> ${_('matches')}</span>
11 </div>
11 </div>
12 %if c.rhodecode_user.username != h.DEFAULT_USER:
12 %if c.rhodecode_user.username != h.DEFAULT_USER:
13 <div class="block-right">
13 <div class="block-right">
14 <%
14 <%
15 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
15 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
16 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
16 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
17 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
17 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
18 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
18 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
19
19
20 gr_name = c.repo_group.group_name if c.repo_group else None
20 gr_name = c.repo_group.group_name if c.repo_group else None
21 # create repositories with write permission on group is set to true
21 # create repositories with write permission on group is set to true
22 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
22 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
23 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
23 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
24 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
24 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
25 %>
25 %>
26
26
27 %if not c.repo_group:
27 %if not c.repo_group:
28 ## no repository group context here
28 ## no repository group context here
29 %if is_admin or create_repo:
29 %if is_admin or create_repo:
30 <a href="${h.url('new_repo')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
30 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
31 %endif
31 %endif
32
32
33 %if is_admin or create_repo_group:
33 %if is_admin or create_repo_group:
34 <a href="${h.url('new_repo_group')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
34 <a href="${h.url('new_repo_group')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
35 %endif
35 %endif
36 %else:
36 %else:
37 ##we're inside other repository group other terms apply
37 ##we're inside other repository group other terms apply
38 %if is_admin or group_admin or (group_write and create_on_write):
38 %if is_admin or group_admin or (group_write and create_on_write):
39 <a href="${h.url('new_repo',parent_group=c.repo_group.group_id)}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
39 <a href="${h.route_path('repo_new',parent_group=c.repo_group.group_id)}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
40 %endif
40 %endif
41 %if is_admin or group_admin:
41 %if is_admin or group_admin:
42 <a href="${h.url('new_repo_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
42 <a href="${h.url('new_repo_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
43 %endif
43 %endif
44 %if is_admin or group_admin:
44 %if is_admin or group_admin:
45 <a href="${h.url('edit_repo_group',group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
45 <a href="${h.url('edit_repo_group',group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
46 %endif
46 %endif
47 %endif
47 %endif
48 </div>
48 </div>
49 %endif
49 %endif
50 </div>
50 </div>
51 <!-- end box / title -->
51 <!-- end box / title -->
52 <div class="table">
52 <div class="table">
53 <div id="groups_list_wrap">
53 <div id="groups_list_wrap">
54 <table id="group_list_table" class="display"></table>
54 <table id="group_list_table" class="display"></table>
55 </div>
55 </div>
56 </div>
56 </div>
57
57
58 <div class="table">
58 <div class="table">
59 <div id="repos_list_wrap">
59 <div id="repos_list_wrap">
60 <table id="repo_list_table" class="display"></table>
60 <table id="repo_list_table" class="display"></table>
61 </div>
61 </div>
62 </div>
62 </div>
63 </div>
63 </div>
64 <script>
64 <script>
65 $(document).ready(function() {
65 $(document).ready(function() {
66
66
67 var get_datatable_count = function() {
67 var get_datatable_count = function() {
68 var api = $('#repo_list_table').dataTable().api();
68 var api = $('#repo_list_table').dataTable().api();
69 var pageInfo = api.page.info();
69 var pageInfo = api.page.info();
70 var repos = pageInfo.recordsDisplay;
70 var repos = pageInfo.recordsDisplay;
71 var reposTotal = pageInfo.recordsTotal;
71 var reposTotal = pageInfo.recordsTotal;
72
72
73 api = $('#group_list_table').dataTable().api();
73 api = $('#group_list_table').dataTable().api();
74 pageInfo = api.page.info();
74 pageInfo = api.page.info();
75 var repoGroups = pageInfo.recordsDisplay;
75 var repoGroups = pageInfo.recordsDisplay;
76 var repoGroupsTotal = pageInfo.recordsTotal;
76 var repoGroupsTotal = pageInfo.recordsTotal;
77
77
78 if (repoGroups !== repoGroupsTotal) {
78 if (repoGroups !== repoGroupsTotal) {
79 $('#match_count').text(repos+repoGroups);
79 $('#match_count').text(repos+repoGroups);
80 }
80 }
81 if (repos !== reposTotal) {
81 if (repos !== reposTotal) {
82 $('#match_container').show();
82 $('#match_container').show();
83 }
83 }
84 if ($('#q_filter').val() === '') {
84 if ($('#q_filter').val() === '') {
85 $('#match_container').hide();
85 $('#match_container').hide();
86 }
86 }
87 };
87 };
88
88
89 // repo group list
89 // repo group list
90 $('#group_list_table').DataTable({
90 $('#group_list_table').DataTable({
91 data: ${c.repo_groups_data|n},
91 data: ${c.repo_groups_data|n},
92 dom: 'rtp',
92 dom: 'rtp',
93 pageLength: ${c.visual.dashboard_items},
93 pageLength: ${c.visual.dashboard_items},
94 order: [[ 0, "asc" ]],
94 order: [[ 0, "asc" ]],
95 columns: [
95 columns: [
96 { data: {"_": "name",
96 { data: {"_": "name",
97 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
97 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
98 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
98 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
99 { data: {"_": "desc",
99 { data: {"_": "desc",
100 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
100 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
101 { data: {"_": "last_change",
101 { data: {"_": "last_change",
102 "sort": "last_change_raw",
102 "sort": "last_change_raw",
103 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
103 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
104 { data: {"_": "owner",
104 { data: {"_": "owner",
105 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
105 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
106 ],
106 ],
107 language: {
107 language: {
108 paginate: DEFAULT_GRID_PAGINATION,
108 paginate: DEFAULT_GRID_PAGINATION,
109 emptyTable: _gettext("No repository groups available yet.")
109 emptyTable: _gettext("No repository groups available yet.")
110 },
110 },
111 "drawCallback": function( settings, json ) {
111 "drawCallback": function( settings, json ) {
112 timeagoActivate();
112 timeagoActivate();
113 quick_repo_menu();
113 quick_repo_menu();
114 }
114 }
115 });
115 });
116
116
117 // repo list
117 // repo list
118 $('#repo_list_table').DataTable({
118 $('#repo_list_table').DataTable({
119 data: ${c.repos_data|n},
119 data: ${c.repos_data|n},
120 dom: 'rtp',
120 dom: 'rtp',
121 order: [[ 0, "asc" ]],
121 order: [[ 0, "asc" ]],
122 pageLength: ${c.visual.dashboard_items},
122 pageLength: ${c.visual.dashboard_items},
123 columns: [
123 columns: [
124 { data: {"_": "name",
124 { data: {"_": "name",
125 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
125 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
126 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
126 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
127 { data: {"_": "desc",
127 { data: {"_": "desc",
128 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
128 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
129 { data: {"_": "last_change",
129 { data: {"_": "last_change",
130 "sort": "last_change_raw",
130 "sort": "last_change_raw",
131 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
131 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
132 { data: {"_": "last_changeset",
132 { data: {"_": "last_changeset",
133 "sort": "last_changeset_raw",
133 "sort": "last_changeset_raw",
134 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
134 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
135 { data: {"_": "owner",
135 { data: {"_": "owner",
136 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
136 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
137 ],
137 ],
138 language: {
138 language: {
139 paginate: DEFAULT_GRID_PAGINATION,
139 paginate: DEFAULT_GRID_PAGINATION,
140 emptyTable: _gettext("No repositories available yet.")
140 emptyTable: _gettext("No repositories available yet.")
141 },
141 },
142 "drawCallback": function( settings, json ) {
142 "drawCallback": function( settings, json ) {
143 timeagoActivate();
143 timeagoActivate();
144 quick_repo_menu();
144 quick_repo_menu();
145 }
145 }
146 });
146 });
147
147
148 // update the counter when doing search
148 // update the counter when doing search
149 $('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) {
149 $('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) {
150 get_datatable_count();
150 get_datatable_count();
151 });
151 });
152
152
153 // filter, filter both grids
153 // filter, filter both grids
154 $('#q_filter').on( 'keyup', function () {
154 $('#q_filter').on( 'keyup', function () {
155 var repo_api = $('#repo_list_table').dataTable().api();
155 var repo_api = $('#repo_list_table').dataTable().api();
156 repo_api
156 repo_api
157 .columns( 0 )
157 .columns( 0 )
158 .search( this.value )
158 .search( this.value )
159 .draw();
159 .draw();
160
160
161 var repo_group_api = $('#group_list_table').dataTable().api();
161 var repo_group_api = $('#group_list_table').dataTable().api();
162 repo_group_api
162 repo_group_api
163 .columns( 0 )
163 .columns( 0 )
164 .search( this.value )
164 .search( this.value )
165 .draw();
165 .draw();
166 });
166 });
167
167
168 // refilter table if page load via back button
168 // refilter table if page load via back button
169 $("#q_filter").trigger('keyup');
169 $("#q_filter").trigger('keyup');
170
170
171 });
171 });
172 </script>
172 </script>
173 </%def>
173 </%def>
@@ -1,32 +1,32 b''
1 <%inherit file="/summary/base.mako"/>
1 <%inherit file="/summary/base.mako"/>
2
2
3 <%namespace name="components" file="/summary/components.mako"/>
3 <%namespace name="components" file="/summary/components.mako"/>
4
4
5 <%def name="main()">
5 <%def name="main()">
6 <div class="title">
6 <div class="title">
7 ${self.repo_page_title(c.rhodecode_db_repo)}
7 ${self.repo_page_title(c.rhodecode_db_repo)}
8 </div>
8 </div>
9
9
10 <div id="repo-summary" class="summary">
10 <div id="repo-summary" class="summary">
11 ${components.summary_detail(breadcrumbs_links=self.breadcrumbs_links(), show_downloads=False)}
11 ${components.summary_detail(breadcrumbs_links=self.breadcrumbs_links(), show_downloads=False)}
12 ${components.summary_stats(gravatar_function=self.gravatar_with_user)}
12 ${components.summary_stats(gravatar_function=self.gravatar_with_user)}
13 </div><!--end repo-summary-->
13 </div><!--end repo-summary-->
14
14
15 <div class="alert alert-dismissable alert-warning">
15 <div class="alert alert-dismissable alert-warning">
16 <strong>Missing requirements</strong>
16 <strong>Missing requirements</strong>
17 Commits cannot be displayed, because this repository uses one or more extensions, which was not enabled. <br/>
17 Commits cannot be displayed, because this repository uses one or more extensions, which was not enabled. <br/>
18 Please <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">enable extension in settings</a>, or contact the repository owner for help.
18 Please <a href="${h.route_path('edit_repo_vcs', repo_name=c.repo_name)}">enable extension in settings</a>, or contact the repository owner for help.
19 Missing extensions could be:
19 Missing extensions could be:
20 <pre>
20 <pre>
21
21
22 - Mercurial largefiles
22 - Mercurial largefiles
23 - Git LFS
23 - Git LFS
24 </pre>
24 </pre>
25 </div>
25 </div>
26
26
27 </%def>
27 </%def>
28
28
29
29
30 <%def name="menu_bar_subnav()">
30 <%def name="menu_bar_subnav()">
31 ${self.repo_menu(active='summary')}
31 ${self.repo_menu(active='summary')}
32 </%def>
32 </%def>
@@ -1,151 +1,150 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests import (
23 from rhodecode.tests import TestController, url
24 TestController, url, assert_session_flash, link_to)
25 from rhodecode.model.db import User, UserGroup
26 from rhodecode.model.meta import Session
27 from rhodecode.tests.fixture import Fixture
24 from rhodecode.tests.fixture import Fixture
28
25
29
26
30 def route_path(name, params=None, **kwargs):
27 def route_path(name, params=None, **kwargs):
31 import urllib
28 import urllib
32 from rhodecode.apps._base import ADMIN_PREFIX
29 from rhodecode.apps._base import ADMIN_PREFIX
33
30
34 base_url = {
31 base_url = {
35 'home': '/',
32 'home': '/',
33 'repos':
34 ADMIN_PREFIX + '/repos',
36 'user_groups':
35 'user_groups':
37 ADMIN_PREFIX + '/user_groups',
36 ADMIN_PREFIX + '/user_groups',
38 'user_groups_data':
37 'user_groups_data':
39 ADMIN_PREFIX + '/user_groups_data',
38 ADMIN_PREFIX + '/user_groups_data',
40 }[name].format(**kwargs)
39 }[name].format(**kwargs)
41
40
42 if params:
41 if params:
43 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
42 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
44 return base_url
43 return base_url
45
44
46
45
47 fixture = Fixture()
46 fixture = Fixture()
48
47
49
48
50 class TestAdminDelegatedUser(TestController):
49 class TestAdminDelegatedUser(TestController):
51
50
52 def test_regular_user_cannot_see_admin_interfaces(
51 def test_regular_user_cannot_see_admin_interfaces(
53 self, user_util, xhr_header):
52 self, user_util, xhr_header):
54 user = user_util.create_user(password='qweqwe')
53 user = user_util.create_user(password='qweqwe')
55 self.log_user(user.username, 'qweqwe')
54 self.log_user(user.username, 'qweqwe')
56
55
57 # check if in home view, such user doesn't see the "admin" menus
56 # check if in home view, such user doesn't see the "admin" menus
58 response = self.app.get(route_path('home'))
57 response = self.app.get(route_path('home'))
59
58
60 assert_response = response.assert_response()
59 assert_response = response.assert_response()
61
60
62 assert_response.no_element_exists('li.local-admin-repos')
61 assert_response.no_element_exists('li.local-admin-repos')
63 assert_response.no_element_exists('li.local-admin-repo-groups')
62 assert_response.no_element_exists('li.local-admin-repo-groups')
64 assert_response.no_element_exists('li.local-admin-user-groups')
63 assert_response.no_element_exists('li.local-admin-user-groups')
65
64
66 response = self.app.get(url('repos'), status=200)
65 response = self.app.get(route_path('repos'), status=200)
67 response.mustcontain('data: []')
66 response.mustcontain('data: []')
68
67
69 response = self.app.get(url('repo_groups'), status=200)
68 response = self.app.get(url('repo_groups'), status=200)
70 response.mustcontain('data: []')
69 response.mustcontain('data: []')
71
70
72 response = self.app.get(route_path('user_groups_data'),
71 response = self.app.get(route_path('user_groups_data'),
73 status=200, extra_environ=xhr_header)
72 status=200, extra_environ=xhr_header)
74 assert response.json['data'] == []
73 assert response.json['data'] == []
75
74
76 def test_regular_user_can_see_admin_interfaces_if_owner(
75 def test_regular_user_can_see_admin_interfaces_if_owner(
77 self, user_util, xhr_header):
76 self, user_util, xhr_header):
78 user = user_util.create_user(password='qweqwe')
77 user = user_util.create_user(password='qweqwe')
79 username = user.username
78 username = user.username
80
79
81 repo = user_util.create_repo(owner=username)
80 repo = user_util.create_repo(owner=username)
82 repo_name = repo.repo_name
81 repo_name = repo.repo_name
83
82
84 repo_group = user_util.create_repo_group(owner=username)
83 repo_group = user_util.create_repo_group(owner=username)
85 repo_group_name = repo_group.group_name
84 repo_group_name = repo_group.group_name
86
85
87 user_group = user_util.create_user_group(owner=username)
86 user_group = user_util.create_user_group(owner=username)
88 user_group_name = user_group.users_group_name
87 user_group_name = user_group.users_group_name
89
88
90 self.log_user(username, 'qweqwe')
89 self.log_user(username, 'qweqwe')
91 # check if in home view, such user doesn't see the "admin" menus
90 # check if in home view, such user doesn't see the "admin" menus
92 response = self.app.get(route_path('home'))
91 response = self.app.get(route_path('home'))
93
92
94 assert_response = response.assert_response()
93 assert_response = response.assert_response()
95
94
96 assert_response.one_element_exists('li.local-admin-repos')
95 assert_response.one_element_exists('li.local-admin-repos')
97 assert_response.one_element_exists('li.local-admin-repo-groups')
96 assert_response.one_element_exists('li.local-admin-repo-groups')
98 assert_response.one_element_exists('li.local-admin-user-groups')
97 assert_response.one_element_exists('li.local-admin-user-groups')
99
98
100 # admin interfaces have visible elements
99 # admin interfaces have visible elements
101 response = self.app.get(url('repos'), status=200)
100 response = self.app.get(route_path('repos'), status=200)
102 response.mustcontain('"name_raw": "{}"'.format(repo_name))
101 response.mustcontain('"name_raw": "{}"'.format(repo_name))
103
102
104 response = self.app.get(url('repo_groups'), status=200)
103 response = self.app.get(url('repo_groups'), status=200)
105 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
104 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
106
105
107 response = self.app.get(route_path('user_groups_data'),
106 response = self.app.get(route_path('user_groups_data'),
108 extra_environ=xhr_header, status=200)
107 extra_environ=xhr_header, status=200)
109 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
108 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
110
109
111 def test_regular_user_can_see_admin_interfaces_if_admin_perm(
110 def test_regular_user_can_see_admin_interfaces_if_admin_perm(
112 self, user_util, xhr_header):
111 self, user_util, xhr_header):
113 user = user_util.create_user(password='qweqwe')
112 user = user_util.create_user(password='qweqwe')
114 username = user.username
113 username = user.username
115
114
116 repo = user_util.create_repo()
115 repo = user_util.create_repo()
117 repo_name = repo.repo_name
116 repo_name = repo.repo_name
118
117
119 repo_group = user_util.create_repo_group()
118 repo_group = user_util.create_repo_group()
120 repo_group_name = repo_group.group_name
119 repo_group_name = repo_group.group_name
121
120
122 user_group = user_util.create_user_group()
121 user_group = user_util.create_user_group()
123 user_group_name = user_group.users_group_name
122 user_group_name = user_group.users_group_name
124
123
125 user_util.grant_user_permission_to_repo(
124 user_util.grant_user_permission_to_repo(
126 repo, user, 'repository.admin')
125 repo, user, 'repository.admin')
127 user_util.grant_user_permission_to_repo_group(
126 user_util.grant_user_permission_to_repo_group(
128 repo_group, user, 'group.admin')
127 repo_group, user, 'group.admin')
129 user_util.grant_user_permission_to_user_group(
128 user_util.grant_user_permission_to_user_group(
130 user_group, user, 'usergroup.admin')
129 user_group, user, 'usergroup.admin')
131
130
132 self.log_user(username, 'qweqwe')
131 self.log_user(username, 'qweqwe')
133 # check if in home view, such user doesn't see the "admin" menus
132 # check if in home view, such user doesn't see the "admin" menus
134 response = self.app.get(route_path('home'))
133 response = self.app.get(route_path('home'))
135
134
136 assert_response = response.assert_response()
135 assert_response = response.assert_response()
137
136
138 assert_response.one_element_exists('li.local-admin-repos')
137 assert_response.one_element_exists('li.local-admin-repos')
139 assert_response.one_element_exists('li.local-admin-repo-groups')
138 assert_response.one_element_exists('li.local-admin-repo-groups')
140 assert_response.one_element_exists('li.local-admin-user-groups')
139 assert_response.one_element_exists('li.local-admin-user-groups')
141
140
142 # admin interfaces have visible elements
141 # admin interfaces have visible elements
143 response = self.app.get(url('repos'), status=200)
142 response = self.app.get(route_path('repos'), status=200)
144 response.mustcontain('"name_raw": "{}"'.format(repo_name))
143 response.mustcontain('"name_raw": "{}"'.format(repo_name))
145
144
146 response = self.app.get(url('repo_groups'), status=200)
145 response = self.app.get(url('repo_groups'), status=200)
147 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
146 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
148
147
149 response = self.app.get(route_path('user_groups_data'),
148 response = self.app.get(route_path('user_groups_data'),
150 extra_environ=xhr_header, status=200)
149 extra_environ=xhr_header, status=200)
151 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
150 response.mustcontain('"name_raw": "{}"'.format(user_group_name))
@@ -1,255 +1,94 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from mock import Mock, patch
22 from mock import Mock, patch
23
23
24 from rhodecode.lib import base
24 from rhodecode.lib import base
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 from rhodecode.lib import helpers as h
27 from rhodecode.model import db
25 from rhodecode.model import db
28
26
29
27
30 @pytest.mark.parametrize('result_key, expected_value', [
28 @pytest.mark.parametrize('result_key, expected_value', [
31 ('username', 'stub_username'),
29 ('username', 'stub_username'),
32 ('action', 'stub_action'),
30 ('action', 'stub_action'),
33 ('repository', 'stub_repo_name'),
31 ('repository', 'stub_repo_name'),
34 ('scm', 'stub_scm'),
32 ('scm', 'stub_scm'),
35 ('hooks', ['stub_hook']),
33 ('hooks', ['stub_hook']),
36 ('config', 'stub_ini_filename'),
34 ('config', 'stub_ini_filename'),
37 ('ip', '1.2.3.4'),
35 ('ip', '1.2.3.4'),
38 ('server_url', 'https://example.com'),
36 ('server_url', 'https://example.com'),
39 ('user_agent', 'client-text-v1.1'),
37 ('user_agent', 'client-text-v1.1'),
40 # TODO: johbo: Commpare locking parameters with `_get_rc_scm_extras`
38 # TODO: johbo: Commpare locking parameters with `_get_rc_scm_extras`
41 # in hooks_utils.
39 # in hooks_utils.
42 ('make_lock', None),
40 ('make_lock', None),
43 ('locked_by', [None, None, None]),
41 ('locked_by', [None, None, None]),
44 ])
42 ])
45 def test_vcs_operation_context_parameters(result_key, expected_value):
43 def test_vcs_operation_context_parameters(result_key, expected_value):
46 result = call_vcs_operation_context()
44 result = call_vcs_operation_context()
47 assert result[result_key] == expected_value
45 assert result[result_key] == expected_value
48
46
49
47
50 @patch('rhodecode.model.db.User.get_by_username', Mock())
48 @patch('rhodecode.model.db.User.get_by_username', Mock())
51 @patch('rhodecode.model.db.Repository.get_by_repo_name')
49 @patch('rhodecode.model.db.Repository.get_by_repo_name')
52 def test_vcs_operation_context_checks_locking(mock_get_by_repo_name):
50 def test_vcs_operation_context_checks_locking(mock_get_by_repo_name):
53 mock_get_locking_state = mock_get_by_repo_name().get_locking_state
51 mock_get_locking_state = mock_get_by_repo_name().get_locking_state
54 mock_get_locking_state.return_value = (None, None, [None, None, None])
52 mock_get_locking_state.return_value = (None, None, [None, None, None])
55 call_vcs_operation_context(check_locking=True)
53 call_vcs_operation_context(check_locking=True)
56 assert mock_get_locking_state.called
54 assert mock_get_locking_state.called
57
55
58
56
59 @patch('rhodecode.model.db.Repository.get_locking_state')
57 @patch('rhodecode.model.db.Repository.get_locking_state')
60 def test_vcs_operation_context_skips_locking_checks_if_anonymouse(
58 def test_vcs_operation_context_skips_locking_checks_if_anonymouse(
61 mock_get_locking_state):
59 mock_get_locking_state):
62 call_vcs_operation_context(
60 call_vcs_operation_context(
63 username=db.User.DEFAULT_USER, check_locking=True)
61 username=db.User.DEFAULT_USER, check_locking=True)
64 assert not mock_get_locking_state.called
62 assert not mock_get_locking_state.called
65
63
66
64
67 @patch('rhodecode.model.db.Repository.get_locking_state')
65 @patch('rhodecode.model.db.Repository.get_locking_state')
68 def test_vcs_operation_context_can_skip_locking_check(mock_get_locking_state):
66 def test_vcs_operation_context_can_skip_locking_check(mock_get_locking_state):
69 call_vcs_operation_context(check_locking=False)
67 call_vcs_operation_context(check_locking=False)
70 assert not mock_get_locking_state.called
68 assert not mock_get_locking_state.called
71
69
72
70
73 @patch.object(
71 @patch.object(
74 base, 'get_enabled_hook_classes', Mock(return_value=['stub_hook']))
72 base, 'get_enabled_hook_classes', Mock(return_value=['stub_hook']))
75 @patch('rhodecode.lib.utils2.get_server_url',
73 @patch('rhodecode.lib.utils2.get_server_url',
76 Mock(return_value='https://example.com'))
74 Mock(return_value='https://example.com'))
77 def call_vcs_operation_context(**kwargs_override):
75 def call_vcs_operation_context(**kwargs_override):
78 kwargs = {
76 kwargs = {
79 'repo_name': 'stub_repo_name',
77 'repo_name': 'stub_repo_name',
80 'username': 'stub_username',
78 'username': 'stub_username',
81 'action': 'stub_action',
79 'action': 'stub_action',
82 'scm': 'stub_scm',
80 'scm': 'stub_scm',
83 'check_locking': False,
81 'check_locking': False,
84 }
82 }
85 kwargs.update(kwargs_override)
83 kwargs.update(kwargs_override)
86 config_file_patch = patch.dict(
84 config_file_patch = patch.dict(
87 'rhodecode.CONFIG', {'__file__': 'stub_ini_filename'})
85 'rhodecode.CONFIG', {'__file__': 'stub_ini_filename'})
88 settings_patch = patch.object(base, 'VcsSettingsModel')
86 settings_patch = patch.object(base, 'VcsSettingsModel')
89 with config_file_patch, settings_patch as settings_mock:
87 with config_file_patch, settings_patch as settings_mock:
90 result = base.vcs_operation_context(
88 result = base.vcs_operation_context(
91 environ={'HTTP_USER_AGENT': 'client-text-v1.1',
89 environ={'HTTP_USER_AGENT': 'client-text-v1.1',
92 'REMOTE_ADDR': '1.2.3.4'}, **kwargs)
90 'REMOTE_ADDR': '1.2.3.4'}, **kwargs)
93 settings_mock.assert_called_once_with(repo='stub_repo_name')
91 settings_mock.assert_called_once_with(repo='stub_repo_name')
94 return result
92 return result
95
93
96
94
97 class TestBaseRepoController(object):
98 def test_context_is_updated_when_update_global_counters_is_called(self):
99 followers = 1
100 forks = 2
101 pull_requests = 3
102 is_following = True
103 scm_model = Mock(name="scm_model")
104 db_repo = Mock(name="db_repo")
105 scm_model.get_followers.return_value = followers
106 scm_model.get_forks.return_value = forks
107 scm_model.get_pull_requests.return_value = pull_requests
108 scm_model.is_following_repo.return_value = is_following
109
110 controller = base.BaseRepoController()
111 with patch.object(base, 'c') as context_mock:
112 controller._update_global_counters(scm_model, db_repo)
113
114 scm_model.get_pull_requests.assert_called_once_with(db_repo)
115
116 assert context_mock.repository_pull_requests == pull_requests
117
118
119 class TestBaseRepoControllerHandleMissingRequirements(object):
120 def test_logs_error_and_sets_repo_to_none(self, app):
121 controller = base.BaseRepoController()
122 error_message = 'Some message'
123 error = RepositoryRequirementError(error_message)
124 context_patcher = patch.object(base, 'c')
125 log_patcher = patch.object(base, 'log')
126 request_patcher = patch.object(base, 'request')
127 redirect_patcher = patch.object(base, 'redirect')
128 controller.rhodecode_repo = 'something'
129
130 with context_patcher as context_mock, log_patcher as log_mock, \
131 request_patcher, redirect_patcher:
132 context_mock.repo_name = 'abcde'
133 controller._handle_missing_requirements(error)
134
135 expected_log_message = (
136 'Requirements are missing for repository %s: %s', 'abcde',
137 error_message)
138 log_mock.error.assert_called_once_with(*expected_log_message)
139
140 assert controller.rhodecode_repo is None
141
142 @pytest.mark.parametrize('path, should_redirect', [
143 ('/abcde', False),
144 ('/abcde/settings', False),
145 ('/abcde/settings/vcs', False),
146 ('/_admin/repos/abcde', False), # Settings update
147 ('/abcde/changelog', True),
148 ('/abcde/files/tip', True),
149 ('/abcde/settings/statistics', True),
150 ])
151 def test_redirects_if_not_summary_or_settings_page(
152 self, app, path, should_redirect):
153 repo_name = 'abcde'
154 controller = base.BaseRepoController()
155 error = RepositoryRequirementError('Some message')
156 context_patcher = patch.object(base, 'c')
157 controller.rhodecode_repo = repo_name
158 request_patcher = patch.object(base, 'request')
159 redirect_patcher = patch.object(base, 'redirect')
160
161 with context_patcher as context_mock, \
162 request_patcher as request_mock, \
163 redirect_patcher as redirect_mock:
164 request_mock.path = path
165 context_mock.repo_name = repo_name
166 controller._handle_missing_requirements(error)
167
168 expected_url = h.route_path('repo_summary', repo_name=repo_name)
169 if should_redirect:
170 redirect_mock.assert_called_once_with(expected_url)
171 else:
172 redirect_mock.call_count == 0
173
174
175 class TestBaseRepoControllerBefore(object):
176 def test_flag_is_true_when_requirements_are_missing(self, before_mocks):
177 controller = self._get_controller()
178
179 handle_patcher = patch.object(
180 controller, '_handle_missing_requirements')
181
182 error = RepositoryRequirementError()
183 before_mocks.repository.scm_instance.side_effect = error
184
185 with handle_patcher as handle_mock:
186 controller.__before__()
187
188 handle_mock.assert_called_once_with(error)
189 assert before_mocks['context'].repository_requirements_missing is True
190
191 def test_flag_is_false_when_no_requirements_are_missing(
192 self, before_mocks):
193 controller = self._get_controller()
194
195 handle_patcher = patch.object(
196 controller, '_handle_missing_requirements')
197 with handle_patcher as handle_mock:
198 controller.__before__()
199 handle_mock.call_count == 0
200 assert before_mocks['context'].repository_requirements_missing is False
201
202 def test_update_global_counters_is_called(self, before_mocks):
203 controller = self._get_controller()
204
205 update_counters_patcher = patch.object(
206 controller, '_update_global_counters')
207
208 with update_counters_patcher as update_counters_mock:
209 controller.__before__()
210 update_counters_mock.assert_called_once_with(
211 controller.scm_model, before_mocks.repository)
212
213 def _get_controller(self):
214 controller = base.BaseRepoController()
215 controller.scm_model = Mock()
216 controller.rhodecode_repo = Mock()
217 return controller
218
219
220 @pytest.fixture
221 def before_mocks(request):
222 patcher = BeforePatcher()
223 patcher.start()
224 request.addfinalizer(patcher.stop)
225 return patcher
226
227
228 class BeforePatcher(object):
229 patchers = {}
230 mocks = {}
231 repository = None
232
233 def __init__(self):
234 self.repository = Mock()
235
236 def start(self):
237 self.patchers = {
238 'request': patch.object(base, 'request'),
239 'before': patch.object(base.BaseController, '__before__'),
240 'context': patch.object(base, 'c'),
241 'repo': patch.object(
242 base.Repository, 'get_by_repo_name',
243 return_value=self.repository)
244
245 }
246 self.mocks = {
247 p: self.patchers[p].start() for p in self.patchers
248 }
249
250 def stop(self):
251 for patcher in self.patchers.values():
252 patcher.stop()
253
254 def __getitem__(self, key):
255 return self.mocks[key]
@@ -1,1069 +1,1069 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.model.settings import VcsSettingsModel, UiSetting
26 from rhodecode.model.settings import VcsSettingsModel, UiSetting
27
27
28
28
29 HOOKS_FORM_DATA = {
29 HOOKS_FORM_DATA = {
30 'hooks_changegroup_repo_size': True,
30 'hooks_changegroup_repo_size': True,
31 'hooks_changegroup_push_logger': True,
31 'hooks_changegroup_push_logger': True,
32 'hooks_outgoing_pull_logger': True
32 'hooks_outgoing_pull_logger': True
33 }
33 }
34
34
35 SVN_FORM_DATA = {
35 SVN_FORM_DATA = {
36 'new_svn_branch': 'test-branch',
36 'new_svn_branch': 'test-branch',
37 'new_svn_tag': 'test-tag'
37 'new_svn_tag': 'test-tag'
38 }
38 }
39
39
40 GENERAL_FORM_DATA = {
40 GENERAL_FORM_DATA = {
41 'rhodecode_pr_merge_enabled': True,
41 'rhodecode_pr_merge_enabled': True,
42 'rhodecode_use_outdated_comments': True,
42 'rhodecode_use_outdated_comments': True,
43 'rhodecode_hg_use_rebase_for_merging': True,
43 'rhodecode_hg_use_rebase_for_merging': True,
44 }
44 }
45
45
46
46
47 class TestInheritGlobalSettingsProperty(object):
47 class TestInheritGlobalSettingsProperty(object):
48 def test_get_raises_exception_when_repository_not_specified(self):
48 def test_get_raises_exception_when_repository_not_specified(self):
49 model = VcsSettingsModel()
49 model = VcsSettingsModel()
50 with pytest.raises(Exception) as exc_info:
50 with pytest.raises(Exception) as exc_info:
51 model.inherit_global_settings
51 model.inherit_global_settings
52 assert exc_info.value.message == 'Repository is not specified'
52 assert exc_info.value.message == 'Repository is not specified'
53
53
54 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
54 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
55 model = VcsSettingsModel(repo=repo_stub.repo_name)
55 model = VcsSettingsModel(repo=repo_stub.repo_name)
56 assert model.inherit_global_settings is True
56 assert model.inherit_global_settings is True
57
57
58 def test_value_is_returned(self, repo_stub, settings_util):
58 def test_value_is_returned(self, repo_stub, settings_util):
59 model = VcsSettingsModel(repo=repo_stub.repo_name)
59 model = VcsSettingsModel(repo=repo_stub.repo_name)
60 settings_util.create_repo_rhodecode_setting(
60 settings_util.create_repo_rhodecode_setting(
61 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
61 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
62 assert model.inherit_global_settings is False
62 assert model.inherit_global_settings is False
63
63
64 def test_value_is_set(self, repo_stub):
64 def test_value_is_set(self, repo_stub):
65 model = VcsSettingsModel(repo=repo_stub.repo_name)
65 model = VcsSettingsModel(repo=repo_stub.repo_name)
66 model.inherit_global_settings = False
66 model.inherit_global_settings = False
67 setting = model.repo_settings.get_setting_by_name(
67 setting = model.repo_settings.get_setting_by_name(
68 VcsSettingsModel.INHERIT_SETTINGS)
68 VcsSettingsModel.INHERIT_SETTINGS)
69 try:
69 try:
70 assert setting.app_settings_type == 'bool'
70 assert setting.app_settings_type == 'bool'
71 assert setting.app_settings_value is False
71 assert setting.app_settings_value is False
72 finally:
72 finally:
73 Session().delete(setting)
73 Session().delete(setting)
74 Session().commit()
74 Session().commit()
75
75
76 def test_set_raises_exception_when_repository_not_specified(self):
76 def test_set_raises_exception_when_repository_not_specified(self):
77 model = VcsSettingsModel()
77 model = VcsSettingsModel()
78 with pytest.raises(Exception) as exc_info:
78 with pytest.raises(Exception) as exc_info:
79 model.inherit_global_settings = False
79 model.inherit_global_settings = False
80 assert exc_info.value.message == 'Repository is not specified'
80 assert exc_info.value.message == 'Repository is not specified'
81
81
82
82
83 class TestVcsSettingsModel(object):
83 class TestVcsSettingsModel(object):
84 def test_global_svn_branch_patterns(self):
84 def test_global_svn_branch_patterns(self):
85 model = VcsSettingsModel()
85 model = VcsSettingsModel()
86 expected_result = {'test': 'test'}
86 expected_result = {'test': 'test'}
87 with mock.patch.object(model, 'global_settings') as settings_mock:
87 with mock.patch.object(model, 'global_settings') as settings_mock:
88 get_settings = settings_mock.get_ui_by_section
88 get_settings = settings_mock.get_ui_by_section
89 get_settings.return_value = expected_result
89 get_settings.return_value = expected_result
90 settings_mock.return_value = expected_result
90 settings_mock.return_value = expected_result
91 result = model.get_global_svn_branch_patterns()
91 result = model.get_global_svn_branch_patterns()
92
92
93 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
93 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
94 assert expected_result == result
94 assert expected_result == result
95
95
96 def test_repo_svn_branch_patterns(self):
96 def test_repo_svn_branch_patterns(self):
97 model = VcsSettingsModel()
97 model = VcsSettingsModel()
98 expected_result = {'test': 'test'}
98 expected_result = {'test': 'test'}
99 with mock.patch.object(model, 'repo_settings') as settings_mock:
99 with mock.patch.object(model, 'repo_settings') as settings_mock:
100 get_settings = settings_mock.get_ui_by_section
100 get_settings = settings_mock.get_ui_by_section
101 get_settings.return_value = expected_result
101 get_settings.return_value = expected_result
102 settings_mock.return_value = expected_result
102 settings_mock.return_value = expected_result
103 result = model.get_repo_svn_branch_patterns()
103 result = model.get_repo_svn_branch_patterns()
104
104
105 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
105 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
106 assert expected_result == result
106 assert expected_result == result
107
107
108 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
108 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
109 self):
109 self):
110 model = VcsSettingsModel()
110 model = VcsSettingsModel()
111 with pytest.raises(Exception) as exc_info:
111 with pytest.raises(Exception) as exc_info:
112 model.get_repo_svn_branch_patterns()
112 model.get_repo_svn_branch_patterns()
113 assert exc_info.value.message == 'Repository is not specified'
113 assert exc_info.value.message == 'Repository is not specified'
114
114
115 def test_global_svn_tag_patterns(self):
115 def test_global_svn_tag_patterns(self):
116 model = VcsSettingsModel()
116 model = VcsSettingsModel()
117 expected_result = {'test': 'test'}
117 expected_result = {'test': 'test'}
118 with mock.patch.object(model, 'global_settings') as settings_mock:
118 with mock.patch.object(model, 'global_settings') as settings_mock:
119 get_settings = settings_mock.get_ui_by_section
119 get_settings = settings_mock.get_ui_by_section
120 get_settings.return_value = expected_result
120 get_settings.return_value = expected_result
121 settings_mock.return_value = expected_result
121 settings_mock.return_value = expected_result
122 result = model.get_global_svn_tag_patterns()
122 result = model.get_global_svn_tag_patterns()
123
123
124 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
124 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
125 assert expected_result == result
125 assert expected_result == result
126
126
127 def test_repo_svn_tag_patterns(self):
127 def test_repo_svn_tag_patterns(self):
128 model = VcsSettingsModel()
128 model = VcsSettingsModel()
129 expected_result = {'test': 'test'}
129 expected_result = {'test': 'test'}
130 with mock.patch.object(model, 'repo_settings') as settings_mock:
130 with mock.patch.object(model, 'repo_settings') as settings_mock:
131 get_settings = settings_mock.get_ui_by_section
131 get_settings = settings_mock.get_ui_by_section
132 get_settings.return_value = expected_result
132 get_settings.return_value = expected_result
133 settings_mock.return_value = expected_result
133 settings_mock.return_value = expected_result
134 result = model.get_repo_svn_tag_patterns()
134 result = model.get_repo_svn_tag_patterns()
135
135
136 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
136 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
137 assert expected_result == result
137 assert expected_result == result
138
138
139 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
139 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
140 model = VcsSettingsModel()
140 model = VcsSettingsModel()
141 with pytest.raises(Exception) as exc_info:
141 with pytest.raises(Exception) as exc_info:
142 model.get_repo_svn_tag_patterns()
142 model.get_repo_svn_tag_patterns()
143 assert exc_info.value.message == 'Repository is not specified'
143 assert exc_info.value.message == 'Repository is not specified'
144
144
145 def test_get_global_settings(self):
145 def test_get_global_settings(self):
146 expected_result = {'test': 'test'}
146 expected_result = {'test': 'test'}
147 model = VcsSettingsModel()
147 model = VcsSettingsModel()
148 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
148 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
149 collect_mock.return_value = expected_result
149 collect_mock.return_value = expected_result
150 result = model.get_global_settings()
150 result = model.get_global_settings()
151
151
152 collect_mock.assert_called_once_with(global_=True)
152 collect_mock.assert_called_once_with(global_=True)
153 assert result == expected_result
153 assert result == expected_result
154
154
155 def test_get_repo_settings(self, repo_stub):
155 def test_get_repo_settings(self, repo_stub):
156 model = VcsSettingsModel(repo=repo_stub.repo_name)
156 model = VcsSettingsModel(repo=repo_stub.repo_name)
157 expected_result = {'test': 'test'}
157 expected_result = {'test': 'test'}
158 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
158 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
159 collect_mock.return_value = expected_result
159 collect_mock.return_value = expected_result
160 result = model.get_repo_settings()
160 result = model.get_repo_settings()
161
161
162 collect_mock.assert_called_once_with(global_=False)
162 collect_mock.assert_called_once_with(global_=False)
163 assert result == expected_result
163 assert result == expected_result
164
164
165 @pytest.mark.parametrize('settings, global_', [
165 @pytest.mark.parametrize('settings, global_', [
166 ('global_settings', True),
166 ('global_settings', True),
167 ('repo_settings', False)
167 ('repo_settings', False)
168 ])
168 ])
169 def test_collect_all_settings(self, settings, global_):
169 def test_collect_all_settings(self, settings, global_):
170 model = VcsSettingsModel()
170 model = VcsSettingsModel()
171 result_mock = self._mock_result()
171 result_mock = self._mock_result()
172
172
173 settings_patch = mock.patch.object(model, settings)
173 settings_patch = mock.patch.object(model, settings)
174 with settings_patch as settings_mock:
174 with settings_patch as settings_mock:
175 settings_mock.get_ui_by_section_and_key.return_value = result_mock
175 settings_mock.get_ui_by_section_and_key.return_value = result_mock
176 settings_mock.get_setting_by_name.return_value = result_mock
176 settings_mock.get_setting_by_name.return_value = result_mock
177 result = model._collect_all_settings(global_=global_)
177 result = model._collect_all_settings(global_=global_)
178
178
179 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
179 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
180 self._assert_get_settings_calls(
180 self._assert_get_settings_calls(
181 settings_mock, ui_settings, model.GENERAL_SETTINGS)
181 settings_mock, ui_settings, model.GENERAL_SETTINGS)
182 self._assert_collect_all_settings_result(
182 self._assert_collect_all_settings_result(
183 ui_settings, model.GENERAL_SETTINGS, result)
183 ui_settings, model.GENERAL_SETTINGS, result)
184
184
185 @pytest.mark.parametrize('settings, global_', [
185 @pytest.mark.parametrize('settings, global_', [
186 ('global_settings', True),
186 ('global_settings', True),
187 ('repo_settings', False)
187 ('repo_settings', False)
188 ])
188 ])
189 def test_collect_all_settings_without_empty_value(self, settings, global_):
189 def test_collect_all_settings_without_empty_value(self, settings, global_):
190 model = VcsSettingsModel()
190 model = VcsSettingsModel()
191
191
192 settings_patch = mock.patch.object(model, settings)
192 settings_patch = mock.patch.object(model, settings)
193 with settings_patch as settings_mock:
193 with settings_patch as settings_mock:
194 settings_mock.get_ui_by_section_and_key.return_value = None
194 settings_mock.get_ui_by_section_and_key.return_value = None
195 settings_mock.get_setting_by_name.return_value = None
195 settings_mock.get_setting_by_name.return_value = None
196 result = model._collect_all_settings(global_=global_)
196 result = model._collect_all_settings(global_=global_)
197
197
198 assert result == {}
198 assert result == {}
199
199
200 def _mock_result(self):
200 def _mock_result(self):
201 result_mock = mock.Mock()
201 result_mock = mock.Mock()
202 result_mock.ui_value = 'ui_value'
202 result_mock.ui_value = 'ui_value'
203 result_mock.ui_active = True
203 result_mock.ui_active = True
204 result_mock.app_settings_value = 'setting_value'
204 result_mock.app_settings_value = 'setting_value'
205 return result_mock
205 return result_mock
206
206
207 def _assert_get_settings_calls(
207 def _assert_get_settings_calls(
208 self, settings_mock, ui_settings, general_settings):
208 self, settings_mock, ui_settings, general_settings):
209 assert (
209 assert (
210 settings_mock.get_ui_by_section_and_key.call_count ==
210 settings_mock.get_ui_by_section_and_key.call_count ==
211 len(ui_settings))
211 len(ui_settings))
212 assert (
212 assert (
213 settings_mock.get_setting_by_name.call_count ==
213 settings_mock.get_setting_by_name.call_count ==
214 len(general_settings))
214 len(general_settings))
215
215
216 for section, key in ui_settings:
216 for section, key in ui_settings:
217 expected_call = mock.call(section, key)
217 expected_call = mock.call(section, key)
218 assert (
218 assert (
219 expected_call in
219 expected_call in
220 settings_mock.get_ui_by_section_and_key.call_args_list)
220 settings_mock.get_ui_by_section_and_key.call_args_list)
221
221
222 for name in general_settings:
222 for name in general_settings:
223 expected_call = mock.call(name)
223 expected_call = mock.call(name)
224 assert (
224 assert (
225 expected_call in
225 expected_call in
226 settings_mock.get_setting_by_name.call_args_list)
226 settings_mock.get_setting_by_name.call_args_list)
227
227
228 def _assert_collect_all_settings_result(
228 def _assert_collect_all_settings_result(
229 self, ui_settings, general_settings, result):
229 self, ui_settings, general_settings, result):
230 expected_result = {}
230 expected_result = {}
231 for section, key in ui_settings:
231 for section, key in ui_settings:
232 key = '{}_{}'.format(section, key.replace('.', '_'))
232 key = '{}_{}'.format(section, key.replace('.', '_'))
233
233
234 if section in ('extensions', 'hooks'):
234 if section in ('extensions', 'hooks'):
235 value = True
235 value = True
236 elif key in ['vcs_git_lfs_enabled']:
236 elif key in ['vcs_git_lfs_enabled']:
237 value = True
237 value = True
238 else:
238 else:
239 value = 'ui_value'
239 value = 'ui_value'
240 expected_result[key] = value
240 expected_result[key] = value
241
241
242 for name in general_settings:
242 for name in general_settings:
243 key = 'rhodecode_' + name
243 key = 'rhodecode_' + name
244 expected_result[key] = 'setting_value'
244 expected_result[key] = 'setting_value'
245
245
246 assert expected_result == result
246 assert expected_result == result
247
247
248
248
249 class TestCreateOrUpdateRepoHookSettings(object):
249 class TestCreateOrUpdateRepoHookSettings(object):
250 def test_create_when_no_repo_object_found(self, repo_stub):
250 def test_create_when_no_repo_object_found(self, repo_stub):
251 model = VcsSettingsModel(repo=repo_stub.repo_name)
251 model = VcsSettingsModel(repo=repo_stub.repo_name)
252
252
253 self._create_settings(model, HOOKS_FORM_DATA)
253 self._create_settings(model, HOOKS_FORM_DATA)
254
254
255 cleanup = []
255 cleanup = []
256 try:
256 try:
257 for section, key in model.HOOKS_SETTINGS:
257 for section, key in model.HOOKS_SETTINGS:
258 ui = model.repo_settings.get_ui_by_section_and_key(
258 ui = model.repo_settings.get_ui_by_section_and_key(
259 section, key)
259 section, key)
260 assert ui.ui_active is True
260 assert ui.ui_active is True
261 cleanup.append(ui)
261 cleanup.append(ui)
262 finally:
262 finally:
263 for ui in cleanup:
263 for ui in cleanup:
264 Session().delete(ui)
264 Session().delete(ui)
265 Session().commit()
265 Session().commit()
266
266
267 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
267 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
268 model = VcsSettingsModel(repo=repo_stub.repo_name)
268 model = VcsSettingsModel(repo=repo_stub.repo_name)
269
269
270 deleted_key = 'hooks_changegroup_repo_size'
270 deleted_key = 'hooks_changegroup_repo_size'
271 data = HOOKS_FORM_DATA.copy()
271 data = HOOKS_FORM_DATA.copy()
272 data.pop(deleted_key)
272 data.pop(deleted_key)
273
273
274 with pytest.raises(ValueError) as exc_info:
274 with pytest.raises(ValueError) as exc_info:
275 model.create_or_update_repo_hook_settings(data)
275 model.create_or_update_repo_hook_settings(data)
276 assert (
276 assert (
277 exc_info.value.message ==
277 exc_info.value.message ==
278 'The given data does not contain {} key'.format(deleted_key))
278 'The given data does not contain {} key'.format(deleted_key))
279
279
280 def test_update_when_repo_object_found(self, repo_stub, settings_util):
280 def test_update_when_repo_object_found(self, repo_stub, settings_util):
281 model = VcsSettingsModel(repo=repo_stub.repo_name)
281 model = VcsSettingsModel(repo=repo_stub.repo_name)
282 for section, key in model.HOOKS_SETTINGS:
282 for section, key in model.HOOKS_SETTINGS:
283 settings_util.create_repo_rhodecode_ui(
283 settings_util.create_repo_rhodecode_ui(
284 repo_stub, section, None, key=key, active=False)
284 repo_stub, section, None, key=key, active=False)
285 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
285 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
286 for section, key in model.HOOKS_SETTINGS:
286 for section, key in model.HOOKS_SETTINGS:
287 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
287 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
288 assert ui.ui_active is True
288 assert ui.ui_active is True
289
289
290 def _create_settings(self, model, data):
290 def _create_settings(self, model, data):
291 global_patch = mock.patch.object(model, 'global_settings')
291 global_patch = mock.patch.object(model, 'global_settings')
292 global_setting = mock.Mock()
292 global_setting = mock.Mock()
293 global_setting.ui_value = 'Test value'
293 global_setting.ui_value = 'Test value'
294 with global_patch as global_mock:
294 with global_patch as global_mock:
295 global_mock.get_ui_by_section_and_key.return_value = global_setting
295 global_mock.get_ui_by_section_and_key.return_value = global_setting
296 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
296 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
297
297
298
298
299 class TestUpdateGlobalHookSettings(object):
299 class TestUpdateGlobalHookSettings(object):
300 def test_update_raises_exception_when_data_incomplete(self):
300 def test_update_raises_exception_when_data_incomplete(self):
301 model = VcsSettingsModel()
301 model = VcsSettingsModel()
302
302
303 deleted_key = 'hooks_changegroup_repo_size'
303 deleted_key = 'hooks_changegroup_repo_size'
304 data = HOOKS_FORM_DATA.copy()
304 data = HOOKS_FORM_DATA.copy()
305 data.pop(deleted_key)
305 data.pop(deleted_key)
306
306
307 with pytest.raises(ValueError) as exc_info:
307 with pytest.raises(ValueError) as exc_info:
308 model.update_global_hook_settings(data)
308 model.update_global_hook_settings(data)
309 assert (
309 assert (
310 exc_info.value.message ==
310 exc_info.value.message ==
311 'The given data does not contain {} key'.format(deleted_key))
311 'The given data does not contain {} key'.format(deleted_key))
312
312
313 def test_update_global_hook_settings(self, settings_util):
313 def test_update_global_hook_settings(self, settings_util):
314 model = VcsSettingsModel()
314 model = VcsSettingsModel()
315 setting_mock = mock.MagicMock()
315 setting_mock = mock.MagicMock()
316 setting_mock.ui_active = False
316 setting_mock.ui_active = False
317 get_settings_patcher = mock.patch.object(
317 get_settings_patcher = mock.patch.object(
318 model.global_settings, 'get_ui_by_section_and_key',
318 model.global_settings, 'get_ui_by_section_and_key',
319 return_value=setting_mock)
319 return_value=setting_mock)
320 session_patcher = mock.patch('rhodecode.model.settings.Session')
320 session_patcher = mock.patch('rhodecode.model.settings.Session')
321 with get_settings_patcher as get_settings_mock, session_patcher:
321 with get_settings_patcher as get_settings_mock, session_patcher:
322 model.update_global_hook_settings(HOOKS_FORM_DATA)
322 model.update_global_hook_settings(HOOKS_FORM_DATA)
323 assert setting_mock.ui_active is True
323 assert setting_mock.ui_active is True
324 assert get_settings_mock.call_count == 3
324 assert get_settings_mock.call_count == 3
325
325
326
326
327 class TestCreateOrUpdateRepoGeneralSettings(object):
327 class TestCreateOrUpdateRepoGeneralSettings(object):
328 def test_calls_create_or_update_general_settings(self, repo_stub):
328 def test_calls_create_or_update_general_settings(self, repo_stub):
329 model = VcsSettingsModel(repo=repo_stub.repo_name)
329 model = VcsSettingsModel(repo=repo_stub.repo_name)
330 create_patch = mock.patch.object(
330 create_patch = mock.patch.object(
331 model, '_create_or_update_general_settings')
331 model, '_create_or_update_general_settings')
332 with create_patch as create_mock:
332 with create_patch as create_mock:
333 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
333 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
334 create_mock.assert_called_once_with(
334 create_mock.assert_called_once_with(
335 model.repo_settings, GENERAL_FORM_DATA)
335 model.repo_settings, GENERAL_FORM_DATA)
336
336
337 def test_raises_exception_when_repository_is_not_specified(self):
337 def test_raises_exception_when_repository_is_not_specified(self):
338 model = VcsSettingsModel()
338 model = VcsSettingsModel()
339 with pytest.raises(Exception) as exc_info:
339 with pytest.raises(Exception) as exc_info:
340 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
340 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
341 assert exc_info.value.message == 'Repository is not specified'
341 assert exc_info.value.message == 'Repository is not specified'
342
342
343
343
344 class TestCreateOrUpdatGlobalGeneralSettings(object):
344 class TestCreateOrUpdatGlobalGeneralSettings(object):
345 def test_calls_create_or_update_general_settings(self):
345 def test_calls_create_or_update_general_settings(self):
346 model = VcsSettingsModel()
346 model = VcsSettingsModel()
347 create_patch = mock.patch.object(
347 create_patch = mock.patch.object(
348 model, '_create_or_update_general_settings')
348 model, '_create_or_update_general_settings')
349 with create_patch as create_mock:
349 with create_patch as create_mock:
350 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
350 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
351 create_mock.assert_called_once_with(
351 create_mock.assert_called_once_with(
352 model.global_settings, GENERAL_FORM_DATA)
352 model.global_settings, GENERAL_FORM_DATA)
353
353
354
354
355 class TestCreateOrUpdateGeneralSettings(object):
355 class TestCreateOrUpdateGeneralSettings(object):
356 def test_create_when_no_repo_settings_found(self, repo_stub):
356 def test_create_when_no_repo_settings_found(self, repo_stub):
357 model = VcsSettingsModel(repo=repo_stub.repo_name)
357 model = VcsSettingsModel(repo=repo_stub.repo_name)
358 model._create_or_update_general_settings(
358 model._create_or_update_general_settings(
359 model.repo_settings, GENERAL_FORM_DATA)
359 model.repo_settings, GENERAL_FORM_DATA)
360
360
361 cleanup = []
361 cleanup = []
362 try:
362 try:
363 for name in model.GENERAL_SETTINGS:
363 for name in model.GENERAL_SETTINGS:
364 setting = model.repo_settings.get_setting_by_name(name)
364 setting = model.repo_settings.get_setting_by_name(name)
365 assert setting.app_settings_value is True
365 assert setting.app_settings_value is True
366 cleanup.append(setting)
366 cleanup.append(setting)
367 finally:
367 finally:
368 for setting in cleanup:
368 for setting in cleanup:
369 Session().delete(setting)
369 Session().delete(setting)
370 Session().commit()
370 Session().commit()
371
371
372 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
372 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
373 model = VcsSettingsModel(repo=repo_stub.repo_name)
373 model = VcsSettingsModel(repo=repo_stub.repo_name)
374
374
375 deleted_key = 'rhodecode_pr_merge_enabled'
375 deleted_key = 'rhodecode_pr_merge_enabled'
376 data = GENERAL_FORM_DATA.copy()
376 data = GENERAL_FORM_DATA.copy()
377 data.pop(deleted_key)
377 data.pop(deleted_key)
378
378
379 with pytest.raises(ValueError) as exc_info:
379 with pytest.raises(ValueError) as exc_info:
380 model._create_or_update_general_settings(model.repo_settings, data)
380 model._create_or_update_general_settings(model.repo_settings, data)
381 assert (
381 assert (
382 exc_info.value.message ==
382 exc_info.value.message ==
383 'The given data does not contain {} key'.format(deleted_key))
383 'The given data does not contain {} key'.format(deleted_key))
384
384
385 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
385 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
386 model = VcsSettingsModel(repo=repo_stub.repo_name)
386 model = VcsSettingsModel(repo=repo_stub.repo_name)
387 for name in model.GENERAL_SETTINGS:
387 for name in model.GENERAL_SETTINGS:
388 settings_util.create_repo_rhodecode_setting(
388 settings_util.create_repo_rhodecode_setting(
389 repo_stub, name, False, 'bool')
389 repo_stub, name, False, 'bool')
390
390
391 model._create_or_update_general_settings(
391 model._create_or_update_general_settings(
392 model.repo_settings, GENERAL_FORM_DATA)
392 model.repo_settings, GENERAL_FORM_DATA)
393
393
394 for name in model.GENERAL_SETTINGS:
394 for name in model.GENERAL_SETTINGS:
395 setting = model.repo_settings.get_setting_by_name(name)
395 setting = model.repo_settings.get_setting_by_name(name)
396 assert setting.app_settings_value is True
396 assert setting.app_settings_value is True
397
397
398
398
399 class TestCreateRepoSvnSettings(object):
399 class TestCreateRepoSvnSettings(object):
400 def test_calls_create_svn_settings(self, repo_stub):
400 def test_calls_create_svn_settings(self, repo_stub):
401 model = VcsSettingsModel(repo=repo_stub.repo_name)
401 model = VcsSettingsModel(repo=repo_stub.repo_name)
402 with mock.patch.object(model, '_create_svn_settings') as create_mock:
402 with mock.patch.object(model, '_create_svn_settings') as create_mock:
403 model.create_repo_svn_settings(SVN_FORM_DATA)
403 model.create_repo_svn_settings(SVN_FORM_DATA)
404 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
404 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
405
405
406 def test_raises_exception_when_repository_is_not_specified(self):
406 def test_raises_exception_when_repository_is_not_specified(self):
407 model = VcsSettingsModel()
407 model = VcsSettingsModel()
408 with pytest.raises(Exception) as exc_info:
408 with pytest.raises(Exception) as exc_info:
409 model.create_repo_svn_settings(SVN_FORM_DATA)
409 model.create_repo_svn_settings(SVN_FORM_DATA)
410 assert exc_info.value.message == 'Repository is not specified'
410 assert exc_info.value.message == 'Repository is not specified'
411
411
412
412
413 class TestCreateSvnSettings(object):
413 class TestCreateSvnSettings(object):
414 def test_create(self, repo_stub):
414 def test_create(self, repo_stub):
415 model = VcsSettingsModel(repo=repo_stub.repo_name)
415 model = VcsSettingsModel(repo=repo_stub.repo_name)
416 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
416 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
417 Session().commit()
417 Session().commit()
418
418
419 branch_ui = model.repo_settings.get_ui_by_section(
419 branch_ui = model.repo_settings.get_ui_by_section(
420 model.SVN_BRANCH_SECTION)
420 model.SVN_BRANCH_SECTION)
421 tag_ui = model.repo_settings.get_ui_by_section(
421 tag_ui = model.repo_settings.get_ui_by_section(
422 model.SVN_TAG_SECTION)
422 model.SVN_TAG_SECTION)
423
423
424 try:
424 try:
425 assert len(branch_ui) == 1
425 assert len(branch_ui) == 1
426 assert len(tag_ui) == 1
426 assert len(tag_ui) == 1
427 finally:
427 finally:
428 Session().delete(branch_ui[0])
428 Session().delete(branch_ui[0])
429 Session().delete(tag_ui[0])
429 Session().delete(tag_ui[0])
430 Session().commit()
430 Session().commit()
431
431
432 def test_create_tag(self, repo_stub):
432 def test_create_tag(self, repo_stub):
433 model = VcsSettingsModel(repo=repo_stub.repo_name)
433 model = VcsSettingsModel(repo=repo_stub.repo_name)
434 data = SVN_FORM_DATA.copy()
434 data = SVN_FORM_DATA.copy()
435 data.pop('new_svn_branch')
435 data.pop('new_svn_branch')
436 model._create_svn_settings(model.repo_settings, data)
436 model._create_svn_settings(model.repo_settings, data)
437 Session().commit()
437 Session().commit()
438
438
439 branch_ui = model.repo_settings.get_ui_by_section(
439 branch_ui = model.repo_settings.get_ui_by_section(
440 model.SVN_BRANCH_SECTION)
440 model.SVN_BRANCH_SECTION)
441 tag_ui = model.repo_settings.get_ui_by_section(
441 tag_ui = model.repo_settings.get_ui_by_section(
442 model.SVN_TAG_SECTION)
442 model.SVN_TAG_SECTION)
443
443
444 try:
444 try:
445 assert len(branch_ui) == 0
445 assert len(branch_ui) == 0
446 assert len(tag_ui) == 1
446 assert len(tag_ui) == 1
447 finally:
447 finally:
448 Session().delete(tag_ui[0])
448 Session().delete(tag_ui[0])
449 Session().commit()
449 Session().commit()
450
450
451 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
451 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
452 model = VcsSettingsModel(repo=repo_stub.repo_name)
452 model = VcsSettingsModel(repo=repo_stub.repo_name)
453 model._create_svn_settings(model.repo_settings, {})
453 model._create_svn_settings(model.repo_settings, {})
454 Session().commit()
454 Session().commit()
455
455
456 branch_ui = model.repo_settings.get_ui_by_section(
456 branch_ui = model.repo_settings.get_ui_by_section(
457 model.SVN_BRANCH_SECTION)
457 model.SVN_BRANCH_SECTION)
458 tag_ui = model.repo_settings.get_ui_by_section(
458 tag_ui = model.repo_settings.get_ui_by_section(
459 model.SVN_TAG_SECTION)
459 model.SVN_TAG_SECTION)
460
460
461 assert len(branch_ui) == 0
461 assert len(branch_ui) == 0
462 assert len(tag_ui) == 0
462 assert len(tag_ui) == 0
463
463
464 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
464 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
465 model = VcsSettingsModel(repo=repo_stub.repo_name)
465 model = VcsSettingsModel(repo=repo_stub.repo_name)
466 data = {
466 data = {
467 'new_svn_branch': '',
467 'new_svn_branch': '',
468 'new_svn_tag': ''
468 'new_svn_tag': ''
469 }
469 }
470 model._create_svn_settings(model.repo_settings, data)
470 model._create_svn_settings(model.repo_settings, data)
471 Session().commit()
471 Session().commit()
472
472
473 branch_ui = model.repo_settings.get_ui_by_section(
473 branch_ui = model.repo_settings.get_ui_by_section(
474 model.SVN_BRANCH_SECTION)
474 model.SVN_BRANCH_SECTION)
475 tag_ui = model.repo_settings.get_ui_by_section(
475 tag_ui = model.repo_settings.get_ui_by_section(
476 model.SVN_TAG_SECTION)
476 model.SVN_TAG_SECTION)
477
477
478 assert len(branch_ui) == 0
478 assert len(branch_ui) == 0
479 assert len(tag_ui) == 0
479 assert len(tag_ui) == 0
480
480
481
481
482 class TestCreateOrUpdateUi(object):
482 class TestCreateOrUpdateUi(object):
483 def test_create(self, repo_stub):
483 def test_create(self, repo_stub):
484 model = VcsSettingsModel(repo=repo_stub.repo_name)
484 model = VcsSettingsModel(repo=repo_stub.repo_name)
485 model._create_or_update_ui(
485 model._create_or_update_ui(
486 model.repo_settings, 'test-section', 'test-key', active=False,
486 model.repo_settings, 'test-section', 'test-key', active=False,
487 value='False')
487 value='False')
488 Session().commit()
488 Session().commit()
489
489
490 created_ui = model.repo_settings.get_ui_by_section_and_key(
490 created_ui = model.repo_settings.get_ui_by_section_and_key(
491 'test-section', 'test-key')
491 'test-section', 'test-key')
492
492
493 try:
493 try:
494 assert created_ui.ui_active is False
494 assert created_ui.ui_active is False
495 assert str2bool(created_ui.ui_value) is False
495 assert str2bool(created_ui.ui_value) is False
496 finally:
496 finally:
497 Session().delete(created_ui)
497 Session().delete(created_ui)
498 Session().commit()
498 Session().commit()
499
499
500 def test_update(self, repo_stub, settings_util):
500 def test_update(self, repo_stub, settings_util):
501 model = VcsSettingsModel(repo=repo_stub.repo_name)
501 model = VcsSettingsModel(repo=repo_stub.repo_name)
502
502
503 largefiles, phases, evolve = model.HG_SETTINGS
503 largefiles, phases, evolve = model.HG_SETTINGS
504
504
505 section = 'test-section'
505 section = 'test-section'
506 key = 'test-key'
506 key = 'test-key'
507 settings_util.create_repo_rhodecode_ui(
507 settings_util.create_repo_rhodecode_ui(
508 repo_stub, section, 'True', key=key, active=True)
508 repo_stub, section, 'True', key=key, active=True)
509
509
510 model._create_or_update_ui(
510 model._create_or_update_ui(
511 model.repo_settings, section, key, active=False, value='False')
511 model.repo_settings, section, key, active=False, value='False')
512 Session().commit()
512 Session().commit()
513
513
514 created_ui = model.repo_settings.get_ui_by_section_and_key(
514 created_ui = model.repo_settings.get_ui_by_section_and_key(
515 section, key)
515 section, key)
516 assert created_ui.ui_active is False
516 assert created_ui.ui_active is False
517 assert str2bool(created_ui.ui_value) is False
517 assert str2bool(created_ui.ui_value) is False
518
518
519
519
520 class TestCreateOrUpdateRepoHgSettings(object):
520 class TestCreateOrUpdateRepoHgSettings(object):
521 FORM_DATA = {
521 FORM_DATA = {
522 'extensions_largefiles': False,
522 'extensions_largefiles': False,
523 'extensions_evolve': False,
523 'extensions_evolve': False,
524 'phases_publish': False
524 'phases_publish': False
525 }
525 }
526
526
527 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
527 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
528 model = VcsSettingsModel(repo=repo_stub.repo_name)
528 model = VcsSettingsModel(repo=repo_stub.repo_name)
529 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
529 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
530 model.create_or_update_repo_hg_settings(self.FORM_DATA)
530 model.create_or_update_repo_hg_settings(self.FORM_DATA)
531 expected_calls = [
531 expected_calls = [
532 mock.call(model.repo_settings, 'extensions', 'largefiles',
532 mock.call(model.repo_settings, 'extensions', 'largefiles',
533 active=False, value=''),
533 active=False, value=''),
534 mock.call(model.repo_settings, 'extensions', 'evolve',
534 mock.call(model.repo_settings, 'extensions', 'evolve',
535 active=False, value=''),
535 active=False, value=''),
536 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
536 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
537 ]
537 ]
538 assert expected_calls == create_mock.call_args_list
538 assert expected_calls == create_mock.call_args_list
539
539
540 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
540 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
541 def test_key_is_not_found(self, repo_stub, field_to_remove):
541 def test_key_is_not_found(self, repo_stub, field_to_remove):
542 model = VcsSettingsModel(repo=repo_stub.repo_name)
542 model = VcsSettingsModel(repo=repo_stub.repo_name)
543 data = self.FORM_DATA.copy()
543 data = self.FORM_DATA.copy()
544 data.pop(field_to_remove)
544 data.pop(field_to_remove)
545 with pytest.raises(ValueError) as exc_info:
545 with pytest.raises(ValueError) as exc_info:
546 model.create_or_update_repo_hg_settings(data)
546 model.create_or_update_repo_hg_settings(data)
547 expected_message = 'The given data does not contain {} key'.format(
547 expected_message = 'The given data does not contain {} key'.format(
548 field_to_remove)
548 field_to_remove)
549 assert exc_info.value.message == expected_message
549 assert exc_info.value.message == expected_message
550
550
551 def test_create_raises_exception_when_repository_not_specified(self):
551 def test_create_raises_exception_when_repository_not_specified(self):
552 model = VcsSettingsModel()
552 model = VcsSettingsModel()
553 with pytest.raises(Exception) as exc_info:
553 with pytest.raises(Exception) as exc_info:
554 model.create_or_update_repo_hg_settings(self.FORM_DATA)
554 model.create_or_update_repo_hg_settings(self.FORM_DATA)
555 assert exc_info.value.message == 'Repository is not specified'
555 assert exc_info.value.message == 'Repository is not specified'
556
556
557
557
558 class TestUpdateGlobalSslSetting(object):
558 class TestUpdateGlobalSslSetting(object):
559 def test_updates_global_hg_settings(self):
559 def test_updates_global_hg_settings(self):
560 model = VcsSettingsModel()
560 model = VcsSettingsModel()
561 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
561 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
562 model.update_global_ssl_setting('False')
562 model.update_global_ssl_setting('False')
563 create_mock.assert_called_once_with(
563 create_mock.assert_called_once_with(
564 model.global_settings, 'web', 'push_ssl', value='False')
564 model.global_settings, 'web', 'push_ssl', value='False')
565
565
566
566
567 class TestUpdateGlobalPathSetting(object):
567 class TestUpdateGlobalPathSetting(object):
568 def test_updates_global_path_settings(self):
568 def test_updates_global_path_settings(self):
569 model = VcsSettingsModel()
569 model = VcsSettingsModel()
570 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
570 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
571 model.update_global_path_setting('False')
571 model.update_global_path_setting('False')
572 create_mock.assert_called_once_with(
572 create_mock.assert_called_once_with(
573 model.global_settings, 'paths', '/', value='False')
573 model.global_settings, 'paths', '/', value='False')
574
574
575
575
576 class TestCreateOrUpdateGlobalHgSettings(object):
576 class TestCreateOrUpdateGlobalHgSettings(object):
577 FORM_DATA = {
577 FORM_DATA = {
578 'extensions_largefiles': False,
578 'extensions_largefiles': False,
579 'largefiles_usercache': '/example/largefiles-store',
579 'largefiles_usercache': '/example/largefiles-store',
580 'phases_publish': False,
580 'phases_publish': False,
581 'extensions_hgsubversion': False,
581 'extensions_hgsubversion': False,
582 'extensions_evolve': False
582 'extensions_evolve': False
583 }
583 }
584
584
585 def test_creates_repo_hg_settings_when_data_is_correct(self):
585 def test_creates_repo_hg_settings_when_data_is_correct(self):
586 model = VcsSettingsModel()
586 model = VcsSettingsModel()
587 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
587 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
588 model.create_or_update_global_hg_settings(self.FORM_DATA)
588 model.create_or_update_global_hg_settings(self.FORM_DATA)
589 expected_calls = [
589 expected_calls = [
590 mock.call(model.global_settings, 'extensions', 'largefiles',
590 mock.call(model.global_settings, 'extensions', 'largefiles',
591 active=False, value=''),
591 active=False, value=''),
592 mock.call(model.global_settings, 'largefiles', 'usercache',
592 mock.call(model.global_settings, 'largefiles', 'usercache',
593 value='/example/largefiles-store'),
593 value='/example/largefiles-store'),
594 mock.call(model.global_settings, 'phases', 'publish',
594 mock.call(model.global_settings, 'phases', 'publish',
595 value='False'),
595 value='False'),
596 mock.call(model.global_settings, 'extensions', 'hgsubversion',
596 mock.call(model.global_settings, 'extensions', 'hgsubversion',
597 active=False),
597 active=False),
598 mock.call(model.global_settings, 'extensions', 'evolve',
598 mock.call(model.global_settings, 'extensions', 'evolve',
599 active=False, value='')
599 active=False, value='')
600 ]
600 ]
601 assert expected_calls == create_mock.call_args_list
601 assert expected_calls == create_mock.call_args_list
602
602
603 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
603 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
604 def test_key_is_not_found(self, repo_stub, field_to_remove):
604 def test_key_is_not_found(self, repo_stub, field_to_remove):
605 model = VcsSettingsModel(repo=repo_stub.repo_name)
605 model = VcsSettingsModel(repo=repo_stub.repo_name)
606 data = self.FORM_DATA.copy()
606 data = self.FORM_DATA.copy()
607 data.pop(field_to_remove)
607 data.pop(field_to_remove)
608 with pytest.raises(Exception) as exc_info:
608 with pytest.raises(Exception) as exc_info:
609 model.create_or_update_global_hg_settings(data)
609 model.create_or_update_global_hg_settings(data)
610 expected_message = 'The given data does not contain {} key'.format(
610 expected_message = 'The given data does not contain {} key'.format(
611 field_to_remove)
611 field_to_remove)
612 assert exc_info.value.message == expected_message
612 assert exc_info.value.message == expected_message
613
613
614
614
615 class TestCreateOrUpdateGlobalGitSettings(object):
615 class TestCreateOrUpdateGlobalGitSettings(object):
616 FORM_DATA = {
616 FORM_DATA = {
617 'vcs_git_lfs_enabled': False,
617 'vcs_git_lfs_enabled': False,
618 'vcs_git_lfs_store_location': '/example/lfs-store',
618 'vcs_git_lfs_store_location': '/example/lfs-store',
619 }
619 }
620
620
621 def test_creates_repo_hg_settings_when_data_is_correct(self):
621 def test_creates_repo_hg_settings_when_data_is_correct(self):
622 model = VcsSettingsModel()
622 model = VcsSettingsModel()
623 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
623 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
624 model.create_or_update_global_git_settings(self.FORM_DATA)
624 model.create_or_update_global_git_settings(self.FORM_DATA)
625 expected_calls = [
625 expected_calls = [
626 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled',
626 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled',
627 active=False, value=False),
627 active=False, value=False),
628 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location',
628 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location',
629 value='/example/lfs-store'),
629 value='/example/lfs-store'),
630 ]
630 ]
631 assert expected_calls == create_mock.call_args_list
631 assert expected_calls == create_mock.call_args_list
632
632
633
633
634 class TestDeleteRepoSvnPattern(object):
634 class TestDeleteRepoSvnPattern(object):
635 def test_success_when_repo_is_set(self, backend_svn):
635 def test_success_when_repo_is_set(self, backend_svn):
636 repo_name = backend_svn.repo_name
636 repo_name = backend_svn.repo_name
637 model = VcsSettingsModel(repo=repo_name)
637 model = VcsSettingsModel(repo=repo_name)
638 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
638 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
639 with delete_ui_patch as delete_ui_mock:
639 with delete_ui_patch as delete_ui_mock:
640 model.delete_repo_svn_pattern(123)
640 model.delete_repo_svn_pattern(123)
641 delete_ui_mock.assert_called_once_with(123)
641 delete_ui_mock.assert_called_once_with(123)
642
642
643 def test_raises_exception_when_repository_is_not_specified(self):
643 def test_raises_exception_when_repository_is_not_specified(self):
644 model = VcsSettingsModel()
644 model = VcsSettingsModel()
645 with pytest.raises(Exception) as exc_info:
645 with pytest.raises(Exception) as exc_info:
646 model.delete_repo_svn_pattern(123)
646 model.delete_repo_svn_pattern(123)
647 assert exc_info.value.message == 'Repository is not specified'
647 assert exc_info.value.message == 'Repository is not specified'
648
648
649
649
650 class TestDeleteGlobalSvnPattern(object):
650 class TestDeleteGlobalSvnPattern(object):
651 def test_delete_global_svn_pattern_calls_delete_ui(self):
651 def test_delete_global_svn_pattern_calls_delete_ui(self):
652 model = VcsSettingsModel()
652 model = VcsSettingsModel()
653 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
653 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
654 with delete_ui_patch as delete_ui_mock:
654 with delete_ui_patch as delete_ui_mock:
655 model.delete_global_svn_pattern(123)
655 model.delete_global_svn_pattern(123)
656 delete_ui_mock.assert_called_once_with(123)
656 delete_ui_mock.assert_called_once_with(123)
657
657
658
658
659 class TestFilterUiSettings(object):
659 class TestFilterUiSettings(object):
660 def test_settings_are_filtered(self):
660 def test_settings_are_filtered(self):
661 model = VcsSettingsModel()
661 model = VcsSettingsModel()
662 repo_settings = [
662 repo_settings = [
663 UiSetting('extensions', 'largefiles', '', True),
663 UiSetting('extensions', 'largefiles', '', True),
664 UiSetting('phases', 'publish', 'True', True),
664 UiSetting('phases', 'publish', 'True', True),
665 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
665 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
666 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
666 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
667 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
667 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
668 UiSetting(
668 UiSetting(
669 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
669 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
670 'test_branch', True),
670 'test_branch', True),
671 UiSetting(
671 UiSetting(
672 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
672 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
673 'test_tag', True),
673 'test_tag', True),
674 ]
674 ]
675 non_repo_settings = [
675 non_repo_settings = [
676 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
676 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
677 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
677 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
678 UiSetting('hooks', 'test2', 'hook', True),
678 UiSetting('hooks', 'test2', 'hook', True),
679 UiSetting(
679 UiSetting(
680 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
680 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
681 'test_tag', True),
681 'test_tag', True),
682 ]
682 ]
683 settings = repo_settings + non_repo_settings
683 settings = repo_settings + non_repo_settings
684 filtered_settings = model._filter_ui_settings(settings)
684 filtered_settings = model._filter_ui_settings(settings)
685 assert sorted(filtered_settings) == sorted(repo_settings)
685 assert sorted(filtered_settings) == sorted(repo_settings)
686
686
687
687
688 class TestFilterGeneralSettings(object):
688 class TestFilterGeneralSettings(object):
689 def test_settings_are_filtered(self):
689 def test_settings_are_filtered(self):
690 model = VcsSettingsModel()
690 model = VcsSettingsModel()
691 settings = {
691 settings = {
692 'rhodecode_abcde': 'value1',
692 'rhodecode_abcde': 'value1',
693 'rhodecode_vwxyz': 'value2',
693 'rhodecode_vwxyz': 'value2',
694 }
694 }
695 general_settings = {
695 general_settings = {
696 'rhodecode_{}'.format(key): 'value'
696 'rhodecode_{}'.format(key): 'value'
697 for key in VcsSettingsModel.GENERAL_SETTINGS
697 for key in VcsSettingsModel.GENERAL_SETTINGS
698 }
698 }
699 settings.update(general_settings)
699 settings.update(general_settings)
700
700
701 filtered_settings = model._filter_general_settings(general_settings)
701 filtered_settings = model._filter_general_settings(general_settings)
702 assert sorted(filtered_settings) == sorted(general_settings)
702 assert sorted(filtered_settings) == sorted(general_settings)
703
703
704
704
705 class TestGetRepoUiSettings(object):
705 class TestGetRepoUiSettings(object):
706 def test_global_uis_are_returned_when_no_repo_uis_found(
706 def test_global_uis_are_returned_when_no_repo_uis_found(
707 self, repo_stub):
707 self, repo_stub):
708 model = VcsSettingsModel(repo=repo_stub.repo_name)
708 model = VcsSettingsModel(repo=repo_stub.repo_name)
709 result = model.get_repo_ui_settings()
709 result = model.get_repo_ui_settings()
710 svn_sections = (
710 svn_sections = (
711 VcsSettingsModel.SVN_TAG_SECTION,
711 VcsSettingsModel.SVN_TAG_SECTION,
712 VcsSettingsModel.SVN_BRANCH_SECTION)
712 VcsSettingsModel.SVN_BRANCH_SECTION)
713 expected_result = [
713 expected_result = [
714 s for s in model.global_settings.get_ui()
714 s for s in model.global_settings.get_ui()
715 if s.section not in svn_sections]
715 if s.section not in svn_sections]
716 assert sorted(result) == sorted(expected_result)
716 assert sorted(result) == sorted(expected_result)
717
717
718 def test_repo_uis_are_overriding_global_uis(
718 def test_repo_uis_are_overriding_global_uis(
719 self, repo_stub, settings_util):
719 self, repo_stub, settings_util):
720 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
720 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
721 settings_util.create_repo_rhodecode_ui(
721 settings_util.create_repo_rhodecode_ui(
722 repo_stub, section, 'repo', key=key, active=False)
722 repo_stub, section, 'repo', key=key, active=False)
723 model = VcsSettingsModel(repo=repo_stub.repo_name)
723 model = VcsSettingsModel(repo=repo_stub.repo_name)
724 result = model.get_repo_ui_settings()
724 result = model.get_repo_ui_settings()
725 for setting in result:
725 for setting in result:
726 locator = (setting.section, setting.key)
726 locator = (setting.section, setting.key)
727 if locator in VcsSettingsModel.HOOKS_SETTINGS:
727 if locator in VcsSettingsModel.HOOKS_SETTINGS:
728 assert setting.value == 'repo'
728 assert setting.value == 'repo'
729
729
730 assert setting.active is False
730 assert setting.active is False
731
731
732 def test_global_svn_patterns_are_not_in_list(
732 def test_global_svn_patterns_are_not_in_list(
733 self, repo_stub, settings_util):
733 self, repo_stub, settings_util):
734 svn_sections = (
734 svn_sections = (
735 VcsSettingsModel.SVN_TAG_SECTION,
735 VcsSettingsModel.SVN_TAG_SECTION,
736 VcsSettingsModel.SVN_BRANCH_SECTION)
736 VcsSettingsModel.SVN_BRANCH_SECTION)
737 for section in svn_sections:
737 for section in svn_sections:
738 settings_util.create_rhodecode_ui(
738 settings_util.create_rhodecode_ui(
739 section, 'repo', key='deadbeef' + section, active=False)
739 section, 'repo', key='deadbeef' + section, active=False)
740 model = VcsSettingsModel(repo=repo_stub.repo_name)
740 model = VcsSettingsModel(repo=repo_stub.repo_name)
741 result = model.get_repo_ui_settings()
741 result = model.get_repo_ui_settings()
742 for setting in result:
742 for setting in result:
743 assert setting.section not in svn_sections
743 assert setting.section not in svn_sections
744
744
745 def test_repo_uis_filtered_by_section_are_returned(
745 def test_repo_uis_filtered_by_section_are_returned(
746 self, repo_stub, settings_util):
746 self, repo_stub, settings_util):
747 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
747 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
748 settings_util.create_repo_rhodecode_ui(
748 settings_util.create_repo_rhodecode_ui(
749 repo_stub, section, 'repo', key=key, active=False)
749 repo_stub, section, 'repo', key=key, active=False)
750 model = VcsSettingsModel(repo=repo_stub.repo_name)
750 model = VcsSettingsModel(repo=repo_stub.repo_name)
751 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
751 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
752 result = model.get_repo_ui_settings(section=section)
752 result = model.get_repo_ui_settings(section=section)
753 for setting in result:
753 for setting in result:
754 assert setting.section == section
754 assert setting.section == section
755
755
756 def test_repo_uis_filtered_by_key_are_returned(
756 def test_repo_uis_filtered_by_key_are_returned(
757 self, repo_stub, settings_util):
757 self, repo_stub, settings_util):
758 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
758 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
759 settings_util.create_repo_rhodecode_ui(
759 settings_util.create_repo_rhodecode_ui(
760 repo_stub, section, 'repo', key=key, active=False)
760 repo_stub, section, 'repo', key=key, active=False)
761 model = VcsSettingsModel(repo=repo_stub.repo_name)
761 model = VcsSettingsModel(repo=repo_stub.repo_name)
762 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
762 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
763 result = model.get_repo_ui_settings(key=key)
763 result = model.get_repo_ui_settings(key=key)
764 for setting in result:
764 for setting in result:
765 assert setting.key == key
765 assert setting.key == key
766
766
767 def test_raises_exception_when_repository_is_not_specified(self):
767 def test_raises_exception_when_repository_is_not_specified(self):
768 model = VcsSettingsModel()
768 model = VcsSettingsModel()
769 with pytest.raises(Exception) as exc_info:
769 with pytest.raises(Exception) as exc_info:
770 model.get_repo_ui_settings()
770 model.get_repo_ui_settings()
771 assert exc_info.value.message == 'Repository is not specified'
771 assert exc_info.value.message == 'Repository is not specified'
772
772
773
773
774 class TestGetRepoGeneralSettings(object):
774 class TestGetRepoGeneralSettings(object):
775 def test_global_settings_are_returned_when_no_repo_settings_found(
775 def test_global_settings_are_returned_when_no_repo_settings_found(
776 self, repo_stub):
776 self, repo_stub):
777 model = VcsSettingsModel(repo=repo_stub.repo_name)
777 model = VcsSettingsModel(repo=repo_stub.repo_name)
778 result = model.get_repo_general_settings()
778 result = model.get_repo_general_settings()
779 expected_result = model.global_settings.get_all_settings()
779 expected_result = model.global_settings.get_all_settings()
780 assert sorted(result) == sorted(expected_result)
780 assert sorted(result) == sorted(expected_result)
781
781
782 def test_repo_uis_are_overriding_global_uis(
782 def test_repo_uis_are_overriding_global_uis(
783 self, repo_stub, settings_util):
783 self, repo_stub, settings_util):
784 for key in VcsSettingsModel.GENERAL_SETTINGS:
784 for key in VcsSettingsModel.GENERAL_SETTINGS:
785 settings_util.create_repo_rhodecode_setting(
785 settings_util.create_repo_rhodecode_setting(
786 repo_stub, key, 'abcde', type_='unicode')
786 repo_stub, key, 'abcde', type_='unicode')
787 model = VcsSettingsModel(repo=repo_stub.repo_name)
787 model = VcsSettingsModel(repo=repo_stub.repo_name)
788 result = model.get_repo_ui_settings()
788 result = model.get_repo_ui_settings()
789 for key in result:
789 for key in result:
790 if key in VcsSettingsModel.GENERAL_SETTINGS:
790 if key in VcsSettingsModel.GENERAL_SETTINGS:
791 assert result[key] == 'abcde'
791 assert result[key] == 'abcde'
792
792
793 def test_raises_exception_when_repository_is_not_specified(self):
793 def test_raises_exception_when_repository_is_not_specified(self):
794 model = VcsSettingsModel()
794 model = VcsSettingsModel()
795 with pytest.raises(Exception) as exc_info:
795 with pytest.raises(Exception) as exc_info:
796 model.get_repo_general_settings()
796 model.get_repo_general_settings()
797 assert exc_info.value.message == 'Repository is not specified'
797 assert exc_info.value.message == 'Repository is not specified'
798
798
799
799
800 class TestGetGlobalGeneralSettings(object):
800 class TestGetGlobalGeneralSettings(object):
801 def test_global_settings_are_returned(self, repo_stub):
801 def test_global_settings_are_returned(self, repo_stub):
802 model = VcsSettingsModel()
802 model = VcsSettingsModel()
803 result = model.get_global_general_settings()
803 result = model.get_global_general_settings()
804 expected_result = model.global_settings.get_all_settings()
804 expected_result = model.global_settings.get_all_settings()
805 assert sorted(result) == sorted(expected_result)
805 assert sorted(result) == sorted(expected_result)
806
806
807 def test_repo_uis_are_not_overriding_global_uis(
807 def test_repo_uis_are_not_overriding_global_uis(
808 self, repo_stub, settings_util):
808 self, repo_stub, settings_util):
809 for key in VcsSettingsModel.GENERAL_SETTINGS:
809 for key in VcsSettingsModel.GENERAL_SETTINGS:
810 settings_util.create_repo_rhodecode_setting(
810 settings_util.create_repo_rhodecode_setting(
811 repo_stub, key, 'abcde', type_='unicode')
811 repo_stub, key, 'abcde', type_='unicode')
812 model = VcsSettingsModel(repo=repo_stub.repo_name)
812 model = VcsSettingsModel(repo=repo_stub.repo_name)
813 result = model.get_global_general_settings()
813 result = model.get_global_general_settings()
814 expected_result = model.global_settings.get_all_settings()
814 expected_result = model.global_settings.get_all_settings()
815 assert sorted(result) == sorted(expected_result)
815 assert sorted(result) == sorted(expected_result)
816
816
817
817
818 class TestGetGlobalUiSettings(object):
818 class TestGetGlobalUiSettings(object):
819 def test_global_uis_are_returned(self, repo_stub):
819 def test_global_uis_are_returned(self, repo_stub):
820 model = VcsSettingsModel()
820 model = VcsSettingsModel()
821 result = model.get_global_ui_settings()
821 result = model.get_global_ui_settings()
822 expected_result = model.global_settings.get_ui()
822 expected_result = model.global_settings.get_ui()
823 assert sorted(result) == sorted(expected_result)
823 assert sorted(result) == sorted(expected_result)
824
824
825 def test_repo_uis_are_not_overriding_global_uis(
825 def test_repo_uis_are_not_overriding_global_uis(
826 self, repo_stub, settings_util):
826 self, repo_stub, settings_util):
827 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
827 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
828 settings_util.create_repo_rhodecode_ui(
828 settings_util.create_repo_rhodecode_ui(
829 repo_stub, section, 'repo', key=key, active=False)
829 repo_stub, section, 'repo', key=key, active=False)
830 model = VcsSettingsModel(repo=repo_stub.repo_name)
830 model = VcsSettingsModel(repo=repo_stub.repo_name)
831 result = model.get_global_ui_settings()
831 result = model.get_global_ui_settings()
832 expected_result = model.global_settings.get_ui()
832 expected_result = model.global_settings.get_ui()
833 assert sorted(result) == sorted(expected_result)
833 assert sorted(result) == sorted(expected_result)
834
834
835 def test_ui_settings_filtered_by_section(
835 def test_ui_settings_filtered_by_section(
836 self, repo_stub, settings_util):
836 self, repo_stub, settings_util):
837 model = VcsSettingsModel(repo=repo_stub.repo_name)
837 model = VcsSettingsModel(repo=repo_stub.repo_name)
838 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
838 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
839 result = model.get_global_ui_settings(section=section)
839 result = model.get_global_ui_settings(section=section)
840 expected_result = model.global_settings.get_ui(section=section)
840 expected_result = model.global_settings.get_ui(section=section)
841 assert sorted(result) == sorted(expected_result)
841 assert sorted(result) == sorted(expected_result)
842
842
843 def test_ui_settings_filtered_by_key(
843 def test_ui_settings_filtered_by_key(
844 self, repo_stub, settings_util):
844 self, repo_stub, settings_util):
845 model = VcsSettingsModel(repo=repo_stub.repo_name)
845 model = VcsSettingsModel(repo=repo_stub.repo_name)
846 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
846 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
847 result = model.get_global_ui_settings(key=key)
847 result = model.get_global_ui_settings(key=key)
848 expected_result = model.global_settings.get_ui(key=key)
848 expected_result = model.global_settings.get_ui(key=key)
849 assert sorted(result) == sorted(expected_result)
849 assert sorted(result) == sorted(expected_result)
850
850
851
851
852 class TestGetGeneralSettings(object):
852 class TestGetGeneralSettings(object):
853 def test_global_settings_are_returned_when_inherited_is_true(
853 def test_global_settings_are_returned_when_inherited_is_true(
854 self, repo_stub, settings_util):
854 self, repo_stub, settings_util):
855 model = VcsSettingsModel(repo=repo_stub.repo_name)
855 model = VcsSettingsModel(repo=repo_stub.repo_name)
856 model.inherit_global_settings = True
856 model.inherit_global_settings = True
857 for key in VcsSettingsModel.GENERAL_SETTINGS:
857 for key in VcsSettingsModel.GENERAL_SETTINGS:
858 settings_util.create_repo_rhodecode_setting(
858 settings_util.create_repo_rhodecode_setting(
859 repo_stub, key, 'abcde', type_='unicode')
859 repo_stub, key, 'abcde', type_='unicode')
860 result = model.get_general_settings()
860 result = model.get_general_settings()
861 expected_result = model.get_global_general_settings()
861 expected_result = model.get_global_general_settings()
862 assert sorted(result) == sorted(expected_result)
862 assert sorted(result) == sorted(expected_result)
863
863
864 def test_repo_settings_are_returned_when_inherited_is_false(
864 def test_repo_settings_are_returned_when_inherited_is_false(
865 self, repo_stub, settings_util):
865 self, repo_stub, settings_util):
866 model = VcsSettingsModel(repo=repo_stub.repo_name)
866 model = VcsSettingsModel(repo=repo_stub.repo_name)
867 model.inherit_global_settings = False
867 model.inherit_global_settings = False
868 for key in VcsSettingsModel.GENERAL_SETTINGS:
868 for key in VcsSettingsModel.GENERAL_SETTINGS:
869 settings_util.create_repo_rhodecode_setting(
869 settings_util.create_repo_rhodecode_setting(
870 repo_stub, key, 'abcde', type_='unicode')
870 repo_stub, key, 'abcde', type_='unicode')
871 result = model.get_general_settings()
871 result = model.get_general_settings()
872 expected_result = model.get_repo_general_settings()
872 expected_result = model.get_repo_general_settings()
873 assert sorted(result) == sorted(expected_result)
873 assert sorted(result) == sorted(expected_result)
874
874
875 def test_global_settings_are_returned_when_no_repository_specified(self):
875 def test_global_settings_are_returned_when_no_repository_specified(self):
876 model = VcsSettingsModel()
876 model = VcsSettingsModel()
877 result = model.get_general_settings()
877 result = model.get_general_settings()
878 expected_result = model.get_global_general_settings()
878 expected_result = model.get_global_general_settings()
879 assert sorted(result) == sorted(expected_result)
879 assert sorted(result) == sorted(expected_result)
880
880
881
881
882 class TestGetUiSettings(object):
882 class TestGetUiSettings(object):
883 def test_global_settings_are_returned_when_inherited_is_true(
883 def test_global_settings_are_returned_when_inherited_is_true(
884 self, repo_stub, settings_util):
884 self, repo_stub, settings_util):
885 model = VcsSettingsModel(repo=repo_stub.repo_name)
885 model = VcsSettingsModel(repo=repo_stub.repo_name)
886 model.inherit_global_settings = True
886 model.inherit_global_settings = True
887 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
887 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
888 settings_util.create_repo_rhodecode_ui(
888 settings_util.create_repo_rhodecode_ui(
889 repo_stub, section, 'repo', key=key, active=True)
889 repo_stub, section, 'repo', key=key, active=True)
890 result = model.get_ui_settings()
890 result = model.get_ui_settings()
891 expected_result = model.get_global_ui_settings()
891 expected_result = model.get_global_ui_settings()
892 assert sorted(result) == sorted(expected_result)
892 assert sorted(result) == sorted(expected_result)
893
893
894 def test_repo_settings_are_returned_when_inherited_is_false(
894 def test_repo_settings_are_returned_when_inherited_is_false(
895 self, repo_stub, settings_util):
895 self, repo_stub, settings_util):
896 model = VcsSettingsModel(repo=repo_stub.repo_name)
896 model = VcsSettingsModel(repo=repo_stub.repo_name)
897 model.inherit_global_settings = False
897 model.inherit_global_settings = False
898 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
898 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
899 settings_util.create_repo_rhodecode_ui(
899 settings_util.create_repo_rhodecode_ui(
900 repo_stub, section, 'repo', key=key, active=True)
900 repo_stub, section, 'repo', key=key, active=True)
901 result = model.get_ui_settings()
901 result = model.get_ui_settings()
902 expected_result = model.get_repo_ui_settings()
902 expected_result = model.get_repo_ui_settings()
903 assert sorted(result) == sorted(expected_result)
903 assert sorted(result) == sorted(expected_result)
904
904
905 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
905 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
906 model = VcsSettingsModel(repo=repo_stub.repo_name)
906 model = VcsSettingsModel(repo=repo_stub.repo_name)
907 model.inherit_global_settings = False
907 model.inherit_global_settings = False
908 args = ('section', 'key')
908 args = ('section', 'key')
909 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
909 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
910 model.get_ui_settings(*args)
910 model.get_ui_settings(*args)
911 settings_mock.assert_called_once_with(*args)
911 settings_mock.assert_called_once_with(*args)
912
912
913 def test_global_settings_filtered_by_section_and_key(self):
913 def test_global_settings_filtered_by_section_and_key(self):
914 model = VcsSettingsModel()
914 model = VcsSettingsModel()
915 args = ('section', 'key')
915 args = ('section', 'key')
916 with mock.patch.object(model, 'get_global_ui_settings') as (
916 with mock.patch.object(model, 'get_global_ui_settings') as (
917 settings_mock):
917 settings_mock):
918 model.get_ui_settings(*args)
918 model.get_ui_settings(*args)
919 settings_mock.assert_called_once_with(*args)
919 settings_mock.assert_called_once_with(*args)
920
920
921 def test_global_settings_are_returned_when_no_repository_specified(self):
921 def test_global_settings_are_returned_when_no_repository_specified(self):
922 model = VcsSettingsModel()
922 model = VcsSettingsModel()
923 result = model.get_ui_settings()
923 result = model.get_ui_settings()
924 expected_result = model.get_global_ui_settings()
924 expected_result = model.get_global_ui_settings()
925 assert sorted(result) == sorted(expected_result)
925 assert sorted(result) == sorted(expected_result)
926
926
927
927
928 class TestGetSvnPatterns(object):
928 class TestGetSvnPatterns(object):
929 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
929 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
930 model = VcsSettingsModel(repo=repo_stub.repo_name)
930 model = VcsSettingsModel(repo=repo_stub.repo_name)
931 args = ('section', )
931 args = ('section', )
932 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
932 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
933 model.get_svn_patterns(*args)
933 model.get_svn_patterns(*args)
934 settings_mock.assert_called_once_with(*args)
934 settings_mock.assert_called_once_with(*args)
935
935
936 def test_global_settings_filtered_by_section_and_key(self):
936 def test_global_settings_filtered_by_section_and_key(self):
937 model = VcsSettingsModel()
937 model = VcsSettingsModel()
938 args = ('section', )
938 args = ('section', )
939 with mock.patch.object(model, 'get_global_ui_settings') as (
939 with mock.patch.object(model, 'get_global_ui_settings') as (
940 settings_mock):
940 settings_mock):
941 model.get_svn_patterns(*args)
941 model.get_svn_patterns(*args)
942 settings_mock.assert_called_once_with(*args)
942 settings_mock.assert_called_once_with(*args)
943
943
944
944
945 class TestGetReposLocation(object):
945 class TestGetReposLocation(object):
946 def test_returns_repos_location(self, repo_stub):
946 def test_returns_repos_location(self, repo_stub):
947 model = VcsSettingsModel()
947 model = VcsSettingsModel()
948
948
949 result_mock = mock.Mock()
949 result_mock = mock.Mock()
950 result_mock.ui_value = '/tmp'
950 result_mock.ui_value = '/tmp'
951
951
952 with mock.patch.object(model, 'global_settings') as settings_mock:
952 with mock.patch.object(model, 'global_settings') as settings_mock:
953 settings_mock.get_ui_by_key.return_value = result_mock
953 settings_mock.get_ui_by_key.return_value = result_mock
954 result = model.get_repos_location()
954 result = model.get_repos_location()
955
955
956 settings_mock.get_ui_by_key.assert_called_once_with('/')
956 settings_mock.get_ui_by_key.assert_called_once_with('/')
957 assert result == '/tmp'
957 assert result == '/tmp'
958
958
959
959
960 class TestCreateOrUpdateRepoSettings(object):
960 class TestCreateOrUpdateRepoSettings(object):
961 FORM_DATA = {
961 FORM_DATA = {
962 'inherit_global_settings': False,
962 'inherit_global_settings': False,
963 'hooks_changegroup_repo_size': False,
963 'hooks_changegroup_repo_size': False,
964 'hooks_changegroup_push_logger': False,
964 'hooks_changegroup_push_logger': False,
965 'hooks_outgoing_pull_logger': False,
965 'hooks_outgoing_pull_logger': False,
966 'extensions_largefiles': False,
966 'extensions_largefiles': False,
967 'extensions_evolve': False,
967 'extensions_evolve': False,
968 'largefiles_usercache': '/example/largefiles-store',
968 'largefiles_usercache': '/example/largefiles-store',
969 'vcs_git_lfs_enabled': False,
969 'vcs_git_lfs_enabled': False,
970 'vcs_git_lfs_store_location': '/',
970 'vcs_git_lfs_store_location': '/',
971 'phases_publish': 'False',
971 'phases_publish': 'False',
972 'rhodecode_pr_merge_enabled': False,
972 'rhodecode_pr_merge_enabled': False,
973 'rhodecode_use_outdated_comments': False,
973 'rhodecode_use_outdated_comments': False,
974 'new_svn_branch': '',
974 'new_svn_branch': '',
975 'new_svn_tag': ''
975 'new_svn_tag': ''
976 }
976 }
977
977
978 def test_get_raises_exception_when_repository_not_specified(self):
978 def test_get_raises_exception_when_repository_not_specified(self):
979 model = VcsSettingsModel()
979 model = VcsSettingsModel()
980 with pytest.raises(Exception) as exc_info:
980 with pytest.raises(Exception) as exc_info:
981 model.create_or_update_repo_settings(data=self.FORM_DATA)
981 model.create_or_update_repo_settings(data=self.FORM_DATA)
982 assert exc_info.value.message == 'Repository is not specified'
982 assert exc_info.value.message == 'Repository is not specified'
983
983
984 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
984 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
985 repo = backend_svn.create_repo()
985 repo = backend_svn.create_repo()
986 model = VcsSettingsModel(repo=repo)
986 model = VcsSettingsModel(repo=repo)
987 with self._patch_model(model) as mocks:
987 with self._patch_model(model) as mocks:
988 model.create_or_update_repo_settings(
988 model.create_or_update_repo_settings(
989 data=self.FORM_DATA, inherit_global_settings=False)
989 data=self.FORM_DATA, inherit_global_settings=False)
990 mocks['create_repo_svn_settings'].assert_called_once_with(
990 mocks['create_repo_svn_settings'].assert_called_once_with(
991 self.FORM_DATA)
991 self.FORM_DATA)
992 non_called_methods = (
992 non_called_methods = (
993 'create_or_update_repo_hook_settings',
993 'create_or_update_repo_hook_settings',
994 'create_or_update_repo_pr_settings',
994 'create_or_update_repo_pr_settings',
995 'create_or_update_repo_hg_settings')
995 'create_or_update_repo_hg_settings')
996 for method in non_called_methods:
996 for method in non_called_methods:
997 assert mocks[method].call_count == 0
997 assert mocks[method].call_count == 0
998
998
999 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
999 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
1000 repo = backend_hg.create_repo()
1000 repo = backend_hg.create_repo()
1001 model = VcsSettingsModel(repo=repo)
1001 model = VcsSettingsModel(repo=repo)
1002 with self._patch_model(model) as mocks:
1002 with self._patch_model(model) as mocks:
1003 model.create_or_update_repo_settings(
1003 model.create_or_update_repo_settings(
1004 data=self.FORM_DATA, inherit_global_settings=False)
1004 data=self.FORM_DATA, inherit_global_settings=False)
1005
1005
1006 assert mocks['create_repo_svn_settings'].call_count == 0
1006 assert mocks['create_repo_svn_settings'].call_count == 0
1007 called_methods = (
1007 called_methods = (
1008 'create_or_update_repo_hook_settings',
1008 'create_or_update_repo_hook_settings',
1009 'create_or_update_repo_pr_settings',
1009 'create_or_update_repo_pr_settings',
1010 'create_or_update_repo_hg_settings')
1010 'create_or_update_repo_hg_settings')
1011 for method in called_methods:
1011 for method in called_methods:
1012 mocks[method].assert_called_once_with(self.FORM_DATA)
1012 mocks[method].assert_called_once_with(self.FORM_DATA)
1013
1013
1014 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1014 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1015 self, backend_git):
1015 self, backend_git):
1016 repo = backend_git.create_repo()
1016 repo = backend_git.create_repo()
1017 model = VcsSettingsModel(repo=repo)
1017 model = VcsSettingsModel(repo=repo)
1018 with self._patch_model(model) as mocks:
1018 with self._patch_model(model) as mocks:
1019 model.create_or_update_repo_settings(
1019 model.create_or_update_repo_settings(
1020 data=self.FORM_DATA, inherit_global_settings=False)
1020 data=self.FORM_DATA, inherit_global_settings=False)
1021
1021
1022 assert mocks['create_repo_svn_settings'].call_count == 0
1022 assert mocks['create_repo_svn_settings'].call_count == 0
1023 called_methods = (
1023 called_methods = (
1024 'create_or_update_repo_hook_settings',
1024 'create_or_update_repo_hook_settings',
1025 'create_or_update_repo_pr_settings')
1025 'create_or_update_repo_pr_settings')
1026 non_called_methods = (
1026 non_called_methods = (
1027 'create_repo_svn_settings',
1027 'create_repo_svn_settings',
1028 'create_or_update_repo_hg_settings'
1028 'create_or_update_repo_hg_settings'
1029 )
1029 )
1030 for method in called_methods:
1030 for method in called_methods:
1031 mocks[method].assert_called_once_with(self.FORM_DATA)
1031 mocks[method].assert_called_once_with(self.FORM_DATA)
1032 for method in non_called_methods:
1032 for method in non_called_methods:
1033 assert mocks[method].call_count == 0
1033 assert mocks[method].call_count == 0
1034
1034
1035 def test_no_methods_are_called_when_settings_are_inherited(
1035 def test_no_methods_are_called_when_settings_are_inherited(
1036 self, backend):
1036 self, backend):
1037 repo = backend.create_repo()
1037 repo = backend.create_repo()
1038 model = VcsSettingsModel(repo=repo)
1038 model = VcsSettingsModel(repo=repo)
1039 with self._patch_model(model) as mocks:
1039 with self._patch_model(model) as mocks:
1040 model.create_or_update_repo_settings(
1040 model.create_or_update_repo_settings(
1041 data=self.FORM_DATA, inherit_global_settings=True)
1041 data=self.FORM_DATA, inherit_global_settings=True)
1042 for method_name in mocks:
1042 for method_name in mocks:
1043 assert mocks[method_name].call_count == 0
1043 assert mocks[method_name].call_count == 0
1044
1044
1045 def test_cache_is_marked_for_invalidation(self, repo_stub):
1045 def test_cache_is_marked_for_invalidation(self, repo_stub):
1046 model = VcsSettingsModel(repo=repo_stub)
1046 model = VcsSettingsModel(repo=repo_stub)
1047 invalidation_patcher = mock.patch(
1047 invalidation_patcher = mock.patch(
1048 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
1048 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
1049 with invalidation_patcher as invalidation_mock:
1049 with invalidation_patcher as invalidation_mock:
1050 model.create_or_update_repo_settings(
1050 model.create_or_update_repo_settings(
1051 data=self.FORM_DATA, inherit_global_settings=True)
1051 data=self.FORM_DATA, inherit_global_settings=True)
1052 invalidation_mock.assert_called_once_with(
1052 invalidation_mock.assert_called_once_with(
1053 repo_stub.repo_name, delete=True)
1053 repo_stub.repo_name, delete=True)
1054
1054
1055 def test_inherit_flag_is_saved(self, repo_stub):
1055 def test_inherit_flag_is_saved(self, repo_stub):
1056 model = VcsSettingsModel(repo=repo_stub)
1056 model = VcsSettingsModel(repo=repo_stub)
1057 model.inherit_global_settings = True
1057 model.inherit_global_settings = True
1058 with self._patch_model(model):
1058 with self._patch_model(model):
1059 model.create_or_update_repo_settings(
1059 model.create_or_update_repo_settings(
1060 data=self.FORM_DATA, inherit_global_settings=False)
1060 data=self.FORM_DATA, inherit_global_settings=False)
1061 assert model.inherit_global_settings is False
1061 assert model.inherit_global_settings is False
1062
1062
1063 def _patch_model(self, model):
1063 def _patch_model(self, model):
1064 return mock.patch.multiple(
1064 return mock.patch.multiple(
1065 model,
1065 model,
1066 create_repo_svn_settings=mock.DEFAULT,
1066 create_repo_svn_settings=mock.DEFAULT,
1067 create_or_update_repo_hook_settings=mock.DEFAULT,
1067 create_or_update_repo_hook_settings=mock.DEFAULT,
1068 create_or_update_repo_pr_settings=mock.DEFAULT,
1068 create_or_update_repo_pr_settings=mock.DEFAULT,
1069 create_or_update_repo_hg_settings=mock.DEFAULT)
1069 create_or_update_repo_hg_settings=mock.DEFAULT)
This diff has been collapsed as it changes many lines, (563 lines changed) Show them Hide them
@@ -1,563 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2013-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
22 """
23 Repositories controller for RhodeCode
24 """
25
26 import logging
27 import traceback
28
29 import formencode
30 from formencode import htmlfill
31 from pylons import request, tmpl_context as c, url
32 from pylons.controllers.util import redirect
33 from pylons.i18n.translation import _
34 from webob.exc import HTTPForbidden, HTTPBadRequest
35
36 from pyramid.httpexceptions import HTTPFound
37 import rhodecode
38 from rhodecode.lib import auth, helpers as h
39 from rhodecode.lib.auth import (
40 LoginRequired, HasPermissionAllDecorator,
41 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
42 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
43 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.ext_json import json
45 from rhodecode.lib.utils import repo_name_slug, jsonify
46 from rhodecode.lib.utils2 import safe_int, str2bool
47 from rhodecode.model.db import (Repository, RepoGroup, RepositoryField)
48 from rhodecode.model.forms import (
49 RepoForm, RepoFieldForm, RepoVcsSettingsForm, IssueTrackerPatternsForm)
50 from rhodecode.model.meta import Session
51 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
53 from rhodecode.model.settings import (
54 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
55 SettingNotFound)
56
57 log = logging.getLogger(__name__)
58
59
60 class ReposController(BaseRepoController):
61 """
62 REST Controller styled on the Atom Publishing Protocol"""
63 # To properly map this controller, ensure your config/routing.py
64 # file has a resource setup:
65 # map.resource('repo', 'repos')
66
67 @LoginRequired()
68 def __before__(self):
69 super(ReposController, self).__before__()
70
71 def _load_repo(self, repo_name):
72 repo_obj = Repository.get_by_repo_name(repo_name)
73
74 if repo_obj is None:
75 h.not_mapped_error(repo_name)
76 return redirect(url('repos'))
77
78 return repo_obj
79
80 def __load_defaults(self, repo=None):
81 acl_groups = RepoGroupList(RepoGroup.query().all(),
82 perm_set=['group.write', 'group.admin'])
83 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
84 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
85
86 # in case someone no longer have a group.write access to a repository
87 # pre fill the list with this entry, we don't care if this is the same
88 # but it will allow saving repo data properly.
89
90 repo_group = None
91 if repo:
92 repo_group = repo.group
93 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
94 c.repo_groups_choices.append(unicode(repo_group.group_id))
95 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
96
97 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
98 c.landing_revs_choices = choices
99
100 def __load_data(self, repo_name=None):
101 """
102 Load defaults settings for edit, and update
103
104 :param repo_name:
105 """
106 c.repo_info = self._load_repo(repo_name)
107 self.__load_defaults(c.repo_info)
108
109 # override defaults for exact repo info here git/hg etc
110 if not c.repository_requirements_missing:
111 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
112 c.repo_info)
113 c.landing_revs_choices = choices
114 defaults = RepoModel()._get_defaults(repo_name)
115
116 return defaults
117
118 def _log_creation_exception(self, e, repo_name):
119 reason = None
120 if len(e.args) == 2:
121 reason = e.args[1]
122
123 if reason == 'INVALID_CERTIFICATE':
124 log.exception(
125 'Exception creating a repository: invalid certificate')
126 msg = (_('Error creating repository %s: invalid certificate')
127 % repo_name)
128 else:
129 log.exception("Exception creating a repository")
130 msg = (_('Error creating repository %s')
131 % repo_name)
132
133 return msg
134
135 @NotAnonymous()
136 def index(self, format='html'):
137 """GET /repos: All items in the collection"""
138 # url('repos')
139
140 repo_list = Repository.get_all_repos()
141 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
142 repos_data = RepoModel().get_repos_as_dict(
143 repo_list=c.repo_list, admin=True, super_user_actions=True)
144 # json used to render the grid
145 c.data = json.dumps(repos_data)
146
147 return render('admin/repos/repos.mako')
148
149 # perms check inside
150 @NotAnonymous()
151 @auth.CSRFRequired()
152 def create(self):
153 """
154 POST /repos: Create a new item"""
155 # url('repos')
156
157 self.__load_defaults()
158 form_result = {}
159 task_id = None
160 c.personal_repo_group = c.rhodecode_user.personal_repo_group
161 try:
162 # CanWriteToGroup validators checks permissions of this POST
163 form_result = RepoForm(repo_groups=c.repo_groups_choices,
164 landing_revs=c.landing_revs_choices)()\
165 .to_python(dict(request.POST))
166
167 # create is done sometimes async on celery, db transaction
168 # management is handled there.
169 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
170 from celery.result import BaseAsyncResult
171 if isinstance(task, BaseAsyncResult):
172 task_id = task.task_id
173 except formencode.Invalid as errors:
174 return htmlfill.render(
175 render('admin/repos/repo_add.mako'),
176 defaults=errors.value,
177 errors=errors.error_dict or {},
178 prefix_error=False,
179 encoding="UTF-8",
180 force_defaults=False)
181
182 except Exception as e:
183 msg = self._log_creation_exception(e, form_result.get('repo_name'))
184 h.flash(msg, category='error')
185 return redirect(h.route_path('home'))
186
187 raise HTTPFound(
188 h.route_path('repo_creating',
189 repo_name=form_result['repo_name_full'],
190 _query=dict(task_id=task_id)))
191
192 # perms check inside
193 @NotAnonymous()
194 def create_repository(self):
195 """GET /_admin/create_repository: Form to create a new item"""
196 new_repo = request.GET.get('repo', '')
197 parent_group = safe_int(request.GET.get('parent_group'))
198 _gr = RepoGroup.get(parent_group)
199
200 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
201 # you're not super admin nor have global create permissions,
202 # but maybe you have at least write permission to a parent group ?
203
204 gr_name = _gr.group_name if _gr else None
205 # create repositories with write permission on group is set to true
206 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
207 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
208 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
209 if not (group_admin or (group_write and create_on_write)):
210 raise HTTPForbidden
211
212 acl_groups = RepoGroupList(RepoGroup.query().all(),
213 perm_set=['group.write', 'group.admin'])
214 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
215 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
216 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
217 c.personal_repo_group = c.rhodecode_user.personal_repo_group
218 c.new_repo = repo_name_slug(new_repo)
219
220 # apply the defaults from defaults page
221 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
222 # set checkbox to autochecked
223 defaults['repo_copy_permissions'] = True
224
225 parent_group_choice = '-1'
226 if not c.rhodecode_user.is_admin and c.rhodecode_user.personal_repo_group:
227 parent_group_choice = c.rhodecode_user.personal_repo_group
228
229 if parent_group and _gr:
230 if parent_group in [x[0] for x in c.repo_groups]:
231 parent_group_choice = unicode(parent_group)
232
233 defaults.update({'repo_group': parent_group_choice})
234
235 return htmlfill.render(
236 render('admin/repos/repo_add.mako'),
237 defaults=defaults,
238 errors={},
239 prefix_error=False,
240 encoding="UTF-8",
241 force_defaults=False
242 )
243
244 @HasPermissionAllDecorator('hg.admin')
245 def show(self, repo_name, format='html'):
246 """GET /repos/repo_name: Show a specific item"""
247 # url('repo', repo_name=ID)
248
249 @HasRepoPermissionAllDecorator('repository.admin')
250 def edit_fields(self, repo_name):
251 """GET /repo_name/settings: Form to edit an existing item"""
252 c.repo_info = self._load_repo(repo_name)
253 c.repo_fields = RepositoryField.query()\
254 .filter(RepositoryField.repository == c.repo_info).all()
255 c.active = 'fields'
256 if request.POST:
257
258 return redirect(url('repo_edit_fields'))
259 return render('admin/repos/repo_edit.mako')
260
261 @HasRepoPermissionAllDecorator('repository.admin')
262 @auth.CSRFRequired()
263 def create_repo_field(self, repo_name):
264 try:
265 form_result = RepoFieldForm()().to_python(dict(request.POST))
266 RepoModel().add_repo_field(
267 repo_name, form_result['new_field_key'],
268 field_type=form_result['new_field_type'],
269 field_value=form_result['new_field_value'],
270 field_label=form_result['new_field_label'],
271 field_desc=form_result['new_field_desc'])
272
273 Session().commit()
274 except Exception as e:
275 log.exception("Exception creating field")
276 msg = _('An error occurred during creation of field')
277 if isinstance(e, formencode.Invalid):
278 msg += ". " + e.msg
279 h.flash(msg, category='error')
280 return redirect(url('edit_repo_fields', repo_name=repo_name))
281
282 @HasRepoPermissionAllDecorator('repository.admin')
283 @auth.CSRFRequired()
284 def delete_repo_field(self, repo_name, field_id):
285 field = RepositoryField.get_or_404(field_id)
286 try:
287 RepoModel().delete_repo_field(repo_name, field.field_key)
288 Session().commit()
289 except Exception as e:
290 log.exception("Exception during removal of field")
291 msg = _('An error occurred during removal of field')
292 h.flash(msg, category='error')
293 return redirect(url('edit_repo_fields', repo_name=repo_name))
294
295 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
296 @auth.CSRFRequired()
297 def toggle_locking(self, repo_name):
298 """
299 Toggle locking of repository by simple GET call to url
300
301 :param repo_name:
302 """
303
304 try:
305 repo = Repository.get_by_repo_name(repo_name)
306
307 if repo.enable_locking:
308 if repo.locked[0]:
309 Repository.unlock(repo)
310 action = _('Unlocked')
311 else:
312 Repository.lock(repo, c.rhodecode_user.user_id,
313 lock_reason=Repository.LOCK_WEB)
314 action = _('Locked')
315
316 h.flash(_('Repository has been %s') % action,
317 category='success')
318 except Exception:
319 log.exception("Exception during unlocking")
320 h.flash(_('An error occurred during unlocking'),
321 category='error')
322 return redirect(h.route_path('repo_summary', repo_name=repo_name))
323
324 @HasRepoPermissionAllDecorator('repository.admin')
325 @auth.CSRFRequired()
326 def edit_remote(self, repo_name):
327 """PUT /{repo_name}/settings/remote: edit the repo remote."""
328 try:
329 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
330 h.flash(_('Pulled from remote location'), category='success')
331 except Exception:
332 log.exception("Exception during pull from remote")
333 h.flash(_('An error occurred during pull from remote location'),
334 category='error')
335 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
336
337 @HasRepoPermissionAllDecorator('repository.admin')
338 def edit_remote_form(self, repo_name):
339 """GET /repo_name/settings: Form to edit an existing item"""
340 c.repo_info = self._load_repo(repo_name)
341 c.active = 'remote'
342
343 return render('admin/repos/repo_edit.mako')
344
345 @HasRepoPermissionAllDecorator('repository.admin')
346 @auth.CSRFRequired()
347 def edit_statistics(self, repo_name):
348 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
349 try:
350 RepoModel().delete_stats(repo_name)
351 Session().commit()
352 except Exception as e:
353 log.error(traceback.format_exc())
354 h.flash(_('An error occurred during deletion of repository stats'),
355 category='error')
356 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
357
358 @HasRepoPermissionAllDecorator('repository.admin')
359 def edit_statistics_form(self, repo_name):
360 """GET /repo_name/settings: Form to edit an existing item"""
361 c.repo_info = self._load_repo(repo_name)
362 repo = c.repo_info.scm_instance()
363
364 if c.repo_info.stats:
365 # this is on what revision we ended up so we add +1 for count
366 last_rev = c.repo_info.stats.stat_on_revision + 1
367 else:
368 last_rev = 0
369 c.stats_revision = last_rev
370
371 c.repo_last_rev = repo.count()
372
373 if last_rev == 0 or c.repo_last_rev == 0:
374 c.stats_percentage = 0
375 else:
376 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
377
378 c.active = 'statistics'
379
380 return render('admin/repos/repo_edit.mako')
381
382 @HasRepoPermissionAllDecorator('repository.admin')
383 @auth.CSRFRequired()
384 def repo_issuetracker_test(self, repo_name):
385 if request.is_xhr:
386 return h.urlify_commit_message(
387 request.POST.get('test_text', ''),
388 repo_name)
389 else:
390 raise HTTPBadRequest()
391
392 @HasRepoPermissionAllDecorator('repository.admin')
393 @auth.CSRFRequired()
394 def repo_issuetracker_delete(self, repo_name):
395 uid = request.POST.get('uid')
396 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
397 try:
398 repo_settings.delete_entries(uid)
399 except Exception:
400 h.flash(_('Error occurred during deleting issue tracker entry'),
401 category='error')
402 else:
403 h.flash(_('Removed issue tracker entry'), category='success')
404 return redirect(url('repo_settings_issuetracker',
405 repo_name=repo_name))
406
407 def _update_patterns(self, form, repo_settings):
408 for uid in form['delete_patterns']:
409 repo_settings.delete_entries(uid)
410
411 for pattern in form['patterns']:
412 for setting, value, type_ in pattern:
413 sett = repo_settings.create_or_update_setting(
414 setting, value, type_)
415 Session().add(sett)
416
417 Session().commit()
418
419 @HasRepoPermissionAllDecorator('repository.admin')
420 @auth.CSRFRequired()
421 def repo_issuetracker_save(self, repo_name):
422 # Save inheritance
423 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
424 inherited = (request.POST.get('inherit_global_issuetracker')
425 == "inherited")
426 repo_settings.inherit_global_settings = inherited
427 Session().commit()
428
429 form = IssueTrackerPatternsForm()().to_python(request.POST)
430 if form:
431 self._update_patterns(form, repo_settings)
432
433 h.flash(_('Updated issue tracker entries'), category='success')
434 return redirect(url('repo_settings_issuetracker',
435 repo_name=repo_name))
436
437 @HasRepoPermissionAllDecorator('repository.admin')
438 def repo_issuetracker(self, repo_name):
439 """GET /admin/settings/issue-tracker: All items in the collection"""
440 c.active = 'issuetracker'
441 c.data = 'data'
442 c.repo_info = self._load_repo(repo_name)
443
444 repo = Repository.get_by_repo_name(repo_name)
445 c.settings_model = IssueTrackerSettingsModel(repo=repo)
446 c.global_patterns = c.settings_model.get_global_settings()
447 c.repo_patterns = c.settings_model.get_repo_settings()
448
449 return render('admin/repos/repo_edit.mako')
450
451 @HasRepoPermissionAllDecorator('repository.admin')
452 def repo_settings_vcs(self, repo_name):
453 """GET /{repo_name}/settings/vcs/: All items in the collection"""
454
455 model = VcsSettingsModel(repo=repo_name)
456
457 c.active = 'vcs'
458 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
459 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
460 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
461 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
462 c.repo_info = self._load_repo(repo_name)
463 defaults = self._vcs_form_defaults(repo_name)
464 c.inherit_global_settings = defaults['inherit_global_settings']
465 c.labs_active = str2bool(
466 rhodecode.CONFIG.get('labs_settings_active', 'true'))
467
468 return htmlfill.render(
469 render('admin/repos/repo_edit.mako'),
470 defaults=defaults,
471 encoding="UTF-8",
472 force_defaults=False)
473
474 @HasRepoPermissionAllDecorator('repository.admin')
475 @auth.CSRFRequired()
476 def repo_settings_vcs_update(self, repo_name):
477 """POST /{repo_name}/settings/vcs/: All items in the collection"""
478 c.active = 'vcs'
479
480 model = VcsSettingsModel(repo=repo_name)
481 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
482 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
483 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
484 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
485 c.repo_info = self._load_repo(repo_name)
486 defaults = self._vcs_form_defaults(repo_name)
487 c.inherit_global_settings = defaults['inherit_global_settings']
488
489 application_form = RepoVcsSettingsForm(repo_name)()
490 try:
491 form_result = application_form.to_python(dict(request.POST))
492 except formencode.Invalid as errors:
493 h.flash(
494 _("Some form inputs contain invalid data."),
495 category='error')
496 return htmlfill.render(
497 render('admin/repos/repo_edit.mako'),
498 defaults=errors.value,
499 errors=errors.error_dict or {},
500 prefix_error=False,
501 encoding="UTF-8",
502 force_defaults=False
503 )
504
505 try:
506 inherit_global_settings = form_result['inherit_global_settings']
507 model.create_or_update_repo_settings(
508 form_result, inherit_global_settings=inherit_global_settings)
509 except Exception:
510 log.exception("Exception while updating settings")
511 h.flash(
512 _('Error occurred during updating repository VCS settings'),
513 category='error')
514 else:
515 Session().commit()
516 h.flash(_('Updated VCS settings'), category='success')
517 return redirect(url('repo_vcs_settings', repo_name=repo_name))
518
519 return htmlfill.render(
520 render('admin/repos/repo_edit.mako'),
521 defaults=self._vcs_form_defaults(repo_name),
522 encoding="UTF-8",
523 force_defaults=False)
524
525 @HasRepoPermissionAllDecorator('repository.admin')
526 @auth.CSRFRequired()
527 @jsonify
528 def repo_delete_svn_pattern(self, repo_name):
529 if not request.is_xhr:
530 return False
531
532 delete_pattern_id = request.POST.get('delete_svn_pattern')
533 model = VcsSettingsModel(repo=repo_name)
534 try:
535 model.delete_repo_svn_pattern(delete_pattern_id)
536 except SettingNotFound:
537 raise HTTPBadRequest()
538
539 Session().commit()
540 return True
541
542 def _vcs_form_defaults(self, repo_name):
543 model = VcsSettingsModel(repo=repo_name)
544 global_defaults = model.get_global_settings()
545
546 repo_defaults = {}
547 repo_defaults.update(global_defaults)
548 repo_defaults.update(model.get_repo_settings())
549
550 global_defaults = {
551 '{}_inherited'.format(k): global_defaults[k]
552 for k in global_defaults}
553
554 defaults = {
555 'inherit_global_settings': model.inherit_global_settings
556 }
557 defaults.update(global_defaults)
558 defaults.update(repo_defaults)
559 defaults.update({
560 'new_svn_branch': '',
561 'new_svn_tag': '',
562 })
563 return defaults
General Comments 0
You need to be logged in to leave comments. Login now