##// END OF EJS Templates
repositories: ported repo_creating checks to pyramid....
marcink -
r1985:f5a73a5e default
parent child Browse files
Show More
@@ -0,0 +1,110 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.auth import (NotAnonymous, HasRepoPermissionAny)
29 from rhodecode.model.db import Repository
30
31 log = logging.getLogger(__name__)
32
33
34 class RepoChecksView(BaseAppView):
35 def load_default_context(self):
36 c = self._get_local_tmpl_context()
37 self._register_global_c(c)
38 return c
39
40 @NotAnonymous()
41 @view_config(
42 route_name='repo_creating', request_method='GET',
43 renderer='rhodecode:templates/admin/repos/repo_creating.mako')
44 def repo_creating(self):
45 c = self.load_default_context()
46
47 repo_name = self.request.matchdict['repo_name']
48 db_repo = Repository.get_by_repo_name(repo_name)
49 if not db_repo:
50 raise HTTPNotFound()
51
52 # check if maybe repo is already created
53 if db_repo.repo_state in [Repository.STATE_CREATED]:
54 # re-check permissions before redirecting to prevent resource
55 # discovery by checking the 302 code
56 perm_set = ['repository.read', 'repository.write', 'repository.admin']
57 has_perm = HasRepoPermissionAny(*perm_set)(
58 db_repo.repo_name, 'Repo Creating check')
59 if not has_perm:
60 raise HTTPNotFound()
61
62 raise HTTPFound(h.route_path(
63 'repo_summary', repo_name=db_repo.repo_name))
64
65 c.task_id = self.request.GET.get('task_id')
66 c.repo_name = repo_name
67
68 return self._get_template_context(c)
69
70 @NotAnonymous()
71 @view_config(
72 route_name='repo_creating_check', request_method='GET',
73 renderer='json_ext')
74 def repo_creating_check(self):
75 _ = self.request.translate
76 task_id = self.request.GET.get('task_id')
77 self.load_default_context()
78
79 repo_name = self.request.matchdict['repo_name']
80
81 if task_id and task_id not in ['None']:
82 import rhodecode
83 from celery.result import AsyncResult
84 if rhodecode.CELERY_ENABLED:
85 task = AsyncResult(task_id)
86 if task.failed():
87 msg = self._log_creation_exception(task.result, repo_name)
88 h.flash(msg, category='error')
89 raise HTTPFound(h.route_path('home'), code=501)
90
91 db_repo = Repository.get_by_repo_name(repo_name)
92 if db_repo and db_repo.repo_state == Repository.STATE_CREATED:
93 if db_repo.clone_uri:
94 clone_uri = db_repo.clone_uri_hidden
95 h.flash(_('Created repository %s from %s')
96 % (db_repo.repo_name, clone_uri), category='success')
97 else:
98 repo_url = h.link_to(
99 db_repo.repo_name,
100 h.route_path('repo_summary', repo_name=db_repo.repo_name))
101 fork = db_repo.fork
102 if fork:
103 fork_name = fork.repo_name
104 h.flash(h.literal(_('Forked repository %s as %s')
105 % (fork_name, repo_url)), category='success')
106 else:
107 h.flash(h.literal(_('Created repository %s') % repo_url),
108 category='success')
109 return {'result': True}
110 return {'result': False}
@@ -1,446 +1,454 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 time
21 import time
22 import logging
22 import logging
23
23
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
27 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.model import repo
29 from rhodecode.model import repo
30 from rhodecode.model import repo_group
30 from rhodecode.model import repo_group
31 from rhodecode.model.db import User
31 from rhodecode.model.db import User
32 from rhodecode.model.scm import ScmModel
32 from rhodecode.model.scm import ScmModel
33
33
34 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
35
35
36
36
37 ADMIN_PREFIX = '/_admin'
37 ADMIN_PREFIX = '/_admin'
38 STATIC_FILE_PREFIX = '/_static'
38 STATIC_FILE_PREFIX = '/_static'
39
39
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 def add_route_with_slash(config,name, pattern, **kw):
54 def add_route_with_slash(config,name, pattern, **kw):
55 config.add_route(name, pattern, **kw)
55 config.add_route(name, pattern, **kw)
56 if not pattern.endswith('/'):
56 if not pattern.endswith('/'):
57 config.add_route(name + '_slash', pattern + '/', **kw)
57 config.add_route(name + '_slash', pattern + '/', **kw)
58
58
59
59
60 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
60 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
61 """
61 """
62 Adds regex requirements to pyramid routes using a mapping dict
62 Adds regex requirements to pyramid routes using a mapping dict
63 e.g::
63 e.g::
64 add_route_requirements('{repo_name}/settings')
64 add_route_requirements('{repo_name}/settings')
65 """
65 """
66 for key, regex in requirements.items():
66 for key, regex in requirements.items():
67 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
67 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
68 return route_path
68 return route_path
69
69
70
70
71 def get_format_ref_id(repo):
71 def get_format_ref_id(repo):
72 """Returns a `repo` specific reference formatter function"""
72 """Returns a `repo` specific reference formatter function"""
73 if h.is_svn(repo):
73 if h.is_svn(repo):
74 return _format_ref_id_svn
74 return _format_ref_id_svn
75 else:
75 else:
76 return _format_ref_id
76 return _format_ref_id
77
77
78
78
79 def _format_ref_id(name, raw_id):
79 def _format_ref_id(name, raw_id):
80 """Default formatting of a given reference `name`"""
80 """Default formatting of a given reference `name`"""
81 return name
81 return name
82
82
83
83
84 def _format_ref_id_svn(name, raw_id):
84 def _format_ref_id_svn(name, raw_id):
85 """Special way of formatting a reference for Subversion including path"""
85 """Special way of formatting a reference for Subversion including path"""
86 return '%s@%s' % (name, raw_id)
86 return '%s@%s' % (name, raw_id)
87
87
88
88
89 class TemplateArgs(StrictAttributeDict):
89 class TemplateArgs(StrictAttributeDict):
90 pass
90 pass
91
91
92
92
93 class BaseAppView(object):
93 class BaseAppView(object):
94
94
95 def __init__(self, context, request):
95 def __init__(self, context, request):
96 self.request = request
96 self.request = request
97 self.context = context
97 self.context = context
98 self.session = request.session
98 self.session = request.session
99 self._rhodecode_user = request.user # auth user
99 self._rhodecode_user = request.user # auth user
100 self._rhodecode_db_user = self._rhodecode_user.get_instance()
100 self._rhodecode_db_user = self._rhodecode_user.get_instance()
101 self._maybe_needs_password_change(
101 self._maybe_needs_password_change(
102 request.matched_route.name, self._rhodecode_db_user)
102 request.matched_route.name, self._rhodecode_db_user)
103
103
104 def _maybe_needs_password_change(self, view_name, user_obj):
104 def _maybe_needs_password_change(self, view_name, user_obj):
105 log.debug('Checking if user %s needs password change on view %s',
105 log.debug('Checking if user %s needs password change on view %s',
106 user_obj, view_name)
106 user_obj, view_name)
107 skip_user_views = [
107 skip_user_views = [
108 'logout', 'login',
108 'logout', 'login',
109 'my_account_password', 'my_account_password_update'
109 'my_account_password', 'my_account_password_update'
110 ]
110 ]
111
111
112 if not user_obj:
112 if not user_obj:
113 return
113 return
114
114
115 if user_obj.username == User.DEFAULT_USER:
115 if user_obj.username == User.DEFAULT_USER:
116 return
116 return
117
117
118 now = time.time()
118 now = time.time()
119 should_change = user_obj.user_data.get('force_password_change')
119 should_change = user_obj.user_data.get('force_password_change')
120 change_after = safe_int(should_change) or 0
120 change_after = safe_int(should_change) or 0
121 if should_change and now > change_after:
121 if should_change and now > change_after:
122 log.debug('User %s requires password change', user_obj)
122 log.debug('User %s requires password change', user_obj)
123 h.flash('You are required to change your password', 'warning',
123 h.flash('You are required to change your password', 'warning',
124 ignore_duplicate=True)
124 ignore_duplicate=True)
125
125
126 if view_name not in skip_user_views:
126 if view_name not in skip_user_views:
127 raise HTTPFound(
127 raise HTTPFound(
128 self.request.route_path('my_account_password'))
128 self.request.route_path('my_account_password'))
129
129
130 def _log_creation_exception(self, e, repo_name):
130 def _log_creation_exception(self, e, repo_name):
131 _ = self.request.translate
131 _ = self.request.translate
132 reason = None
132 reason = None
133 if len(e.args) == 2:
133 if len(e.args) == 2:
134 reason = e.args[1]
134 reason = e.args[1]
135
135
136 if reason == 'INVALID_CERTIFICATE':
136 if reason == 'INVALID_CERTIFICATE':
137 log.exception(
137 log.exception(
138 'Exception creating a repository: invalid certificate')
138 'Exception creating a repository: invalid certificate')
139 msg = (_('Error creating repository %s: invalid certificate')
139 msg = (_('Error creating repository %s: invalid certificate')
140 % repo_name)
140 % repo_name)
141 else:
141 else:
142 log.exception("Exception creating a repository")
142 log.exception("Exception creating a repository")
143 msg = (_('Error creating repository %s')
143 msg = (_('Error creating repository %s')
144 % repo_name)
144 % repo_name)
145 return msg
145 return msg
146
146
147 def _get_local_tmpl_context(self, include_app_defaults=False):
147 def _get_local_tmpl_context(self, include_app_defaults=False):
148 c = TemplateArgs()
148 c = TemplateArgs()
149 c.auth_user = self.request.user
149 c.auth_user = self.request.user
150 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
150 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
151 c.rhodecode_user = self.request.user
151 c.rhodecode_user = self.request.user
152
152
153 if include_app_defaults:
153 if include_app_defaults:
154 # NOTE(marcink): after full pyramid migration include_app_defaults
154 # NOTE(marcink): after full pyramid migration include_app_defaults
155 # should be turned on by default
155 # should be turned on by default
156 from rhodecode.lib.base import attach_context_attributes
156 from rhodecode.lib.base import attach_context_attributes
157 attach_context_attributes(c, self.request, self.request.user.user_id)
157 attach_context_attributes(c, self.request, self.request.user.user_id)
158
158
159 return c
159 return c
160
160
161 def _register_global_c(self, tmpl_args):
161 def _register_global_c(self, tmpl_args):
162 """
162 """
163 Registers attributes to pylons global `c`
163 Registers attributes to pylons global `c`
164 """
164 """
165
165
166 # TODO(marcink): remove once pyramid migration is finished
166 # TODO(marcink): remove once pyramid migration is finished
167 from pylons import tmpl_context as c
167 from pylons import tmpl_context as c
168 try:
168 try:
169 for k, v in tmpl_args.items():
169 for k, v in tmpl_args.items():
170 setattr(c, k, v)
170 setattr(c, k, v)
171 except TypeError:
171 except TypeError:
172 log.exception('Failed to register pylons C')
172 log.exception('Failed to register pylons C')
173 pass
173 pass
174
174
175 def _get_template_context(self, tmpl_args):
175 def _get_template_context(self, tmpl_args):
176 self._register_global_c(tmpl_args)
176 self._register_global_c(tmpl_args)
177
177
178 local_tmpl_args = {
178 local_tmpl_args = {
179 'defaults': {},
179 'defaults': {},
180 'errors': {},
180 'errors': {},
181 # register a fake 'c' to be used in templates instead of global
181 # register a fake 'c' to be used in templates instead of global
182 # pylons c, after migration to pyramid we should rename it to 'c'
182 # pylons c, after migration to pyramid we should rename it to 'c'
183 # make sure we replace usage of _c in templates too
183 # make sure we replace usage of _c in templates too
184 '_c': tmpl_args
184 '_c': tmpl_args
185 }
185 }
186 local_tmpl_args.update(tmpl_args)
186 local_tmpl_args.update(tmpl_args)
187 return local_tmpl_args
187 return local_tmpl_args
188
188
189 def load_default_context(self):
189 def load_default_context(self):
190 """
190 """
191 example:
191 example:
192
192
193 def load_default_context(self):
193 def load_default_context(self):
194 c = self._get_local_tmpl_context()
194 c = self._get_local_tmpl_context()
195 c.custom_var = 'foobar'
195 c.custom_var = 'foobar'
196 self._register_global_c(c)
196 self._register_global_c(c)
197 return c
197 return c
198 """
198 """
199 raise NotImplementedError('Needs implementation in view class')
199 raise NotImplementedError('Needs implementation in view class')
200
200
201
201
202 class RepoAppView(BaseAppView):
202 class RepoAppView(BaseAppView):
203
203
204 def __init__(self, context, request):
204 def __init__(self, context, request):
205 super(RepoAppView, self).__init__(context, request)
205 super(RepoAppView, self).__init__(context, request)
206 self.db_repo = request.db_repo
206 self.db_repo = request.db_repo
207 self.db_repo_name = self.db_repo.repo_name
207 self.db_repo_name = self.db_repo.repo_name
208 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
208 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
209
209
210 def _handle_missing_requirements(self, error):
210 def _handle_missing_requirements(self, error):
211 log.error(
211 log.error(
212 'Requirements are missing for repository %s: %s',
212 'Requirements are missing for repository %s: %s',
213 self.db_repo_name, error.message)
213 self.db_repo_name, error.message)
214
214
215 def _get_local_tmpl_context(self, include_app_defaults=False):
215 def _get_local_tmpl_context(self, include_app_defaults=False):
216 _ = self.request.translate
216 _ = self.request.translate
217 c = super(RepoAppView, self)._get_local_tmpl_context(
217 c = super(RepoAppView, self)._get_local_tmpl_context(
218 include_app_defaults=include_app_defaults)
218 include_app_defaults=include_app_defaults)
219
219
220 # register common vars for this type of view
220 # register common vars for this type of view
221 c.rhodecode_db_repo = self.db_repo
221 c.rhodecode_db_repo = self.db_repo
222 c.repo_name = self.db_repo_name
222 c.repo_name = self.db_repo_name
223 c.repository_pull_requests = self.db_repo_pull_requests
223 c.repository_pull_requests = self.db_repo_pull_requests
224
224
225 c.repository_requirements_missing = False
225 c.repository_requirements_missing = False
226 try:
226 try:
227 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
227 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
228 except RepositoryRequirementError as e:
228 except RepositoryRequirementError as e:
229 c.repository_requirements_missing = True
229 c.repository_requirements_missing = True
230 self._handle_missing_requirements(e)
230 self._handle_missing_requirements(e)
231 self.rhodecode_vcs_repo = None
231 self.rhodecode_vcs_repo = None
232
232
233 if (not c.repository_requirements_missing
233 if (not c.repository_requirements_missing
234 and self.rhodecode_vcs_repo is None):
234 and self.rhodecode_vcs_repo is None):
235 # unable to fetch this repo as vcs instance, report back to user
235 # unable to fetch this repo as vcs instance, report back to user
236 h.flash(_(
236 h.flash(_(
237 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
237 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
238 "Please check if it exist, or is not damaged.") %
238 "Please check if it exist, or is not damaged.") %
239 {'repo_name': c.repo_name},
239 {'repo_name': c.repo_name},
240 category='error', ignore_duplicate=True)
240 category='error', ignore_duplicate=True)
241 raise HTTPFound(h.route_path('home'))
241 raise HTTPFound(h.route_path('home'))
242
242
243 return c
243 return c
244
244
245 def _get_f_path(self, matchdict, default=None):
245 def _get_f_path(self, matchdict, default=None):
246 f_path = matchdict.get('f_path')
246 f_path = matchdict.get('f_path')
247 if f_path:
247 if f_path:
248 # fix for multiple initial slashes that causes errors for GIT
248 # fix for multiple initial slashes that causes errors for GIT
249 return f_path.lstrip('/')
249 return f_path.lstrip('/')
250
250
251 return default
251 return default
252
252
253
253
254 class DataGridAppView(object):
254 class DataGridAppView(object):
255 """
255 """
256 Common class to have re-usable grid rendering components
256 Common class to have re-usable grid rendering components
257 """
257 """
258
258
259 def _extract_ordering(self, request, column_map=None):
259 def _extract_ordering(self, request, column_map=None):
260 column_map = column_map or {}
260 column_map = column_map or {}
261 column_index = safe_int(request.GET.get('order[0][column]'))
261 column_index = safe_int(request.GET.get('order[0][column]'))
262 order_dir = request.GET.get(
262 order_dir = request.GET.get(
263 'order[0][dir]', 'desc')
263 'order[0][dir]', 'desc')
264 order_by = request.GET.get(
264 order_by = request.GET.get(
265 'columns[%s][data][sort]' % column_index, 'name_raw')
265 'columns[%s][data][sort]' % column_index, 'name_raw')
266
266
267 # translate datatable to DB columns
267 # translate datatable to DB columns
268 order_by = column_map.get(order_by) or order_by
268 order_by = column_map.get(order_by) or order_by
269
269
270 search_q = request.GET.get('search[value]')
270 search_q = request.GET.get('search[value]')
271 return search_q, order_by, order_dir
271 return search_q, order_by, order_dir
272
272
273 def _extract_chunk(self, request):
273 def _extract_chunk(self, request):
274 start = safe_int(request.GET.get('start'), 0)
274 start = safe_int(request.GET.get('start'), 0)
275 length = safe_int(request.GET.get('length'), 25)
275 length = safe_int(request.GET.get('length'), 25)
276 draw = safe_int(request.GET.get('draw'))
276 draw = safe_int(request.GET.get('draw'))
277 return draw, start, length
277 return draw, start, length
278
278
279
279
280 class BaseReferencesView(RepoAppView):
280 class BaseReferencesView(RepoAppView):
281 """
281 """
282 Base for reference view for branches, tags and bookmarks.
282 Base for reference view for branches, tags and bookmarks.
283 """
283 """
284 def load_default_context(self):
284 def load_default_context(self):
285 c = self._get_local_tmpl_context()
285 c = self._get_local_tmpl_context()
286
286
287 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
287 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
288 c.repo_info = self.db_repo
288 c.repo_info = self.db_repo
289
289
290 self._register_global_c(c)
290 self._register_global_c(c)
291 return c
291 return c
292
292
293 def load_refs_context(self, ref_items, partials_template):
293 def load_refs_context(self, ref_items, partials_template):
294 _render = self.request.get_partial_renderer(partials_template)
294 _render = self.request.get_partial_renderer(partials_template)
295 pre_load = ["author", "date", "message"]
295 pre_load = ["author", "date", "message"]
296
296
297 is_svn = h.is_svn(self.rhodecode_vcs_repo)
297 is_svn = h.is_svn(self.rhodecode_vcs_repo)
298 is_hg = h.is_hg(self.rhodecode_vcs_repo)
298 is_hg = h.is_hg(self.rhodecode_vcs_repo)
299
299
300 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
300 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
301
301
302 closed_refs = {}
302 closed_refs = {}
303 if is_hg:
303 if is_hg:
304 closed_refs = self.rhodecode_vcs_repo.branches_closed
304 closed_refs = self.rhodecode_vcs_repo.branches_closed
305
305
306 data = []
306 data = []
307 for ref_name, commit_id in ref_items:
307 for ref_name, commit_id in ref_items:
308 commit = self.rhodecode_vcs_repo.get_commit(
308 commit = self.rhodecode_vcs_repo.get_commit(
309 commit_id=commit_id, pre_load=pre_load)
309 commit_id=commit_id, pre_load=pre_load)
310 closed = ref_name in closed_refs
310 closed = ref_name in closed_refs
311
311
312 # TODO: johbo: Unify generation of reference links
312 # TODO: johbo: Unify generation of reference links
313 use_commit_id = '/' in ref_name or is_svn
313 use_commit_id = '/' in ref_name or is_svn
314
314
315 if use_commit_id:
315 if use_commit_id:
316 files_url = h.route_path(
316 files_url = h.route_path(
317 'repo_files',
317 'repo_files',
318 repo_name=self.db_repo_name,
318 repo_name=self.db_repo_name,
319 f_path=ref_name if is_svn else '',
319 f_path=ref_name if is_svn else '',
320 commit_id=commit_id)
320 commit_id=commit_id)
321
321
322 else:
322 else:
323 files_url = h.route_path(
323 files_url = h.route_path(
324 'repo_files',
324 'repo_files',
325 repo_name=self.db_repo_name,
325 repo_name=self.db_repo_name,
326 f_path=ref_name if is_svn else '',
326 f_path=ref_name if is_svn else '',
327 commit_id=ref_name,
327 commit_id=ref_name,
328 _query=dict(at=ref_name))
328 _query=dict(at=ref_name))
329
329
330 data.append({
330 data.append({
331 "name": _render('name', ref_name, files_url, closed),
331 "name": _render('name', ref_name, files_url, closed),
332 "name_raw": ref_name,
332 "name_raw": ref_name,
333 "date": _render('date', commit.date),
333 "date": _render('date', commit.date),
334 "date_raw": datetime_to_time(commit.date),
334 "date_raw": datetime_to_time(commit.date),
335 "author": _render('author', commit.author),
335 "author": _render('author', commit.author),
336 "commit": _render(
336 "commit": _render(
337 'commit', commit.message, commit.raw_id, commit.idx),
337 'commit', commit.message, commit.raw_id, commit.idx),
338 "commit_raw": commit.idx,
338 "commit_raw": commit.idx,
339 "compare": _render(
339 "compare": _render(
340 'compare', format_ref_id(ref_name, commit.raw_id)),
340 'compare', format_ref_id(ref_name, commit.raw_id)),
341 })
341 })
342
342
343 return data
343 return data
344
344
345
345
346 class RepoRoutePredicate(object):
346 class RepoRoutePredicate(object):
347 def __init__(self, val, config):
347 def __init__(self, val, config):
348 self.val = val
348 self.val = val
349
349
350 def text(self):
350 def text(self):
351 return 'repo_route = %s' % self.val
351 return 'repo_route = %s' % self.val
352
352
353 phash = text
353 phash = text
354
354
355 def __call__(self, info, request):
355 def __call__(self, info, request):
356
356
357 if hasattr(request, 'vcs_call'):
357 if hasattr(request, 'vcs_call'):
358 # skip vcs calls
358 # skip vcs calls
359 return
359 return
360
360
361 repo_name = info['match']['repo_name']
361 repo_name = info['match']['repo_name']
362 repo_model = repo.RepoModel()
362 repo_model = repo.RepoModel()
363 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
363 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
364
364
365 def redirect_if_creating(db_repo):
366 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
367 raise HTTPFound(
368 request.route_path('repo_creating',
369 repo_name=db_repo.repo_name))
370
365 if by_name_match:
371 if by_name_match:
366 # register this as request object we can re-use later
372 # register this as request object we can re-use later
367 request.db_repo = by_name_match
373 request.db_repo = by_name_match
374 redirect_if_creating(by_name_match)
368 return True
375 return True
369
376
370 by_id_match = repo_model.get_repo_by_id(repo_name)
377 by_id_match = repo_model.get_repo_by_id(repo_name)
371 if by_id_match:
378 if by_id_match:
372 request.db_repo = by_id_match
379 request.db_repo = by_id_match
380 redirect_if_creating(by_id_match)
373 return True
381 return True
374
382
375 return False
383 return False
376
384
377
385
378 class RepoTypeRoutePredicate(object):
386 class RepoTypeRoutePredicate(object):
379 def __init__(self, val, config):
387 def __init__(self, val, config):
380 self.val = val or ['hg', 'git', 'svn']
388 self.val = val or ['hg', 'git', 'svn']
381
389
382 def text(self):
390 def text(self):
383 return 'repo_accepted_type = %s' % self.val
391 return 'repo_accepted_type = %s' % self.val
384
392
385 phash = text
393 phash = text
386
394
387 def __call__(self, info, request):
395 def __call__(self, info, request):
388 if hasattr(request, 'vcs_call'):
396 if hasattr(request, 'vcs_call'):
389 # skip vcs calls
397 # skip vcs calls
390 return
398 return
391
399
392 rhodecode_db_repo = request.db_repo
400 rhodecode_db_repo = request.db_repo
393
401
394 log.debug(
402 log.debug(
395 '%s checking repo type for %s in %s',
403 '%s checking repo type for %s in %s',
396 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
404 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
397
405
398 if rhodecode_db_repo.repo_type in self.val:
406 if rhodecode_db_repo.repo_type in self.val:
399 return True
407 return True
400 else:
408 else:
401 log.warning('Current view is not supported for repo type:%s',
409 log.warning('Current view is not supported for repo type:%s',
402 rhodecode_db_repo.repo_type)
410 rhodecode_db_repo.repo_type)
403 #
411 #
404 # h.flash(h.literal(
412 # h.flash(h.literal(
405 # _('Action not supported for %s.' % rhodecode_repo.alias)),
413 # _('Action not supported for %s.' % rhodecode_repo.alias)),
406 # category='warning')
414 # category='warning')
407 # return redirect(
415 # return redirect(
408 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
416 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
409
417
410 return False
418 return False
411
419
412
420
413 class RepoGroupRoutePredicate(object):
421 class RepoGroupRoutePredicate(object):
414 def __init__(self, val, config):
422 def __init__(self, val, config):
415 self.val = val
423 self.val = val
416
424
417 def text(self):
425 def text(self):
418 return 'repo_group_route = %s' % self.val
426 return 'repo_group_route = %s' % self.val
419
427
420 phash = text
428 phash = text
421
429
422 def __call__(self, info, request):
430 def __call__(self, info, request):
423 if hasattr(request, 'vcs_call'):
431 if hasattr(request, 'vcs_call'):
424 # skip vcs calls
432 # skip vcs calls
425 return
433 return
426
434
427 repo_group_name = info['match']['repo_group_name']
435 repo_group_name = info['match']['repo_group_name']
428 repo_group_model = repo_group.RepoGroupModel()
436 repo_group_model = repo_group.RepoGroupModel()
429 by_name_match = repo_group_model.get_by_group_name(
437 by_name_match = repo_group_model.get_by_group_name(
430 repo_group_name, cache=True)
438 repo_group_name, cache=True)
431
439
432 if by_name_match:
440 if by_name_match:
433 # register this as request object we can re-use later
441 # register this as request object we can re-use later
434 request.db_repo_group = by_name_match
442 request.db_repo_group = by_name_match
435 return True
443 return True
436
444
437 return False
445 return False
438
446
439
447
440 def includeme(config):
448 def includeme(config):
441 config.add_route_predicate(
449 config.add_route_predicate(
442 'repo_route', RepoRoutePredicate)
450 'repo_route', RepoRoutePredicate)
443 config.add_route_predicate(
451 config.add_route_predicate(
444 'repo_accepted_types', RepoTypeRoutePredicate)
452 'repo_accepted_types', RepoTypeRoutePredicate)
445 config.add_route_predicate(
453 config.add_route_predicate(
446 'repo_group_route', RepoGroupRoutePredicate)
454 'repo_group_route', RepoGroupRoutePredicate)
@@ -1,357 +1,366 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
26 config.add_route(
27 name='repo_creating',
28 pattern='/{repo_name:.*?[^/]}/repo_creating')
29
30 config.add_route(
31 name='repo_creating_check',
32 pattern='/{repo_name:.*?[^/]}/repo_creating_check')
33
25 # Summary
34 # Summary
26 # NOTE(marcink): one additional route is defined in very bottom, catch
35 # NOTE(marcink): one additional route is defined in very bottom, catch
27 # all pattern
36 # all pattern
28 config.add_route(
37 config.add_route(
29 name='repo_summary_explicit',
38 name='repo_summary_explicit',
30 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
39 pattern='/{repo_name:.*?[^/]}/summary', repo_route=True)
31 config.add_route(
40 config.add_route(
32 name='repo_summary_commits',
41 name='repo_summary_commits',
33 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
42 pattern='/{repo_name:.*?[^/]}/summary-commits', repo_route=True)
34
43
35 # Commits
44 # Commits
36 config.add_route(
45 config.add_route(
37 name='repo_commit',
46 name='repo_commit',
38 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
47 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}', repo_route=True)
39
48
40 config.add_route(
49 config.add_route(
41 name='repo_commit_children',
50 name='repo_commit_children',
42 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
51 pattern='/{repo_name:.*?[^/]}/changeset_children/{commit_id}', repo_route=True)
43
52
44 config.add_route(
53 config.add_route(
45 name='repo_commit_parents',
54 name='repo_commit_parents',
46 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
55 pattern='/{repo_name:.*?[^/]}/changeset_parents/{commit_id}', repo_route=True)
47
56
48 config.add_route(
57 config.add_route(
49 name='repo_commit_raw',
58 name='repo_commit_raw',
50 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
59 pattern='/{repo_name:.*?[^/]}/changeset-diff/{commit_id}', repo_route=True)
51
60
52 config.add_route(
61 config.add_route(
53 name='repo_commit_patch',
62 name='repo_commit_patch',
54 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
63 pattern='/{repo_name:.*?[^/]}/changeset-patch/{commit_id}', repo_route=True)
55
64
56 config.add_route(
65 config.add_route(
57 name='repo_commit_download',
66 name='repo_commit_download',
58 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
67 pattern='/{repo_name:.*?[^/]}/changeset-download/{commit_id}', repo_route=True)
59
68
60 config.add_route(
69 config.add_route(
61 name='repo_commit_data',
70 name='repo_commit_data',
62 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
71 pattern='/{repo_name:.*?[^/]}/changeset-data/{commit_id}', repo_route=True)
63
72
64 config.add_route(
73 config.add_route(
65 name='repo_commit_comment_create',
74 name='repo_commit_comment_create',
66 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
75 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/create', repo_route=True)
67
76
68 config.add_route(
77 config.add_route(
69 name='repo_commit_comment_preview',
78 name='repo_commit_comment_preview',
70 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
79 pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True)
71
80
72 config.add_route(
81 config.add_route(
73 name='repo_commit_comment_delete',
82 name='repo_commit_comment_delete',
74 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)
75
84
76 # still working url for backward compat.
85 # still working url for backward compat.
77 config.add_route(
86 config.add_route(
78 name='repo_commit_raw_deprecated',
87 name='repo_commit_raw_deprecated',
79 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
88 pattern='/{repo_name:.*?[^/]}/raw-changeset/{commit_id}', repo_route=True)
80
89
81 # Files
90 # Files
82 config.add_route(
91 config.add_route(
83 name='repo_archivefile',
92 name='repo_archivefile',
84 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
93 pattern='/{repo_name:.*?[^/]}/archive/{fname}', repo_route=True)
85
94
86 config.add_route(
95 config.add_route(
87 name='repo_files_diff',
96 name='repo_files_diff',
88 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
97 pattern='/{repo_name:.*?[^/]}/diff/{f_path:.*}', repo_route=True)
89 config.add_route( # legacy route to make old links work
98 config.add_route( # legacy route to make old links work
90 name='repo_files_diff_2way_redirect',
99 name='repo_files_diff_2way_redirect',
91 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
100 pattern='/{repo_name:.*?[^/]}/diff-2way/{f_path:.*}', repo_route=True)
92
101
93 config.add_route(
102 config.add_route(
94 name='repo_files',
103 name='repo_files',
95 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
104 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/{f_path:.*}', repo_route=True)
96 config.add_route(
105 config.add_route(
97 name='repo_files:default_path',
106 name='repo_files:default_path',
98 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
107 pattern='/{repo_name:.*?[^/]}/files/{commit_id}/', repo_route=True)
99 config.add_route(
108 config.add_route(
100 name='repo_files:default_commit',
109 name='repo_files:default_commit',
101 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
110 pattern='/{repo_name:.*?[^/]}/files', repo_route=True)
102
111
103 config.add_route(
112 config.add_route(
104 name='repo_files:rendered',
113 name='repo_files:rendered',
105 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
114 pattern='/{repo_name:.*?[^/]}/render/{commit_id}/{f_path:.*}', repo_route=True)
106
115
107 config.add_route(
116 config.add_route(
108 name='repo_files:annotated',
117 name='repo_files:annotated',
109 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
118 pattern='/{repo_name:.*?[^/]}/annotate/{commit_id}/{f_path:.*}', repo_route=True)
110 config.add_route(
119 config.add_route(
111 name='repo_files:annotated_previous',
120 name='repo_files:annotated_previous',
112 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)
113
122
114 config.add_route(
123 config.add_route(
115 name='repo_nodetree_full',
124 name='repo_nodetree_full',
116 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)
117 config.add_route(
126 config.add_route(
118 name='repo_nodetree_full:default_path',
127 name='repo_nodetree_full:default_path',
119 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
128 pattern='/{repo_name:.*?[^/]}/nodetree_full/{commit_id}/', repo_route=True)
120
129
121 config.add_route(
130 config.add_route(
122 name='repo_files_nodelist',
131 name='repo_files_nodelist',
123 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
132 pattern='/{repo_name:.*?[^/]}/nodelist/{commit_id}/{f_path:.*}', repo_route=True)
124
133
125 config.add_route(
134 config.add_route(
126 name='repo_file_raw',
135 name='repo_file_raw',
127 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
136 pattern='/{repo_name:.*?[^/]}/raw/{commit_id}/{f_path:.*}', repo_route=True)
128
137
129 config.add_route(
138 config.add_route(
130 name='repo_file_download',
139 name='repo_file_download',
131 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
140 pattern='/{repo_name:.*?[^/]}/download/{commit_id}/{f_path:.*}', repo_route=True)
132 config.add_route( # backward compat to keep old links working
141 config.add_route( # backward compat to keep old links working
133 name='repo_file_download:legacy',
142 name='repo_file_download:legacy',
134 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
143 pattern='/{repo_name:.*?[^/]}/rawfile/{commit_id}/{f_path:.*}',
135 repo_route=True)
144 repo_route=True)
136
145
137 config.add_route(
146 config.add_route(
138 name='repo_file_history',
147 name='repo_file_history',
139 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
148 pattern='/{repo_name:.*?[^/]}/history/{commit_id}/{f_path:.*}', repo_route=True)
140
149
141 config.add_route(
150 config.add_route(
142 name='repo_file_authors',
151 name='repo_file_authors',
143 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
152 pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
144
153
145 config.add_route(
154 config.add_route(
146 name='repo_files_remove_file',
155 name='repo_files_remove_file',
147 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
156 pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
148 repo_route=True)
157 repo_route=True)
149 config.add_route(
158 config.add_route(
150 name='repo_files_delete_file',
159 name='repo_files_delete_file',
151 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
160 pattern='/{repo_name:.*?[^/]}/delete_file/{commit_id}/{f_path:.*}',
152 repo_route=True)
161 repo_route=True)
153 config.add_route(
162 config.add_route(
154 name='repo_files_edit_file',
163 name='repo_files_edit_file',
155 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
164 pattern='/{repo_name:.*?[^/]}/edit_file/{commit_id}/{f_path:.*}',
156 repo_route=True)
165 repo_route=True)
157 config.add_route(
166 config.add_route(
158 name='repo_files_update_file',
167 name='repo_files_update_file',
159 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
168 pattern='/{repo_name:.*?[^/]}/update_file/{commit_id}/{f_path:.*}',
160 repo_route=True)
169 repo_route=True)
161 config.add_route(
170 config.add_route(
162 name='repo_files_add_file',
171 name='repo_files_add_file',
163 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
172 pattern='/{repo_name:.*?[^/]}/add_file/{commit_id}/{f_path:.*}',
164 repo_route=True)
173 repo_route=True)
165 config.add_route(
174 config.add_route(
166 name='repo_files_create_file',
175 name='repo_files_create_file',
167 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
176 pattern='/{repo_name:.*?[^/]}/create_file/{commit_id}/{f_path:.*}',
168 repo_route=True)
177 repo_route=True)
169
178
170 # Refs data
179 # Refs data
171 config.add_route(
180 config.add_route(
172 name='repo_refs_data',
181 name='repo_refs_data',
173 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
182 pattern='/{repo_name:.*?[^/]}/refs-data', repo_route=True)
174
183
175 config.add_route(
184 config.add_route(
176 name='repo_refs_changelog_data',
185 name='repo_refs_changelog_data',
177 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
186 pattern='/{repo_name:.*?[^/]}/refs-data-changelog', repo_route=True)
178
187
179 config.add_route(
188 config.add_route(
180 name='repo_stats',
189 name='repo_stats',
181 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
190 pattern='/{repo_name:.*?[^/]}/repo_stats/{commit_id}', repo_route=True)
182
191
183 # Changelog
192 # Changelog
184 config.add_route(
193 config.add_route(
185 name='repo_changelog',
194 name='repo_changelog',
186 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
195 pattern='/{repo_name:.*?[^/]}/changelog', repo_route=True)
187 config.add_route(
196 config.add_route(
188 name='repo_changelog_file',
197 name='repo_changelog_file',
189 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
198 pattern='/{repo_name:.*?[^/]}/changelog/{commit_id}/{f_path:.*}', repo_route=True)
190 config.add_route(
199 config.add_route(
191 name='repo_changelog_elements',
200 name='repo_changelog_elements',
192 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
201 pattern='/{repo_name:.*?[^/]}/changelog_elements', repo_route=True)
193
202
194 # Compare
203 # Compare
195 config.add_route(
204 config.add_route(
196 name='repo_compare_select',
205 name='repo_compare_select',
197 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
206 pattern='/{repo_name:.*?[^/]}/compare', repo_route=True)
198
207
199 config.add_route(
208 config.add_route(
200 name='repo_compare',
209 name='repo_compare',
201 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)
202
211
203 # Tags
212 # Tags
204 config.add_route(
213 config.add_route(
205 name='tags_home',
214 name='tags_home',
206 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
215 pattern='/{repo_name:.*?[^/]}/tags', repo_route=True)
207
216
208 # Branches
217 # Branches
209 config.add_route(
218 config.add_route(
210 name='branches_home',
219 name='branches_home',
211 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
220 pattern='/{repo_name:.*?[^/]}/branches', repo_route=True)
212
221
213 config.add_route(
222 config.add_route(
214 name='bookmarks_home',
223 name='bookmarks_home',
215 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
224 pattern='/{repo_name:.*?[^/]}/bookmarks', repo_route=True)
216
225
217 # Pull Requests
226 # Pull Requests
218 config.add_route(
227 config.add_route(
219 name='pullrequest_show',
228 name='pullrequest_show',
220 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
229 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
221 repo_route=True)
230 repo_route=True)
222
231
223 config.add_route(
232 config.add_route(
224 name='pullrequest_show_all',
233 name='pullrequest_show_all',
225 pattern='/{repo_name:.*?[^/]}/pull-request',
234 pattern='/{repo_name:.*?[^/]}/pull-request',
226 repo_route=True, repo_accepted_types=['hg', 'git'])
235 repo_route=True, repo_accepted_types=['hg', 'git'])
227
236
228 config.add_route(
237 config.add_route(
229 name='pullrequest_show_all_data',
238 name='pullrequest_show_all_data',
230 pattern='/{repo_name:.*?[^/]}/pull-request-data',
239 pattern='/{repo_name:.*?[^/]}/pull-request-data',
231 repo_route=True, repo_accepted_types=['hg', 'git'])
240 repo_route=True, repo_accepted_types=['hg', 'git'])
232
241
233 config.add_route(
242 config.add_route(
234 name='pullrequest_repo_refs',
243 name='pullrequest_repo_refs',
235 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
244 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
236 repo_route=True)
245 repo_route=True)
237
246
238 config.add_route(
247 config.add_route(
239 name='pullrequest_repo_destinations',
248 name='pullrequest_repo_destinations',
240 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
249 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
241 repo_route=True)
250 repo_route=True)
242
251
243 config.add_route(
252 config.add_route(
244 name='pullrequest_new',
253 name='pullrequest_new',
245 pattern='/{repo_name:.*?[^/]}/pull-request/new',
254 pattern='/{repo_name:.*?[^/]}/pull-request/new',
246 repo_route=True, repo_accepted_types=['hg', 'git'])
255 repo_route=True, repo_accepted_types=['hg', 'git'])
247
256
248 config.add_route(
257 config.add_route(
249 name='pullrequest_create',
258 name='pullrequest_create',
250 pattern='/{repo_name:.*?[^/]}/pull-request/create',
259 pattern='/{repo_name:.*?[^/]}/pull-request/create',
251 repo_route=True, repo_accepted_types=['hg', 'git'])
260 repo_route=True, repo_accepted_types=['hg', 'git'])
252
261
253 config.add_route(
262 config.add_route(
254 name='pullrequest_update',
263 name='pullrequest_update',
255 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
264 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
256 repo_route=True)
265 repo_route=True)
257
266
258 config.add_route(
267 config.add_route(
259 name='pullrequest_merge',
268 name='pullrequest_merge',
260 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
269 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
261 repo_route=True)
270 repo_route=True)
262
271
263 config.add_route(
272 config.add_route(
264 name='pullrequest_delete',
273 name='pullrequest_delete',
265 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
274 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
266 repo_route=True)
275 repo_route=True)
267
276
268 config.add_route(
277 config.add_route(
269 name='pullrequest_comment_create',
278 name='pullrequest_comment_create',
270 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
279 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
271 repo_route=True)
280 repo_route=True)
272
281
273 config.add_route(
282 config.add_route(
274 name='pullrequest_comment_delete',
283 name='pullrequest_comment_delete',
275 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
284 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
276 repo_route=True, repo_accepted_types=['hg', 'git'])
285 repo_route=True, repo_accepted_types=['hg', 'git'])
277
286
278 # Settings
287 # Settings
279 config.add_route(
288 config.add_route(
280 name='edit_repo',
289 name='edit_repo',
281 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
290 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
282
291
283 # Settings advanced
292 # Settings advanced
284 config.add_route(
293 config.add_route(
285 name='edit_repo_advanced',
294 name='edit_repo_advanced',
286 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
295 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
287 config.add_route(
296 config.add_route(
288 name='edit_repo_advanced_delete',
297 name='edit_repo_advanced_delete',
289 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
298 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
290 config.add_route(
299 config.add_route(
291 name='edit_repo_advanced_locking',
300 name='edit_repo_advanced_locking',
292 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
301 pattern='/{repo_name:.*?[^/]}/settings/advanced/locking', repo_route=True)
293 config.add_route(
302 config.add_route(
294 name='edit_repo_advanced_journal',
303 name='edit_repo_advanced_journal',
295 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
304 pattern='/{repo_name:.*?[^/]}/settings/advanced/journal', repo_route=True)
296 config.add_route(
305 config.add_route(
297 name='edit_repo_advanced_fork',
306 name='edit_repo_advanced_fork',
298 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
307 pattern='/{repo_name:.*?[^/]}/settings/advanced/fork', repo_route=True)
299
308
300 # Caches
309 # Caches
301 config.add_route(
310 config.add_route(
302 name='edit_repo_caches',
311 name='edit_repo_caches',
303 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
312 pattern='/{repo_name:.*?[^/]}/settings/caches', repo_route=True)
304
313
305 # Permissions
314 # Permissions
306 config.add_route(
315 config.add_route(
307 name='edit_repo_perms',
316 name='edit_repo_perms',
308 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
317 pattern='/{repo_name:.*?[^/]}/settings/permissions', repo_route=True)
309
318
310 # Repo Review Rules
319 # Repo Review Rules
311 config.add_route(
320 config.add_route(
312 name='repo_reviewers',
321 name='repo_reviewers',
313 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
322 pattern='/{repo_name:.*?[^/]}/settings/review/rules', repo_route=True)
314
323
315 config.add_route(
324 config.add_route(
316 name='repo_default_reviewers_data',
325 name='repo_default_reviewers_data',
317 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
326 pattern='/{repo_name:.*?[^/]}/settings/review/default-reviewers', repo_route=True)
318
327
319 # Maintenance
328 # Maintenance
320 config.add_route(
329 config.add_route(
321 name='repo_maintenance',
330 name='repo_maintenance',
322 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
331 pattern='/{repo_name:.*?[^/]}/settings/maintenance', repo_route=True)
323
332
324 config.add_route(
333 config.add_route(
325 name='repo_maintenance_execute',
334 name='repo_maintenance_execute',
326 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
335 pattern='/{repo_name:.*?[^/]}/settings/maintenance/execute', repo_route=True)
327
336
328 # Strip
337 # Strip
329 config.add_route(
338 config.add_route(
330 name='strip',
339 name='strip',
331 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
340 pattern='/{repo_name:.*?[^/]}/settings/strip', repo_route=True)
332
341
333 config.add_route(
342 config.add_route(
334 name='strip_check',
343 name='strip_check',
335 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
344 pattern='/{repo_name:.*?[^/]}/settings/strip_check', repo_route=True)
336
345
337 config.add_route(
346 config.add_route(
338 name='strip_execute',
347 name='strip_execute',
339 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
348 pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True)
340
349
341 # ATOM/RSS Feed
350 # ATOM/RSS Feed
342 config.add_route(
351 config.add_route(
343 name='rss_feed_home',
352 name='rss_feed_home',
344 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
353 pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True)
345
354
346 config.add_route(
355 config.add_route(
347 name='atom_feed_home',
356 name='atom_feed_home',
348 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
357 pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True)
349
358
350 # NOTE(marcink): needs to be at the end for catch-all
359 # NOTE(marcink): needs to be at the end for catch-all
351 add_route_with_slash(
360 add_route_with_slash(
352 config,
361 config,
353 name='repo_summary',
362 name='repo_summary',
354 pattern='/{repo_name:.*?[^/]}', repo_route=True)
363 pattern='/{repo_name:.*?[^/]}', repo_route=True)
355
364
356 # Scan module for configuration decorators.
365 # Scan module for configuration decorators.
357 config.scan()
366 config.scan()
@@ -1,523 +1,523 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 re
21 import re
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
26 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.compat import OrderedDict
28 from rhodecode.lib.compat import OrderedDict
29 from rhodecode.lib.utils2 import AttributeDict, safe_str
29 from rhodecode.lib.utils2 import AttributeDict, safe_str
30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
31 from rhodecode.model.db import Repository
31 from rhodecode.model.db import Repository
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo import RepoModel
34 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.scm import ScmModel
35 from rhodecode.tests import assert_session_flash
35 from rhodecode.tests import assert_session_flash
36 from rhodecode.tests.fixture import Fixture
36 from rhodecode.tests.fixture import Fixture
37 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
37 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
38
38
39
39
40 fixture = Fixture()
40 fixture = Fixture()
41
41
42
42
43 def route_path(name, params=None, **kwargs):
43 def route_path(name, params=None, **kwargs):
44 import urllib
44 import urllib
45
45
46 base_url = {
46 base_url = {
47 'repo_summary': '/{repo_name}',
47 'repo_summary': '/{repo_name}',
48 'repo_stats': '/{repo_name}/repo_stats/{commit_id}',
48 'repo_stats': '/{repo_name}/repo_stats/{commit_id}',
49 'repo_refs_data': '/{repo_name}/refs-data',
49 'repo_refs_data': '/{repo_name}/refs-data',
50 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog'
50 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog',
51
51 'repo_creating_check': '/{repo_name}/repo_creating_check',
52 }[name].format(**kwargs)
52 }[name].format(**kwargs)
53
53
54 if params:
54 if params:
55 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
55 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
56 return base_url
56 return base_url
57
57
58
58
59 @pytest.mark.usefixtures('app')
59 @pytest.mark.usefixtures('app')
60 class TestSummaryView(object):
60 class TestSummaryView(object):
61 def test_index(self, autologin_user, backend, http_host_only_stub):
61 def test_index(self, autologin_user, backend, http_host_only_stub):
62 repo_id = backend.repo.repo_id
62 repo_id = backend.repo.repo_id
63 repo_name = backend.repo_name
63 repo_name = backend.repo_name
64 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
64 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
65 return_value=False):
65 return_value=False):
66 response = self.app.get(
66 response = self.app.get(
67 route_path('repo_summary', repo_name=repo_name))
67 route_path('repo_summary', repo_name=repo_name))
68
68
69 # repo type
69 # repo type
70 response.mustcontain(
70 response.mustcontain(
71 '<i class="icon-%s">' % (backend.alias, )
71 '<i class="icon-%s">' % (backend.alias, )
72 )
72 )
73 # public/private
73 # public/private
74 response.mustcontain(
74 response.mustcontain(
75 """<i class="icon-unlock-alt">"""
75 """<i class="icon-unlock-alt">"""
76 )
76 )
77
77
78 # clone url...
78 # clone url...
79 response.mustcontain(
79 response.mustcontain(
80 'id="clone_url" readonly="readonly"'
80 'id="clone_url" readonly="readonly"'
81 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
81 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
82 response.mustcontain(
82 response.mustcontain(
83 'id="clone_url_id" readonly="readonly"'
83 'id="clone_url_id" readonly="readonly"'
84 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
84 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
85
85
86 def test_index_svn_without_proxy(
86 def test_index_svn_without_proxy(
87 self, autologin_user, backend_svn, http_host_only_stub):
87 self, autologin_user, backend_svn, http_host_only_stub):
88 repo_id = backend_svn.repo.repo_id
88 repo_id = backend_svn.repo.repo_id
89 repo_name = backend_svn.repo_name
89 repo_name = backend_svn.repo_name
90 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
90 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
91 # clone url...
91 # clone url...
92 response.mustcontain(
92 response.mustcontain(
93 'id="clone_url" disabled'
93 'id="clone_url" disabled'
94 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
94 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
95 response.mustcontain(
95 response.mustcontain(
96 'id="clone_url_id" disabled'
96 'id="clone_url_id" disabled'
97 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
97 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
98
98
99 def test_index_with_trailing_slash(
99 def test_index_with_trailing_slash(
100 self, autologin_user, backend, http_host_only_stub):
100 self, autologin_user, backend, http_host_only_stub):
101
101
102 repo_id = backend.repo.repo_id
102 repo_id = backend.repo.repo_id
103 repo_name = backend.repo_name
103 repo_name = backend.repo_name
104 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
104 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
105 return_value=False):
105 return_value=False):
106 response = self.app.get(
106 response = self.app.get(
107 route_path('repo_summary', repo_name=repo_name) + '/',
107 route_path('repo_summary', repo_name=repo_name) + '/',
108 status=200)
108 status=200)
109
109
110 # clone url...
110 # clone url...
111 response.mustcontain(
111 response.mustcontain(
112 'id="clone_url" readonly="readonly"'
112 'id="clone_url" readonly="readonly"'
113 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
113 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
114 response.mustcontain(
114 response.mustcontain(
115 'id="clone_url_id" readonly="readonly"'
115 'id="clone_url_id" readonly="readonly"'
116 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
116 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
117
117
118 def test_index_by_id(self, autologin_user, backend):
118 def test_index_by_id(self, autologin_user, backend):
119 repo_id = backend.repo.repo_id
119 repo_id = backend.repo.repo_id
120 response = self.app.get(
120 response = self.app.get(
121 route_path('repo_summary', repo_name='_%s' % (repo_id,)))
121 route_path('repo_summary', repo_name='_%s' % (repo_id,)))
122
122
123 # repo type
123 # repo type
124 response.mustcontain(
124 response.mustcontain(
125 '<i class="icon-%s">' % (backend.alias, )
125 '<i class="icon-%s">' % (backend.alias, )
126 )
126 )
127 # public/private
127 # public/private
128 response.mustcontain(
128 response.mustcontain(
129 """<i class="icon-unlock-alt">"""
129 """<i class="icon-unlock-alt">"""
130 )
130 )
131
131
132 def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user):
132 def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user):
133 fixture.create_repo(name='repo_1')
133 fixture.create_repo(name='repo_1')
134 response = self.app.get(route_path('repo_summary', repo_name='repo_1'))
134 response = self.app.get(route_path('repo_summary', repo_name='repo_1'))
135
135
136 try:
136 try:
137 response.mustcontain("repo_1")
137 response.mustcontain("repo_1")
138 finally:
138 finally:
139 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
139 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
140 Session().commit()
140 Session().commit()
141
141
142 def test_index_with_anonymous_access_disabled(
142 def test_index_with_anonymous_access_disabled(
143 self, backend, disable_anonymous_user):
143 self, backend, disable_anonymous_user):
144 response = self.app.get(
144 response = self.app.get(
145 route_path('repo_summary', repo_name=backend.repo_name), status=302)
145 route_path('repo_summary', repo_name=backend.repo_name), status=302)
146 assert 'login' in response.location
146 assert 'login' in response.location
147
147
148 def _enable_stats(self, repo):
148 def _enable_stats(self, repo):
149 r = Repository.get_by_repo_name(repo)
149 r = Repository.get_by_repo_name(repo)
150 r.enable_statistics = True
150 r.enable_statistics = True
151 Session().add(r)
151 Session().add(r)
152 Session().commit()
152 Session().commit()
153
153
154 expected_trending = {
154 expected_trending = {
155 'hg': {
155 'hg': {
156 "py": {"count": 68, "desc": ["Python"]},
156 "py": {"count": 68, "desc": ["Python"]},
157 "rst": {"count": 16, "desc": ["Rst"]},
157 "rst": {"count": 16, "desc": ["Rst"]},
158 "css": {"count": 2, "desc": ["Css"]},
158 "css": {"count": 2, "desc": ["Css"]},
159 "sh": {"count": 2, "desc": ["Bash"]},
159 "sh": {"count": 2, "desc": ["Bash"]},
160 "bat": {"count": 1, "desc": ["Batch"]},
160 "bat": {"count": 1, "desc": ["Batch"]},
161 "cfg": {"count": 1, "desc": ["Ini"]},
161 "cfg": {"count": 1, "desc": ["Ini"]},
162 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
162 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
163 "ini": {"count": 1, "desc": ["Ini"]},
163 "ini": {"count": 1, "desc": ["Ini"]},
164 "js": {"count": 1, "desc": ["Javascript"]},
164 "js": {"count": 1, "desc": ["Javascript"]},
165 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
165 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
166 },
166 },
167 'git': {
167 'git': {
168 "py": {"count": 68, "desc": ["Python"]},
168 "py": {"count": 68, "desc": ["Python"]},
169 "rst": {"count": 16, "desc": ["Rst"]},
169 "rst": {"count": 16, "desc": ["Rst"]},
170 "css": {"count": 2, "desc": ["Css"]},
170 "css": {"count": 2, "desc": ["Css"]},
171 "sh": {"count": 2, "desc": ["Bash"]},
171 "sh": {"count": 2, "desc": ["Bash"]},
172 "bat": {"count": 1, "desc": ["Batch"]},
172 "bat": {"count": 1, "desc": ["Batch"]},
173 "cfg": {"count": 1, "desc": ["Ini"]},
173 "cfg": {"count": 1, "desc": ["Ini"]},
174 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
174 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
175 "ini": {"count": 1, "desc": ["Ini"]},
175 "ini": {"count": 1, "desc": ["Ini"]},
176 "js": {"count": 1, "desc": ["Javascript"]},
176 "js": {"count": 1, "desc": ["Javascript"]},
177 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
177 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
178 },
178 },
179 'svn': {
179 'svn': {
180 "py": {"count": 75, "desc": ["Python"]},
180 "py": {"count": 75, "desc": ["Python"]},
181 "rst": {"count": 16, "desc": ["Rst"]},
181 "rst": {"count": 16, "desc": ["Rst"]},
182 "html": {"count": 11, "desc": ["EvoqueHtml", "Html"]},
182 "html": {"count": 11, "desc": ["EvoqueHtml", "Html"]},
183 "css": {"count": 2, "desc": ["Css"]},
183 "css": {"count": 2, "desc": ["Css"]},
184 "bat": {"count": 1, "desc": ["Batch"]},
184 "bat": {"count": 1, "desc": ["Batch"]},
185 "cfg": {"count": 1, "desc": ["Ini"]},
185 "cfg": {"count": 1, "desc": ["Ini"]},
186 "ini": {"count": 1, "desc": ["Ini"]},
186 "ini": {"count": 1, "desc": ["Ini"]},
187 "js": {"count": 1, "desc": ["Javascript"]},
187 "js": {"count": 1, "desc": ["Javascript"]},
188 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]},
188 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]},
189 "sh": {"count": 1, "desc": ["Bash"]}
189 "sh": {"count": 1, "desc": ["Bash"]}
190 },
190 },
191 }
191 }
192
192
193 def test_repo_stats(self, autologin_user, backend, xhr_header):
193 def test_repo_stats(self, autologin_user, backend, xhr_header):
194 response = self.app.get(
194 response = self.app.get(
195 route_path(
195 route_path(
196 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
196 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
197 extra_environ=xhr_header,
197 extra_environ=xhr_header,
198 status=200)
198 status=200)
199 assert re.match(r'6[\d\.]+ KiB', response.json['size'])
199 assert re.match(r'6[\d\.]+ KiB', response.json['size'])
200
200
201 def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header):
201 def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header):
202 repo_name = backend.repo_name
202 repo_name = backend.repo_name
203
203
204 # codes stats
204 # codes stats
205 self._enable_stats(repo_name)
205 self._enable_stats(repo_name)
206 ScmModel().mark_for_invalidation(repo_name)
206 ScmModel().mark_for_invalidation(repo_name)
207
207
208 response = self.app.get(
208 response = self.app.get(
209 route_path(
209 route_path(
210 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
210 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
211 extra_environ=xhr_header,
211 extra_environ=xhr_header,
212 status=200)
212 status=200)
213
213
214 expected_data = self.expected_trending[backend.alias]
214 expected_data = self.expected_trending[backend.alias]
215 returned_stats = response.json['code_stats']
215 returned_stats = response.json['code_stats']
216 for k, v in expected_data.items():
216 for k, v in expected_data.items():
217 assert v == returned_stats[k]
217 assert v == returned_stats[k]
218
218
219 def test_repo_refs_data(self, backend):
219 def test_repo_refs_data(self, backend):
220 response = self.app.get(
220 response = self.app.get(
221 route_path('repo_refs_data', repo_name=backend.repo_name),
221 route_path('repo_refs_data', repo_name=backend.repo_name),
222 status=200)
222 status=200)
223
223
224 # Ensure that there is the correct amount of items in the result
224 # Ensure that there is the correct amount of items in the result
225 repo = backend.repo.scm_instance()
225 repo = backend.repo.scm_instance()
226 data = response.json['results']
226 data = response.json['results']
227 items = sum(len(section['children']) for section in data)
227 items = sum(len(section['children']) for section in data)
228 repo_refs = len(repo.branches) + len(repo.tags) + len(repo.bookmarks)
228 repo_refs = len(repo.branches) + len(repo.tags) + len(repo.bookmarks)
229 assert items == repo_refs
229 assert items == repo_refs
230
230
231 def test_index_shows_missing_requirements_message(
231 def test_index_shows_missing_requirements_message(
232 self, backend, autologin_user):
232 self, backend, autologin_user):
233 repo_name = backend.repo_name
233 repo_name = backend.repo_name
234 scm_patcher = mock.patch.object(
234 scm_patcher = mock.patch.object(
235 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
235 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
236
236
237 with scm_patcher:
237 with scm_patcher:
238 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
238 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
239 assert_response = AssertResponse(response)
239 assert_response = AssertResponse(response)
240 assert_response.element_contains(
240 assert_response.element_contains(
241 '.main .alert-warning strong', 'Missing requirements')
241 '.main .alert-warning strong', 'Missing requirements')
242 assert_response.element_contains(
242 assert_response.element_contains(
243 '.main .alert-warning',
243 '.main .alert-warning',
244 'Commits cannot be displayed, because this repository '
244 'Commits cannot be displayed, because this repository '
245 'uses one or more extensions, which was not enabled.')
245 'uses one or more extensions, which was not enabled.')
246
246
247 def test_missing_requirements_page_does_not_contains_switch_to(
247 def test_missing_requirements_page_does_not_contains_switch_to(
248 self, autologin_user, backend):
248 self, autologin_user, backend):
249 repo_name = backend.repo_name
249 repo_name = backend.repo_name
250 scm_patcher = mock.patch.object(
250 scm_patcher = mock.patch.object(
251 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
251 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
252
252
253 with scm_patcher:
253 with scm_patcher:
254 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
254 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
255 response.mustcontain(no='Switch To')
255 response.mustcontain(no='Switch To')
256
256
257
257
258 @pytest.mark.usefixtures('app')
258 @pytest.mark.usefixtures('app')
259 class TestRepoLocation(object):
259 class TestRepoLocation(object):
260
260
261 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
261 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
262 def test_missing_filesystem_repo(
262 def test_missing_filesystem_repo(
263 self, autologin_user, backend, suffix, csrf_token):
263 self, autologin_user, backend, suffix, csrf_token):
264 repo = backend.create_repo(name_suffix=suffix)
264 repo = backend.create_repo(name_suffix=suffix)
265 repo_name = repo.repo_name
265 repo_name = repo.repo_name
266
266
267 # delete from file system
267 # delete from file system
268 RepoModel()._delete_filesystem_repo(repo)
268 RepoModel()._delete_filesystem_repo(repo)
269
269
270 # test if the repo is still in the database
270 # test if the repo is still in the database
271 new_repo = RepoModel().get_by_repo_name(repo_name)
271 new_repo = RepoModel().get_by_repo_name(repo_name)
272 assert new_repo.repo_name == repo_name
272 assert new_repo.repo_name == repo_name
273
273
274 # check if repo is not in the filesystem
274 # check if repo is not in the filesystem
275 assert not repo_on_filesystem(repo_name)
275 assert not repo_on_filesystem(repo_name)
276
276
277 response = self.app.get(
277 response = self.app.get(
278 route_path('repo_summary', repo_name=safe_str(repo_name)), status=302)
278 route_path('repo_summary', repo_name=safe_str(repo_name)), status=302)
279
279
280 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
280 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
281 'Please check if it exist, or is not damaged.' % repo_name
281 'Please check if it exist, or is not damaged.' % repo_name
282 assert_session_flash(response, msg)
282 assert_session_flash(response, msg)
283
283
284 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
284 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
285 def test_missing_filesystem_repo_on_repo_check(
285 def test_missing_filesystem_repo_on_repo_check(
286 self, autologin_user, backend, suffix, csrf_token):
286 self, autologin_user, backend, suffix, csrf_token):
287 repo = backend.create_repo(name_suffix=suffix)
287 repo = backend.create_repo(name_suffix=suffix)
288 repo_name = repo.repo_name
288 repo_name = repo.repo_name
289
289
290 # delete from file system
290 # delete from file system
291 RepoModel()._delete_filesystem_repo(repo)
291 RepoModel()._delete_filesystem_repo(repo)
292
292
293 # test if the repo is still in the database
293 # test if the repo is still in the database
294 new_repo = RepoModel().get_by_repo_name(repo_name)
294 new_repo = RepoModel().get_by_repo_name(repo_name)
295 assert new_repo.repo_name == repo_name
295 assert new_repo.repo_name == repo_name
296
296
297 # check if repo is not in the filesystem
297 # check if repo is not in the filesystem
298 assert not repo_on_filesystem(repo_name)
298 assert not repo_on_filesystem(repo_name)
299
299
300 # flush the session
300 # flush the session
301 self.app.get(
301 self.app.get(
302 route_path('repo_summary', repo_name=safe_str(repo_name)),
302 route_path('repo_summary', repo_name=safe_str(repo_name)),
303 status=302)
303 status=302)
304
304
305 response = self.app.get(
305 response = self.app.get(
306 route_path('repo_creating_check', repo_name=safe_str(repo_name)),
306 route_path('repo_creating_check', repo_name=safe_str(repo_name)),
307 status=200)
307 status=200)
308 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
308 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
309 'Please check if it exist, or is not damaged.' % repo_name
309 'Please check if it exist, or is not damaged.' % repo_name
310 assert_session_flash(response, msg )
310 assert_session_flash(response, msg )
311
311
312
312
313 @pytest.fixture()
313 @pytest.fixture()
314 def summary_view(context_stub, request_stub, user_util):
314 def summary_view(context_stub, request_stub, user_util):
315 """
315 """
316 Bootstrap view to test the view functions
316 Bootstrap view to test the view functions
317 """
317 """
318 request_stub.matched_route = AttributeDict(name='test_view')
318 request_stub.matched_route = AttributeDict(name='test_view')
319
319
320 request_stub.user = user_util.create_user().AuthUser
320 request_stub.user = user_util.create_user().AuthUser
321 request_stub.db_repo = user_util.create_repo()
321 request_stub.db_repo = user_util.create_repo()
322
322
323 view = RepoSummaryView(context=context_stub, request=request_stub)
323 view = RepoSummaryView(context=context_stub, request=request_stub)
324 return view
324 return view
325
325
326
326
327 @pytest.mark.usefixtures('app')
327 @pytest.mark.usefixtures('app')
328 class TestCreateReferenceData(object):
328 class TestCreateReferenceData(object):
329
329
330 @pytest.fixture
330 @pytest.fixture
331 def example_refs(self):
331 def example_refs(self):
332 section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id')))
332 section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id')))
333 example_refs = [
333 example_refs = [
334 ('section_1', section_1_refs, 't1'),
334 ('section_1', section_1_refs, 't1'),
335 ('section_2', {'c': 'c_id'}, 't2'),
335 ('section_2', {'c': 'c_id'}, 't2'),
336 ]
336 ]
337 return example_refs
337 return example_refs
338
338
339 def test_generates_refs_based_on_commit_ids(self, example_refs, summary_view):
339 def test_generates_refs_based_on_commit_ids(self, example_refs, summary_view):
340 repo = mock.Mock()
340 repo = mock.Mock()
341 repo.name = 'test-repo'
341 repo.name = 'test-repo'
342 repo.alias = 'git'
342 repo.alias = 'git'
343 full_repo_name = 'pytest-repo-group/' + repo.name
343 full_repo_name = 'pytest-repo-group/' + repo.name
344
344
345 result = summary_view._create_reference_data(
345 result = summary_view._create_reference_data(
346 repo, full_repo_name, example_refs)
346 repo, full_repo_name, example_refs)
347
347
348 expected_files_url = '/{}/files/'.format(full_repo_name)
348 expected_files_url = '/{}/files/'.format(full_repo_name)
349 expected_result = [
349 expected_result = [
350 {
350 {
351 'children': [
351 'children': [
352 {
352 {
353 'id': 'a', 'raw_id': 'a_id', 'text': 'a', 'type': 't1',
353 'id': 'a', 'raw_id': 'a_id', 'text': 'a', 'type': 't1',
354 'files_url': expected_files_url + 'a/?at=a',
354 'files_url': expected_files_url + 'a/?at=a',
355 },
355 },
356 {
356 {
357 'id': 'b', 'raw_id': 'b_id', 'text': 'b', 'type': 't1',
357 'id': 'b', 'raw_id': 'b_id', 'text': 'b', 'type': 't1',
358 'files_url': expected_files_url + 'b/?at=b',
358 'files_url': expected_files_url + 'b/?at=b',
359 }
359 }
360 ],
360 ],
361 'text': 'section_1'
361 'text': 'section_1'
362 },
362 },
363 {
363 {
364 'children': [
364 'children': [
365 {
365 {
366 'id': 'c', 'raw_id': 'c_id', 'text': 'c', 'type': 't2',
366 'id': 'c', 'raw_id': 'c_id', 'text': 'c', 'type': 't2',
367 'files_url': expected_files_url + 'c/?at=c',
367 'files_url': expected_files_url + 'c/?at=c',
368 }
368 }
369 ],
369 ],
370 'text': 'section_2'
370 'text': 'section_2'
371 }]
371 }]
372 assert result == expected_result
372 assert result == expected_result
373
373
374 def test_generates_refs_with_path_for_svn(self, example_refs, summary_view):
374 def test_generates_refs_with_path_for_svn(self, example_refs, summary_view):
375 repo = mock.Mock()
375 repo = mock.Mock()
376 repo.name = 'test-repo'
376 repo.name = 'test-repo'
377 repo.alias = 'svn'
377 repo.alias = 'svn'
378 full_repo_name = 'pytest-repo-group/' + repo.name
378 full_repo_name = 'pytest-repo-group/' + repo.name
379
379
380 result = summary_view._create_reference_data(
380 result = summary_view._create_reference_data(
381 repo, full_repo_name, example_refs)
381 repo, full_repo_name, example_refs)
382
382
383 expected_files_url = '/{}/files/'.format(full_repo_name)
383 expected_files_url = '/{}/files/'.format(full_repo_name)
384 expected_result = [
384 expected_result = [
385 {
385 {
386 'children': [
386 'children': [
387 {
387 {
388 'id': 'a@a_id', 'raw_id': 'a_id',
388 'id': 'a@a_id', 'raw_id': 'a_id',
389 'text': 'a', 'type': 't1',
389 'text': 'a', 'type': 't1',
390 'files_url': expected_files_url + 'a_id/a?at=a',
390 'files_url': expected_files_url + 'a_id/a?at=a',
391 },
391 },
392 {
392 {
393 'id': 'b@b_id', 'raw_id': 'b_id',
393 'id': 'b@b_id', 'raw_id': 'b_id',
394 'text': 'b', 'type': 't1',
394 'text': 'b', 'type': 't1',
395 'files_url': expected_files_url + 'b_id/b?at=b',
395 'files_url': expected_files_url + 'b_id/b?at=b',
396 }
396 }
397 ],
397 ],
398 'text': 'section_1'
398 'text': 'section_1'
399 },
399 },
400 {
400 {
401 'children': [
401 'children': [
402 {
402 {
403 'id': 'c@c_id', 'raw_id': 'c_id',
403 'id': 'c@c_id', 'raw_id': 'c_id',
404 'text': 'c', 'type': 't2',
404 'text': 'c', 'type': 't2',
405 'files_url': expected_files_url + 'c_id/c?at=c',
405 'files_url': expected_files_url + 'c_id/c?at=c',
406 }
406 }
407 ],
407 ],
408 'text': 'section_2'
408 'text': 'section_2'
409 }
409 }
410 ]
410 ]
411 assert result == expected_result
411 assert result == expected_result
412
412
413
413
414 class TestCreateFilesUrl(object):
414 class TestCreateFilesUrl(object):
415
415
416 def test_creates_non_svn_url(self, app, summary_view):
416 def test_creates_non_svn_url(self, app, summary_view):
417 repo = mock.Mock()
417 repo = mock.Mock()
418 repo.name = 'abcde'
418 repo.name = 'abcde'
419 full_repo_name = 'test-repo-group/' + repo.name
419 full_repo_name = 'test-repo-group/' + repo.name
420 ref_name = 'branch1'
420 ref_name = 'branch1'
421 raw_id = 'deadbeef0123456789'
421 raw_id = 'deadbeef0123456789'
422 is_svn = False
422 is_svn = False
423
423
424 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
424 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
425 result = summary_view._create_files_url(
425 result = summary_view._create_files_url(
426 repo, full_repo_name, ref_name, raw_id, is_svn)
426 repo, full_repo_name, ref_name, raw_id, is_svn)
427 url_mock.assert_called_once_with(
427 url_mock.assert_called_once_with(
428 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
428 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
429 f_path='', _query=dict(at=ref_name))
429 f_path='', _query=dict(at=ref_name))
430 assert result == url_mock.return_value
430 assert result == url_mock.return_value
431
431
432 def test_creates_svn_url(self, app, summary_view):
432 def test_creates_svn_url(self, app, summary_view):
433 repo = mock.Mock()
433 repo = mock.Mock()
434 repo.name = 'abcde'
434 repo.name = 'abcde'
435 full_repo_name = 'test-repo-group/' + repo.name
435 full_repo_name = 'test-repo-group/' + repo.name
436 ref_name = 'branch1'
436 ref_name = 'branch1'
437 raw_id = 'deadbeef0123456789'
437 raw_id = 'deadbeef0123456789'
438 is_svn = True
438 is_svn = True
439
439
440 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
440 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
441 result = summary_view._create_files_url(
441 result = summary_view._create_files_url(
442 repo, full_repo_name, ref_name, raw_id, is_svn)
442 repo, full_repo_name, ref_name, raw_id, is_svn)
443 url_mock.assert_called_once_with(
443 url_mock.assert_called_once_with(
444 'repo_files', repo_name=full_repo_name, f_path=ref_name,
444 'repo_files', repo_name=full_repo_name, f_path=ref_name,
445 commit_id=raw_id, _query=dict(at=ref_name))
445 commit_id=raw_id, _query=dict(at=ref_name))
446 assert result == url_mock.return_value
446 assert result == url_mock.return_value
447
447
448 def test_name_has_slashes(self, app, summary_view):
448 def test_name_has_slashes(self, app, summary_view):
449 repo = mock.Mock()
449 repo = mock.Mock()
450 repo.name = 'abcde'
450 repo.name = 'abcde'
451 full_repo_name = 'test-repo-group/' + repo.name
451 full_repo_name = 'test-repo-group/' + repo.name
452 ref_name = 'branch1/branch2'
452 ref_name = 'branch1/branch2'
453 raw_id = 'deadbeef0123456789'
453 raw_id = 'deadbeef0123456789'
454 is_svn = False
454 is_svn = False
455
455
456 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
456 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
457 result = summary_view._create_files_url(
457 result = summary_view._create_files_url(
458 repo, full_repo_name, ref_name, raw_id, is_svn)
458 repo, full_repo_name, ref_name, raw_id, is_svn)
459 url_mock.assert_called_once_with(
459 url_mock.assert_called_once_with(
460 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
460 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
461 f_path='', _query=dict(at=ref_name))
461 f_path='', _query=dict(at=ref_name))
462 assert result == url_mock.return_value
462 assert result == url_mock.return_value
463
463
464
464
465 class TestReferenceItems(object):
465 class TestReferenceItems(object):
466 repo = mock.Mock()
466 repo = mock.Mock()
467 repo.name = 'pytest-repo'
467 repo.name = 'pytest-repo'
468 repo_full_name = 'pytest-repo-group/' + repo.name
468 repo_full_name = 'pytest-repo-group/' + repo.name
469 ref_type = 'branch'
469 ref_type = 'branch'
470 fake_url = '/abcde/'
470 fake_url = '/abcde/'
471
471
472 @staticmethod
472 @staticmethod
473 def _format_function(name, id_):
473 def _format_function(name, id_):
474 return 'format_function_{}_{}'.format(name, id_)
474 return 'format_function_{}_{}'.format(name, id_)
475
475
476 def test_creates_required_amount_of_items(self, summary_view):
476 def test_creates_required_amount_of_items(self, summary_view):
477 amount = 100
477 amount = 100
478 refs = {
478 refs = {
479 'ref{}'.format(i): '{0:040d}'.format(i)
479 'ref{}'.format(i): '{0:040d}'.format(i)
480 for i in range(amount)
480 for i in range(amount)
481 }
481 }
482
482
483 url_patcher = mock.patch.object(summary_view, '_create_files_url')
483 url_patcher = mock.patch.object(summary_view, '_create_files_url')
484 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
484 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
485 return_value=False)
485 return_value=False)
486
486
487 with url_patcher as url_mock, svn_patcher:
487 with url_patcher as url_mock, svn_patcher:
488 result = summary_view._create_reference_items(
488 result = summary_view._create_reference_items(
489 self.repo, self.repo_full_name, refs, self.ref_type,
489 self.repo, self.repo_full_name, refs, self.ref_type,
490 self._format_function)
490 self._format_function)
491 assert len(result) == amount
491 assert len(result) == amount
492 assert url_mock.call_count == amount
492 assert url_mock.call_count == amount
493
493
494 def test_single_item_details(self, summary_view):
494 def test_single_item_details(self, summary_view):
495 ref_name = 'ref1'
495 ref_name = 'ref1'
496 ref_id = 'deadbeef'
496 ref_id = 'deadbeef'
497 refs = {
497 refs = {
498 ref_name: ref_id
498 ref_name: ref_id
499 }
499 }
500
500
501 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
501 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
502 return_value=False)
502 return_value=False)
503
503
504 url_patcher = mock.patch.object(
504 url_patcher = mock.patch.object(
505 summary_view, '_create_files_url', return_value=self.fake_url)
505 summary_view, '_create_files_url', return_value=self.fake_url)
506
506
507 with url_patcher as url_mock, svn_patcher:
507 with url_patcher as url_mock, svn_patcher:
508 result = summary_view._create_reference_items(
508 result = summary_view._create_reference_items(
509 self.repo, self.repo_full_name, refs, self.ref_type,
509 self.repo, self.repo_full_name, refs, self.ref_type,
510 self._format_function)
510 self._format_function)
511
511
512 url_mock.assert_called_once_with(
512 url_mock.assert_called_once_with(
513 self.repo, self.repo_full_name, ref_name, ref_id, False)
513 self.repo, self.repo_full_name, ref_name, ref_id, False)
514 expected_result = [
514 expected_result = [
515 {
515 {
516 'text': ref_name,
516 'text': ref_name,
517 'id': self._format_function(ref_name, ref_id),
517 'id': self._format_function(ref_name, ref_id),
518 'raw_id': ref_id,
518 'raw_id': ref_id,
519 'type': self.ref_type,
519 'type': self.ref_type,
520 'files_url': self.fake_url
520 'files_url': self.fake_url
521 }
521 }
522 ]
522 ]
523 assert result == expected_result
523 assert result == expected_result
@@ -1,515 +1,508 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
179 # ADMIN REPOSITORY ROUTES
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 controller='admin/repos') as m:
181 controller='admin/repos') as m:
182 m.connect('repos', '/repos',
182 m.connect('repos', '/repos',
183 action='create', conditions={'method': ['POST']})
183 action='create', conditions={'method': ['POST']})
184 m.connect('repos', '/repos',
184 m.connect('repos', '/repos',
185 action='index', conditions={'method': ['GET']})
185 action='index', conditions={'method': ['GET']})
186 m.connect('new_repo', '/create_repository', jsroute=True,
186 m.connect('new_repo', '/create_repository', jsroute=True,
187 action='create_repository', conditions={'method': ['GET']})
187 action='create_repository', conditions={'method': ['GET']})
188 m.connect('delete_repo', '/repos/{repo_name}',
188 m.connect('delete_repo', '/repos/{repo_name}',
189 action='delete', conditions={'method': ['DELETE']},
189 action='delete', conditions={'method': ['DELETE']},
190 requirements=URL_NAME_REQUIREMENTS)
190 requirements=URL_NAME_REQUIREMENTS)
191 m.connect('repo', '/repos/{repo_name}',
191 m.connect('repo', '/repos/{repo_name}',
192 action='show', conditions={'method': ['GET'],
192 action='show', conditions={'method': ['GET'],
193 'function': check_repo},
193 'function': check_repo},
194 requirements=URL_NAME_REQUIREMENTS)
194 requirements=URL_NAME_REQUIREMENTS)
195
195
196 # ADMIN REPOSITORY GROUPS ROUTES
196 # ADMIN REPOSITORY GROUPS ROUTES
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
198 controller='admin/repo_groups') as m:
198 controller='admin/repo_groups') as m:
199 m.connect('repo_groups', '/repo_groups',
199 m.connect('repo_groups', '/repo_groups',
200 action='create', conditions={'method': ['POST']})
200 action='create', conditions={'method': ['POST']})
201 m.connect('repo_groups', '/repo_groups',
201 m.connect('repo_groups', '/repo_groups',
202 action='index', conditions={'method': ['GET']})
202 action='index', conditions={'method': ['GET']})
203 m.connect('new_repo_group', '/repo_groups/new',
203 m.connect('new_repo_group', '/repo_groups/new',
204 action='new', conditions={'method': ['GET']})
204 action='new', conditions={'method': ['GET']})
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
206 action='update', conditions={'method': ['PUT'],
206 action='update', conditions={'method': ['PUT'],
207 'function': check_group},
207 'function': check_group},
208 requirements=URL_NAME_REQUIREMENTS)
208 requirements=URL_NAME_REQUIREMENTS)
209
209
210 # EXTRAS REPO GROUP ROUTES
210 # EXTRAS REPO GROUP ROUTES
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
212 action='edit',
212 action='edit',
213 conditions={'method': ['GET'], 'function': check_group},
213 conditions={'method': ['GET'], 'function': check_group},
214 requirements=URL_NAME_REQUIREMENTS)
214 requirements=URL_NAME_REQUIREMENTS)
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
216 action='edit',
216 action='edit',
217 conditions={'method': ['PUT'], 'function': check_group},
217 conditions={'method': ['PUT'], 'function': check_group},
218 requirements=URL_NAME_REQUIREMENTS)
218 requirements=URL_NAME_REQUIREMENTS)
219
219
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
221 action='edit_repo_group_advanced',
221 action='edit_repo_group_advanced',
222 conditions={'method': ['GET'], 'function': check_group},
222 conditions={'method': ['GET'], 'function': check_group},
223 requirements=URL_NAME_REQUIREMENTS)
223 requirements=URL_NAME_REQUIREMENTS)
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
225 action='edit_repo_group_advanced',
225 action='edit_repo_group_advanced',
226 conditions={'method': ['PUT'], 'function': check_group},
226 conditions={'method': ['PUT'], 'function': check_group},
227 requirements=URL_NAME_REQUIREMENTS)
227 requirements=URL_NAME_REQUIREMENTS)
228
228
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
230 action='edit_repo_group_perms',
230 action='edit_repo_group_perms',
231 conditions={'method': ['GET'], 'function': check_group},
231 conditions={'method': ['GET'], 'function': check_group},
232 requirements=URL_NAME_REQUIREMENTS)
232 requirements=URL_NAME_REQUIREMENTS)
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
234 action='update_perms',
234 action='update_perms',
235 conditions={'method': ['PUT'], 'function': check_group},
235 conditions={'method': ['PUT'], 'function': check_group},
236 requirements=URL_NAME_REQUIREMENTS)
236 requirements=URL_NAME_REQUIREMENTS)
237
237
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
239 action='delete', conditions={'method': ['DELETE'],
239 action='delete', conditions={'method': ['DELETE'],
240 'function': check_group},
240 'function': check_group},
241 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
242
242
243 # ADMIN USER ROUTES
243 # ADMIN USER ROUTES
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 controller='admin/users') as m:
245 controller='admin/users') as m:
246 m.connect('users', '/users',
246 m.connect('users', '/users',
247 action='create', conditions={'method': ['POST']})
247 action='create', conditions={'method': ['POST']})
248 m.connect('new_user', '/users/new',
248 m.connect('new_user', '/users/new',
249 action='new', conditions={'method': ['GET']})
249 action='new', conditions={'method': ['GET']})
250 m.connect('update_user', '/users/{user_id}',
250 m.connect('update_user', '/users/{user_id}',
251 action='update', conditions={'method': ['PUT']})
251 action='update', conditions={'method': ['PUT']})
252 m.connect('delete_user', '/users/{user_id}',
252 m.connect('delete_user', '/users/{user_id}',
253 action='delete', conditions={'method': ['DELETE']})
253 action='delete', conditions={'method': ['DELETE']})
254 m.connect('edit_user', '/users/{user_id}/edit',
254 m.connect('edit_user', '/users/{user_id}/edit',
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
256 m.connect('user', '/users/{user_id}',
256 m.connect('user', '/users/{user_id}',
257 action='show', conditions={'method': ['GET']})
257 action='show', conditions={'method': ['GET']})
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
259 action='reset_password', conditions={'method': ['POST']})
259 action='reset_password', conditions={'method': ['POST']})
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
261 action='create_personal_repo_group', conditions={'method': ['POST']})
261 action='create_personal_repo_group', conditions={'method': ['POST']})
262
262
263 # EXTRAS USER ROUTES
263 # EXTRAS USER ROUTES
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
265 action='edit_advanced', conditions={'method': ['GET']})
265 action='edit_advanced', conditions={'method': ['GET']})
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
267 action='update_advanced', conditions={'method': ['PUT']})
267 action='update_advanced', conditions={'method': ['PUT']})
268
268
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
270 action='edit_global_perms', conditions={'method': ['GET']})
270 action='edit_global_perms', conditions={'method': ['GET']})
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
272 action='update_global_perms', conditions={'method': ['PUT']})
272 action='update_global_perms', conditions={'method': ['PUT']})
273
273
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
275 action='edit_perms_summary', conditions={'method': ['GET']})
275 action='edit_perms_summary', conditions={'method': ['GET']})
276
276
277 # ADMIN USER GROUPS REST ROUTES
277 # ADMIN USER GROUPS REST ROUTES
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
279 controller='admin/user_groups') as m:
279 controller='admin/user_groups') as m:
280 m.connect('users_groups', '/user_groups',
280 m.connect('users_groups', '/user_groups',
281 action='create', conditions={'method': ['POST']})
281 action='create', conditions={'method': ['POST']})
282 m.connect('new_users_group', '/user_groups/new',
282 m.connect('new_users_group', '/user_groups/new',
283 action='new', conditions={'method': ['GET']})
283 action='new', conditions={'method': ['GET']})
284 m.connect('update_users_group', '/user_groups/{user_group_id}',
284 m.connect('update_users_group', '/user_groups/{user_group_id}',
285 action='update', conditions={'method': ['PUT']})
285 action='update', conditions={'method': ['PUT']})
286 m.connect('delete_users_group', '/user_groups/{user_group_id}',
286 m.connect('delete_users_group', '/user_groups/{user_group_id}',
287 action='delete', conditions={'method': ['DELETE']})
287 action='delete', conditions={'method': ['DELETE']})
288 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
288 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
289 action='edit', conditions={'method': ['GET']},
289 action='edit', conditions={'method': ['GET']},
290 function=check_user_group)
290 function=check_user_group)
291
291
292 # EXTRAS USER GROUP ROUTES
292 # EXTRAS USER GROUP ROUTES
293 m.connect('edit_user_group_global_perms',
293 m.connect('edit_user_group_global_perms',
294 '/user_groups/{user_group_id}/edit/global_permissions',
294 '/user_groups/{user_group_id}/edit/global_permissions',
295 action='edit_global_perms', conditions={'method': ['GET']})
295 action='edit_global_perms', conditions={'method': ['GET']})
296 m.connect('edit_user_group_global_perms',
296 m.connect('edit_user_group_global_perms',
297 '/user_groups/{user_group_id}/edit/global_permissions',
297 '/user_groups/{user_group_id}/edit/global_permissions',
298 action='update_global_perms', conditions={'method': ['PUT']})
298 action='update_global_perms', conditions={'method': ['PUT']})
299 m.connect('edit_user_group_perms_summary',
299 m.connect('edit_user_group_perms_summary',
300 '/user_groups/{user_group_id}/edit/permissions_summary',
300 '/user_groups/{user_group_id}/edit/permissions_summary',
301 action='edit_perms_summary', conditions={'method': ['GET']})
301 action='edit_perms_summary', conditions={'method': ['GET']})
302
302
303 m.connect('edit_user_group_perms',
303 m.connect('edit_user_group_perms',
304 '/user_groups/{user_group_id}/edit/permissions',
304 '/user_groups/{user_group_id}/edit/permissions',
305 action='edit_perms', conditions={'method': ['GET']})
305 action='edit_perms', conditions={'method': ['GET']})
306 m.connect('edit_user_group_perms',
306 m.connect('edit_user_group_perms',
307 '/user_groups/{user_group_id}/edit/permissions',
307 '/user_groups/{user_group_id}/edit/permissions',
308 action='update_perms', conditions={'method': ['PUT']})
308 action='update_perms', conditions={'method': ['PUT']})
309
309
310 m.connect('edit_user_group_advanced',
310 m.connect('edit_user_group_advanced',
311 '/user_groups/{user_group_id}/edit/advanced',
311 '/user_groups/{user_group_id}/edit/advanced',
312 action='edit_advanced', conditions={'method': ['GET']})
312 action='edit_advanced', conditions={'method': ['GET']})
313
313
314 m.connect('edit_user_group_advanced_sync',
314 m.connect('edit_user_group_advanced_sync',
315 '/user_groups/{user_group_id}/edit/advanced/sync',
315 '/user_groups/{user_group_id}/edit/advanced/sync',
316 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
316 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
317
317
318 # ADMIN DEFAULTS REST ROUTES
318 # ADMIN DEFAULTS REST ROUTES
319 with rmap.submapper(path_prefix=ADMIN_PREFIX,
319 with rmap.submapper(path_prefix=ADMIN_PREFIX,
320 controller='admin/defaults') as m:
320 controller='admin/defaults') as m:
321 m.connect('admin_defaults_repositories', '/defaults/repositories',
321 m.connect('admin_defaults_repositories', '/defaults/repositories',
322 action='update_repository_defaults', conditions={'method': ['POST']})
322 action='update_repository_defaults', conditions={'method': ['POST']})
323 m.connect('admin_defaults_repositories', '/defaults/repositories',
323 m.connect('admin_defaults_repositories', '/defaults/repositories',
324 action='index', conditions={'method': ['GET']})
324 action='index', conditions={'method': ['GET']})
325
325
326 # ADMIN SETTINGS ROUTES
326 # ADMIN SETTINGS ROUTES
327 with rmap.submapper(path_prefix=ADMIN_PREFIX,
327 with rmap.submapper(path_prefix=ADMIN_PREFIX,
328 controller='admin/settings') as m:
328 controller='admin/settings') as m:
329
329
330 # default
330 # default
331 m.connect('admin_settings', '/settings',
331 m.connect('admin_settings', '/settings',
332 action='settings_global_update',
332 action='settings_global_update',
333 conditions={'method': ['POST']})
333 conditions={'method': ['POST']})
334 m.connect('admin_settings', '/settings',
334 m.connect('admin_settings', '/settings',
335 action='settings_global', conditions={'method': ['GET']})
335 action='settings_global', conditions={'method': ['GET']})
336
336
337 m.connect('admin_settings_vcs', '/settings/vcs',
337 m.connect('admin_settings_vcs', '/settings/vcs',
338 action='settings_vcs_update',
338 action='settings_vcs_update',
339 conditions={'method': ['POST']})
339 conditions={'method': ['POST']})
340 m.connect('admin_settings_vcs', '/settings/vcs',
340 m.connect('admin_settings_vcs', '/settings/vcs',
341 action='settings_vcs',
341 action='settings_vcs',
342 conditions={'method': ['GET']})
342 conditions={'method': ['GET']})
343 m.connect('admin_settings_vcs', '/settings/vcs',
343 m.connect('admin_settings_vcs', '/settings/vcs',
344 action='delete_svn_pattern',
344 action='delete_svn_pattern',
345 conditions={'method': ['DELETE']})
345 conditions={'method': ['DELETE']})
346
346
347 m.connect('admin_settings_mapping', '/settings/mapping',
347 m.connect('admin_settings_mapping', '/settings/mapping',
348 action='settings_mapping_update',
348 action='settings_mapping_update',
349 conditions={'method': ['POST']})
349 conditions={'method': ['POST']})
350 m.connect('admin_settings_mapping', '/settings/mapping',
350 m.connect('admin_settings_mapping', '/settings/mapping',
351 action='settings_mapping', conditions={'method': ['GET']})
351 action='settings_mapping', conditions={'method': ['GET']})
352
352
353 m.connect('admin_settings_global', '/settings/global',
353 m.connect('admin_settings_global', '/settings/global',
354 action='settings_global_update',
354 action='settings_global_update',
355 conditions={'method': ['POST']})
355 conditions={'method': ['POST']})
356 m.connect('admin_settings_global', '/settings/global',
356 m.connect('admin_settings_global', '/settings/global',
357 action='settings_global', conditions={'method': ['GET']})
357 action='settings_global', conditions={'method': ['GET']})
358
358
359 m.connect('admin_settings_visual', '/settings/visual',
359 m.connect('admin_settings_visual', '/settings/visual',
360 action='settings_visual_update',
360 action='settings_visual_update',
361 conditions={'method': ['POST']})
361 conditions={'method': ['POST']})
362 m.connect('admin_settings_visual', '/settings/visual',
362 m.connect('admin_settings_visual', '/settings/visual',
363 action='settings_visual', conditions={'method': ['GET']})
363 action='settings_visual', conditions={'method': ['GET']})
364
364
365 m.connect('admin_settings_issuetracker',
365 m.connect('admin_settings_issuetracker',
366 '/settings/issue-tracker', action='settings_issuetracker',
366 '/settings/issue-tracker', action='settings_issuetracker',
367 conditions={'method': ['GET']})
367 conditions={'method': ['GET']})
368 m.connect('admin_settings_issuetracker_save',
368 m.connect('admin_settings_issuetracker_save',
369 '/settings/issue-tracker/save',
369 '/settings/issue-tracker/save',
370 action='settings_issuetracker_save',
370 action='settings_issuetracker_save',
371 conditions={'method': ['POST']})
371 conditions={'method': ['POST']})
372 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
372 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
373 action='settings_issuetracker_test',
373 action='settings_issuetracker_test',
374 conditions={'method': ['POST']})
374 conditions={'method': ['POST']})
375 m.connect('admin_issuetracker_delete',
375 m.connect('admin_issuetracker_delete',
376 '/settings/issue-tracker/delete',
376 '/settings/issue-tracker/delete',
377 action='settings_issuetracker_delete',
377 action='settings_issuetracker_delete',
378 conditions={'method': ['DELETE']})
378 conditions={'method': ['DELETE']})
379
379
380 m.connect('admin_settings_email', '/settings/email',
380 m.connect('admin_settings_email', '/settings/email',
381 action='settings_email_update',
381 action='settings_email_update',
382 conditions={'method': ['POST']})
382 conditions={'method': ['POST']})
383 m.connect('admin_settings_email', '/settings/email',
383 m.connect('admin_settings_email', '/settings/email',
384 action='settings_email', conditions={'method': ['GET']})
384 action='settings_email', conditions={'method': ['GET']})
385
385
386 m.connect('admin_settings_hooks', '/settings/hooks',
386 m.connect('admin_settings_hooks', '/settings/hooks',
387 action='settings_hooks_update',
387 action='settings_hooks_update',
388 conditions={'method': ['POST', 'DELETE']})
388 conditions={'method': ['POST', 'DELETE']})
389 m.connect('admin_settings_hooks', '/settings/hooks',
389 m.connect('admin_settings_hooks', '/settings/hooks',
390 action='settings_hooks', conditions={'method': ['GET']})
390 action='settings_hooks', conditions={'method': ['GET']})
391
391
392 m.connect('admin_settings_search', '/settings/search',
392 m.connect('admin_settings_search', '/settings/search',
393 action='settings_search', conditions={'method': ['GET']})
393 action='settings_search', conditions={'method': ['GET']})
394
394
395 m.connect('admin_settings_supervisor', '/settings/supervisor',
395 m.connect('admin_settings_supervisor', '/settings/supervisor',
396 action='settings_supervisor', conditions={'method': ['GET']})
396 action='settings_supervisor', conditions={'method': ['GET']})
397 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
397 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
398 action='settings_supervisor_log', conditions={'method': ['GET']})
398 action='settings_supervisor_log', conditions={'method': ['GET']})
399
399
400 m.connect('admin_settings_labs', '/settings/labs',
400 m.connect('admin_settings_labs', '/settings/labs',
401 action='settings_labs_update',
401 action='settings_labs_update',
402 conditions={'method': ['POST']})
402 conditions={'method': ['POST']})
403 m.connect('admin_settings_labs', '/settings/labs',
403 m.connect('admin_settings_labs', '/settings/labs',
404 action='settings_labs', conditions={'method': ['GET']})
404 action='settings_labs', conditions={'method': ['GET']})
405
405
406 # ADMIN MY ACCOUNT
406 # ADMIN MY ACCOUNT
407 with rmap.submapper(path_prefix=ADMIN_PREFIX,
407 with rmap.submapper(path_prefix=ADMIN_PREFIX,
408 controller='admin/my_account') as m:
408 controller='admin/my_account') as m:
409
409
410 # NOTE(marcink): this needs to be kept for password force flag to be
410 # NOTE(marcink): this needs to be kept for password force flag to be
411 # handled in pylons controllers, remove after full migration to pyramid
411 # handled in pylons controllers, remove after full migration to pyramid
412 m.connect('my_account_password', '/my_account/password',
412 m.connect('my_account_password', '/my_account/password',
413 action='my_account_password', conditions={'method': ['GET']})
413 action='my_account_password', conditions={'method': ['GET']})
414
414
415 #==========================================================================
415 #==========================================================================
416 # REPOSITORY ROUTES
416 # REPOSITORY ROUTES
417 #==========================================================================
417 #==========================================================================
418
418
419 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
420 controller='admin/repos', action='repo_creating',
421 requirements=URL_NAME_REQUIREMENTS)
422 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
423 controller='admin/repos', action='repo_check',
424 requirements=URL_NAME_REQUIREMENTS)
425
426 # repo edit options
419 # repo edit options
427 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
420 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
428 controller='admin/repos', action='edit_fields',
421 controller='admin/repos', action='edit_fields',
429 conditions={'method': ['GET'], 'function': check_repo},
422 conditions={'method': ['GET'], 'function': check_repo},
430 requirements=URL_NAME_REQUIREMENTS)
423 requirements=URL_NAME_REQUIREMENTS)
431 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
424 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
432 controller='admin/repos', action='create_repo_field',
425 controller='admin/repos', action='create_repo_field',
433 conditions={'method': ['PUT'], 'function': check_repo},
426 conditions={'method': ['PUT'], 'function': check_repo},
434 requirements=URL_NAME_REQUIREMENTS)
427 requirements=URL_NAME_REQUIREMENTS)
435 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
428 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
436 controller='admin/repos', action='delete_repo_field',
429 controller='admin/repos', action='delete_repo_field',
437 conditions={'method': ['DELETE'], 'function': check_repo},
430 conditions={'method': ['DELETE'], 'function': check_repo},
438 requirements=URL_NAME_REQUIREMENTS)
431 requirements=URL_NAME_REQUIREMENTS)
439
432
440 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
433 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
441 controller='admin/repos', action='toggle_locking',
434 controller='admin/repos', action='toggle_locking',
442 conditions={'method': ['GET'], 'function': check_repo},
435 conditions={'method': ['GET'], 'function': check_repo},
443 requirements=URL_NAME_REQUIREMENTS)
436 requirements=URL_NAME_REQUIREMENTS)
444
437
445 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
438 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
446 controller='admin/repos', action='edit_remote_form',
439 controller='admin/repos', action='edit_remote_form',
447 conditions={'method': ['GET'], 'function': check_repo},
440 conditions={'method': ['GET'], 'function': check_repo},
448 requirements=URL_NAME_REQUIREMENTS)
441 requirements=URL_NAME_REQUIREMENTS)
449 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
442 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
450 controller='admin/repos', action='edit_remote',
443 controller='admin/repos', action='edit_remote',
451 conditions={'method': ['PUT'], 'function': check_repo},
444 conditions={'method': ['PUT'], 'function': check_repo},
452 requirements=URL_NAME_REQUIREMENTS)
445 requirements=URL_NAME_REQUIREMENTS)
453
446
454 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
447 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
455 controller='admin/repos', action='edit_statistics_form',
448 controller='admin/repos', action='edit_statistics_form',
456 conditions={'method': ['GET'], 'function': check_repo},
449 conditions={'method': ['GET'], 'function': check_repo},
457 requirements=URL_NAME_REQUIREMENTS)
450 requirements=URL_NAME_REQUIREMENTS)
458 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
451 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
459 controller='admin/repos', action='edit_statistics',
452 controller='admin/repos', action='edit_statistics',
460 conditions={'method': ['PUT'], 'function': check_repo},
453 conditions={'method': ['PUT'], 'function': check_repo},
461 requirements=URL_NAME_REQUIREMENTS)
454 requirements=URL_NAME_REQUIREMENTS)
462 rmap.connect('repo_settings_issuetracker',
455 rmap.connect('repo_settings_issuetracker',
463 '/{repo_name}/settings/issue-tracker',
456 '/{repo_name}/settings/issue-tracker',
464 controller='admin/repos', action='repo_issuetracker',
457 controller='admin/repos', action='repo_issuetracker',
465 conditions={'method': ['GET'], 'function': check_repo},
458 conditions={'method': ['GET'], 'function': check_repo},
466 requirements=URL_NAME_REQUIREMENTS)
459 requirements=URL_NAME_REQUIREMENTS)
467 rmap.connect('repo_issuetracker_test',
460 rmap.connect('repo_issuetracker_test',
468 '/{repo_name}/settings/issue-tracker/test',
461 '/{repo_name}/settings/issue-tracker/test',
469 controller='admin/repos', action='repo_issuetracker_test',
462 controller='admin/repos', action='repo_issuetracker_test',
470 conditions={'method': ['POST'], 'function': check_repo},
463 conditions={'method': ['POST'], 'function': check_repo},
471 requirements=URL_NAME_REQUIREMENTS)
464 requirements=URL_NAME_REQUIREMENTS)
472 rmap.connect('repo_issuetracker_delete',
465 rmap.connect('repo_issuetracker_delete',
473 '/{repo_name}/settings/issue-tracker/delete',
466 '/{repo_name}/settings/issue-tracker/delete',
474 controller='admin/repos', action='repo_issuetracker_delete',
467 controller='admin/repos', action='repo_issuetracker_delete',
475 conditions={'method': ['DELETE'], 'function': check_repo},
468 conditions={'method': ['DELETE'], 'function': check_repo},
476 requirements=URL_NAME_REQUIREMENTS)
469 requirements=URL_NAME_REQUIREMENTS)
477 rmap.connect('repo_issuetracker_save',
470 rmap.connect('repo_issuetracker_save',
478 '/{repo_name}/settings/issue-tracker/save',
471 '/{repo_name}/settings/issue-tracker/save',
479 controller='admin/repos', action='repo_issuetracker_save',
472 controller='admin/repos', action='repo_issuetracker_save',
480 conditions={'method': ['POST'], 'function': check_repo},
473 conditions={'method': ['POST'], 'function': check_repo},
481 requirements=URL_NAME_REQUIREMENTS)
474 requirements=URL_NAME_REQUIREMENTS)
482 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
475 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
483 controller='admin/repos', action='repo_settings_vcs_update',
476 controller='admin/repos', action='repo_settings_vcs_update',
484 conditions={'method': ['POST'], 'function': check_repo},
477 conditions={'method': ['POST'], 'function': check_repo},
485 requirements=URL_NAME_REQUIREMENTS)
478 requirements=URL_NAME_REQUIREMENTS)
486 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
479 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
487 controller='admin/repos', action='repo_settings_vcs',
480 controller='admin/repos', action='repo_settings_vcs',
488 conditions={'method': ['GET'], 'function': check_repo},
481 conditions={'method': ['GET'], 'function': check_repo},
489 requirements=URL_NAME_REQUIREMENTS)
482 requirements=URL_NAME_REQUIREMENTS)
490 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
483 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
491 controller='admin/repos', action='repo_delete_svn_pattern',
484 controller='admin/repos', action='repo_delete_svn_pattern',
492 conditions={'method': ['DELETE'], 'function': check_repo},
485 conditions={'method': ['DELETE'], 'function': check_repo},
493 requirements=URL_NAME_REQUIREMENTS)
486 requirements=URL_NAME_REQUIREMENTS)
494 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
487 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
495 controller='admin/repos', action='repo_settings_pullrequest',
488 controller='admin/repos', action='repo_settings_pullrequest',
496 conditions={'method': ['GET', 'POST'], 'function': check_repo},
489 conditions={'method': ['GET', 'POST'], 'function': check_repo},
497 requirements=URL_NAME_REQUIREMENTS)
490 requirements=URL_NAME_REQUIREMENTS)
498
491
499
492
500 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
493 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
501 controller='forks', action='fork_create',
494 controller='forks', action='fork_create',
502 conditions={'function': check_repo, 'method': ['POST']},
495 conditions={'function': check_repo, 'method': ['POST']},
503 requirements=URL_NAME_REQUIREMENTS)
496 requirements=URL_NAME_REQUIREMENTS)
504
497
505 rmap.connect('repo_fork_home', '/{repo_name}/fork',
498 rmap.connect('repo_fork_home', '/{repo_name}/fork',
506 controller='forks', action='fork',
499 controller='forks', action='fork',
507 conditions={'function': check_repo},
500 conditions={'function': check_repo},
508 requirements=URL_NAME_REQUIREMENTS)
501 requirements=URL_NAME_REQUIREMENTS)
509
502
510 rmap.connect('repo_forks_home', '/{repo_name}/forks',
503 rmap.connect('repo_forks_home', '/{repo_name}/forks',
511 controller='forks', action='forks',
504 controller='forks', action='forks',
512 conditions={'function': check_repo},
505 conditions={'function': check_repo},
513 requirements=URL_NAME_REQUIREMENTS)
506 requirements=URL_NAME_REQUIREMENTS)
514
507
515 return rmap
508 return rmap
@@ -1,606 +1,563 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-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 Repositories controller for RhodeCode
23 Repositories controller for RhodeCode
24 """
24 """
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 import formencode
29 import formencode
30 from formencode import htmlfill
30 from formencode import htmlfill
31 from pylons import request, tmpl_context as c, url
31 from pylons import request, tmpl_context as c, url
32 from pylons.controllers.util import redirect
32 from pylons.controllers.util import redirect
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
34 from webob.exc import HTTPForbidden, HTTPBadRequest
35
35
36 from pyramid.httpexceptions import HTTPFound
36 import rhodecode
37 import rhodecode
37 from rhodecode.lib import auth, helpers as h
38 from rhodecode.lib import auth, helpers as h
38 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
39 LoginRequired, HasPermissionAllDecorator,
40 LoginRequired, HasPermissionAllDecorator,
40 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
41 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
42 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.utils import repo_name_slug, jsonify
45 from rhodecode.lib.utils import repo_name_slug, jsonify
45 from rhodecode.lib.utils2 import safe_int, str2bool
46 from rhodecode.lib.utils2 import safe_int, str2bool
46 from rhodecode.model.db import (Repository, RepoGroup, RepositoryField)
47 from rhodecode.model.db import (Repository, RepoGroup, RepositoryField)
47 from rhodecode.model.forms import (
48 from rhodecode.model.forms import (
48 RepoForm, RepoFieldForm, RepoVcsSettingsForm, IssueTrackerPatternsForm)
49 RepoForm, RepoFieldForm, RepoVcsSettingsForm, IssueTrackerPatternsForm)
49 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
52 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
52 from rhodecode.model.settings import (
53 from rhodecode.model.settings import (
53 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
54 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
54 SettingNotFound)
55 SettingNotFound)
55
56
56 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
57
58
58
59
59 class ReposController(BaseRepoController):
60 class ReposController(BaseRepoController):
60 """
61 """
61 REST Controller styled on the Atom Publishing Protocol"""
62 REST Controller styled on the Atom Publishing Protocol"""
62 # To properly map this controller, ensure your config/routing.py
63 # To properly map this controller, ensure your config/routing.py
63 # file has a resource setup:
64 # file has a resource setup:
64 # map.resource('repo', 'repos')
65 # map.resource('repo', 'repos')
65
66
66 @LoginRequired()
67 @LoginRequired()
67 def __before__(self):
68 def __before__(self):
68 super(ReposController, self).__before__()
69 super(ReposController, self).__before__()
69
70
70 def _load_repo(self, repo_name):
71 def _load_repo(self, repo_name):
71 repo_obj = Repository.get_by_repo_name(repo_name)
72 repo_obj = Repository.get_by_repo_name(repo_name)
72
73
73 if repo_obj is None:
74 if repo_obj is None:
74 h.not_mapped_error(repo_name)
75 h.not_mapped_error(repo_name)
75 return redirect(url('repos'))
76 return redirect(url('repos'))
76
77
77 return repo_obj
78 return repo_obj
78
79
79 def __load_defaults(self, repo=None):
80 def __load_defaults(self, repo=None):
80 acl_groups = RepoGroupList(RepoGroup.query().all(),
81 acl_groups = RepoGroupList(RepoGroup.query().all(),
81 perm_set=['group.write', 'group.admin'])
82 perm_set=['group.write', 'group.admin'])
82 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
83 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
83 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
84 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
84
85
85 # in case someone no longer have a group.write access to a repository
86 # in case someone no longer have a group.write access to a repository
86 # pre fill the list with this entry, we don't care if this is the same
87 # pre fill the list with this entry, we don't care if this is the same
87 # but it will allow saving repo data properly.
88 # but it will allow saving repo data properly.
88
89
89 repo_group = None
90 repo_group = None
90 if repo:
91 if repo:
91 repo_group = repo.group
92 repo_group = repo.group
92 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
93 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
93 c.repo_groups_choices.append(unicode(repo_group.group_id))
94 c.repo_groups_choices.append(unicode(repo_group.group_id))
94 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
95 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
95
96
96 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
97 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
97 c.landing_revs_choices = choices
98 c.landing_revs_choices = choices
98
99
99 def __load_data(self, repo_name=None):
100 def __load_data(self, repo_name=None):
100 """
101 """
101 Load defaults settings for edit, and update
102 Load defaults settings for edit, and update
102
103
103 :param repo_name:
104 :param repo_name:
104 """
105 """
105 c.repo_info = self._load_repo(repo_name)
106 c.repo_info = self._load_repo(repo_name)
106 self.__load_defaults(c.repo_info)
107 self.__load_defaults(c.repo_info)
107
108
108 # override defaults for exact repo info here git/hg etc
109 # override defaults for exact repo info here git/hg etc
109 if not c.repository_requirements_missing:
110 if not c.repository_requirements_missing:
110 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
111 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
111 c.repo_info)
112 c.repo_info)
112 c.landing_revs_choices = choices
113 c.landing_revs_choices = choices
113 defaults = RepoModel()._get_defaults(repo_name)
114 defaults = RepoModel()._get_defaults(repo_name)
114
115
115 return defaults
116 return defaults
116
117
117 def _log_creation_exception(self, e, repo_name):
118 def _log_creation_exception(self, e, repo_name):
118 reason = None
119 reason = None
119 if len(e.args) == 2:
120 if len(e.args) == 2:
120 reason = e.args[1]
121 reason = e.args[1]
121
122
122 if reason == 'INVALID_CERTIFICATE':
123 if reason == 'INVALID_CERTIFICATE':
123 log.exception(
124 log.exception(
124 'Exception creating a repository: invalid certificate')
125 'Exception creating a repository: invalid certificate')
125 msg = (_('Error creating repository %s: invalid certificate')
126 msg = (_('Error creating repository %s: invalid certificate')
126 % repo_name)
127 % repo_name)
127 else:
128 else:
128 log.exception("Exception creating a repository")
129 log.exception("Exception creating a repository")
129 msg = (_('Error creating repository %s')
130 msg = (_('Error creating repository %s')
130 % repo_name)
131 % repo_name)
131
132
132 return msg
133 return msg
133
134
134 @NotAnonymous()
135 @NotAnonymous()
135 def index(self, format='html'):
136 def index(self, format='html'):
136 """GET /repos: All items in the collection"""
137 """GET /repos: All items in the collection"""
137 # url('repos')
138 # url('repos')
138
139
139 repo_list = Repository.get_all_repos()
140 repo_list = Repository.get_all_repos()
140 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
141 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
141 repos_data = RepoModel().get_repos_as_dict(
142 repos_data = RepoModel().get_repos_as_dict(
142 repo_list=c.repo_list, admin=True, super_user_actions=True)
143 repo_list=c.repo_list, admin=True, super_user_actions=True)
143 # json used to render the grid
144 # json used to render the grid
144 c.data = json.dumps(repos_data)
145 c.data = json.dumps(repos_data)
145
146
146 return render('admin/repos/repos.mako')
147 return render('admin/repos/repos.mako')
147
148
148 # perms check inside
149 # perms check inside
149 @NotAnonymous()
150 @NotAnonymous()
150 @auth.CSRFRequired()
151 @auth.CSRFRequired()
151 def create(self):
152 def create(self):
152 """
153 """
153 POST /repos: Create a new item"""
154 POST /repos: Create a new item"""
154 # url('repos')
155 # url('repos')
155
156
156 self.__load_defaults()
157 self.__load_defaults()
157 form_result = {}
158 form_result = {}
158 task_id = None
159 task_id = None
159 c.personal_repo_group = c.rhodecode_user.personal_repo_group
160 c.personal_repo_group = c.rhodecode_user.personal_repo_group
160 try:
161 try:
161 # CanWriteToGroup validators checks permissions of this POST
162 # CanWriteToGroup validators checks permissions of this POST
162 form_result = RepoForm(repo_groups=c.repo_groups_choices,
163 form_result = RepoForm(repo_groups=c.repo_groups_choices,
163 landing_revs=c.landing_revs_choices)()\
164 landing_revs=c.landing_revs_choices)()\
164 .to_python(dict(request.POST))
165 .to_python(dict(request.POST))
165
166
166 # create is done sometimes async on celery, db transaction
167 # create is done sometimes async on celery, db transaction
167 # management is handled there.
168 # management is handled there.
168 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
169 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
169 from celery.result import BaseAsyncResult
170 from celery.result import BaseAsyncResult
170 if isinstance(task, BaseAsyncResult):
171 if isinstance(task, BaseAsyncResult):
171 task_id = task.task_id
172 task_id = task.task_id
172 except formencode.Invalid as errors:
173 except formencode.Invalid as errors:
173 return htmlfill.render(
174 return htmlfill.render(
174 render('admin/repos/repo_add.mako'),
175 render('admin/repos/repo_add.mako'),
175 defaults=errors.value,
176 defaults=errors.value,
176 errors=errors.error_dict or {},
177 errors=errors.error_dict or {},
177 prefix_error=False,
178 prefix_error=False,
178 encoding="UTF-8",
179 encoding="UTF-8",
179 force_defaults=False)
180 force_defaults=False)
180
181
181 except Exception as e:
182 except Exception as e:
182 msg = self._log_creation_exception(e, form_result.get('repo_name'))
183 msg = self._log_creation_exception(e, form_result.get('repo_name'))
183 h.flash(msg, category='error')
184 h.flash(msg, category='error')
184 return redirect(h.route_path('home'))
185 return redirect(h.route_path('home'))
185
186
186 return redirect(h.url('repo_creating_home',
187 raise HTTPFound(
187 repo_name=form_result['repo_name_full'],
188 h.route_path('repo_creating',
188 task_id=task_id))
189 repo_name=form_result['repo_name_full'],
190 _query=dict(task_id=task_id)))
189
191
190 # perms check inside
192 # perms check inside
191 @NotAnonymous()
193 @NotAnonymous()
192 def create_repository(self):
194 def create_repository(self):
193 """GET /_admin/create_repository: Form to create a new item"""
195 """GET /_admin/create_repository: Form to create a new item"""
194 new_repo = request.GET.get('repo', '')
196 new_repo = request.GET.get('repo', '')
195 parent_group = safe_int(request.GET.get('parent_group'))
197 parent_group = safe_int(request.GET.get('parent_group'))
196 _gr = RepoGroup.get(parent_group)
198 _gr = RepoGroup.get(parent_group)
197
199
198 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
200 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
199 # you're not super admin nor have global create permissions,
201 # you're not super admin nor have global create permissions,
200 # but maybe you have at least write permission to a parent group ?
202 # but maybe you have at least write permission to a parent group ?
201
203
202 gr_name = _gr.group_name if _gr else None
204 gr_name = _gr.group_name if _gr else None
203 # create repositories with write permission on group is set to true
205 # create repositories with write permission on group is set to true
204 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
206 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
205 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
207 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
206 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
208 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
207 if not (group_admin or (group_write and create_on_write)):
209 if not (group_admin or (group_write and create_on_write)):
208 raise HTTPForbidden
210 raise HTTPForbidden
209
211
210 acl_groups = RepoGroupList(RepoGroup.query().all(),
212 acl_groups = RepoGroupList(RepoGroup.query().all(),
211 perm_set=['group.write', 'group.admin'])
213 perm_set=['group.write', 'group.admin'])
212 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
214 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
213 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
215 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
214 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
216 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
215 c.personal_repo_group = c.rhodecode_user.personal_repo_group
217 c.personal_repo_group = c.rhodecode_user.personal_repo_group
216 c.new_repo = repo_name_slug(new_repo)
218 c.new_repo = repo_name_slug(new_repo)
217
219
218 # apply the defaults from defaults page
220 # apply the defaults from defaults page
219 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
221 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
220 # set checkbox to autochecked
222 # set checkbox to autochecked
221 defaults['repo_copy_permissions'] = True
223 defaults['repo_copy_permissions'] = True
222
224
223 parent_group_choice = '-1'
225 parent_group_choice = '-1'
224 if not c.rhodecode_user.is_admin and c.rhodecode_user.personal_repo_group:
226 if not c.rhodecode_user.is_admin and c.rhodecode_user.personal_repo_group:
225 parent_group_choice = c.rhodecode_user.personal_repo_group
227 parent_group_choice = c.rhodecode_user.personal_repo_group
226
228
227 if parent_group and _gr:
229 if parent_group and _gr:
228 if parent_group in [x[0] for x in c.repo_groups]:
230 if parent_group in [x[0] for x in c.repo_groups]:
229 parent_group_choice = unicode(parent_group)
231 parent_group_choice = unicode(parent_group)
230
232
231 defaults.update({'repo_group': parent_group_choice})
233 defaults.update({'repo_group': parent_group_choice})
232
234
233 return htmlfill.render(
235 return htmlfill.render(
234 render('admin/repos/repo_add.mako'),
236 render('admin/repos/repo_add.mako'),
235 defaults=defaults,
237 defaults=defaults,
236 errors={},
238 errors={},
237 prefix_error=False,
239 prefix_error=False,
238 encoding="UTF-8",
240 encoding="UTF-8",
239 force_defaults=False
241 force_defaults=False
240 )
242 )
241
243
242 @NotAnonymous()
243 def repo_creating(self, repo_name):
244 c.repo = repo_name
245 c.task_id = request.GET.get('task_id')
246 if not c.repo:
247 raise HTTPNotFound()
248 return render('admin/repos/repo_creating.mako')
249
250 @NotAnonymous()
251 @jsonify
252 def repo_check(self, repo_name):
253 c.repo = repo_name
254 task_id = request.GET.get('task_id')
255
256 if task_id and task_id not in ['None']:
257 import rhodecode
258 from celery.result import AsyncResult
259 if rhodecode.CELERY_ENABLED:
260 task = AsyncResult(task_id)
261 if task.failed():
262 msg = self._log_creation_exception(task.result, c.repo)
263 h.flash(msg, category='error')
264 return redirect(h.route_path('home'), code=501)
265
266 repo = Repository.get_by_repo_name(repo_name)
267 if repo and repo.repo_state == Repository.STATE_CREATED:
268 if repo.clone_uri:
269 clone_uri = repo.clone_uri_hidden
270 h.flash(_('Created repository %s from %s')
271 % (repo.repo_name, clone_uri), category='success')
272 else:
273 repo_url = h.link_to(
274 repo.repo_name,
275 h.route_path('repo_summary',repo_name=repo.repo_name))
276 fork = repo.fork
277 if fork:
278 fork_name = fork.repo_name
279 h.flash(h.literal(_('Forked repository %s as %s')
280 % (fork_name, repo_url)), category='success')
281 else:
282 h.flash(h.literal(_('Created repository %s') % repo_url),
283 category='success')
284 return {'result': True}
285 return {'result': False}
286
287 @HasPermissionAllDecorator('hg.admin')
244 @HasPermissionAllDecorator('hg.admin')
288 def show(self, repo_name, format='html'):
245 def show(self, repo_name, format='html'):
289 """GET /repos/repo_name: Show a specific item"""
246 """GET /repos/repo_name: Show a specific item"""
290 # url('repo', repo_name=ID)
247 # url('repo', repo_name=ID)
291
248
292 @HasRepoPermissionAllDecorator('repository.admin')
249 @HasRepoPermissionAllDecorator('repository.admin')
293 def edit_fields(self, repo_name):
250 def edit_fields(self, repo_name):
294 """GET /repo_name/settings: Form to edit an existing item"""
251 """GET /repo_name/settings: Form to edit an existing item"""
295 c.repo_info = self._load_repo(repo_name)
252 c.repo_info = self._load_repo(repo_name)
296 c.repo_fields = RepositoryField.query()\
253 c.repo_fields = RepositoryField.query()\
297 .filter(RepositoryField.repository == c.repo_info).all()
254 .filter(RepositoryField.repository == c.repo_info).all()
298 c.active = 'fields'
255 c.active = 'fields'
299 if request.POST:
256 if request.POST:
300
257
301 return redirect(url('repo_edit_fields'))
258 return redirect(url('repo_edit_fields'))
302 return render('admin/repos/repo_edit.mako')
259 return render('admin/repos/repo_edit.mako')
303
260
304 @HasRepoPermissionAllDecorator('repository.admin')
261 @HasRepoPermissionAllDecorator('repository.admin')
305 @auth.CSRFRequired()
262 @auth.CSRFRequired()
306 def create_repo_field(self, repo_name):
263 def create_repo_field(self, repo_name):
307 try:
264 try:
308 form_result = RepoFieldForm()().to_python(dict(request.POST))
265 form_result = RepoFieldForm()().to_python(dict(request.POST))
309 RepoModel().add_repo_field(
266 RepoModel().add_repo_field(
310 repo_name, form_result['new_field_key'],
267 repo_name, form_result['new_field_key'],
311 field_type=form_result['new_field_type'],
268 field_type=form_result['new_field_type'],
312 field_value=form_result['new_field_value'],
269 field_value=form_result['new_field_value'],
313 field_label=form_result['new_field_label'],
270 field_label=form_result['new_field_label'],
314 field_desc=form_result['new_field_desc'])
271 field_desc=form_result['new_field_desc'])
315
272
316 Session().commit()
273 Session().commit()
317 except Exception as e:
274 except Exception as e:
318 log.exception("Exception creating field")
275 log.exception("Exception creating field")
319 msg = _('An error occurred during creation of field')
276 msg = _('An error occurred during creation of field')
320 if isinstance(e, formencode.Invalid):
277 if isinstance(e, formencode.Invalid):
321 msg += ". " + e.msg
278 msg += ". " + e.msg
322 h.flash(msg, category='error')
279 h.flash(msg, category='error')
323 return redirect(url('edit_repo_fields', repo_name=repo_name))
280 return redirect(url('edit_repo_fields', repo_name=repo_name))
324
281
325 @HasRepoPermissionAllDecorator('repository.admin')
282 @HasRepoPermissionAllDecorator('repository.admin')
326 @auth.CSRFRequired()
283 @auth.CSRFRequired()
327 def delete_repo_field(self, repo_name, field_id):
284 def delete_repo_field(self, repo_name, field_id):
328 field = RepositoryField.get_or_404(field_id)
285 field = RepositoryField.get_or_404(field_id)
329 try:
286 try:
330 RepoModel().delete_repo_field(repo_name, field.field_key)
287 RepoModel().delete_repo_field(repo_name, field.field_key)
331 Session().commit()
288 Session().commit()
332 except Exception as e:
289 except Exception as e:
333 log.exception("Exception during removal of field")
290 log.exception("Exception during removal of field")
334 msg = _('An error occurred during removal of field')
291 msg = _('An error occurred during removal of field')
335 h.flash(msg, category='error')
292 h.flash(msg, category='error')
336 return redirect(url('edit_repo_fields', repo_name=repo_name))
293 return redirect(url('edit_repo_fields', repo_name=repo_name))
337
294
338 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
295 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
339 @auth.CSRFRequired()
296 @auth.CSRFRequired()
340 def toggle_locking(self, repo_name):
297 def toggle_locking(self, repo_name):
341 """
298 """
342 Toggle locking of repository by simple GET call to url
299 Toggle locking of repository by simple GET call to url
343
300
344 :param repo_name:
301 :param repo_name:
345 """
302 """
346
303
347 try:
304 try:
348 repo = Repository.get_by_repo_name(repo_name)
305 repo = Repository.get_by_repo_name(repo_name)
349
306
350 if repo.enable_locking:
307 if repo.enable_locking:
351 if repo.locked[0]:
308 if repo.locked[0]:
352 Repository.unlock(repo)
309 Repository.unlock(repo)
353 action = _('Unlocked')
310 action = _('Unlocked')
354 else:
311 else:
355 Repository.lock(repo, c.rhodecode_user.user_id,
312 Repository.lock(repo, c.rhodecode_user.user_id,
356 lock_reason=Repository.LOCK_WEB)
313 lock_reason=Repository.LOCK_WEB)
357 action = _('Locked')
314 action = _('Locked')
358
315
359 h.flash(_('Repository has been %s') % action,
316 h.flash(_('Repository has been %s') % action,
360 category='success')
317 category='success')
361 except Exception:
318 except Exception:
362 log.exception("Exception during unlocking")
319 log.exception("Exception during unlocking")
363 h.flash(_('An error occurred during unlocking'),
320 h.flash(_('An error occurred during unlocking'),
364 category='error')
321 category='error')
365 return redirect(h.route_path('repo_summary', repo_name=repo_name))
322 return redirect(h.route_path('repo_summary', repo_name=repo_name))
366
323
367 @HasRepoPermissionAllDecorator('repository.admin')
324 @HasRepoPermissionAllDecorator('repository.admin')
368 @auth.CSRFRequired()
325 @auth.CSRFRequired()
369 def edit_remote(self, repo_name):
326 def edit_remote(self, repo_name):
370 """PUT /{repo_name}/settings/remote: edit the repo remote."""
327 """PUT /{repo_name}/settings/remote: edit the repo remote."""
371 try:
328 try:
372 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
329 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
373 h.flash(_('Pulled from remote location'), category='success')
330 h.flash(_('Pulled from remote location'), category='success')
374 except Exception:
331 except Exception:
375 log.exception("Exception during pull from remote")
332 log.exception("Exception during pull from remote")
376 h.flash(_('An error occurred during pull from remote location'),
333 h.flash(_('An error occurred during pull from remote location'),
377 category='error')
334 category='error')
378 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
335 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
379
336
380 @HasRepoPermissionAllDecorator('repository.admin')
337 @HasRepoPermissionAllDecorator('repository.admin')
381 def edit_remote_form(self, repo_name):
338 def edit_remote_form(self, repo_name):
382 """GET /repo_name/settings: Form to edit an existing item"""
339 """GET /repo_name/settings: Form to edit an existing item"""
383 c.repo_info = self._load_repo(repo_name)
340 c.repo_info = self._load_repo(repo_name)
384 c.active = 'remote'
341 c.active = 'remote'
385
342
386 return render('admin/repos/repo_edit.mako')
343 return render('admin/repos/repo_edit.mako')
387
344
388 @HasRepoPermissionAllDecorator('repository.admin')
345 @HasRepoPermissionAllDecorator('repository.admin')
389 @auth.CSRFRequired()
346 @auth.CSRFRequired()
390 def edit_statistics(self, repo_name):
347 def edit_statistics(self, repo_name):
391 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
348 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
392 try:
349 try:
393 RepoModel().delete_stats(repo_name)
350 RepoModel().delete_stats(repo_name)
394 Session().commit()
351 Session().commit()
395 except Exception as e:
352 except Exception as e:
396 log.error(traceback.format_exc())
353 log.error(traceback.format_exc())
397 h.flash(_('An error occurred during deletion of repository stats'),
354 h.flash(_('An error occurred during deletion of repository stats'),
398 category='error')
355 category='error')
399 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
356 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
400
357
401 @HasRepoPermissionAllDecorator('repository.admin')
358 @HasRepoPermissionAllDecorator('repository.admin')
402 def edit_statistics_form(self, repo_name):
359 def edit_statistics_form(self, repo_name):
403 """GET /repo_name/settings: Form to edit an existing item"""
360 """GET /repo_name/settings: Form to edit an existing item"""
404 c.repo_info = self._load_repo(repo_name)
361 c.repo_info = self._load_repo(repo_name)
405 repo = c.repo_info.scm_instance()
362 repo = c.repo_info.scm_instance()
406
363
407 if c.repo_info.stats:
364 if c.repo_info.stats:
408 # this is on what revision we ended up so we add +1 for count
365 # this is on what revision we ended up so we add +1 for count
409 last_rev = c.repo_info.stats.stat_on_revision + 1
366 last_rev = c.repo_info.stats.stat_on_revision + 1
410 else:
367 else:
411 last_rev = 0
368 last_rev = 0
412 c.stats_revision = last_rev
369 c.stats_revision = last_rev
413
370
414 c.repo_last_rev = repo.count()
371 c.repo_last_rev = repo.count()
415
372
416 if last_rev == 0 or c.repo_last_rev == 0:
373 if last_rev == 0 or c.repo_last_rev == 0:
417 c.stats_percentage = 0
374 c.stats_percentage = 0
418 else:
375 else:
419 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
376 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
420
377
421 c.active = 'statistics'
378 c.active = 'statistics'
422
379
423 return render('admin/repos/repo_edit.mako')
380 return render('admin/repos/repo_edit.mako')
424
381
425 @HasRepoPermissionAllDecorator('repository.admin')
382 @HasRepoPermissionAllDecorator('repository.admin')
426 @auth.CSRFRequired()
383 @auth.CSRFRequired()
427 def repo_issuetracker_test(self, repo_name):
384 def repo_issuetracker_test(self, repo_name):
428 if request.is_xhr:
385 if request.is_xhr:
429 return h.urlify_commit_message(
386 return h.urlify_commit_message(
430 request.POST.get('test_text', ''),
387 request.POST.get('test_text', ''),
431 repo_name)
388 repo_name)
432 else:
389 else:
433 raise HTTPBadRequest()
390 raise HTTPBadRequest()
434
391
435 @HasRepoPermissionAllDecorator('repository.admin')
392 @HasRepoPermissionAllDecorator('repository.admin')
436 @auth.CSRFRequired()
393 @auth.CSRFRequired()
437 def repo_issuetracker_delete(self, repo_name):
394 def repo_issuetracker_delete(self, repo_name):
438 uid = request.POST.get('uid')
395 uid = request.POST.get('uid')
439 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
396 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
440 try:
397 try:
441 repo_settings.delete_entries(uid)
398 repo_settings.delete_entries(uid)
442 except Exception:
399 except Exception:
443 h.flash(_('Error occurred during deleting issue tracker entry'),
400 h.flash(_('Error occurred during deleting issue tracker entry'),
444 category='error')
401 category='error')
445 else:
402 else:
446 h.flash(_('Removed issue tracker entry'), category='success')
403 h.flash(_('Removed issue tracker entry'), category='success')
447 return redirect(url('repo_settings_issuetracker',
404 return redirect(url('repo_settings_issuetracker',
448 repo_name=repo_name))
405 repo_name=repo_name))
449
406
450 def _update_patterns(self, form, repo_settings):
407 def _update_patterns(self, form, repo_settings):
451 for uid in form['delete_patterns']:
408 for uid in form['delete_patterns']:
452 repo_settings.delete_entries(uid)
409 repo_settings.delete_entries(uid)
453
410
454 for pattern in form['patterns']:
411 for pattern in form['patterns']:
455 for setting, value, type_ in pattern:
412 for setting, value, type_ in pattern:
456 sett = repo_settings.create_or_update_setting(
413 sett = repo_settings.create_or_update_setting(
457 setting, value, type_)
414 setting, value, type_)
458 Session().add(sett)
415 Session().add(sett)
459
416
460 Session().commit()
417 Session().commit()
461
418
462 @HasRepoPermissionAllDecorator('repository.admin')
419 @HasRepoPermissionAllDecorator('repository.admin')
463 @auth.CSRFRequired()
420 @auth.CSRFRequired()
464 def repo_issuetracker_save(self, repo_name):
421 def repo_issuetracker_save(self, repo_name):
465 # Save inheritance
422 # Save inheritance
466 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
423 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
467 inherited = (request.POST.get('inherit_global_issuetracker')
424 inherited = (request.POST.get('inherit_global_issuetracker')
468 == "inherited")
425 == "inherited")
469 repo_settings.inherit_global_settings = inherited
426 repo_settings.inherit_global_settings = inherited
470 Session().commit()
427 Session().commit()
471
428
472 form = IssueTrackerPatternsForm()().to_python(request.POST)
429 form = IssueTrackerPatternsForm()().to_python(request.POST)
473 if form:
430 if form:
474 self._update_patterns(form, repo_settings)
431 self._update_patterns(form, repo_settings)
475
432
476 h.flash(_('Updated issue tracker entries'), category='success')
433 h.flash(_('Updated issue tracker entries'), category='success')
477 return redirect(url('repo_settings_issuetracker',
434 return redirect(url('repo_settings_issuetracker',
478 repo_name=repo_name))
435 repo_name=repo_name))
479
436
480 @HasRepoPermissionAllDecorator('repository.admin')
437 @HasRepoPermissionAllDecorator('repository.admin')
481 def repo_issuetracker(self, repo_name):
438 def repo_issuetracker(self, repo_name):
482 """GET /admin/settings/issue-tracker: All items in the collection"""
439 """GET /admin/settings/issue-tracker: All items in the collection"""
483 c.active = 'issuetracker'
440 c.active = 'issuetracker'
484 c.data = 'data'
441 c.data = 'data'
485 c.repo_info = self._load_repo(repo_name)
442 c.repo_info = self._load_repo(repo_name)
486
443
487 repo = Repository.get_by_repo_name(repo_name)
444 repo = Repository.get_by_repo_name(repo_name)
488 c.settings_model = IssueTrackerSettingsModel(repo=repo)
445 c.settings_model = IssueTrackerSettingsModel(repo=repo)
489 c.global_patterns = c.settings_model.get_global_settings()
446 c.global_patterns = c.settings_model.get_global_settings()
490 c.repo_patterns = c.settings_model.get_repo_settings()
447 c.repo_patterns = c.settings_model.get_repo_settings()
491
448
492 return render('admin/repos/repo_edit.mako')
449 return render('admin/repos/repo_edit.mako')
493
450
494 @HasRepoPermissionAllDecorator('repository.admin')
451 @HasRepoPermissionAllDecorator('repository.admin')
495 def repo_settings_vcs(self, repo_name):
452 def repo_settings_vcs(self, repo_name):
496 """GET /{repo_name}/settings/vcs/: All items in the collection"""
453 """GET /{repo_name}/settings/vcs/: All items in the collection"""
497
454
498 model = VcsSettingsModel(repo=repo_name)
455 model = VcsSettingsModel(repo=repo_name)
499
456
500 c.active = 'vcs'
457 c.active = 'vcs'
501 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
458 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
502 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
459 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
503 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
460 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
504 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
461 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
505 c.repo_info = self._load_repo(repo_name)
462 c.repo_info = self._load_repo(repo_name)
506 defaults = self._vcs_form_defaults(repo_name)
463 defaults = self._vcs_form_defaults(repo_name)
507 c.inherit_global_settings = defaults['inherit_global_settings']
464 c.inherit_global_settings = defaults['inherit_global_settings']
508 c.labs_active = str2bool(
465 c.labs_active = str2bool(
509 rhodecode.CONFIG.get('labs_settings_active', 'true'))
466 rhodecode.CONFIG.get('labs_settings_active', 'true'))
510
467
511 return htmlfill.render(
468 return htmlfill.render(
512 render('admin/repos/repo_edit.mako'),
469 render('admin/repos/repo_edit.mako'),
513 defaults=defaults,
470 defaults=defaults,
514 encoding="UTF-8",
471 encoding="UTF-8",
515 force_defaults=False)
472 force_defaults=False)
516
473
517 @HasRepoPermissionAllDecorator('repository.admin')
474 @HasRepoPermissionAllDecorator('repository.admin')
518 @auth.CSRFRequired()
475 @auth.CSRFRequired()
519 def repo_settings_vcs_update(self, repo_name):
476 def repo_settings_vcs_update(self, repo_name):
520 """POST /{repo_name}/settings/vcs/: All items in the collection"""
477 """POST /{repo_name}/settings/vcs/: All items in the collection"""
521 c.active = 'vcs'
478 c.active = 'vcs'
522
479
523 model = VcsSettingsModel(repo=repo_name)
480 model = VcsSettingsModel(repo=repo_name)
524 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
481 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
525 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
482 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
526 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
483 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
527 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
484 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
528 c.repo_info = self._load_repo(repo_name)
485 c.repo_info = self._load_repo(repo_name)
529 defaults = self._vcs_form_defaults(repo_name)
486 defaults = self._vcs_form_defaults(repo_name)
530 c.inherit_global_settings = defaults['inherit_global_settings']
487 c.inherit_global_settings = defaults['inherit_global_settings']
531
488
532 application_form = RepoVcsSettingsForm(repo_name)()
489 application_form = RepoVcsSettingsForm(repo_name)()
533 try:
490 try:
534 form_result = application_form.to_python(dict(request.POST))
491 form_result = application_form.to_python(dict(request.POST))
535 except formencode.Invalid as errors:
492 except formencode.Invalid as errors:
536 h.flash(
493 h.flash(
537 _("Some form inputs contain invalid data."),
494 _("Some form inputs contain invalid data."),
538 category='error')
495 category='error')
539 return htmlfill.render(
496 return htmlfill.render(
540 render('admin/repos/repo_edit.mako'),
497 render('admin/repos/repo_edit.mako'),
541 defaults=errors.value,
498 defaults=errors.value,
542 errors=errors.error_dict or {},
499 errors=errors.error_dict or {},
543 prefix_error=False,
500 prefix_error=False,
544 encoding="UTF-8",
501 encoding="UTF-8",
545 force_defaults=False
502 force_defaults=False
546 )
503 )
547
504
548 try:
505 try:
549 inherit_global_settings = form_result['inherit_global_settings']
506 inherit_global_settings = form_result['inherit_global_settings']
550 model.create_or_update_repo_settings(
507 model.create_or_update_repo_settings(
551 form_result, inherit_global_settings=inherit_global_settings)
508 form_result, inherit_global_settings=inherit_global_settings)
552 except Exception:
509 except Exception:
553 log.exception("Exception while updating settings")
510 log.exception("Exception while updating settings")
554 h.flash(
511 h.flash(
555 _('Error occurred during updating repository VCS settings'),
512 _('Error occurred during updating repository VCS settings'),
556 category='error')
513 category='error')
557 else:
514 else:
558 Session().commit()
515 Session().commit()
559 h.flash(_('Updated VCS settings'), category='success')
516 h.flash(_('Updated VCS settings'), category='success')
560 return redirect(url('repo_vcs_settings', repo_name=repo_name))
517 return redirect(url('repo_vcs_settings', repo_name=repo_name))
561
518
562 return htmlfill.render(
519 return htmlfill.render(
563 render('admin/repos/repo_edit.mako'),
520 render('admin/repos/repo_edit.mako'),
564 defaults=self._vcs_form_defaults(repo_name),
521 defaults=self._vcs_form_defaults(repo_name),
565 encoding="UTF-8",
522 encoding="UTF-8",
566 force_defaults=False)
523 force_defaults=False)
567
524
568 @HasRepoPermissionAllDecorator('repository.admin')
525 @HasRepoPermissionAllDecorator('repository.admin')
569 @auth.CSRFRequired()
526 @auth.CSRFRequired()
570 @jsonify
527 @jsonify
571 def repo_delete_svn_pattern(self, repo_name):
528 def repo_delete_svn_pattern(self, repo_name):
572 if not request.is_xhr:
529 if not request.is_xhr:
573 return False
530 return False
574
531
575 delete_pattern_id = request.POST.get('delete_svn_pattern')
532 delete_pattern_id = request.POST.get('delete_svn_pattern')
576 model = VcsSettingsModel(repo=repo_name)
533 model = VcsSettingsModel(repo=repo_name)
577 try:
534 try:
578 model.delete_repo_svn_pattern(delete_pattern_id)
535 model.delete_repo_svn_pattern(delete_pattern_id)
579 except SettingNotFound:
536 except SettingNotFound:
580 raise HTTPBadRequest()
537 raise HTTPBadRequest()
581
538
582 Session().commit()
539 Session().commit()
583 return True
540 return True
584
541
585 def _vcs_form_defaults(self, repo_name):
542 def _vcs_form_defaults(self, repo_name):
586 model = VcsSettingsModel(repo=repo_name)
543 model = VcsSettingsModel(repo=repo_name)
587 global_defaults = model.get_global_settings()
544 global_defaults = model.get_global_settings()
588
545
589 repo_defaults = {}
546 repo_defaults = {}
590 repo_defaults.update(global_defaults)
547 repo_defaults.update(global_defaults)
591 repo_defaults.update(model.get_repo_settings())
548 repo_defaults.update(model.get_repo_settings())
592
549
593 global_defaults = {
550 global_defaults = {
594 '{}_inherited'.format(k): global_defaults[k]
551 '{}_inherited'.format(k): global_defaults[k]
595 for k in global_defaults}
552 for k in global_defaults}
596
553
597 defaults = {
554 defaults = {
598 'inherit_global_settings': model.inherit_global_settings
555 'inherit_global_settings': model.inherit_global_settings
599 }
556 }
600 defaults.update(global_defaults)
557 defaults.update(global_defaults)
601 defaults.update(repo_defaults)
558 defaults.update(repo_defaults)
602 defaults.update({
559 defaults.update({
603 'new_svn_branch': '',
560 'new_svn_branch': '',
604 'new_svn_tag': '',
561 'new_svn_tag': '',
605 })
562 })
606 return defaults
563 return defaults
@@ -1,196 +1,198 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 forks controller for rhodecode
22 forks controller for rhodecode
23 """
23 """
24
24
25 import formencode
25 import formencode
26 import logging
26 import logging
27 from formencode import htmlfill
27 from formencode import htmlfill
28
28
29 from pylons import tmpl_context as c, request, url
29 from pylons import tmpl_context as c, request, url
30 from pylons.controllers.util import redirect
30 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from pyramid.httpexceptions import HTTPFound
33 import rhodecode.lib.helpers as h
34 import rhodecode.lib.helpers as h
34
35
35 from rhodecode.lib import auth
36 from rhodecode.lib import auth
36 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
38 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
39 HasRepoPermissionAny, HasPermissionAnyDecorator, HasAcceptedRepoType)
40 HasRepoPermissionAny, HasPermissionAnyDecorator, HasAcceptedRepoType)
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
42 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.forms import RepoForkForm
44 from rhodecode.model.forms import RepoForkForm
44 from rhodecode.model.scm import ScmModel, RepoGroupList
45 from rhodecode.model.scm import ScmModel, RepoGroupList
45 from rhodecode.lib.utils2 import safe_int
46 from rhodecode.lib.utils2 import safe_int
46
47
47 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
48
49
49
50
50 class ForksController(BaseRepoController):
51 class ForksController(BaseRepoController):
51
52
52 def __before__(self):
53 def __before__(self):
53 super(ForksController, self).__before__()
54 super(ForksController, self).__before__()
54
55
55 def __load_defaults(self):
56 def __load_defaults(self):
56 acl_groups = RepoGroupList(
57 acl_groups = RepoGroupList(
57 RepoGroup.query().all(),
58 RepoGroup.query().all(),
58 perm_set=['group.write', 'group.admin'])
59 perm_set=['group.write', 'group.admin'])
59 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
60 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
61 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
61 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
62 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
62 c.landing_revs_choices = choices
63 c.landing_revs_choices = choices
63 c.personal_repo_group = c.rhodecode_user.personal_repo_group
64 c.personal_repo_group = c.rhodecode_user.personal_repo_group
64
65
65 def __load_data(self, repo_name=None):
66 def __load_data(self, repo_name=None):
66 """
67 """
67 Load defaults settings for edit, and update
68 Load defaults settings for edit, and update
68
69
69 :param repo_name:
70 :param repo_name:
70 """
71 """
71 self.__load_defaults()
72 self.__load_defaults()
72
73
73 c.repo_info = Repository.get_by_repo_name(repo_name)
74 c.repo_info = Repository.get_by_repo_name(repo_name)
74 repo = c.repo_info.scm_instance()
75 repo = c.repo_info.scm_instance()
75
76
76 if c.repo_info is None:
77 if c.repo_info is None:
77 h.not_mapped_error(repo_name)
78 h.not_mapped_error(repo_name)
78 return redirect(url('repos'))
79 return redirect(url('repos'))
79
80
80 c.default_user_id = User.get_default_user().user_id
81 c.default_user_id = User.get_default_user().user_id
81 c.in_public_journal = UserFollowing.query()\
82 c.in_public_journal = UserFollowing.query()\
82 .filter(UserFollowing.user_id == c.default_user_id)\
83 .filter(UserFollowing.user_id == c.default_user_id)\
83 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
84 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
84
85
85 if c.repo_info.stats:
86 if c.repo_info.stats:
86 last_rev = c.repo_info.stats.stat_on_revision+1
87 last_rev = c.repo_info.stats.stat_on_revision+1
87 else:
88 else:
88 last_rev = 0
89 last_rev = 0
89 c.stats_revision = last_rev
90 c.stats_revision = last_rev
90
91
91 c.repo_last_rev = repo.count()
92 c.repo_last_rev = repo.count()
92
93
93 if last_rev == 0 or c.repo_last_rev == 0:
94 if last_rev == 0 or c.repo_last_rev == 0:
94 c.stats_percentage = 0
95 c.stats_percentage = 0
95 else:
96 else:
96 c.stats_percentage = '%.2f' % ((float((last_rev)) /
97 c.stats_percentage = '%.2f' % ((float((last_rev)) /
97 c.repo_last_rev) * 100)
98 c.repo_last_rev) * 100)
98
99
99 defaults = RepoModel()._get_defaults(repo_name)
100 defaults = RepoModel()._get_defaults(repo_name)
100 # alter the description to indicate a fork
101 # alter the description to indicate a fork
101 defaults['description'] = ('fork of repository: %s \n%s'
102 defaults['description'] = ('fork of repository: %s \n%s'
102 % (defaults['repo_name'],
103 % (defaults['repo_name'],
103 defaults['description']))
104 defaults['description']))
104 # add suffix to fork
105 # add suffix to fork
105 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
106 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
106
107
107 return defaults
108 return defaults
108
109
109 @LoginRequired()
110 @LoginRequired()
110 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
111 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
111 'repository.admin')
112 'repository.admin')
112 @HasAcceptedRepoType('git', 'hg')
113 @HasAcceptedRepoType('git', 'hg')
113 def forks(self, repo_name):
114 def forks(self, repo_name):
114 p = safe_int(request.GET.get('page', 1), 1)
115 p = safe_int(request.GET.get('page', 1), 1)
115 repo_id = c.rhodecode_db_repo.repo_id
116 repo_id = c.rhodecode_db_repo.repo_id
116 d = []
117 d = []
117 for r in Repository.get_repo_forks(repo_id):
118 for r in Repository.get_repo_forks(repo_id):
118 if not HasRepoPermissionAny(
119 if not HasRepoPermissionAny(
119 'repository.read', 'repository.write', 'repository.admin'
120 'repository.read', 'repository.write', 'repository.admin'
120 )(r.repo_name, 'get forks check'):
121 )(r.repo_name, 'get forks check'):
121 continue
122 continue
122 d.append(r)
123 d.append(r)
123 c.forks_pager = Page(d, page=p, items_per_page=20)
124 c.forks_pager = Page(d, page=p, items_per_page=20)
124
125
125 c.forks_data = render('/forks/forks_data.mako')
126 c.forks_data = render('/forks/forks_data.mako')
126
127
127 if request.environ.get('HTTP_X_PJAX'):
128 if request.environ.get('HTTP_X_PJAX'):
128 return c.forks_data
129 return c.forks_data
129
130
130 return render('/forks/forks.mako')
131 return render('/forks/forks.mako')
131
132
132 @LoginRequired()
133 @LoginRequired()
133 @NotAnonymous()
134 @NotAnonymous()
134 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
135 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
135 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
136 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
136 'repository.admin')
137 'repository.admin')
137 @HasAcceptedRepoType('git', 'hg')
138 @HasAcceptedRepoType('git', 'hg')
138 def fork(self, repo_name):
139 def fork(self, repo_name):
139 c.repo_info = Repository.get_by_repo_name(repo_name)
140 c.repo_info = Repository.get_by_repo_name(repo_name)
140 if not c.repo_info:
141 if not c.repo_info:
141 h.not_mapped_error(repo_name)
142 h.not_mapped_error(repo_name)
142 return redirect(h.route_path('home'))
143 return redirect(h.route_path('home'))
143
144
144 defaults = self.__load_data(repo_name)
145 defaults = self.__load_data(repo_name)
145
146
146 return htmlfill.render(
147 return htmlfill.render(
147 render('forks/fork.mako'),
148 render('forks/fork.mako'),
148 defaults=defaults,
149 defaults=defaults,
149 encoding="UTF-8",
150 encoding="UTF-8",
150 force_defaults=False
151 force_defaults=False
151 )
152 )
152
153
153 @LoginRequired()
154 @LoginRequired()
154 @NotAnonymous()
155 @NotAnonymous()
155 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
156 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
156 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
157 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
157 'repository.admin')
158 'repository.admin')
158 @HasAcceptedRepoType('git', 'hg')
159 @HasAcceptedRepoType('git', 'hg')
159 @auth.CSRFRequired()
160 @auth.CSRFRequired()
160 def fork_create(self, repo_name):
161 def fork_create(self, repo_name):
161 self.__load_defaults()
162 self.__load_defaults()
162 c.repo_info = Repository.get_by_repo_name(repo_name)
163 c.repo_info = Repository.get_by_repo_name(repo_name)
163 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
164 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
164 repo_groups=c.repo_groups_choices,
165 repo_groups=c.repo_groups_choices,
165 landing_revs=c.landing_revs_choices)()
166 landing_revs=c.landing_revs_choices)()
166 form_result = {}
167 form_result = {}
167 task_id = None
168 task_id = None
168 try:
169 try:
169 form_result = _form.to_python(dict(request.POST))
170 form_result = _form.to_python(dict(request.POST))
170 # create fork is done sometimes async on celery, db transaction
171 # create fork is done sometimes async on celery, db transaction
171 # management is handled there.
172 # management is handled there.
172 task = RepoModel().create_fork(
173 task = RepoModel().create_fork(
173 form_result, c.rhodecode_user.user_id)
174 form_result, c.rhodecode_user.user_id)
174 from celery.result import BaseAsyncResult
175 from celery.result import BaseAsyncResult
175 if isinstance(task, BaseAsyncResult):
176 if isinstance(task, BaseAsyncResult):
176 task_id = task.task_id
177 task_id = task.task_id
177 except formencode.Invalid as errors:
178 except formencode.Invalid as errors:
178 c.new_repo = errors.value['repo_name']
179 c.new_repo = errors.value['repo_name']
179 return htmlfill.render(
180 return htmlfill.render(
180 render('forks/fork.mako'),
181 render('forks/fork.mako'),
181 defaults=errors.value,
182 defaults=errors.value,
182 errors=errors.error_dict or {},
183 errors=errors.error_dict or {},
183 prefix_error=False,
184 prefix_error=False,
184 encoding="UTF-8",
185 encoding="UTF-8",
185 force_defaults=False)
186 force_defaults=False)
186 except Exception:
187 except Exception:
187 log.exception(
188 log.exception(
188 u'Exception while trying to fork the repository %s', repo_name)
189 u'Exception while trying to fork the repository %s', repo_name)
189 msg = (
190 msg = (
190 _('An error occurred during repository forking %s') %
191 _('An error occurred during repository forking %s') %
191 (repo_name, ))
192 (repo_name, ))
192 h.flash(msg, category='error')
193 h.flash(msg, category='error')
193
194
194 return redirect(h.url('repo_creating_home',
195 raise HTTPFound(
195 repo_name=form_result['repo_name_full'],
196 h.route_path('repo_creating',
196 task_id=task_id))
197 repo_name=form_result['repo_name_full'],
198 _query=dict(task_id=task_id)))
@@ -1,2392 +1,2397 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'fonts';
8 @import 'fonts';
9 @import 'variables';
9 @import 'variables';
10 @import 'bootstrap-variables';
10 @import 'bootstrap-variables';
11 @import 'form-bootstrap';
11 @import 'form-bootstrap';
12 @import 'codemirror';
12 @import 'codemirror';
13 @import 'legacy_code_styles';
13 @import 'legacy_code_styles';
14 @import 'readme-box';
14 @import 'readme-box';
15 @import 'progress-bar';
15 @import 'progress-bar';
16
16
17 @import 'type';
17 @import 'type';
18 @import 'alerts';
18 @import 'alerts';
19 @import 'buttons';
19 @import 'buttons';
20 @import 'tags';
20 @import 'tags';
21 @import 'code-block';
21 @import 'code-block';
22 @import 'examples';
22 @import 'examples';
23 @import 'login';
23 @import 'login';
24 @import 'main-content';
24 @import 'main-content';
25 @import 'select2';
25 @import 'select2';
26 @import 'comments';
26 @import 'comments';
27 @import 'panels-bootstrap';
27 @import 'panels-bootstrap';
28 @import 'panels';
28 @import 'panels';
29 @import 'deform';
29 @import 'deform';
30
30
31 //--- BASE ------------------//
31 //--- BASE ------------------//
32 .noscript-error {
32 .noscript-error {
33 top: 0;
33 top: 0;
34 left: 0;
34 left: 0;
35 width: 100%;
35 width: 100%;
36 z-index: 101;
36 z-index: 101;
37 text-align: center;
37 text-align: center;
38 font-family: @text-semibold;
38 font-family: @text-semibold;
39 font-size: 120%;
39 font-size: 120%;
40 color: white;
40 color: white;
41 background-color: @alert2;
41 background-color: @alert2;
42 padding: 5px 0 5px 0;
42 padding: 5px 0 5px 0;
43 }
43 }
44
44
45 html {
45 html {
46 display: table;
46 display: table;
47 height: 100%;
47 height: 100%;
48 width: 100%;
48 width: 100%;
49 }
49 }
50
50
51 body {
51 body {
52 display: table-cell;
52 display: table-cell;
53 width: 100%;
53 width: 100%;
54 }
54 }
55
55
56 //--- LAYOUT ------------------//
56 //--- LAYOUT ------------------//
57
57
58 .hidden{
58 .hidden{
59 display: none !important;
59 display: none !important;
60 }
60 }
61
61
62 .box{
62 .box{
63 float: left;
63 float: left;
64 width: 100%;
64 width: 100%;
65 }
65 }
66
66
67 .browser-header {
67 .browser-header {
68 clear: both;
68 clear: both;
69 }
69 }
70 .main {
70 .main {
71 clear: both;
71 clear: both;
72 padding:0 0 @pagepadding;
72 padding:0 0 @pagepadding;
73 height: auto;
73 height: auto;
74
74
75 &:after { //clearfix
75 &:after { //clearfix
76 content:"";
76 content:"";
77 clear:both;
77 clear:both;
78 width:100%;
78 width:100%;
79 display:block;
79 display:block;
80 }
80 }
81 }
81 }
82
82
83 .action-link{
83 .action-link{
84 margin-left: @padding;
84 margin-left: @padding;
85 padding-left: @padding;
85 padding-left: @padding;
86 border-left: @border-thickness solid @border-default-color;
86 border-left: @border-thickness solid @border-default-color;
87 }
87 }
88
88
89 input + .action-link, .action-link.first{
89 input + .action-link, .action-link.first{
90 border-left: none;
90 border-left: none;
91 }
91 }
92
92
93 .action-link.last{
93 .action-link.last{
94 margin-right: @padding;
94 margin-right: @padding;
95 padding-right: @padding;
95 padding-right: @padding;
96 }
96 }
97
97
98 .action-link.active,
98 .action-link.active,
99 .action-link.active a{
99 .action-link.active a{
100 color: @grey4;
100 color: @grey4;
101 }
101 }
102
102
103 .clipboard-action {
103 .clipboard-action {
104 cursor: pointer;
104 cursor: pointer;
105 }
105 }
106
106
107 ul.simple-list{
107 ul.simple-list{
108 list-style: none;
108 list-style: none;
109 margin: 0;
109 margin: 0;
110 padding: 0;
110 padding: 0;
111 }
111 }
112
112
113 .main-content {
113 .main-content {
114 padding-bottom: @pagepadding;
114 padding-bottom: @pagepadding;
115 }
115 }
116
116
117 .wide-mode-wrapper {
117 .wide-mode-wrapper {
118 max-width:4000px !important;
118 max-width:4000px !important;
119 }
119 }
120
120
121 .wrapper {
121 .wrapper {
122 position: relative;
122 position: relative;
123 max-width: @wrapper-maxwidth;
123 max-width: @wrapper-maxwidth;
124 margin: 0 auto;
124 margin: 0 auto;
125 }
125 }
126
126
127 #content {
127 #content {
128 clear: both;
128 clear: both;
129 padding: 0 @contentpadding;
129 padding: 0 @contentpadding;
130 }
130 }
131
131
132 .advanced-settings-fields{
132 .advanced-settings-fields{
133 input{
133 input{
134 margin-left: @textmargin;
134 margin-left: @textmargin;
135 margin-right: @padding/2;
135 margin-right: @padding/2;
136 }
136 }
137 }
137 }
138
138
139 .cs_files_title {
139 .cs_files_title {
140 margin: @pagepadding 0 0;
140 margin: @pagepadding 0 0;
141 }
141 }
142
142
143 input.inline[type="file"] {
143 input.inline[type="file"] {
144 display: inline;
144 display: inline;
145 }
145 }
146
146
147 .error_page {
147 .error_page {
148 margin: 10% auto;
148 margin: 10% auto;
149
149
150 h1 {
150 h1 {
151 color: @grey2;
151 color: @grey2;
152 }
152 }
153
153
154 .alert {
154 .alert {
155 margin: @padding 0;
155 margin: @padding 0;
156 }
156 }
157
157
158 .error-branding {
158 .error-branding {
159 font-family: @text-semibold;
159 font-family: @text-semibold;
160 color: @grey4;
160 color: @grey4;
161 }
161 }
162
162
163 .error_message {
163 .error_message {
164 font-family: @text-regular;
164 font-family: @text-regular;
165 }
165 }
166
166
167 .sidebar {
167 .sidebar {
168 min-height: 275px;
168 min-height: 275px;
169 margin: 0;
169 margin: 0;
170 padding: 0 0 @sidebarpadding @sidebarpadding;
170 padding: 0 0 @sidebarpadding @sidebarpadding;
171 border: none;
171 border: none;
172 }
172 }
173
173
174 .main-content {
174 .main-content {
175 position: relative;
175 position: relative;
176 margin: 0 @sidebarpadding @sidebarpadding;
176 margin: 0 @sidebarpadding @sidebarpadding;
177 padding: 0 0 0 @sidebarpadding;
177 padding: 0 0 0 @sidebarpadding;
178 border-left: @border-thickness solid @grey5;
178 border-left: @border-thickness solid @grey5;
179
179
180 @media (max-width:767px) {
180 @media (max-width:767px) {
181 clear: both;
181 clear: both;
182 width: 100%;
182 width: 100%;
183 margin: 0;
183 margin: 0;
184 border: none;
184 border: none;
185 }
185 }
186 }
186 }
187
187
188 .inner-column {
188 .inner-column {
189 float: left;
189 float: left;
190 width: 29.75%;
190 width: 29.75%;
191 min-height: 150px;
191 min-height: 150px;
192 margin: @sidebarpadding 2% 0 0;
192 margin: @sidebarpadding 2% 0 0;
193 padding: 0 2% 0 0;
193 padding: 0 2% 0 0;
194 border-right: @border-thickness solid @grey5;
194 border-right: @border-thickness solid @grey5;
195
195
196 @media (max-width:767px) {
196 @media (max-width:767px) {
197 clear: both;
197 clear: both;
198 width: 100%;
198 width: 100%;
199 border: none;
199 border: none;
200 }
200 }
201
201
202 ul {
202 ul {
203 padding-left: 1.25em;
203 padding-left: 1.25em;
204 }
204 }
205
205
206 &:last-child {
206 &:last-child {
207 margin: @sidebarpadding 0 0;
207 margin: @sidebarpadding 0 0;
208 border: none;
208 border: none;
209 }
209 }
210
210
211 h4 {
211 h4 {
212 margin: 0 0 @padding;
212 margin: 0 0 @padding;
213 font-family: @text-semibold;
213 font-family: @text-semibold;
214 }
214 }
215 }
215 }
216 }
216 }
217 .error-page-logo {
217 .error-page-logo {
218 width: 130px;
218 width: 130px;
219 height: 160px;
219 height: 160px;
220 }
220 }
221
221
222 // HEADER
222 // HEADER
223 .header {
223 .header {
224
224
225 // TODO: johbo: Fix login pages, so that they work without a min-height
225 // TODO: johbo: Fix login pages, so that they work without a min-height
226 // for the header and then remove the min-height. I chose a smaller value
226 // for the header and then remove the min-height. I chose a smaller value
227 // intentionally here to avoid rendering issues in the main navigation.
227 // intentionally here to avoid rendering issues in the main navigation.
228 min-height: 49px;
228 min-height: 49px;
229
229
230 position: relative;
230 position: relative;
231 vertical-align: bottom;
231 vertical-align: bottom;
232 padding: 0 @header-padding;
232 padding: 0 @header-padding;
233 background-color: @grey2;
233 background-color: @grey2;
234 color: @grey5;
234 color: @grey5;
235
235
236 .title {
236 .title {
237 overflow: visible;
237 overflow: visible;
238 }
238 }
239
239
240 &:before,
240 &:before,
241 &:after {
241 &:after {
242 content: "";
242 content: "";
243 clear: both;
243 clear: both;
244 width: 100%;
244 width: 100%;
245 }
245 }
246
246
247 // TODO: johbo: Avoids breaking "Repositories" chooser
247 // TODO: johbo: Avoids breaking "Repositories" chooser
248 .select2-container .select2-choice .select2-arrow {
248 .select2-container .select2-choice .select2-arrow {
249 display: none;
249 display: none;
250 }
250 }
251 }
251 }
252
252
253 #header-inner {
253 #header-inner {
254 &.title {
254 &.title {
255 margin: 0;
255 margin: 0;
256 }
256 }
257 &:before,
257 &:before,
258 &:after {
258 &:after {
259 content: "";
259 content: "";
260 clear: both;
260 clear: both;
261 }
261 }
262 }
262 }
263
263
264 // Gists
264 // Gists
265 #files_data {
265 #files_data {
266 clear: both; //for firefox
266 clear: both; //for firefox
267 }
267 }
268 #gistid {
268 #gistid {
269 margin-right: @padding;
269 margin-right: @padding;
270 }
270 }
271
271
272 // Global Settings Editor
272 // Global Settings Editor
273 .textarea.editor {
273 .textarea.editor {
274 float: left;
274 float: left;
275 position: relative;
275 position: relative;
276 max-width: @texteditor-width;
276 max-width: @texteditor-width;
277
277
278 select {
278 select {
279 position: absolute;
279 position: absolute;
280 top:10px;
280 top:10px;
281 right:0;
281 right:0;
282 }
282 }
283
283
284 .CodeMirror {
284 .CodeMirror {
285 margin: 0;
285 margin: 0;
286 }
286 }
287
287
288 .help-block {
288 .help-block {
289 margin: 0 0 @padding;
289 margin: 0 0 @padding;
290 padding:.5em;
290 padding:.5em;
291 background-color: @grey6;
291 background-color: @grey6;
292 &.pre-formatting {
292 &.pre-formatting {
293 white-space: pre;
293 white-space: pre;
294 }
294 }
295 }
295 }
296 }
296 }
297
297
298 ul.auth_plugins {
298 ul.auth_plugins {
299 margin: @padding 0 @padding @legend-width;
299 margin: @padding 0 @padding @legend-width;
300 padding: 0;
300 padding: 0;
301
301
302 li {
302 li {
303 margin-bottom: @padding;
303 margin-bottom: @padding;
304 line-height: 1em;
304 line-height: 1em;
305 list-style-type: none;
305 list-style-type: none;
306
306
307 .auth_buttons .btn {
307 .auth_buttons .btn {
308 margin-right: @padding;
308 margin-right: @padding;
309 }
309 }
310
310
311 &:before { content: none; }
311 &:before { content: none; }
312 }
312 }
313 }
313 }
314
314
315
315
316 // My Account PR list
316 // My Account PR list
317
317
318 #show_closed {
318 #show_closed {
319 margin: 0 1em 0 0;
319 margin: 0 1em 0 0;
320 }
320 }
321
321
322 .pullrequestlist {
322 .pullrequestlist {
323 .closed {
323 .closed {
324 background-color: @grey6;
324 background-color: @grey6;
325 }
325 }
326 .td-status {
326 .td-status {
327 padding-left: .5em;
327 padding-left: .5em;
328 }
328 }
329 .log-container .truncate {
329 .log-container .truncate {
330 height: 2.75em;
330 height: 2.75em;
331 white-space: pre-line;
331 white-space: pre-line;
332 }
332 }
333 table.rctable .user {
333 table.rctable .user {
334 padding-left: 0;
334 padding-left: 0;
335 }
335 }
336 table.rctable {
336 table.rctable {
337 td.td-description,
337 td.td-description,
338 .rc-user {
338 .rc-user {
339 min-width: auto;
339 min-width: auto;
340 }
340 }
341 }
341 }
342 }
342 }
343
343
344 // Pull Requests
344 // Pull Requests
345
345
346 .pullrequests_section_head {
346 .pullrequests_section_head {
347 display: block;
347 display: block;
348 clear: both;
348 clear: both;
349 margin: @padding 0;
349 margin: @padding 0;
350 font-family: @text-bold;
350 font-family: @text-bold;
351 }
351 }
352
352
353 .pr-origininfo, .pr-targetinfo {
353 .pr-origininfo, .pr-targetinfo {
354 position: relative;
354 position: relative;
355
355
356 .tag {
356 .tag {
357 display: inline-block;
357 display: inline-block;
358 margin: 0 1em .5em 0;
358 margin: 0 1em .5em 0;
359 }
359 }
360
360
361 .clone-url {
361 .clone-url {
362 display: inline-block;
362 display: inline-block;
363 margin: 0 0 .5em 0;
363 margin: 0 0 .5em 0;
364 padding: 0;
364 padding: 0;
365 line-height: 1.2em;
365 line-height: 1.2em;
366 }
366 }
367 }
367 }
368
368
369 .pr-mergeinfo {
369 .pr-mergeinfo {
370 min-width: 95% !important;
370 min-width: 95% !important;
371 padding: 0 !important;
371 padding: 0 !important;
372 border: 0;
372 border: 0;
373 }
373 }
374 .pr-mergeinfo-copy {
374 .pr-mergeinfo-copy {
375 padding: 0 0;
375 padding: 0 0;
376 }
376 }
377
377
378 .pr-pullinfo {
378 .pr-pullinfo {
379 min-width: 95% !important;
379 min-width: 95% !important;
380 padding: 0 !important;
380 padding: 0 !important;
381 border: 0;
381 border: 0;
382 }
382 }
383 .pr-pullinfo-copy {
383 .pr-pullinfo-copy {
384 padding: 0 0;
384 padding: 0 0;
385 }
385 }
386
386
387
387
388 #pr-title-input {
388 #pr-title-input {
389 width: 72%;
389 width: 72%;
390 font-size: 1em;
390 font-size: 1em;
391 font-family: @text-bold;
391 font-family: @text-bold;
392 margin: 0;
392 margin: 0;
393 padding: 0 0 0 @padding/4;
393 padding: 0 0 0 @padding/4;
394 line-height: 1.7em;
394 line-height: 1.7em;
395 color: @text-color;
395 color: @text-color;
396 letter-spacing: .02em;
396 letter-spacing: .02em;
397 }
397 }
398
398
399 #pullrequest_title {
399 #pullrequest_title {
400 width: 100%;
400 width: 100%;
401 box-sizing: border-box;
401 box-sizing: border-box;
402 }
402 }
403
403
404 #pr_open_message {
404 #pr_open_message {
405 border: @border-thickness solid #fff;
405 border: @border-thickness solid #fff;
406 border-radius: @border-radius;
406 border-radius: @border-radius;
407 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
407 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
408 text-align: left;
408 text-align: left;
409 overflow: hidden;
409 overflow: hidden;
410 }
410 }
411
411
412 .pr-submit-button {
412 .pr-submit-button {
413 float: right;
413 float: right;
414 margin: 0 0 0 5px;
414 margin: 0 0 0 5px;
415 }
415 }
416
416
417 .pr-spacing-container {
417 .pr-spacing-container {
418 padding: 20px;
418 padding: 20px;
419 clear: both
419 clear: both
420 }
420 }
421
421
422 #pr-description-input {
422 #pr-description-input {
423 margin-bottom: 0;
423 margin-bottom: 0;
424 }
424 }
425
425
426 .pr-description-label {
426 .pr-description-label {
427 vertical-align: top;
427 vertical-align: top;
428 }
428 }
429
429
430 .perms_section_head {
430 .perms_section_head {
431 min-width: 625px;
431 min-width: 625px;
432
432
433 h2 {
433 h2 {
434 margin-bottom: 0;
434 margin-bottom: 0;
435 }
435 }
436
436
437 .label-checkbox {
437 .label-checkbox {
438 float: left;
438 float: left;
439 }
439 }
440
440
441 &.field {
441 &.field {
442 margin: @space 0 @padding;
442 margin: @space 0 @padding;
443 }
443 }
444
444
445 &:first-child.field {
445 &:first-child.field {
446 margin-top: 0;
446 margin-top: 0;
447
447
448 .label {
448 .label {
449 margin-top: 0;
449 margin-top: 0;
450 padding-top: 0;
450 padding-top: 0;
451 }
451 }
452
452
453 .radios {
453 .radios {
454 padding-top: 0;
454 padding-top: 0;
455 }
455 }
456 }
456 }
457
457
458 .radios {
458 .radios {
459 float: right;
459 float: right;
460 position: relative;
460 position: relative;
461 width: 405px;
461 width: 405px;
462 }
462 }
463 }
463 }
464
464
465 //--- MODULES ------------------//
465 //--- MODULES ------------------//
466
466
467
467
468 // Server Announcement
468 // Server Announcement
469 #server-announcement {
469 #server-announcement {
470 width: 95%;
470 width: 95%;
471 margin: @padding auto;
471 margin: @padding auto;
472 padding: @padding;
472 padding: @padding;
473 border-width: 2px;
473 border-width: 2px;
474 border-style: solid;
474 border-style: solid;
475 .border-radius(2px);
475 .border-radius(2px);
476 font-family: @text-bold;
476 font-family: @text-bold;
477
477
478 &.info { border-color: @alert4; background-color: @alert4-inner; }
478 &.info { border-color: @alert4; background-color: @alert4-inner; }
479 &.warning { border-color: @alert3; background-color: @alert3-inner; }
479 &.warning { border-color: @alert3; background-color: @alert3-inner; }
480 &.error { border-color: @alert2; background-color: @alert2-inner; }
480 &.error { border-color: @alert2; background-color: @alert2-inner; }
481 &.success { border-color: @alert1; background-color: @alert1-inner; }
481 &.success { border-color: @alert1; background-color: @alert1-inner; }
482 &.neutral { border-color: @grey3; background-color: @grey6; }
482 &.neutral { border-color: @grey3; background-color: @grey6; }
483 }
483 }
484
484
485 // Fixed Sidebar Column
485 // Fixed Sidebar Column
486 .sidebar-col-wrapper {
486 .sidebar-col-wrapper {
487 padding-left: @sidebar-all-width;
487 padding-left: @sidebar-all-width;
488
488
489 .sidebar {
489 .sidebar {
490 width: @sidebar-width;
490 width: @sidebar-width;
491 margin-left: -@sidebar-all-width;
491 margin-left: -@sidebar-all-width;
492 }
492 }
493 }
493 }
494
494
495 .sidebar-col-wrapper.scw-small {
495 .sidebar-col-wrapper.scw-small {
496 padding-left: @sidebar-small-all-width;
496 padding-left: @sidebar-small-all-width;
497
497
498 .sidebar {
498 .sidebar {
499 width: @sidebar-small-width;
499 width: @sidebar-small-width;
500 margin-left: -@sidebar-small-all-width;
500 margin-left: -@sidebar-small-all-width;
501 }
501 }
502 }
502 }
503
503
504
504
505 // FOOTER
505 // FOOTER
506 #footer {
506 #footer {
507 padding: 0;
507 padding: 0;
508 text-align: center;
508 text-align: center;
509 vertical-align: middle;
509 vertical-align: middle;
510 color: @grey2;
510 color: @grey2;
511 background-color: @grey6;
511 background-color: @grey6;
512
512
513 p {
513 p {
514 margin: 0;
514 margin: 0;
515 padding: 1em;
515 padding: 1em;
516 line-height: 1em;
516 line-height: 1em;
517 }
517 }
518
518
519 .server-instance { //server instance
519 .server-instance { //server instance
520 display: none;
520 display: none;
521 }
521 }
522
522
523 .title {
523 .title {
524 float: none;
524 float: none;
525 margin: 0 auto;
525 margin: 0 auto;
526 }
526 }
527 }
527 }
528
528
529 button.close {
529 button.close {
530 padding: 0;
530 padding: 0;
531 cursor: pointer;
531 cursor: pointer;
532 background: transparent;
532 background: transparent;
533 border: 0;
533 border: 0;
534 .box-shadow(none);
534 .box-shadow(none);
535 -webkit-appearance: none;
535 -webkit-appearance: none;
536 }
536 }
537
537
538 .close {
538 .close {
539 float: right;
539 float: right;
540 font-size: 21px;
540 font-size: 21px;
541 font-family: @text-bootstrap;
541 font-family: @text-bootstrap;
542 line-height: 1em;
542 line-height: 1em;
543 font-weight: bold;
543 font-weight: bold;
544 color: @grey2;
544 color: @grey2;
545
545
546 &:hover,
546 &:hover,
547 &:focus {
547 &:focus {
548 color: @grey1;
548 color: @grey1;
549 text-decoration: none;
549 text-decoration: none;
550 cursor: pointer;
550 cursor: pointer;
551 }
551 }
552 }
552 }
553
553
554 // GRID
554 // GRID
555 .sorting,
555 .sorting,
556 .sorting_desc,
556 .sorting_desc,
557 .sorting_asc {
557 .sorting_asc {
558 cursor: pointer;
558 cursor: pointer;
559 }
559 }
560 .sorting_desc:after {
560 .sorting_desc:after {
561 content: "\00A0\25B2";
561 content: "\00A0\25B2";
562 font-size: .75em;
562 font-size: .75em;
563 }
563 }
564 .sorting_asc:after {
564 .sorting_asc:after {
565 content: "\00A0\25BC";
565 content: "\00A0\25BC";
566 font-size: .68em;
566 font-size: .68em;
567 }
567 }
568
568
569
569
570 .user_auth_tokens {
570 .user_auth_tokens {
571
571
572 &.truncate {
572 &.truncate {
573 white-space: nowrap;
573 white-space: nowrap;
574 overflow: hidden;
574 overflow: hidden;
575 text-overflow: ellipsis;
575 text-overflow: ellipsis;
576 }
576 }
577
577
578 .fields .field .input {
578 .fields .field .input {
579 margin: 0;
579 margin: 0;
580 }
580 }
581
581
582 input#description {
582 input#description {
583 width: 100px;
583 width: 100px;
584 margin: 0;
584 margin: 0;
585 }
585 }
586
586
587 .drop-menu {
587 .drop-menu {
588 // TODO: johbo: Remove this, should work out of the box when
588 // TODO: johbo: Remove this, should work out of the box when
589 // having multiple inputs inline
589 // having multiple inputs inline
590 margin: 0 0 0 5px;
590 margin: 0 0 0 5px;
591 }
591 }
592 }
592 }
593 #user_list_table {
593 #user_list_table {
594 .closed {
594 .closed {
595 background-color: @grey6;
595 background-color: @grey6;
596 }
596 }
597 }
597 }
598
598
599
599
600 input {
600 input {
601 &.disabled {
601 &.disabled {
602 opacity: .5;
602 opacity: .5;
603 }
603 }
604 }
604 }
605
605
606 // remove extra padding in firefox
606 // remove extra padding in firefox
607 input::-moz-focus-inner { border:0; padding:0 }
607 input::-moz-focus-inner { border:0; padding:0 }
608
608
609 .adjacent input {
609 .adjacent input {
610 margin-bottom: @padding;
610 margin-bottom: @padding;
611 }
611 }
612
612
613 .permissions_boxes {
613 .permissions_boxes {
614 display: block;
614 display: block;
615 }
615 }
616
616
617 //TODO: lisa: this should be in tables
617 //TODO: lisa: this should be in tables
618 .show_more_col {
618 .show_more_col {
619 width: 20px;
619 width: 20px;
620 }
620 }
621
621
622 //FORMS
622 //FORMS
623
623
624 .medium-inline,
624 .medium-inline,
625 input#description.medium-inline {
625 input#description.medium-inline {
626 display: inline;
626 display: inline;
627 width: @medium-inline-input-width;
627 width: @medium-inline-input-width;
628 min-width: 100px;
628 min-width: 100px;
629 }
629 }
630
630
631 select {
631 select {
632 //reset
632 //reset
633 -webkit-appearance: none;
633 -webkit-appearance: none;
634 -moz-appearance: none;
634 -moz-appearance: none;
635
635
636 display: inline-block;
636 display: inline-block;
637 height: 28px;
637 height: 28px;
638 width: auto;
638 width: auto;
639 margin: 0 @padding @padding 0;
639 margin: 0 @padding @padding 0;
640 padding: 0 18px 0 8px;
640 padding: 0 18px 0 8px;
641 line-height:1em;
641 line-height:1em;
642 font-size: @basefontsize;
642 font-size: @basefontsize;
643 border: @border-thickness solid @rcblue;
643 border: @border-thickness solid @rcblue;
644 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
644 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
645 color: @rcblue;
645 color: @rcblue;
646
646
647 &:after {
647 &:after {
648 content: "\00A0\25BE";
648 content: "\00A0\25BE";
649 }
649 }
650
650
651 &:focus {
651 &:focus {
652 outline: none;
652 outline: none;
653 }
653 }
654 }
654 }
655
655
656 option {
656 option {
657 &:focus {
657 &:focus {
658 outline: none;
658 outline: none;
659 }
659 }
660 }
660 }
661
661
662 input,
662 input,
663 textarea {
663 textarea {
664 padding: @input-padding;
664 padding: @input-padding;
665 border: @input-border-thickness solid @border-highlight-color;
665 border: @input-border-thickness solid @border-highlight-color;
666 .border-radius (@border-radius);
666 .border-radius (@border-radius);
667 font-family: @text-light;
667 font-family: @text-light;
668 font-size: @basefontsize;
668 font-size: @basefontsize;
669
669
670 &.input-sm {
670 &.input-sm {
671 padding: 5px;
671 padding: 5px;
672 }
672 }
673
673
674 &#description {
674 &#description {
675 min-width: @input-description-minwidth;
675 min-width: @input-description-minwidth;
676 min-height: 1em;
676 min-height: 1em;
677 padding: 10px;
677 padding: 10px;
678 }
678 }
679 }
679 }
680
680
681 .field-sm {
681 .field-sm {
682 input,
682 input,
683 textarea {
683 textarea {
684 padding: 5px;
684 padding: 5px;
685 }
685 }
686 }
686 }
687
687
688 textarea {
688 textarea {
689 display: block;
689 display: block;
690 clear: both;
690 clear: both;
691 width: 100%;
691 width: 100%;
692 min-height: 100px;
692 min-height: 100px;
693 margin-bottom: @padding;
693 margin-bottom: @padding;
694 .box-sizing(border-box);
694 .box-sizing(border-box);
695 overflow: auto;
695 overflow: auto;
696 }
696 }
697
697
698 label {
698 label {
699 font-family: @text-light;
699 font-family: @text-light;
700 }
700 }
701
701
702 // GRAVATARS
702 // GRAVATARS
703 // centers gravatar on username to the right
703 // centers gravatar on username to the right
704
704
705 .gravatar {
705 .gravatar {
706 display: inline;
706 display: inline;
707 min-width: 16px;
707 min-width: 16px;
708 min-height: 16px;
708 min-height: 16px;
709 margin: -5px 0;
709 margin: -5px 0;
710 padding: 0;
710 padding: 0;
711 line-height: 1em;
711 line-height: 1em;
712 border: 1px solid @grey4;
712 border: 1px solid @grey4;
713 box-sizing: content-box;
713 box-sizing: content-box;
714
714
715 &.gravatar-large {
715 &.gravatar-large {
716 margin: -0.5em .25em -0.5em 0;
716 margin: -0.5em .25em -0.5em 0;
717 }
717 }
718
718
719 & + .user {
719 & + .user {
720 display: inline;
720 display: inline;
721 margin: 0;
721 margin: 0;
722 padding: 0 0 0 .17em;
722 padding: 0 0 0 .17em;
723 line-height: 1em;
723 line-height: 1em;
724 }
724 }
725 }
725 }
726
726
727 .user-inline-data {
727 .user-inline-data {
728 display: inline-block;
728 display: inline-block;
729 float: left;
729 float: left;
730 padding-left: .5em;
730 padding-left: .5em;
731 line-height: 1.3em;
731 line-height: 1.3em;
732 }
732 }
733
733
734 .rc-user { // gravatar + user wrapper
734 .rc-user { // gravatar + user wrapper
735 float: left;
735 float: left;
736 position: relative;
736 position: relative;
737 min-width: 100px;
737 min-width: 100px;
738 max-width: 200px;
738 max-width: 200px;
739 min-height: (@gravatar-size + @border-thickness * 2); // account for border
739 min-height: (@gravatar-size + @border-thickness * 2); // account for border
740 display: block;
740 display: block;
741 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
741 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
742
742
743
743
744 .gravatar {
744 .gravatar {
745 display: block;
745 display: block;
746 position: absolute;
746 position: absolute;
747 top: 0;
747 top: 0;
748 left: 0;
748 left: 0;
749 min-width: @gravatar-size;
749 min-width: @gravatar-size;
750 min-height: @gravatar-size;
750 min-height: @gravatar-size;
751 margin: 0;
751 margin: 0;
752 }
752 }
753
753
754 .user {
754 .user {
755 display: block;
755 display: block;
756 max-width: 175px;
756 max-width: 175px;
757 padding-top: 2px;
757 padding-top: 2px;
758 overflow: hidden;
758 overflow: hidden;
759 text-overflow: ellipsis;
759 text-overflow: ellipsis;
760 }
760 }
761 }
761 }
762
762
763 .gist-gravatar,
763 .gist-gravatar,
764 .journal_container {
764 .journal_container {
765 .gravatar-large {
765 .gravatar-large {
766 margin: 0 .5em -10px 0;
766 margin: 0 .5em -10px 0;
767 }
767 }
768 }
768 }
769
769
770
770
771 // ADMIN SETTINGS
771 // ADMIN SETTINGS
772
772
773 // Tag Patterns
773 // Tag Patterns
774 .tag_patterns {
774 .tag_patterns {
775 .tag_input {
775 .tag_input {
776 margin-bottom: @padding;
776 margin-bottom: @padding;
777 }
777 }
778 }
778 }
779
779
780 .locked_input {
780 .locked_input {
781 position: relative;
781 position: relative;
782
782
783 input {
783 input {
784 display: inline;
784 display: inline;
785 margin: 3px 5px 0px 0px;
785 margin: 3px 5px 0px 0px;
786 }
786 }
787
787
788 br {
788 br {
789 display: none;
789 display: none;
790 }
790 }
791
791
792 .error-message {
792 .error-message {
793 float: left;
793 float: left;
794 width: 100%;
794 width: 100%;
795 }
795 }
796
796
797 .lock_input_button {
797 .lock_input_button {
798 display: inline;
798 display: inline;
799 }
799 }
800
800
801 .help-block {
801 .help-block {
802 clear: both;
802 clear: both;
803 }
803 }
804 }
804 }
805
805
806 // Notifications
806 // Notifications
807
807
808 .notifications_buttons {
808 .notifications_buttons {
809 margin: 0 0 @space 0;
809 margin: 0 0 @space 0;
810 padding: 0;
810 padding: 0;
811
811
812 .btn {
812 .btn {
813 display: inline-block;
813 display: inline-block;
814 }
814 }
815 }
815 }
816
816
817 .notification-list {
817 .notification-list {
818
818
819 div {
819 div {
820 display: inline-block;
820 display: inline-block;
821 vertical-align: middle;
821 vertical-align: middle;
822 }
822 }
823
823
824 .container {
824 .container {
825 display: block;
825 display: block;
826 margin: 0 0 @padding 0;
826 margin: 0 0 @padding 0;
827 }
827 }
828
828
829 .delete-notifications {
829 .delete-notifications {
830 margin-left: @padding;
830 margin-left: @padding;
831 text-align: right;
831 text-align: right;
832 cursor: pointer;
832 cursor: pointer;
833 }
833 }
834
834
835 .read-notifications {
835 .read-notifications {
836 margin-left: @padding/2;
836 margin-left: @padding/2;
837 text-align: right;
837 text-align: right;
838 width: 35px;
838 width: 35px;
839 cursor: pointer;
839 cursor: pointer;
840 }
840 }
841
841
842 .icon-minus-sign {
842 .icon-minus-sign {
843 color: @alert2;
843 color: @alert2;
844 }
844 }
845
845
846 .icon-ok-sign {
846 .icon-ok-sign {
847 color: @alert1;
847 color: @alert1;
848 }
848 }
849 }
849 }
850
850
851 .user_settings {
851 .user_settings {
852 float: left;
852 float: left;
853 clear: both;
853 clear: both;
854 display: block;
854 display: block;
855 width: 100%;
855 width: 100%;
856
856
857 .gravatar_box {
857 .gravatar_box {
858 margin-bottom: @padding;
858 margin-bottom: @padding;
859
859
860 &:after {
860 &:after {
861 content: " ";
861 content: " ";
862 clear: both;
862 clear: both;
863 width: 100%;
863 width: 100%;
864 }
864 }
865 }
865 }
866
866
867 .fields .field {
867 .fields .field {
868 clear: both;
868 clear: both;
869 }
869 }
870 }
870 }
871
871
872 .advanced_settings {
872 .advanced_settings {
873 margin-bottom: @space;
873 margin-bottom: @space;
874
874
875 .help-block {
875 .help-block {
876 margin-left: 0;
876 margin-left: 0;
877 }
877 }
878
878
879 button + .help-block {
879 button + .help-block {
880 margin-top: @padding;
880 margin-top: @padding;
881 }
881 }
882 }
882 }
883
883
884 // admin settings radio buttons and labels
884 // admin settings radio buttons and labels
885 .label-2 {
885 .label-2 {
886 float: left;
886 float: left;
887 width: @label2-width;
887 width: @label2-width;
888
888
889 label {
889 label {
890 color: @grey1;
890 color: @grey1;
891 }
891 }
892 }
892 }
893 .checkboxes {
893 .checkboxes {
894 float: left;
894 float: left;
895 width: @checkboxes-width;
895 width: @checkboxes-width;
896 margin-bottom: @padding;
896 margin-bottom: @padding;
897
897
898 .checkbox {
898 .checkbox {
899 width: 100%;
899 width: 100%;
900
900
901 label {
901 label {
902 margin: 0;
902 margin: 0;
903 padding: 0;
903 padding: 0;
904 }
904 }
905 }
905 }
906
906
907 .checkbox + .checkbox {
907 .checkbox + .checkbox {
908 display: inline-block;
908 display: inline-block;
909 }
909 }
910
910
911 label {
911 label {
912 margin-right: 1em;
912 margin-right: 1em;
913 }
913 }
914 }
914 }
915
915
916 // CHANGELOG
916 // CHANGELOG
917 .container_header {
917 .container_header {
918 float: left;
918 float: left;
919 display: block;
919 display: block;
920 width: 100%;
920 width: 100%;
921 margin: @padding 0 @padding;
921 margin: @padding 0 @padding;
922
922
923 #filter_changelog {
923 #filter_changelog {
924 float: left;
924 float: left;
925 margin-right: @padding;
925 margin-right: @padding;
926 }
926 }
927
927
928 .breadcrumbs_light {
928 .breadcrumbs_light {
929 display: inline-block;
929 display: inline-block;
930 }
930 }
931 }
931 }
932
932
933 .info_box {
933 .info_box {
934 float: right;
934 float: right;
935 }
935 }
936
936
937
937
938 #graph_nodes {
938 #graph_nodes {
939 padding-top: 43px;
939 padding-top: 43px;
940 }
940 }
941
941
942 #graph_content{
942 #graph_content{
943
943
944 // adjust for table headers so that graph renders properly
944 // adjust for table headers so that graph renders properly
945 // #graph_nodes padding - table cell padding
945 // #graph_nodes padding - table cell padding
946 padding-top: (@space - (@basefontsize * 2.4));
946 padding-top: (@space - (@basefontsize * 2.4));
947
947
948 &.graph_full_width {
948 &.graph_full_width {
949 width: 100%;
949 width: 100%;
950 max-width: 100%;
950 max-width: 100%;
951 }
951 }
952 }
952 }
953
953
954 #graph {
954 #graph {
955 .flag_status {
955 .flag_status {
956 margin: 0;
956 margin: 0;
957 }
957 }
958
958
959 .pagination-left {
959 .pagination-left {
960 float: left;
960 float: left;
961 clear: both;
961 clear: both;
962 }
962 }
963
963
964 .log-container {
964 .log-container {
965 max-width: 345px;
965 max-width: 345px;
966
966
967 .message{
967 .message{
968 max-width: 340px;
968 max-width: 340px;
969 }
969 }
970 }
970 }
971
971
972 .graph-col-wrapper {
972 .graph-col-wrapper {
973 padding-left: 110px;
973 padding-left: 110px;
974
974
975 #graph_nodes {
975 #graph_nodes {
976 width: 100px;
976 width: 100px;
977 margin-left: -110px;
977 margin-left: -110px;
978 float: left;
978 float: left;
979 clear: left;
979 clear: left;
980 }
980 }
981 }
981 }
982
982
983 .load-more-commits {
983 .load-more-commits {
984 text-align: center;
984 text-align: center;
985 }
985 }
986 .load-more-commits:hover {
986 .load-more-commits:hover {
987 background-color: @grey7;
987 background-color: @grey7;
988 }
988 }
989 .load-more-commits {
989 .load-more-commits {
990 a {
990 a {
991 display: block;
991 display: block;
992 }
992 }
993 }
993 }
994 }
994 }
995
995
996 #filter_changelog {
996 #filter_changelog {
997 float: left;
997 float: left;
998 }
998 }
999
999
1000
1000
1001 //--- THEME ------------------//
1001 //--- THEME ------------------//
1002
1002
1003 #logo {
1003 #logo {
1004 float: left;
1004 float: left;
1005 margin: 9px 0 0 0;
1005 margin: 9px 0 0 0;
1006
1006
1007 .header {
1007 .header {
1008 background-color: transparent;
1008 background-color: transparent;
1009 }
1009 }
1010
1010
1011 a {
1011 a {
1012 display: inline-block;
1012 display: inline-block;
1013 }
1013 }
1014
1014
1015 img {
1015 img {
1016 height:30px;
1016 height:30px;
1017 }
1017 }
1018 }
1018 }
1019
1019
1020 .logo-wrapper {
1020 .logo-wrapper {
1021 float:left;
1021 float:left;
1022 }
1022 }
1023
1023
1024 .branding{
1024 .branding{
1025 float: left;
1025 float: left;
1026 padding: 9px 2px;
1026 padding: 9px 2px;
1027 line-height: 1em;
1027 line-height: 1em;
1028 font-size: @navigation-fontsize;
1028 font-size: @navigation-fontsize;
1029 }
1029 }
1030
1030
1031 img {
1031 img {
1032 border: none;
1032 border: none;
1033 outline: none;
1033 outline: none;
1034 }
1034 }
1035 user-profile-header
1035 user-profile-header
1036 label {
1036 label {
1037
1037
1038 input[type="checkbox"] {
1038 input[type="checkbox"] {
1039 margin-right: 1em;
1039 margin-right: 1em;
1040 }
1040 }
1041 input[type="radio"] {
1041 input[type="radio"] {
1042 margin-right: 1em;
1042 margin-right: 1em;
1043 }
1043 }
1044 }
1044 }
1045
1045
1046 .flag_status {
1046 .flag_status {
1047 margin: 2px 8px 6px 2px;
1047 margin: 2px 8px 6px 2px;
1048 &.under_review {
1048 &.under_review {
1049 .circle(5px, @alert3);
1049 .circle(5px, @alert3);
1050 }
1050 }
1051 &.approved {
1051 &.approved {
1052 .circle(5px, @alert1);
1052 .circle(5px, @alert1);
1053 }
1053 }
1054 &.rejected,
1054 &.rejected,
1055 &.forced_closed{
1055 &.forced_closed{
1056 .circle(5px, @alert2);
1056 .circle(5px, @alert2);
1057 }
1057 }
1058 &.not_reviewed {
1058 &.not_reviewed {
1059 .circle(5px, @grey5);
1059 .circle(5px, @grey5);
1060 }
1060 }
1061 }
1061 }
1062
1062
1063 .flag_status_comment_box {
1063 .flag_status_comment_box {
1064 margin: 5px 6px 0px 2px;
1064 margin: 5px 6px 0px 2px;
1065 }
1065 }
1066 .test_pattern_preview {
1066 .test_pattern_preview {
1067 margin: @space 0;
1067 margin: @space 0;
1068
1068
1069 p {
1069 p {
1070 margin-bottom: 0;
1070 margin-bottom: 0;
1071 border-bottom: @border-thickness solid @border-default-color;
1071 border-bottom: @border-thickness solid @border-default-color;
1072 color: @grey3;
1072 color: @grey3;
1073 }
1073 }
1074
1074
1075 .btn {
1075 .btn {
1076 margin-bottom: @padding;
1076 margin-bottom: @padding;
1077 }
1077 }
1078 }
1078 }
1079 #test_pattern_result {
1079 #test_pattern_result {
1080 display: none;
1080 display: none;
1081 &:extend(pre);
1081 &:extend(pre);
1082 padding: .9em;
1082 padding: .9em;
1083 color: @grey3;
1083 color: @grey3;
1084 background-color: @grey7;
1084 background-color: @grey7;
1085 border-right: @border-thickness solid @border-default-color;
1085 border-right: @border-thickness solid @border-default-color;
1086 border-bottom: @border-thickness solid @border-default-color;
1086 border-bottom: @border-thickness solid @border-default-color;
1087 border-left: @border-thickness solid @border-default-color;
1087 border-left: @border-thickness solid @border-default-color;
1088 }
1088 }
1089
1089
1090 #repo_vcs_settings {
1090 #repo_vcs_settings {
1091 #inherit_overlay_vcs_default {
1091 #inherit_overlay_vcs_default {
1092 display: none;
1092 display: none;
1093 }
1093 }
1094 #inherit_overlay_vcs_custom {
1094 #inherit_overlay_vcs_custom {
1095 display: custom;
1095 display: custom;
1096 }
1096 }
1097 &.inherited {
1097 &.inherited {
1098 #inherit_overlay_vcs_default {
1098 #inherit_overlay_vcs_default {
1099 display: block;
1099 display: block;
1100 }
1100 }
1101 #inherit_overlay_vcs_custom {
1101 #inherit_overlay_vcs_custom {
1102 display: none;
1102 display: none;
1103 }
1103 }
1104 }
1104 }
1105 }
1105 }
1106
1106
1107 .issue-tracker-link {
1107 .issue-tracker-link {
1108 color: @rcblue;
1108 color: @rcblue;
1109 }
1109 }
1110
1110
1111 // Issue Tracker Table Show/Hide
1111 // Issue Tracker Table Show/Hide
1112 #repo_issue_tracker {
1112 #repo_issue_tracker {
1113 #inherit_overlay {
1113 #inherit_overlay {
1114 display: none;
1114 display: none;
1115 }
1115 }
1116 #custom_overlay {
1116 #custom_overlay {
1117 display: custom;
1117 display: custom;
1118 }
1118 }
1119 &.inherited {
1119 &.inherited {
1120 #inherit_overlay {
1120 #inherit_overlay {
1121 display: block;
1121 display: block;
1122 }
1122 }
1123 #custom_overlay {
1123 #custom_overlay {
1124 display: none;
1124 display: none;
1125 }
1125 }
1126 }
1126 }
1127 }
1127 }
1128 table.issuetracker {
1128 table.issuetracker {
1129 &.readonly {
1129 &.readonly {
1130 tr, td {
1130 tr, td {
1131 color: @grey3;
1131 color: @grey3;
1132 }
1132 }
1133 }
1133 }
1134 .edit {
1134 .edit {
1135 display: none;
1135 display: none;
1136 }
1136 }
1137 .editopen {
1137 .editopen {
1138 .edit {
1138 .edit {
1139 display: inline;
1139 display: inline;
1140 }
1140 }
1141 .entry {
1141 .entry {
1142 display: none;
1142 display: none;
1143 }
1143 }
1144 }
1144 }
1145 tr td.td-action {
1145 tr td.td-action {
1146 min-width: 117px;
1146 min-width: 117px;
1147 }
1147 }
1148 td input {
1148 td input {
1149 max-width: none;
1149 max-width: none;
1150 min-width: 30px;
1150 min-width: 30px;
1151 width: 80%;
1151 width: 80%;
1152 }
1152 }
1153 .issuetracker_pref input {
1153 .issuetracker_pref input {
1154 width: 40%;
1154 width: 40%;
1155 }
1155 }
1156 input.edit_issuetracker_update {
1156 input.edit_issuetracker_update {
1157 margin-right: 0;
1157 margin-right: 0;
1158 width: auto;
1158 width: auto;
1159 }
1159 }
1160 }
1160 }
1161
1161
1162 table.integrations {
1162 table.integrations {
1163 .td-icon {
1163 .td-icon {
1164 width: 20px;
1164 width: 20px;
1165 .integration-icon {
1165 .integration-icon {
1166 height: 20px;
1166 height: 20px;
1167 width: 20px;
1167 width: 20px;
1168 }
1168 }
1169 }
1169 }
1170 }
1170 }
1171
1171
1172 .integrations {
1172 .integrations {
1173 a.integration-box {
1173 a.integration-box {
1174 color: @text-color;
1174 color: @text-color;
1175 &:hover {
1175 &:hover {
1176 .panel {
1176 .panel {
1177 background: #fbfbfb;
1177 background: #fbfbfb;
1178 }
1178 }
1179 }
1179 }
1180 .integration-icon {
1180 .integration-icon {
1181 width: 30px;
1181 width: 30px;
1182 height: 30px;
1182 height: 30px;
1183 margin-right: 20px;
1183 margin-right: 20px;
1184 float: left;
1184 float: left;
1185 }
1185 }
1186
1186
1187 .panel-body {
1187 .panel-body {
1188 padding: 10px;
1188 padding: 10px;
1189 }
1189 }
1190 .panel {
1190 .panel {
1191 margin-bottom: 10px;
1191 margin-bottom: 10px;
1192 }
1192 }
1193 h2 {
1193 h2 {
1194 display: inline-block;
1194 display: inline-block;
1195 margin: 0;
1195 margin: 0;
1196 min-width: 140px;
1196 min-width: 140px;
1197 }
1197 }
1198 }
1198 }
1199 }
1199 }
1200
1200
1201 //Permissions Settings
1201 //Permissions Settings
1202 #add_perm {
1202 #add_perm {
1203 margin: 0 0 @padding;
1203 margin: 0 0 @padding;
1204 cursor: pointer;
1204 cursor: pointer;
1205 }
1205 }
1206
1206
1207 .perm_ac {
1207 .perm_ac {
1208 input {
1208 input {
1209 width: 95%;
1209 width: 95%;
1210 }
1210 }
1211 }
1211 }
1212
1212
1213 .autocomplete-suggestions {
1213 .autocomplete-suggestions {
1214 width: auto !important; // overrides autocomplete.js
1214 width: auto !important; // overrides autocomplete.js
1215 margin: 0;
1215 margin: 0;
1216 border: @border-thickness solid @rcblue;
1216 border: @border-thickness solid @rcblue;
1217 border-radius: @border-radius;
1217 border-radius: @border-radius;
1218 color: @rcblue;
1218 color: @rcblue;
1219 background-color: white;
1219 background-color: white;
1220 }
1220 }
1221 .autocomplete-selected {
1221 .autocomplete-selected {
1222 background: #F0F0F0;
1222 background: #F0F0F0;
1223 }
1223 }
1224 .ac-container-wrap {
1224 .ac-container-wrap {
1225 margin: 0;
1225 margin: 0;
1226 padding: 8px;
1226 padding: 8px;
1227 border-bottom: @border-thickness solid @rclightblue;
1227 border-bottom: @border-thickness solid @rclightblue;
1228 list-style-type: none;
1228 list-style-type: none;
1229 cursor: pointer;
1229 cursor: pointer;
1230
1230
1231 &:hover {
1231 &:hover {
1232 background-color: @rclightblue;
1232 background-color: @rclightblue;
1233 }
1233 }
1234
1234
1235 img {
1235 img {
1236 height: @gravatar-size;
1236 height: @gravatar-size;
1237 width: @gravatar-size;
1237 width: @gravatar-size;
1238 margin-right: 1em;
1238 margin-right: 1em;
1239 }
1239 }
1240
1240
1241 strong {
1241 strong {
1242 font-weight: normal;
1242 font-weight: normal;
1243 }
1243 }
1244 }
1244 }
1245
1245
1246 // Settings Dropdown
1246 // Settings Dropdown
1247 .user-menu .container {
1247 .user-menu .container {
1248 padding: 0 4px;
1248 padding: 0 4px;
1249 margin: 0;
1249 margin: 0;
1250 }
1250 }
1251
1251
1252 .user-menu .gravatar {
1252 .user-menu .gravatar {
1253 cursor: pointer;
1253 cursor: pointer;
1254 }
1254 }
1255
1255
1256 .codeblock {
1256 .codeblock {
1257 margin-bottom: @padding;
1257 margin-bottom: @padding;
1258 clear: both;
1258 clear: both;
1259
1259
1260 .stats{
1260 .stats{
1261 overflow: hidden;
1261 overflow: hidden;
1262 }
1262 }
1263
1263
1264 .message{
1264 .message{
1265 textarea{
1265 textarea{
1266 margin: 0;
1266 margin: 0;
1267 }
1267 }
1268 }
1268 }
1269
1269
1270 .code-header {
1270 .code-header {
1271 .stats {
1271 .stats {
1272 line-height: 2em;
1272 line-height: 2em;
1273
1273
1274 .revision_id {
1274 .revision_id {
1275 margin-left: 0;
1275 margin-left: 0;
1276 }
1276 }
1277 .buttons {
1277 .buttons {
1278 padding-right: 0;
1278 padding-right: 0;
1279 }
1279 }
1280 }
1280 }
1281
1281
1282 .item{
1282 .item{
1283 margin-right: 0.5em;
1283 margin-right: 0.5em;
1284 }
1284 }
1285 }
1285 }
1286
1286
1287 #editor_container{
1287 #editor_container{
1288 position: relative;
1288 position: relative;
1289 margin: @padding;
1289 margin: @padding;
1290 }
1290 }
1291 }
1291 }
1292
1292
1293 #file_history_container {
1293 #file_history_container {
1294 display: none;
1294 display: none;
1295 }
1295 }
1296
1296
1297 .file-history-inner {
1297 .file-history-inner {
1298 margin-bottom: 10px;
1298 margin-bottom: 10px;
1299 }
1299 }
1300
1300
1301 // Pull Requests
1301 // Pull Requests
1302 .summary-details {
1302 .summary-details {
1303 width: 72%;
1303 width: 72%;
1304 }
1304 }
1305 .pr-summary {
1305 .pr-summary {
1306 border-bottom: @border-thickness solid @grey5;
1306 border-bottom: @border-thickness solid @grey5;
1307 margin-bottom: @space;
1307 margin-bottom: @space;
1308 }
1308 }
1309 .reviewers-title {
1309 .reviewers-title {
1310 width: 25%;
1310 width: 25%;
1311 min-width: 200px;
1311 min-width: 200px;
1312 }
1312 }
1313 .reviewers {
1313 .reviewers {
1314 width: 25%;
1314 width: 25%;
1315 min-width: 200px;
1315 min-width: 200px;
1316 }
1316 }
1317 .reviewers ul li {
1317 .reviewers ul li {
1318 position: relative;
1318 position: relative;
1319 width: 100%;
1319 width: 100%;
1320 margin-bottom: 8px;
1320 margin-bottom: 8px;
1321 }
1321 }
1322
1322
1323 .reviewer_entry {
1323 .reviewer_entry {
1324 min-height: 55px;
1324 min-height: 55px;
1325 }
1325 }
1326
1326
1327 .reviewers_member {
1327 .reviewers_member {
1328 width: 100%;
1328 width: 100%;
1329 overflow: auto;
1329 overflow: auto;
1330 }
1330 }
1331 .reviewer_reason {
1331 .reviewer_reason {
1332 padding-left: 20px;
1332 padding-left: 20px;
1333 }
1333 }
1334 .reviewer_status {
1334 .reviewer_status {
1335 display: inline-block;
1335 display: inline-block;
1336 vertical-align: top;
1336 vertical-align: top;
1337 width: 7%;
1337 width: 7%;
1338 min-width: 20px;
1338 min-width: 20px;
1339 height: 1.2em;
1339 height: 1.2em;
1340 margin-top: 3px;
1340 margin-top: 3px;
1341 line-height: 1em;
1341 line-height: 1em;
1342 }
1342 }
1343
1343
1344 .reviewer_name {
1344 .reviewer_name {
1345 display: inline-block;
1345 display: inline-block;
1346 max-width: 83%;
1346 max-width: 83%;
1347 padding-right: 20px;
1347 padding-right: 20px;
1348 vertical-align: middle;
1348 vertical-align: middle;
1349 line-height: 1;
1349 line-height: 1;
1350
1350
1351 .rc-user {
1351 .rc-user {
1352 min-width: 0;
1352 min-width: 0;
1353 margin: -2px 1em 0 0;
1353 margin: -2px 1em 0 0;
1354 }
1354 }
1355
1355
1356 .reviewer {
1356 .reviewer {
1357 float: left;
1357 float: left;
1358 }
1358 }
1359 }
1359 }
1360
1360
1361 .reviewer_member_mandatory,
1361 .reviewer_member_mandatory,
1362 .reviewer_member_mandatory_remove,
1362 .reviewer_member_mandatory_remove,
1363 .reviewer_member_remove {
1363 .reviewer_member_remove {
1364 position: absolute;
1364 position: absolute;
1365 right: 0;
1365 right: 0;
1366 top: 0;
1366 top: 0;
1367 width: 16px;
1367 width: 16px;
1368 margin-bottom: 10px;
1368 margin-bottom: 10px;
1369 padding: 0;
1369 padding: 0;
1370 color: black;
1370 color: black;
1371 }
1371 }
1372
1372
1373 .reviewer_member_mandatory_remove {
1373 .reviewer_member_mandatory_remove {
1374 color: @grey4;
1374 color: @grey4;
1375 }
1375 }
1376
1376
1377 .reviewer_member_mandatory {
1377 .reviewer_member_mandatory {
1378 padding-top:20px;
1378 padding-top:20px;
1379 }
1379 }
1380
1380
1381 .reviewer_member_status {
1381 .reviewer_member_status {
1382 margin-top: 5px;
1382 margin-top: 5px;
1383 }
1383 }
1384 .pr-summary #summary{
1384 .pr-summary #summary{
1385 width: 100%;
1385 width: 100%;
1386 }
1386 }
1387 .pr-summary .action_button:hover {
1387 .pr-summary .action_button:hover {
1388 border: 0;
1388 border: 0;
1389 cursor: pointer;
1389 cursor: pointer;
1390 }
1390 }
1391 .pr-details-title {
1391 .pr-details-title {
1392 padding-bottom: 8px;
1392 padding-bottom: 8px;
1393 border-bottom: @border-thickness solid @grey5;
1393 border-bottom: @border-thickness solid @grey5;
1394
1394
1395 .action_button.disabled {
1395 .action_button.disabled {
1396 color: @grey4;
1396 color: @grey4;
1397 cursor: inherit;
1397 cursor: inherit;
1398 }
1398 }
1399 .action_button {
1399 .action_button {
1400 color: @rcblue;
1400 color: @rcblue;
1401 }
1401 }
1402 }
1402 }
1403 .pr-details-content {
1403 .pr-details-content {
1404 margin-top: @textmargin;
1404 margin-top: @textmargin;
1405 margin-bottom: @textmargin;
1405 margin-bottom: @textmargin;
1406 }
1406 }
1407 .pr-description {
1407 .pr-description {
1408 white-space:pre-wrap;
1408 white-space:pre-wrap;
1409 }
1409 }
1410
1410
1411 .pr-reviewer-rules {
1411 .pr-reviewer-rules {
1412 padding: 10px 0px 20px 0px;
1412 padding: 10px 0px 20px 0px;
1413 }
1413 }
1414
1414
1415 .group_members {
1415 .group_members {
1416 margin-top: 0;
1416 margin-top: 0;
1417 padding: 0;
1417 padding: 0;
1418 list-style: outside none none;
1418 list-style: outside none none;
1419
1419
1420 img {
1420 img {
1421 height: @gravatar-size;
1421 height: @gravatar-size;
1422 width: @gravatar-size;
1422 width: @gravatar-size;
1423 margin-right: .5em;
1423 margin-right: .5em;
1424 margin-left: 3px;
1424 margin-left: 3px;
1425 }
1425 }
1426
1426
1427 .to-delete {
1427 .to-delete {
1428 .user {
1428 .user {
1429 text-decoration: line-through;
1429 text-decoration: line-through;
1430 }
1430 }
1431 }
1431 }
1432 }
1432 }
1433
1433
1434 .compare_view_commits_title {
1434 .compare_view_commits_title {
1435 .disabled {
1435 .disabled {
1436 cursor: inherit;
1436 cursor: inherit;
1437 &:hover{
1437 &:hover{
1438 background-color: inherit;
1438 background-color: inherit;
1439 color: inherit;
1439 color: inherit;
1440 }
1440 }
1441 }
1441 }
1442 }
1442 }
1443
1443
1444 .subtitle-compare {
1444 .subtitle-compare {
1445 margin: -15px 0px 0px 0px;
1445 margin: -15px 0px 0px 0px;
1446 }
1446 }
1447
1447
1448 .comments-summary-td {
1448 .comments-summary-td {
1449 border-top: 1px dashed @grey5;
1449 border-top: 1px dashed @grey5;
1450 }
1450 }
1451
1451
1452 // new entry in group_members
1452 // new entry in group_members
1453 .td-author-new-entry {
1453 .td-author-new-entry {
1454 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1454 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1455 }
1455 }
1456
1456
1457 .usergroup_member_remove {
1457 .usergroup_member_remove {
1458 width: 16px;
1458 width: 16px;
1459 margin-bottom: 10px;
1459 margin-bottom: 10px;
1460 padding: 0;
1460 padding: 0;
1461 color: black !important;
1461 color: black !important;
1462 cursor: pointer;
1462 cursor: pointer;
1463 }
1463 }
1464
1464
1465 .reviewer_ac .ac-input {
1465 .reviewer_ac .ac-input {
1466 width: 92%;
1466 width: 92%;
1467 margin-bottom: 1em;
1467 margin-bottom: 1em;
1468 }
1468 }
1469
1469
1470 .compare_view_commits tr{
1470 .compare_view_commits tr{
1471 height: 20px;
1471 height: 20px;
1472 }
1472 }
1473 .compare_view_commits td {
1473 .compare_view_commits td {
1474 vertical-align: top;
1474 vertical-align: top;
1475 padding-top: 10px;
1475 padding-top: 10px;
1476 }
1476 }
1477 .compare_view_commits .author {
1477 .compare_view_commits .author {
1478 margin-left: 5px;
1478 margin-left: 5px;
1479 }
1479 }
1480
1480
1481 .compare_view_commits {
1481 .compare_view_commits {
1482 .color-a {
1482 .color-a {
1483 color: @alert1;
1483 color: @alert1;
1484 }
1484 }
1485
1485
1486 .color-c {
1486 .color-c {
1487 color: @color3;
1487 color: @color3;
1488 }
1488 }
1489
1489
1490 .color-r {
1490 .color-r {
1491 color: @color5;
1491 color: @color5;
1492 }
1492 }
1493
1493
1494 .color-a-bg {
1494 .color-a-bg {
1495 background-color: @alert1;
1495 background-color: @alert1;
1496 }
1496 }
1497
1497
1498 .color-c-bg {
1498 .color-c-bg {
1499 background-color: @alert3;
1499 background-color: @alert3;
1500 }
1500 }
1501
1501
1502 .color-r-bg {
1502 .color-r-bg {
1503 background-color: @alert2;
1503 background-color: @alert2;
1504 }
1504 }
1505
1505
1506 .color-a-border {
1506 .color-a-border {
1507 border: 1px solid @alert1;
1507 border: 1px solid @alert1;
1508 }
1508 }
1509
1509
1510 .color-c-border {
1510 .color-c-border {
1511 border: 1px solid @alert3;
1511 border: 1px solid @alert3;
1512 }
1512 }
1513
1513
1514 .color-r-border {
1514 .color-r-border {
1515 border: 1px solid @alert2;
1515 border: 1px solid @alert2;
1516 }
1516 }
1517
1517
1518 .commit-change-indicator {
1518 .commit-change-indicator {
1519 width: 15px;
1519 width: 15px;
1520 height: 15px;
1520 height: 15px;
1521 position: relative;
1521 position: relative;
1522 left: 15px;
1522 left: 15px;
1523 }
1523 }
1524
1524
1525 .commit-change-content {
1525 .commit-change-content {
1526 text-align: center;
1526 text-align: center;
1527 vertical-align: middle;
1527 vertical-align: middle;
1528 line-height: 15px;
1528 line-height: 15px;
1529 }
1529 }
1530 }
1530 }
1531
1531
1532 .compare_view_files {
1532 .compare_view_files {
1533 width: 100%;
1533 width: 100%;
1534
1534
1535 td {
1535 td {
1536 vertical-align: middle;
1536 vertical-align: middle;
1537 }
1537 }
1538 }
1538 }
1539
1539
1540 .compare_view_filepath {
1540 .compare_view_filepath {
1541 color: @grey1;
1541 color: @grey1;
1542 }
1542 }
1543
1543
1544 .show_more {
1544 .show_more {
1545 display: inline-block;
1545 display: inline-block;
1546 position: relative;
1546 position: relative;
1547 vertical-align: middle;
1547 vertical-align: middle;
1548 width: 4px;
1548 width: 4px;
1549 height: @basefontsize;
1549 height: @basefontsize;
1550
1550
1551 &:after {
1551 &:after {
1552 content: "\00A0\25BE";
1552 content: "\00A0\25BE";
1553 display: inline-block;
1553 display: inline-block;
1554 width:10px;
1554 width:10px;
1555 line-height: 5px;
1555 line-height: 5px;
1556 font-size: 12px;
1556 font-size: 12px;
1557 cursor: pointer;
1557 cursor: pointer;
1558 }
1558 }
1559 }
1559 }
1560
1560
1561 .journal_more .show_more {
1561 .journal_more .show_more {
1562 display: inline;
1562 display: inline;
1563
1563
1564 &:after {
1564 &:after {
1565 content: none;
1565 content: none;
1566 }
1566 }
1567 }
1567 }
1568
1568
1569 .open .show_more:after,
1569 .open .show_more:after,
1570 .select2-dropdown-open .show_more:after {
1570 .select2-dropdown-open .show_more:after {
1571 .rotate(180deg);
1571 .rotate(180deg);
1572 margin-left: 4px;
1572 margin-left: 4px;
1573 }
1573 }
1574
1574
1575
1575
1576 .compare_view_commits .collapse_commit:after {
1576 .compare_view_commits .collapse_commit:after {
1577 cursor: pointer;
1577 cursor: pointer;
1578 content: "\00A0\25B4";
1578 content: "\00A0\25B4";
1579 margin-left: -3px;
1579 margin-left: -3px;
1580 font-size: 17px;
1580 font-size: 17px;
1581 color: @grey4;
1581 color: @grey4;
1582 }
1582 }
1583
1583
1584 .diff_links {
1584 .diff_links {
1585 margin-left: 8px;
1585 margin-left: 8px;
1586 }
1586 }
1587
1587
1588 div.ancestor {
1588 div.ancestor {
1589 margin: -30px 0px;
1589 margin: -30px 0px;
1590 }
1590 }
1591
1591
1592 .cs_icon_td input[type="checkbox"] {
1592 .cs_icon_td input[type="checkbox"] {
1593 display: none;
1593 display: none;
1594 }
1594 }
1595
1595
1596 .cs_icon_td .expand_file_icon:after {
1596 .cs_icon_td .expand_file_icon:after {
1597 cursor: pointer;
1597 cursor: pointer;
1598 content: "\00A0\25B6";
1598 content: "\00A0\25B6";
1599 font-size: 12px;
1599 font-size: 12px;
1600 color: @grey4;
1600 color: @grey4;
1601 }
1601 }
1602
1602
1603 .cs_icon_td .collapse_file_icon:after {
1603 .cs_icon_td .collapse_file_icon:after {
1604 cursor: pointer;
1604 cursor: pointer;
1605 content: "\00A0\25BC";
1605 content: "\00A0\25BC";
1606 font-size: 12px;
1606 font-size: 12px;
1607 color: @grey4;
1607 color: @grey4;
1608 }
1608 }
1609
1609
1610 /*new binary
1610 /*new binary
1611 NEW_FILENODE = 1
1611 NEW_FILENODE = 1
1612 DEL_FILENODE = 2
1612 DEL_FILENODE = 2
1613 MOD_FILENODE = 3
1613 MOD_FILENODE = 3
1614 RENAMED_FILENODE = 4
1614 RENAMED_FILENODE = 4
1615 COPIED_FILENODE = 5
1615 COPIED_FILENODE = 5
1616 CHMOD_FILENODE = 6
1616 CHMOD_FILENODE = 6
1617 BIN_FILENODE = 7
1617 BIN_FILENODE = 7
1618 */
1618 */
1619 .cs_files_expand {
1619 .cs_files_expand {
1620 font-size: @basefontsize + 5px;
1620 font-size: @basefontsize + 5px;
1621 line-height: 1.8em;
1621 line-height: 1.8em;
1622 float: right;
1622 float: right;
1623 }
1623 }
1624
1624
1625 .cs_files_expand span{
1625 .cs_files_expand span{
1626 color: @rcblue;
1626 color: @rcblue;
1627 cursor: pointer;
1627 cursor: pointer;
1628 }
1628 }
1629 .cs_files {
1629 .cs_files {
1630 clear: both;
1630 clear: both;
1631 padding-bottom: @padding;
1631 padding-bottom: @padding;
1632
1632
1633 .cur_cs {
1633 .cur_cs {
1634 margin: 10px 2px;
1634 margin: 10px 2px;
1635 font-weight: bold;
1635 font-weight: bold;
1636 }
1636 }
1637
1637
1638 .node {
1638 .node {
1639 float: left;
1639 float: left;
1640 }
1640 }
1641
1641
1642 .changes {
1642 .changes {
1643 float: right;
1643 float: right;
1644 color: white;
1644 color: white;
1645 font-size: @basefontsize - 4px;
1645 font-size: @basefontsize - 4px;
1646 margin-top: 4px;
1646 margin-top: 4px;
1647 opacity: 0.6;
1647 opacity: 0.6;
1648 filter: Alpha(opacity=60); /* IE8 and earlier */
1648 filter: Alpha(opacity=60); /* IE8 and earlier */
1649
1649
1650 .added {
1650 .added {
1651 background-color: @alert1;
1651 background-color: @alert1;
1652 float: left;
1652 float: left;
1653 text-align: center;
1653 text-align: center;
1654 }
1654 }
1655
1655
1656 .deleted {
1656 .deleted {
1657 background-color: @alert2;
1657 background-color: @alert2;
1658 float: left;
1658 float: left;
1659 text-align: center;
1659 text-align: center;
1660 }
1660 }
1661
1661
1662 .bin {
1662 .bin {
1663 background-color: @alert1;
1663 background-color: @alert1;
1664 text-align: center;
1664 text-align: center;
1665 }
1665 }
1666
1666
1667 /*new binary*/
1667 /*new binary*/
1668 .bin.bin1 {
1668 .bin.bin1 {
1669 background-color: @alert1;
1669 background-color: @alert1;
1670 text-align: center;
1670 text-align: center;
1671 }
1671 }
1672
1672
1673 /*deleted binary*/
1673 /*deleted binary*/
1674 .bin.bin2 {
1674 .bin.bin2 {
1675 background-color: @alert2;
1675 background-color: @alert2;
1676 text-align: center;
1676 text-align: center;
1677 }
1677 }
1678
1678
1679 /*mod binary*/
1679 /*mod binary*/
1680 .bin.bin3 {
1680 .bin.bin3 {
1681 background-color: @grey2;
1681 background-color: @grey2;
1682 text-align: center;
1682 text-align: center;
1683 }
1683 }
1684
1684
1685 /*rename file*/
1685 /*rename file*/
1686 .bin.bin4 {
1686 .bin.bin4 {
1687 background-color: @alert4;
1687 background-color: @alert4;
1688 text-align: center;
1688 text-align: center;
1689 }
1689 }
1690
1690
1691 /*copied file*/
1691 /*copied file*/
1692 .bin.bin5 {
1692 .bin.bin5 {
1693 background-color: @alert4;
1693 background-color: @alert4;
1694 text-align: center;
1694 text-align: center;
1695 }
1695 }
1696
1696
1697 /*chmod file*/
1697 /*chmod file*/
1698 .bin.bin6 {
1698 .bin.bin6 {
1699 background-color: @grey2;
1699 background-color: @grey2;
1700 text-align: center;
1700 text-align: center;
1701 }
1701 }
1702 }
1702 }
1703 }
1703 }
1704
1704
1705 .cs_files .cs_added, .cs_files .cs_A,
1705 .cs_files .cs_added, .cs_files .cs_A,
1706 .cs_files .cs_added, .cs_files .cs_M,
1706 .cs_files .cs_added, .cs_files .cs_M,
1707 .cs_files .cs_added, .cs_files .cs_D {
1707 .cs_files .cs_added, .cs_files .cs_D {
1708 height: 16px;
1708 height: 16px;
1709 padding-right: 10px;
1709 padding-right: 10px;
1710 margin-top: 7px;
1710 margin-top: 7px;
1711 text-align: left;
1711 text-align: left;
1712 }
1712 }
1713
1713
1714 .cs_icon_td {
1714 .cs_icon_td {
1715 min-width: 16px;
1715 min-width: 16px;
1716 width: 16px;
1716 width: 16px;
1717 }
1717 }
1718
1718
1719 .pull-request-merge {
1719 .pull-request-merge {
1720 border: 1px solid @grey5;
1720 border: 1px solid @grey5;
1721 padding: 10px 0px 20px;
1721 padding: 10px 0px 20px;
1722 margin-top: 10px;
1722 margin-top: 10px;
1723 margin-bottom: 20px;
1723 margin-bottom: 20px;
1724 }
1724 }
1725
1725
1726 .pull-request-merge ul {
1726 .pull-request-merge ul {
1727 padding: 0px 0px;
1727 padding: 0px 0px;
1728 }
1728 }
1729
1729
1730 .pull-request-merge li:before{
1730 .pull-request-merge li:before{
1731 content:none;
1731 content:none;
1732 }
1732 }
1733
1733
1734 .pull-request-merge .pull-request-wrap {
1734 .pull-request-merge .pull-request-wrap {
1735 height: auto;
1735 height: auto;
1736 padding: 0px 0px;
1736 padding: 0px 0px;
1737 text-align: right;
1737 text-align: right;
1738 }
1738 }
1739
1739
1740 .pull-request-merge span {
1740 .pull-request-merge span {
1741 margin-right: 5px;
1741 margin-right: 5px;
1742 }
1742 }
1743
1743
1744 .pull-request-merge-actions {
1744 .pull-request-merge-actions {
1745 height: 30px;
1745 height: 30px;
1746 padding: 0px 0px;
1746 padding: 0px 0px;
1747 }
1747 }
1748
1748
1749 .merge-status {
1749 .merge-status {
1750 margin-right: 5px;
1750 margin-right: 5px;
1751 }
1751 }
1752
1752
1753 .merge-message {
1753 .merge-message {
1754 font-size: 1.2em
1754 font-size: 1.2em
1755 }
1755 }
1756
1756
1757 .merge-message.success i,
1757 .merge-message.success i,
1758 .merge-icon.success i {
1758 .merge-icon.success i {
1759 color:@alert1;
1759 color:@alert1;
1760 }
1760 }
1761
1761
1762 .merge-message.warning i,
1762 .merge-message.warning i,
1763 .merge-icon.warning i {
1763 .merge-icon.warning i {
1764 color: @alert3;
1764 color: @alert3;
1765 }
1765 }
1766
1766
1767 .merge-message.error i,
1767 .merge-message.error i,
1768 .merge-icon.error i {
1768 .merge-icon.error i {
1769 color:@alert2;
1769 color:@alert2;
1770 }
1770 }
1771
1771
1772 .pr-versions {
1772 .pr-versions {
1773 font-size: 1.1em;
1773 font-size: 1.1em;
1774
1774
1775 table {
1775 table {
1776 padding: 0px 5px;
1776 padding: 0px 5px;
1777 }
1777 }
1778
1778
1779 td {
1779 td {
1780 line-height: 15px;
1780 line-height: 15px;
1781 }
1781 }
1782
1782
1783 .flag_status {
1783 .flag_status {
1784 margin: 0;
1784 margin: 0;
1785 }
1785 }
1786
1786
1787 .compare-radio-button {
1787 .compare-radio-button {
1788 position: relative;
1788 position: relative;
1789 top: -3px;
1789 top: -3px;
1790 }
1790 }
1791 }
1791 }
1792
1792
1793
1793
1794 #close_pull_request {
1794 #close_pull_request {
1795 margin-right: 0px;
1795 margin-right: 0px;
1796 }
1796 }
1797
1797
1798 .empty_data {
1798 .empty_data {
1799 color: @grey4;
1799 color: @grey4;
1800 }
1800 }
1801
1801
1802 #changeset_compare_view_content {
1802 #changeset_compare_view_content {
1803 margin-bottom: @space;
1803 margin-bottom: @space;
1804 clear: both;
1804 clear: both;
1805 width: 100%;
1805 width: 100%;
1806 box-sizing: border-box;
1806 box-sizing: border-box;
1807 .border-radius(@border-radius);
1807 .border-radius(@border-radius);
1808
1808
1809 .help-block {
1809 .help-block {
1810 margin: @padding 0;
1810 margin: @padding 0;
1811 color: @text-color;
1811 color: @text-color;
1812 &.pre-formatting {
1812 &.pre-formatting {
1813 white-space: pre;
1813 white-space: pre;
1814 }
1814 }
1815 }
1815 }
1816
1816
1817 .empty_data {
1817 .empty_data {
1818 margin: @padding 0;
1818 margin: @padding 0;
1819 }
1819 }
1820
1820
1821 .alert {
1821 .alert {
1822 margin-bottom: @space;
1822 margin-bottom: @space;
1823 }
1823 }
1824 }
1824 }
1825
1825
1826 .table_disp {
1826 .table_disp {
1827 .status {
1827 .status {
1828 width: auto;
1828 width: auto;
1829
1829
1830 .flag_status {
1830 .flag_status {
1831 float: left;
1831 float: left;
1832 }
1832 }
1833 }
1833 }
1834 }
1834 }
1835
1835
1836
1837 .creation_in_progress {
1838 color: @grey4
1839 }
1840
1836 .status_box_menu {
1841 .status_box_menu {
1837 margin: 0;
1842 margin: 0;
1838 }
1843 }
1839
1844
1840 .notification-table{
1845 .notification-table{
1841 margin-bottom: @space;
1846 margin-bottom: @space;
1842 display: table;
1847 display: table;
1843 width: 100%;
1848 width: 100%;
1844
1849
1845 .container{
1850 .container{
1846 display: table-row;
1851 display: table-row;
1847
1852
1848 .notification-header{
1853 .notification-header{
1849 border-bottom: @border-thickness solid @border-default-color;
1854 border-bottom: @border-thickness solid @border-default-color;
1850 }
1855 }
1851
1856
1852 .notification-subject{
1857 .notification-subject{
1853 display: table-cell;
1858 display: table-cell;
1854 }
1859 }
1855 }
1860 }
1856 }
1861 }
1857
1862
1858 // Notifications
1863 // Notifications
1859 .notification-header{
1864 .notification-header{
1860 display: table;
1865 display: table;
1861 width: 100%;
1866 width: 100%;
1862 padding: floor(@basefontsize/2) 0;
1867 padding: floor(@basefontsize/2) 0;
1863 line-height: 1em;
1868 line-height: 1em;
1864
1869
1865 .desc, .delete-notifications, .read-notifications{
1870 .desc, .delete-notifications, .read-notifications{
1866 display: table-cell;
1871 display: table-cell;
1867 text-align: left;
1872 text-align: left;
1868 }
1873 }
1869
1874
1870 .desc{
1875 .desc{
1871 width: 1163px;
1876 width: 1163px;
1872 }
1877 }
1873
1878
1874 .delete-notifications, .read-notifications{
1879 .delete-notifications, .read-notifications{
1875 width: 35px;
1880 width: 35px;
1876 min-width: 35px; //fixes when only one button is displayed
1881 min-width: 35px; //fixes when only one button is displayed
1877 }
1882 }
1878 }
1883 }
1879
1884
1880 .notification-body {
1885 .notification-body {
1881 .markdown-block,
1886 .markdown-block,
1882 .rst-block {
1887 .rst-block {
1883 padding: @padding 0;
1888 padding: @padding 0;
1884 }
1889 }
1885
1890
1886 .notification-subject {
1891 .notification-subject {
1887 padding: @textmargin 0;
1892 padding: @textmargin 0;
1888 border-bottom: @border-thickness solid @border-default-color;
1893 border-bottom: @border-thickness solid @border-default-color;
1889 }
1894 }
1890 }
1895 }
1891
1896
1892
1897
1893 .notifications_buttons{
1898 .notifications_buttons{
1894 float: right;
1899 float: right;
1895 }
1900 }
1896
1901
1897 #notification-status{
1902 #notification-status{
1898 display: inline;
1903 display: inline;
1899 }
1904 }
1900
1905
1901 // Repositories
1906 // Repositories
1902
1907
1903 #summary.fields{
1908 #summary.fields{
1904 display: table;
1909 display: table;
1905
1910
1906 .field{
1911 .field{
1907 display: table-row;
1912 display: table-row;
1908
1913
1909 .label-summary{
1914 .label-summary{
1910 display: table-cell;
1915 display: table-cell;
1911 min-width: @label-summary-minwidth;
1916 min-width: @label-summary-minwidth;
1912 padding-top: @padding/2;
1917 padding-top: @padding/2;
1913 padding-bottom: @padding/2;
1918 padding-bottom: @padding/2;
1914 padding-right: @padding/2;
1919 padding-right: @padding/2;
1915 }
1920 }
1916
1921
1917 .input{
1922 .input{
1918 display: table-cell;
1923 display: table-cell;
1919 padding: @padding/2;
1924 padding: @padding/2;
1920
1925
1921 input{
1926 input{
1922 min-width: 29em;
1927 min-width: 29em;
1923 padding: @padding/4;
1928 padding: @padding/4;
1924 }
1929 }
1925 }
1930 }
1926 .statistics, .downloads{
1931 .statistics, .downloads{
1927 .disabled{
1932 .disabled{
1928 color: @grey4;
1933 color: @grey4;
1929 }
1934 }
1930 }
1935 }
1931 }
1936 }
1932 }
1937 }
1933
1938
1934 #summary{
1939 #summary{
1935 width: 70%;
1940 width: 70%;
1936 }
1941 }
1937
1942
1938
1943
1939 // Journal
1944 // Journal
1940 .journal.title {
1945 .journal.title {
1941 h5 {
1946 h5 {
1942 float: left;
1947 float: left;
1943 margin: 0;
1948 margin: 0;
1944 width: 70%;
1949 width: 70%;
1945 }
1950 }
1946
1951
1947 ul {
1952 ul {
1948 float: right;
1953 float: right;
1949 display: inline-block;
1954 display: inline-block;
1950 margin: 0;
1955 margin: 0;
1951 width: 30%;
1956 width: 30%;
1952 text-align: right;
1957 text-align: right;
1953
1958
1954 li {
1959 li {
1955 display: inline;
1960 display: inline;
1956 font-size: @journal-fontsize;
1961 font-size: @journal-fontsize;
1957 line-height: 1em;
1962 line-height: 1em;
1958
1963
1959 &:before { content: none; }
1964 &:before { content: none; }
1960 }
1965 }
1961 }
1966 }
1962 }
1967 }
1963
1968
1964 .filterexample {
1969 .filterexample {
1965 position: absolute;
1970 position: absolute;
1966 top: 95px;
1971 top: 95px;
1967 left: @contentpadding;
1972 left: @contentpadding;
1968 color: @rcblue;
1973 color: @rcblue;
1969 font-size: 11px;
1974 font-size: 11px;
1970 font-family: @text-regular;
1975 font-family: @text-regular;
1971 cursor: help;
1976 cursor: help;
1972
1977
1973 &:hover {
1978 &:hover {
1974 color: @rcdarkblue;
1979 color: @rcdarkblue;
1975 }
1980 }
1976
1981
1977 @media (max-width:768px) {
1982 @media (max-width:768px) {
1978 position: relative;
1983 position: relative;
1979 top: auto;
1984 top: auto;
1980 left: auto;
1985 left: auto;
1981 display: block;
1986 display: block;
1982 }
1987 }
1983 }
1988 }
1984
1989
1985
1990
1986 #journal{
1991 #journal{
1987 margin-bottom: @space;
1992 margin-bottom: @space;
1988
1993
1989 .journal_day{
1994 .journal_day{
1990 margin-bottom: @textmargin/2;
1995 margin-bottom: @textmargin/2;
1991 padding-bottom: @textmargin/2;
1996 padding-bottom: @textmargin/2;
1992 font-size: @journal-fontsize;
1997 font-size: @journal-fontsize;
1993 border-bottom: @border-thickness solid @border-default-color;
1998 border-bottom: @border-thickness solid @border-default-color;
1994 }
1999 }
1995
2000
1996 .journal_container{
2001 .journal_container{
1997 margin-bottom: @space;
2002 margin-bottom: @space;
1998
2003
1999 .journal_user{
2004 .journal_user{
2000 display: inline-block;
2005 display: inline-block;
2001 }
2006 }
2002 .journal_action_container{
2007 .journal_action_container{
2003 display: block;
2008 display: block;
2004 margin-top: @textmargin;
2009 margin-top: @textmargin;
2005
2010
2006 div{
2011 div{
2007 display: inline;
2012 display: inline;
2008 }
2013 }
2009
2014
2010 div.journal_action_params{
2015 div.journal_action_params{
2011 display: block;
2016 display: block;
2012 }
2017 }
2013
2018
2014 div.journal_repo:after{
2019 div.journal_repo:after{
2015 content: "\A";
2020 content: "\A";
2016 white-space: pre;
2021 white-space: pre;
2017 }
2022 }
2018
2023
2019 div.date{
2024 div.date{
2020 display: block;
2025 display: block;
2021 margin-bottom: @textmargin;
2026 margin-bottom: @textmargin;
2022 }
2027 }
2023 }
2028 }
2024 }
2029 }
2025 }
2030 }
2026
2031
2027 // Files
2032 // Files
2028 .edit-file-title {
2033 .edit-file-title {
2029 border-bottom: @border-thickness solid @border-default-color;
2034 border-bottom: @border-thickness solid @border-default-color;
2030
2035
2031 .breadcrumbs {
2036 .breadcrumbs {
2032 margin-bottom: 0;
2037 margin-bottom: 0;
2033 }
2038 }
2034 }
2039 }
2035
2040
2036 .edit-file-fieldset {
2041 .edit-file-fieldset {
2037 margin-top: @sidebarpadding;
2042 margin-top: @sidebarpadding;
2038
2043
2039 .fieldset {
2044 .fieldset {
2040 .left-label {
2045 .left-label {
2041 width: 13%;
2046 width: 13%;
2042 }
2047 }
2043 .right-content {
2048 .right-content {
2044 width: 87%;
2049 width: 87%;
2045 max-width: 100%;
2050 max-width: 100%;
2046 }
2051 }
2047 .filename-label {
2052 .filename-label {
2048 margin-top: 13px;
2053 margin-top: 13px;
2049 }
2054 }
2050 .commit-message-label {
2055 .commit-message-label {
2051 margin-top: 4px;
2056 margin-top: 4px;
2052 }
2057 }
2053 .file-upload-input {
2058 .file-upload-input {
2054 input {
2059 input {
2055 display: none;
2060 display: none;
2056 }
2061 }
2057 margin-top: 10px;
2062 margin-top: 10px;
2058 }
2063 }
2059 .file-upload-label {
2064 .file-upload-label {
2060 margin-top: 10px;
2065 margin-top: 10px;
2061 }
2066 }
2062 p {
2067 p {
2063 margin-top: 5px;
2068 margin-top: 5px;
2064 }
2069 }
2065
2070
2066 }
2071 }
2067 .custom-path-link {
2072 .custom-path-link {
2068 margin-left: 5px;
2073 margin-left: 5px;
2069 }
2074 }
2070 #commit {
2075 #commit {
2071 resize: vertical;
2076 resize: vertical;
2072 }
2077 }
2073 }
2078 }
2074
2079
2075 .delete-file-preview {
2080 .delete-file-preview {
2076 max-height: 250px;
2081 max-height: 250px;
2077 }
2082 }
2078
2083
2079 .new-file,
2084 .new-file,
2080 #filter_activate,
2085 #filter_activate,
2081 #filter_deactivate {
2086 #filter_deactivate {
2082 float: left;
2087 float: left;
2083 margin: 0 0 0 15px;
2088 margin: 0 0 0 15px;
2084 }
2089 }
2085
2090
2086 h3.files_location{
2091 h3.files_location{
2087 line-height: 2.4em;
2092 line-height: 2.4em;
2088 }
2093 }
2089
2094
2090 .browser-nav {
2095 .browser-nav {
2091 display: table;
2096 display: table;
2092 margin-bottom: @space;
2097 margin-bottom: @space;
2093
2098
2094
2099
2095 .info_box {
2100 .info_box {
2096 display: inline-table;
2101 display: inline-table;
2097 height: 2.5em;
2102 height: 2.5em;
2098
2103
2099 .browser-cur-rev, .info_box_elem {
2104 .browser-cur-rev, .info_box_elem {
2100 display: table-cell;
2105 display: table-cell;
2101 vertical-align: middle;
2106 vertical-align: middle;
2102 }
2107 }
2103
2108
2104 .info_box_elem {
2109 .info_box_elem {
2105 border-top: @border-thickness solid @rcblue;
2110 border-top: @border-thickness solid @rcblue;
2106 border-bottom: @border-thickness solid @rcblue;
2111 border-bottom: @border-thickness solid @rcblue;
2107
2112
2108 #at_rev, a {
2113 #at_rev, a {
2109 padding: 0.6em 0.9em;
2114 padding: 0.6em 0.9em;
2110 margin: 0;
2115 margin: 0;
2111 .box-shadow(none);
2116 .box-shadow(none);
2112 border: 0;
2117 border: 0;
2113 height: 12px;
2118 height: 12px;
2114 }
2119 }
2115
2120
2116 input#at_rev {
2121 input#at_rev {
2117 max-width: 50px;
2122 max-width: 50px;
2118 text-align: right;
2123 text-align: right;
2119 }
2124 }
2120
2125
2121 &.previous {
2126 &.previous {
2122 border: @border-thickness solid @rcblue;
2127 border: @border-thickness solid @rcblue;
2123 .disabled {
2128 .disabled {
2124 color: @grey4;
2129 color: @grey4;
2125 cursor: not-allowed;
2130 cursor: not-allowed;
2126 }
2131 }
2127 }
2132 }
2128
2133
2129 &.next {
2134 &.next {
2130 border: @border-thickness solid @rcblue;
2135 border: @border-thickness solid @rcblue;
2131 .disabled {
2136 .disabled {
2132 color: @grey4;
2137 color: @grey4;
2133 cursor: not-allowed;
2138 cursor: not-allowed;
2134 }
2139 }
2135 }
2140 }
2136 }
2141 }
2137
2142
2138 .browser-cur-rev {
2143 .browser-cur-rev {
2139
2144
2140 span{
2145 span{
2141 margin: 0;
2146 margin: 0;
2142 color: @rcblue;
2147 color: @rcblue;
2143 height: 12px;
2148 height: 12px;
2144 display: inline-block;
2149 display: inline-block;
2145 padding: 0.7em 1em ;
2150 padding: 0.7em 1em ;
2146 border: @border-thickness solid @rcblue;
2151 border: @border-thickness solid @rcblue;
2147 margin-right: @padding;
2152 margin-right: @padding;
2148 }
2153 }
2149 }
2154 }
2150 }
2155 }
2151
2156
2152 .search_activate {
2157 .search_activate {
2153 display: table-cell;
2158 display: table-cell;
2154 vertical-align: middle;
2159 vertical-align: middle;
2155
2160
2156 input, label{
2161 input, label{
2157 margin: 0;
2162 margin: 0;
2158 padding: 0;
2163 padding: 0;
2159 }
2164 }
2160
2165
2161 input{
2166 input{
2162 margin-left: @textmargin;
2167 margin-left: @textmargin;
2163 }
2168 }
2164
2169
2165 }
2170 }
2166 }
2171 }
2167
2172
2168 .browser-cur-rev{
2173 .browser-cur-rev{
2169 margin-bottom: @textmargin;
2174 margin-bottom: @textmargin;
2170 }
2175 }
2171
2176
2172 #node_filter_box_loading{
2177 #node_filter_box_loading{
2173 .info_text;
2178 .info_text;
2174 }
2179 }
2175
2180
2176 .browser-search {
2181 .browser-search {
2177 margin: -25px 0px 5px 0px;
2182 margin: -25px 0px 5px 0px;
2178 }
2183 }
2179
2184
2180 .node-filter {
2185 .node-filter {
2181 font-size: @repo-title-fontsize;
2186 font-size: @repo-title-fontsize;
2182 padding: 4px 0px 0px 0px;
2187 padding: 4px 0px 0px 0px;
2183
2188
2184 .node-filter-path {
2189 .node-filter-path {
2185 float: left;
2190 float: left;
2186 color: @grey4;
2191 color: @grey4;
2187 }
2192 }
2188 .node-filter-input {
2193 .node-filter-input {
2189 float: left;
2194 float: left;
2190 margin: -2px 0px 0px 2px;
2195 margin: -2px 0px 0px 2px;
2191 input {
2196 input {
2192 padding: 2px;
2197 padding: 2px;
2193 border: none;
2198 border: none;
2194 font-size: @repo-title-fontsize;
2199 font-size: @repo-title-fontsize;
2195 }
2200 }
2196 }
2201 }
2197 }
2202 }
2198
2203
2199
2204
2200 .browser-result{
2205 .browser-result{
2201 td a{
2206 td a{
2202 margin-left: 0.5em;
2207 margin-left: 0.5em;
2203 display: inline-block;
2208 display: inline-block;
2204
2209
2205 em{
2210 em{
2206 font-family: @text-bold;
2211 font-family: @text-bold;
2207 }
2212 }
2208 }
2213 }
2209 }
2214 }
2210
2215
2211 .browser-highlight{
2216 .browser-highlight{
2212 background-color: @grey5-alpha;
2217 background-color: @grey5-alpha;
2213 }
2218 }
2214
2219
2215
2220
2216 // Search
2221 // Search
2217
2222
2218 .search-form{
2223 .search-form{
2219 #q {
2224 #q {
2220 width: @search-form-width;
2225 width: @search-form-width;
2221 }
2226 }
2222 .fields{
2227 .fields{
2223 margin: 0 0 @space;
2228 margin: 0 0 @space;
2224 }
2229 }
2225
2230
2226 label{
2231 label{
2227 display: inline-block;
2232 display: inline-block;
2228 margin-right: @textmargin;
2233 margin-right: @textmargin;
2229 padding-top: 0.25em;
2234 padding-top: 0.25em;
2230 }
2235 }
2231
2236
2232
2237
2233 .results{
2238 .results{
2234 clear: both;
2239 clear: both;
2235 margin: 0 0 @padding;
2240 margin: 0 0 @padding;
2236 }
2241 }
2237 }
2242 }
2238
2243
2239 div.search-feedback-items {
2244 div.search-feedback-items {
2240 display: inline-block;
2245 display: inline-block;
2241 padding:0px 0px 0px 96px;
2246 padding:0px 0px 0px 96px;
2242 }
2247 }
2243
2248
2244 div.search-code-body {
2249 div.search-code-body {
2245 background-color: #ffffff; padding: 5px 0 5px 10px;
2250 background-color: #ffffff; padding: 5px 0 5px 10px;
2246 pre {
2251 pre {
2247 .match { background-color: #faffa6;}
2252 .match { background-color: #faffa6;}
2248 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2253 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2249 }
2254 }
2250 }
2255 }
2251
2256
2252 .expand_commit.search {
2257 .expand_commit.search {
2253 .show_more.open {
2258 .show_more.open {
2254 height: auto;
2259 height: auto;
2255 max-height: none;
2260 max-height: none;
2256 }
2261 }
2257 }
2262 }
2258
2263
2259 .search-results {
2264 .search-results {
2260
2265
2261 h2 {
2266 h2 {
2262 margin-bottom: 0;
2267 margin-bottom: 0;
2263 }
2268 }
2264 .codeblock {
2269 .codeblock {
2265 border: none;
2270 border: none;
2266 background: transparent;
2271 background: transparent;
2267 }
2272 }
2268
2273
2269 .codeblock-header {
2274 .codeblock-header {
2270 border: none;
2275 border: none;
2271 background: transparent;
2276 background: transparent;
2272 }
2277 }
2273
2278
2274 .code-body {
2279 .code-body {
2275 border: @border-thickness solid @border-default-color;
2280 border: @border-thickness solid @border-default-color;
2276 .border-radius(@border-radius);
2281 .border-radius(@border-radius);
2277 }
2282 }
2278
2283
2279 .td-commit {
2284 .td-commit {
2280 &:extend(pre);
2285 &:extend(pre);
2281 border-bottom: @border-thickness solid @border-default-color;
2286 border-bottom: @border-thickness solid @border-default-color;
2282 }
2287 }
2283
2288
2284 .message {
2289 .message {
2285 height: auto;
2290 height: auto;
2286 max-width: 350px;
2291 max-width: 350px;
2287 white-space: normal;
2292 white-space: normal;
2288 text-overflow: initial;
2293 text-overflow: initial;
2289 overflow: visible;
2294 overflow: visible;
2290
2295
2291 .match { background-color: #faffa6;}
2296 .match { background-color: #faffa6;}
2292 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2297 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2293 }
2298 }
2294
2299
2295 }
2300 }
2296
2301
2297 table.rctable td.td-search-results div {
2302 table.rctable td.td-search-results div {
2298 max-width: 100%;
2303 max-width: 100%;
2299 }
2304 }
2300
2305
2301 #tip-box, .tip-box{
2306 #tip-box, .tip-box{
2302 padding: @menupadding/2;
2307 padding: @menupadding/2;
2303 display: block;
2308 display: block;
2304 border: @border-thickness solid @border-highlight-color;
2309 border: @border-thickness solid @border-highlight-color;
2305 .border-radius(@border-radius);
2310 .border-radius(@border-radius);
2306 background-color: white;
2311 background-color: white;
2307 z-index: 99;
2312 z-index: 99;
2308 white-space: pre-wrap;
2313 white-space: pre-wrap;
2309 }
2314 }
2310
2315
2311 #linktt {
2316 #linktt {
2312 width: 79px;
2317 width: 79px;
2313 }
2318 }
2314
2319
2315 #help_kb .modal-content{
2320 #help_kb .modal-content{
2316 max-width: 750px;
2321 max-width: 750px;
2317 margin: 10% auto;
2322 margin: 10% auto;
2318
2323
2319 table{
2324 table{
2320 td,th{
2325 td,th{
2321 border-bottom: none;
2326 border-bottom: none;
2322 line-height: 2.5em;
2327 line-height: 2.5em;
2323 }
2328 }
2324 th{
2329 th{
2325 padding-bottom: @textmargin/2;
2330 padding-bottom: @textmargin/2;
2326 }
2331 }
2327 td.keys{
2332 td.keys{
2328 text-align: center;
2333 text-align: center;
2329 }
2334 }
2330 }
2335 }
2331
2336
2332 .block-left{
2337 .block-left{
2333 width: 45%;
2338 width: 45%;
2334 margin-right: 5%;
2339 margin-right: 5%;
2335 }
2340 }
2336 .modal-footer{
2341 .modal-footer{
2337 clear: both;
2342 clear: both;
2338 }
2343 }
2339 .key.tag{
2344 .key.tag{
2340 padding: 0.5em;
2345 padding: 0.5em;
2341 background-color: @rcblue;
2346 background-color: @rcblue;
2342 color: white;
2347 color: white;
2343 border-color: @rcblue;
2348 border-color: @rcblue;
2344 .box-shadow(none);
2349 .box-shadow(none);
2345 }
2350 }
2346 }
2351 }
2347
2352
2348
2353
2349
2354
2350 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2355 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2351
2356
2352 @import 'statistics-graph';
2357 @import 'statistics-graph';
2353 @import 'tables';
2358 @import 'tables';
2354 @import 'forms';
2359 @import 'forms';
2355 @import 'diff';
2360 @import 'diff';
2356 @import 'summary';
2361 @import 'summary';
2357 @import 'navigation';
2362 @import 'navigation';
2358
2363
2359 //--- SHOW/HIDE SECTIONS --//
2364 //--- SHOW/HIDE SECTIONS --//
2360
2365
2361 .btn-collapse {
2366 .btn-collapse {
2362 float: right;
2367 float: right;
2363 text-align: right;
2368 text-align: right;
2364 font-family: @text-light;
2369 font-family: @text-light;
2365 font-size: @basefontsize;
2370 font-size: @basefontsize;
2366 cursor: pointer;
2371 cursor: pointer;
2367 border: none;
2372 border: none;
2368 color: @rcblue;
2373 color: @rcblue;
2369 }
2374 }
2370
2375
2371 table.rctable,
2376 table.rctable,
2372 table.dataTable {
2377 table.dataTable {
2373 .btn-collapse {
2378 .btn-collapse {
2374 float: right;
2379 float: right;
2375 text-align: right;
2380 text-align: right;
2376 }
2381 }
2377 }
2382 }
2378
2383
2379
2384
2380 // TODO: johbo: Fix for IE10, this avoids that we see a border
2385 // TODO: johbo: Fix for IE10, this avoids that we see a border
2381 // and padding around checkboxes and radio boxes. Move to the right place,
2386 // and padding around checkboxes and radio boxes. Move to the right place,
2382 // or better: Remove this once we did the form refactoring.
2387 // or better: Remove this once we did the form refactoring.
2383 input[type=checkbox],
2388 input[type=checkbox],
2384 input[type=radio] {
2389 input[type=radio] {
2385 padding: 0;
2390 padding: 0;
2386 border: none;
2391 border: none;
2387 }
2392 }
2388
2393
2389 .toggle-ajax-spinner{
2394 .toggle-ajax-spinner{
2390 height: 16px;
2395 height: 16px;
2391 width: 16px;
2396 width: 16px;
2392 }
2397 }
@@ -1,220 +1,222 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', []);
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 pyroutes.register('favicon', '/favicon.ico', []);
17 pyroutes.register('favicon', '/favicon.ico', []);
18 pyroutes.register('robots', '/robots.txt', []);
18 pyroutes.register('robots', '/robots.txt', []);
19 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
19 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
20 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
20 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
21 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
21 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
22 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
22 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
23 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
23 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']);
24 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']);
25 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
26 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']);
27 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
27 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
28 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
28 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']);
29 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']);
30 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
31 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']);
32 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
32 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
33 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
33 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']);
34 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', []);
35 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
36 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
36 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
37 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
37 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
38 pyroutes.register('admin_home', '/_admin', []);
38 pyroutes.register('admin_home', '/_admin', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 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']);
40 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']);
41 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']);
42 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', []);
43 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', []);
44 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
45 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
45 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
46 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
46 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
47 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
47 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
48 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
48 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
49 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
49 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
50 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
50 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
51 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
51 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
52 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
52 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
53 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
53 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
54 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
54 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
55 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
55 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
56 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
56 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
57 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
57 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
58 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
58 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
59 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
59 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
60 pyroutes.register('users', '/_admin/users', []);
60 pyroutes.register('users', '/_admin/users', []);
61 pyroutes.register('users_data', '/_admin/users_data', []);
61 pyroutes.register('users_data', '/_admin/users_data', []);
62 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
62 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']);
63 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']);
64 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
65 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
65 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
66 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
66 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
67 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
67 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
68 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
68 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
69 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
69 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
70 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
70 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
71 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
71 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
72 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
72 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
73 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
73 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
74 pyroutes.register('user_groups', '/_admin/user_groups', []);
74 pyroutes.register('user_groups', '/_admin/user_groups', []);
75 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
75 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
76 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
76 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
77 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
77 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
78 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
78 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
79 pyroutes.register('channelstream_proxy', '/_channelstream', []);
79 pyroutes.register('channelstream_proxy', '/_channelstream', []);
80 pyroutes.register('login', '/_admin/login', []);
80 pyroutes.register('login', '/_admin/login', []);
81 pyroutes.register('logout', '/_admin/logout', []);
81 pyroutes.register('logout', '/_admin/logout', []);
82 pyroutes.register('register', '/_admin/register', []);
82 pyroutes.register('register', '/_admin/register', []);
83 pyroutes.register('reset_password', '/_admin/password_reset', []);
83 pyroutes.register('reset_password', '/_admin/password_reset', []);
84 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
84 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
85 pyroutes.register('home', '/', []);
85 pyroutes.register('home', '/', []);
86 pyroutes.register('user_autocomplete_data', '/_users', []);
86 pyroutes.register('user_autocomplete_data', '/_users', []);
87 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
87 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
88 pyroutes.register('repo_list_data', '/_repos', []);
88 pyroutes.register('repo_list_data', '/_repos', []);
89 pyroutes.register('goto_switcher_data', '/_goto_data', []);
89 pyroutes.register('goto_switcher_data', '/_goto_data', []);
90 pyroutes.register('journal', '/_admin/journal', []);
90 pyroutes.register('journal', '/_admin/journal', []);
91 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
91 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
92 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
92 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
93 pyroutes.register('journal_public', '/_admin/public_journal', []);
93 pyroutes.register('journal_public', '/_admin/public_journal', []);
94 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
94 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
95 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
95 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
96 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
96 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
97 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
97 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
98 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
98 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
99 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
100 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
99 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
101 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
100 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
102 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
101 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
103 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
102 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
104 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
103 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
105 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
104 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
106 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
105 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
107 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
106 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
108 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
107 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
109 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
108 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
110 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
109 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
111 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
110 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
112 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
111 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
113 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
112 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
114 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
113 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
115 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
114 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
116 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
115 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
117 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
116 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
118 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
117 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
119 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
118 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
122 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
123 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
122 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
124 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
123 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
125 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
124 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
125 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
128 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
128 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
136 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
137 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
136 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
138 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
137 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
139 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
138 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
140 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
139 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
141 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
142 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
141 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
143 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
142 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']);
144 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']);
143 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
145 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
144 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
146 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
145 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
147 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
146 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
148 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
147 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
149 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
148 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
150 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
149 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
151 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
150 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
152 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
151 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
153 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
152 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
154 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
153 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
155 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
154 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
156 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
155 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
157 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
156 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
158 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
157 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']);
159 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']);
158 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
160 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
159 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
161 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
160 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
162 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
161 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
163 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
162 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
164 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
163 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
165 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
164 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
166 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
165 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
167 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
166 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
168 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
167 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
169 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
168 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
170 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
169 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
171 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
170 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
172 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
171 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
173 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
172 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
174 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
173 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
175 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
174 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
176 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
175 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
177 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
176 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
178 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
177 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
179 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
178 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
180 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
179 pyroutes.register('search', '/_admin/search', []);
181 pyroutes.register('search', '/_admin/search', []);
180 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
182 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
181 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
183 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
182 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
184 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
183 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
185 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
184 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
186 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
185 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
187 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
186 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
188 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
187 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
189 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
188 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
190 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
189 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
191 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
190 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
192 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
191 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
193 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
192 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
194 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
193 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
195 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
194 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
196 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
195 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
197 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
196 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
198 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
197 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
199 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
198 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
200 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
199 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
201 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
200 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
202 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
201 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
203 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
202 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
204 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
203 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
205 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
204 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
206 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
205 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
207 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
206 pyroutes.register('gists_show', '/_admin/gists', []);
208 pyroutes.register('gists_show', '/_admin/gists', []);
207 pyroutes.register('gists_new', '/_admin/gists/new', []);
209 pyroutes.register('gists_new', '/_admin/gists/new', []);
208 pyroutes.register('gists_create', '/_admin/gists/create', []);
210 pyroutes.register('gists_create', '/_admin/gists/create', []);
209 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
211 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
210 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
212 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
211 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
213 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
212 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
214 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
213 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
215 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
214 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
216 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
215 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
217 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
216 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
218 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
217 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
219 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
218 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
220 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
219 pyroutes.register('apiv2', '/_admin/api', []);
221 pyroutes.register('apiv2', '/_admin/api', []);
220 }
222 }
@@ -1,70 +1,77 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 ${_('%s Creating repository') % c.repo_name}
5 ${_('{} Creating repository').format(c.repo_name)}
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 ${_('Creating repository')} ${c.repo}
12 ${_('Creating repository')} ${c.repo_name}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_nav()">
15 <%def name="menu_bar_nav()">
16 ${self.menu_items(active='repositories')}
16 ${self.menu_items(active='repositories')}
17 </%def>
17 </%def>
18 <%def name="main()">
18 <%def name="main()">
19 <div class="box">
19 <div class="box">
20 <!-- box / title -->
20 <!-- box / title -->
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 </div>
23 </div>
24
24
25 <div id="progress-message">
25 <div id="progress-message">
26 ${_('Repository "%(repo_name)s" is being created, you will be redirected when this process is finished.' % {'repo_name':c.repo_name})}
26 ${_('Repository "%(repo_name)s" is being created, you will be redirected when this process is finished.' % {'repo_name':c.repo_name})}
27 </div>
27 </div>
28
28
29 <div id="progress">
29 <div id="progress">
30 <div class="progress progress-striped active">
30 <div class="progress progress-striped active">
31 <div class="progress-bar progress-bar" role="progressbar"
31 <div class="progress-bar progress-bar" role="progressbar"
32 aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
32 aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
33 </div>
33 </div>
34 </div>
34 </div>
35 </div>
35 </div>
36 </div>
36 </div>
37
37
38 <script>
38 <script>
39 (function worker() {
39 (function worker() {
40 var skipCheck = false;
40 var skipCheck = false;
41 var url = "${h.url('repo_check_home', repo_name=c.repo_name, repo=c.repo, task_id=c.task_id)}";
41 var url = "${h.route_path('repo_creating_check', repo_name=c.repo_name, _query=dict(task_id=c.task_id))}";
42 $.ajax({
42 $.ajax({
43 url: url,
43 url: url,
44 complete: function(resp) {
44 complete: function(resp) {
45 if (resp.status == 200) {
45 if (resp.status == 200) {
46 var jsonResponse = resp.responseJSON;
46 var jsonResponse = resp.responseJSON;
47
47
48 if (jsonResponse === undefined) {
48 if (jsonResponse === undefined) {
49 setTimeout(function () {
49 setTimeout(function () {
50 // we might have a backend problem, try dashboard again
50 // we might have a backend problem, try dashboard again
51 window.location = "${h.route_path('repo_summary', repo_name = c.repo)}";
51 window.location = "${h.route_path('repo_summary', repo_name = c.repo_name)}";
52 }, 3000);
52 }, 3000);
53 } else {
53 } else {
54 if (skipCheck || jsonResponse.result === true) {
54 if (skipCheck || jsonResponse.result === true) {
55 // success, means go to dashboard
55 // success, means go to dashboard
56 window.location = "${h.route_path('repo_summary', repo_name = c.repo)}";
56 window.location = "${h.route_path('repo_summary', repo_name = c.repo_name)}";
57 } else {
57 } else {
58 // Schedule the next request when the current one's complete
58 // Schedule the next request when the current one's complete
59 setTimeout(worker, 1000);
59 setTimeout(worker, 1000);
60 }
60 }
61 }
61 }
62 }
62 }
63 else {
63 else {
64 window.location = "${h.route_path('home')}";
64 var payload = {
65 message: {
66 message: _gettext('Fetching repository state failed. Error code: {0} {1}. Try refreshing this page.').format(resp.status, resp.statusText),
67 level: 'error',
68 force: true
69 }
70 };
71 $.Topic('/notifications').publish(payload);
65 }
72 }
66 }
73 }
67 });
74 });
68 })();
75 })();
69 </script>
76 </script>
70 </%def> No newline at end of file
77 </%def>
@@ -1,317 +1,319 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5
5
6 ## REPOSITORY RENDERERS
6 ## REPOSITORY RENDERERS
7 <%def name="quick_menu(repo_name)">
7 <%def name="quick_menu(repo_name)">
8 <i class="icon-more"></i>
8 <i class="icon-more"></i>
9 <div class="menu_items_container hidden">
9 <div class="menu_items_container hidden">
10 <ul class="menu_items">
10 <ul class="menu_items">
11 <li>
11 <li>
12 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
12 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
13 <span>${_('Summary')}</span>
13 <span>${_('Summary')}</span>
14 </a>
14 </a>
15 </li>
15 </li>
16 <li>
16 <li>
17 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
17 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
18 <span>${_('Changelog')}</span>
18 <span>${_('Changelog')}</span>
19 </a>
19 </a>
20 </li>
20 </li>
21 <li>
21 <li>
22 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
22 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
23 <span>${_('Files')}</span>
23 <span>${_('Files')}</span>
24 </a>
24 </a>
25 </li>
25 </li>
26 <li>
26 <li>
27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
28 <span>${_('Fork')}</span>
28 <span>${_('Fork')}</span>
29 </a>
29 </a>
30 </li>
30 </li>
31 </ul>
31 </ul>
32 </div>
32 </div>
33 </%def>
33 </%def>
34
34
35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
36 <%
36 <%
37 def get_name(name,short_name=short_name):
37 def get_name(name,short_name=short_name):
38 if short_name:
38 if short_name:
39 return name.split('/')[-1]
39 return name.split('/')[-1]
40 else:
40 else:
41 return name
41 return name
42 %>
42 %>
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 ##NAME
44 ##NAME
45 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
45 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
46
46
47 ##TYPE OF REPO
47 ##TYPE OF REPO
48 %if h.is_hg(rtype):
48 %if h.is_hg(rtype):
49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
50 %elif h.is_git(rtype):
50 %elif h.is_git(rtype):
51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
52 %elif h.is_svn(rtype):
52 %elif h.is_svn(rtype):
53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
54 %endif
54 %endif
55
55
56 ##PRIVATE/PUBLIC
56 ##PRIVATE/PUBLIC
57 %if private and c.visual.show_private_icon:
57 %if private and c.visual.show_private_icon:
58 <i class="icon-lock" title="${_('Private repository')}"></i>
58 <i class="icon-lock" title="${_('Private repository')}"></i>
59 %elif not private and c.visual.show_public_icon:
59 %elif not private and c.visual.show_public_icon:
60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
61 %else:
61 %else:
62 <span></span>
62 <span></span>
63 %endif
63 %endif
64 ${get_name(name)}
64 ${get_name(name)}
65 </a>
65 </a>
66 %if fork_of:
66 %if fork_of:
67 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
67 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
68 %endif
68 %endif
69 %if rstate == 'repo_state_pending':
69 %if rstate == 'repo_state_pending':
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
70 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
71 (${_('creating...')})
72 </span>
71 %endif
73 %endif
72 </div>
74 </div>
73 </%def>
75 </%def>
74
76
75 <%def name="repo_desc(description)">
77 <%def name="repo_desc(description)">
76 <div class="truncate-wrap">${description}</div>
78 <div class="truncate-wrap">${description}</div>
77 </%def>
79 </%def>
78
80
79 <%def name="last_change(last_change)">
81 <%def name="last_change(last_change)">
80 ${h.age_component(last_change)}
82 ${h.age_component(last_change)}
81 </%def>
83 </%def>
82
84
83 <%def name="revision(name,rev,tip,author,last_msg)">
85 <%def name="revision(name,rev,tip,author,last_msg)">
84 <div>
86 <div>
85 %if rev >= 0:
87 %if rev >= 0:
86 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
88 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
87 %else:
89 %else:
88 ${_('No commits yet')}
90 ${_('No commits yet')}
89 %endif
91 %endif
90 </div>
92 </div>
91 </%def>
93 </%def>
92
94
93 <%def name="rss(name)">
95 <%def name="rss(name)">
94 %if c.rhodecode_user.username != h.DEFAULT_USER:
96 %if c.rhodecode_user.username != h.DEFAULT_USER:
95 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
97 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
96 %else:
98 %else:
97 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
99 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
98 %endif
100 %endif
99 </%def>
101 </%def>
100
102
101 <%def name="atom(name)">
103 <%def name="atom(name)">
102 %if c.rhodecode_user.username != h.DEFAULT_USER:
104 %if c.rhodecode_user.username != h.DEFAULT_USER:
103 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
105 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
104 %else:
106 %else:
105 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
107 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
106 %endif
108 %endif
107 </%def>
109 </%def>
108
110
109 <%def name="user_gravatar(email, size=16)">
111 <%def name="user_gravatar(email, size=16)">
110 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
112 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
111 ${base.gravatar(email, 16)}
113 ${base.gravatar(email, 16)}
112 </div>
114 </div>
113 </%def>
115 </%def>
114
116
115 <%def name="repo_actions(repo_name, super_user=True)">
117 <%def name="repo_actions(repo_name, super_user=True)">
116 <div>
118 <div>
117 <div class="grid_edit">
119 <div class="grid_edit">
118 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
120 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
119 <i class="icon-pencil"></i>Edit</a>
121 <i class="icon-pencil"></i>Edit</a>
120 </div>
122 </div>
121 <div class="grid_delete">
123 <div class="grid_delete">
122 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)}
124 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)}
123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
125 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
126 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
125 ${h.end_form()}
127 ${h.end_form()}
126 </div>
128 </div>
127 </div>
129 </div>
128 </%def>
130 </%def>
129
131
130 <%def name="repo_state(repo_state)">
132 <%def name="repo_state(repo_state)">
131 <div>
133 <div>
132 %if repo_state == 'repo_state_pending':
134 %if repo_state == 'repo_state_pending':
133 <div class="tag tag4">${_('Creating')}</div>
135 <div class="tag tag4">${_('Creating')}</div>
134 %elif repo_state == 'repo_state_created':
136 %elif repo_state == 'repo_state_created':
135 <div class="tag tag1">${_('Created')}</div>
137 <div class="tag tag1">${_('Created')}</div>
136 %else:
138 %else:
137 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
139 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
138 %endif
140 %endif
139 </div>
141 </div>
140 </%def>
142 </%def>
141
143
142
144
143 ## REPO GROUP RENDERERS
145 ## REPO GROUP RENDERERS
144 <%def name="quick_repo_group_menu(repo_group_name)">
146 <%def name="quick_repo_group_menu(repo_group_name)">
145 <i class="icon-more"></i>
147 <i class="icon-more"></i>
146 <div class="menu_items_container hidden">
148 <div class="menu_items_container hidden">
147 <ul class="menu_items">
149 <ul class="menu_items">
148 <li>
150 <li>
149 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
151 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
150 <span class="icon">
152 <span class="icon">
151 <i class="icon-file-text"></i>
153 <i class="icon-file-text"></i>
152 </span>
154 </span>
153 <span>${_('Summary')}</span>
155 <span>${_('Summary')}</span>
154 </a>
156 </a>
155 </li>
157 </li>
156
158
157 </ul>
159 </ul>
158 </div>
160 </div>
159 </%def>
161 </%def>
160
162
161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
163 <%def name="repo_group_name(repo_group_name, children_groups=None)">
162 <div>
164 <div>
163 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
165 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
166 <i class="icon-folder-close" title="${_('Repository group')}"></i>
165 %if children_groups:
167 %if children_groups:
166 ${h.literal(' &raquo; '.join(children_groups))}
168 ${h.literal(' &raquo; '.join(children_groups))}
167 %else:
169 %else:
168 ${repo_group_name}
170 ${repo_group_name}
169 %endif
171 %endif
170 </a>
172 </a>
171 </div>
173 </div>
172 </%def>
174 </%def>
173
175
174 <%def name="repo_group_desc(description)">
176 <%def name="repo_group_desc(description)">
175 <div class="truncate-wrap">${description}</div>
177 <div class="truncate-wrap">${description}</div>
176 </%def>
178 </%def>
177
179
178 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
180 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
179 <div class="grid_edit">
181 <div class="grid_edit">
180 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
182 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
181 </div>
183 </div>
182 <div class="grid_delete">
184 <div class="grid_delete">
183 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
185 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
184 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
186 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
185 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
187 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
186 ${h.end_form()}
188 ${h.end_form()}
187 </div>
189 </div>
188 </%def>
190 </%def>
189
191
190
192
191 <%def name="user_actions(user_id, username)">
193 <%def name="user_actions(user_id, username)">
192 <div class="grid_edit">
194 <div class="grid_edit">
193 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
195 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
194 <i class="icon-pencil"></i>Edit</a>
196 <i class="icon-pencil"></i>Edit</a>
195 </div>
197 </div>
196 <div class="grid_delete">
198 <div class="grid_delete">
197 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
199 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
198 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
200 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
199 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
201 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
200 ${h.end_form()}
202 ${h.end_form()}
201 </div>
203 </div>
202 </%def>
204 </%def>
203
205
204 <%def name="user_group_actions(user_group_id, user_group_name)">
206 <%def name="user_group_actions(user_group_id, user_group_name)">
205 <div class="grid_edit">
207 <div class="grid_edit">
206 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
208 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
207 </div>
209 </div>
208 <div class="grid_delete">
210 <div class="grid_delete">
209 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
211 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
210 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
212 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
211 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
213 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
212 ${h.end_form()}
214 ${h.end_form()}
213 </div>
215 </div>
214 </%def>
216 </%def>
215
217
216
218
217 <%def name="user_name(user_id, username)">
219 <%def name="user_name(user_id, username)">
218 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
220 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
219 </%def>
221 </%def>
220
222
221 <%def name="user_profile(username)">
223 <%def name="user_profile(username)">
222 ${base.gravatar_with_user(username, 16)}
224 ${base.gravatar_with_user(username, 16)}
223 </%def>
225 </%def>
224
226
225 <%def name="user_group_name(user_group_id, user_group_name)">
227 <%def name="user_group_name(user_group_id, user_group_name)">
226 <div>
228 <div>
227 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
229 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
228 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
230 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
229 </div>
231 </div>
230 </%def>
232 </%def>
231
233
232
234
233 ## GISTS
235 ## GISTS
234
236
235 <%def name="gist_gravatar(full_contact)">
237 <%def name="gist_gravatar(full_contact)">
236 <div class="gist_gravatar">
238 <div class="gist_gravatar">
237 ${base.gravatar(full_contact, 30)}
239 ${base.gravatar(full_contact, 30)}
238 </div>
240 </div>
239 </%def>
241 </%def>
240
242
241 <%def name="gist_access_id(gist_access_id, full_contact)">
243 <%def name="gist_access_id(gist_access_id, full_contact)">
242 <div>
244 <div>
243 <b>
245 <b>
244 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
246 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
245 </b>
247 </b>
246 </div>
248 </div>
247 </%def>
249 </%def>
248
250
249 <%def name="gist_author(full_contact, created_on, expires)">
251 <%def name="gist_author(full_contact, created_on, expires)">
250 ${base.gravatar_with_user(full_contact, 16)}
252 ${base.gravatar_with_user(full_contact, 16)}
251 </%def>
253 </%def>
252
254
253
255
254 <%def name="gist_created(created_on)">
256 <%def name="gist_created(created_on)">
255 <div class="created">
257 <div class="created">
256 ${h.age_component(created_on, time_is_local=True)}
258 ${h.age_component(created_on, time_is_local=True)}
257 </div>
259 </div>
258 </%def>
260 </%def>
259
261
260 <%def name="gist_expires(expires)">
262 <%def name="gist_expires(expires)">
261 <div class="created">
263 <div class="created">
262 %if expires == -1:
264 %if expires == -1:
263 ${_('never')}
265 ${_('never')}
264 %else:
266 %else:
265 ${h.age_component(h.time_to_utcdatetime(expires))}
267 ${h.age_component(h.time_to_utcdatetime(expires))}
266 %endif
268 %endif
267 </div>
269 </div>
268 </%def>
270 </%def>
269
271
270 <%def name="gist_type(gist_type)">
272 <%def name="gist_type(gist_type)">
271 %if gist_type != 'public':
273 %if gist_type != 'public':
272 <div class="tag">${_('Private')}</div>
274 <div class="tag">${_('Private')}</div>
273 %endif
275 %endif
274 </%def>
276 </%def>
275
277
276 <%def name="gist_description(gist_description)">
278 <%def name="gist_description(gist_description)">
277 ${gist_description}
279 ${gist_description}
278 </%def>
280 </%def>
279
281
280
282
281 ## PULL REQUESTS GRID RENDERERS
283 ## PULL REQUESTS GRID RENDERERS
282
284
283 <%def name="pullrequest_target_repo(repo_name)">
285 <%def name="pullrequest_target_repo(repo_name)">
284 <div class="truncate">
286 <div class="truncate">
285 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
287 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
286 </div>
288 </div>
287 </%def>
289 </%def>
288 <%def name="pullrequest_status(status)">
290 <%def name="pullrequest_status(status)">
289 <div class="${'flag_status %s' % status} pull-left"></div>
291 <div class="${'flag_status %s' % status} pull-left"></div>
290 </%def>
292 </%def>
291
293
292 <%def name="pullrequest_title(title, description)">
294 <%def name="pullrequest_title(title, description)">
293 ${title} <br/>
295 ${title} <br/>
294 ${h.shorter(description, 40)}
296 ${h.shorter(description, 40)}
295 </%def>
297 </%def>
296
298
297 <%def name="pullrequest_comments(comments_nr)">
299 <%def name="pullrequest_comments(comments_nr)">
298 <i class="icon-comment"></i> ${comments_nr}
300 <i class="icon-comment"></i> ${comments_nr}
299 </%def>
301 </%def>
300
302
301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
303 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
302 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
304 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
303 % if short:
305 % if short:
304 #${pull_request_id}
306 #${pull_request_id}
305 % else:
307 % else:
306 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
308 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
307 % endif
309 % endif
308 </a>
310 </a>
309 </%def>
311 </%def>
310
312
311 <%def name="pullrequest_updated_on(updated_on)">
313 <%def name="pullrequest_updated_on(updated_on)">
312 ${h.age_component(h.time_to_utcdatetime(updated_on))}
314 ${h.age_component(h.time_to_utcdatetime(updated_on))}
313 </%def>
315 </%def>
314
316
315 <%def name="pullrequest_author(full_contact)">
317 <%def name="pullrequest_author(full_contact)">
316 ${base.gravatar_with_user(full_contact, 16)}
318 ${base.gravatar_with_user(full_contact, 16)}
317 </%def>
319 </%def>
@@ -1,229 +1,227 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 os
21 import os
22
22
23 from rhodecode.lib import helpers as h
23 from rhodecode.lib import helpers as h
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.repo_group import RepoGroupModel
26 from rhodecode.tests import (
26 from rhodecode.tests import (
27 url, TestController, assert_session_flash, GIT_REPO, HG_REPO,
27 url, TestController, assert_session_flash, GIT_REPO, HG_REPO,
28 TESTS_TMP_PATH, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
28 TESTS_TMP_PATH, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
29 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33
33
34
34
35 def test_update(app, csrf_token, autologin_user, user_util):
35 def test_update(app, csrf_token, autologin_user, user_util):
36 repo_group = user_util.create_repo_group()
36 repo_group = user_util.create_repo_group()
37 description = 'description for newly created repo group'
37 description = 'description for newly created repo group'
38 Session().commit()
38 Session().commit()
39 response = app.post(
39 response = app.post(
40 url('update_repo_group', group_name=repo_group.group_name),
40 url('update_repo_group', group_name=repo_group.group_name),
41 fixture._get_group_create_params(
41 fixture._get_group_create_params(
42 group_name=repo_group.group_name,
42 group_name=repo_group.group_name,
43 group_description=description,
43 group_description=description,
44 csrf_token=csrf_token,
44 csrf_token=csrf_token,
45 _method='PUT')
45 _method='PUT')
46 )
46 )
47 # TODO: anderson: johbo: we believe that this update should return
47 # TODO: anderson: johbo: we believe that this update should return
48 # a redirect instead of rendering the template.
48 # a redirect instead of rendering the template.
49 assert response.status_code == 200
49 assert response.status_code == 200
50
50
51
51
52 def test_edit(app, user_util, autologin_user):
52 def test_edit(app, user_util, autologin_user):
53 repo_group = user_util.create_repo_group()
53 repo_group = user_util.create_repo_group()
54 Session().commit()
54 Session().commit()
55 response = app.get(
55 response = app.get(
56 url('edit_repo_group', group_name=repo_group.group_name))
56 url('edit_repo_group', group_name=repo_group.group_name))
57 assert response.status_code == 200
57 assert response.status_code == 200
58
58
59
59
60 def test_edit_repo_group_perms(app, user_util, autologin_user):
60 def test_edit_repo_group_perms(app, user_util, autologin_user):
61 repo_group = user_util.create_repo_group()
61 repo_group = user_util.create_repo_group()
62 Session().commit()
62 Session().commit()
63 response = app.get(
63 response = app.get(
64 url('edit_repo_group_perms', group_name=repo_group.group_name))
64 url('edit_repo_group_perms', group_name=repo_group.group_name))
65 assert response.status_code == 200
65 assert response.status_code == 200
66
66
67
67
68 def test_update_fails_when_parent_pointing_to_self(
68 def test_update_fails_when_parent_pointing_to_self(
69 app, csrf_token, user_util, autologin_user):
69 app, csrf_token, user_util, autologin_user):
70 group = user_util.create_repo_group()
70 group = user_util.create_repo_group()
71 response = app.post(
71 response = app.post(
72 url('update_repo_group', group_name=group.group_name),
72 url('update_repo_group', group_name=group.group_name),
73 fixture._get_group_create_params(
73 fixture._get_group_create_params(
74 group_parent_id=group.group_id,
74 group_parent_id=group.group_id,
75 csrf_token=csrf_token,
75 csrf_token=csrf_token,
76 _method='PUT')
76 _method='PUT')
77 )
77 )
78 response.mustcontain(
78 response.mustcontain(
79 '<select class="medium error" id="group_parent_id"'
79 '<select class="medium error" id="group_parent_id"'
80 ' name="group_parent_id">')
80 ' name="group_parent_id">')
81 response.mustcontain('<span class="error-message">Value must be one of:')
81 response.mustcontain('<span class="error-message">Value must be one of:')
82
82
83
83
84 class _BaseTest(TestController):
84 class _BaseTest(TestController):
85
85
86 REPO_GROUP = None
86 REPO_GROUP = None
87 NEW_REPO_GROUP = None
87 NEW_REPO_GROUP = None
88 REPO = None
88 REPO = None
89 REPO_TYPE = None
89 REPO_TYPE = None
90
90
91 def test_index(self):
91 def test_index(self):
92 self.log_user()
92 self.log_user()
93 response = self.app.get(url('repo_groups'))
93 response = self.app.get(url('repo_groups'))
94 response.mustcontain('data: []')
94 response.mustcontain('data: []')
95
95
96 def test_index_after_creating_group(self):
96 def test_index_after_creating_group(self):
97 self.log_user()
97 self.log_user()
98 fixture.create_repo_group('test_repo_group')
98 fixture.create_repo_group('test_repo_group')
99 response = self.app.get(url('repo_groups'))
99 response = self.app.get(url('repo_groups'))
100 response.mustcontain('"name_raw": "test_repo_group"')
100 response.mustcontain('"name_raw": "test_repo_group"')
101 fixture.destroy_repo_group('test_repo_group')
101 fixture.destroy_repo_group('test_repo_group')
102
102
103 def test_new(self):
103 def test_new(self):
104 self.log_user()
104 self.log_user()
105 self.app.get(url('new_repo_group'))
105 self.app.get(url('new_repo_group'))
106
106
107 def test_new_by_regular_user_no_permission(self):
107 def test_new_by_regular_user_no_permission(self):
108 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
108 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
109 self.app.get(url('new_repo_group'), status=403)
109 self.app.get(url('new_repo_group'), status=403)
110
110
111 def test_create(self):
111 def test_create(self):
112 self.log_user()
112 self.log_user()
113 repo_group_name = self.NEW_REPO_GROUP
113 repo_group_name = self.NEW_REPO_GROUP
114 repo_group_name_unicode = repo_group_name.decode('utf8')
114 repo_group_name_unicode = repo_group_name.decode('utf8')
115 description = 'description for newly created repo group'
115 description = 'description for newly created repo group'
116
116
117 response = self.app.post(
117 response = self.app.post(
118 url('repo_groups'),
118 url('repo_groups'),
119 fixture._get_group_create_params(
119 fixture._get_group_create_params(
120 group_name=repo_group_name,
120 group_name=repo_group_name,
121 group_description=description,
121 group_description=description,
122 csrf_token=self.csrf_token))
122 csrf_token=self.csrf_token))
123
123
124 # run the check page that triggers the flash message
124 # run the check page that triggers the flash message
125 # response = self.app.get(url('repo_check_home', repo_name=repo_name))
126 # assert response.json == {u'result': True}
127 repo_gr_url = h.route_path(
125 repo_gr_url = h.route_path(
128 'repo_group_home', repo_group_name=repo_group_name)
126 'repo_group_home', repo_group_name=repo_group_name)
129
127
130 assert_session_flash(
128 assert_session_flash(
131 response,
129 response,
132 'Created repository group <a href="%s">%s</a>' % (
130 'Created repository group <a href="%s">%s</a>' % (
133 repo_gr_url, repo_group_name_unicode))
131 repo_gr_url, repo_group_name_unicode))
134
132
135 # # test if the repo group was created in the database
133 # # test if the repo group was created in the database
136 new_repo_group = RepoGroupModel()._get_repo_group(
134 new_repo_group = RepoGroupModel()._get_repo_group(
137 repo_group_name_unicode)
135 repo_group_name_unicode)
138 assert new_repo_group is not None
136 assert new_repo_group is not None
139
137
140 assert new_repo_group.group_name == repo_group_name_unicode
138 assert new_repo_group.group_name == repo_group_name_unicode
141 assert new_repo_group.group_description == description
139 assert new_repo_group.group_description == description
142
140
143 # test if the repository is visible in the list ?
141 # test if the repository is visible in the list ?
144 response = self.app.get(repo_gr_url)
142 response = self.app.get(repo_gr_url)
145 response.mustcontain(repo_group_name)
143 response.mustcontain(repo_group_name)
146
144
147 # test if the repository group was created on filesystem
145 # test if the repository group was created on filesystem
148 is_on_filesystem = os.path.isdir(
146 is_on_filesystem = os.path.isdir(
149 os.path.join(TESTS_TMP_PATH, repo_group_name))
147 os.path.join(TESTS_TMP_PATH, repo_group_name))
150 if not is_on_filesystem:
148 if not is_on_filesystem:
151 self.fail('no repo group %s in filesystem' % repo_group_name)
149 self.fail('no repo group %s in filesystem' % repo_group_name)
152
150
153 RepoGroupModel().delete(repo_group_name_unicode)
151 RepoGroupModel().delete(repo_group_name_unicode)
154 Session().commit()
152 Session().commit()
155
153
156 def test_create_subgroup(self, user_util):
154 def test_create_subgroup(self, user_util):
157 self.log_user()
155 self.log_user()
158 repo_group_name = self.NEW_REPO_GROUP
156 repo_group_name = self.NEW_REPO_GROUP
159 parent_group = user_util.create_repo_group()
157 parent_group = user_util.create_repo_group()
160 parent_group_name = parent_group.group_name
158 parent_group_name = parent_group.group_name
161
159
162 expected_group_name = '{}/{}'.format(
160 expected_group_name = '{}/{}'.format(
163 parent_group_name, repo_group_name)
161 parent_group_name, repo_group_name)
164 expected_group_name_unicode = expected_group_name.decode('utf8')
162 expected_group_name_unicode = expected_group_name.decode('utf8')
165
163
166 try:
164 try:
167 response = self.app.post(
165 response = self.app.post(
168 url('repo_groups'),
166 url('repo_groups'),
169 fixture._get_group_create_params(
167 fixture._get_group_create_params(
170 group_name=repo_group_name,
168 group_name=repo_group_name,
171 group_parent_id=parent_group.group_id,
169 group_parent_id=parent_group.group_id,
172 group_description='Test desciption',
170 group_description='Test desciption',
173 csrf_token=self.csrf_token))
171 csrf_token=self.csrf_token))
174
172
175 assert_session_flash(
173 assert_session_flash(
176 response,
174 response,
177 u'Created repository group <a href="%s">%s</a>' % (
175 u'Created repository group <a href="%s">%s</a>' % (
178 h.route_path('repo_group_home', repo_group_name=expected_group_name),
176 h.route_path('repo_group_home', repo_group_name=expected_group_name),
179 expected_group_name_unicode))
177 expected_group_name_unicode))
180 finally:
178 finally:
181 RepoGroupModel().delete(expected_group_name_unicode)
179 RepoGroupModel().delete(expected_group_name_unicode)
182 Session().commit()
180 Session().commit()
183
181
184 def test_user_with_creation_permissions_cannot_create_subgroups(
182 def test_user_with_creation_permissions_cannot_create_subgroups(
185 self, user_util):
183 self, user_util):
186
184
187 user_util.grant_user_permission(
185 user_util.grant_user_permission(
188 TEST_USER_REGULAR_LOGIN, 'hg.repogroup.create.true')
186 TEST_USER_REGULAR_LOGIN, 'hg.repogroup.create.true')
189 parent_group = user_util.create_repo_group()
187 parent_group = user_util.create_repo_group()
190 parent_group_id = parent_group.group_id
188 parent_group_id = parent_group.group_id
191 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
189 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
192 self.app.get(
190 self.app.get(
193 url('new_repo_group', parent_group=parent_group_id,),
191 url('new_repo_group', parent_group=parent_group_id,),
194 status=403)
192 status=403)
195
193
196
194
197 class TestRepoGroupsControllerGIT(_BaseTest):
195 class TestRepoGroupsControllerGIT(_BaseTest):
198 REPO_GROUP = None
196 REPO_GROUP = None
199 NEW_REPO_GROUP = 'git_repo'
197 NEW_REPO_GROUP = 'git_repo'
200 REPO = GIT_REPO
198 REPO = GIT_REPO
201 REPO_TYPE = 'git'
199 REPO_TYPE = 'git'
202
200
203
201
204 class TestRepoGroupsControllerNonAsciiGit(_BaseTest):
202 class TestRepoGroupsControllerNonAsciiGit(_BaseTest):
205 REPO_GROUP = None
203 REPO_GROUP = None
206 NEW_REPO_GROUP = 'git_repo_Δ…Δ‡'
204 NEW_REPO_GROUP = 'git_repo_Δ…Δ‡'
207 REPO = GIT_REPO
205 REPO = GIT_REPO
208 REPO_TYPE = 'git'
206 REPO_TYPE = 'git'
209
207
210
208
211 class TestRepoGroupsControllerHG(_BaseTest):
209 class TestRepoGroupsControllerHG(_BaseTest):
212 REPO_GROUP = None
210 REPO_GROUP = None
213 NEW_REPO_GROUP = 'hg_repo'
211 NEW_REPO_GROUP = 'hg_repo'
214 REPO = HG_REPO
212 REPO = HG_REPO
215 REPO_TYPE = 'hg'
213 REPO_TYPE = 'hg'
216
214
217
215
218 class TestRepoGroupsControllerNumericalHG(_BaseTest):
216 class TestRepoGroupsControllerNumericalHG(_BaseTest):
219 REPO_GROUP = None
217 REPO_GROUP = None
220 NEW_REPO_GROUP = '12345'
218 NEW_REPO_GROUP = '12345'
221 REPO = HG_REPO
219 REPO = HG_REPO
222 REPO_TYPE = 'hg'
220 REPO_TYPE = 'hg'
223
221
224
222
225 class TestRepoGroupsControllerNonAsciiHG(_BaseTest):
223 class TestRepoGroupsControllerNonAsciiHG(_BaseTest):
226 REPO_GROUP = None
224 REPO_GROUP = None
227 NEW_REPO_GROUP = 'hg_repo_Δ…Δ‡'
225 NEW_REPO_GROUP = 'hg_repo_Δ…Δ‡'
228 REPO = HG_REPO
226 REPO = HG_REPO
229 REPO_TYPE = 'hg'
227 REPO_TYPE = 'hg'
@@ -1,1117 +1,1131 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.lib import auth
26 from rhodecode.lib import auth
27 from rhodecode.lib.utils2 import safe_str, str2bool
27 from rhodecode.lib.utils2 import safe_str, str2bool
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.model.db import (
29 from rhodecode.model.db import (
30 Repository, RepoGroup, UserRepoToPerm, User, Permission)
30 Repository, RepoGroup, UserRepoToPerm, User, Permission)
31 from rhodecode.model.meta import Session
31 from rhodecode.model.meta import Session
32 from rhodecode.model.repo import RepoModel
32 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo_group import RepoGroupModel
33 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
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, url, assert_session_flash, TEST_USER_ADMIN_LOGIN,
38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, HG_REPO, GIT_REPO,
38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, logout_user_session)
39 logout_user_session)
40 from rhodecode.tests.fixture import Fixture, error_function
39 from rhodecode.tests.fixture import Fixture, error_function
41 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
40 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
42
41
43 fixture = Fixture()
42 fixture = Fixture()
44
43
45
44
45 def route_path(name, params=None, **kwargs):
46 import urllib
47
48 base_url = {
49 'repo_summary': '/{repo_name}',
50 'repo_creating_check': '/{repo_name}/repo_creating_check',
51 }[name].format(**kwargs)
52
53 if params:
54 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
55 return base_url
56
57
46 @pytest.mark.usefixtures("app")
58 @pytest.mark.usefixtures("app")
47 class TestAdminRepos(object):
59 class TestAdminRepos(object):
48
60
49 def test_index(self):
61 def test_index(self):
50 self.app.get(url('repos'))
62 self.app.get(url('repos'))
51
63
52 def test_create_page_restricted(self, autologin_user, backend):
64 def test_create_page_restricted(self, autologin_user, backend):
53 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
65 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
54 response = self.app.get(url('new_repo'), status=200)
66 response = self.app.get(url('new_repo'), status=200)
55 assert_response = AssertResponse(response)
67 assert_response = AssertResponse(response)
56 element = assert_response.get_element('#repo_type')
68 element = assert_response.get_element('#repo_type')
57 assert element.text_content() == '\ngit\n'
69 assert element.text_content() == '\ngit\n'
58
70
59 def test_create_page_non_restricted(self, autologin_user, backend):
71 def test_create_page_non_restricted(self, autologin_user, backend):
60 response = self.app.get(url('new_repo'), status=200)
72 response = self.app.get(url('new_repo'), status=200)
61 assert_response = AssertResponse(response)
73 assert_response = AssertResponse(response)
62 assert_response.element_contains('#repo_type', 'git')
74 assert_response.element_contains('#repo_type', 'git')
63 assert_response.element_contains('#repo_type', 'svn')
75 assert_response.element_contains('#repo_type', 'svn')
64 assert_response.element_contains('#repo_type', 'hg')
76 assert_response.element_contains('#repo_type', 'hg')
65
77
66 @pytest.mark.parametrize("suffix",
78 @pytest.mark.parametrize("suffix",
67 [u'', u'xxa'], ids=['', 'non-ascii'])
79 [u'', u'xxa'], ids=['', 'non-ascii'])
68 def test_create(self, autologin_user, backend, suffix, csrf_token):
80 def test_create(self, autologin_user, backend, suffix, csrf_token):
69 repo_name_unicode = backend.new_repo_name(suffix=suffix)
81 repo_name_unicode = backend.new_repo_name(suffix=suffix)
70 repo_name = repo_name_unicode.encode('utf8')
82 repo_name = repo_name_unicode.encode('utf8')
71 description_unicode = u'description for newly created repo' + suffix
83 description_unicode = u'description for newly created repo' + suffix
72 description = description_unicode.encode('utf8')
84 description = description_unicode.encode('utf8')
73 response = self.app.post(
85 response = self.app.post(
74 url('repos'),
86 url('repos'),
75 fixture._get_repo_create_params(
87 fixture._get_repo_create_params(
76 repo_private=False,
88 repo_private=False,
77 repo_name=repo_name,
89 repo_name=repo_name,
78 repo_type=backend.alias,
90 repo_type=backend.alias,
79 repo_description=description,
91 repo_description=description,
80 csrf_token=csrf_token),
92 csrf_token=csrf_token),
81 status=302)
93 status=302)
82
94
83 self.assert_repository_is_created_correctly(
95 self.assert_repository_is_created_correctly(
84 repo_name, description, backend)
96 repo_name, description, backend)
85
97
86 def test_create_numeric(self, autologin_user, backend, csrf_token):
98 def test_create_numeric(self, autologin_user, backend, csrf_token):
87 numeric_repo = '1234'
99 numeric_repo = '1234'
88 repo_name = numeric_repo
100 repo_name = numeric_repo
89 description = 'description for newly created repo' + numeric_repo
101 description = 'description for newly created repo' + numeric_repo
90 self.app.post(
102 self.app.post(
91 url('repos'),
103 url('repos'),
92 fixture._get_repo_create_params(
104 fixture._get_repo_create_params(
93 repo_private=False,
105 repo_private=False,
94 repo_name=repo_name,
106 repo_name=repo_name,
95 repo_type=backend.alias,
107 repo_type=backend.alias,
96 repo_description=description,
108 repo_description=description,
97 csrf_token=csrf_token))
109 csrf_token=csrf_token))
98
110
99 self.assert_repository_is_created_correctly(
111 self.assert_repository_is_created_correctly(
100 repo_name, description, backend)
112 repo_name, description, backend)
101
113
102 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ‡Δ™'], ids=['', 'non-ascii'])
114 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ‡Δ™'], ids=['', 'non-ascii'])
103 def test_create_in_group(
115 def test_create_in_group(
104 self, autologin_user, backend, suffix, csrf_token):
116 self, autologin_user, backend, suffix, csrf_token):
105 # create GROUP
117 # create GROUP
106 group_name = 'sometest_%s' % backend.alias
118 group_name = 'sometest_%s' % backend.alias
107 gr = RepoGroupModel().create(group_name=group_name,
119 gr = RepoGroupModel().create(group_name=group_name,
108 group_description='test',
120 group_description='test',
109 owner=TEST_USER_ADMIN_LOGIN)
121 owner=TEST_USER_ADMIN_LOGIN)
110 Session().commit()
122 Session().commit()
111
123
112 repo_name = u'ingroup' + suffix
124 repo_name = u'ingroup' + suffix
113 repo_name_full = RepoGroup.url_sep().join(
125 repo_name_full = RepoGroup.url_sep().join(
114 [group_name, repo_name])
126 [group_name, repo_name])
115 description = u'description for newly created repo'
127 description = u'description for newly created repo'
116 self.app.post(
128 self.app.post(
117 url('repos'),
129 url('repos'),
118 fixture._get_repo_create_params(
130 fixture._get_repo_create_params(
119 repo_private=False,
131 repo_private=False,
120 repo_name=safe_str(repo_name),
132 repo_name=safe_str(repo_name),
121 repo_type=backend.alias,
133 repo_type=backend.alias,
122 repo_description=description,
134 repo_description=description,
123 repo_group=gr.group_id,
135 repo_group=gr.group_id,
124 csrf_token=csrf_token))
136 csrf_token=csrf_token))
125
137
126 # TODO: johbo: Cleanup work to fixture
138 # TODO: johbo: Cleanup work to fixture
127 try:
139 try:
128 self.assert_repository_is_created_correctly(
140 self.assert_repository_is_created_correctly(
129 repo_name_full, description, backend)
141 repo_name_full, description, backend)
130
142
131 new_repo = RepoModel().get_by_repo_name(repo_name_full)
143 new_repo = RepoModel().get_by_repo_name(repo_name_full)
132 inherited_perms = UserRepoToPerm.query().filter(
144 inherited_perms = UserRepoToPerm.query().filter(
133 UserRepoToPerm.repository_id == new_repo.repo_id).all()
145 UserRepoToPerm.repository_id == new_repo.repo_id).all()
134 assert len(inherited_perms) == 1
146 assert len(inherited_perms) == 1
135 finally:
147 finally:
136 RepoModel().delete(repo_name_full)
148 RepoModel().delete(repo_name_full)
137 RepoGroupModel().delete(group_name)
149 RepoGroupModel().delete(group_name)
138 Session().commit()
150 Session().commit()
139
151
140 def test_create_in_group_numeric(
152 def test_create_in_group_numeric(
141 self, autologin_user, backend, csrf_token):
153 self, autologin_user, backend, csrf_token):
142 # create GROUP
154 # create GROUP
143 group_name = 'sometest_%s' % backend.alias
155 group_name = 'sometest_%s' % backend.alias
144 gr = RepoGroupModel().create(group_name=group_name,
156 gr = RepoGroupModel().create(group_name=group_name,
145 group_description='test',
157 group_description='test',
146 owner=TEST_USER_ADMIN_LOGIN)
158 owner=TEST_USER_ADMIN_LOGIN)
147 Session().commit()
159 Session().commit()
148
160
149 repo_name = '12345'
161 repo_name = '12345'
150 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
162 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
151 description = 'description for newly created repo'
163 description = 'description for newly created repo'
152 self.app.post(
164 self.app.post(
153 url('repos'),
165 url('repos'),
154 fixture._get_repo_create_params(
166 fixture._get_repo_create_params(
155 repo_private=False,
167 repo_private=False,
156 repo_name=repo_name,
168 repo_name=repo_name,
157 repo_type=backend.alias,
169 repo_type=backend.alias,
158 repo_description=description,
170 repo_description=description,
159 repo_group=gr.group_id,
171 repo_group=gr.group_id,
160 csrf_token=csrf_token))
172 csrf_token=csrf_token))
161
173
162 # TODO: johbo: Cleanup work to fixture
174 # TODO: johbo: Cleanup work to fixture
163 try:
175 try:
164 self.assert_repository_is_created_correctly(
176 self.assert_repository_is_created_correctly(
165 repo_name_full, description, backend)
177 repo_name_full, description, backend)
166
178
167 new_repo = RepoModel().get_by_repo_name(repo_name_full)
179 new_repo = RepoModel().get_by_repo_name(repo_name_full)
168 inherited_perms = UserRepoToPerm.query()\
180 inherited_perms = UserRepoToPerm.query()\
169 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
181 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
170 assert len(inherited_perms) == 1
182 assert len(inherited_perms) == 1
171 finally:
183 finally:
172 RepoModel().delete(repo_name_full)
184 RepoModel().delete(repo_name_full)
173 RepoGroupModel().delete(group_name)
185 RepoGroupModel().delete(group_name)
174 Session().commit()
186 Session().commit()
175
187
176 def test_create_in_group_without_needed_permissions(self, backend):
188 def test_create_in_group_without_needed_permissions(self, backend):
177 session = login_user_session(
189 session = login_user_session(
178 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
190 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
179 csrf_token = auth.get_csrf_token(session)
191 csrf_token = auth.get_csrf_token(session)
180 # revoke
192 # revoke
181 user_model = UserModel()
193 user_model = UserModel()
182 # disable fork and create on default user
194 # disable fork and create on default user
183 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
195 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
184 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
196 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
185 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
197 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
186 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
198 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
187
199
188 # disable on regular user
200 # disable on regular user
189 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
201 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
190 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
202 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
191 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
203 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
192 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
204 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
193 Session().commit()
205 Session().commit()
194
206
195 # create GROUP
207 # create GROUP
196 group_name = 'reg_sometest_%s' % backend.alias
208 group_name = 'reg_sometest_%s' % backend.alias
197 gr = RepoGroupModel().create(group_name=group_name,
209 gr = RepoGroupModel().create(group_name=group_name,
198 group_description='test',
210 group_description='test',
199 owner=TEST_USER_ADMIN_LOGIN)
211 owner=TEST_USER_ADMIN_LOGIN)
200 Session().commit()
212 Session().commit()
201
213
202 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
214 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
203 gr_allowed = RepoGroupModel().create(
215 gr_allowed = RepoGroupModel().create(
204 group_name=group_name_allowed,
216 group_name=group_name_allowed,
205 group_description='test',
217 group_description='test',
206 owner=TEST_USER_REGULAR_LOGIN)
218 owner=TEST_USER_REGULAR_LOGIN)
207 Session().commit()
219 Session().commit()
208
220
209 repo_name = 'ingroup'
221 repo_name = 'ingroup'
210 description = 'description for newly created repo'
222 description = 'description for newly created repo'
211 response = self.app.post(
223 response = self.app.post(
212 url('repos'),
224 url('repos'),
213 fixture._get_repo_create_params(
225 fixture._get_repo_create_params(
214 repo_private=False,
226 repo_private=False,
215 repo_name=repo_name,
227 repo_name=repo_name,
216 repo_type=backend.alias,
228 repo_type=backend.alias,
217 repo_description=description,
229 repo_description=description,
218 repo_group=gr.group_id,
230 repo_group=gr.group_id,
219 csrf_token=csrf_token))
231 csrf_token=csrf_token))
220
232
221 response.mustcontain('Invalid value')
233 response.mustcontain('Invalid value')
222
234
223 # user is allowed to create in this group
235 # user is allowed to create in this group
224 repo_name = 'ingroup'
236 repo_name = 'ingroup'
225 repo_name_full = RepoGroup.url_sep().join(
237 repo_name_full = RepoGroup.url_sep().join(
226 [group_name_allowed, repo_name])
238 [group_name_allowed, repo_name])
227 description = 'description for newly created repo'
239 description = 'description for newly created repo'
228 response = self.app.post(
240 response = self.app.post(
229 url('repos'),
241 url('repos'),
230 fixture._get_repo_create_params(
242 fixture._get_repo_create_params(
231 repo_private=False,
243 repo_private=False,
232 repo_name=repo_name,
244 repo_name=repo_name,
233 repo_type=backend.alias,
245 repo_type=backend.alias,
234 repo_description=description,
246 repo_description=description,
235 repo_group=gr_allowed.group_id,
247 repo_group=gr_allowed.group_id,
236 csrf_token=csrf_token))
248 csrf_token=csrf_token))
237
249
238 # TODO: johbo: Cleanup in pytest fixture
250 # TODO: johbo: Cleanup in pytest fixture
239 try:
251 try:
240 self.assert_repository_is_created_correctly(
252 self.assert_repository_is_created_correctly(
241 repo_name_full, description, backend)
253 repo_name_full, description, backend)
242
254
243 new_repo = RepoModel().get_by_repo_name(repo_name_full)
255 new_repo = RepoModel().get_by_repo_name(repo_name_full)
244 inherited_perms = UserRepoToPerm.query().filter(
256 inherited_perms = UserRepoToPerm.query().filter(
245 UserRepoToPerm.repository_id == new_repo.repo_id).all()
257 UserRepoToPerm.repository_id == new_repo.repo_id).all()
246 assert len(inherited_perms) == 1
258 assert len(inherited_perms) == 1
247
259
248 assert repo_on_filesystem(repo_name_full)
260 assert repo_on_filesystem(repo_name_full)
249 finally:
261 finally:
250 RepoModel().delete(repo_name_full)
262 RepoModel().delete(repo_name_full)
251 RepoGroupModel().delete(group_name)
263 RepoGroupModel().delete(group_name)
252 RepoGroupModel().delete(group_name_allowed)
264 RepoGroupModel().delete(group_name_allowed)
253 Session().commit()
265 Session().commit()
254
266
255 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
267 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
256 csrf_token):
268 csrf_token):
257 # create GROUP
269 # create GROUP
258 group_name = 'sometest_%s' % backend.alias
270 group_name = 'sometest_%s' % backend.alias
259 gr = RepoGroupModel().create(group_name=group_name,
271 gr = RepoGroupModel().create(group_name=group_name,
260 group_description='test',
272 group_description='test',
261 owner=TEST_USER_ADMIN_LOGIN)
273 owner=TEST_USER_ADMIN_LOGIN)
262 perm = Permission.get_by_key('repository.write')
274 perm = Permission.get_by_key('repository.write')
263 RepoGroupModel().grant_user_permission(
275 RepoGroupModel().grant_user_permission(
264 gr, TEST_USER_REGULAR_LOGIN, perm)
276 gr, TEST_USER_REGULAR_LOGIN, perm)
265
277
266 # add repo permissions
278 # add repo permissions
267 Session().commit()
279 Session().commit()
268
280
269 repo_name = 'ingroup_inherited_%s' % backend.alias
281 repo_name = 'ingroup_inherited_%s' % backend.alias
270 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
282 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
271 description = 'description for newly created repo'
283 description = 'description for newly created repo'
272 self.app.post(
284 self.app.post(
273 url('repos'),
285 url('repos'),
274 fixture._get_repo_create_params(
286 fixture._get_repo_create_params(
275 repo_private=False,
287 repo_private=False,
276 repo_name=repo_name,
288 repo_name=repo_name,
277 repo_type=backend.alias,
289 repo_type=backend.alias,
278 repo_description=description,
290 repo_description=description,
279 repo_group=gr.group_id,
291 repo_group=gr.group_id,
280 repo_copy_permissions=True,
292 repo_copy_permissions=True,
281 csrf_token=csrf_token))
293 csrf_token=csrf_token))
282
294
283 # TODO: johbo: Cleanup to pytest fixture
295 # TODO: johbo: Cleanup to pytest fixture
284 try:
296 try:
285 self.assert_repository_is_created_correctly(
297 self.assert_repository_is_created_correctly(
286 repo_name_full, description, backend)
298 repo_name_full, description, backend)
287 except Exception:
299 except Exception:
288 RepoGroupModel().delete(group_name)
300 RepoGroupModel().delete(group_name)
289 Session().commit()
301 Session().commit()
290 raise
302 raise
291
303
292 # check if inherited permissions are applied
304 # check if inherited permissions are applied
293 new_repo = RepoModel().get_by_repo_name(repo_name_full)
305 new_repo = RepoModel().get_by_repo_name(repo_name_full)
294 inherited_perms = UserRepoToPerm.query().filter(
306 inherited_perms = UserRepoToPerm.query().filter(
295 UserRepoToPerm.repository_id == new_repo.repo_id).all()
307 UserRepoToPerm.repository_id == new_repo.repo_id).all()
296 assert len(inherited_perms) == 2
308 assert len(inherited_perms) == 2
297
309
298 assert TEST_USER_REGULAR_LOGIN in [
310 assert TEST_USER_REGULAR_LOGIN in [
299 x.user.username for x in inherited_perms]
311 x.user.username for x in inherited_perms]
300 assert 'repository.write' in [
312 assert 'repository.write' in [
301 x.permission.permission_name for x in inherited_perms]
313 x.permission.permission_name for x in inherited_perms]
302
314
303 RepoModel().delete(repo_name_full)
315 RepoModel().delete(repo_name_full)
304 RepoGroupModel().delete(group_name)
316 RepoGroupModel().delete(group_name)
305 Session().commit()
317 Session().commit()
306
318
307 @pytest.mark.xfail_backends(
319 @pytest.mark.xfail_backends(
308 "git", "hg", reason="Missing reposerver support")
320 "git", "hg", reason="Missing reposerver support")
309 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
321 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
310 csrf_token):
322 csrf_token):
311 source_repo = backend.create_repo(number_of_commits=2)
323 source_repo = backend.create_repo(number_of_commits=2)
312 source_repo_name = source_repo.repo_name
324 source_repo_name = source_repo.repo_name
313 reposerver.serve(source_repo.scm_instance())
325 reposerver.serve(source_repo.scm_instance())
314
326
315 repo_name = backend.new_repo_name()
327 repo_name = backend.new_repo_name()
316 response = self.app.post(
328 response = self.app.post(
317 url('repos'),
329 url('repos'),
318 fixture._get_repo_create_params(
330 fixture._get_repo_create_params(
319 repo_private=False,
331 repo_private=False,
320 repo_name=repo_name,
332 repo_name=repo_name,
321 repo_type=backend.alias,
333 repo_type=backend.alias,
322 repo_description='',
334 repo_description='',
323 clone_uri=reposerver.url,
335 clone_uri=reposerver.url,
324 csrf_token=csrf_token),
336 csrf_token=csrf_token),
325 status=302)
337 status=302)
326
338
327 # Should be redirected to the creating page
339 # Should be redirected to the creating page
328 response.mustcontain('repo_creating')
340 response.mustcontain('repo_creating')
329
341
330 # Expecting that both repositories have same history
342 # Expecting that both repositories have same history
331 source_repo = RepoModel().get_by_repo_name(source_repo_name)
343 source_repo = RepoModel().get_by_repo_name(source_repo_name)
332 source_vcs = source_repo.scm_instance()
344 source_vcs = source_repo.scm_instance()
333 repo = RepoModel().get_by_repo_name(repo_name)
345 repo = RepoModel().get_by_repo_name(repo_name)
334 repo_vcs = repo.scm_instance()
346 repo_vcs = repo.scm_instance()
335 assert source_vcs[0].message == repo_vcs[0].message
347 assert source_vcs[0].message == repo_vcs[0].message
336 assert source_vcs.count() == repo_vcs.count()
348 assert source_vcs.count() == repo_vcs.count()
337 assert source_vcs.commit_ids == repo_vcs.commit_ids
349 assert source_vcs.commit_ids == repo_vcs.commit_ids
338
350
339 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
351 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
340 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
352 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
341 csrf_token):
353 csrf_token):
342 repo_name = backend.new_repo_name()
354 repo_name = backend.new_repo_name()
343 description = 'description for newly created repo'
355 description = 'description for newly created repo'
344 response = self.app.post(
356 response = self.app.post(
345 url('repos'),
357 url('repos'),
346 fixture._get_repo_create_params(
358 fixture._get_repo_create_params(
347 repo_private=False,
359 repo_private=False,
348 repo_name=repo_name,
360 repo_name=repo_name,
349 repo_type=backend.alias,
361 repo_type=backend.alias,
350 repo_description=description,
362 repo_description=description,
351 clone_uri='http://repo.invalid/repo',
363 clone_uri='http://repo.invalid/repo',
352 csrf_token=csrf_token))
364 csrf_token=csrf_token))
353 response.mustcontain('invalid clone url')
365 response.mustcontain('invalid clone url')
354
366
355 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
367 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
356 def test_create_remote_repo_wrong_clone_uri_hg_svn(
368 def test_create_remote_repo_wrong_clone_uri_hg_svn(
357 self, autologin_user, backend, csrf_token):
369 self, autologin_user, backend, csrf_token):
358 repo_name = backend.new_repo_name()
370 repo_name = backend.new_repo_name()
359 description = 'description for newly created repo'
371 description = 'description for newly created repo'
360 response = self.app.post(
372 response = self.app.post(
361 url('repos'),
373 url('repos'),
362 fixture._get_repo_create_params(
374 fixture._get_repo_create_params(
363 repo_private=False,
375 repo_private=False,
364 repo_name=repo_name,
376 repo_name=repo_name,
365 repo_type=backend.alias,
377 repo_type=backend.alias,
366 repo_description=description,
378 repo_description=description,
367 clone_uri='svn+http://svn.invalid/repo',
379 clone_uri='svn+http://svn.invalid/repo',
368 csrf_token=csrf_token))
380 csrf_token=csrf_token))
369 response.mustcontain('invalid clone url')
381 response.mustcontain('invalid clone url')
370
382
371 def test_create_with_git_suffix(
383 def test_create_with_git_suffix(
372 self, autologin_user, backend, csrf_token):
384 self, autologin_user, backend, csrf_token):
373 repo_name = backend.new_repo_name() + ".git"
385 repo_name = backend.new_repo_name() + ".git"
374 description = 'description for newly created repo'
386 description = 'description for newly created repo'
375 response = self.app.post(
387 response = self.app.post(
376 url('repos'),
388 url('repos'),
377 fixture._get_repo_create_params(
389 fixture._get_repo_create_params(
378 repo_private=False,
390 repo_private=False,
379 repo_name=repo_name,
391 repo_name=repo_name,
380 repo_type=backend.alias,
392 repo_type=backend.alias,
381 repo_description=description,
393 repo_description=description,
382 csrf_token=csrf_token))
394 csrf_token=csrf_token))
383 response.mustcontain('Repository name cannot end with .git')
395 response.mustcontain('Repository name cannot end with .git')
384
396
385 def test_show(self, autologin_user, backend):
397 def test_show(self, autologin_user, backend):
386 self.app.get(url('repo', repo_name=backend.repo_name))
398 self.app.get(url('repo', repo_name=backend.repo_name))
387
399
388 def test_default_user_cannot_access_private_repo_in_a_group(
400 def test_default_user_cannot_access_private_repo_in_a_group(
389 self, autologin_user, user_util, backend, csrf_token):
401 self, autologin_user, user_util, backend, csrf_token):
390
402
391 group = user_util.create_repo_group()
403 group = user_util.create_repo_group()
392
404
393 repo = backend.create_repo(
405 repo = backend.create_repo(
394 repo_private=True, repo_group=group, repo_copy_permissions=True)
406 repo_private=True, repo_group=group, repo_copy_permissions=True)
395
407
396 permissions = _get_permission_for_user(
408 permissions = _get_permission_for_user(
397 user='default', repo=repo.repo_name)
409 user='default', repo=repo.repo_name)
398 assert len(permissions) == 1
410 assert len(permissions) == 1
399 assert permissions[0].permission.permission_name == 'repository.none'
411 assert permissions[0].permission.permission_name == 'repository.none'
400 assert permissions[0].repository.private is True
412 assert permissions[0].repository.private is True
401
413
402 def test_create_on_top_level_without_permissions(self, backend):
414 def test_create_on_top_level_without_permissions(self, backend):
403 session = login_user_session(
415 session = login_user_session(
404 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
416 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
405 csrf_token = auth.get_csrf_token(session)
417 csrf_token = auth.get_csrf_token(session)
406
418
407 # revoke
419 # revoke
408 user_model = UserModel()
420 user_model = UserModel()
409 # disable fork and create on default user
421 # disable fork and create on default user
410 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
422 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
411 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
423 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
412 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
424 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
413 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
425 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
414
426
415 # disable on regular user
427 # disable on regular user
416 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
428 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
417 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
429 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
418 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
430 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
419 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
431 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
420 Session().commit()
432 Session().commit()
421
433
422 repo_name = backend.new_repo_name()
434 repo_name = backend.new_repo_name()
423 description = 'description for newly created repo'
435 description = 'description for newly created repo'
424 response = self.app.post(
436 response = self.app.post(
425 url('repos'),
437 url('repos'),
426 fixture._get_repo_create_params(
438 fixture._get_repo_create_params(
427 repo_private=False,
439 repo_private=False,
428 repo_name=repo_name,
440 repo_name=repo_name,
429 repo_type=backend.alias,
441 repo_type=backend.alias,
430 repo_description=description,
442 repo_description=description,
431 csrf_token=csrf_token))
443 csrf_token=csrf_token))
432
444
433 response.mustcontain(
445 response.mustcontain(
434 u"You do not have the permission to store repositories in "
446 u"You do not have the permission to store repositories in "
435 u"the root location.")
447 u"the root location.")
436
448
437 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
449 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
438 def test_create_repo_when_filesystem_op_fails(
450 def test_create_repo_when_filesystem_op_fails(
439 self, autologin_user, backend, csrf_token):
451 self, autologin_user, backend, csrf_token):
440 repo_name = backend.new_repo_name()
452 repo_name = backend.new_repo_name()
441 description = 'description for newly created repo'
453 description = 'description for newly created repo'
442
454
443 response = self.app.post(
455 response = self.app.post(
444 url('repos'),
456 url('repos'),
445 fixture._get_repo_create_params(
457 fixture._get_repo_create_params(
446 repo_private=False,
458 repo_private=False,
447 repo_name=repo_name,
459 repo_name=repo_name,
448 repo_type=backend.alias,
460 repo_type=backend.alias,
449 repo_description=description,
461 repo_description=description,
450 csrf_token=csrf_token))
462 csrf_token=csrf_token))
451
463
452 assert_session_flash(
464 assert_session_flash(
453 response, 'Error creating repository %s' % repo_name)
465 response, 'Error creating repository %s' % repo_name)
454 # repo must not be in db
466 # repo must not be in db
455 assert backend.repo is None
467 assert backend.repo is None
456 # repo must not be in filesystem !
468 # repo must not be in filesystem !
457 assert not repo_on_filesystem(repo_name)
469 assert not repo_on_filesystem(repo_name)
458
470
459 def assert_repository_is_created_correctly(
471 def assert_repository_is_created_correctly(
460 self, repo_name, description, backend):
472 self, repo_name, description, backend):
461 repo_name_utf8 = safe_str(repo_name)
473 repo_name_utf8 = safe_str(repo_name)
462
474
463 # run the check page that triggers the flash message
475 # run the check page that triggers the flash message
464 response = self.app.get(url('repo_check_home', repo_name=repo_name))
476 response = self.app.get(
477 route_path('repo_creating_check', repo_name=safe_str(repo_name)))
465 assert response.json == {u'result': True}
478 assert response.json == {u'result': True}
466
479
467 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
480 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
468 urllib.quote(repo_name_utf8), repo_name)
481 urllib.quote(repo_name_utf8), repo_name)
469 assert_session_flash(response, flash_msg)
482 assert_session_flash(response, flash_msg)
470
483
471 # test if the repo was created in the database
484 # test if the repo was created in the database
472 new_repo = RepoModel().get_by_repo_name(repo_name)
485 new_repo = RepoModel().get_by_repo_name(repo_name)
473
486
474 assert new_repo.repo_name == repo_name
487 assert new_repo.repo_name == repo_name
475 assert new_repo.description == description
488 assert new_repo.description == description
476
489
477 # test if the repository is visible in the list ?
490 # test if the repository is visible in the list ?
478 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
491 response = self.app.get(
492 h.route_path('repo_summary', repo_name=safe_str(repo_name)))
479 response.mustcontain(repo_name)
493 response.mustcontain(repo_name)
480 response.mustcontain(backend.alias)
494 response.mustcontain(backend.alias)
481
495
482 assert repo_on_filesystem(repo_name)
496 assert repo_on_filesystem(repo_name)
483
497
484
498
485 @pytest.mark.usefixtures("app")
499 @pytest.mark.usefixtures("app")
486 class TestVcsSettings(object):
500 class TestVcsSettings(object):
487 FORM_DATA = {
501 FORM_DATA = {
488 'inherit_global_settings': False,
502 'inherit_global_settings': False,
489 'hooks_changegroup_repo_size': False,
503 'hooks_changegroup_repo_size': False,
490 'hooks_changegroup_push_logger': False,
504 'hooks_changegroup_push_logger': False,
491 'hooks_outgoing_pull_logger': False,
505 'hooks_outgoing_pull_logger': False,
492 'extensions_largefiles': False,
506 'extensions_largefiles': False,
493 'extensions_evolve': False,
507 'extensions_evolve': False,
494 'phases_publish': 'False',
508 'phases_publish': 'False',
495 'rhodecode_pr_merge_enabled': False,
509 'rhodecode_pr_merge_enabled': False,
496 'rhodecode_use_outdated_comments': False,
510 'rhodecode_use_outdated_comments': False,
497 'new_svn_branch': '',
511 'new_svn_branch': '',
498 'new_svn_tag': ''
512 'new_svn_tag': ''
499 }
513 }
500
514
501 @pytest.mark.skip_backends('svn')
515 @pytest.mark.skip_backends('svn')
502 def test_global_settings_initial_values(self, autologin_user, backend):
516 def test_global_settings_initial_values(self, autologin_user, backend):
503 repo_name = backend.repo_name
517 repo_name = backend.repo_name
504 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
518 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
505
519
506 expected_settings = (
520 expected_settings = (
507 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
521 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
508 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
522 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
509 'hooks_outgoing_pull_logger'
523 'hooks_outgoing_pull_logger'
510 )
524 )
511 for setting in expected_settings:
525 for setting in expected_settings:
512 self.assert_repo_value_equals_global_value(response, setting)
526 self.assert_repo_value_equals_global_value(response, setting)
513
527
514 def test_show_settings_requires_repo_admin_permission(
528 def test_show_settings_requires_repo_admin_permission(
515 self, backend, user_util, settings_util):
529 self, backend, user_util, settings_util):
516 repo = backend.create_repo()
530 repo = backend.create_repo()
517 repo_name = repo.repo_name
531 repo_name = repo.repo_name
518 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
532 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
519 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
533 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
520 login_user_session(
534 login_user_session(
521 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
535 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
522 self.app.get(url('repo_vcs_settings', repo_name=repo_name), status=200)
536 self.app.get(url('repo_vcs_settings', repo_name=repo_name), status=200)
523
537
524 def test_inherit_global_settings_flag_is_true_by_default(
538 def test_inherit_global_settings_flag_is_true_by_default(
525 self, autologin_user, backend):
539 self, autologin_user, backend):
526 repo_name = backend.repo_name
540 repo_name = backend.repo_name
527 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
541 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
528
542
529 assert_response = AssertResponse(response)
543 assert_response = AssertResponse(response)
530 element = assert_response.get_element('#inherit_global_settings')
544 element = assert_response.get_element('#inherit_global_settings')
531 assert element.checked
545 assert element.checked
532
546
533 @pytest.mark.parametrize('checked_value', [True, False])
547 @pytest.mark.parametrize('checked_value', [True, False])
534 def test_inherit_global_settings_value(
548 def test_inherit_global_settings_value(
535 self, autologin_user, backend, checked_value, settings_util):
549 self, autologin_user, backend, checked_value, settings_util):
536 repo = backend.create_repo()
550 repo = backend.create_repo()
537 repo_name = repo.repo_name
551 repo_name = repo.repo_name
538 settings_util.create_repo_rhodecode_setting(
552 settings_util.create_repo_rhodecode_setting(
539 repo, 'inherit_vcs_settings', checked_value, 'bool')
553 repo, 'inherit_vcs_settings', checked_value, 'bool')
540 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
554 response = self.app.get(url('repo_vcs_settings', repo_name=repo_name))
541
555
542 assert_response = AssertResponse(response)
556 assert_response = AssertResponse(response)
543 element = assert_response.get_element('#inherit_global_settings')
557 element = assert_response.get_element('#inherit_global_settings')
544 assert element.checked == checked_value
558 assert element.checked == checked_value
545
559
546 @pytest.mark.skip_backends('svn')
560 @pytest.mark.skip_backends('svn')
547 def test_hooks_settings_are_created(
561 def test_hooks_settings_are_created(
548 self, autologin_user, backend, csrf_token):
562 self, autologin_user, backend, csrf_token):
549 repo_name = backend.repo_name
563 repo_name = backend.repo_name
550 data = self.FORM_DATA.copy()
564 data = self.FORM_DATA.copy()
551 data['csrf_token'] = csrf_token
565 data['csrf_token'] = csrf_token
552 self.app.post(
566 self.app.post(
553 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
567 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
554 settings = SettingsModel(repo=repo_name)
568 settings = SettingsModel(repo=repo_name)
555 try:
569 try:
556 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
570 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
557 ui = settings.get_ui_by_section_and_key(section, key)
571 ui = settings.get_ui_by_section_and_key(section, key)
558 assert ui.ui_active is False
572 assert ui.ui_active is False
559 finally:
573 finally:
560 self._cleanup_repo_settings(settings)
574 self._cleanup_repo_settings(settings)
561
575
562 def test_hooks_settings_are_not_created_for_svn(
576 def test_hooks_settings_are_not_created_for_svn(
563 self, autologin_user, backend_svn, csrf_token):
577 self, autologin_user, backend_svn, csrf_token):
564 repo_name = backend_svn.repo_name
578 repo_name = backend_svn.repo_name
565 data = self.FORM_DATA.copy()
579 data = self.FORM_DATA.copy()
566 data['csrf_token'] = csrf_token
580 data['csrf_token'] = csrf_token
567 self.app.post(
581 self.app.post(
568 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
582 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
569 settings = SettingsModel(repo=repo_name)
583 settings = SettingsModel(repo=repo_name)
570 try:
584 try:
571 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
585 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
572 ui = settings.get_ui_by_section_and_key(section, key)
586 ui = settings.get_ui_by_section_and_key(section, key)
573 assert ui is None
587 assert ui is None
574 finally:
588 finally:
575 self._cleanup_repo_settings(settings)
589 self._cleanup_repo_settings(settings)
576
590
577 @pytest.mark.skip_backends('svn')
591 @pytest.mark.skip_backends('svn')
578 def test_hooks_settings_are_updated(
592 def test_hooks_settings_are_updated(
579 self, autologin_user, backend, csrf_token):
593 self, autologin_user, backend, csrf_token):
580 repo_name = backend.repo_name
594 repo_name = backend.repo_name
581 settings = SettingsModel(repo=repo_name)
595 settings = SettingsModel(repo=repo_name)
582 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
596 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
583 settings.create_ui_section_value(section, '', key=key, active=True)
597 settings.create_ui_section_value(section, '', key=key, active=True)
584
598
585 data = self.FORM_DATA.copy()
599 data = self.FORM_DATA.copy()
586 data['csrf_token'] = csrf_token
600 data['csrf_token'] = csrf_token
587 self.app.post(
601 self.app.post(
588 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
602 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
589 try:
603 try:
590 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
604 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
591 ui = settings.get_ui_by_section_and_key(section, key)
605 ui = settings.get_ui_by_section_and_key(section, key)
592 assert ui.ui_active is False
606 assert ui.ui_active is False
593 finally:
607 finally:
594 self._cleanup_repo_settings(settings)
608 self._cleanup_repo_settings(settings)
595
609
596 def test_hooks_settings_are_not_updated_for_svn(
610 def test_hooks_settings_are_not_updated_for_svn(
597 self, autologin_user, backend_svn, csrf_token):
611 self, autologin_user, backend_svn, csrf_token):
598 repo_name = backend_svn.repo_name
612 repo_name = backend_svn.repo_name
599 settings = SettingsModel(repo=repo_name)
613 settings = SettingsModel(repo=repo_name)
600 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
614 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
601 settings.create_ui_section_value(section, '', key=key, active=True)
615 settings.create_ui_section_value(section, '', key=key, active=True)
602
616
603 data = self.FORM_DATA.copy()
617 data = self.FORM_DATA.copy()
604 data['csrf_token'] = csrf_token
618 data['csrf_token'] = csrf_token
605 self.app.post(
619 self.app.post(
606 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
620 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
607 try:
621 try:
608 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
622 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
609 ui = settings.get_ui_by_section_and_key(section, key)
623 ui = settings.get_ui_by_section_and_key(section, key)
610 assert ui.ui_active is True
624 assert ui.ui_active is True
611 finally:
625 finally:
612 self._cleanup_repo_settings(settings)
626 self._cleanup_repo_settings(settings)
613
627
614 @pytest.mark.skip_backends('svn')
628 @pytest.mark.skip_backends('svn')
615 def test_pr_settings_are_created(
629 def test_pr_settings_are_created(
616 self, autologin_user, backend, csrf_token):
630 self, autologin_user, backend, csrf_token):
617 repo_name = backend.repo_name
631 repo_name = backend.repo_name
618 data = self.FORM_DATA.copy()
632 data = self.FORM_DATA.copy()
619 data['csrf_token'] = csrf_token
633 data['csrf_token'] = csrf_token
620 self.app.post(
634 self.app.post(
621 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
635 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
622 settings = SettingsModel(repo=repo_name)
636 settings = SettingsModel(repo=repo_name)
623 try:
637 try:
624 for name in VcsSettingsModel.GENERAL_SETTINGS:
638 for name in VcsSettingsModel.GENERAL_SETTINGS:
625 setting = settings.get_setting_by_name(name)
639 setting = settings.get_setting_by_name(name)
626 assert setting.app_settings_value is False
640 assert setting.app_settings_value is False
627 finally:
641 finally:
628 self._cleanup_repo_settings(settings)
642 self._cleanup_repo_settings(settings)
629
643
630 def test_pr_settings_are_not_created_for_svn(
644 def test_pr_settings_are_not_created_for_svn(
631 self, autologin_user, backend_svn, csrf_token):
645 self, autologin_user, backend_svn, csrf_token):
632 repo_name = backend_svn.repo_name
646 repo_name = backend_svn.repo_name
633 data = self.FORM_DATA.copy()
647 data = self.FORM_DATA.copy()
634 data['csrf_token'] = csrf_token
648 data['csrf_token'] = csrf_token
635 self.app.post(
649 self.app.post(
636 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
650 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
637 settings = SettingsModel(repo=repo_name)
651 settings = SettingsModel(repo=repo_name)
638 try:
652 try:
639 for name in VcsSettingsModel.GENERAL_SETTINGS:
653 for name in VcsSettingsModel.GENERAL_SETTINGS:
640 setting = settings.get_setting_by_name(name)
654 setting = settings.get_setting_by_name(name)
641 assert setting is None
655 assert setting is None
642 finally:
656 finally:
643 self._cleanup_repo_settings(settings)
657 self._cleanup_repo_settings(settings)
644
658
645 def test_pr_settings_creation_requires_repo_admin_permission(
659 def test_pr_settings_creation_requires_repo_admin_permission(
646 self, backend, user_util, settings_util, csrf_token):
660 self, backend, user_util, settings_util, csrf_token):
647 repo = backend.create_repo()
661 repo = backend.create_repo()
648 repo_name = repo.repo_name
662 repo_name = repo.repo_name
649
663
650 logout_user_session(self.app, csrf_token)
664 logout_user_session(self.app, csrf_token)
651 session = login_user_session(
665 session = login_user_session(
652 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
666 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
653 new_csrf_token = auth.get_csrf_token(session)
667 new_csrf_token = auth.get_csrf_token(session)
654
668
655 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
669 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
656 repo = Repository.get_by_repo_name(repo_name)
670 repo = Repository.get_by_repo_name(repo_name)
657 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
671 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
658 data = self.FORM_DATA.copy()
672 data = self.FORM_DATA.copy()
659 data['csrf_token'] = new_csrf_token
673 data['csrf_token'] = new_csrf_token
660 settings = SettingsModel(repo=repo_name)
674 settings = SettingsModel(repo=repo_name)
661
675
662 try:
676 try:
663 self.app.post(
677 self.app.post(
664 url('repo_vcs_settings', repo_name=repo_name), data,
678 url('repo_vcs_settings', repo_name=repo_name), data,
665 status=302)
679 status=302)
666 finally:
680 finally:
667 self._cleanup_repo_settings(settings)
681 self._cleanup_repo_settings(settings)
668
682
669 @pytest.mark.skip_backends('svn')
683 @pytest.mark.skip_backends('svn')
670 def test_pr_settings_are_updated(
684 def test_pr_settings_are_updated(
671 self, autologin_user, backend, csrf_token):
685 self, autologin_user, backend, csrf_token):
672 repo_name = backend.repo_name
686 repo_name = backend.repo_name
673 settings = SettingsModel(repo=repo_name)
687 settings = SettingsModel(repo=repo_name)
674 for name in VcsSettingsModel.GENERAL_SETTINGS:
688 for name in VcsSettingsModel.GENERAL_SETTINGS:
675 settings.create_or_update_setting(name, True, 'bool')
689 settings.create_or_update_setting(name, True, 'bool')
676
690
677 data = self.FORM_DATA.copy()
691 data = self.FORM_DATA.copy()
678 data['csrf_token'] = csrf_token
692 data['csrf_token'] = csrf_token
679 self.app.post(
693 self.app.post(
680 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
694 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
681 try:
695 try:
682 for name in VcsSettingsModel.GENERAL_SETTINGS:
696 for name in VcsSettingsModel.GENERAL_SETTINGS:
683 setting = settings.get_setting_by_name(name)
697 setting = settings.get_setting_by_name(name)
684 assert setting.app_settings_value is False
698 assert setting.app_settings_value is False
685 finally:
699 finally:
686 self._cleanup_repo_settings(settings)
700 self._cleanup_repo_settings(settings)
687
701
688 def test_pr_settings_are_not_updated_for_svn(
702 def test_pr_settings_are_not_updated_for_svn(
689 self, autologin_user, backend_svn, csrf_token):
703 self, autologin_user, backend_svn, csrf_token):
690 repo_name = backend_svn.repo_name
704 repo_name = backend_svn.repo_name
691 settings = SettingsModel(repo=repo_name)
705 settings = SettingsModel(repo=repo_name)
692 for name in VcsSettingsModel.GENERAL_SETTINGS:
706 for name in VcsSettingsModel.GENERAL_SETTINGS:
693 settings.create_or_update_setting(name, True, 'bool')
707 settings.create_or_update_setting(name, True, 'bool')
694
708
695 data = self.FORM_DATA.copy()
709 data = self.FORM_DATA.copy()
696 data['csrf_token'] = csrf_token
710 data['csrf_token'] = csrf_token
697 self.app.post(
711 self.app.post(
698 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
712 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
699 try:
713 try:
700 for name in VcsSettingsModel.GENERAL_SETTINGS:
714 for name in VcsSettingsModel.GENERAL_SETTINGS:
701 setting = settings.get_setting_by_name(name)
715 setting = settings.get_setting_by_name(name)
702 assert setting.app_settings_value is True
716 assert setting.app_settings_value is True
703 finally:
717 finally:
704 self._cleanup_repo_settings(settings)
718 self._cleanup_repo_settings(settings)
705
719
706 def test_svn_settings_are_created(
720 def test_svn_settings_are_created(
707 self, autologin_user, backend_svn, csrf_token, settings_util):
721 self, autologin_user, backend_svn, csrf_token, settings_util):
708 repo_name = backend_svn.repo_name
722 repo_name = backend_svn.repo_name
709 data = self.FORM_DATA.copy()
723 data = self.FORM_DATA.copy()
710 data['new_svn_tag'] = 'svn-tag'
724 data['new_svn_tag'] = 'svn-tag'
711 data['new_svn_branch'] = 'svn-branch'
725 data['new_svn_branch'] = 'svn-branch'
712 data['csrf_token'] = csrf_token
726 data['csrf_token'] = csrf_token
713
727
714 # Create few global settings to make sure that uniqueness validators
728 # Create few global settings to make sure that uniqueness validators
715 # are not triggered
729 # are not triggered
716 settings_util.create_rhodecode_ui(
730 settings_util.create_rhodecode_ui(
717 VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
731 VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
718 settings_util.create_rhodecode_ui(
732 settings_util.create_rhodecode_ui(
719 VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag')
733 VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag')
720
734
721 self.app.post(
735 self.app.post(
722 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
736 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
723 settings = SettingsModel(repo=repo_name)
737 settings = SettingsModel(repo=repo_name)
724 try:
738 try:
725 svn_branches = settings.get_ui_by_section(
739 svn_branches = settings.get_ui_by_section(
726 VcsSettingsModel.SVN_BRANCH_SECTION)
740 VcsSettingsModel.SVN_BRANCH_SECTION)
727 svn_branch_names = [b.ui_value for b in svn_branches]
741 svn_branch_names = [b.ui_value for b in svn_branches]
728 svn_tags = settings.get_ui_by_section(
742 svn_tags = settings.get_ui_by_section(
729 VcsSettingsModel.SVN_TAG_SECTION)
743 VcsSettingsModel.SVN_TAG_SECTION)
730 svn_tag_names = [b.ui_value for b in svn_tags]
744 svn_tag_names = [b.ui_value for b in svn_tags]
731 assert 'svn-branch' in svn_branch_names
745 assert 'svn-branch' in svn_branch_names
732 assert 'svn-tag' in svn_tag_names
746 assert 'svn-tag' in svn_tag_names
733 finally:
747 finally:
734 self._cleanup_repo_settings(settings)
748 self._cleanup_repo_settings(settings)
735
749
736 def test_svn_settings_are_unique(
750 def test_svn_settings_are_unique(
737 self, autologin_user, backend_svn, csrf_token, settings_util):
751 self, autologin_user, backend_svn, csrf_token, settings_util):
738 repo = backend_svn.repo
752 repo = backend_svn.repo
739 repo_name = repo.repo_name
753 repo_name = repo.repo_name
740 data = self.FORM_DATA.copy()
754 data = self.FORM_DATA.copy()
741 data['new_svn_tag'] = 'test_tag'
755 data['new_svn_tag'] = 'test_tag'
742 data['new_svn_branch'] = 'test_branch'
756 data['new_svn_branch'] = 'test_branch'
743 data['csrf_token'] = csrf_token
757 data['csrf_token'] = csrf_token
744 settings_util.create_repo_rhodecode_ui(
758 settings_util.create_repo_rhodecode_ui(
745 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch')
759 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch')
746 settings_util.create_repo_rhodecode_ui(
760 settings_util.create_repo_rhodecode_ui(
747 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag')
761 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag')
748
762
749 response = self.app.post(
763 response = self.app.post(
750 url('repo_vcs_settings', repo_name=repo_name), data, status=200)
764 url('repo_vcs_settings', repo_name=repo_name), data, status=200)
751 response.mustcontain('Pattern already exists')
765 response.mustcontain('Pattern already exists')
752
766
753 def test_svn_settings_with_empty_values_are_not_created(
767 def test_svn_settings_with_empty_values_are_not_created(
754 self, autologin_user, backend_svn, csrf_token):
768 self, autologin_user, backend_svn, csrf_token):
755 repo_name = backend_svn.repo_name
769 repo_name = backend_svn.repo_name
756 data = self.FORM_DATA.copy()
770 data = self.FORM_DATA.copy()
757 data['csrf_token'] = csrf_token
771 data['csrf_token'] = csrf_token
758 self.app.post(
772 self.app.post(
759 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
773 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
760 settings = SettingsModel(repo=repo_name)
774 settings = SettingsModel(repo=repo_name)
761 try:
775 try:
762 svn_branches = settings.get_ui_by_section(
776 svn_branches = settings.get_ui_by_section(
763 VcsSettingsModel.SVN_BRANCH_SECTION)
777 VcsSettingsModel.SVN_BRANCH_SECTION)
764 svn_tags = settings.get_ui_by_section(
778 svn_tags = settings.get_ui_by_section(
765 VcsSettingsModel.SVN_TAG_SECTION)
779 VcsSettingsModel.SVN_TAG_SECTION)
766 assert len(svn_branches) == 0
780 assert len(svn_branches) == 0
767 assert len(svn_tags) == 0
781 assert len(svn_tags) == 0
768 finally:
782 finally:
769 self._cleanup_repo_settings(settings)
783 self._cleanup_repo_settings(settings)
770
784
771 def test_svn_settings_are_shown_for_svn_repository(
785 def test_svn_settings_are_shown_for_svn_repository(
772 self, autologin_user, backend_svn, csrf_token):
786 self, autologin_user, backend_svn, csrf_token):
773 repo_name = backend_svn.repo_name
787 repo_name = backend_svn.repo_name
774 response = self.app.get(
788 response = self.app.get(
775 url('repo_vcs_settings', repo_name=repo_name), status=200)
789 url('repo_vcs_settings', repo_name=repo_name), status=200)
776 response.mustcontain('Subversion Settings')
790 response.mustcontain('Subversion Settings')
777
791
778 @pytest.mark.skip_backends('svn')
792 @pytest.mark.skip_backends('svn')
779 def test_svn_settings_are_not_created_for_not_svn_repository(
793 def test_svn_settings_are_not_created_for_not_svn_repository(
780 self, autologin_user, backend, csrf_token):
794 self, autologin_user, backend, csrf_token):
781 repo_name = backend.repo_name
795 repo_name = backend.repo_name
782 data = self.FORM_DATA.copy()
796 data = self.FORM_DATA.copy()
783 data['csrf_token'] = csrf_token
797 data['csrf_token'] = csrf_token
784 self.app.post(
798 self.app.post(
785 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
799 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
786 settings = SettingsModel(repo=repo_name)
800 settings = SettingsModel(repo=repo_name)
787 try:
801 try:
788 svn_branches = settings.get_ui_by_section(
802 svn_branches = settings.get_ui_by_section(
789 VcsSettingsModel.SVN_BRANCH_SECTION)
803 VcsSettingsModel.SVN_BRANCH_SECTION)
790 svn_tags = settings.get_ui_by_section(
804 svn_tags = settings.get_ui_by_section(
791 VcsSettingsModel.SVN_TAG_SECTION)
805 VcsSettingsModel.SVN_TAG_SECTION)
792 assert len(svn_branches) == 0
806 assert len(svn_branches) == 0
793 assert len(svn_tags) == 0
807 assert len(svn_tags) == 0
794 finally:
808 finally:
795 self._cleanup_repo_settings(settings)
809 self._cleanup_repo_settings(settings)
796
810
797 @pytest.mark.skip_backends('svn')
811 @pytest.mark.skip_backends('svn')
798 def test_svn_settings_are_shown_only_for_svn_repository(
812 def test_svn_settings_are_shown_only_for_svn_repository(
799 self, autologin_user, backend, csrf_token):
813 self, autologin_user, backend, csrf_token):
800 repo_name = backend.repo_name
814 repo_name = backend.repo_name
801 response = self.app.get(
815 response = self.app.get(
802 url('repo_vcs_settings', repo_name=repo_name), status=200)
816 url('repo_vcs_settings', repo_name=repo_name), status=200)
803 response.mustcontain(no='Subversion Settings')
817 response.mustcontain(no='Subversion Settings')
804
818
805 def test_hg_settings_are_created(
819 def test_hg_settings_are_created(
806 self, autologin_user, backend_hg, csrf_token):
820 self, autologin_user, backend_hg, csrf_token):
807 repo_name = backend_hg.repo_name
821 repo_name = backend_hg.repo_name
808 data = self.FORM_DATA.copy()
822 data = self.FORM_DATA.copy()
809 data['new_svn_tag'] = 'svn-tag'
823 data['new_svn_tag'] = 'svn-tag'
810 data['new_svn_branch'] = 'svn-branch'
824 data['new_svn_branch'] = 'svn-branch'
811 data['csrf_token'] = csrf_token
825 data['csrf_token'] = csrf_token
812 self.app.post(
826 self.app.post(
813 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
827 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
814 settings = SettingsModel(repo=repo_name)
828 settings = SettingsModel(repo=repo_name)
815 try:
829 try:
816 largefiles_ui = settings.get_ui_by_section_and_key(
830 largefiles_ui = settings.get_ui_by_section_and_key(
817 'extensions', 'largefiles')
831 'extensions', 'largefiles')
818 assert largefiles_ui.ui_active is False
832 assert largefiles_ui.ui_active is False
819 phases_ui = settings.get_ui_by_section_and_key(
833 phases_ui = settings.get_ui_by_section_and_key(
820 'phases', 'publish')
834 'phases', 'publish')
821 assert str2bool(phases_ui.ui_value) is False
835 assert str2bool(phases_ui.ui_value) is False
822 finally:
836 finally:
823 self._cleanup_repo_settings(settings)
837 self._cleanup_repo_settings(settings)
824
838
825 def test_hg_settings_are_updated(
839 def test_hg_settings_are_updated(
826 self, autologin_user, backend_hg, csrf_token):
840 self, autologin_user, backend_hg, csrf_token):
827 repo_name = backend_hg.repo_name
841 repo_name = backend_hg.repo_name
828 settings = SettingsModel(repo=repo_name)
842 settings = SettingsModel(repo=repo_name)
829 settings.create_ui_section_value(
843 settings.create_ui_section_value(
830 'extensions', '', key='largefiles', active=True)
844 'extensions', '', key='largefiles', active=True)
831 settings.create_ui_section_value(
845 settings.create_ui_section_value(
832 'phases', '1', key='publish', active=True)
846 'phases', '1', key='publish', active=True)
833
847
834 data = self.FORM_DATA.copy()
848 data = self.FORM_DATA.copy()
835 data['csrf_token'] = csrf_token
849 data['csrf_token'] = csrf_token
836 self.app.post(
850 self.app.post(
837 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
851 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
838 try:
852 try:
839 largefiles_ui = settings.get_ui_by_section_and_key(
853 largefiles_ui = settings.get_ui_by_section_and_key(
840 'extensions', 'largefiles')
854 'extensions', 'largefiles')
841 assert largefiles_ui.ui_active is False
855 assert largefiles_ui.ui_active is False
842 phases_ui = settings.get_ui_by_section_and_key(
856 phases_ui = settings.get_ui_by_section_and_key(
843 'phases', 'publish')
857 'phases', 'publish')
844 assert str2bool(phases_ui.ui_value) is False
858 assert str2bool(phases_ui.ui_value) is False
845 finally:
859 finally:
846 self._cleanup_repo_settings(settings)
860 self._cleanup_repo_settings(settings)
847
861
848 def test_hg_settings_are_shown_for_hg_repository(
862 def test_hg_settings_are_shown_for_hg_repository(
849 self, autologin_user, backend_hg, csrf_token):
863 self, autologin_user, backend_hg, csrf_token):
850 repo_name = backend_hg.repo_name
864 repo_name = backend_hg.repo_name
851 response = self.app.get(
865 response = self.app.get(
852 url('repo_vcs_settings', repo_name=repo_name), status=200)
866 url('repo_vcs_settings', repo_name=repo_name), status=200)
853 response.mustcontain('Mercurial Settings')
867 response.mustcontain('Mercurial Settings')
854
868
855 @pytest.mark.skip_backends('hg')
869 @pytest.mark.skip_backends('hg')
856 def test_hg_settings_are_created_only_for_hg_repository(
870 def test_hg_settings_are_created_only_for_hg_repository(
857 self, autologin_user, backend, csrf_token):
871 self, autologin_user, backend, csrf_token):
858 repo_name = backend.repo_name
872 repo_name = backend.repo_name
859 data = self.FORM_DATA.copy()
873 data = self.FORM_DATA.copy()
860 data['csrf_token'] = csrf_token
874 data['csrf_token'] = csrf_token
861 self.app.post(
875 self.app.post(
862 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
876 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
863 settings = SettingsModel(repo=repo_name)
877 settings = SettingsModel(repo=repo_name)
864 try:
878 try:
865 largefiles_ui = settings.get_ui_by_section_and_key(
879 largefiles_ui = settings.get_ui_by_section_and_key(
866 'extensions', 'largefiles')
880 'extensions', 'largefiles')
867 assert largefiles_ui is None
881 assert largefiles_ui is None
868 phases_ui = settings.get_ui_by_section_and_key(
882 phases_ui = settings.get_ui_by_section_and_key(
869 'phases', 'publish')
883 'phases', 'publish')
870 assert phases_ui is None
884 assert phases_ui is None
871 finally:
885 finally:
872 self._cleanup_repo_settings(settings)
886 self._cleanup_repo_settings(settings)
873
887
874 @pytest.mark.skip_backends('hg')
888 @pytest.mark.skip_backends('hg')
875 def test_hg_settings_are_shown_only_for_hg_repository(
889 def test_hg_settings_are_shown_only_for_hg_repository(
876 self, autologin_user, backend, csrf_token):
890 self, autologin_user, backend, csrf_token):
877 repo_name = backend.repo_name
891 repo_name = backend.repo_name
878 response = self.app.get(
892 response = self.app.get(
879 url('repo_vcs_settings', repo_name=repo_name), status=200)
893 url('repo_vcs_settings', repo_name=repo_name), status=200)
880 response.mustcontain(no='Mercurial Settings')
894 response.mustcontain(no='Mercurial Settings')
881
895
882 @pytest.mark.skip_backends('hg')
896 @pytest.mark.skip_backends('hg')
883 def test_hg_settings_are_updated_only_for_hg_repository(
897 def test_hg_settings_are_updated_only_for_hg_repository(
884 self, autologin_user, backend, csrf_token):
898 self, autologin_user, backend, csrf_token):
885 repo_name = backend.repo_name
899 repo_name = backend.repo_name
886 settings = SettingsModel(repo=repo_name)
900 settings = SettingsModel(repo=repo_name)
887 settings.create_ui_section_value(
901 settings.create_ui_section_value(
888 'extensions', '', key='largefiles', active=True)
902 'extensions', '', key='largefiles', active=True)
889 settings.create_ui_section_value(
903 settings.create_ui_section_value(
890 'phases', '1', key='publish', active=True)
904 'phases', '1', key='publish', active=True)
891
905
892 data = self.FORM_DATA.copy()
906 data = self.FORM_DATA.copy()
893 data['csrf_token'] = csrf_token
907 data['csrf_token'] = csrf_token
894 self.app.post(
908 self.app.post(
895 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
909 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
896 try:
910 try:
897 largefiles_ui = settings.get_ui_by_section_and_key(
911 largefiles_ui = settings.get_ui_by_section_and_key(
898 'extensions', 'largefiles')
912 'extensions', 'largefiles')
899 assert largefiles_ui.ui_active is True
913 assert largefiles_ui.ui_active is True
900 phases_ui = settings.get_ui_by_section_and_key(
914 phases_ui = settings.get_ui_by_section_and_key(
901 'phases', 'publish')
915 'phases', 'publish')
902 assert phases_ui.ui_value == '1'
916 assert phases_ui.ui_value == '1'
903 finally:
917 finally:
904 self._cleanup_repo_settings(settings)
918 self._cleanup_repo_settings(settings)
905
919
906 def test_per_repo_svn_settings_are_displayed(
920 def test_per_repo_svn_settings_are_displayed(
907 self, autologin_user, backend_svn, settings_util):
921 self, autologin_user, backend_svn, settings_util):
908 repo = backend_svn.create_repo()
922 repo = backend_svn.create_repo()
909 repo_name = repo.repo_name
923 repo_name = repo.repo_name
910 branches = [
924 branches = [
911 settings_util.create_repo_rhodecode_ui(
925 settings_util.create_repo_rhodecode_ui(
912 repo, VcsSettingsModel.SVN_BRANCH_SECTION,
926 repo, VcsSettingsModel.SVN_BRANCH_SECTION,
913 'branch_{}'.format(i))
927 'branch_{}'.format(i))
914 for i in range(10)]
928 for i in range(10)]
915 tags = [
929 tags = [
916 settings_util.create_repo_rhodecode_ui(
930 settings_util.create_repo_rhodecode_ui(
917 repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i))
931 repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i))
918 for i in range(10)]
932 for i in range(10)]
919
933
920 response = self.app.get(
934 response = self.app.get(
921 url('repo_vcs_settings', repo_name=repo_name), status=200)
935 url('repo_vcs_settings', repo_name=repo_name), status=200)
922 assert_response = AssertResponse(response)
936 assert_response = AssertResponse(response)
923 for branch in branches:
937 for branch in branches:
924 css_selector = '[name=branch_value_{}]'.format(branch.ui_id)
938 css_selector = '[name=branch_value_{}]'.format(branch.ui_id)
925 element = assert_response.get_element(css_selector)
939 element = assert_response.get_element(css_selector)
926 assert element.value == branch.ui_value
940 assert element.value == branch.ui_value
927 for tag in tags:
941 for tag in tags:
928 css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id)
942 css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id)
929 element = assert_response.get_element(css_selector)
943 element = assert_response.get_element(css_selector)
930 assert element.value == tag.ui_value
944 assert element.value == tag.ui_value
931
945
932 def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn(
946 def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn(
933 self, autologin_user, backend_svn, settings_util):
947 self, autologin_user, backend_svn, settings_util):
934 repo = backend_svn.create_repo()
948 repo = backend_svn.create_repo()
935 repo_name = repo.repo_name
949 repo_name = repo.repo_name
936 response = self.app.get(
950 response = self.app.get(
937 url('repo_vcs_settings', repo_name=repo_name), status=200)
951 url('repo_vcs_settings', repo_name=repo_name), status=200)
938 response.mustcontain(no='<label>Hooks:</label>')
952 response.mustcontain(no='<label>Hooks:</label>')
939 response.mustcontain(no='<label>Pull Request Settings:</label>')
953 response.mustcontain(no='<label>Pull Request Settings:</label>')
940
954
941 def test_inherit_global_settings_value_is_saved(
955 def test_inherit_global_settings_value_is_saved(
942 self, autologin_user, backend, csrf_token):
956 self, autologin_user, backend, csrf_token):
943 repo_name = backend.repo_name
957 repo_name = backend.repo_name
944 data = self.FORM_DATA.copy()
958 data = self.FORM_DATA.copy()
945 data['csrf_token'] = csrf_token
959 data['csrf_token'] = csrf_token
946 data['inherit_global_settings'] = True
960 data['inherit_global_settings'] = True
947 self.app.post(
961 self.app.post(
948 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
962 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
949
963
950 settings = SettingsModel(repo=repo_name)
964 settings = SettingsModel(repo=repo_name)
951 vcs_settings = VcsSettingsModel(repo=repo_name)
965 vcs_settings = VcsSettingsModel(repo=repo_name)
952 try:
966 try:
953 assert vcs_settings.inherit_global_settings is True
967 assert vcs_settings.inherit_global_settings is True
954 finally:
968 finally:
955 self._cleanup_repo_settings(settings)
969 self._cleanup_repo_settings(settings)
956
970
957 def test_repo_cache_is_invalidated_when_settings_are_updated(
971 def test_repo_cache_is_invalidated_when_settings_are_updated(
958 self, autologin_user, backend, csrf_token):
972 self, autologin_user, backend, csrf_token):
959 repo_name = backend.repo_name
973 repo_name = backend.repo_name
960 data = self.FORM_DATA.copy()
974 data = self.FORM_DATA.copy()
961 data['csrf_token'] = csrf_token
975 data['csrf_token'] = csrf_token
962 data['inherit_global_settings'] = True
976 data['inherit_global_settings'] = True
963 settings = SettingsModel(repo=repo_name)
977 settings = SettingsModel(repo=repo_name)
964
978
965 invalidation_patcher = mock.patch(
979 invalidation_patcher = mock.patch(
966 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
980 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
967 with invalidation_patcher as invalidation_mock:
981 with invalidation_patcher as invalidation_mock:
968 self.app.post(
982 self.app.post(
969 url('repo_vcs_settings', repo_name=repo_name), data,
983 url('repo_vcs_settings', repo_name=repo_name), data,
970 status=302)
984 status=302)
971 try:
985 try:
972 invalidation_mock.assert_called_once_with(repo_name, delete=True)
986 invalidation_mock.assert_called_once_with(repo_name, delete=True)
973 finally:
987 finally:
974 self._cleanup_repo_settings(settings)
988 self._cleanup_repo_settings(settings)
975
989
976 def test_other_settings_not_saved_inherit_global_settings_is_true(
990 def test_other_settings_not_saved_inherit_global_settings_is_true(
977 self, autologin_user, backend, csrf_token):
991 self, autologin_user, backend, csrf_token):
978 repo_name = backend.repo_name
992 repo_name = backend.repo_name
979 data = self.FORM_DATA.copy()
993 data = self.FORM_DATA.copy()
980 data['csrf_token'] = csrf_token
994 data['csrf_token'] = csrf_token
981 data['inherit_global_settings'] = True
995 data['inherit_global_settings'] = True
982 self.app.post(
996 self.app.post(
983 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
997 url('repo_vcs_settings', repo_name=repo_name), data, status=302)
984
998
985 settings = SettingsModel(repo=repo_name)
999 settings = SettingsModel(repo=repo_name)
986 ui_settings = (
1000 ui_settings = (
987 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1001 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
988
1002
989 vcs_settings = []
1003 vcs_settings = []
990 try:
1004 try:
991 for section, key in ui_settings:
1005 for section, key in ui_settings:
992 ui = settings.get_ui_by_section_and_key(section, key)
1006 ui = settings.get_ui_by_section_and_key(section, key)
993 if ui:
1007 if ui:
994 vcs_settings.append(ui)
1008 vcs_settings.append(ui)
995 vcs_settings.extend(settings.get_ui_by_section(
1009 vcs_settings.extend(settings.get_ui_by_section(
996 VcsSettingsModel.SVN_BRANCH_SECTION))
1010 VcsSettingsModel.SVN_BRANCH_SECTION))
997 vcs_settings.extend(settings.get_ui_by_section(
1011 vcs_settings.extend(settings.get_ui_by_section(
998 VcsSettingsModel.SVN_TAG_SECTION))
1012 VcsSettingsModel.SVN_TAG_SECTION))
999 for name in VcsSettingsModel.GENERAL_SETTINGS:
1013 for name in VcsSettingsModel.GENERAL_SETTINGS:
1000 setting = settings.get_setting_by_name(name)
1014 setting = settings.get_setting_by_name(name)
1001 if setting:
1015 if setting:
1002 vcs_settings.append(setting)
1016 vcs_settings.append(setting)
1003 assert vcs_settings == []
1017 assert vcs_settings == []
1004 finally:
1018 finally:
1005 self._cleanup_repo_settings(settings)
1019 self._cleanup_repo_settings(settings)
1006
1020
1007 def test_delete_svn_branch_and_tag_patterns(
1021 def test_delete_svn_branch_and_tag_patterns(
1008 self, autologin_user, backend_svn, settings_util, csrf_token):
1022 self, autologin_user, backend_svn, settings_util, csrf_token):
1009 repo = backend_svn.create_repo()
1023 repo = backend_svn.create_repo()
1010 repo_name = repo.repo_name
1024 repo_name = repo.repo_name
1011 branch = settings_util.create_repo_rhodecode_ui(
1025 branch = settings_util.create_repo_rhodecode_ui(
1012 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1026 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1013 cleanup=False)
1027 cleanup=False)
1014 tag = settings_util.create_repo_rhodecode_ui(
1028 tag = settings_util.create_repo_rhodecode_ui(
1015 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False)
1029 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False)
1016 data = {
1030 data = {
1017 '_method': 'delete',
1031 '_method': 'delete',
1018 'csrf_token': csrf_token
1032 'csrf_token': csrf_token
1019 }
1033 }
1020 for id_ in (branch.ui_id, tag.ui_id):
1034 for id_ in (branch.ui_id, tag.ui_id):
1021 data['delete_svn_pattern'] = id_,
1035 data['delete_svn_pattern'] = id_,
1022 self.app.post(
1036 self.app.post(
1023 url('repo_vcs_settings', repo_name=repo_name), data,
1037 url('repo_vcs_settings', repo_name=repo_name), data,
1024 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1038 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1025 settings = VcsSettingsModel(repo=repo_name)
1039 settings = VcsSettingsModel(repo=repo_name)
1026 assert settings.get_repo_svn_branch_patterns() == []
1040 assert settings.get_repo_svn_branch_patterns() == []
1027
1041
1028 def test_delete_svn_branch_requires_repo_admin_permission(
1042 def test_delete_svn_branch_requires_repo_admin_permission(
1029 self, backend_svn, user_util, settings_util, csrf_token):
1043 self, backend_svn, user_util, settings_util, csrf_token):
1030 repo = backend_svn.create_repo()
1044 repo = backend_svn.create_repo()
1031 repo_name = repo.repo_name
1045 repo_name = repo.repo_name
1032
1046
1033 logout_user_session(self.app, csrf_token)
1047 logout_user_session(self.app, csrf_token)
1034 session = login_user_session(
1048 session = login_user_session(
1035 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
1049 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
1036 csrf_token = auth.get_csrf_token(session)
1050 csrf_token = auth.get_csrf_token(session)
1037
1051
1038 repo = Repository.get_by_repo_name(repo_name)
1052 repo = Repository.get_by_repo_name(repo_name)
1039 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1053 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1040 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
1054 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
1041 branch = settings_util.create_repo_rhodecode_ui(
1055 branch = settings_util.create_repo_rhodecode_ui(
1042 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1056 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
1043 cleanup=False)
1057 cleanup=False)
1044 data = {
1058 data = {
1045 '_method': 'delete',
1059 '_method': 'delete',
1046 'csrf_token': csrf_token,
1060 'csrf_token': csrf_token,
1047 'delete_svn_pattern': branch.ui_id
1061 'delete_svn_pattern': branch.ui_id
1048 }
1062 }
1049 self.app.post(
1063 self.app.post(
1050 url('repo_vcs_settings', repo_name=repo_name), data,
1064 url('repo_vcs_settings', repo_name=repo_name), data,
1051 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1065 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
1052
1066
1053 def test_delete_svn_branch_raises_400_when_not_found(
1067 def test_delete_svn_branch_raises_400_when_not_found(
1054 self, autologin_user, backend_svn, settings_util, csrf_token):
1068 self, autologin_user, backend_svn, settings_util, csrf_token):
1055 repo_name = backend_svn.repo_name
1069 repo_name = backend_svn.repo_name
1056 data = {
1070 data = {
1057 '_method': 'delete',
1071 '_method': 'delete',
1058 'delete_svn_pattern': 123,
1072 'delete_svn_pattern': 123,
1059 'csrf_token': csrf_token
1073 'csrf_token': csrf_token
1060 }
1074 }
1061 self.app.post(
1075 self.app.post(
1062 url('repo_vcs_settings', repo_name=repo_name), data,
1076 url('repo_vcs_settings', repo_name=repo_name), data,
1063 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1077 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1064
1078
1065 def test_delete_svn_branch_raises_400_when_no_id_specified(
1079 def test_delete_svn_branch_raises_400_when_no_id_specified(
1066 self, autologin_user, backend_svn, settings_util, csrf_token):
1080 self, autologin_user, backend_svn, settings_util, csrf_token):
1067 repo_name = backend_svn.repo_name
1081 repo_name = backend_svn.repo_name
1068 data = {
1082 data = {
1069 '_method': 'delete',
1083 '_method': 'delete',
1070 'csrf_token': csrf_token
1084 'csrf_token': csrf_token
1071 }
1085 }
1072 self.app.post(
1086 self.app.post(
1073 url('repo_vcs_settings', repo_name=repo_name), data,
1087 url('repo_vcs_settings', repo_name=repo_name), data,
1074 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1088 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=400)
1075
1089
1076 def _cleanup_repo_settings(self, settings_model):
1090 def _cleanup_repo_settings(self, settings_model):
1077 cleanup = []
1091 cleanup = []
1078 ui_settings = (
1092 ui_settings = (
1079 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1093 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
1080
1094
1081 for section, key in ui_settings:
1095 for section, key in ui_settings:
1082 ui = settings_model.get_ui_by_section_and_key(section, key)
1096 ui = settings_model.get_ui_by_section_and_key(section, key)
1083 if ui:
1097 if ui:
1084 cleanup.append(ui)
1098 cleanup.append(ui)
1085
1099
1086 cleanup.extend(settings_model.get_ui_by_section(
1100 cleanup.extend(settings_model.get_ui_by_section(
1087 VcsSettingsModel.INHERIT_SETTINGS))
1101 VcsSettingsModel.INHERIT_SETTINGS))
1088 cleanup.extend(settings_model.get_ui_by_section(
1102 cleanup.extend(settings_model.get_ui_by_section(
1089 VcsSettingsModel.SVN_BRANCH_SECTION))
1103 VcsSettingsModel.SVN_BRANCH_SECTION))
1090 cleanup.extend(settings_model.get_ui_by_section(
1104 cleanup.extend(settings_model.get_ui_by_section(
1091 VcsSettingsModel.SVN_TAG_SECTION))
1105 VcsSettingsModel.SVN_TAG_SECTION))
1092
1106
1093 for name in VcsSettingsModel.GENERAL_SETTINGS:
1107 for name in VcsSettingsModel.GENERAL_SETTINGS:
1094 setting = settings_model.get_setting_by_name(name)
1108 setting = settings_model.get_setting_by_name(name)
1095 if setting:
1109 if setting:
1096 cleanup.append(setting)
1110 cleanup.append(setting)
1097
1111
1098 for object_ in cleanup:
1112 for object_ in cleanup:
1099 Session().delete(object_)
1113 Session().delete(object_)
1100 Session().commit()
1114 Session().commit()
1101
1115
1102 def assert_repo_value_equals_global_value(self, response, setting):
1116 def assert_repo_value_equals_global_value(self, response, setting):
1103 assert_response = AssertResponse(response)
1117 assert_response = AssertResponse(response)
1104 global_css_selector = '[name={}_inherited]'.format(setting)
1118 global_css_selector = '[name={}_inherited]'.format(setting)
1105 repo_css_selector = '[name={}]'.format(setting)
1119 repo_css_selector = '[name={}]'.format(setting)
1106 repo_element = assert_response.get_element(repo_css_selector)
1120 repo_element = assert_response.get_element(repo_css_selector)
1107 global_element = assert_response.get_element(global_css_selector)
1121 global_element = assert_response.get_element(global_css_selector)
1108 assert repo_element.value == global_element.value
1122 assert repo_element.value == global_element.value
1109
1123
1110
1124
1111 def _get_permission_for_user(user, repo):
1125 def _get_permission_for_user(user, repo):
1112 perm = UserRepoToPerm.query()\
1126 perm = UserRepoToPerm.query()\
1113 .filter(UserRepoToPerm.repository ==
1127 .filter(UserRepoToPerm.repository ==
1114 Repository.get_by_repo_name(repo))\
1128 Repository.get_by_repo_name(repo))\
1115 .filter(UserRepoToPerm.user == User.get_by_username(user))\
1129 .filter(UserRepoToPerm.user == User.get_by_username(user))\
1116 .all()
1130 .all()
1117 return perm
1131 return perm
@@ -1,273 +1,288 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.tests import *
23 from rhodecode.tests import *
24 from rhodecode.tests.fixture import Fixture
24 from rhodecode.tests.fixture import Fixture
25 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
26
26
27 from rhodecode.model.db import Repository
27 from rhodecode.model.db import Repository
28 from rhodecode.model.repo import RepoModel
28 from rhodecode.model.repo import RepoModel
29 from rhodecode.model.user import UserModel
29 from rhodecode.model.user import UserModel
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31
31
32 fixture = Fixture()
32 fixture = Fixture()
33
33
34
34
35 def route_path(name, params=None, **kwargs):
36 import urllib
37
38 base_url = {
39 'repo_summary': '/{repo_name}',
40 'repo_creating_check': '/{repo_name}/repo_creating_check',
41 }[name].format(**kwargs)
42
43 if params:
44 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
45 return base_url
46
47
35 class _BaseTest(TestController):
48 class _BaseTest(TestController):
36
49
37 REPO = None
50 REPO = None
38 REPO_TYPE = None
51 REPO_TYPE = None
39 NEW_REPO = None
52 NEW_REPO = None
40 REPO_FORK = None
53 REPO_FORK = None
41
54
42 @pytest.fixture(autouse=True)
55 @pytest.fixture(autouse=True)
43 def prepare(self, request, pylonsapp):
56 def prepare(self, request, pylonsapp):
44 self.username = u'forkuser'
57 self.username = u'forkuser'
45 self.password = u'qweqwe'
58 self.password = u'qweqwe'
46 self.u1 = fixture.create_user(self.username, password=self.password,
59 self.u1 = fixture.create_user(self.username, password=self.password,
47 email=u'fork_king@rhodecode.org')
60 email=u'fork_king@rhodecode.org')
48 Session().commit()
61 Session().commit()
49 self.u1id = self.u1.user_id
62 self.u1id = self.u1.user_id
50 request.addfinalizer(self.cleanup)
63 request.addfinalizer(self.cleanup)
51
64
52 def cleanup(self):
65 def cleanup(self):
53 u1 = UserModel().get(self.u1id)
66 u1 = UserModel().get(self.u1id)
54 Session().delete(u1)
67 Session().delete(u1)
55 Session().commit()
68 Session().commit()
56
69
57 def test_index(self):
70 def test_index(self):
58 self.log_user()
71 self.log_user()
59 repo_name = self.REPO
72 repo_name = self.REPO
60 response = self.app.get(url(controller='forks', action='forks',
73 response = self.app.get(url(controller='forks', action='forks',
61 repo_name=repo_name))
74 repo_name=repo_name))
62
75
63 response.mustcontain("""There are no forks yet""")
76 response.mustcontain("""There are no forks yet""")
64
77
65 def test_no_permissions_to_fork(self):
78 def test_no_permissions_to_fork(self):
66 usr = self.log_user(TEST_USER_REGULAR_LOGIN,
79 usr = self.log_user(TEST_USER_REGULAR_LOGIN,
67 TEST_USER_REGULAR_PASS)['user_id']
80 TEST_USER_REGULAR_PASS)['user_id']
68 user_model = UserModel()
81 user_model = UserModel()
69 user_model.revoke_perm(usr, 'hg.fork.repository')
82 user_model.revoke_perm(usr, 'hg.fork.repository')
70 user_model.grant_perm(usr, 'hg.fork.none')
83 user_model.grant_perm(usr, 'hg.fork.none')
71 u = UserModel().get(usr)
84 u = UserModel().get(usr)
72 u.inherit_default_permissions = False
85 u.inherit_default_permissions = False
73 Session().commit()
86 Session().commit()
74 # try create a fork
87 # try create a fork
75 repo_name = self.REPO
88 repo_name = self.REPO
76 self.app.post(
89 self.app.post(
77 url(controller='forks', action='fork_create', repo_name=repo_name),
90 url(controller='forks', action='fork_create', repo_name=repo_name),
78 {'csrf_token': self.csrf_token}, status=404)
91 {'csrf_token': self.csrf_token}, status=404)
79
92
80 def test_index_with_fork(self):
93 def test_index_with_fork(self):
81 self.log_user()
94 self.log_user()
82
95
83 # create a fork
96 # create a fork
84 fork_name = self.REPO_FORK
97 fork_name = self.REPO_FORK
85 description = 'fork of vcs test'
98 description = 'fork of vcs test'
86 repo_name = self.REPO
99 repo_name = self.REPO
87 source_repo = Repository.get_by_repo_name(repo_name)
100 source_repo = Repository.get_by_repo_name(repo_name)
88 creation_args = {
101 creation_args = {
89 'repo_name': fork_name,
102 'repo_name': fork_name,
90 'repo_group': '',
103 'repo_group': '',
91 'fork_parent_id': source_repo.repo_id,
104 'fork_parent_id': source_repo.repo_id,
92 'repo_type': self.REPO_TYPE,
105 'repo_type': self.REPO_TYPE,
93 'description': description,
106 'description': description,
94 'private': 'False',
107 'private': 'False',
95 'landing_rev': 'rev:tip',
108 'landing_rev': 'rev:tip',
96 'csrf_token': self.csrf_token,
109 'csrf_token': self.csrf_token,
97 }
110 }
98
111
99 self.app.post(url(controller='forks', action='fork_create',
112 self.app.post(url(controller='forks', action='fork_create',
100 repo_name=repo_name), creation_args)
113 repo_name=repo_name), creation_args)
101
114
102 response = self.app.get(url(controller='forks', action='forks',
115 response = self.app.get(url(controller='forks', action='forks',
103 repo_name=repo_name))
116 repo_name=repo_name))
104
117
105 response.mustcontain(
118 response.mustcontain(
106 """<a href="/%s">%s</a>""" % (fork_name, fork_name)
119 """<a href="/%s">%s</a>""" % (fork_name, fork_name)
107 )
120 )
108
121
109 # remove this fork
122 # remove this fork
110 fixture.destroy_repo(fork_name)
123 fixture.destroy_repo(fork_name)
111
124
112 def test_fork_create_into_group(self):
125 def test_fork_create_into_group(self):
113 self.log_user()
126 self.log_user()
114 group = fixture.create_repo_group('vc')
127 group = fixture.create_repo_group('vc')
115 group_id = group.group_id
128 group_id = group.group_id
116 fork_name = self.REPO_FORK
129 fork_name = self.REPO_FORK
117 fork_name_full = 'vc/%s' % fork_name
130 fork_name_full = 'vc/%s' % fork_name
118 description = 'fork of vcs test'
131 description = 'fork of vcs test'
119 repo_name = self.REPO
132 repo_name = self.REPO
120 source_repo = Repository.get_by_repo_name(repo_name)
133 source_repo = Repository.get_by_repo_name(repo_name)
121 creation_args = {
134 creation_args = {
122 'repo_name': fork_name,
135 'repo_name': fork_name,
123 'repo_group': group_id,
136 'repo_group': group_id,
124 'fork_parent_id': source_repo.repo_id,
137 'fork_parent_id': source_repo.repo_id,
125 'repo_type': self.REPO_TYPE,
138 'repo_type': self.REPO_TYPE,
126 'description': description,
139 'description': description,
127 'private': 'False',
140 'private': 'False',
128 'landing_rev': 'rev:tip',
141 'landing_rev': 'rev:tip',
129 'csrf_token': self.csrf_token,
142 'csrf_token': self.csrf_token,
130 }
143 }
131 self.app.post(url(controller='forks', action='fork_create',
144 self.app.post(url(controller='forks', action='fork_create',
132 repo_name=repo_name), creation_args)
145 repo_name=repo_name), creation_args)
133 repo = Repository.get_by_repo_name(fork_name_full)
146 repo = Repository.get_by_repo_name(fork_name_full)
134 assert repo.fork.repo_name == self.REPO
147 assert repo.fork.repo_name == self.REPO
135
148
136 # run the check page that triggers the flash message
149 # run the check page that triggers the flash message
137 response = self.app.get(url('repo_check_home', repo_name=fork_name_full))
150 response = self.app.get(
151 route_path('repo_creating_check', repo_name=fork_name_full))
138 # test if we have a message that fork is ok
152 # test if we have a message that fork is ok
139 assert_session_flash(response,
153 assert_session_flash(response,
140 'Forked repository %s as <a href="/%s">%s</a>'
154 'Forked repository %s as <a href="/%s">%s</a>'
141 % (repo_name, fork_name_full, fork_name_full))
155 % (repo_name, fork_name_full, fork_name_full))
142
156
143 # test if the fork was created in the database
157 # test if the fork was created in the database
144 fork_repo = Session().query(Repository)\
158 fork_repo = Session().query(Repository)\
145 .filter(Repository.repo_name == fork_name_full).one()
159 .filter(Repository.repo_name == fork_name_full).one()
146
160
147 assert fork_repo.repo_name == fork_name_full
161 assert fork_repo.repo_name == fork_name_full
148 assert fork_repo.fork.repo_name == repo_name
162 assert fork_repo.fork.repo_name == repo_name
149
163
150 # test if the repository is visible in the list ?
164 # test if the repository is visible in the list ?
151 response = self.app.get(h.route_path('repo_summary', repo_name=fork_name_full))
165 response = self.app.get(h.route_path('repo_summary', repo_name=fork_name_full))
152 response.mustcontain(fork_name_full)
166 response.mustcontain(fork_name_full)
153 response.mustcontain(self.REPO_TYPE)
167 response.mustcontain(self.REPO_TYPE)
154
168
155 response.mustcontain('Fork of')
169 response.mustcontain('Fork of')
156 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
170 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
157
171
158 fixture.destroy_repo(fork_name_full)
172 fixture.destroy_repo(fork_name_full)
159 fixture.destroy_repo_group(group_id)
173 fixture.destroy_repo_group(group_id)
160
174
161 def test_z_fork_create(self):
175 def test_z_fork_create(self):
162 self.log_user()
176 self.log_user()
163 fork_name = self.REPO_FORK
177 fork_name = self.REPO_FORK
164 description = 'fork of vcs test'
178 description = 'fork of vcs test'
165 repo_name = self.REPO
179 repo_name = self.REPO
166 source_repo = Repository.get_by_repo_name(repo_name)
180 source_repo = Repository.get_by_repo_name(repo_name)
167 creation_args = {
181 creation_args = {
168 'repo_name': fork_name,
182 'repo_name': fork_name,
169 'repo_group': '',
183 'repo_group': '',
170 'fork_parent_id': source_repo.repo_id,
184 'fork_parent_id': source_repo.repo_id,
171 'repo_type': self.REPO_TYPE,
185 'repo_type': self.REPO_TYPE,
172 'description': description,
186 'description': description,
173 'private': 'False',
187 'private': 'False',
174 'landing_rev': 'rev:tip',
188 'landing_rev': 'rev:tip',
175 'csrf_token': self.csrf_token,
189 'csrf_token': self.csrf_token,
176 }
190 }
177 self.app.post(url(controller='forks', action='fork_create',
191 self.app.post(url(controller='forks', action='fork_create',
178 repo_name=repo_name), creation_args)
192 repo_name=repo_name), creation_args)
179 repo = Repository.get_by_repo_name(self.REPO_FORK)
193 repo = Repository.get_by_repo_name(self.REPO_FORK)
180 assert repo.fork.repo_name == self.REPO
194 assert repo.fork.repo_name == self.REPO
181
195
182 # run the check page that triggers the flash message
196 # run the check page that triggers the flash message
183 response = self.app.get(url('repo_check_home', repo_name=fork_name))
197 response = self.app.get(
198 route_path('repo_creating_check', repo_name=fork_name))
184 # test if we have a message that fork is ok
199 # test if we have a message that fork is ok
185 assert_session_flash(response,
200 assert_session_flash(response,
186 'Forked repository %s as <a href="/%s">%s</a>'
201 'Forked repository %s as <a href="/%s">%s</a>'
187 % (repo_name, fork_name, fork_name))
202 % (repo_name, fork_name, fork_name))
188
203
189 # test if the fork was created in the database
204 # test if the fork was created in the database
190 fork_repo = Session().query(Repository)\
205 fork_repo = Session().query(Repository)\
191 .filter(Repository.repo_name == fork_name).one()
206 .filter(Repository.repo_name == fork_name).one()
192
207
193 assert fork_repo.repo_name == fork_name
208 assert fork_repo.repo_name == fork_name
194 assert fork_repo.fork.repo_name == repo_name
209 assert fork_repo.fork.repo_name == repo_name
195
210
196 # test if the repository is visible in the list ?
211 # test if the repository is visible in the list ?
197 response = self.app.get(h.route_path('repo_summary', repo_name=fork_name))
212 response = self.app.get(h.route_path('repo_summary', repo_name=fork_name))
198 response.mustcontain(fork_name)
213 response.mustcontain(fork_name)
199 response.mustcontain(self.REPO_TYPE)
214 response.mustcontain(self.REPO_TYPE)
200 response.mustcontain('Fork of')
215 response.mustcontain('Fork of')
201 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
216 response.mustcontain('<a href="/%s">%s</a>' % (repo_name, repo_name))
202
217
203 def test_zz_fork_permission_page(self):
218 def test_zz_fork_permission_page(self):
204 usr = self.log_user(self.username, self.password)['user_id']
219 usr = self.log_user(self.username, self.password)['user_id']
205 repo_name = self.REPO
220 repo_name = self.REPO
206
221
207 forks = Repository.query()\
222 forks = Repository.query()\
208 .filter(Repository.repo_type == self.REPO_TYPE)\
223 .filter(Repository.repo_type == self.REPO_TYPE)\
209 .filter(Repository.fork_id != None).all()
224 .filter(Repository.fork_id != None).all()
210 assert 1 == len(forks)
225 assert 1 == len(forks)
211
226
212 # set read permissions for this
227 # set read permissions for this
213 RepoModel().grant_user_permission(repo=forks[0],
228 RepoModel().grant_user_permission(repo=forks[0],
214 user=usr,
229 user=usr,
215 perm='repository.read')
230 perm='repository.read')
216 Session().commit()
231 Session().commit()
217
232
218 response = self.app.get(url(controller='forks', action='forks',
233 response = self.app.get(url(controller='forks', action='forks',
219 repo_name=repo_name))
234 repo_name=repo_name))
220
235
221 response.mustcontain('fork of vcs test')
236 response.mustcontain('fork of vcs test')
222
237
223 def test_zzz_fork_permission_page(self):
238 def test_zzz_fork_permission_page(self):
224 usr = self.log_user(self.username, self.password)['user_id']
239 usr = self.log_user(self.username, self.password)['user_id']
225 repo_name = self.REPO
240 repo_name = self.REPO
226
241
227 forks = Repository.query()\
242 forks = Repository.query()\
228 .filter(Repository.repo_type == self.REPO_TYPE)\
243 .filter(Repository.repo_type == self.REPO_TYPE)\
229 .filter(Repository.fork_id != None).all()
244 .filter(Repository.fork_id != None).all()
230 assert 1 == len(forks)
245 assert 1 == len(forks)
231
246
232 # set none
247 # set none
233 RepoModel().grant_user_permission(repo=forks[0],
248 RepoModel().grant_user_permission(repo=forks[0],
234 user=usr, perm='repository.none')
249 user=usr, perm='repository.none')
235 Session().commit()
250 Session().commit()
236 # fork shouldn't be there
251 # fork shouldn't be there
237 response = self.app.get(url(controller='forks', action='forks',
252 response = self.app.get(url(controller='forks', action='forks',
238 repo_name=repo_name))
253 repo_name=repo_name))
239 response.mustcontain('There are no forks yet')
254 response.mustcontain('There are no forks yet')
240
255
241
256
242 class TestGIT(_BaseTest):
257 class TestGIT(_BaseTest):
243 REPO = GIT_REPO
258 REPO = GIT_REPO
244 NEW_REPO = NEW_GIT_REPO
259 NEW_REPO = NEW_GIT_REPO
245 REPO_TYPE = 'git'
260 REPO_TYPE = 'git'
246 REPO_FORK = GIT_FORK
261 REPO_FORK = GIT_FORK
247
262
248
263
249 class TestHG(_BaseTest):
264 class TestHG(_BaseTest):
250 REPO = HG_REPO
265 REPO = HG_REPO
251 NEW_REPO = NEW_HG_REPO
266 NEW_REPO = NEW_HG_REPO
252 REPO_TYPE = 'hg'
267 REPO_TYPE = 'hg'
253 REPO_FORK = HG_FORK
268 REPO_FORK = HG_FORK
254
269
255
270
256 @pytest.mark.usefixtures('app', 'autologin_user')
271 @pytest.mark.usefixtures('app', 'autologin_user')
257 @pytest.mark.skip_backends('git','hg')
272 @pytest.mark.skip_backends('git','hg')
258 class TestSVNFork(object):
273 class TestSVNFork(object):
259
274
260 def test_fork_redirects(self, backend):
275 def test_fork_redirects(self, backend):
261 denied_actions = ['fork','fork_create']
276 denied_actions = ['fork','fork_create']
262 for action in denied_actions:
277 for action in denied_actions:
263 response = self.app.get(url(
278 response = self.app.get(url(
264 controller='forks', action=action,
279 controller='forks', action=action,
265 repo_name=backend.repo_name))
280 repo_name=backend.repo_name))
266 assert response.status_int == 302
281 assert response.status_int == 302
267
282
268 # Not allowed, redirect to the summary
283 # Not allowed, redirect to the summary
269 redirected = response.follow()
284 redirected = response.follow()
270 summary_url = h.route_path('repo_summary', repo_name=backend.repo_name)
285 summary_url = h.route_path('repo_summary', repo_name=backend.repo_name)
271
286
272 # URL adds leading slash and path doesn't have it
287 # URL adds leading slash and path doesn't have it
273 assert redirected.request.path == summary_url
288 assert redirected.request.path == summary_url
General Comments 0
You need to be logged in to leave comments. Login now