##// END OF EJS Templates
missing requirements: better handling of missing requirements for repositories....
marcink -
r2625:aff7e7b8 default
parent child Browse files
Show More
@@ -1,626 +1,635 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 import operator
23 import operator
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26
26
27 from rhodecode.lib import helpers as h, diffs
27 from rhodecode.lib import helpers as h, diffs
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.model import repo
30 from rhodecode.model import repo
31 from rhodecode.model import repo_group
31 from rhodecode.model import repo_group
32 from rhodecode.model import user_group
32 from rhodecode.model import user_group
33 from rhodecode.model import user
33 from rhodecode.model import user
34 from rhodecode.model.db import User
34 from rhodecode.model.db import User
35 from rhodecode.model.scm import ScmModel
35 from rhodecode.model.scm import ScmModel
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 ADMIN_PREFIX = '/_admin'
40 ADMIN_PREFIX = '/_admin'
41 STATIC_FILE_PREFIX = '/_static'
41 STATIC_FILE_PREFIX = '/_static'
42
42
43 URL_NAME_REQUIREMENTS = {
43 URL_NAME_REQUIREMENTS = {
44 # group name can have a slash in them, but they must not end with a slash
44 # group name can have a slash in them, but they must not end with a slash
45 'group_name': r'.*?[^/]',
45 'group_name': r'.*?[^/]',
46 'repo_group_name': r'.*?[^/]',
46 'repo_group_name': r'.*?[^/]',
47 # repo names can have a slash in them, but they must not end with a slash
47 # repo names can have a slash in them, but they must not end with a slash
48 'repo_name': r'.*?[^/]',
48 'repo_name': r'.*?[^/]',
49 # file path eats up everything at the end
49 # file path eats up everything at the end
50 'f_path': r'.*',
50 'f_path': r'.*',
51 # reference types
51 # reference types
52 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
53 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
54 }
54 }
55
55
56
56
57 def add_route_with_slash(config,name, pattern, **kw):
57 def add_route_with_slash(config,name, pattern, **kw):
58 config.add_route(name, pattern, **kw)
58 config.add_route(name, pattern, **kw)
59 if not pattern.endswith('/'):
59 if not pattern.endswith('/'):
60 config.add_route(name + '_slash', pattern + '/', **kw)
60 config.add_route(name + '_slash', pattern + '/', **kw)
61
61
62
62
63 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
63 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
64 """
64 """
65 Adds regex requirements to pyramid routes using a mapping dict
65 Adds regex requirements to pyramid routes using a mapping dict
66 e.g::
66 e.g::
67 add_route_requirements('{repo_name}/settings')
67 add_route_requirements('{repo_name}/settings')
68 """
68 """
69 for key, regex in requirements.items():
69 for key, regex in requirements.items():
70 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
70 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
71 return route_path
71 return route_path
72
72
73
73
74 def get_format_ref_id(repo):
74 def get_format_ref_id(repo):
75 """Returns a `repo` specific reference formatter function"""
75 """Returns a `repo` specific reference formatter function"""
76 if h.is_svn(repo):
76 if h.is_svn(repo):
77 return _format_ref_id_svn
77 return _format_ref_id_svn
78 else:
78 else:
79 return _format_ref_id
79 return _format_ref_id
80
80
81
81
82 def _format_ref_id(name, raw_id):
82 def _format_ref_id(name, raw_id):
83 """Default formatting of a given reference `name`"""
83 """Default formatting of a given reference `name`"""
84 return name
84 return name
85
85
86
86
87 def _format_ref_id_svn(name, raw_id):
87 def _format_ref_id_svn(name, raw_id):
88 """Special way of formatting a reference for Subversion including path"""
88 """Special way of formatting a reference for Subversion including path"""
89 return '%s@%s' % (name, raw_id)
89 return '%s@%s' % (name, raw_id)
90
90
91
91
92 class TemplateArgs(StrictAttributeDict):
92 class TemplateArgs(StrictAttributeDict):
93 pass
93 pass
94
94
95
95
96 class BaseAppView(object):
96 class BaseAppView(object):
97
97
98 def __init__(self, context, request):
98 def __init__(self, context, request):
99 self.request = request
99 self.request = request
100 self.context = context
100 self.context = context
101 self.session = request.session
101 self.session = request.session
102 self._rhodecode_user = request.user # auth user
102 self._rhodecode_user = request.user # auth user
103 self._rhodecode_db_user = self._rhodecode_user.get_instance()
103 self._rhodecode_db_user = self._rhodecode_user.get_instance()
104 self._maybe_needs_password_change(
104 self._maybe_needs_password_change(
105 request.matched_route.name, self._rhodecode_db_user)
105 request.matched_route.name, self._rhodecode_db_user)
106
106
107 def _maybe_needs_password_change(self, view_name, user_obj):
107 def _maybe_needs_password_change(self, view_name, user_obj):
108 log.debug('Checking if user %s needs password change on view %s',
108 log.debug('Checking if user %s needs password change on view %s',
109 user_obj, view_name)
109 user_obj, view_name)
110 skip_user_views = [
110 skip_user_views = [
111 'logout', 'login',
111 'logout', 'login',
112 'my_account_password', 'my_account_password_update'
112 'my_account_password', 'my_account_password_update'
113 ]
113 ]
114
114
115 if not user_obj:
115 if not user_obj:
116 return
116 return
117
117
118 if user_obj.username == User.DEFAULT_USER:
118 if user_obj.username == User.DEFAULT_USER:
119 return
119 return
120
120
121 now = time.time()
121 now = time.time()
122 should_change = user_obj.user_data.get('force_password_change')
122 should_change = user_obj.user_data.get('force_password_change')
123 change_after = safe_int(should_change) or 0
123 change_after = safe_int(should_change) or 0
124 if should_change and now > change_after:
124 if should_change and now > change_after:
125 log.debug('User %s requires password change', user_obj)
125 log.debug('User %s requires password change', user_obj)
126 h.flash('You are required to change your password', 'warning',
126 h.flash('You are required to change your password', 'warning',
127 ignore_duplicate=True)
127 ignore_duplicate=True)
128
128
129 if view_name not in skip_user_views:
129 if view_name not in skip_user_views:
130 raise HTTPFound(
130 raise HTTPFound(
131 self.request.route_path('my_account_password'))
131 self.request.route_path('my_account_password'))
132
132
133 def _log_creation_exception(self, e, repo_name):
133 def _log_creation_exception(self, e, repo_name):
134 _ = self.request.translate
134 _ = self.request.translate
135 reason = None
135 reason = None
136 if len(e.args) == 2:
136 if len(e.args) == 2:
137 reason = e.args[1]
137 reason = e.args[1]
138
138
139 if reason == 'INVALID_CERTIFICATE':
139 if reason == 'INVALID_CERTIFICATE':
140 log.exception(
140 log.exception(
141 'Exception creating a repository: invalid certificate')
141 'Exception creating a repository: invalid certificate')
142 msg = (_('Error creating repository %s: invalid certificate')
142 msg = (_('Error creating repository %s: invalid certificate')
143 % repo_name)
143 % repo_name)
144 else:
144 else:
145 log.exception("Exception creating a repository")
145 log.exception("Exception creating a repository")
146 msg = (_('Error creating repository %s')
146 msg = (_('Error creating repository %s')
147 % repo_name)
147 % repo_name)
148 return msg
148 return msg
149
149
150 def _get_local_tmpl_context(self, include_app_defaults=True):
150 def _get_local_tmpl_context(self, include_app_defaults=True):
151 c = TemplateArgs()
151 c = TemplateArgs()
152 c.auth_user = self.request.user
152 c.auth_user = self.request.user
153 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
153 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
154 c.rhodecode_user = self.request.user
154 c.rhodecode_user = self.request.user
155
155
156 if include_app_defaults:
156 if include_app_defaults:
157 from rhodecode.lib.base import attach_context_attributes
157 from rhodecode.lib.base import attach_context_attributes
158 attach_context_attributes(c, self.request, self.request.user.user_id)
158 attach_context_attributes(c, self.request, self.request.user.user_id)
159
159
160 return c
160 return c
161
161
162 def _get_template_context(self, tmpl_args, **kwargs):
162 def _get_template_context(self, tmpl_args, **kwargs):
163
163
164 local_tmpl_args = {
164 local_tmpl_args = {
165 'defaults': {},
165 'defaults': {},
166 'errors': {},
166 'errors': {},
167 'c': tmpl_args
167 'c': tmpl_args
168 }
168 }
169 local_tmpl_args.update(kwargs)
169 local_tmpl_args.update(kwargs)
170 return local_tmpl_args
170 return local_tmpl_args
171
171
172 def load_default_context(self):
172 def load_default_context(self):
173 """
173 """
174 example:
174 example:
175
175
176 def load_default_context(self):
176 def load_default_context(self):
177 c = self._get_local_tmpl_context()
177 c = self._get_local_tmpl_context()
178 c.custom_var = 'foobar'
178 c.custom_var = 'foobar'
179
179
180 return c
180 return c
181 """
181 """
182 raise NotImplementedError('Needs implementation in view class')
182 raise NotImplementedError('Needs implementation in view class')
183
183
184
184
185 class RepoAppView(BaseAppView):
185 class RepoAppView(BaseAppView):
186
186
187 def __init__(self, context, request):
187 def __init__(self, context, request):
188 super(RepoAppView, self).__init__(context, request)
188 super(RepoAppView, self).__init__(context, request)
189 self.db_repo = request.db_repo
189 self.db_repo = request.db_repo
190 self.db_repo_name = self.db_repo.repo_name
190 self.db_repo_name = self.db_repo.repo_name
191 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
191 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
192
192
193 def _handle_missing_requirements(self, error):
193 def _handle_missing_requirements(self, error):
194 log.error(
194 log.error(
195 'Requirements are missing for repository %s: %s',
195 'Requirements are missing for repository %s: %s',
196 self.db_repo_name, error.message)
196 self.db_repo_name, error.message)
197
197
198 def _get_local_tmpl_context(self, include_app_defaults=True):
198 def _get_local_tmpl_context(self, include_app_defaults=True):
199 _ = self.request.translate
199 _ = self.request.translate
200 c = super(RepoAppView, self)._get_local_tmpl_context(
200 c = super(RepoAppView, self)._get_local_tmpl_context(
201 include_app_defaults=include_app_defaults)
201 include_app_defaults=include_app_defaults)
202
202
203 # register common vars for this type of view
203 # register common vars for this type of view
204 c.rhodecode_db_repo = self.db_repo
204 c.rhodecode_db_repo = self.db_repo
205 c.repo_name = self.db_repo_name
205 c.repo_name = self.db_repo_name
206 c.repository_pull_requests = self.db_repo_pull_requests
206 c.repository_pull_requests = self.db_repo_pull_requests
207 self.path_filter = PathFilter(None)
207 self.path_filter = PathFilter(None)
208
208
209 c.repository_requirements_missing = False
209 c.repository_requirements_missing = {}
210 try:
210 try:
211 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
211 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
212 if self.rhodecode_vcs_repo:
212 if self.rhodecode_vcs_repo:
213 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
213 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
214 c.auth_user.username)
214 c.auth_user.username)
215 self.path_filter = PathFilter(path_perms)
215 self.path_filter = PathFilter(path_perms)
216 except RepositoryRequirementError as e:
216 except RepositoryRequirementError as e:
217 c.repository_requirements_missing = True
217 c.repository_requirements_missing = {'error': str(e)}
218 self._handle_missing_requirements(e)
218 self._handle_missing_requirements(e)
219 self.rhodecode_vcs_repo = None
219 self.rhodecode_vcs_repo = None
220
220
221 c.path_filter = self.path_filter # used by atom_feed_entry.mako
221 c.path_filter = self.path_filter # used by atom_feed_entry.mako
222
222
223 if (not c.repository_requirements_missing
223 if self.rhodecode_vcs_repo is None:
224 and self.rhodecode_vcs_repo is None):
225 # unable to fetch this repo as vcs instance, report back to user
224 # unable to fetch this repo as vcs instance, report back to user
226 h.flash(_(
225 h.flash(_(
227 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
226 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
228 "Please check if it exist, or is not damaged.") %
227 "Please check if it exist, or is not damaged.") %
229 {'repo_name': c.repo_name},
228 {'repo_name': c.repo_name},
230 category='error', ignore_duplicate=True)
229 category='error', ignore_duplicate=True)
231 raise HTTPFound(h.route_path('home'))
230 if c.repository_requirements_missing:
231 route = self.request.matched_route.name
232 if route.startswith(('edit_repo', 'repo_summary')):
233 # allow summary and edit repo on missing requirements
234 return c
235
236 raise HTTPFound(
237 h.route_path('repo_summary', repo_name=self.db_repo_name))
238
239 else: # redirect if we don't show missing requirements
240 raise HTTPFound(h.route_path('home'))
232
241
233 return c
242 return c
234
243
235 def _get_f_path_unchecked(self, matchdict, default=None):
244 def _get_f_path_unchecked(self, matchdict, default=None):
236 """
245 """
237 Should only be used by redirects, everything else should call _get_f_path
246 Should only be used by redirects, everything else should call _get_f_path
238 """
247 """
239 f_path = matchdict.get('f_path')
248 f_path = matchdict.get('f_path')
240 if f_path:
249 if f_path:
241 # fix for multiple initial slashes that causes errors for GIT
250 # fix for multiple initial slashes that causes errors for GIT
242 return f_path.lstrip('/')
251 return f_path.lstrip('/')
243
252
244 return default
253 return default
245
254
246 def _get_f_path(self, matchdict, default=None):
255 def _get_f_path(self, matchdict, default=None):
247 f_path_match = self._get_f_path_unchecked(matchdict, default)
256 f_path_match = self._get_f_path_unchecked(matchdict, default)
248 return self.path_filter.assert_path_permissions(f_path_match)
257 return self.path_filter.assert_path_permissions(f_path_match)
249
258
250
259
251 class PathFilter(object):
260 class PathFilter(object):
252
261
253 # Expects and instance of BasePathPermissionChecker or None
262 # Expects and instance of BasePathPermissionChecker or None
254 def __init__(self, permission_checker):
263 def __init__(self, permission_checker):
255 self.permission_checker = permission_checker
264 self.permission_checker = permission_checker
256
265
257 def assert_path_permissions(self, path):
266 def assert_path_permissions(self, path):
258 if path and self.permission_checker and not self.permission_checker.has_access(path):
267 if path and self.permission_checker and not self.permission_checker.has_access(path):
259 raise HTTPForbidden()
268 raise HTTPForbidden()
260 return path
269 return path
261
270
262 def filter_patchset(self, patchset):
271 def filter_patchset(self, patchset):
263 if not self.permission_checker or not patchset:
272 if not self.permission_checker or not patchset:
264 return patchset, False
273 return patchset, False
265 had_filtered = False
274 had_filtered = False
266 filtered_patchset = []
275 filtered_patchset = []
267 for patch in patchset:
276 for patch in patchset:
268 filename = patch.get('filename', None)
277 filename = patch.get('filename', None)
269 if not filename or self.permission_checker.has_access(filename):
278 if not filename or self.permission_checker.has_access(filename):
270 filtered_patchset.append(patch)
279 filtered_patchset.append(patch)
271 else:
280 else:
272 had_filtered = True
281 had_filtered = True
273 if had_filtered:
282 if had_filtered:
274 if isinstance(patchset, diffs.LimitedDiffContainer):
283 if isinstance(patchset, diffs.LimitedDiffContainer):
275 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
284 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
276 return filtered_patchset, True
285 return filtered_patchset, True
277 else:
286 else:
278 return patchset, False
287 return patchset, False
279
288
280 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
289 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
281 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
290 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
282 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
291 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
283 result.has_hidden_changes = has_hidden_changes
292 result.has_hidden_changes = has_hidden_changes
284 return result
293 return result
285
294
286 def get_raw_patch(self, diff_processor):
295 def get_raw_patch(self, diff_processor):
287 if self.permission_checker is None:
296 if self.permission_checker is None:
288 return diff_processor.as_raw()
297 return diff_processor.as_raw()
289 elif self.permission_checker.has_full_access:
298 elif self.permission_checker.has_full_access:
290 return diff_processor.as_raw()
299 return diff_processor.as_raw()
291 else:
300 else:
292 return '# Repository has user-specific filters, raw patch generation is disabled.'
301 return '# Repository has user-specific filters, raw patch generation is disabled.'
293
302
294 @property
303 @property
295 def is_enabled(self):
304 def is_enabled(self):
296 return self.permission_checker is not None
305 return self.permission_checker is not None
297
306
298
307
299 class RepoGroupAppView(BaseAppView):
308 class RepoGroupAppView(BaseAppView):
300 def __init__(self, context, request):
309 def __init__(self, context, request):
301 super(RepoGroupAppView, self).__init__(context, request)
310 super(RepoGroupAppView, self).__init__(context, request)
302 self.db_repo_group = request.db_repo_group
311 self.db_repo_group = request.db_repo_group
303 self.db_repo_group_name = self.db_repo_group.group_name
312 self.db_repo_group_name = self.db_repo_group.group_name
304
313
305 def _revoke_perms_on_yourself(self, form_result):
314 def _revoke_perms_on_yourself(self, form_result):
306 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
315 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
307 form_result['perm_updates'])
316 form_result['perm_updates'])
308 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
317 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
309 form_result['perm_additions'])
318 form_result['perm_additions'])
310 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
319 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
311 form_result['perm_deletions'])
320 form_result['perm_deletions'])
312 admin_perm = 'group.admin'
321 admin_perm = 'group.admin'
313 if _updates and _updates[0][1] != admin_perm or \
322 if _updates and _updates[0][1] != admin_perm or \
314 _additions and _additions[0][1] != admin_perm or \
323 _additions and _additions[0][1] != admin_perm or \
315 _deletions and _deletions[0][1] != admin_perm:
324 _deletions and _deletions[0][1] != admin_perm:
316 return True
325 return True
317 return False
326 return False
318
327
319
328
320 class UserGroupAppView(BaseAppView):
329 class UserGroupAppView(BaseAppView):
321 def __init__(self, context, request):
330 def __init__(self, context, request):
322 super(UserGroupAppView, self).__init__(context, request)
331 super(UserGroupAppView, self).__init__(context, request)
323 self.db_user_group = request.db_user_group
332 self.db_user_group = request.db_user_group
324 self.db_user_group_name = self.db_user_group.users_group_name
333 self.db_user_group_name = self.db_user_group.users_group_name
325
334
326
335
327 class UserAppView(BaseAppView):
336 class UserAppView(BaseAppView):
328 def __init__(self, context, request):
337 def __init__(self, context, request):
329 super(UserAppView, self).__init__(context, request)
338 super(UserAppView, self).__init__(context, request)
330 self.db_user = request.db_user
339 self.db_user = request.db_user
331 self.db_user_id = self.db_user.user_id
340 self.db_user_id = self.db_user.user_id
332
341
333 _ = self.request.translate
342 _ = self.request.translate
334 if not request.db_user_supports_default:
343 if not request.db_user_supports_default:
335 if self.db_user.username == User.DEFAULT_USER:
344 if self.db_user.username == User.DEFAULT_USER:
336 h.flash(_("Editing user `{}` is disabled.".format(
345 h.flash(_("Editing user `{}` is disabled.".format(
337 User.DEFAULT_USER)), category='warning')
346 User.DEFAULT_USER)), category='warning')
338 raise HTTPFound(h.route_path('users'))
347 raise HTTPFound(h.route_path('users'))
339
348
340
349
341 class DataGridAppView(object):
350 class DataGridAppView(object):
342 """
351 """
343 Common class to have re-usable grid rendering components
352 Common class to have re-usable grid rendering components
344 """
353 """
345
354
346 def _extract_ordering(self, request, column_map=None):
355 def _extract_ordering(self, request, column_map=None):
347 column_map = column_map or {}
356 column_map = column_map or {}
348 column_index = safe_int(request.GET.get('order[0][column]'))
357 column_index = safe_int(request.GET.get('order[0][column]'))
349 order_dir = request.GET.get(
358 order_dir = request.GET.get(
350 'order[0][dir]', 'desc')
359 'order[0][dir]', 'desc')
351 order_by = request.GET.get(
360 order_by = request.GET.get(
352 'columns[%s][data][sort]' % column_index, 'name_raw')
361 'columns[%s][data][sort]' % column_index, 'name_raw')
353
362
354 # translate datatable to DB columns
363 # translate datatable to DB columns
355 order_by = column_map.get(order_by) or order_by
364 order_by = column_map.get(order_by) or order_by
356
365
357 search_q = request.GET.get('search[value]')
366 search_q = request.GET.get('search[value]')
358 return search_q, order_by, order_dir
367 return search_q, order_by, order_dir
359
368
360 def _extract_chunk(self, request):
369 def _extract_chunk(self, request):
361 start = safe_int(request.GET.get('start'), 0)
370 start = safe_int(request.GET.get('start'), 0)
362 length = safe_int(request.GET.get('length'), 25)
371 length = safe_int(request.GET.get('length'), 25)
363 draw = safe_int(request.GET.get('draw'))
372 draw = safe_int(request.GET.get('draw'))
364 return draw, start, length
373 return draw, start, length
365
374
366 def _get_order_col(self, order_by, model):
375 def _get_order_col(self, order_by, model):
367 if isinstance(order_by, basestring):
376 if isinstance(order_by, basestring):
368 try:
377 try:
369 return operator.attrgetter(order_by)(model)
378 return operator.attrgetter(order_by)(model)
370 except AttributeError:
379 except AttributeError:
371 return None
380 return None
372 else:
381 else:
373 return order_by
382 return order_by
374
383
375
384
376 class BaseReferencesView(RepoAppView):
385 class BaseReferencesView(RepoAppView):
377 """
386 """
378 Base for reference view for branches, tags and bookmarks.
387 Base for reference view for branches, tags and bookmarks.
379 """
388 """
380 def load_default_context(self):
389 def load_default_context(self):
381 c = self._get_local_tmpl_context()
390 c = self._get_local_tmpl_context()
382
391
383
392
384 return c
393 return c
385
394
386 def load_refs_context(self, ref_items, partials_template):
395 def load_refs_context(self, ref_items, partials_template):
387 _render = self.request.get_partial_renderer(partials_template)
396 _render = self.request.get_partial_renderer(partials_template)
388 pre_load = ["author", "date", "message"]
397 pre_load = ["author", "date", "message"]
389
398
390 is_svn = h.is_svn(self.rhodecode_vcs_repo)
399 is_svn = h.is_svn(self.rhodecode_vcs_repo)
391 is_hg = h.is_hg(self.rhodecode_vcs_repo)
400 is_hg = h.is_hg(self.rhodecode_vcs_repo)
392
401
393 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
402 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
394
403
395 closed_refs = {}
404 closed_refs = {}
396 if is_hg:
405 if is_hg:
397 closed_refs = self.rhodecode_vcs_repo.branches_closed
406 closed_refs = self.rhodecode_vcs_repo.branches_closed
398
407
399 data = []
408 data = []
400 for ref_name, commit_id in ref_items:
409 for ref_name, commit_id in ref_items:
401 commit = self.rhodecode_vcs_repo.get_commit(
410 commit = self.rhodecode_vcs_repo.get_commit(
402 commit_id=commit_id, pre_load=pre_load)
411 commit_id=commit_id, pre_load=pre_load)
403 closed = ref_name in closed_refs
412 closed = ref_name in closed_refs
404
413
405 # TODO: johbo: Unify generation of reference links
414 # TODO: johbo: Unify generation of reference links
406 use_commit_id = '/' in ref_name or is_svn
415 use_commit_id = '/' in ref_name or is_svn
407
416
408 if use_commit_id:
417 if use_commit_id:
409 files_url = h.route_path(
418 files_url = h.route_path(
410 'repo_files',
419 'repo_files',
411 repo_name=self.db_repo_name,
420 repo_name=self.db_repo_name,
412 f_path=ref_name if is_svn else '',
421 f_path=ref_name if is_svn else '',
413 commit_id=commit_id)
422 commit_id=commit_id)
414
423
415 else:
424 else:
416 files_url = h.route_path(
425 files_url = h.route_path(
417 'repo_files',
426 'repo_files',
418 repo_name=self.db_repo_name,
427 repo_name=self.db_repo_name,
419 f_path=ref_name if is_svn else '',
428 f_path=ref_name if is_svn else '',
420 commit_id=ref_name,
429 commit_id=ref_name,
421 _query=dict(at=ref_name))
430 _query=dict(at=ref_name))
422
431
423 data.append({
432 data.append({
424 "name": _render('name', ref_name, files_url, closed),
433 "name": _render('name', ref_name, files_url, closed),
425 "name_raw": ref_name,
434 "name_raw": ref_name,
426 "date": _render('date', commit.date),
435 "date": _render('date', commit.date),
427 "date_raw": datetime_to_time(commit.date),
436 "date_raw": datetime_to_time(commit.date),
428 "author": _render('author', commit.author),
437 "author": _render('author', commit.author),
429 "commit": _render(
438 "commit": _render(
430 'commit', commit.message, commit.raw_id, commit.idx),
439 'commit', commit.message, commit.raw_id, commit.idx),
431 "commit_raw": commit.idx,
440 "commit_raw": commit.idx,
432 "compare": _render(
441 "compare": _render(
433 'compare', format_ref_id(ref_name, commit.raw_id)),
442 'compare', format_ref_id(ref_name, commit.raw_id)),
434 })
443 })
435
444
436 return data
445 return data
437
446
438
447
439 class RepoRoutePredicate(object):
448 class RepoRoutePredicate(object):
440 def __init__(self, val, config):
449 def __init__(self, val, config):
441 self.val = val
450 self.val = val
442
451
443 def text(self):
452 def text(self):
444 return 'repo_route = %s' % self.val
453 return 'repo_route = %s' % self.val
445
454
446 phash = text
455 phash = text
447
456
448 def __call__(self, info, request):
457 def __call__(self, info, request):
449
458
450 if hasattr(request, 'vcs_call'):
459 if hasattr(request, 'vcs_call'):
451 # skip vcs calls
460 # skip vcs calls
452 return
461 return
453
462
454 repo_name = info['match']['repo_name']
463 repo_name = info['match']['repo_name']
455 repo_model = repo.RepoModel()
464 repo_model = repo.RepoModel()
456 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
465 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
457
466
458 def redirect_if_creating(db_repo):
467 def redirect_if_creating(db_repo):
459 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
468 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
460 raise HTTPFound(
469 raise HTTPFound(
461 request.route_path('repo_creating',
470 request.route_path('repo_creating',
462 repo_name=db_repo.repo_name))
471 repo_name=db_repo.repo_name))
463
472
464 if by_name_match:
473 if by_name_match:
465 # register this as request object we can re-use later
474 # register this as request object we can re-use later
466 request.db_repo = by_name_match
475 request.db_repo = by_name_match
467 redirect_if_creating(by_name_match)
476 redirect_if_creating(by_name_match)
468 return True
477 return True
469
478
470 by_id_match = repo_model.get_repo_by_id(repo_name)
479 by_id_match = repo_model.get_repo_by_id(repo_name)
471 if by_id_match:
480 if by_id_match:
472 request.db_repo = by_id_match
481 request.db_repo = by_id_match
473 redirect_if_creating(by_id_match)
482 redirect_if_creating(by_id_match)
474 return True
483 return True
475
484
476 return False
485 return False
477
486
478
487
479 class RepoTypeRoutePredicate(object):
488 class RepoTypeRoutePredicate(object):
480 def __init__(self, val, config):
489 def __init__(self, val, config):
481 self.val = val or ['hg', 'git', 'svn']
490 self.val = val or ['hg', 'git', 'svn']
482
491
483 def text(self):
492 def text(self):
484 return 'repo_accepted_type = %s' % self.val
493 return 'repo_accepted_type = %s' % self.val
485
494
486 phash = text
495 phash = text
487
496
488 def __call__(self, info, request):
497 def __call__(self, info, request):
489 if hasattr(request, 'vcs_call'):
498 if hasattr(request, 'vcs_call'):
490 # skip vcs calls
499 # skip vcs calls
491 return
500 return
492
501
493 rhodecode_db_repo = request.db_repo
502 rhodecode_db_repo = request.db_repo
494
503
495 log.debug(
504 log.debug(
496 '%s checking repo type for %s in %s',
505 '%s checking repo type for %s in %s',
497 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
506 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
498
507
499 if rhodecode_db_repo.repo_type in self.val:
508 if rhodecode_db_repo.repo_type in self.val:
500 return True
509 return True
501 else:
510 else:
502 log.warning('Current view is not supported for repo type:%s',
511 log.warning('Current view is not supported for repo type:%s',
503 rhodecode_db_repo.repo_type)
512 rhodecode_db_repo.repo_type)
504 #
513 #
505 # h.flash(h.literal(
514 # h.flash(h.literal(
506 # _('Action not supported for %s.' % rhodecode_repo.alias)),
515 # _('Action not supported for %s.' % rhodecode_repo.alias)),
507 # category='warning')
516 # category='warning')
508 # return redirect(
517 # return redirect(
509 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
518 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
510
519
511 return False
520 return False
512
521
513
522
514 class RepoGroupRoutePredicate(object):
523 class RepoGroupRoutePredicate(object):
515 def __init__(self, val, config):
524 def __init__(self, val, config):
516 self.val = val
525 self.val = val
517
526
518 def text(self):
527 def text(self):
519 return 'repo_group_route = %s' % self.val
528 return 'repo_group_route = %s' % self.val
520
529
521 phash = text
530 phash = text
522
531
523 def __call__(self, info, request):
532 def __call__(self, info, request):
524 if hasattr(request, 'vcs_call'):
533 if hasattr(request, 'vcs_call'):
525 # skip vcs calls
534 # skip vcs calls
526 return
535 return
527
536
528 repo_group_name = info['match']['repo_group_name']
537 repo_group_name = info['match']['repo_group_name']
529 repo_group_model = repo_group.RepoGroupModel()
538 repo_group_model = repo_group.RepoGroupModel()
530 by_name_match = repo_group_model.get_by_group_name(
539 by_name_match = repo_group_model.get_by_group_name(
531 repo_group_name, cache=True)
540 repo_group_name, cache=True)
532
541
533 if by_name_match:
542 if by_name_match:
534 # register this as request object we can re-use later
543 # register this as request object we can re-use later
535 request.db_repo_group = by_name_match
544 request.db_repo_group = by_name_match
536 return True
545 return True
537
546
538 return False
547 return False
539
548
540
549
541 class UserGroupRoutePredicate(object):
550 class UserGroupRoutePredicate(object):
542 def __init__(self, val, config):
551 def __init__(self, val, config):
543 self.val = val
552 self.val = val
544
553
545 def text(self):
554 def text(self):
546 return 'user_group_route = %s' % self.val
555 return 'user_group_route = %s' % self.val
547
556
548 phash = text
557 phash = text
549
558
550 def __call__(self, info, request):
559 def __call__(self, info, request):
551 if hasattr(request, 'vcs_call'):
560 if hasattr(request, 'vcs_call'):
552 # skip vcs calls
561 # skip vcs calls
553 return
562 return
554
563
555 user_group_id = info['match']['user_group_id']
564 user_group_id = info['match']['user_group_id']
556 user_group_model = user_group.UserGroup()
565 user_group_model = user_group.UserGroup()
557 by_id_match = user_group_model.get(
566 by_id_match = user_group_model.get(
558 user_group_id, cache=True)
567 user_group_id, cache=True)
559
568
560 if by_id_match:
569 if by_id_match:
561 # register this as request object we can re-use later
570 # register this as request object we can re-use later
562 request.db_user_group = by_id_match
571 request.db_user_group = by_id_match
563 return True
572 return True
564
573
565 return False
574 return False
566
575
567
576
568 class UserRoutePredicateBase(object):
577 class UserRoutePredicateBase(object):
569 supports_default = None
578 supports_default = None
570
579
571 def __init__(self, val, config):
580 def __init__(self, val, config):
572 self.val = val
581 self.val = val
573
582
574 def text(self):
583 def text(self):
575 raise NotImplementedError()
584 raise NotImplementedError()
576
585
577 def __call__(self, info, request):
586 def __call__(self, info, request):
578 if hasattr(request, 'vcs_call'):
587 if hasattr(request, 'vcs_call'):
579 # skip vcs calls
588 # skip vcs calls
580 return
589 return
581
590
582 user_id = info['match']['user_id']
591 user_id = info['match']['user_id']
583 user_model = user.User()
592 user_model = user.User()
584 by_id_match = user_model.get(
593 by_id_match = user_model.get(
585 user_id, cache=True)
594 user_id, cache=True)
586
595
587 if by_id_match:
596 if by_id_match:
588 # register this as request object we can re-use later
597 # register this as request object we can re-use later
589 request.db_user = by_id_match
598 request.db_user = by_id_match
590 request.db_user_supports_default = self.supports_default
599 request.db_user_supports_default = self.supports_default
591 return True
600 return True
592
601
593 return False
602 return False
594
603
595
604
596 class UserRoutePredicate(UserRoutePredicateBase):
605 class UserRoutePredicate(UserRoutePredicateBase):
597 supports_default = False
606 supports_default = False
598
607
599 def text(self):
608 def text(self):
600 return 'user_route = %s' % self.val
609 return 'user_route = %s' % self.val
601
610
602 phash = text
611 phash = text
603
612
604
613
605 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
614 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
606 supports_default = True
615 supports_default = True
607
616
608 def text(self):
617 def text(self):
609 return 'user_with_default_route = %s' % self.val
618 return 'user_with_default_route = %s' % self.val
610
619
611 phash = text
620 phash = text
612
621
613
622
614 def includeme(config):
623 def includeme(config):
615 config.add_route_predicate(
624 config.add_route_predicate(
616 'repo_route', RepoRoutePredicate)
625 'repo_route', RepoRoutePredicate)
617 config.add_route_predicate(
626 config.add_route_predicate(
618 'repo_accepted_types', RepoTypeRoutePredicate)
627 'repo_accepted_types', RepoTypeRoutePredicate)
619 config.add_route_predicate(
628 config.add_route_predicate(
620 'repo_group_route', RepoGroupRoutePredicate)
629 'repo_group_route', RepoGroupRoutePredicate)
621 config.add_route_predicate(
630 config.add_route_predicate(
622 'user_group_route', UserGroupRoutePredicate)
631 'user_group_route', UserGroupRoutePredicate)
623 config.add_route_predicate(
632 config.add_route_predicate(
624 'user_route_with_default', UserRouteWithDefaultPredicate)
633 'user_route_with_default', UserRouteWithDefaultPredicate)
625 config.add_route_predicate(
634 config.add_route_predicate(
626 'user_route', UserRoutePredicate) No newline at end of file
635 'user_route', UserRoutePredicate)
@@ -1,522 +1,523 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 'repo_creating_check': '/{repo_name}/repo_creating_check',
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 def assert_clone_url(response, server, repo, disabled=False):
59 def assert_clone_url(response, server, repo, disabled=False):
60
60
61 response.mustcontain(
61 response.mustcontain(
62 '<input type="text" class="input-monospace clone_url_input" '
62 '<input type="text" class="input-monospace clone_url_input" '
63 '{disabled}readonly="readonly" '
63 '{disabled}readonly="readonly" '
64 'value="http://test_admin@{server}/{repo}"/>'.format(
64 'value="http://test_admin@{server}/{repo}"/>'.format(
65 server=server, repo=repo, disabled='disabled ' if disabled else ' ')
65 server=server, repo=repo, disabled='disabled ' if disabled else ' ')
66 )
66 )
67
67
68
68
69 @pytest.mark.usefixtures('app')
69 @pytest.mark.usefixtures('app')
70 class TestSummaryView(object):
70 class TestSummaryView(object):
71 def test_index(self, autologin_user, backend, http_host_only_stub):
71 def test_index(self, autologin_user, backend, http_host_only_stub):
72 repo_id = backend.repo.repo_id
72 repo_id = backend.repo.repo_id
73 repo_name = backend.repo_name
73 repo_name = backend.repo_name
74 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
74 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
75 return_value=False):
75 return_value=False):
76 response = self.app.get(
76 response = self.app.get(
77 route_path('repo_summary', repo_name=repo_name))
77 route_path('repo_summary', repo_name=repo_name))
78
78
79 # repo type
79 # repo type
80 response.mustcontain(
80 response.mustcontain(
81 '<i class="icon-%s">' % (backend.alias, )
81 '<i class="icon-%s">' % (backend.alias, )
82 )
82 )
83 # public/private
83 # public/private
84 response.mustcontain(
84 response.mustcontain(
85 """<i class="icon-unlock-alt">"""
85 """<i class="icon-unlock-alt">"""
86 )
86 )
87
87
88 # clone url...
88 # clone url...
89 assert_clone_url(response, http_host_only_stub, repo_name)
89 assert_clone_url(response, http_host_only_stub, repo_name)
90 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id))
90 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id))
91
91
92 def test_index_svn_without_proxy(
92 def test_index_svn_without_proxy(
93 self, autologin_user, backend_svn, http_host_only_stub):
93 self, autologin_user, backend_svn, http_host_only_stub):
94 repo_id = backend_svn.repo.repo_id
94 repo_id = backend_svn.repo.repo_id
95 repo_name = backend_svn.repo_name
95 repo_name = backend_svn.repo_name
96 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
96 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
97 # clone url...
97 # clone url...
98
98
99 assert_clone_url(response, http_host_only_stub, repo_name, disabled=True)
99 assert_clone_url(response, http_host_only_stub, repo_name, disabled=True)
100 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id), disabled=True)
100 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id), disabled=True)
101
101
102 def test_index_with_trailing_slash(
102 def test_index_with_trailing_slash(
103 self, autologin_user, backend, http_host_only_stub):
103 self, autologin_user, backend, http_host_only_stub):
104
104
105 repo_id = backend.repo.repo_id
105 repo_id = backend.repo.repo_id
106 repo_name = backend.repo_name
106 repo_name = backend.repo_name
107 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
107 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
108 return_value=False):
108 return_value=False):
109 response = self.app.get(
109 response = self.app.get(
110 route_path('repo_summary', repo_name=repo_name) + '/',
110 route_path('repo_summary', repo_name=repo_name) + '/',
111 status=200)
111 status=200)
112
112
113 # clone url...
113 # clone url...
114 assert_clone_url(response, http_host_only_stub, repo_name)
114 assert_clone_url(response, http_host_only_stub, repo_name)
115 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id))
115 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id))
116
116
117 def test_index_by_id(self, autologin_user, backend):
117 def test_index_by_id(self, autologin_user, backend):
118 repo_id = backend.repo.repo_id
118 repo_id = backend.repo.repo_id
119 response = self.app.get(
119 response = self.app.get(
120 route_path('repo_summary', repo_name='_%s' % (repo_id,)))
120 route_path('repo_summary', repo_name='_%s' % (repo_id,)))
121
121
122 # repo type
122 # repo type
123 response.mustcontain(
123 response.mustcontain(
124 '<i class="icon-%s">' % (backend.alias, )
124 '<i class="icon-%s">' % (backend.alias, )
125 )
125 )
126 # public/private
126 # public/private
127 response.mustcontain(
127 response.mustcontain(
128 """<i class="icon-unlock-alt">"""
128 """<i class="icon-unlock-alt">"""
129 )
129 )
130
130
131 def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user):
131 def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user):
132 fixture.create_repo(name='repo_1')
132 fixture.create_repo(name='repo_1')
133 response = self.app.get(route_path('repo_summary', repo_name='repo_1'))
133 response = self.app.get(route_path('repo_summary', repo_name='repo_1'))
134
134
135 try:
135 try:
136 response.mustcontain("repo_1")
136 response.mustcontain("repo_1")
137 finally:
137 finally:
138 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
138 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
139 Session().commit()
139 Session().commit()
140
140
141 def test_index_with_anonymous_access_disabled(
141 def test_index_with_anonymous_access_disabled(
142 self, backend, disable_anonymous_user):
142 self, backend, disable_anonymous_user):
143 response = self.app.get(
143 response = self.app.get(
144 route_path('repo_summary', repo_name=backend.repo_name), status=302)
144 route_path('repo_summary', repo_name=backend.repo_name), status=302)
145 assert 'login' in response.location
145 assert 'login' in response.location
146
146
147 def _enable_stats(self, repo):
147 def _enable_stats(self, repo):
148 r = Repository.get_by_repo_name(repo)
148 r = Repository.get_by_repo_name(repo)
149 r.enable_statistics = True
149 r.enable_statistics = True
150 Session().add(r)
150 Session().add(r)
151 Session().commit()
151 Session().commit()
152
152
153 expected_trending = {
153 expected_trending = {
154 'hg': {
154 'hg': {
155 "py": {"count": 68, "desc": ["Python"]},
155 "py": {"count": 68, "desc": ["Python"]},
156 "rst": {"count": 16, "desc": ["Rst"]},
156 "rst": {"count": 16, "desc": ["Rst"]},
157 "css": {"count": 2, "desc": ["Css"]},
157 "css": {"count": 2, "desc": ["Css"]},
158 "sh": {"count": 2, "desc": ["Bash"]},
158 "sh": {"count": 2, "desc": ["Bash"]},
159 "bat": {"count": 1, "desc": ["Batch"]},
159 "bat": {"count": 1, "desc": ["Batch"]},
160 "cfg": {"count": 1, "desc": ["Ini"]},
160 "cfg": {"count": 1, "desc": ["Ini"]},
161 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
161 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
162 "ini": {"count": 1, "desc": ["Ini"]},
162 "ini": {"count": 1, "desc": ["Ini"]},
163 "js": {"count": 1, "desc": ["Javascript"]},
163 "js": {"count": 1, "desc": ["Javascript"]},
164 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
164 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
165 },
165 },
166 'git': {
166 'git': {
167 "py": {"count": 68, "desc": ["Python"]},
167 "py": {"count": 68, "desc": ["Python"]},
168 "rst": {"count": 16, "desc": ["Rst"]},
168 "rst": {"count": 16, "desc": ["Rst"]},
169 "css": {"count": 2, "desc": ["Css"]},
169 "css": {"count": 2, "desc": ["Css"]},
170 "sh": {"count": 2, "desc": ["Bash"]},
170 "sh": {"count": 2, "desc": ["Bash"]},
171 "bat": {"count": 1, "desc": ["Batch"]},
171 "bat": {"count": 1, "desc": ["Batch"]},
172 "cfg": {"count": 1, "desc": ["Ini"]},
172 "cfg": {"count": 1, "desc": ["Ini"]},
173 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
173 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
174 "ini": {"count": 1, "desc": ["Ini"]},
174 "ini": {"count": 1, "desc": ["Ini"]},
175 "js": {"count": 1, "desc": ["Javascript"]},
175 "js": {"count": 1, "desc": ["Javascript"]},
176 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
176 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
177 },
177 },
178 'svn': {
178 'svn': {
179 "py": {"count": 75, "desc": ["Python"]},
179 "py": {"count": 75, "desc": ["Python"]},
180 "rst": {"count": 16, "desc": ["Rst"]},
180 "rst": {"count": 16, "desc": ["Rst"]},
181 "html": {"count": 11, "desc": ["EvoqueHtml", "Html"]},
181 "html": {"count": 11, "desc": ["EvoqueHtml", "Html"]},
182 "css": {"count": 2, "desc": ["Css"]},
182 "css": {"count": 2, "desc": ["Css"]},
183 "bat": {"count": 1, "desc": ["Batch"]},
183 "bat": {"count": 1, "desc": ["Batch"]},
184 "cfg": {"count": 1, "desc": ["Ini"]},
184 "cfg": {"count": 1, "desc": ["Ini"]},
185 "ini": {"count": 1, "desc": ["Ini"]},
185 "ini": {"count": 1, "desc": ["Ini"]},
186 "js": {"count": 1, "desc": ["Javascript"]},
186 "js": {"count": 1, "desc": ["Javascript"]},
187 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]},
187 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]},
188 "sh": {"count": 1, "desc": ["Bash"]}
188 "sh": {"count": 1, "desc": ["Bash"]}
189 },
189 },
190 }
190 }
191
191
192 def test_repo_stats(self, autologin_user, backend, xhr_header):
192 def test_repo_stats(self, autologin_user, backend, xhr_header):
193 response = self.app.get(
193 response = self.app.get(
194 route_path(
194 route_path(
195 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
195 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
196 extra_environ=xhr_header,
196 extra_environ=xhr_header,
197 status=200)
197 status=200)
198 assert re.match(r'6[\d\.]+ KiB', response.json['size'])
198 assert re.match(r'6[\d\.]+ KiB', response.json['size'])
199
199
200 def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header):
200 def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header):
201 repo_name = backend.repo_name
201 repo_name = backend.repo_name
202
202
203 # codes stats
203 # codes stats
204 self._enable_stats(repo_name)
204 self._enable_stats(repo_name)
205 ScmModel().mark_for_invalidation(repo_name)
205 ScmModel().mark_for_invalidation(repo_name)
206
206
207 response = self.app.get(
207 response = self.app.get(
208 route_path(
208 route_path(
209 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
209 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
210 extra_environ=xhr_header,
210 extra_environ=xhr_header,
211 status=200)
211 status=200)
212
212
213 expected_data = self.expected_trending[backend.alias]
213 expected_data = self.expected_trending[backend.alias]
214 returned_stats = response.json['code_stats']
214 returned_stats = response.json['code_stats']
215 for k, v in expected_data.items():
215 for k, v in expected_data.items():
216 assert v == returned_stats[k]
216 assert v == returned_stats[k]
217
217
218 def test_repo_refs_data(self, backend):
218 def test_repo_refs_data(self, backend):
219 response = self.app.get(
219 response = self.app.get(
220 route_path('repo_refs_data', repo_name=backend.repo_name),
220 route_path('repo_refs_data', repo_name=backend.repo_name),
221 status=200)
221 status=200)
222
222
223 # Ensure that there is the correct amount of items in the result
223 # Ensure that there is the correct amount of items in the result
224 repo = backend.repo.scm_instance()
224 repo = backend.repo.scm_instance()
225 data = response.json['results']
225 data = response.json['results']
226 items = sum(len(section['children']) for section in data)
226 items = sum(len(section['children']) for section in data)
227 repo_refs = len(repo.branches) + len(repo.tags) + len(repo.bookmarks)
227 repo_refs = len(repo.branches) + len(repo.tags) + len(repo.bookmarks)
228 assert items == repo_refs
228 assert items == repo_refs
229
229
230 def test_index_shows_missing_requirements_message(
230 def test_index_shows_missing_requirements_message(
231 self, backend, autologin_user):
231 self, backend, autologin_user):
232 repo_name = backend.repo_name
232 repo_name = backend.repo_name
233 scm_patcher = mock.patch.object(
233 scm_patcher = mock.patch.object(
234 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
234 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
235
235
236 with scm_patcher:
236 with scm_patcher:
237 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
237 response = self.app.get(
238 route_path('repo_summary', repo_name=repo_name))
238 assert_response = AssertResponse(response)
239 assert_response = AssertResponse(response)
239 assert_response.element_contains(
240 assert_response.element_contains(
240 '.main .alert-warning strong', 'Missing requirements')
241 '.main .alert-warning strong', 'Missing requirements')
241 assert_response.element_contains(
242 assert_response.element_contains(
242 '.main .alert-warning',
243 '.main .alert-warning',
243 'Commits cannot be displayed, because this repository '
244 'Commits cannot be displayed, because this repository '
244 'uses one or more extensions, which was not enabled.')
245 'uses one or more extensions, which was not enabled.')
245
246
246 def test_missing_requirements_page_does_not_contains_switch_to(
247 def test_missing_requirements_page_does_not_contains_switch_to(
247 self, autologin_user, backend):
248 self, autologin_user, backend):
248 repo_name = backend.repo_name
249 repo_name = backend.repo_name
249 scm_patcher = mock.patch.object(
250 scm_patcher = mock.patch.object(
250 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
251 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
251
252
252 with scm_patcher:
253 with scm_patcher:
253 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))
254 response.mustcontain(no='Switch To')
255 response.mustcontain(no='Switch To')
255
256
256
257
257 @pytest.mark.usefixtures('app')
258 @pytest.mark.usefixtures('app')
258 class TestRepoLocation(object):
259 class TestRepoLocation(object):
259
260
260 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
261 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
261 def test_missing_filesystem_repo(
262 def test_missing_filesystem_repo(
262 self, autologin_user, backend, suffix, csrf_token):
263 self, autologin_user, backend, suffix, csrf_token):
263 repo = backend.create_repo(name_suffix=suffix)
264 repo = backend.create_repo(name_suffix=suffix)
264 repo_name = repo.repo_name
265 repo_name = repo.repo_name
265
266
266 # delete from file system
267 # delete from file system
267 RepoModel()._delete_filesystem_repo(repo)
268 RepoModel()._delete_filesystem_repo(repo)
268
269
269 # test if the repo is still in the database
270 # test if the repo is still in the database
270 new_repo = RepoModel().get_by_repo_name(repo_name)
271 new_repo = RepoModel().get_by_repo_name(repo_name)
271 assert new_repo.repo_name == repo_name
272 assert new_repo.repo_name == repo_name
272
273
273 # check if repo is not in the filesystem
274 # check if repo is not in the filesystem
274 assert not repo_on_filesystem(repo_name)
275 assert not repo_on_filesystem(repo_name)
275
276
276 response = self.app.get(
277 response = self.app.get(
277 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)
278
279
279 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
280 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
280 'Please check if it exist, or is not damaged.' % repo_name
281 'Please check if it exist, or is not damaged.' % repo_name
281 assert_session_flash(response, msg)
282 assert_session_flash(response, msg)
282
283
283 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
284 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
284 def test_missing_filesystem_repo_on_repo_check(
285 def test_missing_filesystem_repo_on_repo_check(
285 self, autologin_user, backend, suffix, csrf_token):
286 self, autologin_user, backend, suffix, csrf_token):
286 repo = backend.create_repo(name_suffix=suffix)
287 repo = backend.create_repo(name_suffix=suffix)
287 repo_name = repo.repo_name
288 repo_name = repo.repo_name
288
289
289 # delete from file system
290 # delete from file system
290 RepoModel()._delete_filesystem_repo(repo)
291 RepoModel()._delete_filesystem_repo(repo)
291
292
292 # test if the repo is still in the database
293 # test if the repo is still in the database
293 new_repo = RepoModel().get_by_repo_name(repo_name)
294 new_repo = RepoModel().get_by_repo_name(repo_name)
294 assert new_repo.repo_name == repo_name
295 assert new_repo.repo_name == repo_name
295
296
296 # check if repo is not in the filesystem
297 # check if repo is not in the filesystem
297 assert not repo_on_filesystem(repo_name)
298 assert not repo_on_filesystem(repo_name)
298
299
299 # flush the session
300 # flush the session
300 self.app.get(
301 self.app.get(
301 route_path('repo_summary', repo_name=safe_str(repo_name)),
302 route_path('repo_summary', repo_name=safe_str(repo_name)),
302 status=302)
303 status=302)
303
304
304 response = self.app.get(
305 response = self.app.get(
305 route_path('repo_creating_check', repo_name=safe_str(repo_name)),
306 route_path('repo_creating_check', repo_name=safe_str(repo_name)),
306 status=200)
307 status=200)
307 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
308 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
308 'Please check if it exist, or is not damaged.' % repo_name
309 'Please check if it exist, or is not damaged.' % repo_name
309 assert_session_flash(response, msg )
310 assert_session_flash(response, msg )
310
311
311
312
312 @pytest.fixture()
313 @pytest.fixture()
313 def summary_view(context_stub, request_stub, user_util):
314 def summary_view(context_stub, request_stub, user_util):
314 """
315 """
315 Bootstrap view to test the view functions
316 Bootstrap view to test the view functions
316 """
317 """
317 request_stub.matched_route = AttributeDict(name='test_view')
318 request_stub.matched_route = AttributeDict(name='test_view')
318
319
319 request_stub.user = user_util.create_user().AuthUser()
320 request_stub.user = user_util.create_user().AuthUser()
320 request_stub.db_repo = user_util.create_repo()
321 request_stub.db_repo = user_util.create_repo()
321
322
322 view = RepoSummaryView(context=context_stub, request=request_stub)
323 view = RepoSummaryView(context=context_stub, request=request_stub)
323 return view
324 return view
324
325
325
326
326 @pytest.mark.usefixtures('app')
327 @pytest.mark.usefixtures('app')
327 class TestCreateReferenceData(object):
328 class TestCreateReferenceData(object):
328
329
329 @pytest.fixture
330 @pytest.fixture
330 def example_refs(self):
331 def example_refs(self):
331 section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id')))
332 section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id')))
332 example_refs = [
333 example_refs = [
333 ('section_1', section_1_refs, 't1'),
334 ('section_1', section_1_refs, 't1'),
334 ('section_2', {'c': 'c_id'}, 't2'),
335 ('section_2', {'c': 'c_id'}, 't2'),
335 ]
336 ]
336 return example_refs
337 return example_refs
337
338
338 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):
339 repo = mock.Mock()
340 repo = mock.Mock()
340 repo.name = 'test-repo'
341 repo.name = 'test-repo'
341 repo.alias = 'git'
342 repo.alias = 'git'
342 full_repo_name = 'pytest-repo-group/' + repo.name
343 full_repo_name = 'pytest-repo-group/' + repo.name
343
344
344 result = summary_view._create_reference_data(
345 result = summary_view._create_reference_data(
345 repo, full_repo_name, example_refs)
346 repo, full_repo_name, example_refs)
346
347
347 expected_files_url = '/{}/files/'.format(full_repo_name)
348 expected_files_url = '/{}/files/'.format(full_repo_name)
348 expected_result = [
349 expected_result = [
349 {
350 {
350 'children': [
351 'children': [
351 {
352 {
352 'id': 'a', 'raw_id': 'a_id', 'text': 'a', 'type': 't1',
353 'id': 'a', 'raw_id': 'a_id', 'text': 'a', 'type': 't1',
353 'files_url': expected_files_url + 'a/?at=a',
354 'files_url': expected_files_url + 'a/?at=a',
354 },
355 },
355 {
356 {
356 'id': 'b', 'raw_id': 'b_id', 'text': 'b', 'type': 't1',
357 'id': 'b', 'raw_id': 'b_id', 'text': 'b', 'type': 't1',
357 'files_url': expected_files_url + 'b/?at=b',
358 'files_url': expected_files_url + 'b/?at=b',
358 }
359 }
359 ],
360 ],
360 'text': 'section_1'
361 'text': 'section_1'
361 },
362 },
362 {
363 {
363 'children': [
364 'children': [
364 {
365 {
365 'id': 'c', 'raw_id': 'c_id', 'text': 'c', 'type': 't2',
366 'id': 'c', 'raw_id': 'c_id', 'text': 'c', 'type': 't2',
366 'files_url': expected_files_url + 'c/?at=c',
367 'files_url': expected_files_url + 'c/?at=c',
367 }
368 }
368 ],
369 ],
369 'text': 'section_2'
370 'text': 'section_2'
370 }]
371 }]
371 assert result == expected_result
372 assert result == expected_result
372
373
373 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):
374 repo = mock.Mock()
375 repo = mock.Mock()
375 repo.name = 'test-repo'
376 repo.name = 'test-repo'
376 repo.alias = 'svn'
377 repo.alias = 'svn'
377 full_repo_name = 'pytest-repo-group/' + repo.name
378 full_repo_name = 'pytest-repo-group/' + repo.name
378
379
379 result = summary_view._create_reference_data(
380 result = summary_view._create_reference_data(
380 repo, full_repo_name, example_refs)
381 repo, full_repo_name, example_refs)
381
382
382 expected_files_url = '/{}/files/'.format(full_repo_name)
383 expected_files_url = '/{}/files/'.format(full_repo_name)
383 expected_result = [
384 expected_result = [
384 {
385 {
385 'children': [
386 'children': [
386 {
387 {
387 'id': 'a@a_id', 'raw_id': 'a_id',
388 'id': 'a@a_id', 'raw_id': 'a_id',
388 'text': 'a', 'type': 't1',
389 'text': 'a', 'type': 't1',
389 'files_url': expected_files_url + 'a_id/a?at=a',
390 'files_url': expected_files_url + 'a_id/a?at=a',
390 },
391 },
391 {
392 {
392 'id': 'b@b_id', 'raw_id': 'b_id',
393 'id': 'b@b_id', 'raw_id': 'b_id',
393 'text': 'b', 'type': 't1',
394 'text': 'b', 'type': 't1',
394 'files_url': expected_files_url + 'b_id/b?at=b',
395 'files_url': expected_files_url + 'b_id/b?at=b',
395 }
396 }
396 ],
397 ],
397 'text': 'section_1'
398 'text': 'section_1'
398 },
399 },
399 {
400 {
400 'children': [
401 'children': [
401 {
402 {
402 'id': 'c@c_id', 'raw_id': 'c_id',
403 'id': 'c@c_id', 'raw_id': 'c_id',
403 'text': 'c', 'type': 't2',
404 'text': 'c', 'type': 't2',
404 'files_url': expected_files_url + 'c_id/c?at=c',
405 'files_url': expected_files_url + 'c_id/c?at=c',
405 }
406 }
406 ],
407 ],
407 'text': 'section_2'
408 'text': 'section_2'
408 }
409 }
409 ]
410 ]
410 assert result == expected_result
411 assert result == expected_result
411
412
412
413
413 class TestCreateFilesUrl(object):
414 class TestCreateFilesUrl(object):
414
415
415 def test_creates_non_svn_url(self, app, summary_view):
416 def test_creates_non_svn_url(self, app, summary_view):
416 repo = mock.Mock()
417 repo = mock.Mock()
417 repo.name = 'abcde'
418 repo.name = 'abcde'
418 full_repo_name = 'test-repo-group/' + repo.name
419 full_repo_name = 'test-repo-group/' + repo.name
419 ref_name = 'branch1'
420 ref_name = 'branch1'
420 raw_id = 'deadbeef0123456789'
421 raw_id = 'deadbeef0123456789'
421 is_svn = False
422 is_svn = False
422
423
423 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
424 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
424 result = summary_view._create_files_url(
425 result = summary_view._create_files_url(
425 repo, full_repo_name, ref_name, raw_id, is_svn)
426 repo, full_repo_name, ref_name, raw_id, is_svn)
426 url_mock.assert_called_once_with(
427 url_mock.assert_called_once_with(
427 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
428 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
428 f_path='', _query=dict(at=ref_name))
429 f_path='', _query=dict(at=ref_name))
429 assert result == url_mock.return_value
430 assert result == url_mock.return_value
430
431
431 def test_creates_svn_url(self, app, summary_view):
432 def test_creates_svn_url(self, app, summary_view):
432 repo = mock.Mock()
433 repo = mock.Mock()
433 repo.name = 'abcde'
434 repo.name = 'abcde'
434 full_repo_name = 'test-repo-group/' + repo.name
435 full_repo_name = 'test-repo-group/' + repo.name
435 ref_name = 'branch1'
436 ref_name = 'branch1'
436 raw_id = 'deadbeef0123456789'
437 raw_id = 'deadbeef0123456789'
437 is_svn = True
438 is_svn = True
438
439
439 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
440 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
440 result = summary_view._create_files_url(
441 result = summary_view._create_files_url(
441 repo, full_repo_name, ref_name, raw_id, is_svn)
442 repo, full_repo_name, ref_name, raw_id, is_svn)
442 url_mock.assert_called_once_with(
443 url_mock.assert_called_once_with(
443 'repo_files', repo_name=full_repo_name, f_path=ref_name,
444 'repo_files', repo_name=full_repo_name, f_path=ref_name,
444 commit_id=raw_id, _query=dict(at=ref_name))
445 commit_id=raw_id, _query=dict(at=ref_name))
445 assert result == url_mock.return_value
446 assert result == url_mock.return_value
446
447
447 def test_name_has_slashes(self, app, summary_view):
448 def test_name_has_slashes(self, app, summary_view):
448 repo = mock.Mock()
449 repo = mock.Mock()
449 repo.name = 'abcde'
450 repo.name = 'abcde'
450 full_repo_name = 'test-repo-group/' + repo.name
451 full_repo_name = 'test-repo-group/' + repo.name
451 ref_name = 'branch1/branch2'
452 ref_name = 'branch1/branch2'
452 raw_id = 'deadbeef0123456789'
453 raw_id = 'deadbeef0123456789'
453 is_svn = False
454 is_svn = False
454
455
455 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
456 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
456 result = summary_view._create_files_url(
457 result = summary_view._create_files_url(
457 repo, full_repo_name, ref_name, raw_id, is_svn)
458 repo, full_repo_name, ref_name, raw_id, is_svn)
458 url_mock.assert_called_once_with(
459 url_mock.assert_called_once_with(
459 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
460 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
460 f_path='', _query=dict(at=ref_name))
461 f_path='', _query=dict(at=ref_name))
461 assert result == url_mock.return_value
462 assert result == url_mock.return_value
462
463
463
464
464 class TestReferenceItems(object):
465 class TestReferenceItems(object):
465 repo = mock.Mock()
466 repo = mock.Mock()
466 repo.name = 'pytest-repo'
467 repo.name = 'pytest-repo'
467 repo_full_name = 'pytest-repo-group/' + repo.name
468 repo_full_name = 'pytest-repo-group/' + repo.name
468 ref_type = 'branch'
469 ref_type = 'branch'
469 fake_url = '/abcde/'
470 fake_url = '/abcde/'
470
471
471 @staticmethod
472 @staticmethod
472 def _format_function(name, id_):
473 def _format_function(name, id_):
473 return 'format_function_{}_{}'.format(name, id_)
474 return 'format_function_{}_{}'.format(name, id_)
474
475
475 def test_creates_required_amount_of_items(self, summary_view):
476 def test_creates_required_amount_of_items(self, summary_view):
476 amount = 100
477 amount = 100
477 refs = {
478 refs = {
478 'ref{}'.format(i): '{0:040d}'.format(i)
479 'ref{}'.format(i): '{0:040d}'.format(i)
479 for i in range(amount)
480 for i in range(amount)
480 }
481 }
481
482
482 url_patcher = mock.patch.object(summary_view, '_create_files_url')
483 url_patcher = mock.patch.object(summary_view, '_create_files_url')
483 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
484 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
484 return_value=False)
485 return_value=False)
485
486
486 with url_patcher as url_mock, svn_patcher:
487 with url_patcher as url_mock, svn_patcher:
487 result = summary_view._create_reference_items(
488 result = summary_view._create_reference_items(
488 self.repo, self.repo_full_name, refs, self.ref_type,
489 self.repo, self.repo_full_name, refs, self.ref_type,
489 self._format_function)
490 self._format_function)
490 assert len(result) == amount
491 assert len(result) == amount
491 assert url_mock.call_count == amount
492 assert url_mock.call_count == amount
492
493
493 def test_single_item_details(self, summary_view):
494 def test_single_item_details(self, summary_view):
494 ref_name = 'ref1'
495 ref_name = 'ref1'
495 ref_id = 'deadbeef'
496 ref_id = 'deadbeef'
496 refs = {
497 refs = {
497 ref_name: ref_id
498 ref_name: ref_id
498 }
499 }
499
500
500 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
501 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
501 return_value=False)
502 return_value=False)
502
503
503 url_patcher = mock.patch.object(
504 url_patcher = mock.patch.object(
504 summary_view, '_create_files_url', return_value=self.fake_url)
505 summary_view, '_create_files_url', return_value=self.fake_url)
505
506
506 with url_patcher as url_mock, svn_patcher:
507 with url_patcher as url_mock, svn_patcher:
507 result = summary_view._create_reference_items(
508 result = summary_view._create_reference_items(
508 self.repo, self.repo_full_name, refs, self.ref_type,
509 self.repo, self.repo_full_name, refs, self.ref_type,
509 self._format_function)
510 self._format_function)
510
511
511 url_mock.assert_called_once_with(
512 url_mock.assert_called_once_with(
512 self.repo, self.repo_full_name, ref_name, ref_id, False)
513 self.repo, self.repo_full_name, ref_name, ref_id, False)
513 expected_result = [
514 expected_result = [
514 {
515 {
515 'text': ref_name,
516 'text': ref_name,
516 'id': self._format_function(ref_name, ref_id),
517 'id': self._format_function(ref_name, ref_id),
517 'raw_id': ref_id,
518 'raw_id': ref_id,
518 'type': self.ref_type,
519 'type': self.ref_type,
519 'files_url': self.fake_url
520 'files_url': self.fake_url
520 }
521 }
521 ]
522 ]
522 assert result == expected_result
523 assert result == expected_result
@@ -1,32 +1,34 b''
1 <%inherit file="/summary/summary_base.mako"/>
1 <%inherit file="/summary/summary_base.mako"/>
2
2
3 <%namespace name="components" file="/summary/components.mako"/>
3 <%namespace name="components" file="/summary/components.mako"/>
4
4
5 <%def name="main()">
5 <%def name="main()">
6 <div class="title">
6 <div class="title">
7 ${self.repo_page_title(c.rhodecode_db_repo)}
7 ${self.repo_page_title(c.rhodecode_db_repo)}
8 </div>
8 </div>
9
9
10 <div id="repo-summary" class="summary">
10 <div id="repo-summary" class="summary">
11 ${components.summary_detail(breadcrumbs_links=self.breadcrumbs_links(), show_downloads=False)}
11 ${components.summary_detail(breadcrumbs_links=self.breadcrumbs_links(), show_downloads=False)}
12 ${components.summary_stats(gravatar_function=self.gravatar_with_user)}
12 ${components.summary_stats(gravatar_function=self.gravatar_with_user)}
13 </div><!--end repo-summary-->
13 </div><!--end repo-summary-->
14
14
15 <div class="alert alert-dismissable alert-warning">
15 <div class="alert alert-dismissable alert-warning">
16 <strong>Missing requirements</strong>
16 <strong>Missing requirements</strong>
17 Commits cannot be displayed, because this repository uses one or more extensions, which was not enabled. <br/>
17 Commits cannot be displayed, because this repository uses one or more extensions, which was not enabled. <br/>
18 Please <a href="${h.route_path('edit_repo_vcs', repo_name=c.repo_name)}">enable extension in settings</a>, or contact the repository owner for help.
18 Please <a href="${h.route_path('edit_repo_vcs', repo_name=c.repo_name)}">enable extension in settings</a>, or contact the repository owner for help.
19 Missing extensions could be:
19 Missing extensions could be:
20 <pre>
20 <pre>
21
21
22 - Mercurial largefiles
22 - Mercurial largefiles
23 - Git LFS
23 - Git LFS
24 </pre>
24 </pre>
25 <br/>
26 Requirement error: ${c.repository_requirements_missing.get('error')}
25 </div>
27 </div>
26
28
27 </%def>
29 </%def>
28
30
29
31
30 <%def name="menu_bar_subnav()">
32 <%def name="menu_bar_subnav()">
31 ${self.repo_menu(active='summary')}
33 ${self.repo_menu(active='summary')}
32 </%def>
34 </%def>
General Comments 0
You need to be logged in to leave comments. Login now