##// 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 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import time
22 22 import logging
23 23
24 24 from pyramid.httpexceptions import HTTPFound
25 25
26 26 from rhodecode.lib import helpers as h
27 27 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 29 from rhodecode.model import repo
30 30 from rhodecode.model import repo_group
31 31 from rhodecode.model.db import User
32 32 from rhodecode.model.scm import ScmModel
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 ADMIN_PREFIX = '/_admin'
38 38 STATIC_FILE_PREFIX = '/_static'
39 39
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 'repo_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 def add_route_with_slash(config,name, pattern, **kw):
55 55 config.add_route(name, pattern, **kw)
56 56 if not pattern.endswith('/'):
57 57 config.add_route(name + '_slash', pattern + '/', **kw)
58 58
59 59
60 60 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
61 61 """
62 62 Adds regex requirements to pyramid routes using a mapping dict
63 63 e.g::
64 64 add_route_requirements('{repo_name}/settings')
65 65 """
66 66 for key, regex in requirements.items():
67 67 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
68 68 return route_path
69 69
70 70
71 71 def get_format_ref_id(repo):
72 72 """Returns a `repo` specific reference formatter function"""
73 73 if h.is_svn(repo):
74 74 return _format_ref_id_svn
75 75 else:
76 76 return _format_ref_id
77 77
78 78
79 79 def _format_ref_id(name, raw_id):
80 80 """Default formatting of a given reference `name`"""
81 81 return name
82 82
83 83
84 84 def _format_ref_id_svn(name, raw_id):
85 85 """Special way of formatting a reference for Subversion including path"""
86 86 return '%s@%s' % (name, raw_id)
87 87
88 88
89 89 class TemplateArgs(StrictAttributeDict):
90 90 pass
91 91
92 92
93 93 class BaseAppView(object):
94 94
95 95 def __init__(self, context, request):
96 96 self.request = request
97 97 self.context = context
98 98 self.session = request.session
99 99 self._rhodecode_user = request.user # auth user
100 100 self._rhodecode_db_user = self._rhodecode_user.get_instance()
101 101 self._maybe_needs_password_change(
102 102 request.matched_route.name, self._rhodecode_db_user)
103 103
104 104 def _maybe_needs_password_change(self, view_name, user_obj):
105 105 log.debug('Checking if user %s needs password change on view %s',
106 106 user_obj, view_name)
107 107 skip_user_views = [
108 108 'logout', 'login',
109 109 'my_account_password', 'my_account_password_update'
110 110 ]
111 111
112 112 if not user_obj:
113 113 return
114 114
115 115 if user_obj.username == User.DEFAULT_USER:
116 116 return
117 117
118 118 now = time.time()
119 119 should_change = user_obj.user_data.get('force_password_change')
120 120 change_after = safe_int(should_change) or 0
121 121 if should_change and now > change_after:
122 122 log.debug('User %s requires password change', user_obj)
123 123 h.flash('You are required to change your password', 'warning',
124 124 ignore_duplicate=True)
125 125
126 126 if view_name not in skip_user_views:
127 127 raise HTTPFound(
128 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 147 def _get_local_tmpl_context(self, include_app_defaults=False):
131 148 c = TemplateArgs()
132 149 c.auth_user = self.request.user
133 150 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
134 151 c.rhodecode_user = self.request.user
135 152
136 153 if include_app_defaults:
137 154 # NOTE(marcink): after full pyramid migration include_app_defaults
138 155 # should be turned on by default
139 156 from rhodecode.lib.base import attach_context_attributes
140 157 attach_context_attributes(c, self.request, self.request.user.user_id)
141 158
142 159 return c
143 160
144 161 def _register_global_c(self, tmpl_args):
145 162 """
146 163 Registers attributes to pylons global `c`
147 164 """
148 165
149 166 # TODO(marcink): remove once pyramid migration is finished
150 167 from pylons import tmpl_context as c
151 168 try:
152 169 for k, v in tmpl_args.items():
153 170 setattr(c, k, v)
154 171 except TypeError:
155 172 log.exception('Failed to register pylons C')
156 173 pass
157 174
158 175 def _get_template_context(self, tmpl_args):
159 176 self._register_global_c(tmpl_args)
160 177
161 178 local_tmpl_args = {
162 179 'defaults': {},
163 180 'errors': {},
164 181 # register a fake 'c' to be used in templates instead of global
165 182 # pylons c, after migration to pyramid we should rename it to 'c'
166 183 # make sure we replace usage of _c in templates too
167 184 '_c': tmpl_args
168 185 }
169 186 local_tmpl_args.update(tmpl_args)
170 187 return local_tmpl_args
171 188
172 189 def load_default_context(self):
173 190 """
174 191 example:
175 192
176 193 def load_default_context(self):
177 194 c = self._get_local_tmpl_context()
178 195 c.custom_var = 'foobar'
179 196 self._register_global_c(c)
180 197 return c
181 198 """
182 199 raise NotImplementedError('Needs implementation in view class')
183 200
184 201
185 202 class RepoAppView(BaseAppView):
186 203
187 204 def __init__(self, context, request):
188 205 super(RepoAppView, self).__init__(context, request)
189 206 self.db_repo = request.db_repo
190 207 self.db_repo_name = self.db_repo.repo_name
191 208 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
192 209
193 210 def _handle_missing_requirements(self, error):
194 211 log.error(
195 212 'Requirements are missing for repository %s: %s',
196 213 self.db_repo_name, error.message)
197 214
198 215 def _get_local_tmpl_context(self, include_app_defaults=False):
216 _ = self.request.translate
199 217 c = super(RepoAppView, self)._get_local_tmpl_context(
200 218 include_app_defaults=include_app_defaults)
201 219
202 220 # register common vars for this type of view
203 221 c.rhodecode_db_repo = self.db_repo
204 222 c.repo_name = self.db_repo_name
205 223 c.repository_pull_requests = self.db_repo_pull_requests
206 224
207 225 c.repository_requirements_missing = False
208 226 try:
209 227 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
210 228 except RepositoryRequirementError as e:
211 229 c.repository_requirements_missing = True
212 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 243 return c
215 244
216 245 def _get_f_path(self, matchdict, default=None):
217 246 f_path = matchdict.get('f_path')
218 247 if f_path:
219 248 # fix for multiple initial slashes that causes errors for GIT
220 249 return f_path.lstrip('/')
221 250
222 251 return default
223 252
224 253
225 254 class DataGridAppView(object):
226 255 """
227 256 Common class to have re-usable grid rendering components
228 257 """
229 258
230 259 def _extract_ordering(self, request, column_map=None):
231 260 column_map = column_map or {}
232 261 column_index = safe_int(request.GET.get('order[0][column]'))
233 262 order_dir = request.GET.get(
234 263 'order[0][dir]', 'desc')
235 264 order_by = request.GET.get(
236 265 'columns[%s][data][sort]' % column_index, 'name_raw')
237 266
238 267 # translate datatable to DB columns
239 268 order_by = column_map.get(order_by) or order_by
240 269
241 270 search_q = request.GET.get('search[value]')
242 271 return search_q, order_by, order_dir
243 272
244 273 def _extract_chunk(self, request):
245 274 start = safe_int(request.GET.get('start'), 0)
246 275 length = safe_int(request.GET.get('length'), 25)
247 276 draw = safe_int(request.GET.get('draw'))
248 277 return draw, start, length
249 278
250 279
251 280 class BaseReferencesView(RepoAppView):
252 281 """
253 282 Base for reference view for branches, tags and bookmarks.
254 283 """
255 284 def load_default_context(self):
256 285 c = self._get_local_tmpl_context()
257 286
258 287 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
259 288 c.repo_info = self.db_repo
260 289
261 290 self._register_global_c(c)
262 291 return c
263 292
264 293 def load_refs_context(self, ref_items, partials_template):
265 294 _render = self.request.get_partial_renderer(partials_template)
266 295 pre_load = ["author", "date", "message"]
267 296
268 297 is_svn = h.is_svn(self.rhodecode_vcs_repo)
269 298 is_hg = h.is_hg(self.rhodecode_vcs_repo)
270 299
271 300 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
272 301
273 302 closed_refs = {}
274 303 if is_hg:
275 304 closed_refs = self.rhodecode_vcs_repo.branches_closed
276 305
277 306 data = []
278 307 for ref_name, commit_id in ref_items:
279 308 commit = self.rhodecode_vcs_repo.get_commit(
280 309 commit_id=commit_id, pre_load=pre_load)
281 310 closed = ref_name in closed_refs
282 311
283 312 # TODO: johbo: Unify generation of reference links
284 313 use_commit_id = '/' in ref_name or is_svn
285 314
286 315 if use_commit_id:
287 316 files_url = h.route_path(
288 317 'repo_files',
289 318 repo_name=self.db_repo_name,
290 319 f_path=ref_name if is_svn else '',
291 320 commit_id=commit_id)
292 321
293 322 else:
294 323 files_url = h.route_path(
295 324 'repo_files',
296 325 repo_name=self.db_repo_name,
297 326 f_path=ref_name if is_svn else '',
298 327 commit_id=ref_name,
299 328 _query=dict(at=ref_name))
300 329
301 330 data.append({
302 331 "name": _render('name', ref_name, files_url, closed),
303 332 "name_raw": ref_name,
304 333 "date": _render('date', commit.date),
305 334 "date_raw": datetime_to_time(commit.date),
306 335 "author": _render('author', commit.author),
307 336 "commit": _render(
308 337 'commit', commit.message, commit.raw_id, commit.idx),
309 338 "commit_raw": commit.idx,
310 339 "compare": _render(
311 340 'compare', format_ref_id(ref_name, commit.raw_id)),
312 341 })
313 342
314 343 return data
315 344
316 345
317 346 class RepoRoutePredicate(object):
318 347 def __init__(self, val, config):
319 348 self.val = val
320 349
321 350 def text(self):
322 351 return 'repo_route = %s' % self.val
323 352
324 353 phash = text
325 354
326 355 def __call__(self, info, request):
327 356
328 357 if hasattr(request, 'vcs_call'):
329 358 # skip vcs calls
330 359 return
331 360
332 361 repo_name = info['match']['repo_name']
333 362 repo_model = repo.RepoModel()
334 363 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
335 364
336 365 if by_name_match:
337 366 # register this as request object we can re-use later
338 367 request.db_repo = by_name_match
339 368 return True
340 369
341 370 by_id_match = repo_model.get_repo_by_id(repo_name)
342 371 if by_id_match:
343 372 request.db_repo = by_id_match
344 373 return True
345 374
346 375 return False
347 376
348 377
349 378 class RepoTypeRoutePredicate(object):
350 379 def __init__(self, val, config):
351 380 self.val = val or ['hg', 'git', 'svn']
352 381
353 382 def text(self):
354 383 return 'repo_accepted_type = %s' % self.val
355 384
356 385 phash = text
357 386
358 387 def __call__(self, info, request):
359 388 if hasattr(request, 'vcs_call'):
360 389 # skip vcs calls
361 390 return
362 391
363 392 rhodecode_db_repo = request.db_repo
364 393
365 394 log.debug(
366 395 '%s checking repo type for %s in %s',
367 396 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
368 397
369 398 if rhodecode_db_repo.repo_type in self.val:
370 399 return True
371 400 else:
372 401 log.warning('Current view is not supported for repo type:%s',
373 402 rhodecode_db_repo.repo_type)
374 403 #
375 404 # h.flash(h.literal(
376 405 # _('Action not supported for %s.' % rhodecode_repo.alias)),
377 406 # category='warning')
378 407 # return redirect(
379 408 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
380 409
381 410 return False
382 411
383 412
384 413 class RepoGroupRoutePredicate(object):
385 414 def __init__(self, val, config):
386 415 self.val = val
387 416
388 417 def text(self):
389 418 return 'repo_group_route = %s' % self.val
390 419
391 420 phash = text
392 421
393 422 def __call__(self, info, request):
394 423 if hasattr(request, 'vcs_call'):
395 424 # skip vcs calls
396 425 return
397 426
398 427 repo_group_name = info['match']['repo_group_name']
399 428 repo_group_model = repo_group.RepoGroupModel()
400 429 by_name_match = repo_group_model.get_by_group_name(
401 430 repo_group_name, cache=True)
402 431
403 432 if by_name_match:
404 433 # register this as request object we can re-use later
405 434 request.db_repo_group = by_name_match
406 435 return True
407 436
408 437 return False
409 438
410 439
411 440 def includeme(config):
412 441 config.add_route_predicate(
413 442 'repo_route', RepoRoutePredicate)
414 443 config.add_route_predicate(
415 444 'repo_accepted_types', RepoTypeRoutePredicate)
416 445 config.add_route_predicate(
417 446 'repo_group_route', RepoGroupRoutePredicate)
@@ -1,494 +1,523 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import re
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.compat import OrderedDict
29 from rhodecode.lib.utils2 import AttributeDict
29 from rhodecode.lib.utils2 import AttributeDict, safe_str
30 30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
31 31 from rhodecode.model.db import Repository
32 32 from rhodecode.model.meta import Session
33 33 from rhodecode.model.repo import RepoModel
34 34 from rhodecode.model.scm import ScmModel
35 35 from rhodecode.tests import assert_session_flash
36 36 from rhodecode.tests.fixture import Fixture
37 37 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
38 38
39 39
40 40 fixture = Fixture()
41 41
42 42
43 43 def route_path(name, params=None, **kwargs):
44 44 import urllib
45 45
46 46 base_url = {
47 47 'repo_summary': '/{repo_name}',
48 48 'repo_stats': '/{repo_name}/repo_stats/{commit_id}',
49 49 'repo_refs_data': '/{repo_name}/refs-data',
50 50 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog'
51 51
52 52 }[name].format(**kwargs)
53 53
54 54 if params:
55 55 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
56 56 return base_url
57 57
58 58
59 59 @pytest.mark.usefixtures('app')
60 60 class TestSummaryView(object):
61 61 def test_index(self, autologin_user, backend, http_host_only_stub):
62 62 repo_id = backend.repo.repo_id
63 63 repo_name = backend.repo_name
64 64 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
65 65 return_value=False):
66 66 response = self.app.get(
67 67 route_path('repo_summary', repo_name=repo_name))
68 68
69 69 # repo type
70 70 response.mustcontain(
71 71 '<i class="icon-%s">' % (backend.alias, )
72 72 )
73 73 # public/private
74 74 response.mustcontain(
75 75 """<i class="icon-unlock-alt">"""
76 76 )
77 77
78 78 # clone url...
79 79 response.mustcontain(
80 80 'id="clone_url" readonly="readonly"'
81 81 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
82 82 response.mustcontain(
83 83 'id="clone_url_id" readonly="readonly"'
84 84 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
85 85
86 86 def test_index_svn_without_proxy(
87 87 self, autologin_user, backend_svn, http_host_only_stub):
88 88 repo_id = backend_svn.repo.repo_id
89 89 repo_name = backend_svn.repo_name
90 90 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
91 91 # clone url...
92 92 response.mustcontain(
93 93 'id="clone_url" disabled'
94 94 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
95 95 response.mustcontain(
96 96 'id="clone_url_id" disabled'
97 97 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
98 98
99 99 def test_index_with_trailing_slash(
100 100 self, autologin_user, backend, http_host_only_stub):
101 101
102 102 repo_id = backend.repo.repo_id
103 103 repo_name = backend.repo_name
104 104 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
105 105 return_value=False):
106 106 response = self.app.get(
107 107 route_path('repo_summary', repo_name=repo_name) + '/',
108 108 status=200)
109 109
110 110 # clone url...
111 111 response.mustcontain(
112 112 'id="clone_url" readonly="readonly"'
113 113 ' value="http://test_admin@%s/%s"' % (http_host_only_stub, repo_name, ))
114 114 response.mustcontain(
115 115 'id="clone_url_id" readonly="readonly"'
116 116 ' value="http://test_admin@%s/_%s"' % (http_host_only_stub, repo_id, ))
117 117
118 118 def test_index_by_id(self, autologin_user, backend):
119 119 repo_id = backend.repo.repo_id
120 120 response = self.app.get(
121 121 route_path('repo_summary', repo_name='_%s' % (repo_id,)))
122 122
123 123 # repo type
124 124 response.mustcontain(
125 125 '<i class="icon-%s">' % (backend.alias, )
126 126 )
127 127 # public/private
128 128 response.mustcontain(
129 129 """<i class="icon-unlock-alt">"""
130 130 )
131 131
132 132 def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user):
133 133 fixture.create_repo(name='repo_1')
134 134 response = self.app.get(route_path('repo_summary', repo_name='repo_1'))
135 135
136 136 try:
137 137 response.mustcontain("repo_1")
138 138 finally:
139 139 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
140 140 Session().commit()
141 141
142 142 def test_index_with_anonymous_access_disabled(
143 143 self, backend, disable_anonymous_user):
144 144 response = self.app.get(
145 145 route_path('repo_summary', repo_name=backend.repo_name), status=302)
146 146 assert 'login' in response.location
147 147
148 148 def _enable_stats(self, repo):
149 149 r = Repository.get_by_repo_name(repo)
150 150 r.enable_statistics = True
151 151 Session().add(r)
152 152 Session().commit()
153 153
154 154 expected_trending = {
155 155 'hg': {
156 156 "py": {"count": 68, "desc": ["Python"]},
157 157 "rst": {"count": 16, "desc": ["Rst"]},
158 158 "css": {"count": 2, "desc": ["Css"]},
159 159 "sh": {"count": 2, "desc": ["Bash"]},
160 160 "bat": {"count": 1, "desc": ["Batch"]},
161 161 "cfg": {"count": 1, "desc": ["Ini"]},
162 162 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
163 163 "ini": {"count": 1, "desc": ["Ini"]},
164 164 "js": {"count": 1, "desc": ["Javascript"]},
165 165 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
166 166 },
167 167 'git': {
168 168 "py": {"count": 68, "desc": ["Python"]},
169 169 "rst": {"count": 16, "desc": ["Rst"]},
170 170 "css": {"count": 2, "desc": ["Css"]},
171 171 "sh": {"count": 2, "desc": ["Bash"]},
172 172 "bat": {"count": 1, "desc": ["Batch"]},
173 173 "cfg": {"count": 1, "desc": ["Ini"]},
174 174 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
175 175 "ini": {"count": 1, "desc": ["Ini"]},
176 176 "js": {"count": 1, "desc": ["Javascript"]},
177 177 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
178 178 },
179 179 'svn': {
180 180 "py": {"count": 75, "desc": ["Python"]},
181 181 "rst": {"count": 16, "desc": ["Rst"]},
182 182 "html": {"count": 11, "desc": ["EvoqueHtml", "Html"]},
183 183 "css": {"count": 2, "desc": ["Css"]},
184 184 "bat": {"count": 1, "desc": ["Batch"]},
185 185 "cfg": {"count": 1, "desc": ["Ini"]},
186 186 "ini": {"count": 1, "desc": ["Ini"]},
187 187 "js": {"count": 1, "desc": ["Javascript"]},
188 188 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]},
189 189 "sh": {"count": 1, "desc": ["Bash"]}
190 190 },
191 191 }
192 192
193 193 def test_repo_stats(self, autologin_user, backend, xhr_header):
194 194 response = self.app.get(
195 195 route_path(
196 196 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
197 197 extra_environ=xhr_header,
198 198 status=200)
199 199 assert re.match(r'6[\d\.]+ KiB', response.json['size'])
200 200
201 201 def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header):
202 202 repo_name = backend.repo_name
203 203
204 204 # codes stats
205 205 self._enable_stats(repo_name)
206 206 ScmModel().mark_for_invalidation(repo_name)
207 207
208 208 response = self.app.get(
209 209 route_path(
210 210 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
211 211 extra_environ=xhr_header,
212 212 status=200)
213 213
214 214 expected_data = self.expected_trending[backend.alias]
215 215 returned_stats = response.json['code_stats']
216 216 for k, v in expected_data.items():
217 217 assert v == returned_stats[k]
218 218
219 219 def test_repo_refs_data(self, backend):
220 220 response = self.app.get(
221 221 route_path('repo_refs_data', repo_name=backend.repo_name),
222 222 status=200)
223 223
224 224 # Ensure that there is the correct amount of items in the result
225 225 repo = backend.repo.scm_instance()
226 226 data = response.json['results']
227 227 items = sum(len(section['children']) for section in data)
228 228 repo_refs = len(repo.branches) + len(repo.tags) + len(repo.bookmarks)
229 229 assert items == repo_refs
230 230
231 231 def test_index_shows_missing_requirements_message(
232 232 self, backend, autologin_user):
233 233 repo_name = backend.repo_name
234 234 scm_patcher = mock.patch.object(
235 235 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
236 236
237 237 with scm_patcher:
238 238 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
239 239 assert_response = AssertResponse(response)
240 240 assert_response.element_contains(
241 241 '.main .alert-warning strong', 'Missing requirements')
242 242 assert_response.element_contains(
243 243 '.main .alert-warning',
244 244 'Commits cannot be displayed, because this repository '
245 245 'uses one or more extensions, which was not enabled.')
246 246
247 247 def test_missing_requirements_page_does_not_contains_switch_to(
248 248 self, autologin_user, backend):
249 249 repo_name = backend.repo_name
250 250 scm_patcher = mock.patch.object(
251 251 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
252 252
253 253 with scm_patcher:
254 254 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
255 255 response.mustcontain(no='Switch To')
256 256
257 257
258 258 @pytest.mark.usefixtures('app')
259 259 class TestRepoLocation(object):
260 260
261 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 264 repo = backend.create_repo(name_suffix=suffix)
264 265 repo_name = repo.repo_name
265 266
266 267 # delete from file system
267 268 RepoModel()._delete_filesystem_repo(repo)
268 269
269 270 # test if the repo is still in the database
270 271 new_repo = RepoModel().get_by_repo_name(repo_name)
271 272 assert new_repo.repo_name == repo_name
272 273
273 274 # check if repo is not in the filesystem
274 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):
278 # run the check page that triggers the other flash message
279 response = self.app.get(h.url('repo_check_home', repo_name=repo_name))
280 assert_session_flash(
281 response, 'The repository at %s cannot be located.' % repo_name)
293 # test if the repo is still in the database
294 new_repo = RepoModel().get_by_repo_name(repo_name)
295 assert new_repo.repo_name == repo_name
296
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 313 @pytest.fixture()
285 314 def summary_view(context_stub, request_stub, user_util):
286 315 """
287 316 Bootstrap view to test the view functions
288 317 """
289 318 request_stub.matched_route = AttributeDict(name='test_view')
290 319
291 320 request_stub.user = user_util.create_user().AuthUser
292 321 request_stub.db_repo = user_util.create_repo()
293 322
294 323 view = RepoSummaryView(context=context_stub, request=request_stub)
295 324 return view
296 325
297 326
298 327 @pytest.mark.usefixtures('app')
299 328 class TestCreateReferenceData(object):
300 329
301 330 @pytest.fixture
302 331 def example_refs(self):
303 332 section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id')))
304 333 example_refs = [
305 334 ('section_1', section_1_refs, 't1'),
306 335 ('section_2', {'c': 'c_id'}, 't2'),
307 336 ]
308 337 return example_refs
309 338
310 339 def test_generates_refs_based_on_commit_ids(self, example_refs, summary_view):
311 340 repo = mock.Mock()
312 341 repo.name = 'test-repo'
313 342 repo.alias = 'git'
314 343 full_repo_name = 'pytest-repo-group/' + repo.name
315 344
316 345 result = summary_view._create_reference_data(
317 346 repo, full_repo_name, example_refs)
318 347
319 348 expected_files_url = '/{}/files/'.format(full_repo_name)
320 349 expected_result = [
321 350 {
322 351 'children': [
323 352 {
324 353 'id': 'a', 'raw_id': 'a_id', 'text': 'a', 'type': 't1',
325 354 'files_url': expected_files_url + 'a/?at=a',
326 355 },
327 356 {
328 357 'id': 'b', 'raw_id': 'b_id', 'text': 'b', 'type': 't1',
329 358 'files_url': expected_files_url + 'b/?at=b',
330 359 }
331 360 ],
332 361 'text': 'section_1'
333 362 },
334 363 {
335 364 'children': [
336 365 {
337 366 'id': 'c', 'raw_id': 'c_id', 'text': 'c', 'type': 't2',
338 367 'files_url': expected_files_url + 'c/?at=c',
339 368 }
340 369 ],
341 370 'text': 'section_2'
342 371 }]
343 372 assert result == expected_result
344 373
345 374 def test_generates_refs_with_path_for_svn(self, example_refs, summary_view):
346 375 repo = mock.Mock()
347 376 repo.name = 'test-repo'
348 377 repo.alias = 'svn'
349 378 full_repo_name = 'pytest-repo-group/' + repo.name
350 379
351 380 result = summary_view._create_reference_data(
352 381 repo, full_repo_name, example_refs)
353 382
354 383 expected_files_url = '/{}/files/'.format(full_repo_name)
355 384 expected_result = [
356 385 {
357 386 'children': [
358 387 {
359 388 'id': 'a@a_id', 'raw_id': 'a_id',
360 389 'text': 'a', 'type': 't1',
361 390 'files_url': expected_files_url + 'a_id/a?at=a',
362 391 },
363 392 {
364 393 'id': 'b@b_id', 'raw_id': 'b_id',
365 394 'text': 'b', 'type': 't1',
366 395 'files_url': expected_files_url + 'b_id/b?at=b',
367 396 }
368 397 ],
369 398 'text': 'section_1'
370 399 },
371 400 {
372 401 'children': [
373 402 {
374 403 'id': 'c@c_id', 'raw_id': 'c_id',
375 404 'text': 'c', 'type': 't2',
376 405 'files_url': expected_files_url + 'c_id/c?at=c',
377 406 }
378 407 ],
379 408 'text': 'section_2'
380 409 }
381 410 ]
382 411 assert result == expected_result
383 412
384 413
385 414 class TestCreateFilesUrl(object):
386 415
387 416 def test_creates_non_svn_url(self, app, summary_view):
388 417 repo = mock.Mock()
389 418 repo.name = 'abcde'
390 419 full_repo_name = 'test-repo-group/' + repo.name
391 420 ref_name = 'branch1'
392 421 raw_id = 'deadbeef0123456789'
393 422 is_svn = False
394 423
395 424 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
396 425 result = summary_view._create_files_url(
397 426 repo, full_repo_name, ref_name, raw_id, is_svn)
398 427 url_mock.assert_called_once_with(
399 428 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
400 429 f_path='', _query=dict(at=ref_name))
401 430 assert result == url_mock.return_value
402 431
403 432 def test_creates_svn_url(self, app, summary_view):
404 433 repo = mock.Mock()
405 434 repo.name = 'abcde'
406 435 full_repo_name = 'test-repo-group/' + repo.name
407 436 ref_name = 'branch1'
408 437 raw_id = 'deadbeef0123456789'
409 438 is_svn = True
410 439
411 440 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
412 441 result = summary_view._create_files_url(
413 442 repo, full_repo_name, ref_name, raw_id, is_svn)
414 443 url_mock.assert_called_once_with(
415 444 'repo_files', repo_name=full_repo_name, f_path=ref_name,
416 445 commit_id=raw_id, _query=dict(at=ref_name))
417 446 assert result == url_mock.return_value
418 447
419 448 def test_name_has_slashes(self, app, summary_view):
420 449 repo = mock.Mock()
421 450 repo.name = 'abcde'
422 451 full_repo_name = 'test-repo-group/' + repo.name
423 452 ref_name = 'branch1/branch2'
424 453 raw_id = 'deadbeef0123456789'
425 454 is_svn = False
426 455
427 456 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
428 457 result = summary_view._create_files_url(
429 458 repo, full_repo_name, ref_name, raw_id, is_svn)
430 459 url_mock.assert_called_once_with(
431 460 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
432 461 f_path='', _query=dict(at=ref_name))
433 462 assert result == url_mock.return_value
434 463
435 464
436 465 class TestReferenceItems(object):
437 466 repo = mock.Mock()
438 467 repo.name = 'pytest-repo'
439 468 repo_full_name = 'pytest-repo-group/' + repo.name
440 469 ref_type = 'branch'
441 470 fake_url = '/abcde/'
442 471
443 472 @staticmethod
444 473 def _format_function(name, id_):
445 474 return 'format_function_{}_{}'.format(name, id_)
446 475
447 476 def test_creates_required_amount_of_items(self, summary_view):
448 477 amount = 100
449 478 refs = {
450 479 'ref{}'.format(i): '{0:040d}'.format(i)
451 480 for i in range(amount)
452 481 }
453 482
454 483 url_patcher = mock.patch.object(summary_view, '_create_files_url')
455 484 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
456 485 return_value=False)
457 486
458 487 with url_patcher as url_mock, svn_patcher:
459 488 result = summary_view._create_reference_items(
460 489 self.repo, self.repo_full_name, refs, self.ref_type,
461 490 self._format_function)
462 491 assert len(result) == amount
463 492 assert url_mock.call_count == amount
464 493
465 494 def test_single_item_details(self, summary_view):
466 495 ref_name = 'ref1'
467 496 ref_id = 'deadbeef'
468 497 refs = {
469 498 ref_name: ref_id
470 499 }
471 500
472 501 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
473 502 return_value=False)
474 503
475 504 url_patcher = mock.patch.object(
476 505 summary_view, '_create_files_url', return_value=self.fake_url)
477 506
478 507 with url_patcher as url_mock, svn_patcher:
479 508 result = summary_view._create_reference_items(
480 509 self.repo, self.repo_full_name, refs, self.ref_type,
481 510 self._format_function)
482 511
483 512 url_mock.assert_called_once_with(
484 513 self.repo, self.repo_full_name, ref_name, ref_id, False)
485 514 expected_result = [
486 515 {
487 516 'text': ref_name,
488 517 'id': self._format_function(ref_name, ref_id),
489 518 'raw_id': ref_id,
490 519 'type': self.ref_type,
491 520 'files_url': self.fake_url
492 521 }
493 522 ]
494 523 assert result == expected_result
General Comments 0
You need to be logged in to leave comments. Login now