##// END OF EJS Templates
repositories: handle in a nicer way a filesystem damaged repositories....
marcink -
r1984:cd3d1d07 default
parent child Browse files
Show More
@@ -1,417 +1,446 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):
131 _ = self.request.translate
132 reason = None
133 if len(e.args) == 2:
134 reason = e.args[1]
135
136 if reason == 'INVALID_CERTIFICATE':
137 log.exception(
138 'Exception creating a repository: invalid certificate')
139 msg = (_('Error creating repository %s: invalid certificate')
140 % repo_name)
141 else:
142 log.exception("Exception creating a repository")
143 msg = (_('Error creating repository %s')
144 % repo_name)
145 return msg
146
130 def _get_local_tmpl_context(self, include_app_defaults=False):
147 def _get_local_tmpl_context(self, include_app_defaults=False):
131 c = TemplateArgs()
148 c = TemplateArgs()
132 c.auth_user = self.request.user
149 c.auth_user = self.request.user
133 # 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
134 c.rhodecode_user = self.request.user
151 c.rhodecode_user = self.request.user
135
152
136 if include_app_defaults:
153 if include_app_defaults:
137 # NOTE(marcink): after full pyramid migration include_app_defaults
154 # NOTE(marcink): after full pyramid migration include_app_defaults
138 # should be turned on by default
155 # should be turned on by default
139 from rhodecode.lib.base import attach_context_attributes
156 from rhodecode.lib.base import attach_context_attributes
140 attach_context_attributes(c, self.request, self.request.user.user_id)
157 attach_context_attributes(c, self.request, self.request.user.user_id)
141
158
142 return c
159 return c
143
160
144 def _register_global_c(self, tmpl_args):
161 def _register_global_c(self, tmpl_args):
145 """
162 """
146 Registers attributes to pylons global `c`
163 Registers attributes to pylons global `c`
147 """
164 """
148
165
149 # TODO(marcink): remove once pyramid migration is finished
166 # TODO(marcink): remove once pyramid migration is finished
150 from pylons import tmpl_context as c
167 from pylons import tmpl_context as c
151 try:
168 try:
152 for k, v in tmpl_args.items():
169 for k, v in tmpl_args.items():
153 setattr(c, k, v)
170 setattr(c, k, v)
154 except TypeError:
171 except TypeError:
155 log.exception('Failed to register pylons C')
172 log.exception('Failed to register pylons C')
156 pass
173 pass
157
174
158 def _get_template_context(self, tmpl_args):
175 def _get_template_context(self, tmpl_args):
159 self._register_global_c(tmpl_args)
176 self._register_global_c(tmpl_args)
160
177
161 local_tmpl_args = {
178 local_tmpl_args = {
162 'defaults': {},
179 'defaults': {},
163 'errors': {},
180 'errors': {},
164 # 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
165 # 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'
166 # make sure we replace usage of _c in templates too
183 # make sure we replace usage of _c in templates too
167 '_c': tmpl_args
184 '_c': tmpl_args
168 }
185 }
169 local_tmpl_args.update(tmpl_args)
186 local_tmpl_args.update(tmpl_args)
170 return local_tmpl_args
187 return local_tmpl_args
171
188
172 def load_default_context(self):
189 def load_default_context(self):
173 """
190 """
174 example:
191 example:
175
192
176 def load_default_context(self):
193 def load_default_context(self):
177 c = self._get_local_tmpl_context()
194 c = self._get_local_tmpl_context()
178 c.custom_var = 'foobar'
195 c.custom_var = 'foobar'
179 self._register_global_c(c)
196 self._register_global_c(c)
180 return c
197 return c
181 """
198 """
182 raise NotImplementedError('Needs implementation in view class')
199 raise NotImplementedError('Needs implementation in view class')
183
200
184
201
185 class RepoAppView(BaseAppView):
202 class RepoAppView(BaseAppView):
186
203
187 def __init__(self, context, request):
204 def __init__(self, context, request):
188 super(RepoAppView, self).__init__(context, request)
205 super(RepoAppView, self).__init__(context, request)
189 self.db_repo = request.db_repo
206 self.db_repo = request.db_repo
190 self.db_repo_name = self.db_repo.repo_name
207 self.db_repo_name = self.db_repo.repo_name
191 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)
192
209
193 def _handle_missing_requirements(self, error):
210 def _handle_missing_requirements(self, error):
194 log.error(
211 log.error(
195 'Requirements are missing for repository %s: %s',
212 'Requirements are missing for repository %s: %s',
196 self.db_repo_name, error.message)
213 self.db_repo_name, error.message)
197
214
198 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
199 c = super(RepoAppView, self)._get_local_tmpl_context(
217 c = super(RepoAppView, self)._get_local_tmpl_context(
200 include_app_defaults=include_app_defaults)
218 include_app_defaults=include_app_defaults)
201
219
202 # register common vars for this type of view
220 # register common vars for this type of view
203 c.rhodecode_db_repo = self.db_repo
221 c.rhodecode_db_repo = self.db_repo
204 c.repo_name = self.db_repo_name
222 c.repo_name = self.db_repo_name
205 c.repository_pull_requests = self.db_repo_pull_requests
223 c.repository_pull_requests = self.db_repo_pull_requests
206
224
207 c.repository_requirements_missing = False
225 c.repository_requirements_missing = False
208 try:
226 try:
209 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
227 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
210 except RepositoryRequirementError as e:
228 except RepositoryRequirementError as e:
211 c.repository_requirements_missing = True
229 c.repository_requirements_missing = True
212 self._handle_missing_requirements(e)
230 self._handle_missing_requirements(e)
231 self.rhodecode_vcs_repo = None
232
233 if (not c.repository_requirements_missing
234 and self.rhodecode_vcs_repo is None):
235 # unable to fetch this repo as vcs instance, report back to user
236 h.flash(_(
237 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
238 "Please check if it exist, or is not damaged.") %
239 {'repo_name': c.repo_name},
240 category='error', ignore_duplicate=True)
241 raise HTTPFound(h.route_path('home'))
213
242
214 return c
243 return c
215
244
216 def _get_f_path(self, matchdict, default=None):
245 def _get_f_path(self, matchdict, default=None):
217 f_path = matchdict.get('f_path')
246 f_path = matchdict.get('f_path')
218 if f_path:
247 if f_path:
219 # fix for multiple initial slashes that causes errors for GIT
248 # fix for multiple initial slashes that causes errors for GIT
220 return f_path.lstrip('/')
249 return f_path.lstrip('/')
221
250
222 return default
251 return default
223
252
224
253
225 class DataGridAppView(object):
254 class DataGridAppView(object):
226 """
255 """
227 Common class to have re-usable grid rendering components
256 Common class to have re-usable grid rendering components
228 """
257 """
229
258
230 def _extract_ordering(self, request, column_map=None):
259 def _extract_ordering(self, request, column_map=None):
231 column_map = column_map or {}
260 column_map = column_map or {}
232 column_index = safe_int(request.GET.get('order[0][column]'))
261 column_index = safe_int(request.GET.get('order[0][column]'))
233 order_dir = request.GET.get(
262 order_dir = request.GET.get(
234 'order[0][dir]', 'desc')
263 'order[0][dir]', 'desc')
235 order_by = request.GET.get(
264 order_by = request.GET.get(
236 'columns[%s][data][sort]' % column_index, 'name_raw')
265 'columns[%s][data][sort]' % column_index, 'name_raw')
237
266
238 # translate datatable to DB columns
267 # translate datatable to DB columns
239 order_by = column_map.get(order_by) or order_by
268 order_by = column_map.get(order_by) or order_by
240
269
241 search_q = request.GET.get('search[value]')
270 search_q = request.GET.get('search[value]')
242 return search_q, order_by, order_dir
271 return search_q, order_by, order_dir
243
272
244 def _extract_chunk(self, request):
273 def _extract_chunk(self, request):
245 start = safe_int(request.GET.get('start'), 0)
274 start = safe_int(request.GET.get('start'), 0)
246 length = safe_int(request.GET.get('length'), 25)
275 length = safe_int(request.GET.get('length'), 25)
247 draw = safe_int(request.GET.get('draw'))
276 draw = safe_int(request.GET.get('draw'))
248 return draw, start, length
277 return draw, start, length
249
278
250
279
251 class BaseReferencesView(RepoAppView):
280 class BaseReferencesView(RepoAppView):
252 """
281 """
253 Base for reference view for branches, tags and bookmarks.
282 Base for reference view for branches, tags and bookmarks.
254 """
283 """
255 def load_default_context(self):
284 def load_default_context(self):
256 c = self._get_local_tmpl_context()
285 c = self._get_local_tmpl_context()
257
286
258 # 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
259 c.repo_info = self.db_repo
288 c.repo_info = self.db_repo
260
289
261 self._register_global_c(c)
290 self._register_global_c(c)
262 return c
291 return c
263
292
264 def load_refs_context(self, ref_items, partials_template):
293 def load_refs_context(self, ref_items, partials_template):
265 _render = self.request.get_partial_renderer(partials_template)
294 _render = self.request.get_partial_renderer(partials_template)
266 pre_load = ["author", "date", "message"]
295 pre_load = ["author", "date", "message"]
267
296
268 is_svn = h.is_svn(self.rhodecode_vcs_repo)
297 is_svn = h.is_svn(self.rhodecode_vcs_repo)
269 is_hg = h.is_hg(self.rhodecode_vcs_repo)
298 is_hg = h.is_hg(self.rhodecode_vcs_repo)
270
299
271 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
300 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
272
301
273 closed_refs = {}
302 closed_refs = {}
274 if is_hg:
303 if is_hg:
275 closed_refs = self.rhodecode_vcs_repo.branches_closed
304 closed_refs = self.rhodecode_vcs_repo.branches_closed
276
305
277 data = []
306 data = []
278 for ref_name, commit_id in ref_items:
307 for ref_name, commit_id in ref_items:
279 commit = self.rhodecode_vcs_repo.get_commit(
308 commit = self.rhodecode_vcs_repo.get_commit(
280 commit_id=commit_id, pre_load=pre_load)
309 commit_id=commit_id, pre_load=pre_load)
281 closed = ref_name in closed_refs
310 closed = ref_name in closed_refs
282
311
283 # TODO: johbo: Unify generation of reference links
312 # TODO: johbo: Unify generation of reference links
284 use_commit_id = '/' in ref_name or is_svn
313 use_commit_id = '/' in ref_name or is_svn
285
314
286 if use_commit_id:
315 if use_commit_id:
287 files_url = h.route_path(
316 files_url = h.route_path(
288 'repo_files',
317 'repo_files',
289 repo_name=self.db_repo_name,
318 repo_name=self.db_repo_name,
290 f_path=ref_name if is_svn else '',
319 f_path=ref_name if is_svn else '',
291 commit_id=commit_id)
320 commit_id=commit_id)
292
321
293 else:
322 else:
294 files_url = h.route_path(
323 files_url = h.route_path(
295 'repo_files',
324 'repo_files',
296 repo_name=self.db_repo_name,
325 repo_name=self.db_repo_name,
297 f_path=ref_name if is_svn else '',
326 f_path=ref_name if is_svn else '',
298 commit_id=ref_name,
327 commit_id=ref_name,
299 _query=dict(at=ref_name))
328 _query=dict(at=ref_name))
300
329
301 data.append({
330 data.append({
302 "name": _render('name', ref_name, files_url, closed),
331 "name": _render('name', ref_name, files_url, closed),
303 "name_raw": ref_name,
332 "name_raw": ref_name,
304 "date": _render('date', commit.date),
333 "date": _render('date', commit.date),
305 "date_raw": datetime_to_time(commit.date),
334 "date_raw": datetime_to_time(commit.date),
306 "author": _render('author', commit.author),
335 "author": _render('author', commit.author),
307 "commit": _render(
336 "commit": _render(
308 'commit', commit.message, commit.raw_id, commit.idx),
337 'commit', commit.message, commit.raw_id, commit.idx),
309 "commit_raw": commit.idx,
338 "commit_raw": commit.idx,
310 "compare": _render(
339 "compare": _render(
311 'compare', format_ref_id(ref_name, commit.raw_id)),
340 'compare', format_ref_id(ref_name, commit.raw_id)),
312 })
341 })
313
342
314 return data
343 return data
315
344
316
345
317 class RepoRoutePredicate(object):
346 class RepoRoutePredicate(object):
318 def __init__(self, val, config):
347 def __init__(self, val, config):
319 self.val = val
348 self.val = val
320
349
321 def text(self):
350 def text(self):
322 return 'repo_route = %s' % self.val
351 return 'repo_route = %s' % self.val
323
352
324 phash = text
353 phash = text
325
354
326 def __call__(self, info, request):
355 def __call__(self, info, request):
327
356
328 if hasattr(request, 'vcs_call'):
357 if hasattr(request, 'vcs_call'):
329 # skip vcs calls
358 # skip vcs calls
330 return
359 return
331
360
332 repo_name = info['match']['repo_name']
361 repo_name = info['match']['repo_name']
333 repo_model = repo.RepoModel()
362 repo_model = repo.RepoModel()
334 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)
335
364
336 if by_name_match:
365 if by_name_match:
337 # register this as request object we can re-use later
366 # register this as request object we can re-use later
338 request.db_repo = by_name_match
367 request.db_repo = by_name_match
339 return True
368 return True
340
369
341 by_id_match = repo_model.get_repo_by_id(repo_name)
370 by_id_match = repo_model.get_repo_by_id(repo_name)
342 if by_id_match:
371 if by_id_match:
343 request.db_repo = by_id_match
372 request.db_repo = by_id_match
344 return True
373 return True
345
374
346 return False
375 return False
347
376
348
377
349 class RepoTypeRoutePredicate(object):
378 class RepoTypeRoutePredicate(object):
350 def __init__(self, val, config):
379 def __init__(self, val, config):
351 self.val = val or ['hg', 'git', 'svn']
380 self.val = val or ['hg', 'git', 'svn']
352
381
353 def text(self):
382 def text(self):
354 return 'repo_accepted_type = %s' % self.val
383 return 'repo_accepted_type = %s' % self.val
355
384
356 phash = text
385 phash = text
357
386
358 def __call__(self, info, request):
387 def __call__(self, info, request):
359 if hasattr(request, 'vcs_call'):
388 if hasattr(request, 'vcs_call'):
360 # skip vcs calls
389 # skip vcs calls
361 return
390 return
362
391
363 rhodecode_db_repo = request.db_repo
392 rhodecode_db_repo = request.db_repo
364
393
365 log.debug(
394 log.debug(
366 '%s checking repo type for %s in %s',
395 '%s checking repo type for %s in %s',
367 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
396 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
368
397
369 if rhodecode_db_repo.repo_type in self.val:
398 if rhodecode_db_repo.repo_type in self.val:
370 return True
399 return True
371 else:
400 else:
372 log.warning('Current view is not supported for repo type:%s',
401 log.warning('Current view is not supported for repo type:%s',
373 rhodecode_db_repo.repo_type)
402 rhodecode_db_repo.repo_type)
374 #
403 #
375 # h.flash(h.literal(
404 # h.flash(h.literal(
376 # _('Action not supported for %s.' % rhodecode_repo.alias)),
405 # _('Action not supported for %s.' % rhodecode_repo.alias)),
377 # category='warning')
406 # category='warning')
378 # return redirect(
407 # return redirect(
379 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
408 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
380
409
381 return False
410 return False
382
411
383
412
384 class RepoGroupRoutePredicate(object):
413 class RepoGroupRoutePredicate(object):
385 def __init__(self, val, config):
414 def __init__(self, val, config):
386 self.val = val
415 self.val = val
387
416
388 def text(self):
417 def text(self):
389 return 'repo_group_route = %s' % self.val
418 return 'repo_group_route = %s' % self.val
390
419
391 phash = text
420 phash = text
392
421
393 def __call__(self, info, request):
422 def __call__(self, info, request):
394 if hasattr(request, 'vcs_call'):
423 if hasattr(request, 'vcs_call'):
395 # skip vcs calls
424 # skip vcs calls
396 return
425 return
397
426
398 repo_group_name = info['match']['repo_group_name']
427 repo_group_name = info['match']['repo_group_name']
399 repo_group_model = repo_group.RepoGroupModel()
428 repo_group_model = repo_group.RepoGroupModel()
400 by_name_match = repo_group_model.get_by_group_name(
429 by_name_match = repo_group_model.get_by_group_name(
401 repo_group_name, cache=True)
430 repo_group_name, cache=True)
402
431
403 if by_name_match:
432 if by_name_match:
404 # register this as request object we can re-use later
433 # register this as request object we can re-use later
405 request.db_repo_group = by_name_match
434 request.db_repo_group = by_name_match
406 return True
435 return True
407
436
408 return False
437 return False
409
438
410
439
411 def includeme(config):
440 def includeme(config):
412 config.add_route_predicate(
441 config.add_route_predicate(
413 'repo_route', RepoRoutePredicate)
442 'repo_route', RepoRoutePredicate)
414 config.add_route_predicate(
443 config.add_route_predicate(
415 'repo_accepted_types', RepoTypeRoutePredicate)
444 'repo_accepted_types', RepoTypeRoutePredicate)
416 config.add_route_predicate(
445 config.add_route_predicate(
417 'repo_group_route', RepoGroupRoutePredicate)
446 'repo_group_route', RepoGroupRoutePredicate)
@@ -1,494 +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
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
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_manual_delete(self, autologin_user, backend, suffix, csrf_token):
262 def test_missing_filesystem_repo(
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 self.assert_repo_not_found_redirect(repo_name)
276
277 response = self.app.get(
278 route_path('repo_summary', repo_name=safe_str(repo_name)), status=302)
279
280 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
281 'Please check if it exist, or is not damaged.' % repo_name
282 assert_session_flash(response, msg)
283
284 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
285 def test_missing_filesystem_repo_on_repo_check(
286 self, autologin_user, backend, suffix, csrf_token):
287 repo = backend.create_repo(name_suffix=suffix)
288 repo_name = repo.repo_name
289
290 # delete from file system
291 RepoModel()._delete_filesystem_repo(repo)
276
292
277 def assert_repo_not_found_redirect(self, repo_name):
293 # test if the repo is still in the database
278 # run the check page that triggers the other flash message
294 new_repo = RepoModel().get_by_repo_name(repo_name)
279 response = self.app.get(h.url('repo_check_home', repo_name=repo_name))
295 assert new_repo.repo_name == repo_name
280 assert_session_flash(
296
281 response, 'The repository at %s cannot be located.' % repo_name)
297 # check if repo is not in the filesystem
298 assert not repo_on_filesystem(repo_name)
299
300 # flush the session
301 self.app.get(
302 route_path('repo_summary', repo_name=safe_str(repo_name)),
303 status=302)
304
305 response = self.app.get(
306 route_path('repo_creating_check', repo_name=safe_str(repo_name)),
307 status=200)
308 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
309 'Please check if it exist, or is not damaged.' % repo_name
310 assert_session_flash(response, msg )
282
311
283
312
284 @pytest.fixture()
313 @pytest.fixture()
285 def summary_view(context_stub, request_stub, user_util):
314 def summary_view(context_stub, request_stub, user_util):
286 """
315 """
287 Bootstrap view to test the view functions
316 Bootstrap view to test the view functions
288 """
317 """
289 request_stub.matched_route = AttributeDict(name='test_view')
318 request_stub.matched_route = AttributeDict(name='test_view')
290
319
291 request_stub.user = user_util.create_user().AuthUser
320 request_stub.user = user_util.create_user().AuthUser
292 request_stub.db_repo = user_util.create_repo()
321 request_stub.db_repo = user_util.create_repo()
293
322
294 view = RepoSummaryView(context=context_stub, request=request_stub)
323 view = RepoSummaryView(context=context_stub, request=request_stub)
295 return view
324 return view
296
325
297
326
298 @pytest.mark.usefixtures('app')
327 @pytest.mark.usefixtures('app')
299 class TestCreateReferenceData(object):
328 class TestCreateReferenceData(object):
300
329
301 @pytest.fixture
330 @pytest.fixture
302 def example_refs(self):
331 def example_refs(self):
303 section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id')))
332 section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id')))
304 example_refs = [
333 example_refs = [
305 ('section_1', section_1_refs, 't1'),
334 ('section_1', section_1_refs, 't1'),
306 ('section_2', {'c': 'c_id'}, 't2'),
335 ('section_2', {'c': 'c_id'}, 't2'),
307 ]
336 ]
308 return example_refs
337 return example_refs
309
338
310 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):
311 repo = mock.Mock()
340 repo = mock.Mock()
312 repo.name = 'test-repo'
341 repo.name = 'test-repo'
313 repo.alias = 'git'
342 repo.alias = 'git'
314 full_repo_name = 'pytest-repo-group/' + repo.name
343 full_repo_name = 'pytest-repo-group/' + repo.name
315
344
316 result = summary_view._create_reference_data(
345 result = summary_view._create_reference_data(
317 repo, full_repo_name, example_refs)
346 repo, full_repo_name, example_refs)
318
347
319 expected_files_url = '/{}/files/'.format(full_repo_name)
348 expected_files_url = '/{}/files/'.format(full_repo_name)
320 expected_result = [
349 expected_result = [
321 {
350 {
322 'children': [
351 'children': [
323 {
352 {
324 'id': 'a', 'raw_id': 'a_id', 'text': 'a', 'type': 't1',
353 'id': 'a', 'raw_id': 'a_id', 'text': 'a', 'type': 't1',
325 'files_url': expected_files_url + 'a/?at=a',
354 'files_url': expected_files_url + 'a/?at=a',
326 },
355 },
327 {
356 {
328 'id': 'b', 'raw_id': 'b_id', 'text': 'b', 'type': 't1',
357 'id': 'b', 'raw_id': 'b_id', 'text': 'b', 'type': 't1',
329 'files_url': expected_files_url + 'b/?at=b',
358 'files_url': expected_files_url + 'b/?at=b',
330 }
359 }
331 ],
360 ],
332 'text': 'section_1'
361 'text': 'section_1'
333 },
362 },
334 {
363 {
335 'children': [
364 'children': [
336 {
365 {
337 'id': 'c', 'raw_id': 'c_id', 'text': 'c', 'type': 't2',
366 'id': 'c', 'raw_id': 'c_id', 'text': 'c', 'type': 't2',
338 'files_url': expected_files_url + 'c/?at=c',
367 'files_url': expected_files_url + 'c/?at=c',
339 }
368 }
340 ],
369 ],
341 'text': 'section_2'
370 'text': 'section_2'
342 }]
371 }]
343 assert result == expected_result
372 assert result == expected_result
344
373
345 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):
346 repo = mock.Mock()
375 repo = mock.Mock()
347 repo.name = 'test-repo'
376 repo.name = 'test-repo'
348 repo.alias = 'svn'
377 repo.alias = 'svn'
349 full_repo_name = 'pytest-repo-group/' + repo.name
378 full_repo_name = 'pytest-repo-group/' + repo.name
350
379
351 result = summary_view._create_reference_data(
380 result = summary_view._create_reference_data(
352 repo, full_repo_name, example_refs)
381 repo, full_repo_name, example_refs)
353
382
354 expected_files_url = '/{}/files/'.format(full_repo_name)
383 expected_files_url = '/{}/files/'.format(full_repo_name)
355 expected_result = [
384 expected_result = [
356 {
385 {
357 'children': [
386 'children': [
358 {
387 {
359 'id': 'a@a_id', 'raw_id': 'a_id',
388 'id': 'a@a_id', 'raw_id': 'a_id',
360 'text': 'a', 'type': 't1',
389 'text': 'a', 'type': 't1',
361 'files_url': expected_files_url + 'a_id/a?at=a',
390 'files_url': expected_files_url + 'a_id/a?at=a',
362 },
391 },
363 {
392 {
364 'id': 'b@b_id', 'raw_id': 'b_id',
393 'id': 'b@b_id', 'raw_id': 'b_id',
365 'text': 'b', 'type': 't1',
394 'text': 'b', 'type': 't1',
366 'files_url': expected_files_url + 'b_id/b?at=b',
395 'files_url': expected_files_url + 'b_id/b?at=b',
367 }
396 }
368 ],
397 ],
369 'text': 'section_1'
398 'text': 'section_1'
370 },
399 },
371 {
400 {
372 'children': [
401 'children': [
373 {
402 {
374 'id': 'c@c_id', 'raw_id': 'c_id',
403 'id': 'c@c_id', 'raw_id': 'c_id',
375 'text': 'c', 'type': 't2',
404 'text': 'c', 'type': 't2',
376 'files_url': expected_files_url + 'c_id/c?at=c',
405 'files_url': expected_files_url + 'c_id/c?at=c',
377 }
406 }
378 ],
407 ],
379 'text': 'section_2'
408 'text': 'section_2'
380 }
409 }
381 ]
410 ]
382 assert result == expected_result
411 assert result == expected_result
383
412
384
413
385 class TestCreateFilesUrl(object):
414 class TestCreateFilesUrl(object):
386
415
387 def test_creates_non_svn_url(self, app, summary_view):
416 def test_creates_non_svn_url(self, app, summary_view):
388 repo = mock.Mock()
417 repo = mock.Mock()
389 repo.name = 'abcde'
418 repo.name = 'abcde'
390 full_repo_name = 'test-repo-group/' + repo.name
419 full_repo_name = 'test-repo-group/' + repo.name
391 ref_name = 'branch1'
420 ref_name = 'branch1'
392 raw_id = 'deadbeef0123456789'
421 raw_id = 'deadbeef0123456789'
393 is_svn = False
422 is_svn = False
394
423
395 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
424 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
396 result = summary_view._create_files_url(
425 result = summary_view._create_files_url(
397 repo, full_repo_name, ref_name, raw_id, is_svn)
426 repo, full_repo_name, ref_name, raw_id, is_svn)
398 url_mock.assert_called_once_with(
427 url_mock.assert_called_once_with(
399 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
428 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
400 f_path='', _query=dict(at=ref_name))
429 f_path='', _query=dict(at=ref_name))
401 assert result == url_mock.return_value
430 assert result == url_mock.return_value
402
431
403 def test_creates_svn_url(self, app, summary_view):
432 def test_creates_svn_url(self, app, summary_view):
404 repo = mock.Mock()
433 repo = mock.Mock()
405 repo.name = 'abcde'
434 repo.name = 'abcde'
406 full_repo_name = 'test-repo-group/' + repo.name
435 full_repo_name = 'test-repo-group/' + repo.name
407 ref_name = 'branch1'
436 ref_name = 'branch1'
408 raw_id = 'deadbeef0123456789'
437 raw_id = 'deadbeef0123456789'
409 is_svn = True
438 is_svn = True
410
439
411 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
440 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
412 result = summary_view._create_files_url(
441 result = summary_view._create_files_url(
413 repo, full_repo_name, ref_name, raw_id, is_svn)
442 repo, full_repo_name, ref_name, raw_id, is_svn)
414 url_mock.assert_called_once_with(
443 url_mock.assert_called_once_with(
415 'repo_files', repo_name=full_repo_name, f_path=ref_name,
444 'repo_files', repo_name=full_repo_name, f_path=ref_name,
416 commit_id=raw_id, _query=dict(at=ref_name))
445 commit_id=raw_id, _query=dict(at=ref_name))
417 assert result == url_mock.return_value
446 assert result == url_mock.return_value
418
447
419 def test_name_has_slashes(self, app, summary_view):
448 def test_name_has_slashes(self, app, summary_view):
420 repo = mock.Mock()
449 repo = mock.Mock()
421 repo.name = 'abcde'
450 repo.name = 'abcde'
422 full_repo_name = 'test-repo-group/' + repo.name
451 full_repo_name = 'test-repo-group/' + repo.name
423 ref_name = 'branch1/branch2'
452 ref_name = 'branch1/branch2'
424 raw_id = 'deadbeef0123456789'
453 raw_id = 'deadbeef0123456789'
425 is_svn = False
454 is_svn = False
426
455
427 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
456 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
428 result = summary_view._create_files_url(
457 result = summary_view._create_files_url(
429 repo, full_repo_name, ref_name, raw_id, is_svn)
458 repo, full_repo_name, ref_name, raw_id, is_svn)
430 url_mock.assert_called_once_with(
459 url_mock.assert_called_once_with(
431 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
460 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
432 f_path='', _query=dict(at=ref_name))
461 f_path='', _query=dict(at=ref_name))
433 assert result == url_mock.return_value
462 assert result == url_mock.return_value
434
463
435
464
436 class TestReferenceItems(object):
465 class TestReferenceItems(object):
437 repo = mock.Mock()
466 repo = mock.Mock()
438 repo.name = 'pytest-repo'
467 repo.name = 'pytest-repo'
439 repo_full_name = 'pytest-repo-group/' + repo.name
468 repo_full_name = 'pytest-repo-group/' + repo.name
440 ref_type = 'branch'
469 ref_type = 'branch'
441 fake_url = '/abcde/'
470 fake_url = '/abcde/'
442
471
443 @staticmethod
472 @staticmethod
444 def _format_function(name, id_):
473 def _format_function(name, id_):
445 return 'format_function_{}_{}'.format(name, id_)
474 return 'format_function_{}_{}'.format(name, id_)
446
475
447 def test_creates_required_amount_of_items(self, summary_view):
476 def test_creates_required_amount_of_items(self, summary_view):
448 amount = 100
477 amount = 100
449 refs = {
478 refs = {
450 'ref{}'.format(i): '{0:040d}'.format(i)
479 'ref{}'.format(i): '{0:040d}'.format(i)
451 for i in range(amount)
480 for i in range(amount)
452 }
481 }
453
482
454 url_patcher = mock.patch.object(summary_view, '_create_files_url')
483 url_patcher = mock.patch.object(summary_view, '_create_files_url')
455 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
484 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
456 return_value=False)
485 return_value=False)
457
486
458 with url_patcher as url_mock, svn_patcher:
487 with url_patcher as url_mock, svn_patcher:
459 result = summary_view._create_reference_items(
488 result = summary_view._create_reference_items(
460 self.repo, self.repo_full_name, refs, self.ref_type,
489 self.repo, self.repo_full_name, refs, self.ref_type,
461 self._format_function)
490 self._format_function)
462 assert len(result) == amount
491 assert len(result) == amount
463 assert url_mock.call_count == amount
492 assert url_mock.call_count == amount
464
493
465 def test_single_item_details(self, summary_view):
494 def test_single_item_details(self, summary_view):
466 ref_name = 'ref1'
495 ref_name = 'ref1'
467 ref_id = 'deadbeef'
496 ref_id = 'deadbeef'
468 refs = {
497 refs = {
469 ref_name: ref_id
498 ref_name: ref_id
470 }
499 }
471
500
472 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
501 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
473 return_value=False)
502 return_value=False)
474
503
475 url_patcher = mock.patch.object(
504 url_patcher = mock.patch.object(
476 summary_view, '_create_files_url', return_value=self.fake_url)
505 summary_view, '_create_files_url', return_value=self.fake_url)
477
506
478 with url_patcher as url_mock, svn_patcher:
507 with url_patcher as url_mock, svn_patcher:
479 result = summary_view._create_reference_items(
508 result = summary_view._create_reference_items(
480 self.repo, self.repo_full_name, refs, self.ref_type,
509 self.repo, self.repo_full_name, refs, self.ref_type,
481 self._format_function)
510 self._format_function)
482
511
483 url_mock.assert_called_once_with(
512 url_mock.assert_called_once_with(
484 self.repo, self.repo_full_name, ref_name, ref_id, False)
513 self.repo, self.repo_full_name, ref_name, ref_id, False)
485 expected_result = [
514 expected_result = [
486 {
515 {
487 'text': ref_name,
516 'text': ref_name,
488 'id': self._format_function(ref_name, ref_id),
517 'id': self._format_function(ref_name, ref_id),
489 'raw_id': ref_id,
518 'raw_id': ref_id,
490 'type': self.ref_type,
519 'type': self.ref_type,
491 'files_url': self.fake_url
520 'files_url': self.fake_url
492 }
521 }
493 ]
522 ]
494 assert result == expected_result
523 assert result == expected_result
General Comments 0
You need to be logged in to leave comments. Login now