##// END OF EJS Templates
commits/summary: unify fetching remote attribute in summary and commits page to properly and in the same way show the data.
super-admin -
r4750:8532c1ca default
parent child Browse files
Show More
@@ -1,816 +1,821 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 import operator
24 24
25 25 from pyramid import compat
26 26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
27 27
28 28 from rhodecode.lib import helpers as h, diffs, rc_cache
29 29 from rhodecode.lib.utils2 import (
30 30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
31 31 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
32 32 from rhodecode.lib.vcs.backends.base import EmptyCommit
33 33 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
34 34 from rhodecode.model import repo
35 35 from rhodecode.model import repo_group
36 36 from rhodecode.model import user_group
37 37 from rhodecode.model import user
38 38 from rhodecode.model.db import User
39 39 from rhodecode.model.scm import ScmModel
40 40 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
41 41 from rhodecode.model.repo import ReadmeFinder
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 ADMIN_PREFIX = '/_admin'
47 47 STATIC_FILE_PREFIX = '/_static'
48 48
49 49 URL_NAME_REQUIREMENTS = {
50 50 # group name can have a slash in them, but they must not end with a slash
51 51 'group_name': r'.*?[^/]',
52 52 'repo_group_name': r'.*?[^/]',
53 53 # repo names can have a slash in them, but they must not end with a slash
54 54 'repo_name': r'.*?[^/]',
55 55 # file path eats up everything at the end
56 56 'f_path': r'.*',
57 57 # reference types
58 58 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
59 59 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
60 60 }
61 61
62 62
63 63 def add_route_with_slash(config,name, pattern, **kw):
64 64 config.add_route(name, pattern, **kw)
65 65 if not pattern.endswith('/'):
66 66 config.add_route(name + '_slash', pattern + '/', **kw)
67 67
68 68
69 69 def add_route_requirements(route_path, requirements=None):
70 70 """
71 71 Adds regex requirements to pyramid routes using a mapping dict
72 72 e.g::
73 73 add_route_requirements('{repo_name}/settings')
74 74 """
75 75 requirements = requirements or URL_NAME_REQUIREMENTS
76 76 for key, regex in requirements.items():
77 77 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
78 78 return route_path
79 79
80 80
81 81 def get_format_ref_id(repo):
82 82 """Returns a `repo` specific reference formatter function"""
83 83 if h.is_svn(repo):
84 84 return _format_ref_id_svn
85 85 else:
86 86 return _format_ref_id
87 87
88 88
89 89 def _format_ref_id(name, raw_id):
90 90 """Default formatting of a given reference `name`"""
91 91 return name
92 92
93 93
94 94 def _format_ref_id_svn(name, raw_id):
95 95 """Special way of formatting a reference for Subversion including path"""
96 96 return '%s@%s' % (name, raw_id)
97 97
98 98
99 99 class TemplateArgs(StrictAttributeDict):
100 100 pass
101 101
102 102
103 103 class BaseAppView(object):
104 104
105 105 def __init__(self, context, request):
106 106 self.request = request
107 107 self.context = context
108 108 self.session = request.session
109 109 if not hasattr(request, 'user'):
110 110 # NOTE(marcink): edge case, we ended up in matched route
111 111 # but probably of web-app context, e.g API CALL/VCS CALL
112 112 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
113 113 log.warning('Unable to process request `%s` in this scope', request)
114 114 raise HTTPBadRequest()
115 115
116 116 self._rhodecode_user = request.user # auth user
117 117 self._rhodecode_db_user = self._rhodecode_user.get_instance()
118 118 self._maybe_needs_password_change(
119 119 request.matched_route.name, self._rhodecode_db_user)
120 120
121 121 def _maybe_needs_password_change(self, view_name, user_obj):
122 122
123 123 dont_check_views = [
124 124 'channelstream_connect'
125 125 ]
126 126 if view_name in dont_check_views:
127 127 return
128 128
129 129 log.debug('Checking if user %s needs password change on view %s',
130 130 user_obj, view_name)
131 131
132 132 skip_user_views = [
133 133 'logout', 'login',
134 134 'my_account_password', 'my_account_password_update'
135 135 ]
136 136
137 137 if not user_obj:
138 138 return
139 139
140 140 if user_obj.username == User.DEFAULT_USER:
141 141 return
142 142
143 143 now = time.time()
144 144 should_change = user_obj.user_data.get('force_password_change')
145 145 change_after = safe_int(should_change) or 0
146 146 if should_change and now > change_after:
147 147 log.debug('User %s requires password change', user_obj)
148 148 h.flash('You are required to change your password', 'warning',
149 149 ignore_duplicate=True)
150 150
151 151 if view_name not in skip_user_views:
152 152 raise HTTPFound(
153 153 self.request.route_path('my_account_password'))
154 154
155 155 def _log_creation_exception(self, e, repo_name):
156 156 _ = self.request.translate
157 157 reason = None
158 158 if len(e.args) == 2:
159 159 reason = e.args[1]
160 160
161 161 if reason == 'INVALID_CERTIFICATE':
162 162 log.exception(
163 163 'Exception creating a repository: invalid certificate')
164 164 msg = (_('Error creating repository %s: invalid certificate')
165 165 % repo_name)
166 166 else:
167 167 log.exception("Exception creating a repository")
168 168 msg = (_('Error creating repository %s')
169 169 % repo_name)
170 170 return msg
171 171
172 172 def _get_local_tmpl_context(self, include_app_defaults=True):
173 173 c = TemplateArgs()
174 174 c.auth_user = self.request.user
175 175 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
176 176 c.rhodecode_user = self.request.user
177 177
178 178 if include_app_defaults:
179 179 from rhodecode.lib.base import attach_context_attributes
180 180 attach_context_attributes(c, self.request, self.request.user.user_id)
181 181
182 182 c.is_super_admin = c.auth_user.is_admin
183 183
184 184 c.can_create_repo = c.is_super_admin
185 185 c.can_create_repo_group = c.is_super_admin
186 186 c.can_create_user_group = c.is_super_admin
187 187
188 188 c.is_delegated_admin = False
189 189
190 190 if not c.auth_user.is_default and not c.is_super_admin:
191 191 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
192 192 user=self.request.user)
193 193 repositories = c.auth_user.repositories_admin or c.can_create_repo
194 194
195 195 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
196 196 user=self.request.user)
197 197 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
198 198
199 199 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
200 200 user=self.request.user)
201 201 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
202 202 # delegated admin can create, or manage some objects
203 203 c.is_delegated_admin = repositories or repository_groups or user_groups
204 204 return c
205 205
206 206 def _get_template_context(self, tmpl_args, **kwargs):
207 207
208 208 local_tmpl_args = {
209 209 'defaults': {},
210 210 'errors': {},
211 211 'c': tmpl_args
212 212 }
213 213 local_tmpl_args.update(kwargs)
214 214 return local_tmpl_args
215 215
216 216 def load_default_context(self):
217 217 """
218 218 example:
219 219
220 220 def load_default_context(self):
221 221 c = self._get_local_tmpl_context()
222 222 c.custom_var = 'foobar'
223 223
224 224 return c
225 225 """
226 226 raise NotImplementedError('Needs implementation in view class')
227 227
228 228
229 229 class RepoAppView(BaseAppView):
230 230
231 231 def __init__(self, context, request):
232 232 super(RepoAppView, self).__init__(context, request)
233 233 self.db_repo = request.db_repo
234 234 self.db_repo_name = self.db_repo.repo_name
235 235 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
236 236 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
237 237 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
238 238
239 239 def _handle_missing_requirements(self, error):
240 240 log.error(
241 241 'Requirements are missing for repository %s: %s',
242 242 self.db_repo_name, safe_unicode(error))
243 243
244 244 def _get_local_tmpl_context(self, include_app_defaults=True):
245 245 _ = self.request.translate
246 246 c = super(RepoAppView, self)._get_local_tmpl_context(
247 247 include_app_defaults=include_app_defaults)
248 248
249 249 # register common vars for this type of view
250 250 c.rhodecode_db_repo = self.db_repo
251 251 c.repo_name = self.db_repo_name
252 252 c.repository_pull_requests = self.db_repo_pull_requests
253 253 c.repository_artifacts = self.db_repo_artifacts
254 254 c.repository_is_user_following = ScmModel().is_following_repo(
255 255 self.db_repo_name, self._rhodecode_user.user_id)
256 256 self.path_filter = PathFilter(None)
257 257
258 258 c.repository_requirements_missing = {}
259 259 try:
260 260 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
261 261 # NOTE(marcink):
262 262 # comparison to None since if it's an object __bool__ is expensive to
263 263 # calculate
264 264 if self.rhodecode_vcs_repo is not None:
265 265 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
266 266 c.auth_user.username)
267 267 self.path_filter = PathFilter(path_perms)
268 268 except RepositoryRequirementError as e:
269 269 c.repository_requirements_missing = {'error': str(e)}
270 270 self._handle_missing_requirements(e)
271 271 self.rhodecode_vcs_repo = None
272 272
273 273 c.path_filter = self.path_filter # used by atom_feed_entry.mako
274 274
275 275 if self.rhodecode_vcs_repo is None:
276 276 # unable to fetch this repo as vcs instance, report back to user
277 277 h.flash(_(
278 278 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
279 279 "Please check if it exist, or is not damaged.") %
280 280 {'repo_name': c.repo_name},
281 281 category='error', ignore_duplicate=True)
282 282 if c.repository_requirements_missing:
283 283 route = self.request.matched_route.name
284 284 if route.startswith(('edit_repo', 'repo_summary')):
285 285 # allow summary and edit repo on missing requirements
286 286 return c
287 287
288 288 raise HTTPFound(
289 289 h.route_path('repo_summary', repo_name=self.db_repo_name))
290 290
291 291 else: # redirect if we don't show missing requirements
292 292 raise HTTPFound(h.route_path('home'))
293 293
294 294 c.has_origin_repo_read_perm = False
295 295 if self.db_repo.fork:
296 296 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
297 297 'repository.write', 'repository.read', 'repository.admin')(
298 298 self.db_repo.fork.repo_name, 'summary fork link')
299 299
300 300 return c
301 301
302 302 def _get_f_path_unchecked(self, matchdict, default=None):
303 303 """
304 304 Should only be used by redirects, everything else should call _get_f_path
305 305 """
306 306 f_path = matchdict.get('f_path')
307 307 if f_path:
308 308 # fix for multiple initial slashes that causes errors for GIT
309 309 return f_path.lstrip('/')
310 310
311 311 return default
312 312
313 313 def _get_f_path(self, matchdict, default=None):
314 314 f_path_match = self._get_f_path_unchecked(matchdict, default)
315 315 return self.path_filter.assert_path_permissions(f_path_match)
316 316
317 317 def _get_general_setting(self, target_repo, settings_key, default=False):
318 318 settings_model = VcsSettingsModel(repo=target_repo)
319 319 settings = settings_model.get_general_settings()
320 320 return settings.get(settings_key, default)
321 321
322 322 def _get_repo_setting(self, target_repo, settings_key, default=False):
323 323 settings_model = VcsSettingsModel(repo=target_repo)
324 324 settings = settings_model.get_repo_settings_inherited()
325 325 return settings.get(settings_key, default)
326 326
327 327 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
328 328 log.debug('Looking for README file at path %s', path)
329 329 if commit_id:
330 330 landing_commit_id = commit_id
331 331 else:
332 332 landing_commit = db_repo.get_landing_commit()
333 333 if isinstance(landing_commit, EmptyCommit):
334 334 return None, None
335 335 landing_commit_id = landing_commit.raw_id
336 336
337 337 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
338 338 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
339 339 start = time.time()
340 340
341 341 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
342 342 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
343 343 readme_data = None
344 344 readme_filename = None
345 345
346 346 commit = db_repo.get_commit(_commit_id)
347 347 log.debug("Searching for a README file at commit %s.", _commit_id)
348 348 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
349 349
350 350 if readme_node:
351 351 log.debug('Found README node: %s', readme_node)
352 352 relative_urls = {
353 353 'raw': h.route_path(
354 354 'repo_file_raw', repo_name=_repo_name,
355 355 commit_id=commit.raw_id, f_path=readme_node.path),
356 356 'standard': h.route_path(
357 357 'repo_files', repo_name=_repo_name,
358 358 commit_id=commit.raw_id, f_path=readme_node.path),
359 359 }
360 360 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
361 361 readme_filename = readme_node.unicode_path
362 362
363 363 return readme_data, readme_filename
364 364
365 365 readme_data, readme_filename = generate_repo_readme(
366 366 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
367 367 compute_time = time.time() - start
368 368 log.debug('Repo README for path %s generated and computed in %.4fs',
369 369 path, compute_time)
370 370 return readme_data, readme_filename
371 371
372 372 def _render_readme_or_none(self, commit, readme_node, relative_urls):
373 373 log.debug('Found README file `%s` rendering...', readme_node.path)
374 374 renderer = MarkupRenderer()
375 375 try:
376 376 html_source = renderer.render(
377 377 readme_node.content, filename=readme_node.path)
378 378 if relative_urls:
379 379 return relative_links(html_source, relative_urls)
380 380 return html_source
381 381 except Exception:
382 382 log.exception(
383 383 "Exception while trying to render the README")
384 384
385 385 def get_recache_flag(self):
386 386 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
387 387 flag_val = self.request.GET.get(flag_name)
388 388 if str2bool(flag_val):
389 389 return True
390 390 return False
391 391
392 def get_commit_preload_attrs(cls):
393 pre_load = ['author', 'branch', 'date', 'message', 'parents',
394 'obsolete', 'phase', 'hidden']
395 return pre_load
396
392 397
393 398 class PathFilter(object):
394 399
395 400 # Expects and instance of BasePathPermissionChecker or None
396 401 def __init__(self, permission_checker):
397 402 self.permission_checker = permission_checker
398 403
399 404 def assert_path_permissions(self, path):
400 405 if self.path_access_allowed(path):
401 406 return path
402 407 raise HTTPForbidden()
403 408
404 409 def path_access_allowed(self, path):
405 410 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
406 411 if self.permission_checker:
407 412 has_access = path and self.permission_checker.has_access(path)
408 413 log.debug('ACL Permissions checker enabled, ACL Check has_access: %s', has_access)
409 414 return has_access
410 415
411 416 log.debug('ACL permissions checker not enabled, skipping...')
412 417 return True
413 418
414 419 def filter_patchset(self, patchset):
415 420 if not self.permission_checker or not patchset:
416 421 return patchset, False
417 422 had_filtered = False
418 423 filtered_patchset = []
419 424 for patch in patchset:
420 425 filename = patch.get('filename', None)
421 426 if not filename or self.permission_checker.has_access(filename):
422 427 filtered_patchset.append(patch)
423 428 else:
424 429 had_filtered = True
425 430 if had_filtered:
426 431 if isinstance(patchset, diffs.LimitedDiffContainer):
427 432 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
428 433 return filtered_patchset, True
429 434 else:
430 435 return patchset, False
431 436
432 437 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
433 438 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
434 439 result = diffset.render_patchset(
435 440 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
436 441 result.has_hidden_changes = has_hidden_changes
437 442 return result
438 443
439 444 def get_raw_patch(self, diff_processor):
440 445 if self.permission_checker is None:
441 446 return diff_processor.as_raw()
442 447 elif self.permission_checker.has_full_access:
443 448 return diff_processor.as_raw()
444 449 else:
445 450 return '# Repository has user-specific filters, raw patch generation is disabled.'
446 451
447 452 @property
448 453 def is_enabled(self):
449 454 return self.permission_checker is not None
450 455
451 456
452 457 class RepoGroupAppView(BaseAppView):
453 458 def __init__(self, context, request):
454 459 super(RepoGroupAppView, self).__init__(context, request)
455 460 self.db_repo_group = request.db_repo_group
456 461 self.db_repo_group_name = self.db_repo_group.group_name
457 462
458 463 def _get_local_tmpl_context(self, include_app_defaults=True):
459 464 _ = self.request.translate
460 465 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
461 466 include_app_defaults=include_app_defaults)
462 467 c.repo_group = self.db_repo_group
463 468 return c
464 469
465 470 def _revoke_perms_on_yourself(self, form_result):
466 471 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
467 472 form_result['perm_updates'])
468 473 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
469 474 form_result['perm_additions'])
470 475 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
471 476 form_result['perm_deletions'])
472 477 admin_perm = 'group.admin'
473 478 if _updates and _updates[0][1] != admin_perm or \
474 479 _additions and _additions[0][1] != admin_perm or \
475 480 _deletions and _deletions[0][1] != admin_perm:
476 481 return True
477 482 return False
478 483
479 484
480 485 class UserGroupAppView(BaseAppView):
481 486 def __init__(self, context, request):
482 487 super(UserGroupAppView, self).__init__(context, request)
483 488 self.db_user_group = request.db_user_group
484 489 self.db_user_group_name = self.db_user_group.users_group_name
485 490
486 491
487 492 class UserAppView(BaseAppView):
488 493 def __init__(self, context, request):
489 494 super(UserAppView, self).__init__(context, request)
490 495 self.db_user = request.db_user
491 496 self.db_user_id = self.db_user.user_id
492 497
493 498 _ = self.request.translate
494 499 if not request.db_user_supports_default:
495 500 if self.db_user.username == User.DEFAULT_USER:
496 501 h.flash(_("Editing user `{}` is disabled.".format(
497 502 User.DEFAULT_USER)), category='warning')
498 503 raise HTTPFound(h.route_path('users'))
499 504
500 505
501 506 class DataGridAppView(object):
502 507 """
503 508 Common class to have re-usable grid rendering components
504 509 """
505 510
506 511 def _extract_ordering(self, request, column_map=None):
507 512 column_map = column_map or {}
508 513 column_index = safe_int(request.GET.get('order[0][column]'))
509 514 order_dir = request.GET.get(
510 515 'order[0][dir]', 'desc')
511 516 order_by = request.GET.get(
512 517 'columns[%s][data][sort]' % column_index, 'name_raw')
513 518
514 519 # translate datatable to DB columns
515 520 order_by = column_map.get(order_by) or order_by
516 521
517 522 search_q = request.GET.get('search[value]')
518 523 return search_q, order_by, order_dir
519 524
520 525 def _extract_chunk(self, request):
521 526 start = safe_int(request.GET.get('start'), 0)
522 527 length = safe_int(request.GET.get('length'), 25)
523 528 draw = safe_int(request.GET.get('draw'))
524 529 return draw, start, length
525 530
526 531 def _get_order_col(self, order_by, model):
527 532 if isinstance(order_by, compat.string_types):
528 533 try:
529 534 return operator.attrgetter(order_by)(model)
530 535 except AttributeError:
531 536 return None
532 537 else:
533 538 return order_by
534 539
535 540
536 541 class BaseReferencesView(RepoAppView):
537 542 """
538 543 Base for reference view for branches, tags and bookmarks.
539 544 """
540 545 def load_default_context(self):
541 546 c = self._get_local_tmpl_context()
542 547 return c
543 548
544 549 def load_refs_context(self, ref_items, partials_template):
545 550 _render = self.request.get_partial_renderer(partials_template)
546 551 pre_load = ["author", "date", "message", "parents"]
547 552
548 553 is_svn = h.is_svn(self.rhodecode_vcs_repo)
549 554 is_hg = h.is_hg(self.rhodecode_vcs_repo)
550 555
551 556 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
552 557
553 558 closed_refs = {}
554 559 if is_hg:
555 560 closed_refs = self.rhodecode_vcs_repo.branches_closed
556 561
557 562 data = []
558 563 for ref_name, commit_id in ref_items:
559 564 commit = self.rhodecode_vcs_repo.get_commit(
560 565 commit_id=commit_id, pre_load=pre_load)
561 566 closed = ref_name in closed_refs
562 567
563 568 # TODO: johbo: Unify generation of reference links
564 569 use_commit_id = '/' in ref_name or is_svn
565 570
566 571 if use_commit_id:
567 572 files_url = h.route_path(
568 573 'repo_files',
569 574 repo_name=self.db_repo_name,
570 575 f_path=ref_name if is_svn else '',
571 576 commit_id=commit_id,
572 577 _query=dict(at=ref_name)
573 578 )
574 579
575 580 else:
576 581 files_url = h.route_path(
577 582 'repo_files',
578 583 repo_name=self.db_repo_name,
579 584 f_path=ref_name if is_svn else '',
580 585 commit_id=ref_name,
581 586 _query=dict(at=ref_name)
582 587 )
583 588
584 589 data.append({
585 590 "name": _render('name', ref_name, files_url, closed),
586 591 "name_raw": ref_name,
587 592 "date": _render('date', commit.date),
588 593 "date_raw": datetime_to_time(commit.date),
589 594 "author": _render('author', commit.author),
590 595 "commit": _render(
591 596 'commit', commit.message, commit.raw_id, commit.idx),
592 597 "commit_raw": commit.idx,
593 598 "compare": _render(
594 599 'compare', format_ref_id(ref_name, commit.raw_id)),
595 600 })
596 601
597 602 return data
598 603
599 604
600 605 class RepoRoutePredicate(object):
601 606 def __init__(self, val, config):
602 607 self.val = val
603 608
604 609 def text(self):
605 610 return 'repo_route = %s' % self.val
606 611
607 612 phash = text
608 613
609 614 def __call__(self, info, request):
610 615 if hasattr(request, 'vcs_call'):
611 616 # skip vcs calls
612 617 return
613 618
614 619 repo_name = info['match']['repo_name']
615 620 repo_model = repo.RepoModel()
616 621
617 622 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
618 623
619 624 def redirect_if_creating(route_info, db_repo):
620 625 skip_views = ['edit_repo_advanced_delete']
621 626 route = route_info['route']
622 627 # we should skip delete view so we can actually "remove" repositories
623 628 # if they get stuck in creating state.
624 629 if route.name in skip_views:
625 630 return
626 631
627 632 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
628 633 repo_creating_url = request.route_path(
629 634 'repo_creating', repo_name=db_repo.repo_name)
630 635 raise HTTPFound(repo_creating_url)
631 636
632 637 if by_name_match:
633 638 # register this as request object we can re-use later
634 639 request.db_repo = by_name_match
635 640 redirect_if_creating(info, by_name_match)
636 641 return True
637 642
638 643 by_id_match = repo_model.get_repo_by_id(repo_name)
639 644 if by_id_match:
640 645 request.db_repo = by_id_match
641 646 redirect_if_creating(info, by_id_match)
642 647 return True
643 648
644 649 return False
645 650
646 651
647 652 class RepoForbidArchivedRoutePredicate(object):
648 653 def __init__(self, val, config):
649 654 self.val = val
650 655
651 656 def text(self):
652 657 return 'repo_forbid_archived = %s' % self.val
653 658
654 659 phash = text
655 660
656 661 def __call__(self, info, request):
657 662 _ = request.translate
658 663 rhodecode_db_repo = request.db_repo
659 664
660 665 log.debug(
661 666 '%s checking if archived flag for repo for %s',
662 667 self.__class__.__name__, rhodecode_db_repo.repo_name)
663 668
664 669 if rhodecode_db_repo.archived:
665 670 log.warning('Current view is not supported for archived repo:%s',
666 671 rhodecode_db_repo.repo_name)
667 672
668 673 h.flash(
669 674 h.literal(_('Action not supported for archived repository.')),
670 675 category='warning')
671 676 summary_url = request.route_path(
672 677 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
673 678 raise HTTPFound(summary_url)
674 679 return True
675 680
676 681
677 682 class RepoTypeRoutePredicate(object):
678 683 def __init__(self, val, config):
679 684 self.val = val or ['hg', 'git', 'svn']
680 685
681 686 def text(self):
682 687 return 'repo_accepted_type = %s' % self.val
683 688
684 689 phash = text
685 690
686 691 def __call__(self, info, request):
687 692 if hasattr(request, 'vcs_call'):
688 693 # skip vcs calls
689 694 return
690 695
691 696 rhodecode_db_repo = request.db_repo
692 697
693 698 log.debug(
694 699 '%s checking repo type for %s in %s',
695 700 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
696 701
697 702 if rhodecode_db_repo.repo_type in self.val:
698 703 return True
699 704 else:
700 705 log.warning('Current view is not supported for repo type:%s',
701 706 rhodecode_db_repo.repo_type)
702 707 return False
703 708
704 709
705 710 class RepoGroupRoutePredicate(object):
706 711 def __init__(self, val, config):
707 712 self.val = val
708 713
709 714 def text(self):
710 715 return 'repo_group_route = %s' % self.val
711 716
712 717 phash = text
713 718
714 719 def __call__(self, info, request):
715 720 if hasattr(request, 'vcs_call'):
716 721 # skip vcs calls
717 722 return
718 723
719 724 repo_group_name = info['match']['repo_group_name']
720 725 repo_group_model = repo_group.RepoGroupModel()
721 726 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
722 727
723 728 if by_name_match:
724 729 # register this as request object we can re-use later
725 730 request.db_repo_group = by_name_match
726 731 return True
727 732
728 733 return False
729 734
730 735
731 736 class UserGroupRoutePredicate(object):
732 737 def __init__(self, val, config):
733 738 self.val = val
734 739
735 740 def text(self):
736 741 return 'user_group_route = %s' % self.val
737 742
738 743 phash = text
739 744
740 745 def __call__(self, info, request):
741 746 if hasattr(request, 'vcs_call'):
742 747 # skip vcs calls
743 748 return
744 749
745 750 user_group_id = info['match']['user_group_id']
746 751 user_group_model = user_group.UserGroup()
747 752 by_id_match = user_group_model.get(user_group_id, cache=False)
748 753
749 754 if by_id_match:
750 755 # register this as request object we can re-use later
751 756 request.db_user_group = by_id_match
752 757 return True
753 758
754 759 return False
755 760
756 761
757 762 class UserRoutePredicateBase(object):
758 763 supports_default = None
759 764
760 765 def __init__(self, val, config):
761 766 self.val = val
762 767
763 768 def text(self):
764 769 raise NotImplementedError()
765 770
766 771 def __call__(self, info, request):
767 772 if hasattr(request, 'vcs_call'):
768 773 # skip vcs calls
769 774 return
770 775
771 776 user_id = info['match']['user_id']
772 777 user_model = user.User()
773 778 by_id_match = user_model.get(user_id, cache=False)
774 779
775 780 if by_id_match:
776 781 # register this as request object we can re-use later
777 782 request.db_user = by_id_match
778 783 request.db_user_supports_default = self.supports_default
779 784 return True
780 785
781 786 return False
782 787
783 788
784 789 class UserRoutePredicate(UserRoutePredicateBase):
785 790 supports_default = False
786 791
787 792 def text(self):
788 793 return 'user_route = %s' % self.val
789 794
790 795 phash = text
791 796
792 797
793 798 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
794 799 supports_default = True
795 800
796 801 def text(self):
797 802 return 'user_with_default_route = %s' % self.val
798 803
799 804 phash = text
800 805
801 806
802 807 def includeme(config):
803 808 config.add_route_predicate(
804 809 'repo_route', RepoRoutePredicate)
805 810 config.add_route_predicate(
806 811 'repo_accepted_types', RepoTypeRoutePredicate)
807 812 config.add_route_predicate(
808 813 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
809 814 config.add_route_predicate(
810 815 'repo_group_route', RepoGroupRoutePredicate)
811 816 config.add_route_predicate(
812 817 'user_group_route', UserGroupRoutePredicate)
813 818 config.add_route_predicate(
814 819 'user_route_with_default', UserRouteWithDefaultPredicate)
815 820 config.add_route_predicate(
816 821 'user_route', UserRoutePredicate)
@@ -1,358 +1,355 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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
22 22 import logging
23 23
24 24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
25 25
26 26 from pyramid.renderers import render
27 27 from pyramid.response import Response
28 28
29 29 from rhodecode.apps._base import RepoAppView
30 30 import rhodecode.lib.helpers as h
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, HasRepoPermissionAnyDecorator)
33 33
34 34 from rhodecode.lib.ext_json import json
35 35 from rhodecode.lib.graphmod import _colored, _dagwalker
36 36 from rhodecode.lib.helpers import RepoPage
37 37 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool, safe_unicode
38 38 from rhodecode.lib.vcs.exceptions import (
39 39 RepositoryError, CommitDoesNotExistError,
40 40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44 DEFAULT_CHANGELOG_SIZE = 20
45 45
46 46
47 47 class RepoChangelogView(RepoAppView):
48 48
49 49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
50 50 """
51 51 This is a safe way to get commit. If an error occurs it redirects to
52 52 tip with proper message
53 53
54 54 :param commit_id: id of commit to fetch
55 55 :param redirect_after: toggle redirection
56 56 """
57 57 _ = self.request.translate
58 58
59 59 try:
60 60 return self.rhodecode_vcs_repo.get_commit(commit_id)
61 61 except EmptyRepositoryError:
62 62 if not redirect_after:
63 63 return None
64 64
65 65 h.flash(h.literal(
66 66 _('There are no commits yet')), category='warning')
67 67 raise HTTPFound(
68 68 h.route_path('repo_summary', repo_name=self.db_repo_name))
69 69
70 70 except (CommitDoesNotExistError, LookupError):
71 71 msg = _('No such commit exists for this repository')
72 72 h.flash(msg, category='error')
73 73 raise HTTPNotFound()
74 74 except RepositoryError as e:
75 75 h.flash(h.escape(safe_str(e)), category='error')
76 76 raise HTTPNotFound()
77 77
78 78 def _graph(self, repo, commits, prev_data=None, next_data=None):
79 79 """
80 80 Generates a DAG graph for repo
81 81
82 82 :param repo: repo instance
83 83 :param commits: list of commits
84 84 """
85 85 if not commits:
86 86 return json.dumps([]), json.dumps([])
87 87
88 88 def serialize(commit, parents=True):
89 89 data = dict(
90 90 raw_id=commit.raw_id,
91 91 idx=commit.idx,
92 92 branch=None,
93 93 )
94 94 if parents:
95 95 data['parents'] = [
96 96 serialize(x, parents=False) for x in commit.parents]
97 97 return data
98 98
99 99 prev_data = prev_data or []
100 100 next_data = next_data or []
101 101
102 102 current = [serialize(x) for x in commits]
103 103 commits = prev_data + current + next_data
104 104
105 105 dag = _dagwalker(repo, commits)
106 106
107 107 data = [[commit_id, vtx, edges, branch]
108 108 for commit_id, vtx, edges, branch in _colored(dag)]
109 109 return json.dumps(data), json.dumps(current)
110 110
111 111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
113 113 h.flash(u'Branch {} is not found.'.format(h.escape(safe_unicode(branch_name))),
114 114 category='warning')
115 115 redirect_url = h.route_path(
116 116 'repo_commits_file', repo_name=repo_name,
117 117 commit_id=branch_name, f_path=f_path or '')
118 118 raise HTTPFound(redirect_url)
119 119
120 120 def _load_changelog_data(
121 121 self, c, collection, page, chunk_size, branch_name=None,
122 122 dynamic=False, f_path=None, commit_id=None):
123 123
124 124 def url_generator(page_num):
125 125 query_params = {
126 126 'page': page_num
127 127 }
128 128
129 129 if branch_name:
130 130 query_params.update({
131 131 'branch': branch_name
132 132 })
133 133
134 134 if f_path:
135 135 # changelog for file
136 136 return h.route_path(
137 137 'repo_commits_file',
138 138 repo_name=c.rhodecode_db_repo.repo_name,
139 139 commit_id=commit_id, f_path=f_path,
140 140 _query=query_params)
141 141 else:
142 142 return h.route_path(
143 143 'repo_commits',
144 144 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
145 145
146 146 c.total_cs = len(collection)
147 147 c.showing_commits = min(chunk_size, c.total_cs)
148 148 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
149 149 items_per_page=chunk_size, url_maker=url_generator)
150 150
151 151 c.next_page = c.pagination.next_page
152 152 c.prev_page = c.pagination.previous_page
153 153
154 154 if dynamic:
155 155 if self.request.GET.get('chunk') != 'next':
156 156 c.next_page = None
157 157 if self.request.GET.get('chunk') != 'prev':
158 158 c.prev_page = None
159 159
160 160 page_commit_ids = [x.raw_id for x in c.pagination]
161 161 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
162 162 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
163 163
164 164 def load_default_context(self):
165 165 c = self._get_local_tmpl_context(include_app_defaults=True)
166 166
167 167 c.rhodecode_repo = self.rhodecode_vcs_repo
168 168
169 169 return c
170 170
171 def _get_preload_attrs(self):
172 pre_load = ['author', 'branch', 'date', 'message', 'parents',
173 'obsolete', 'phase', 'hidden']
174 return pre_load
175
176 171 @LoginRequired()
177 172 @HasRepoPermissionAnyDecorator(
178 173 'repository.read', 'repository.write', 'repository.admin')
179 174 def repo_changelog(self):
180 175 c = self.load_default_context()
181 176
182 177 commit_id = self.request.matchdict.get('commit_id')
183 178 f_path = self._get_f_path(self.request.matchdict)
184 179 show_hidden = str2bool(self.request.GET.get('evolve'))
185 180
186 181 chunk_size = 20
187 182
188 183 c.branch_name = branch_name = self.request.GET.get('branch') or ''
189 184 c.book_name = book_name = self.request.GET.get('bookmark') or ''
190 185 c.f_path = f_path
191 186 c.commit_id = commit_id
192 187 c.show_hidden = show_hidden
193 188
194 189 hist_limit = safe_int(self.request.GET.get('limit')) or None
195 190
196 191 p = safe_int(self.request.GET.get('page', 1), 1)
197 192
198 193 c.selected_name = branch_name or book_name
199 194 if not commit_id and branch_name:
200 195 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
201 196
202 197 c.changelog_for_path = f_path
203 pre_load = self._get_preload_attrs()
198 pre_load = self.get_commit_preload_attrs()
204 199
205 200 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
206 201
207 202 try:
208 203 if f_path:
209 204 log.debug('generating changelog for path %s', f_path)
210 205 # get the history for the file !
211 206 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
212 207
213 208 try:
214 209 collection = base_commit.get_path_history(
215 210 f_path, limit=hist_limit, pre_load=pre_load)
216 211 if collection and partial_xhr:
217 212 # for ajax call we remove first one since we're looking
218 213 # at it right now in the context of a file commit
219 214 collection.pop(0)
220 215 except (NodeDoesNotExistError, CommitError):
221 216 # this node is not present at tip!
222 217 try:
223 218 commit = self._get_commit_or_redirect(commit_id)
224 219 collection = commit.get_path_history(f_path)
225 220 except RepositoryError as e:
226 221 h.flash(safe_str(e), category='warning')
227 222 redirect_url = h.route_path(
228 223 'repo_commits', repo_name=self.db_repo_name)
229 224 raise HTTPFound(redirect_url)
230 225 collection = list(reversed(collection))
231 226 else:
232 227 collection = self.rhodecode_vcs_repo.get_commits(
233 228 branch_name=branch_name, show_hidden=show_hidden,
234 229 pre_load=pre_load, translate_tags=False)
235 230
236 231 self._load_changelog_data(
237 232 c, collection, p, chunk_size, c.branch_name,
238 233 f_path=f_path, commit_id=commit_id)
239 234
240 235 except EmptyRepositoryError as e:
241 236 h.flash(h.escape(safe_str(e)), category='warning')
242 237 raise HTTPFound(
243 238 h.route_path('repo_summary', repo_name=self.db_repo_name))
244 239 except HTTPFound:
245 240 raise
246 241 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
247 242 log.exception(safe_str(e))
248 243 h.flash(h.escape(safe_str(e)), category='error')
249 244
250 245 if commit_id:
251 246 # from single commit page, we redirect to main commits
252 247 raise HTTPFound(
253 248 h.route_path('repo_commits', repo_name=self.db_repo_name))
254 249 else:
255 250 # otherwise we redirect to summary
256 251 raise HTTPFound(
257 252 h.route_path('repo_summary', repo_name=self.db_repo_name))
258 253
254
255
259 256 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
260 257 # case when loading dynamic file history in file view
261 258 # loading from ajax, we don't want the first result, it's popped
262 259 # in the code above
263 260 html = render(
264 261 'rhodecode:templates/commits/changelog_file_history.mako',
265 262 self._get_template_context(c), self.request)
266 263 return Response(html)
267 264
268 265 commit_ids = []
269 266 if not f_path:
270 267 # only load graph data when not in file history mode
271 268 commit_ids = c.pagination
272 269
273 270 c.graph_data, c.graph_commits = self._graph(
274 271 self.rhodecode_vcs_repo, commit_ids)
275 272
276 273 return self._get_template_context(c)
277 274
278 275 @LoginRequired()
279 276 @HasRepoPermissionAnyDecorator(
280 277 'repository.read', 'repository.write', 'repository.admin')
281 278 def repo_commits_elements(self):
282 279 c = self.load_default_context()
283 280 commit_id = self.request.matchdict.get('commit_id')
284 281 f_path = self._get_f_path(self.request.matchdict)
285 282 show_hidden = str2bool(self.request.GET.get('evolve'))
286 283
287 284 chunk_size = 20
288 285 hist_limit = safe_int(self.request.GET.get('limit')) or None
289 286
290 287 def wrap_for_error(err):
291 288 html = '<tr>' \
292 289 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
293 290 '</tr>'.format(err)
294 291 return Response(html)
295 292
296 293 c.branch_name = branch_name = self.request.GET.get('branch') or ''
297 294 c.book_name = book_name = self.request.GET.get('bookmark') or ''
298 295 c.f_path = f_path
299 296 c.commit_id = commit_id
300 297 c.show_hidden = show_hidden
301 298
302 299 c.selected_name = branch_name or book_name
303 300 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
304 301 return wrap_for_error(
305 302 safe_str('Branch: {} is not valid'.format(branch_name)))
306 303
307 pre_load = self._get_preload_attrs()
304 pre_load = self.get_commit_preload_attrs()
308 305
309 306 if f_path:
310 307 try:
311 308 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
312 309 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
313 310 log.exception(safe_str(e))
314 311 raise HTTPFound(
315 312 h.route_path('repo_commits', repo_name=self.db_repo_name))
316 313
317 314 collection = base_commit.get_path_history(
318 315 f_path, limit=hist_limit, pre_load=pre_load)
319 316 collection = list(reversed(collection))
320 317 else:
321 318 collection = self.rhodecode_vcs_repo.get_commits(
322 319 branch_name=branch_name, show_hidden=show_hidden, pre_load=pre_load,
323 320 translate_tags=False)
324 321
325 322 p = safe_int(self.request.GET.get('page', 1), 1)
326 323 try:
327 324 self._load_changelog_data(
328 325 c, collection, p, chunk_size, dynamic=True,
329 326 f_path=f_path, commit_id=commit_id)
330 327 except EmptyRepositoryError as e:
331 328 return wrap_for_error(safe_str(e))
332 329 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
333 330 log.exception('Failed to fetch commits')
334 331 return wrap_for_error(safe_str(e))
335 332
336 333 prev_data = None
337 334 next_data = None
338 335
339 336 try:
340 337 prev_graph = json.loads(self.request.POST.get('graph') or '{}')
341 338 except json.JSONDecodeError:
342 339 prev_graph = {}
343 340
344 341 if self.request.GET.get('chunk') == 'prev':
345 342 next_data = prev_graph
346 343 elif self.request.GET.get('chunk') == 'next':
347 344 prev_data = prev_graph
348 345
349 346 commit_ids = []
350 347 if not f_path:
351 348 # only load graph data when not in file history mode
352 349 commit_ids = c.pagination
353 350
354 351 c.graph_data, c.graph_commits = self._graph(
355 352 self.rhodecode_vcs_repo, commit_ids,
356 353 prev_data=prev_data, next_data=next_data)
357 354
358 355 return self._get_template_context(c)
@@ -1,289 +1,290 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 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 logging
22 22 import string
23 23 import time
24 24
25 25 import rhodecode
26 26
27 27
28 28
29 29 from rhodecode.lib.view_utils import get_format_ref_id
30 30 from rhodecode.apps._base import RepoAppView
31 31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 32 from rhodecode.lib import helpers as h, rc_cache
33 33 from rhodecode.lib.utils2 import safe_str, safe_int
34 34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 35 from rhodecode.lib.ext_json import json
36 36 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 37 from rhodecode.lib.vcs.exceptions import (
38 38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
39 39 from rhodecode.model.db import Statistics, CacheKey, User
40 40 from rhodecode.model.meta import Session
41 41 from rhodecode.model.scm import ScmModel
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class RepoSummaryView(RepoAppView):
47 47
48 48 def load_default_context(self):
49 49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 50 c.rhodecode_repo = None
51 51 if not c.repository_requirements_missing:
52 52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 53 return c
54 54
55 55 def _load_commits_context(self, c):
56 56 p = safe_int(self.request.GET.get('page'), 1)
57 57 size = safe_int(self.request.GET.get('size'), 10)
58 58
59 59 def url_generator(page_num):
60 60 query_params = {
61 61 'page': page_num,
62 62 'size': size
63 63 }
64 64 return h.route_path(
65 65 'repo_summary_commits',
66 66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
67 67
68 pre_load = ['author', 'branch', 'date', 'message']
68 pre_load = self.get_commit_preload_attrs()
69
69 70 try:
70 71 collection = self.rhodecode_vcs_repo.get_commits(
71 72 pre_load=pre_load, translate_tags=False)
72 73 except EmptyRepositoryError:
73 74 collection = self.rhodecode_vcs_repo
74 75
75 76 c.repo_commits = h.RepoPage(
76 77 collection, page=p, items_per_page=size, url_maker=url_generator)
77 78 page_ids = [x.raw_id for x in c.repo_commits]
78 79 c.comments = self.db_repo.get_comments(page_ids)
79 80 c.statuses = self.db_repo.statuses(page_ids)
80 81
81 82 def _prepare_and_set_clone_url(self, c):
82 83 username = ''
83 84 if self._rhodecode_user.username != User.DEFAULT_USER:
84 85 username = safe_str(self._rhodecode_user.username)
85 86
86 87 _def_clone_uri = c.clone_uri_tmpl
87 88 _def_clone_uri_id = c.clone_uri_id_tmpl
88 89 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
89 90
90 91 c.clone_repo_url = self.db_repo.clone_url(
91 92 user=username, uri_tmpl=_def_clone_uri)
92 93 c.clone_repo_url_id = self.db_repo.clone_url(
93 94 user=username, uri_tmpl=_def_clone_uri_id)
94 95 c.clone_repo_url_ssh = self.db_repo.clone_url(
95 96 uri_tmpl=_def_clone_uri_ssh, ssh=True)
96 97
97 98 @LoginRequired()
98 99 @HasRepoPermissionAnyDecorator(
99 100 'repository.read', 'repository.write', 'repository.admin')
100 101 def summary_commits(self):
101 102 c = self.load_default_context()
102 103 self._prepare_and_set_clone_url(c)
103 104 self._load_commits_context(c)
104 105 return self._get_template_context(c)
105 106
106 107 @LoginRequired()
107 108 @HasRepoPermissionAnyDecorator(
108 109 'repository.read', 'repository.write', 'repository.admin')
109 110 def summary(self):
110 111 c = self.load_default_context()
111 112
112 113 # Prepare the clone URL
113 114 self._prepare_and_set_clone_url(c)
114 115
115 116 # If enabled, get statistics data
116 117 c.show_stats = bool(self.db_repo.enable_statistics)
117 118
118 119 stats = Session().query(Statistics) \
119 120 .filter(Statistics.repository == self.db_repo) \
120 121 .scalar()
121 122
122 123 c.stats_percentage = 0
123 124
124 125 if stats and stats.languages:
125 126 c.no_data = False is self.db_repo.enable_statistics
126 127 lang_stats_d = json.loads(stats.languages)
127 128
128 129 # Sort first by decreasing count and second by the file extension,
129 130 # so we have a consistent output.
130 131 lang_stats_items = sorted(lang_stats_d.iteritems(),
131 132 key=lambda k: (-k[1], k[0]))[:10]
132 133 lang_stats = [(x, {"count": y,
133 134 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
134 135 for x, y in lang_stats_items]
135 136
136 137 c.trending_languages = json.dumps(lang_stats)
137 138 else:
138 139 c.no_data = True
139 140 c.trending_languages = json.dumps({})
140 141
141 142 scm_model = ScmModel()
142 143 c.enable_downloads = self.db_repo.enable_downloads
143 144 c.repository_followers = scm_model.get_followers(self.db_repo)
144 145 c.repository_forks = scm_model.get_forks(self.db_repo)
145 146
146 147 # first interaction with the VCS instance after here...
147 148 if c.repository_requirements_missing:
148 149 self.request.override_renderer = \
149 150 'rhodecode:templates/summary/missing_requirements.mako'
150 151 return self._get_template_context(c)
151 152
152 153 c.readme_data, c.readme_file = \
153 154 self._get_readme_data(self.db_repo, c.visual.default_renderer)
154 155
155 156 # loads the summary commits template context
156 157 self._load_commits_context(c)
157 158
158 159 return self._get_template_context(c)
159 160
160 161 @LoginRequired()
161 162 @HasRepoPermissionAnyDecorator(
162 163 'repository.read', 'repository.write', 'repository.admin')
163 164 def repo_stats(self):
164 165 show_stats = bool(self.db_repo.enable_statistics)
165 166 repo_id = self.db_repo.repo_id
166 167
167 168 landing_commit = self.db_repo.get_landing_commit()
168 169 if isinstance(landing_commit, EmptyCommit):
169 170 return {'size': 0, 'code_stats': {}}
170 171
171 172 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
172 173 cache_on = cache_seconds > 0
173 174
174 175 log.debug(
175 176 'Computing REPO STATS for repo_id %s commit_id `%s` '
176 177 'with caching: %s[TTL: %ss]' % (
177 178 repo_id, landing_commit, cache_on, cache_seconds or 0))
178 179
179 180 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
180 181 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
181 182
182 183 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
183 184 condition=cache_on)
184 185 def compute_stats(repo_id, commit_id, _show_stats):
185 186 code_stats = {}
186 187 size = 0
187 188 try:
188 189 commit = self.db_repo.get_commit(commit_id)
189 190
190 191 for node in commit.get_filenodes_generator():
191 192 size += node.size
192 193 if not _show_stats:
193 194 continue
194 195 ext = string.lower(node.extension)
195 196 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
196 197 if ext_info:
197 198 if ext in code_stats:
198 199 code_stats[ext]['count'] += 1
199 200 else:
200 201 code_stats[ext] = {"count": 1, "desc": ext_info}
201 202 except (EmptyRepositoryError, CommitDoesNotExistError):
202 203 pass
203 204 return {'size': h.format_byte_size_binary(size),
204 205 'code_stats': code_stats}
205 206
206 207 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
207 208 return stats
208 209
209 210 @LoginRequired()
210 211 @HasRepoPermissionAnyDecorator(
211 212 'repository.read', 'repository.write', 'repository.admin')
212 213 def repo_refs_data(self):
213 214 _ = self.request.translate
214 215 self.load_default_context()
215 216
216 217 repo = self.rhodecode_vcs_repo
217 218 refs_to_create = [
218 219 (_("Branch"), repo.branches, 'branch'),
219 220 (_("Tag"), repo.tags, 'tag'),
220 221 (_("Bookmark"), repo.bookmarks, 'book'),
221 222 ]
222 223 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
223 224 data = {
224 225 'more': False,
225 226 'results': res
226 227 }
227 228 return data
228 229
229 230 @LoginRequired()
230 231 @HasRepoPermissionAnyDecorator(
231 232 'repository.read', 'repository.write', 'repository.admin')
232 233 def repo_refs_changelog_data(self):
233 234 _ = self.request.translate
234 235 self.load_default_context()
235 236
236 237 repo = self.rhodecode_vcs_repo
237 238
238 239 refs_to_create = [
239 240 (_("Branches"), repo.branches, 'branch'),
240 241 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
241 242 # TODO: enable when vcs can handle bookmarks filters
242 243 # (_("Bookmarks"), repo.bookmarks, "book"),
243 244 ]
244 245 res = self._create_reference_data(
245 246 repo, self.db_repo_name, refs_to_create)
246 247 data = {
247 248 'more': False,
248 249 'results': res
249 250 }
250 251 return data
251 252
252 253 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
253 254 format_ref_id = get_format_ref_id(repo)
254 255
255 256 result = []
256 257 for title, refs, ref_type in refs_to_create:
257 258 if refs:
258 259 result.append({
259 260 'text': title,
260 261 'children': self._create_reference_items(
261 262 repo, full_repo_name, refs, ref_type,
262 263 format_ref_id),
263 264 })
264 265 return result
265 266
266 267 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
267 268 result = []
268 269 is_svn = h.is_svn(repo)
269 270 for ref_name, raw_id in refs.iteritems():
270 271 files_url = self._create_files_url(
271 272 repo, full_repo_name, ref_name, raw_id, is_svn)
272 273 result.append({
273 274 'text': ref_name,
274 275 'id': format_ref_id(ref_name, raw_id),
275 276 'raw_id': raw_id,
276 277 'type': ref_type,
277 278 'files_url': files_url,
278 279 'idx': 0,
279 280 })
280 281 return result
281 282
282 283 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
283 284 use_commit_id = '/' in ref_name or is_svn
284 285 return h.route_path(
285 286 'repo_files',
286 287 repo_name=full_repo_name,
287 288 f_path=ref_name if is_svn else '',
288 289 commit_id=raw_id if use_commit_id else ref_name,
289 290 _query=dict(at=ref_name))
General Comments 0
You need to be logged in to leave comments. Login now