##// END OF EJS Templates
repositories: bring back watch action in summary view
marcink -
r3670:4c16050d new-ui
parent child Browse files
Show More
@@ -1,723 +1,725 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid import compat
25 from pyramid import compat
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
27
27
28 from rhodecode.lib import helpers as h, diffs
28 from rhodecode.lib import helpers as h, diffs
29 from rhodecode.lib.utils2 import (
29 from rhodecode.lib.utils2 import (
30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
31 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
31 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
32 from rhodecode.model import repo
32 from rhodecode.model import repo
33 from rhodecode.model import repo_group
33 from rhodecode.model import repo_group
34 from rhodecode.model import user_group
34 from rhodecode.model import user_group
35 from rhodecode.model import user
35 from rhodecode.model import user
36 from rhodecode.model.db import User
36 from rhodecode.model.db import User
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.settings import VcsSettingsModel
38 from rhodecode.model.settings import VcsSettingsModel
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 ADMIN_PREFIX = '/_admin'
43 ADMIN_PREFIX = '/_admin'
44 STATIC_FILE_PREFIX = '/_static'
44 STATIC_FILE_PREFIX = '/_static'
45
45
46 URL_NAME_REQUIREMENTS = {
46 URL_NAME_REQUIREMENTS = {
47 # group name can have a slash in them, but they must not end with a slash
47 # group name can have a slash in them, but they must not end with a slash
48 'group_name': r'.*?[^/]',
48 'group_name': r'.*?[^/]',
49 'repo_group_name': r'.*?[^/]',
49 'repo_group_name': r'.*?[^/]',
50 # repo names can have a slash in them, but they must not end with a slash
50 # repo names can have a slash in them, but they must not end with a slash
51 'repo_name': r'.*?[^/]',
51 'repo_name': r'.*?[^/]',
52 # file path eats up everything at the end
52 # file path eats up everything at the end
53 'f_path': r'.*',
53 'f_path': r'.*',
54 # reference types
54 # reference types
55 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
55 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
56 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
56 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
57 }
57 }
58
58
59
59
60 def add_route_with_slash(config,name, pattern, **kw):
60 def add_route_with_slash(config,name, pattern, **kw):
61 config.add_route(name, pattern, **kw)
61 config.add_route(name, pattern, **kw)
62 if not pattern.endswith('/'):
62 if not pattern.endswith('/'):
63 config.add_route(name + '_slash', pattern + '/', **kw)
63 config.add_route(name + '_slash', pattern + '/', **kw)
64
64
65
65
66 def add_route_requirements(route_path, requirements=None):
66 def add_route_requirements(route_path, requirements=None):
67 """
67 """
68 Adds regex requirements to pyramid routes using a mapping dict
68 Adds regex requirements to pyramid routes using a mapping dict
69 e.g::
69 e.g::
70 add_route_requirements('{repo_name}/settings')
70 add_route_requirements('{repo_name}/settings')
71 """
71 """
72 requirements = requirements or URL_NAME_REQUIREMENTS
72 requirements = requirements or URL_NAME_REQUIREMENTS
73 for key, regex in requirements.items():
73 for key, regex in requirements.items():
74 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
74 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
75 return route_path
75 return route_path
76
76
77
77
78 def get_format_ref_id(repo):
78 def get_format_ref_id(repo):
79 """Returns a `repo` specific reference formatter function"""
79 """Returns a `repo` specific reference formatter function"""
80 if h.is_svn(repo):
80 if h.is_svn(repo):
81 return _format_ref_id_svn
81 return _format_ref_id_svn
82 else:
82 else:
83 return _format_ref_id
83 return _format_ref_id
84
84
85
85
86 def _format_ref_id(name, raw_id):
86 def _format_ref_id(name, raw_id):
87 """Default formatting of a given reference `name`"""
87 """Default formatting of a given reference `name`"""
88 return name
88 return name
89
89
90
90
91 def _format_ref_id_svn(name, raw_id):
91 def _format_ref_id_svn(name, raw_id):
92 """Special way of formatting a reference for Subversion including path"""
92 """Special way of formatting a reference for Subversion including path"""
93 return '%s@%s' % (name, raw_id)
93 return '%s@%s' % (name, raw_id)
94
94
95
95
96 class TemplateArgs(StrictAttributeDict):
96 class TemplateArgs(StrictAttributeDict):
97 pass
97 pass
98
98
99
99
100 class BaseAppView(object):
100 class BaseAppView(object):
101
101
102 def __init__(self, context, request):
102 def __init__(self, context, request):
103 self.request = request
103 self.request = request
104 self.context = context
104 self.context = context
105 self.session = request.session
105 self.session = request.session
106 if not hasattr(request, 'user'):
106 if not hasattr(request, 'user'):
107 # NOTE(marcink): edge case, we ended up in matched route
107 # NOTE(marcink): edge case, we ended up in matched route
108 # but probably of web-app context, e.g API CALL/VCS CALL
108 # but probably of web-app context, e.g API CALL/VCS CALL
109 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
109 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
110 log.warning('Unable to process request `%s` in this scope', request)
110 log.warning('Unable to process request `%s` in this scope', request)
111 raise HTTPBadRequest()
111 raise HTTPBadRequest()
112
112
113 self._rhodecode_user = request.user # auth user
113 self._rhodecode_user = request.user # auth user
114 self._rhodecode_db_user = self._rhodecode_user.get_instance()
114 self._rhodecode_db_user = self._rhodecode_user.get_instance()
115 self._maybe_needs_password_change(
115 self._maybe_needs_password_change(
116 request.matched_route.name, self._rhodecode_db_user)
116 request.matched_route.name, self._rhodecode_db_user)
117
117
118 def _maybe_needs_password_change(self, view_name, user_obj):
118 def _maybe_needs_password_change(self, view_name, user_obj):
119 log.debug('Checking if user %s needs password change on view %s',
119 log.debug('Checking if user %s needs password change on view %s',
120 user_obj, view_name)
120 user_obj, view_name)
121 skip_user_views = [
121 skip_user_views = [
122 'logout', 'login',
122 'logout', 'login',
123 'my_account_password', 'my_account_password_update'
123 'my_account_password', 'my_account_password_update'
124 ]
124 ]
125
125
126 if not user_obj:
126 if not user_obj:
127 return
127 return
128
128
129 if user_obj.username == User.DEFAULT_USER:
129 if user_obj.username == User.DEFAULT_USER:
130 return
130 return
131
131
132 now = time.time()
132 now = time.time()
133 should_change = user_obj.user_data.get('force_password_change')
133 should_change = user_obj.user_data.get('force_password_change')
134 change_after = safe_int(should_change) or 0
134 change_after = safe_int(should_change) or 0
135 if should_change and now > change_after:
135 if should_change and now > change_after:
136 log.debug('User %s requires password change', user_obj)
136 log.debug('User %s requires password change', user_obj)
137 h.flash('You are required to change your password', 'warning',
137 h.flash('You are required to change your password', 'warning',
138 ignore_duplicate=True)
138 ignore_duplicate=True)
139
139
140 if view_name not in skip_user_views:
140 if view_name not in skip_user_views:
141 raise HTTPFound(
141 raise HTTPFound(
142 self.request.route_path('my_account_password'))
142 self.request.route_path('my_account_password'))
143
143
144 def _log_creation_exception(self, e, repo_name):
144 def _log_creation_exception(self, e, repo_name):
145 _ = self.request.translate
145 _ = self.request.translate
146 reason = None
146 reason = None
147 if len(e.args) == 2:
147 if len(e.args) == 2:
148 reason = e.args[1]
148 reason = e.args[1]
149
149
150 if reason == 'INVALID_CERTIFICATE':
150 if reason == 'INVALID_CERTIFICATE':
151 log.exception(
151 log.exception(
152 'Exception creating a repository: invalid certificate')
152 'Exception creating a repository: invalid certificate')
153 msg = (_('Error creating repository %s: invalid certificate')
153 msg = (_('Error creating repository %s: invalid certificate')
154 % repo_name)
154 % repo_name)
155 else:
155 else:
156 log.exception("Exception creating a repository")
156 log.exception("Exception creating a repository")
157 msg = (_('Error creating repository %s')
157 msg = (_('Error creating repository %s')
158 % repo_name)
158 % repo_name)
159 return msg
159 return msg
160
160
161 def _get_local_tmpl_context(self, include_app_defaults=True):
161 def _get_local_tmpl_context(self, include_app_defaults=True):
162 c = TemplateArgs()
162 c = TemplateArgs()
163 c.auth_user = self.request.user
163 c.auth_user = self.request.user
164 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
164 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
165 c.rhodecode_user = self.request.user
165 c.rhodecode_user = self.request.user
166
166
167 if include_app_defaults:
167 if include_app_defaults:
168 from rhodecode.lib.base import attach_context_attributes
168 from rhodecode.lib.base import attach_context_attributes
169 attach_context_attributes(c, self.request, self.request.user.user_id)
169 attach_context_attributes(c, self.request, self.request.user.user_id)
170
170
171 c.is_super_admin = c.auth_user.is_admin
171 c.is_super_admin = c.auth_user.is_admin
172
172
173 c.can_create_repo = c.is_super_admin
173 c.can_create_repo = c.is_super_admin
174 c.can_create_repo_group = c.is_super_admin
174 c.can_create_repo_group = c.is_super_admin
175 c.can_create_user_group = c.is_super_admin
175 c.can_create_user_group = c.is_super_admin
176
176
177 c.is_delegated_admin = False
177 c.is_delegated_admin = False
178
178
179 if not c.auth_user.is_default and not c.is_super_admin:
179 if not c.auth_user.is_default and not c.is_super_admin:
180 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
180 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
181 user=self.request.user)
181 user=self.request.user)
182 repositories = c.auth_user.repositories_admin or c.can_create_repo
182 repositories = c.auth_user.repositories_admin or c.can_create_repo
183
183
184 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
184 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
185 user=self.request.user)
185 user=self.request.user)
186 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
186 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
187
187
188 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
188 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
189 user=self.request.user)
189 user=self.request.user)
190 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
190 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
191 # delegated admin can create, or manage some objects
191 # delegated admin can create, or manage some objects
192 c.is_delegated_admin = repositories or repository_groups or user_groups
192 c.is_delegated_admin = repositories or repository_groups or user_groups
193 return c
193 return c
194
194
195 def _get_template_context(self, tmpl_args, **kwargs):
195 def _get_template_context(self, tmpl_args, **kwargs):
196
196
197 local_tmpl_args = {
197 local_tmpl_args = {
198 'defaults': {},
198 'defaults': {},
199 'errors': {},
199 'errors': {},
200 'c': tmpl_args
200 'c': tmpl_args
201 }
201 }
202 local_tmpl_args.update(kwargs)
202 local_tmpl_args.update(kwargs)
203 return local_tmpl_args
203 return local_tmpl_args
204
204
205 def load_default_context(self):
205 def load_default_context(self):
206 """
206 """
207 example:
207 example:
208
208
209 def load_default_context(self):
209 def load_default_context(self):
210 c = self._get_local_tmpl_context()
210 c = self._get_local_tmpl_context()
211 c.custom_var = 'foobar'
211 c.custom_var = 'foobar'
212
212
213 return c
213 return c
214 """
214 """
215 raise NotImplementedError('Needs implementation in view class')
215 raise NotImplementedError('Needs implementation in view class')
216
216
217
217
218 class RepoAppView(BaseAppView):
218 class RepoAppView(BaseAppView):
219
219
220 def __init__(self, context, request):
220 def __init__(self, context, request):
221 super(RepoAppView, self).__init__(context, request)
221 super(RepoAppView, self).__init__(context, request)
222 self.db_repo = request.db_repo
222 self.db_repo = request.db_repo
223 self.db_repo_name = self.db_repo.repo_name
223 self.db_repo_name = self.db_repo.repo_name
224 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
224 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
225
225
226 def _handle_missing_requirements(self, error):
226 def _handle_missing_requirements(self, error):
227 log.error(
227 log.error(
228 'Requirements are missing for repository %s: %s',
228 'Requirements are missing for repository %s: %s',
229 self.db_repo_name, safe_unicode(error))
229 self.db_repo_name, safe_unicode(error))
230
230
231 def _get_local_tmpl_context(self, include_app_defaults=True):
231 def _get_local_tmpl_context(self, include_app_defaults=True):
232 _ = self.request.translate
232 _ = self.request.translate
233 c = super(RepoAppView, self)._get_local_tmpl_context(
233 c = super(RepoAppView, self)._get_local_tmpl_context(
234 include_app_defaults=include_app_defaults)
234 include_app_defaults=include_app_defaults)
235
235
236 # register common vars for this type of view
236 # register common vars for this type of view
237 c.rhodecode_db_repo = self.db_repo
237 c.rhodecode_db_repo = self.db_repo
238 c.repo_name = self.db_repo_name
238 c.repo_name = self.db_repo_name
239 c.repository_pull_requests = self.db_repo_pull_requests
239 c.repository_pull_requests = self.db_repo_pull_requests
240 c.repository_is_user_following = ScmModel().is_following_repo(
241 self.db_repo_name, self._rhodecode_user.user_id)
240 self.path_filter = PathFilter(None)
242 self.path_filter = PathFilter(None)
241
243
242 c.repository_requirements_missing = {}
244 c.repository_requirements_missing = {}
243 try:
245 try:
244 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
246 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
245 if self.rhodecode_vcs_repo:
247 if self.rhodecode_vcs_repo:
246 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
248 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
247 c.auth_user.username)
249 c.auth_user.username)
248 self.path_filter = PathFilter(path_perms)
250 self.path_filter = PathFilter(path_perms)
249 except RepositoryRequirementError as e:
251 except RepositoryRequirementError as e:
250 c.repository_requirements_missing = {'error': str(e)}
252 c.repository_requirements_missing = {'error': str(e)}
251 self._handle_missing_requirements(e)
253 self._handle_missing_requirements(e)
252 self.rhodecode_vcs_repo = None
254 self.rhodecode_vcs_repo = None
253
255
254 c.path_filter = self.path_filter # used by atom_feed_entry.mako
256 c.path_filter = self.path_filter # used by atom_feed_entry.mako
255
257
256 if self.rhodecode_vcs_repo is None:
258 if self.rhodecode_vcs_repo is None:
257 # unable to fetch this repo as vcs instance, report back to user
259 # unable to fetch this repo as vcs instance, report back to user
258 h.flash(_(
260 h.flash(_(
259 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
261 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
260 "Please check if it exist, or is not damaged.") %
262 "Please check if it exist, or is not damaged.") %
261 {'repo_name': c.repo_name},
263 {'repo_name': c.repo_name},
262 category='error', ignore_duplicate=True)
264 category='error', ignore_duplicate=True)
263 if c.repository_requirements_missing:
265 if c.repository_requirements_missing:
264 route = self.request.matched_route.name
266 route = self.request.matched_route.name
265 if route.startswith(('edit_repo', 'repo_summary')):
267 if route.startswith(('edit_repo', 'repo_summary')):
266 # allow summary and edit repo on missing requirements
268 # allow summary and edit repo on missing requirements
267 return c
269 return c
268
270
269 raise HTTPFound(
271 raise HTTPFound(
270 h.route_path('repo_summary', repo_name=self.db_repo_name))
272 h.route_path('repo_summary', repo_name=self.db_repo_name))
271
273
272 else: # redirect if we don't show missing requirements
274 else: # redirect if we don't show missing requirements
273 raise HTTPFound(h.route_path('home'))
275 raise HTTPFound(h.route_path('home'))
274
276
275 c.has_origin_repo_read_perm = False
277 c.has_origin_repo_read_perm = False
276 if self.db_repo.fork:
278 if self.db_repo.fork:
277 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
279 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
278 'repository.write', 'repository.read', 'repository.admin')(
280 'repository.write', 'repository.read', 'repository.admin')(
279 self.db_repo.fork.repo_name, 'summary fork link')
281 self.db_repo.fork.repo_name, 'summary fork link')
280
282
281 return c
283 return c
282
284
283 def _get_f_path_unchecked(self, matchdict, default=None):
285 def _get_f_path_unchecked(self, matchdict, default=None):
284 """
286 """
285 Should only be used by redirects, everything else should call _get_f_path
287 Should only be used by redirects, everything else should call _get_f_path
286 """
288 """
287 f_path = matchdict.get('f_path')
289 f_path = matchdict.get('f_path')
288 if f_path:
290 if f_path:
289 # fix for multiple initial slashes that causes errors for GIT
291 # fix for multiple initial slashes that causes errors for GIT
290 return f_path.lstrip('/')
292 return f_path.lstrip('/')
291
293
292 return default
294 return default
293
295
294 def _get_f_path(self, matchdict, default=None):
296 def _get_f_path(self, matchdict, default=None):
295 f_path_match = self._get_f_path_unchecked(matchdict, default)
297 f_path_match = self._get_f_path_unchecked(matchdict, default)
296 return self.path_filter.assert_path_permissions(f_path_match)
298 return self.path_filter.assert_path_permissions(f_path_match)
297
299
298 def _get_general_setting(self, target_repo, settings_key, default=False):
300 def _get_general_setting(self, target_repo, settings_key, default=False):
299 settings_model = VcsSettingsModel(repo=target_repo)
301 settings_model = VcsSettingsModel(repo=target_repo)
300 settings = settings_model.get_general_settings()
302 settings = settings_model.get_general_settings()
301 return settings.get(settings_key, default)
303 return settings.get(settings_key, default)
302
304
303 def get_recache_flag(self):
305 def get_recache_flag(self):
304 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
306 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
305 flag_val = self.request.GET.get(flag_name)
307 flag_val = self.request.GET.get(flag_name)
306 if str2bool(flag_val):
308 if str2bool(flag_val):
307 return True
309 return True
308 return False
310 return False
309
311
310
312
311 class PathFilter(object):
313 class PathFilter(object):
312
314
313 # Expects and instance of BasePathPermissionChecker or None
315 # Expects and instance of BasePathPermissionChecker or None
314 def __init__(self, permission_checker):
316 def __init__(self, permission_checker):
315 self.permission_checker = permission_checker
317 self.permission_checker = permission_checker
316
318
317 def assert_path_permissions(self, path):
319 def assert_path_permissions(self, path):
318 if path and self.permission_checker and not self.permission_checker.has_access(path):
320 if path and self.permission_checker and not self.permission_checker.has_access(path):
319 raise HTTPForbidden()
321 raise HTTPForbidden()
320 return path
322 return path
321
323
322 def filter_patchset(self, patchset):
324 def filter_patchset(self, patchset):
323 if not self.permission_checker or not patchset:
325 if not self.permission_checker or not patchset:
324 return patchset, False
326 return patchset, False
325 had_filtered = False
327 had_filtered = False
326 filtered_patchset = []
328 filtered_patchset = []
327 for patch in patchset:
329 for patch in patchset:
328 filename = patch.get('filename', None)
330 filename = patch.get('filename', None)
329 if not filename or self.permission_checker.has_access(filename):
331 if not filename or self.permission_checker.has_access(filename):
330 filtered_patchset.append(patch)
332 filtered_patchset.append(patch)
331 else:
333 else:
332 had_filtered = True
334 had_filtered = True
333 if had_filtered:
335 if had_filtered:
334 if isinstance(patchset, diffs.LimitedDiffContainer):
336 if isinstance(patchset, diffs.LimitedDiffContainer):
335 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
337 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
336 return filtered_patchset, True
338 return filtered_patchset, True
337 else:
339 else:
338 return patchset, False
340 return patchset, False
339
341
340 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
342 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
341 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
343 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
342 result = diffset.render_patchset(
344 result = diffset.render_patchset(
343 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
345 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
344 result.has_hidden_changes = has_hidden_changes
346 result.has_hidden_changes = has_hidden_changes
345 return result
347 return result
346
348
347 def get_raw_patch(self, diff_processor):
349 def get_raw_patch(self, diff_processor):
348 if self.permission_checker is None:
350 if self.permission_checker is None:
349 return diff_processor.as_raw()
351 return diff_processor.as_raw()
350 elif self.permission_checker.has_full_access:
352 elif self.permission_checker.has_full_access:
351 return diff_processor.as_raw()
353 return diff_processor.as_raw()
352 else:
354 else:
353 return '# Repository has user-specific filters, raw patch generation is disabled.'
355 return '# Repository has user-specific filters, raw patch generation is disabled.'
354
356
355 @property
357 @property
356 def is_enabled(self):
358 def is_enabled(self):
357 return self.permission_checker is not None
359 return self.permission_checker is not None
358
360
359
361
360 class RepoGroupAppView(BaseAppView):
362 class RepoGroupAppView(BaseAppView):
361 def __init__(self, context, request):
363 def __init__(self, context, request):
362 super(RepoGroupAppView, self).__init__(context, request)
364 super(RepoGroupAppView, self).__init__(context, request)
363 self.db_repo_group = request.db_repo_group
365 self.db_repo_group = request.db_repo_group
364 self.db_repo_group_name = self.db_repo_group.group_name
366 self.db_repo_group_name = self.db_repo_group.group_name
365
367
366 def _get_local_tmpl_context(self, include_app_defaults=True):
368 def _get_local_tmpl_context(self, include_app_defaults=True):
367 _ = self.request.translate
369 _ = self.request.translate
368 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
370 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
369 include_app_defaults=include_app_defaults)
371 include_app_defaults=include_app_defaults)
370 c.repo_group = self.db_repo_group
372 c.repo_group = self.db_repo_group
371 return c
373 return c
372
374
373 def _revoke_perms_on_yourself(self, form_result):
375 def _revoke_perms_on_yourself(self, form_result):
374 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
376 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
375 form_result['perm_updates'])
377 form_result['perm_updates'])
376 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
378 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
377 form_result['perm_additions'])
379 form_result['perm_additions'])
378 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
380 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
379 form_result['perm_deletions'])
381 form_result['perm_deletions'])
380 admin_perm = 'group.admin'
382 admin_perm = 'group.admin'
381 if _updates and _updates[0][1] != admin_perm or \
383 if _updates and _updates[0][1] != admin_perm or \
382 _additions and _additions[0][1] != admin_perm or \
384 _additions and _additions[0][1] != admin_perm or \
383 _deletions and _deletions[0][1] != admin_perm:
385 _deletions and _deletions[0][1] != admin_perm:
384 return True
386 return True
385 return False
387 return False
386
388
387
389
388 class UserGroupAppView(BaseAppView):
390 class UserGroupAppView(BaseAppView):
389 def __init__(self, context, request):
391 def __init__(self, context, request):
390 super(UserGroupAppView, self).__init__(context, request)
392 super(UserGroupAppView, self).__init__(context, request)
391 self.db_user_group = request.db_user_group
393 self.db_user_group = request.db_user_group
392 self.db_user_group_name = self.db_user_group.users_group_name
394 self.db_user_group_name = self.db_user_group.users_group_name
393
395
394
396
395 class UserAppView(BaseAppView):
397 class UserAppView(BaseAppView):
396 def __init__(self, context, request):
398 def __init__(self, context, request):
397 super(UserAppView, self).__init__(context, request)
399 super(UserAppView, self).__init__(context, request)
398 self.db_user = request.db_user
400 self.db_user = request.db_user
399 self.db_user_id = self.db_user.user_id
401 self.db_user_id = self.db_user.user_id
400
402
401 _ = self.request.translate
403 _ = self.request.translate
402 if not request.db_user_supports_default:
404 if not request.db_user_supports_default:
403 if self.db_user.username == User.DEFAULT_USER:
405 if self.db_user.username == User.DEFAULT_USER:
404 h.flash(_("Editing user `{}` is disabled.".format(
406 h.flash(_("Editing user `{}` is disabled.".format(
405 User.DEFAULT_USER)), category='warning')
407 User.DEFAULT_USER)), category='warning')
406 raise HTTPFound(h.route_path('users'))
408 raise HTTPFound(h.route_path('users'))
407
409
408
410
409 class DataGridAppView(object):
411 class DataGridAppView(object):
410 """
412 """
411 Common class to have re-usable grid rendering components
413 Common class to have re-usable grid rendering components
412 """
414 """
413
415
414 def _extract_ordering(self, request, column_map=None):
416 def _extract_ordering(self, request, column_map=None):
415 column_map = column_map or {}
417 column_map = column_map or {}
416 column_index = safe_int(request.GET.get('order[0][column]'))
418 column_index = safe_int(request.GET.get('order[0][column]'))
417 order_dir = request.GET.get(
419 order_dir = request.GET.get(
418 'order[0][dir]', 'desc')
420 'order[0][dir]', 'desc')
419 order_by = request.GET.get(
421 order_by = request.GET.get(
420 'columns[%s][data][sort]' % column_index, 'name_raw')
422 'columns[%s][data][sort]' % column_index, 'name_raw')
421
423
422 # translate datatable to DB columns
424 # translate datatable to DB columns
423 order_by = column_map.get(order_by) or order_by
425 order_by = column_map.get(order_by) or order_by
424
426
425 search_q = request.GET.get('search[value]')
427 search_q = request.GET.get('search[value]')
426 return search_q, order_by, order_dir
428 return search_q, order_by, order_dir
427
429
428 def _extract_chunk(self, request):
430 def _extract_chunk(self, request):
429 start = safe_int(request.GET.get('start'), 0)
431 start = safe_int(request.GET.get('start'), 0)
430 length = safe_int(request.GET.get('length'), 25)
432 length = safe_int(request.GET.get('length'), 25)
431 draw = safe_int(request.GET.get('draw'))
433 draw = safe_int(request.GET.get('draw'))
432 return draw, start, length
434 return draw, start, length
433
435
434 def _get_order_col(self, order_by, model):
436 def _get_order_col(self, order_by, model):
435 if isinstance(order_by, compat.string_types):
437 if isinstance(order_by, compat.string_types):
436 try:
438 try:
437 return operator.attrgetter(order_by)(model)
439 return operator.attrgetter(order_by)(model)
438 except AttributeError:
440 except AttributeError:
439 return None
441 return None
440 else:
442 else:
441 return order_by
443 return order_by
442
444
443
445
444 class BaseReferencesView(RepoAppView):
446 class BaseReferencesView(RepoAppView):
445 """
447 """
446 Base for reference view for branches, tags and bookmarks.
448 Base for reference view for branches, tags and bookmarks.
447 """
449 """
448 def load_default_context(self):
450 def load_default_context(self):
449 c = self._get_local_tmpl_context()
451 c = self._get_local_tmpl_context()
450
452
451
453
452 return c
454 return c
453
455
454 def load_refs_context(self, ref_items, partials_template):
456 def load_refs_context(self, ref_items, partials_template):
455 _render = self.request.get_partial_renderer(partials_template)
457 _render = self.request.get_partial_renderer(partials_template)
456 pre_load = ["author", "date", "message"]
458 pre_load = ["author", "date", "message"]
457
459
458 is_svn = h.is_svn(self.rhodecode_vcs_repo)
460 is_svn = h.is_svn(self.rhodecode_vcs_repo)
459 is_hg = h.is_hg(self.rhodecode_vcs_repo)
461 is_hg = h.is_hg(self.rhodecode_vcs_repo)
460
462
461 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
463 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
462
464
463 closed_refs = {}
465 closed_refs = {}
464 if is_hg:
466 if is_hg:
465 closed_refs = self.rhodecode_vcs_repo.branches_closed
467 closed_refs = self.rhodecode_vcs_repo.branches_closed
466
468
467 data = []
469 data = []
468 for ref_name, commit_id in ref_items:
470 for ref_name, commit_id in ref_items:
469 commit = self.rhodecode_vcs_repo.get_commit(
471 commit = self.rhodecode_vcs_repo.get_commit(
470 commit_id=commit_id, pre_load=pre_load)
472 commit_id=commit_id, pre_load=pre_load)
471 closed = ref_name in closed_refs
473 closed = ref_name in closed_refs
472
474
473 # TODO: johbo: Unify generation of reference links
475 # TODO: johbo: Unify generation of reference links
474 use_commit_id = '/' in ref_name or is_svn
476 use_commit_id = '/' in ref_name or is_svn
475
477
476 if use_commit_id:
478 if use_commit_id:
477 files_url = h.route_path(
479 files_url = h.route_path(
478 'repo_files',
480 'repo_files',
479 repo_name=self.db_repo_name,
481 repo_name=self.db_repo_name,
480 f_path=ref_name if is_svn else '',
482 f_path=ref_name if is_svn else '',
481 commit_id=commit_id)
483 commit_id=commit_id)
482
484
483 else:
485 else:
484 files_url = h.route_path(
486 files_url = h.route_path(
485 'repo_files',
487 'repo_files',
486 repo_name=self.db_repo_name,
488 repo_name=self.db_repo_name,
487 f_path=ref_name if is_svn else '',
489 f_path=ref_name if is_svn else '',
488 commit_id=ref_name,
490 commit_id=ref_name,
489 _query=dict(at=ref_name))
491 _query=dict(at=ref_name))
490
492
491 data.append({
493 data.append({
492 "name": _render('name', ref_name, files_url, closed),
494 "name": _render('name', ref_name, files_url, closed),
493 "name_raw": ref_name,
495 "name_raw": ref_name,
494 "date": _render('date', commit.date),
496 "date": _render('date', commit.date),
495 "date_raw": datetime_to_time(commit.date),
497 "date_raw": datetime_to_time(commit.date),
496 "author": _render('author', commit.author),
498 "author": _render('author', commit.author),
497 "commit": _render(
499 "commit": _render(
498 'commit', commit.message, commit.raw_id, commit.idx),
500 'commit', commit.message, commit.raw_id, commit.idx),
499 "commit_raw": commit.idx,
501 "commit_raw": commit.idx,
500 "compare": _render(
502 "compare": _render(
501 'compare', format_ref_id(ref_name, commit.raw_id)),
503 'compare', format_ref_id(ref_name, commit.raw_id)),
502 })
504 })
503
505
504 return data
506 return data
505
507
506
508
507 class RepoRoutePredicate(object):
509 class RepoRoutePredicate(object):
508 def __init__(self, val, config):
510 def __init__(self, val, config):
509 self.val = val
511 self.val = val
510
512
511 def text(self):
513 def text(self):
512 return 'repo_route = %s' % self.val
514 return 'repo_route = %s' % self.val
513
515
514 phash = text
516 phash = text
515
517
516 def __call__(self, info, request):
518 def __call__(self, info, request):
517 if hasattr(request, 'vcs_call'):
519 if hasattr(request, 'vcs_call'):
518 # skip vcs calls
520 # skip vcs calls
519 return
521 return
520
522
521 repo_name = info['match']['repo_name']
523 repo_name = info['match']['repo_name']
522 repo_model = repo.RepoModel()
524 repo_model = repo.RepoModel()
523
525
524 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
526 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
525
527
526 def redirect_if_creating(route_info, db_repo):
528 def redirect_if_creating(route_info, db_repo):
527 skip_views = ['edit_repo_advanced_delete']
529 skip_views = ['edit_repo_advanced_delete']
528 route = route_info['route']
530 route = route_info['route']
529 # we should skip delete view so we can actually "remove" repositories
531 # we should skip delete view so we can actually "remove" repositories
530 # if they get stuck in creating state.
532 # if they get stuck in creating state.
531 if route.name in skip_views:
533 if route.name in skip_views:
532 return
534 return
533
535
534 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
536 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
535 repo_creating_url = request.route_path(
537 repo_creating_url = request.route_path(
536 'repo_creating', repo_name=db_repo.repo_name)
538 'repo_creating', repo_name=db_repo.repo_name)
537 raise HTTPFound(repo_creating_url)
539 raise HTTPFound(repo_creating_url)
538
540
539 if by_name_match:
541 if by_name_match:
540 # register this as request object we can re-use later
542 # register this as request object we can re-use later
541 request.db_repo = by_name_match
543 request.db_repo = by_name_match
542 redirect_if_creating(info, by_name_match)
544 redirect_if_creating(info, by_name_match)
543 return True
545 return True
544
546
545 by_id_match = repo_model.get_repo_by_id(repo_name)
547 by_id_match = repo_model.get_repo_by_id(repo_name)
546 if by_id_match:
548 if by_id_match:
547 request.db_repo = by_id_match
549 request.db_repo = by_id_match
548 redirect_if_creating(info, by_id_match)
550 redirect_if_creating(info, by_id_match)
549 return True
551 return True
550
552
551 return False
553 return False
552
554
553
555
554 class RepoForbidArchivedRoutePredicate(object):
556 class RepoForbidArchivedRoutePredicate(object):
555 def __init__(self, val, config):
557 def __init__(self, val, config):
556 self.val = val
558 self.val = val
557
559
558 def text(self):
560 def text(self):
559 return 'repo_forbid_archived = %s' % self.val
561 return 'repo_forbid_archived = %s' % self.val
560
562
561 phash = text
563 phash = text
562
564
563 def __call__(self, info, request):
565 def __call__(self, info, request):
564 _ = request.translate
566 _ = request.translate
565 rhodecode_db_repo = request.db_repo
567 rhodecode_db_repo = request.db_repo
566
568
567 log.debug(
569 log.debug(
568 '%s checking if archived flag for repo for %s',
570 '%s checking if archived flag for repo for %s',
569 self.__class__.__name__, rhodecode_db_repo.repo_name)
571 self.__class__.__name__, rhodecode_db_repo.repo_name)
570
572
571 if rhodecode_db_repo.archived:
573 if rhodecode_db_repo.archived:
572 log.warning('Current view is not supported for archived repo:%s',
574 log.warning('Current view is not supported for archived repo:%s',
573 rhodecode_db_repo.repo_name)
575 rhodecode_db_repo.repo_name)
574
576
575 h.flash(
577 h.flash(
576 h.literal(_('Action not supported for archived repository.')),
578 h.literal(_('Action not supported for archived repository.')),
577 category='warning')
579 category='warning')
578 summary_url = request.route_path(
580 summary_url = request.route_path(
579 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
581 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
580 raise HTTPFound(summary_url)
582 raise HTTPFound(summary_url)
581 return True
583 return True
582
584
583
585
584 class RepoTypeRoutePredicate(object):
586 class RepoTypeRoutePredicate(object):
585 def __init__(self, val, config):
587 def __init__(self, val, config):
586 self.val = val or ['hg', 'git', 'svn']
588 self.val = val or ['hg', 'git', 'svn']
587
589
588 def text(self):
590 def text(self):
589 return 'repo_accepted_type = %s' % self.val
591 return 'repo_accepted_type = %s' % self.val
590
592
591 phash = text
593 phash = text
592
594
593 def __call__(self, info, request):
595 def __call__(self, info, request):
594 if hasattr(request, 'vcs_call'):
596 if hasattr(request, 'vcs_call'):
595 # skip vcs calls
597 # skip vcs calls
596 return
598 return
597
599
598 rhodecode_db_repo = request.db_repo
600 rhodecode_db_repo = request.db_repo
599
601
600 log.debug(
602 log.debug(
601 '%s checking repo type for %s in %s',
603 '%s checking repo type for %s in %s',
602 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
604 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
603
605
604 if rhodecode_db_repo.repo_type in self.val:
606 if rhodecode_db_repo.repo_type in self.val:
605 return True
607 return True
606 else:
608 else:
607 log.warning('Current view is not supported for repo type:%s',
609 log.warning('Current view is not supported for repo type:%s',
608 rhodecode_db_repo.repo_type)
610 rhodecode_db_repo.repo_type)
609 return False
611 return False
610
612
611
613
612 class RepoGroupRoutePredicate(object):
614 class RepoGroupRoutePredicate(object):
613 def __init__(self, val, config):
615 def __init__(self, val, config):
614 self.val = val
616 self.val = val
615
617
616 def text(self):
618 def text(self):
617 return 'repo_group_route = %s' % self.val
619 return 'repo_group_route = %s' % self.val
618
620
619 phash = text
621 phash = text
620
622
621 def __call__(self, info, request):
623 def __call__(self, info, request):
622 if hasattr(request, 'vcs_call'):
624 if hasattr(request, 'vcs_call'):
623 # skip vcs calls
625 # skip vcs calls
624 return
626 return
625
627
626 repo_group_name = info['match']['repo_group_name']
628 repo_group_name = info['match']['repo_group_name']
627 repo_group_model = repo_group.RepoGroupModel()
629 repo_group_model = repo_group.RepoGroupModel()
628 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
630 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
629
631
630 if by_name_match:
632 if by_name_match:
631 # register this as request object we can re-use later
633 # register this as request object we can re-use later
632 request.db_repo_group = by_name_match
634 request.db_repo_group = by_name_match
633 return True
635 return True
634
636
635 return False
637 return False
636
638
637
639
638 class UserGroupRoutePredicate(object):
640 class UserGroupRoutePredicate(object):
639 def __init__(self, val, config):
641 def __init__(self, val, config):
640 self.val = val
642 self.val = val
641
643
642 def text(self):
644 def text(self):
643 return 'user_group_route = %s' % self.val
645 return 'user_group_route = %s' % self.val
644
646
645 phash = text
647 phash = text
646
648
647 def __call__(self, info, request):
649 def __call__(self, info, request):
648 if hasattr(request, 'vcs_call'):
650 if hasattr(request, 'vcs_call'):
649 # skip vcs calls
651 # skip vcs calls
650 return
652 return
651
653
652 user_group_id = info['match']['user_group_id']
654 user_group_id = info['match']['user_group_id']
653 user_group_model = user_group.UserGroup()
655 user_group_model = user_group.UserGroup()
654 by_id_match = user_group_model.get(user_group_id, cache=False)
656 by_id_match = user_group_model.get(user_group_id, cache=False)
655
657
656 if by_id_match:
658 if by_id_match:
657 # register this as request object we can re-use later
659 # register this as request object we can re-use later
658 request.db_user_group = by_id_match
660 request.db_user_group = by_id_match
659 return True
661 return True
660
662
661 return False
663 return False
662
664
663
665
664 class UserRoutePredicateBase(object):
666 class UserRoutePredicateBase(object):
665 supports_default = None
667 supports_default = None
666
668
667 def __init__(self, val, config):
669 def __init__(self, val, config):
668 self.val = val
670 self.val = val
669
671
670 def text(self):
672 def text(self):
671 raise NotImplementedError()
673 raise NotImplementedError()
672
674
673 def __call__(self, info, request):
675 def __call__(self, info, request):
674 if hasattr(request, 'vcs_call'):
676 if hasattr(request, 'vcs_call'):
675 # skip vcs calls
677 # skip vcs calls
676 return
678 return
677
679
678 user_id = info['match']['user_id']
680 user_id = info['match']['user_id']
679 user_model = user.User()
681 user_model = user.User()
680 by_id_match = user_model.get(user_id, cache=False)
682 by_id_match = user_model.get(user_id, cache=False)
681
683
682 if by_id_match:
684 if by_id_match:
683 # register this as request object we can re-use later
685 # register this as request object we can re-use later
684 request.db_user = by_id_match
686 request.db_user = by_id_match
685 request.db_user_supports_default = self.supports_default
687 request.db_user_supports_default = self.supports_default
686 return True
688 return True
687
689
688 return False
690 return False
689
691
690
692
691 class UserRoutePredicate(UserRoutePredicateBase):
693 class UserRoutePredicate(UserRoutePredicateBase):
692 supports_default = False
694 supports_default = False
693
695
694 def text(self):
696 def text(self):
695 return 'user_route = %s' % self.val
697 return 'user_route = %s' % self.val
696
698
697 phash = text
699 phash = text
698
700
699
701
700 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
702 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
701 supports_default = True
703 supports_default = True
702
704
703 def text(self):
705 def text(self):
704 return 'user_with_default_route = %s' % self.val
706 return 'user_with_default_route = %s' % self.val
705
707
706 phash = text
708 phash = text
707
709
708
710
709 def includeme(config):
711 def includeme(config):
710 config.add_route_predicate(
712 config.add_route_predicate(
711 'repo_route', RepoRoutePredicate)
713 'repo_route', RepoRoutePredicate)
712 config.add_route_predicate(
714 config.add_route_predicate(
713 'repo_accepted_types', RepoTypeRoutePredicate)
715 'repo_accepted_types', RepoTypeRoutePredicate)
714 config.add_route_predicate(
716 config.add_route_predicate(
715 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
717 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
716 config.add_route_predicate(
718 config.add_route_predicate(
717 'repo_group_route', RepoGroupRoutePredicate)
719 'repo_group_route', RepoGroupRoutePredicate)
718 config.add_route_predicate(
720 config.add_route_predicate(
719 'user_group_route', UserGroupRoutePredicate)
721 'user_group_route', UserGroupRoutePredicate)
720 config.add_route_predicate(
722 config.add_route_predicate(
721 'user_route_with_default', UserRouteWithDefaultPredicate)
723 'user_route_with_default', UserRouteWithDefaultPredicate)
722 config.add_route_predicate(
724 config.add_route_predicate(
723 'user_route', UserRoutePredicate)
725 'user_route', UserRoutePredicate)
@@ -1,386 +1,384 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import itertools
23 import itertools
24
24
25 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
25 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
26
26
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.httpexceptions import HTTPBadRequest
28 from pyramid.httpexceptions import HTTPBadRequest
29 from pyramid.response import Response
29 from pyramid.response import Response
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode.apps._base import BaseAppView
32 from rhodecode.apps._base import BaseAppView
33 from rhodecode.model.db import (
33 from rhodecode.model.db import (
34 or_, joinedload, UserLog, UserFollowing, User, UserApiKeys)
34 or_, joinedload, UserLog, UserFollowing, User, UserApiKeys)
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.helpers import Page
38 from rhodecode.lib.user_log_filter import user_log_filter
38 from rhodecode.lib.user_log_filter import user_log_filter
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
41 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class JournalView(BaseAppView):
46 class JournalView(BaseAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
50
50
51 self._load_defaults(c.rhodecode_name)
51 self._load_defaults(c.rhodecode_name)
52
52
53 # TODO(marcink): what is this, why we need a global register ?
53 # TODO(marcink): what is this, why we need a global register ?
54 c.search_term = self.request.GET.get('filter') or ''
54 c.search_term = self.request.GET.get('filter') or ''
55 return c
55 return c
56
56
57 def _get_config(self, rhodecode_name):
57 def _get_config(self, rhodecode_name):
58 import rhodecode
58 import rhodecode
59 config = rhodecode.CONFIG
59 config = rhodecode.CONFIG
60
60
61 return {
61 return {
62 'language': 'en-us',
62 'language': 'en-us',
63 'feed_ttl': '5', # TTL of feed,
63 'feed_ttl': '5', # TTL of feed,
64 'feed_items_per_page':
64 'feed_items_per_page':
65 safe_int(config.get('rss_items_per_page', 20)),
65 safe_int(config.get('rss_items_per_page', 20)),
66 'rhodecode_name': rhodecode_name
66 'rhodecode_name': rhodecode_name
67 }
67 }
68
68
69 def _load_defaults(self, rhodecode_name):
69 def _load_defaults(self, rhodecode_name):
70 config = self._get_config(rhodecode_name)
70 config = self._get_config(rhodecode_name)
71 # common values for feeds
71 # common values for feeds
72 self.language = config["language"]
72 self.language = config["language"]
73 self.ttl = config["feed_ttl"]
73 self.ttl = config["feed_ttl"]
74 self.feed_items_per_page = config['feed_items_per_page']
74 self.feed_items_per_page = config['feed_items_per_page']
75 self.rhodecode_name = config['rhodecode_name']
75 self.rhodecode_name = config['rhodecode_name']
76
76
77 def _get_daily_aggregate(self, journal):
77 def _get_daily_aggregate(self, journal):
78 groups = []
78 groups = []
79 for k, g in itertools.groupby(journal, lambda x: x.action_as_day):
79 for k, g in itertools.groupby(journal, lambda x: x.action_as_day):
80 user_group = []
80 user_group = []
81 # groupby username if it's a present value, else
81 # groupby username if it's a present value, else
82 # fallback to journal username
82 # fallback to journal username
83 for _, g2 in itertools.groupby(
83 for _, g2 in itertools.groupby(
84 list(g), lambda x: x.user.username if x.user else x.username):
84 list(g), lambda x: x.user.username if x.user else x.username):
85 l = list(g2)
85 l = list(g2)
86 user_group.append((l[0].user, l))
86 user_group.append((l[0].user, l))
87
87
88 groups.append((k, user_group,))
88 groups.append((k, user_group,))
89
89
90 return groups
90 return groups
91
91
92 def _get_journal_data(self, following_repos, search_term):
92 def _get_journal_data(self, following_repos, search_term):
93 repo_ids = [x.follows_repository.repo_id for x in following_repos
93 repo_ids = [x.follows_repository.repo_id for x in following_repos
94 if x.follows_repository is not None]
94 if x.follows_repository is not None]
95 user_ids = [x.follows_user.user_id for x in following_repos
95 user_ids = [x.follows_user.user_id for x in following_repos
96 if x.follows_user is not None]
96 if x.follows_user is not None]
97
97
98 filtering_criterion = None
98 filtering_criterion = None
99
99
100 if repo_ids and user_ids:
100 if repo_ids and user_ids:
101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
102 UserLog.user_id.in_(user_ids))
102 UserLog.user_id.in_(user_ids))
103 if repo_ids and not user_ids:
103 if repo_ids and not user_ids:
104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
105 if not repo_ids and user_ids:
105 if not repo_ids and user_ids:
106 filtering_criterion = UserLog.user_id.in_(user_ids)
106 filtering_criterion = UserLog.user_id.in_(user_ids)
107 if filtering_criterion is not None:
107 if filtering_criterion is not None:
108 journal = Session().query(UserLog)\
108 journal = Session().query(UserLog)\
109 .options(joinedload(UserLog.user))\
109 .options(joinedload(UserLog.user))\
110 .options(joinedload(UserLog.repository))
110 .options(joinedload(UserLog.repository))
111 # filter
111 # filter
112 try:
112 try:
113 journal = user_log_filter(journal, search_term)
113 journal = user_log_filter(journal, search_term)
114 except Exception:
114 except Exception:
115 # we want this to crash for now
115 # we want this to crash for now
116 raise
116 raise
117 journal = journal.filter(filtering_criterion)\
117 journal = journal.filter(filtering_criterion)\
118 .order_by(UserLog.action_date.desc())
118 .order_by(UserLog.action_date.desc())
119 else:
119 else:
120 journal = []
120 journal = []
121
121
122 return journal
122 return journal
123
123
124 def feed_uid(self, entry_id):
124 def feed_uid(self, entry_id):
125 return '{}:{}'.format('journal', md5_safe(entry_id))
125 return '{}:{}'.format('journal', md5_safe(entry_id))
126
126
127 def _atom_feed(self, repos, search_term, public=True):
127 def _atom_feed(self, repos, search_term, public=True):
128 _ = self.request.translate
128 _ = self.request.translate
129 journal = self._get_journal_data(repos, search_term)
129 journal = self._get_journal_data(repos, search_term)
130 if public:
130 if public:
131 _link = h.route_url('journal_public_atom')
131 _link = h.route_url('journal_public_atom')
132 _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'),
132 _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'),
133 'atom feed')
133 'atom feed')
134 else:
134 else:
135 _link = h.route_url('journal_atom')
135 _link = h.route_url('journal_atom')
136 _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed')
136 _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed')
137
137
138 feed = Atom1Feed(
138 feed = Atom1Feed(
139 title=_desc, link=_link, description=_desc,
139 title=_desc, link=_link, description=_desc,
140 language=self.language, ttl=self.ttl)
140 language=self.language, ttl=self.ttl)
141
141
142 for entry in journal[:self.feed_items_per_page]:
142 for entry in journal[:self.feed_items_per_page]:
143 user = entry.user
143 user = entry.user
144 if user is None:
144 if user is None:
145 # fix deleted users
145 # fix deleted users
146 user = AttributeDict({'short_contact': entry.username,
146 user = AttributeDict({'short_contact': entry.username,
147 'email': '',
147 'email': '',
148 'full_contact': ''})
148 'full_contact': ''})
149 action, action_extra, ico = h.action_parser(
149 action, action_extra, ico = h.action_parser(
150 self.request, entry, feed=True)
150 self.request, entry, feed=True)
151 title = "%s - %s %s" % (user.short_contact, action(),
151 title = "%s - %s %s" % (user.short_contact, action(),
152 entry.repository.repo_name)
152 entry.repository.repo_name)
153 desc = action_extra()
153 desc = action_extra()
154 _url = h.route_url('home')
154 _url = h.route_url('home')
155 if entry.repository is not None:
155 if entry.repository is not None:
156 _url = h.route_url('repo_changelog',
156 _url = h.route_url('repo_changelog',
157 repo_name=entry.repository.repo_name)
157 repo_name=entry.repository.repo_name)
158
158
159 feed.add_item(
159 feed.add_item(
160 unique_id=self.feed_uid(entry.user_log_id),
160 unique_id=self.feed_uid(entry.user_log_id),
161 title=title,
161 title=title,
162 pubdate=entry.action_date,
162 pubdate=entry.action_date,
163 link=_url,
163 link=_url,
164 author_email=user.email,
164 author_email=user.email,
165 author_name=user.full_contact,
165 author_name=user.full_contact,
166 description=desc)
166 description=desc)
167
167
168 response = Response(feed.writeString('utf-8'))
168 response = Response(feed.writeString('utf-8'))
169 response.content_type = feed.mime_type
169 response.content_type = feed.mime_type
170 return response
170 return response
171
171
172 def _rss_feed(self, repos, search_term, public=True):
172 def _rss_feed(self, repos, search_term, public=True):
173 _ = self.request.translate
173 _ = self.request.translate
174 journal = self._get_journal_data(repos, search_term)
174 journal = self._get_journal_data(repos, search_term)
175 if public:
175 if public:
176 _link = h.route_url('journal_public_atom')
176 _link = h.route_url('journal_public_atom')
177 _desc = '%s %s %s' % (
177 _desc = '%s %s %s' % (
178 self.rhodecode_name, _('public journal'), 'rss feed')
178 self.rhodecode_name, _('public journal'), 'rss feed')
179 else:
179 else:
180 _link = h.route_url('journal_atom')
180 _link = h.route_url('journal_atom')
181 _desc = '%s %s %s' % (
181 _desc = '%s %s %s' % (
182 self.rhodecode_name, _('journal'), 'rss feed')
182 self.rhodecode_name, _('journal'), 'rss feed')
183
183
184 feed = Rss201rev2Feed(
184 feed = Rss201rev2Feed(
185 title=_desc, link=_link, description=_desc,
185 title=_desc, link=_link, description=_desc,
186 language=self.language, ttl=self.ttl)
186 language=self.language, ttl=self.ttl)
187
187
188 for entry in journal[:self.feed_items_per_page]:
188 for entry in journal[:self.feed_items_per_page]:
189 user = entry.user
189 user = entry.user
190 if user is None:
190 if user is None:
191 # fix deleted users
191 # fix deleted users
192 user = AttributeDict({'short_contact': entry.username,
192 user = AttributeDict({'short_contact': entry.username,
193 'email': '',
193 'email': '',
194 'full_contact': ''})
194 'full_contact': ''})
195 action, action_extra, ico = h.action_parser(
195 action, action_extra, ico = h.action_parser(
196 self.request, entry, feed=True)
196 self.request, entry, feed=True)
197 title = "%s - %s %s" % (user.short_contact, action(),
197 title = "%s - %s %s" % (user.short_contact, action(),
198 entry.repository.repo_name)
198 entry.repository.repo_name)
199 desc = action_extra()
199 desc = action_extra()
200 _url = h.route_url('home')
200 _url = h.route_url('home')
201 if entry.repository is not None:
201 if entry.repository is not None:
202 _url = h.route_url('repo_changelog',
202 _url = h.route_url('repo_changelog',
203 repo_name=entry.repository.repo_name)
203 repo_name=entry.repository.repo_name)
204
204
205 feed.add_item(
205 feed.add_item(
206 unique_id=self.feed_uid(entry.user_log_id),
206 unique_id=self.feed_uid(entry.user_log_id),
207 title=title,
207 title=title,
208 pubdate=entry.action_date,
208 pubdate=entry.action_date,
209 link=_url,
209 link=_url,
210 author_email=user.email,
210 author_email=user.email,
211 author_name=user.full_contact,
211 author_name=user.full_contact,
212 description=desc)
212 description=desc)
213
213
214 response = Response(feed.writeString('utf-8'))
214 response = Response(feed.writeString('utf-8'))
215 response.content_type = feed.mime_type
215 response.content_type = feed.mime_type
216 return response
216 return response
217
217
218 @LoginRequired()
218 @LoginRequired()
219 @NotAnonymous()
219 @NotAnonymous()
220 @view_config(
220 @view_config(
221 route_name='journal', request_method='GET',
221 route_name='journal', request_method='GET',
222 renderer=None)
222 renderer=None)
223 def journal(self):
223 def journal(self):
224 c = self.load_default_context()
224 c = self.load_default_context()
225
225
226 p = safe_int(self.request.GET.get('page', 1), 1)
226 p = safe_int(self.request.GET.get('page', 1), 1)
227 c.user = User.get(self._rhodecode_user.user_id)
227 c.user = User.get(self._rhodecode_user.user_id)
228 following = Session().query(UserFollowing)\
228 following = Session().query(UserFollowing)\
229 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
229 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
230 .options(joinedload(UserFollowing.follows_repository))\
230 .options(joinedload(UserFollowing.follows_repository))\
231 .all()
231 .all()
232
232
233 journal = self._get_journal_data(following, c.search_term)
233 journal = self._get_journal_data(following, c.search_term)
234
234
235 def url_generator(**kw):
235 def url_generator(**kw):
236 query_params = {
236 query_params = {
237 'filter': c.search_term
237 'filter': c.search_term
238 }
238 }
239 query_params.update(kw)
239 query_params.update(kw)
240 return self.request.current_route_path(_query=query_params)
240 return self.request.current_route_path(_query=query_params)
241
241
242 c.journal_pager = Page(
242 c.journal_pager = Page(
243 journal, page=p, items_per_page=20, url=url_generator)
243 journal, page=p, items_per_page=20, url=url_generator)
244 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
244 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
245
245
246 c.journal_data = render(
246 c.journal_data = render(
247 'rhodecode:templates/journal/journal_data.mako',
247 'rhodecode:templates/journal/journal_data.mako',
248 self._get_template_context(c), self.request)
248 self._get_template_context(c), self.request)
249
249
250 if self.request.is_xhr:
250 if self.request.is_xhr:
251 return Response(c.journal_data)
251 return Response(c.journal_data)
252
252
253 html = render(
253 html = render(
254 'rhodecode:templates/journal/journal.mako',
254 'rhodecode:templates/journal/journal.mako',
255 self._get_template_context(c), self.request)
255 self._get_template_context(c), self.request)
256 return Response(html)
256 return Response(html)
257
257
258 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
258 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
259 @NotAnonymous()
259 @NotAnonymous()
260 @view_config(
260 @view_config(
261 route_name='journal_atom', request_method='GET',
261 route_name='journal_atom', request_method='GET',
262 renderer=None)
262 renderer=None)
263 def journal_atom(self):
263 def journal_atom(self):
264 """
264 """
265 Produce an atom-1.0 feed via feedgenerator module
265 Produce an atom-1.0 feed via feedgenerator module
266 """
266 """
267 c = self.load_default_context()
267 c = self.load_default_context()
268 following_repos = Session().query(UserFollowing)\
268 following_repos = Session().query(UserFollowing)\
269 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
269 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
270 .options(joinedload(UserFollowing.follows_repository))\
270 .options(joinedload(UserFollowing.follows_repository))\
271 .all()
271 .all()
272 return self._atom_feed(following_repos, c.search_term, public=False)
272 return self._atom_feed(following_repos, c.search_term, public=False)
273
273
274 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
274 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
275 @NotAnonymous()
275 @NotAnonymous()
276 @view_config(
276 @view_config(
277 route_name='journal_rss', request_method='GET',
277 route_name='journal_rss', request_method='GET',
278 renderer=None)
278 renderer=None)
279 def journal_rss(self):
279 def journal_rss(self):
280 """
280 """
281 Produce an rss feed via feedgenerator module
281 Produce an rss feed via feedgenerator module
282 """
282 """
283 c = self.load_default_context()
283 c = self.load_default_context()
284 following_repos = Session().query(UserFollowing)\
284 following_repos = Session().query(UserFollowing)\
285 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
285 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
286 .options(joinedload(UserFollowing.follows_repository))\
286 .options(joinedload(UserFollowing.follows_repository))\
287 .all()
287 .all()
288 return self._rss_feed(following_repos, c.search_term, public=False)
288 return self._rss_feed(following_repos, c.search_term, public=False)
289
289
290 @LoginRequired()
290 @LoginRequired()
291 @NotAnonymous()
291 @NotAnonymous()
292 @CSRFRequired()
292 @CSRFRequired()
293 @view_config(
293 @view_config(
294 route_name='toggle_following', request_method='POST',
294 route_name='toggle_following', request_method='POST',
295 renderer='json_ext')
295 renderer='json_ext')
296 def toggle_following(self):
296 def toggle_following(self):
297 user_id = self.request.POST.get('follows_user_id')
297 user_id = self.request.POST.get('follows_user_id')
298 if user_id:
298 if user_id:
299 try:
299 try:
300 ScmModel().toggle_following_user(
300 ScmModel().toggle_following_user(user_id, self._rhodecode_user.user_id)
301 user_id, self._rhodecode_user.user_id)
302 Session().commit()
301 Session().commit()
303 return 'ok'
302 return 'ok'
304 except Exception:
303 except Exception:
305 raise HTTPBadRequest()
304 raise HTTPBadRequest()
306
305
307 repo_id = self.request.POST.get('follows_repo_id')
306 repo_id = self.request.POST.get('follows_repo_id')
308 if repo_id:
307 if repo_id:
309 try:
308 try:
310 ScmModel().toggle_following_repo(
309 ScmModel().toggle_following_repo(repo_id, self._rhodecode_user.user_id)
311 repo_id, self._rhodecode_user.user_id)
312 Session().commit()
310 Session().commit()
313 return 'ok'
311 return 'ok'
314 except Exception:
312 except Exception:
315 raise HTTPBadRequest()
313 raise HTTPBadRequest()
316
314
317 raise HTTPBadRequest()
315 raise HTTPBadRequest()
318
316
319 @LoginRequired()
317 @LoginRequired()
320 @view_config(
318 @view_config(
321 route_name='journal_public', request_method='GET',
319 route_name='journal_public', request_method='GET',
322 renderer=None)
320 renderer=None)
323 def journal_public(self):
321 def journal_public(self):
324 c = self.load_default_context()
322 c = self.load_default_context()
325 # Return a rendered template
323 # Return a rendered template
326 p = safe_int(self.request.GET.get('page', 1), 1)
324 p = safe_int(self.request.GET.get('page', 1), 1)
327
325
328 c.following = Session().query(UserFollowing)\
326 c.following = Session().query(UserFollowing)\
329 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
327 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
330 .options(joinedload(UserFollowing.follows_repository))\
328 .options(joinedload(UserFollowing.follows_repository))\
331 .all()
329 .all()
332
330
333 journal = self._get_journal_data(c.following, c.search_term)
331 journal = self._get_journal_data(c.following, c.search_term)
334
332
335 def url_generator(**kw):
333 def url_generator(**kw):
336 query_params = {}
334 query_params = {}
337 query_params.update(kw)
335 query_params.update(kw)
338 return self.request.current_route_path(_query=query_params)
336 return self.request.current_route_path(_query=query_params)
339
337
340 c.journal_pager = Page(
338 c.journal_pager = Page(
341 journal, page=p, items_per_page=20, url=url_generator)
339 journal, page=p, items_per_page=20, url=url_generator)
342 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
340 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
343
341
344 c.journal_data = render(
342 c.journal_data = render(
345 'rhodecode:templates/journal/journal_data.mako',
343 'rhodecode:templates/journal/journal_data.mako',
346 self._get_template_context(c), self.request)
344 self._get_template_context(c), self.request)
347
345
348 if self.request.is_xhr:
346 if self.request.is_xhr:
349 return Response(c.journal_data)
347 return Response(c.journal_data)
350
348
351 html = render(
349 html = render(
352 'rhodecode:templates/journal/public_journal.mako',
350 'rhodecode:templates/journal/public_journal.mako',
353 self._get_template_context(c), self.request)
351 self._get_template_context(c), self.request)
354 return Response(html)
352 return Response(html)
355
353
356 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
354 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
357 @view_config(
355 @view_config(
358 route_name='journal_public_atom', request_method='GET',
356 route_name='journal_public_atom', request_method='GET',
359 renderer=None)
357 renderer=None)
360 def journal_public_atom(self):
358 def journal_public_atom(self):
361 """
359 """
362 Produce an atom-1.0 feed via feedgenerator module
360 Produce an atom-1.0 feed via feedgenerator module
363 """
361 """
364 c = self.load_default_context()
362 c = self.load_default_context()
365 following_repos = Session().query(UserFollowing)\
363 following_repos = Session().query(UserFollowing)\
366 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
364 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
367 .options(joinedload(UserFollowing.follows_repository))\
365 .options(joinedload(UserFollowing.follows_repository))\
368 .all()
366 .all()
369
367
370 return self._atom_feed(following_repos, c.search_term)
368 return self._atom_feed(following_repos, c.search_term)
371
369
372 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
370 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
373 @view_config(
371 @view_config(
374 route_name='journal_public_rss', request_method='GET',
372 route_name='journal_public_rss', request_method='GET',
375 renderer=None)
373 renderer=None)
376 def journal_public_rss(self):
374 def journal_public_rss(self):
377 """
375 """
378 Produce an rss2 feed via feedgenerator module
376 Produce an rss2 feed via feedgenerator module
379 """
377 """
380 c = self.load_default_context()
378 c = self.load_default_context()
381 following_repos = Session().query(UserFollowing)\
379 following_repos = Session().query(UserFollowing)\
382 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
380 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
383 .options(joinedload(UserFollowing.follows_repository))\
381 .options(joinedload(UserFollowing.follows_repository))\
384 .all()
382 .all()
385
383
386 return self._rss_feed(following_repos, c.search_term)
384 return self._rss_feed(following_repos, c.search_term)
@@ -1,743 +1,743 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 logging
21 import logging
22 import datetime
22 import datetime
23 import string
23 import string
24
24
25 import formencode
25 import formencode
26 import formencode.htmlfill
26 import formencode.htmlfill
27 import peppercorn
27 import peppercorn
28 from pyramid.httpexceptions import HTTPFound
28 from pyramid.httpexceptions import HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode import forms
32 from rhodecode import forms
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, \
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, \
37 HasRepoPermissionAny, HasRepoGroupPermissionAny
37 HasRepoPermissionAny, HasRepoGroupPermissionAny
38 from rhodecode.lib.channelstream import (
38 from rhodecode.lib.channelstream import (
39 channelstream_request, ChannelstreamException)
39 channelstream_request, ChannelstreamException)
40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
41 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.auth_token import AuthTokenModel
42 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.db import (
43 from rhodecode.model.db import (
44 IntegrityError, joinedload,
44 IntegrityError, joinedload,
45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
46 PullRequest, UserBookmark, RepoGroup)
46 PullRequest, UserBookmark, RepoGroup)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.scm import RepoList
49 from rhodecode.model.scm import RepoList
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.validation_schema.schemas import user_schema
53 from rhodecode.model.validation_schema.schemas import user_schema
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class MyAccountView(BaseAppView, DataGridAppView):
58 class MyAccountView(BaseAppView, DataGridAppView):
59 ALLOW_SCOPED_TOKENS = False
59 ALLOW_SCOPED_TOKENS = False
60 """
60 """
61 This view has alternative version inside EE, if modified please take a look
61 This view has alternative version inside EE, if modified please take a look
62 in there as well.
62 in there as well.
63 """
63 """
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.user = c.auth_user.get_instance()
67 c.user = c.auth_user.get_instance()
68 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
68 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
69
69
70 return c
70 return c
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @NotAnonymous()
73 @NotAnonymous()
74 @view_config(
74 @view_config(
75 route_name='my_account_profile', request_method='GET',
75 route_name='my_account_profile', request_method='GET',
76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
77 def my_account_profile(self):
77 def my_account_profile(self):
78 c = self.load_default_context()
78 c = self.load_default_context()
79 c.active = 'profile'
79 c.active = 'profile'
80 return self._get_template_context(c)
80 return self._get_template_context(c)
81
81
82 @LoginRequired()
82 @LoginRequired()
83 @NotAnonymous()
83 @NotAnonymous()
84 @view_config(
84 @view_config(
85 route_name='my_account_password', request_method='GET',
85 route_name='my_account_password', request_method='GET',
86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
87 def my_account_password(self):
87 def my_account_password(self):
88 c = self.load_default_context()
88 c = self.load_default_context()
89 c.active = 'password'
89 c.active = 'password'
90 c.extern_type = c.user.extern_type
90 c.extern_type = c.user.extern_type
91
91
92 schema = user_schema.ChangePasswordSchema().bind(
92 schema = user_schema.ChangePasswordSchema().bind(
93 username=c.user.username)
93 username=c.user.username)
94
94
95 form = forms.Form(
95 form = forms.Form(
96 schema,
96 schema,
97 action=h.route_path('my_account_password_update'),
97 action=h.route_path('my_account_password_update'),
98 buttons=(forms.buttons.save, forms.buttons.reset))
98 buttons=(forms.buttons.save, forms.buttons.reset))
99
99
100 c.form = form
100 c.form = form
101 return self._get_template_context(c)
101 return self._get_template_context(c)
102
102
103 @LoginRequired()
103 @LoginRequired()
104 @NotAnonymous()
104 @NotAnonymous()
105 @CSRFRequired()
105 @CSRFRequired()
106 @view_config(
106 @view_config(
107 route_name='my_account_password_update', request_method='POST',
107 route_name='my_account_password_update', request_method='POST',
108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
109 def my_account_password_update(self):
109 def my_account_password_update(self):
110 _ = self.request.translate
110 _ = self.request.translate
111 c = self.load_default_context()
111 c = self.load_default_context()
112 c.active = 'password'
112 c.active = 'password'
113 c.extern_type = c.user.extern_type
113 c.extern_type = c.user.extern_type
114
114
115 schema = user_schema.ChangePasswordSchema().bind(
115 schema = user_schema.ChangePasswordSchema().bind(
116 username=c.user.username)
116 username=c.user.username)
117
117
118 form = forms.Form(
118 form = forms.Form(
119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
120
120
121 if c.extern_type != 'rhodecode':
121 if c.extern_type != 'rhodecode':
122 raise HTTPFound(self.request.route_path('my_account_password'))
122 raise HTTPFound(self.request.route_path('my_account_password'))
123
123
124 controls = self.request.POST.items()
124 controls = self.request.POST.items()
125 try:
125 try:
126 valid_data = form.validate(controls)
126 valid_data = form.validate(controls)
127 UserModel().update_user(c.user.user_id, **valid_data)
127 UserModel().update_user(c.user.user_id, **valid_data)
128 c.user.update_userdata(force_password_change=False)
128 c.user.update_userdata(force_password_change=False)
129 Session().commit()
129 Session().commit()
130 except forms.ValidationFailure as e:
130 except forms.ValidationFailure as e:
131 c.form = e
131 c.form = e
132 return self._get_template_context(c)
132 return self._get_template_context(c)
133
133
134 except Exception:
134 except Exception:
135 log.exception("Exception updating password")
135 log.exception("Exception updating password")
136 h.flash(_('Error occurred during update of user password'),
136 h.flash(_('Error occurred during update of user password'),
137 category='error')
137 category='error')
138 else:
138 else:
139 instance = c.auth_user.get_instance()
139 instance = c.auth_user.get_instance()
140 self.session.setdefault('rhodecode_user', {}).update(
140 self.session.setdefault('rhodecode_user', {}).update(
141 {'password': md5(instance.password)})
141 {'password': md5(instance.password)})
142 self.session.save()
142 self.session.save()
143 h.flash(_("Successfully updated password"), category='success')
143 h.flash(_("Successfully updated password"), category='success')
144
144
145 raise HTTPFound(self.request.route_path('my_account_password'))
145 raise HTTPFound(self.request.route_path('my_account_password'))
146
146
147 @LoginRequired()
147 @LoginRequired()
148 @NotAnonymous()
148 @NotAnonymous()
149 @view_config(
149 @view_config(
150 route_name='my_account_auth_tokens', request_method='GET',
150 route_name='my_account_auth_tokens', request_method='GET',
151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
152 def my_account_auth_tokens(self):
152 def my_account_auth_tokens(self):
153 _ = self.request.translate
153 _ = self.request.translate
154
154
155 c = self.load_default_context()
155 c = self.load_default_context()
156 c.active = 'auth_tokens'
156 c.active = 'auth_tokens'
157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
158 c.role_values = [
158 c.role_values = [
159 (x, AuthTokenModel.cls._get_role_name(x))
159 (x, AuthTokenModel.cls._get_role_name(x))
160 for x in AuthTokenModel.cls.ROLES]
160 for x in AuthTokenModel.cls.ROLES]
161 c.role_options = [(c.role_values, _("Role"))]
161 c.role_options = [(c.role_values, _("Role"))]
162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
163 c.user.user_id, show_expired=True)
163 c.user.user_id, show_expired=True)
164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
165 return self._get_template_context(c)
165 return self._get_template_context(c)
166
166
167 def maybe_attach_token_scope(self, token):
167 def maybe_attach_token_scope(self, token):
168 # implemented in EE edition
168 # implemented in EE edition
169 pass
169 pass
170
170
171 @LoginRequired()
171 @LoginRequired()
172 @NotAnonymous()
172 @NotAnonymous()
173 @CSRFRequired()
173 @CSRFRequired()
174 @view_config(
174 @view_config(
175 route_name='my_account_auth_tokens_add', request_method='POST',)
175 route_name='my_account_auth_tokens_add', request_method='POST',)
176 def my_account_auth_tokens_add(self):
176 def my_account_auth_tokens_add(self):
177 _ = self.request.translate
177 _ = self.request.translate
178 c = self.load_default_context()
178 c = self.load_default_context()
179
179
180 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
180 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
181 description = self.request.POST.get('description')
181 description = self.request.POST.get('description')
182 role = self.request.POST.get('role')
182 role = self.request.POST.get('role')
183
183
184 token = UserModel().add_auth_token(
184 token = UserModel().add_auth_token(
185 user=c.user.user_id,
185 user=c.user.user_id,
186 lifetime_minutes=lifetime, role=role, description=description,
186 lifetime_minutes=lifetime, role=role, description=description,
187 scope_callback=self.maybe_attach_token_scope)
187 scope_callback=self.maybe_attach_token_scope)
188 token_data = token.get_api_data()
188 token_data = token.get_api_data()
189
189
190 audit_logger.store_web(
190 audit_logger.store_web(
191 'user.edit.token.add', action_data={
191 'user.edit.token.add', action_data={
192 'data': {'token': token_data, 'user': 'self'}},
192 'data': {'token': token_data, 'user': 'self'}},
193 user=self._rhodecode_user, )
193 user=self._rhodecode_user, )
194 Session().commit()
194 Session().commit()
195
195
196 h.flash(_("Auth token successfully created"), category='success')
196 h.flash(_("Auth token successfully created"), category='success')
197 return HTTPFound(h.route_path('my_account_auth_tokens'))
197 return HTTPFound(h.route_path('my_account_auth_tokens'))
198
198
199 @LoginRequired()
199 @LoginRequired()
200 @NotAnonymous()
200 @NotAnonymous()
201 @CSRFRequired()
201 @CSRFRequired()
202 @view_config(
202 @view_config(
203 route_name='my_account_auth_tokens_delete', request_method='POST')
203 route_name='my_account_auth_tokens_delete', request_method='POST')
204 def my_account_auth_tokens_delete(self):
204 def my_account_auth_tokens_delete(self):
205 _ = self.request.translate
205 _ = self.request.translate
206 c = self.load_default_context()
206 c = self.load_default_context()
207
207
208 del_auth_token = self.request.POST.get('del_auth_token')
208 del_auth_token = self.request.POST.get('del_auth_token')
209
209
210 if del_auth_token:
210 if del_auth_token:
211 token = UserApiKeys.get_or_404(del_auth_token)
211 token = UserApiKeys.get_or_404(del_auth_token)
212 token_data = token.get_api_data()
212 token_data = token.get_api_data()
213
213
214 AuthTokenModel().delete(del_auth_token, c.user.user_id)
214 AuthTokenModel().delete(del_auth_token, c.user.user_id)
215 audit_logger.store_web(
215 audit_logger.store_web(
216 'user.edit.token.delete', action_data={
216 'user.edit.token.delete', action_data={
217 'data': {'token': token_data, 'user': 'self'}},
217 'data': {'token': token_data, 'user': 'self'}},
218 user=self._rhodecode_user,)
218 user=self._rhodecode_user,)
219 Session().commit()
219 Session().commit()
220 h.flash(_("Auth token successfully deleted"), category='success')
220 h.flash(_("Auth token successfully deleted"), category='success')
221
221
222 return HTTPFound(h.route_path('my_account_auth_tokens'))
222 return HTTPFound(h.route_path('my_account_auth_tokens'))
223
223
224 @LoginRequired()
224 @LoginRequired()
225 @NotAnonymous()
225 @NotAnonymous()
226 @view_config(
226 @view_config(
227 route_name='my_account_emails', request_method='GET',
227 route_name='my_account_emails', request_method='GET',
228 renderer='rhodecode:templates/admin/my_account/my_account.mako')
228 renderer='rhodecode:templates/admin/my_account/my_account.mako')
229 def my_account_emails(self):
229 def my_account_emails(self):
230 _ = self.request.translate
230 _ = self.request.translate
231
231
232 c = self.load_default_context()
232 c = self.load_default_context()
233 c.active = 'emails'
233 c.active = 'emails'
234
234
235 c.user_email_map = UserEmailMap.query()\
235 c.user_email_map = UserEmailMap.query()\
236 .filter(UserEmailMap.user == c.user).all()
236 .filter(UserEmailMap.user == c.user).all()
237
237
238 schema = user_schema.AddEmailSchema().bind(
238 schema = user_schema.AddEmailSchema().bind(
239 username=c.user.username, user_emails=c.user.emails)
239 username=c.user.username, user_emails=c.user.emails)
240
240
241 form = forms.RcForm(schema,
241 form = forms.RcForm(schema,
242 action=h.route_path('my_account_emails_add'),
242 action=h.route_path('my_account_emails_add'),
243 buttons=(forms.buttons.save, forms.buttons.reset))
243 buttons=(forms.buttons.save, forms.buttons.reset))
244
244
245 c.form = form
245 c.form = form
246 return self._get_template_context(c)
246 return self._get_template_context(c)
247
247
248 @LoginRequired()
248 @LoginRequired()
249 @NotAnonymous()
249 @NotAnonymous()
250 @CSRFRequired()
250 @CSRFRequired()
251 @view_config(
251 @view_config(
252 route_name='my_account_emails_add', request_method='POST',
252 route_name='my_account_emails_add', request_method='POST',
253 renderer='rhodecode:templates/admin/my_account/my_account.mako')
253 renderer='rhodecode:templates/admin/my_account/my_account.mako')
254 def my_account_emails_add(self):
254 def my_account_emails_add(self):
255 _ = self.request.translate
255 _ = self.request.translate
256 c = self.load_default_context()
256 c = self.load_default_context()
257 c.active = 'emails'
257 c.active = 'emails'
258
258
259 schema = user_schema.AddEmailSchema().bind(
259 schema = user_schema.AddEmailSchema().bind(
260 username=c.user.username, user_emails=c.user.emails)
260 username=c.user.username, user_emails=c.user.emails)
261
261
262 form = forms.RcForm(
262 form = forms.RcForm(
263 schema, action=h.route_path('my_account_emails_add'),
263 schema, action=h.route_path('my_account_emails_add'),
264 buttons=(forms.buttons.save, forms.buttons.reset))
264 buttons=(forms.buttons.save, forms.buttons.reset))
265
265
266 controls = self.request.POST.items()
266 controls = self.request.POST.items()
267 try:
267 try:
268 valid_data = form.validate(controls)
268 valid_data = form.validate(controls)
269 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
269 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
270 audit_logger.store_web(
270 audit_logger.store_web(
271 'user.edit.email.add', action_data={
271 'user.edit.email.add', action_data={
272 'data': {'email': valid_data['email'], 'user': 'self'}},
272 'data': {'email': valid_data['email'], 'user': 'self'}},
273 user=self._rhodecode_user,)
273 user=self._rhodecode_user,)
274 Session().commit()
274 Session().commit()
275 except formencode.Invalid as error:
275 except formencode.Invalid as error:
276 h.flash(h.escape(error.error_dict['email']), category='error')
276 h.flash(h.escape(error.error_dict['email']), category='error')
277 except forms.ValidationFailure as e:
277 except forms.ValidationFailure as e:
278 c.user_email_map = UserEmailMap.query() \
278 c.user_email_map = UserEmailMap.query() \
279 .filter(UserEmailMap.user == c.user).all()
279 .filter(UserEmailMap.user == c.user).all()
280 c.form = e
280 c.form = e
281 return self._get_template_context(c)
281 return self._get_template_context(c)
282 except Exception:
282 except Exception:
283 log.exception("Exception adding email")
283 log.exception("Exception adding email")
284 h.flash(_('Error occurred during adding email'),
284 h.flash(_('Error occurred during adding email'),
285 category='error')
285 category='error')
286 else:
286 else:
287 h.flash(_("Successfully added email"), category='success')
287 h.flash(_("Successfully added email"), category='success')
288
288
289 raise HTTPFound(self.request.route_path('my_account_emails'))
289 raise HTTPFound(self.request.route_path('my_account_emails'))
290
290
291 @LoginRequired()
291 @LoginRequired()
292 @NotAnonymous()
292 @NotAnonymous()
293 @CSRFRequired()
293 @CSRFRequired()
294 @view_config(
294 @view_config(
295 route_name='my_account_emails_delete', request_method='POST')
295 route_name='my_account_emails_delete', request_method='POST')
296 def my_account_emails_delete(self):
296 def my_account_emails_delete(self):
297 _ = self.request.translate
297 _ = self.request.translate
298 c = self.load_default_context()
298 c = self.load_default_context()
299
299
300 del_email_id = self.request.POST.get('del_email_id')
300 del_email_id = self.request.POST.get('del_email_id')
301 if del_email_id:
301 if del_email_id:
302 email = UserEmailMap.get_or_404(del_email_id).email
302 email = UserEmailMap.get_or_404(del_email_id).email
303 UserModel().delete_extra_email(c.user.user_id, del_email_id)
303 UserModel().delete_extra_email(c.user.user_id, del_email_id)
304 audit_logger.store_web(
304 audit_logger.store_web(
305 'user.edit.email.delete', action_data={
305 'user.edit.email.delete', action_data={
306 'data': {'email': email, 'user': 'self'}},
306 'data': {'email': email, 'user': 'self'}},
307 user=self._rhodecode_user,)
307 user=self._rhodecode_user,)
308 Session().commit()
308 Session().commit()
309 h.flash(_("Email successfully deleted"),
309 h.flash(_("Email successfully deleted"),
310 category='success')
310 category='success')
311 return HTTPFound(h.route_path('my_account_emails'))
311 return HTTPFound(h.route_path('my_account_emails'))
312
312
313 @LoginRequired()
313 @LoginRequired()
314 @NotAnonymous()
314 @NotAnonymous()
315 @CSRFRequired()
315 @CSRFRequired()
316 @view_config(
316 @view_config(
317 route_name='my_account_notifications_test_channelstream',
317 route_name='my_account_notifications_test_channelstream',
318 request_method='POST', renderer='json_ext')
318 request_method='POST', renderer='json_ext')
319 def my_account_notifications_test_channelstream(self):
319 def my_account_notifications_test_channelstream(self):
320 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
320 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
321 self._rhodecode_user.username, datetime.datetime.now())
321 self._rhodecode_user.username, datetime.datetime.now())
322 payload = {
322 payload = {
323 # 'channel': 'broadcast',
323 # 'channel': 'broadcast',
324 'type': 'message',
324 'type': 'message',
325 'timestamp': datetime.datetime.utcnow(),
325 'timestamp': datetime.datetime.utcnow(),
326 'user': 'system',
326 'user': 'system',
327 'pm_users': [self._rhodecode_user.username],
327 'pm_users': [self._rhodecode_user.username],
328 'message': {
328 'message': {
329 'message': message,
329 'message': message,
330 'level': 'info',
330 'level': 'info',
331 'topic': '/notifications'
331 'topic': '/notifications'
332 }
332 }
333 }
333 }
334
334
335 registry = self.request.registry
335 registry = self.request.registry
336 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
336 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
337 channelstream_config = rhodecode_plugins.get('channelstream', {})
337 channelstream_config = rhodecode_plugins.get('channelstream', {})
338
338
339 try:
339 try:
340 channelstream_request(channelstream_config, [payload], '/message')
340 channelstream_request(channelstream_config, [payload], '/message')
341 except ChannelstreamException as e:
341 except ChannelstreamException as e:
342 log.exception('Failed to send channelstream data')
342 log.exception('Failed to send channelstream data')
343 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
343 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
344 return {"response": 'Channelstream data sent. '
344 return {"response": 'Channelstream data sent. '
345 'You should see a new live message now.'}
345 'You should see a new live message now.'}
346
346
347 def _load_my_repos_data(self, watched=False):
347 def _load_my_repos_data(self, watched=False):
348 if watched:
348 if watched:
349 admin = False
349 admin = False
350 follows_repos = Session().query(UserFollowing)\
350 follows_repos = Session().query(UserFollowing)\
351 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
351 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
352 .options(joinedload(UserFollowing.follows_repository))\
352 .options(joinedload(UserFollowing.follows_repository))\
353 .all()
353 .all()
354 repo_list = [x.follows_repository for x in follows_repos]
354 repo_list = [x.follows_repository for x in follows_repos]
355 else:
355 else:
356 admin = True
356 admin = True
357 repo_list = Repository.get_all_repos(
357 repo_list = Repository.get_all_repos(
358 user_id=self._rhodecode_user.user_id)
358 user_id=self._rhodecode_user.user_id)
359 repo_list = RepoList(repo_list, perm_set=[
359 repo_list = RepoList(repo_list, perm_set=[
360 'repository.read', 'repository.write', 'repository.admin'])
360 'repository.read', 'repository.write', 'repository.admin'])
361
361
362 repos_data = RepoModel().get_repos_as_dict(
362 repos_data = RepoModel().get_repos_as_dict(
363 repo_list=repo_list, admin=admin)
363 repo_list=repo_list, admin=admin, short_name=False)
364 # json used to render the grid
364 # json used to render the grid
365 return json.dumps(repos_data)
365 return json.dumps(repos_data)
366
366
367 @LoginRequired()
367 @LoginRequired()
368 @NotAnonymous()
368 @NotAnonymous()
369 @view_config(
369 @view_config(
370 route_name='my_account_repos', request_method='GET',
370 route_name='my_account_repos', request_method='GET',
371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
372 def my_account_repos(self):
372 def my_account_repos(self):
373 c = self.load_default_context()
373 c = self.load_default_context()
374 c.active = 'repos'
374 c.active = 'repos'
375
375
376 # json used to render the grid
376 # json used to render the grid
377 c.data = self._load_my_repos_data()
377 c.data = self._load_my_repos_data()
378 return self._get_template_context(c)
378 return self._get_template_context(c)
379
379
380 @LoginRequired()
380 @LoginRequired()
381 @NotAnonymous()
381 @NotAnonymous()
382 @view_config(
382 @view_config(
383 route_name='my_account_watched', request_method='GET',
383 route_name='my_account_watched', request_method='GET',
384 renderer='rhodecode:templates/admin/my_account/my_account.mako')
384 renderer='rhodecode:templates/admin/my_account/my_account.mako')
385 def my_account_watched(self):
385 def my_account_watched(self):
386 c = self.load_default_context()
386 c = self.load_default_context()
387 c.active = 'watched'
387 c.active = 'watched'
388
388
389 # json used to render the grid
389 # json used to render the grid
390 c.data = self._load_my_repos_data(watched=True)
390 c.data = self._load_my_repos_data(watched=True)
391 return self._get_template_context(c)
391 return self._get_template_context(c)
392
392
393 @LoginRequired()
393 @LoginRequired()
394 @NotAnonymous()
394 @NotAnonymous()
395 @view_config(
395 @view_config(
396 route_name='my_account_bookmarks', request_method='GET',
396 route_name='my_account_bookmarks', request_method='GET',
397 renderer='rhodecode:templates/admin/my_account/my_account.mako')
397 renderer='rhodecode:templates/admin/my_account/my_account.mako')
398 def my_account_bookmarks(self):
398 def my_account_bookmarks(self):
399 c = self.load_default_context()
399 c = self.load_default_context()
400 c.active = 'bookmarks'
400 c.active = 'bookmarks'
401 return self._get_template_context(c)
401 return self._get_template_context(c)
402
402
403 def _process_entry(self, entry, user_id):
403 def _process_entry(self, entry, user_id):
404 position = safe_int(entry.get('position'))
404 position = safe_int(entry.get('position'))
405 if position is None:
405 if position is None:
406 return
406 return
407
407
408 # check if this is an existing entry
408 # check if this is an existing entry
409 is_new = False
409 is_new = False
410 db_entry = UserBookmark().get_by_position_for_user(position, user_id)
410 db_entry = UserBookmark().get_by_position_for_user(position, user_id)
411
411
412 if db_entry and str2bool(entry.get('remove')):
412 if db_entry and str2bool(entry.get('remove')):
413 log.debug('Marked bookmark %s for deletion', db_entry)
413 log.debug('Marked bookmark %s for deletion', db_entry)
414 Session().delete(db_entry)
414 Session().delete(db_entry)
415 return
415 return
416
416
417 if not db_entry:
417 if not db_entry:
418 # new
418 # new
419 db_entry = UserBookmark()
419 db_entry = UserBookmark()
420 is_new = True
420 is_new = True
421
421
422 should_save = False
422 should_save = False
423 default_redirect_url = ''
423 default_redirect_url = ''
424
424
425 # save repo
425 # save repo
426 if entry.get('bookmark_repo'):
426 if entry.get('bookmark_repo'):
427 repo = Repository.get(entry['bookmark_repo'])
427 repo = Repository.get(entry['bookmark_repo'])
428 perm_check = HasRepoPermissionAny(
428 perm_check = HasRepoPermissionAny(
429 'repository.read', 'repository.write', 'repository.admin')
429 'repository.read', 'repository.write', 'repository.admin')
430 if repo and perm_check(repo_name=repo.repo_name):
430 if repo and perm_check(repo_name=repo.repo_name):
431 db_entry.repository = repo
431 db_entry.repository = repo
432 should_save = True
432 should_save = True
433 default_redirect_url = '${repo_url}'
433 default_redirect_url = '${repo_url}'
434 # save repo group
434 # save repo group
435 elif entry.get('bookmark_repo_group'):
435 elif entry.get('bookmark_repo_group'):
436 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
436 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
437 perm_check = HasRepoGroupPermissionAny(
437 perm_check = HasRepoGroupPermissionAny(
438 'group.read', 'group.write', 'group.admin')
438 'group.read', 'group.write', 'group.admin')
439
439
440 if repo_group and perm_check(group_name=repo_group.group_name):
440 if repo_group and perm_check(group_name=repo_group.group_name):
441 db_entry.repository_group = repo_group
441 db_entry.repository_group = repo_group
442 should_save = True
442 should_save = True
443 default_redirect_url = '${repo_group_url}'
443 default_redirect_url = '${repo_group_url}'
444 # save generic info
444 # save generic info
445 elif entry.get('title') and entry.get('redirect_url'):
445 elif entry.get('title') and entry.get('redirect_url'):
446 should_save = True
446 should_save = True
447
447
448 if should_save:
448 if should_save:
449 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
449 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
450 # mark user and position
450 # mark user and position
451 db_entry.user_id = user_id
451 db_entry.user_id = user_id
452 db_entry.position = position
452 db_entry.position = position
453 db_entry.title = entry.get('title')
453 db_entry.title = entry.get('title')
454 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
454 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
455
455
456 Session().add(db_entry)
456 Session().add(db_entry)
457
457
458 @LoginRequired()
458 @LoginRequired()
459 @NotAnonymous()
459 @NotAnonymous()
460 @CSRFRequired()
460 @CSRFRequired()
461 @view_config(
461 @view_config(
462 route_name='my_account_bookmarks_update', request_method='POST')
462 route_name='my_account_bookmarks_update', request_method='POST')
463 def my_account_bookmarks_update(self):
463 def my_account_bookmarks_update(self):
464 _ = self.request.translate
464 _ = self.request.translate
465 c = self.load_default_context()
465 c = self.load_default_context()
466 c.active = 'bookmarks'
466 c.active = 'bookmarks'
467
467
468 controls = peppercorn.parse(self.request.POST.items())
468 controls = peppercorn.parse(self.request.POST.items())
469 user_id = c.user.user_id
469 user_id = c.user.user_id
470
470
471 try:
471 try:
472 for entry in controls.get('bookmarks', []):
472 for entry in controls.get('bookmarks', []):
473 self._process_entry(entry, user_id)
473 self._process_entry(entry, user_id)
474
474
475 Session().commit()
475 Session().commit()
476 h.flash(_("Update Bookmarks"), category='success')
476 h.flash(_("Update Bookmarks"), category='success')
477 except IntegrityError:
477 except IntegrityError:
478 h.flash(_("Failed to update bookmarks. "
478 h.flash(_("Failed to update bookmarks. "
479 "Make sure an unique position is used"), category='error')
479 "Make sure an unique position is used"), category='error')
480
480
481 return HTTPFound(h.route_path('my_account_bookmarks'))
481 return HTTPFound(h.route_path('my_account_bookmarks'))
482
482
483 @LoginRequired()
483 @LoginRequired()
484 @NotAnonymous()
484 @NotAnonymous()
485 @view_config(
485 @view_config(
486 route_name='my_account_goto_bookmark', request_method='GET',
486 route_name='my_account_goto_bookmark', request_method='GET',
487 renderer='rhodecode:templates/admin/my_account/my_account.mako')
487 renderer='rhodecode:templates/admin/my_account/my_account.mako')
488 def my_account_goto_bookmark(self):
488 def my_account_goto_bookmark(self):
489
489
490 bookmark_id = self.request.matchdict['bookmark_id']
490 bookmark_id = self.request.matchdict['bookmark_id']
491 user_bookmark = UserBookmark().query()\
491 user_bookmark = UserBookmark().query()\
492 .filter(UserBookmark.user_id == self.request.user.user_id) \
492 .filter(UserBookmark.user_id == self.request.user.user_id) \
493 .filter(UserBookmark.position == bookmark_id).scalar()
493 .filter(UserBookmark.position == bookmark_id).scalar()
494
494
495 redirect_url = h.route_path('my_account_bookmarks')
495 redirect_url = h.route_path('my_account_bookmarks')
496 if not user_bookmark:
496 if not user_bookmark:
497 raise HTTPFound(redirect_url)
497 raise HTTPFound(redirect_url)
498
498
499 # repository set
499 # repository set
500 if user_bookmark.repository:
500 if user_bookmark.repository:
501 repo_name = user_bookmark.repository.repo_name
501 repo_name = user_bookmark.repository.repo_name
502 base_redirect_url = h.route_path(
502 base_redirect_url = h.route_path(
503 'repo_summary', repo_name=repo_name)
503 'repo_summary', repo_name=repo_name)
504 if user_bookmark.redirect_url and \
504 if user_bookmark.redirect_url and \
505 '${repo_url}' in user_bookmark.redirect_url:
505 '${repo_url}' in user_bookmark.redirect_url:
506 redirect_url = string.Template(user_bookmark.redirect_url)\
506 redirect_url = string.Template(user_bookmark.redirect_url)\
507 .safe_substitute({'repo_url': base_redirect_url})
507 .safe_substitute({'repo_url': base_redirect_url})
508 else:
508 else:
509 redirect_url = base_redirect_url
509 redirect_url = base_redirect_url
510 # repository group set
510 # repository group set
511 elif user_bookmark.repository_group:
511 elif user_bookmark.repository_group:
512 repo_group_name = user_bookmark.repository_group.group_name
512 repo_group_name = user_bookmark.repository_group.group_name
513 base_redirect_url = h.route_path(
513 base_redirect_url = h.route_path(
514 'repo_group_home', repo_group_name=repo_group_name)
514 'repo_group_home', repo_group_name=repo_group_name)
515 if user_bookmark.redirect_url and \
515 if user_bookmark.redirect_url and \
516 '${repo_group_url}' in user_bookmark.redirect_url:
516 '${repo_group_url}' in user_bookmark.redirect_url:
517 redirect_url = string.Template(user_bookmark.redirect_url)\
517 redirect_url = string.Template(user_bookmark.redirect_url)\
518 .safe_substitute({'repo_group_url': base_redirect_url})
518 .safe_substitute({'repo_group_url': base_redirect_url})
519 else:
519 else:
520 redirect_url = base_redirect_url
520 redirect_url = base_redirect_url
521 # custom URL set
521 # custom URL set
522 elif user_bookmark.redirect_url:
522 elif user_bookmark.redirect_url:
523 server_url = h.route_url('home').rstrip('/')
523 server_url = h.route_url('home').rstrip('/')
524 redirect_url = string.Template(user_bookmark.redirect_url) \
524 redirect_url = string.Template(user_bookmark.redirect_url) \
525 .safe_substitute({'server_url': server_url})
525 .safe_substitute({'server_url': server_url})
526
526
527 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
527 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
528 raise HTTPFound(redirect_url)
528 raise HTTPFound(redirect_url)
529
529
530 @LoginRequired()
530 @LoginRequired()
531 @NotAnonymous()
531 @NotAnonymous()
532 @view_config(
532 @view_config(
533 route_name='my_account_perms', request_method='GET',
533 route_name='my_account_perms', request_method='GET',
534 renderer='rhodecode:templates/admin/my_account/my_account.mako')
534 renderer='rhodecode:templates/admin/my_account/my_account.mako')
535 def my_account_perms(self):
535 def my_account_perms(self):
536 c = self.load_default_context()
536 c = self.load_default_context()
537 c.active = 'perms'
537 c.active = 'perms'
538
538
539 c.perm_user = c.auth_user
539 c.perm_user = c.auth_user
540 return self._get_template_context(c)
540 return self._get_template_context(c)
541
541
542 @LoginRequired()
542 @LoginRequired()
543 @NotAnonymous()
543 @NotAnonymous()
544 @view_config(
544 @view_config(
545 route_name='my_account_notifications', request_method='GET',
545 route_name='my_account_notifications', request_method='GET',
546 renderer='rhodecode:templates/admin/my_account/my_account.mako')
546 renderer='rhodecode:templates/admin/my_account/my_account.mako')
547 def my_notifications(self):
547 def my_notifications(self):
548 c = self.load_default_context()
548 c = self.load_default_context()
549 c.active = 'notifications'
549 c.active = 'notifications'
550
550
551 return self._get_template_context(c)
551 return self._get_template_context(c)
552
552
553 @LoginRequired()
553 @LoginRequired()
554 @NotAnonymous()
554 @NotAnonymous()
555 @CSRFRequired()
555 @CSRFRequired()
556 @view_config(
556 @view_config(
557 route_name='my_account_notifications_toggle_visibility',
557 route_name='my_account_notifications_toggle_visibility',
558 request_method='POST', renderer='json_ext')
558 request_method='POST', renderer='json_ext')
559 def my_notifications_toggle_visibility(self):
559 def my_notifications_toggle_visibility(self):
560 user = self._rhodecode_db_user
560 user = self._rhodecode_db_user
561 new_status = not user.user_data.get('notification_status', True)
561 new_status = not user.user_data.get('notification_status', True)
562 user.update_userdata(notification_status=new_status)
562 user.update_userdata(notification_status=new_status)
563 Session().commit()
563 Session().commit()
564 return user.user_data['notification_status']
564 return user.user_data['notification_status']
565
565
566 @LoginRequired()
566 @LoginRequired()
567 @NotAnonymous()
567 @NotAnonymous()
568 @view_config(
568 @view_config(
569 route_name='my_account_edit',
569 route_name='my_account_edit',
570 request_method='GET',
570 request_method='GET',
571 renderer='rhodecode:templates/admin/my_account/my_account.mako')
571 renderer='rhodecode:templates/admin/my_account/my_account.mako')
572 def my_account_edit(self):
572 def my_account_edit(self):
573 c = self.load_default_context()
573 c = self.load_default_context()
574 c.active = 'profile_edit'
574 c.active = 'profile_edit'
575 c.extern_type = c.user.extern_type
575 c.extern_type = c.user.extern_type
576 c.extern_name = c.user.extern_name
576 c.extern_name = c.user.extern_name
577
577
578 schema = user_schema.UserProfileSchema().bind(
578 schema = user_schema.UserProfileSchema().bind(
579 username=c.user.username, user_emails=c.user.emails)
579 username=c.user.username, user_emails=c.user.emails)
580 appstruct = {
580 appstruct = {
581 'username': c.user.username,
581 'username': c.user.username,
582 'email': c.user.email,
582 'email': c.user.email,
583 'firstname': c.user.firstname,
583 'firstname': c.user.firstname,
584 'lastname': c.user.lastname,
584 'lastname': c.user.lastname,
585 }
585 }
586 c.form = forms.RcForm(
586 c.form = forms.RcForm(
587 schema, appstruct=appstruct,
587 schema, appstruct=appstruct,
588 action=h.route_path('my_account_update'),
588 action=h.route_path('my_account_update'),
589 buttons=(forms.buttons.save, forms.buttons.reset))
589 buttons=(forms.buttons.save, forms.buttons.reset))
590
590
591 return self._get_template_context(c)
591 return self._get_template_context(c)
592
592
593 @LoginRequired()
593 @LoginRequired()
594 @NotAnonymous()
594 @NotAnonymous()
595 @CSRFRequired()
595 @CSRFRequired()
596 @view_config(
596 @view_config(
597 route_name='my_account_update',
597 route_name='my_account_update',
598 request_method='POST',
598 request_method='POST',
599 renderer='rhodecode:templates/admin/my_account/my_account.mako')
599 renderer='rhodecode:templates/admin/my_account/my_account.mako')
600 def my_account_update(self):
600 def my_account_update(self):
601 _ = self.request.translate
601 _ = self.request.translate
602 c = self.load_default_context()
602 c = self.load_default_context()
603 c.active = 'profile_edit'
603 c.active = 'profile_edit'
604 c.perm_user = c.auth_user
604 c.perm_user = c.auth_user
605 c.extern_type = c.user.extern_type
605 c.extern_type = c.user.extern_type
606 c.extern_name = c.user.extern_name
606 c.extern_name = c.user.extern_name
607
607
608 schema = user_schema.UserProfileSchema().bind(
608 schema = user_schema.UserProfileSchema().bind(
609 username=c.user.username, user_emails=c.user.emails)
609 username=c.user.username, user_emails=c.user.emails)
610 form = forms.RcForm(
610 form = forms.RcForm(
611 schema, buttons=(forms.buttons.save, forms.buttons.reset))
611 schema, buttons=(forms.buttons.save, forms.buttons.reset))
612
612
613 controls = self.request.POST.items()
613 controls = self.request.POST.items()
614 try:
614 try:
615 valid_data = form.validate(controls)
615 valid_data = form.validate(controls)
616 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
616 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
617 'new_password', 'password_confirmation']
617 'new_password', 'password_confirmation']
618 if c.extern_type != "rhodecode":
618 if c.extern_type != "rhodecode":
619 # forbid updating username for external accounts
619 # forbid updating username for external accounts
620 skip_attrs.append('username')
620 skip_attrs.append('username')
621 old_email = c.user.email
621 old_email = c.user.email
622 UserModel().update_user(
622 UserModel().update_user(
623 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
623 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
624 **valid_data)
624 **valid_data)
625 if old_email != valid_data['email']:
625 if old_email != valid_data['email']:
626 old = UserEmailMap.query() \
626 old = UserEmailMap.query() \
627 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
627 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
628 old.email = old_email
628 old.email = old_email
629 h.flash(_('Your account was updated successfully'), category='success')
629 h.flash(_('Your account was updated successfully'), category='success')
630 Session().commit()
630 Session().commit()
631 except forms.ValidationFailure as e:
631 except forms.ValidationFailure as e:
632 c.form = e
632 c.form = e
633 return self._get_template_context(c)
633 return self._get_template_context(c)
634 except Exception:
634 except Exception:
635 log.exception("Exception updating user")
635 log.exception("Exception updating user")
636 h.flash(_('Error occurred during update of user'),
636 h.flash(_('Error occurred during update of user'),
637 category='error')
637 category='error')
638 raise HTTPFound(h.route_path('my_account_profile'))
638 raise HTTPFound(h.route_path('my_account_profile'))
639
639
640 def _get_pull_requests_list(self, statuses):
640 def _get_pull_requests_list(self, statuses):
641 draw, start, limit = self._extract_chunk(self.request)
641 draw, start, limit = self._extract_chunk(self.request)
642 search_q, order_by, order_dir = self._extract_ordering(self.request)
642 search_q, order_by, order_dir = self._extract_ordering(self.request)
643 _render = self.request.get_partial_renderer(
643 _render = self.request.get_partial_renderer(
644 'rhodecode:templates/data_table/_dt_elements.mako')
644 'rhodecode:templates/data_table/_dt_elements.mako')
645
645
646 pull_requests = PullRequestModel().get_im_participating_in(
646 pull_requests = PullRequestModel().get_im_participating_in(
647 user_id=self._rhodecode_user.user_id,
647 user_id=self._rhodecode_user.user_id,
648 statuses=statuses,
648 statuses=statuses,
649 offset=start, length=limit, order_by=order_by,
649 offset=start, length=limit, order_by=order_by,
650 order_dir=order_dir)
650 order_dir=order_dir)
651
651
652 pull_requests_total_count = PullRequestModel().count_im_participating_in(
652 pull_requests_total_count = PullRequestModel().count_im_participating_in(
653 user_id=self._rhodecode_user.user_id, statuses=statuses)
653 user_id=self._rhodecode_user.user_id, statuses=statuses)
654
654
655 data = []
655 data = []
656 comments_model = CommentsModel()
656 comments_model = CommentsModel()
657 for pr in pull_requests:
657 for pr in pull_requests:
658 repo_id = pr.target_repo_id
658 repo_id = pr.target_repo_id
659 comments = comments_model.get_all_comments(
659 comments = comments_model.get_all_comments(
660 repo_id, pull_request=pr)
660 repo_id, pull_request=pr)
661 owned = pr.user_id == self._rhodecode_user.user_id
661 owned = pr.user_id == self._rhodecode_user.user_id
662
662
663 data.append({
663 data.append({
664 'target_repo': _render('pullrequest_target_repo',
664 'target_repo': _render('pullrequest_target_repo',
665 pr.target_repo.repo_name),
665 pr.target_repo.repo_name),
666 'name': _render('pullrequest_name',
666 'name': _render('pullrequest_name',
667 pr.pull_request_id, pr.target_repo.repo_name,
667 pr.pull_request_id, pr.target_repo.repo_name,
668 short=True),
668 short=True),
669 'name_raw': pr.pull_request_id,
669 'name_raw': pr.pull_request_id,
670 'status': _render('pullrequest_status',
670 'status': _render('pullrequest_status',
671 pr.calculated_review_status()),
671 pr.calculated_review_status()),
672 'title': _render(
672 'title': _render(
673 'pullrequest_title', pr.title, pr.description),
673 'pullrequest_title', pr.title, pr.description),
674 'description': h.escape(pr.description),
674 'description': h.escape(pr.description),
675 'updated_on': _render('pullrequest_updated_on',
675 'updated_on': _render('pullrequest_updated_on',
676 h.datetime_to_time(pr.updated_on)),
676 h.datetime_to_time(pr.updated_on)),
677 'updated_on_raw': h.datetime_to_time(pr.updated_on),
677 'updated_on_raw': h.datetime_to_time(pr.updated_on),
678 'created_on': _render('pullrequest_updated_on',
678 'created_on': _render('pullrequest_updated_on',
679 h.datetime_to_time(pr.created_on)),
679 h.datetime_to_time(pr.created_on)),
680 'created_on_raw': h.datetime_to_time(pr.created_on),
680 'created_on_raw': h.datetime_to_time(pr.created_on),
681 'author': _render('pullrequest_author',
681 'author': _render('pullrequest_author',
682 pr.author.full_contact, ),
682 pr.author.full_contact, ),
683 'author_raw': pr.author.full_name,
683 'author_raw': pr.author.full_name,
684 'comments': _render('pullrequest_comments', len(comments)),
684 'comments': _render('pullrequest_comments', len(comments)),
685 'comments_raw': len(comments),
685 'comments_raw': len(comments),
686 'closed': pr.is_closed(),
686 'closed': pr.is_closed(),
687 'owned': owned
687 'owned': owned
688 })
688 })
689
689
690 # json used to render the grid
690 # json used to render the grid
691 data = ({
691 data = ({
692 'draw': draw,
692 'draw': draw,
693 'data': data,
693 'data': data,
694 'recordsTotal': pull_requests_total_count,
694 'recordsTotal': pull_requests_total_count,
695 'recordsFiltered': pull_requests_total_count,
695 'recordsFiltered': pull_requests_total_count,
696 })
696 })
697 return data
697 return data
698
698
699 @LoginRequired()
699 @LoginRequired()
700 @NotAnonymous()
700 @NotAnonymous()
701 @view_config(
701 @view_config(
702 route_name='my_account_pullrequests',
702 route_name='my_account_pullrequests',
703 request_method='GET',
703 request_method='GET',
704 renderer='rhodecode:templates/admin/my_account/my_account.mako')
704 renderer='rhodecode:templates/admin/my_account/my_account.mako')
705 def my_account_pullrequests(self):
705 def my_account_pullrequests(self):
706 c = self.load_default_context()
706 c = self.load_default_context()
707 c.active = 'pullrequests'
707 c.active = 'pullrequests'
708 req_get = self.request.GET
708 req_get = self.request.GET
709
709
710 c.closed = str2bool(req_get.get('pr_show_closed'))
710 c.closed = str2bool(req_get.get('pr_show_closed'))
711
711
712 return self._get_template_context(c)
712 return self._get_template_context(c)
713
713
714 @LoginRequired()
714 @LoginRequired()
715 @NotAnonymous()
715 @NotAnonymous()
716 @view_config(
716 @view_config(
717 route_name='my_account_pullrequests_data',
717 route_name='my_account_pullrequests_data',
718 request_method='GET', renderer='json_ext')
718 request_method='GET', renderer='json_ext')
719 def my_account_pullrequests_data(self):
719 def my_account_pullrequests_data(self):
720 self.load_default_context()
720 self.load_default_context()
721 req_get = self.request.GET
721 req_get = self.request.GET
722 closed = str2bool(req_get.get('closed'))
722 closed = str2bool(req_get.get('closed'))
723
723
724 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
724 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
725 if closed:
725 if closed:
726 statuses += [PullRequest.STATUS_CLOSED]
726 statuses += [PullRequest.STATUS_CLOSED]
727
727
728 data = self._get_pull_requests_list(statuses=statuses)
728 data = self._get_pull_requests_list(statuses=statuses)
729 return data
729 return data
730
730
731 @LoginRequired()
731 @LoginRequired()
732 @NotAnonymous()
732 @NotAnonymous()
733 @view_config(
733 @view_config(
734 route_name='my_account_user_group_membership',
734 route_name='my_account_user_group_membership',
735 request_method='GET',
735 request_method='GET',
736 renderer='rhodecode:templates/admin/my_account/my_account.mako')
736 renderer='rhodecode:templates/admin/my_account/my_account.mako')
737 def my_account_user_group_membership(self):
737 def my_account_user_group_membership(self):
738 c = self.load_default_context()
738 c = self.load_default_context()
739 c.active = 'user_group_membership'
739 c.active = 'user_group_membership'
740 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
740 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
741 for group in self._rhodecode_db_user.group_member]
741 for group in self._rhodecode_db_user.group_member]
742 c.user_groups = json.dumps(groups)
742 c.user_groups = json.dumps(groups)
743 return self._get_template_context(c)
743 return self._get_template_context(c)
@@ -1,392 +1,390 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 logging
21 import logging
22 import string
22 import string
23 import rhodecode
23 import rhodecode
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.lib.view_utils import get_format_ref_id
27 from rhodecode.lib.view_utils import get_format_ref_id
28 from rhodecode.apps._base import RepoAppView
28 from rhodecode.apps._base import RepoAppView
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 from rhodecode.lib import helpers as h, rc_cache
30 from rhodecode.lib import helpers as h, rc_cache
31 from rhodecode.lib.utils2 import safe_str, safe_int
31 from rhodecode.lib.utils2 import safe_str, safe_int
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
33 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.exceptions import (
36 from rhodecode.lib.vcs.exceptions import (
37 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
37 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 from rhodecode.model.db import Statistics, CacheKey, User
38 from rhodecode.model.db import Statistics, CacheKey, User
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.repo import ReadmeFinder
40 from rhodecode.model.repo import ReadmeFinder
41 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class RepoSummaryView(RepoAppView):
46 class RepoSummaryView(RepoAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c.rhodecode_repo = None
50 c.rhodecode_repo = None
51 if not c.repository_requirements_missing:
51 if not c.repository_requirements_missing:
52 c.rhodecode_repo = self.rhodecode_vcs_repo
52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 return c
53 return c
54
54
55 def _get_readme_data(self, db_repo, renderer_type):
55 def _get_readme_data(self, db_repo, renderer_type):
56
56
57 log.debug('Looking for README file')
57 log.debug('Looking for README file')
58
58
59 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
59 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
60 db_repo.repo_id, CacheKey.CACHE_TYPE_README)
60 db_repo.repo_id, CacheKey.CACHE_TYPE_README)
61 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
61 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
62 repo_id=self.db_repo.repo_id)
62 repo_id=self.db_repo.repo_id)
63 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
63 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
64
64
65 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
65 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
66 def generate_repo_readme(repo_id, _repo_name, _renderer_type):
66 def generate_repo_readme(repo_id, _repo_name, _renderer_type):
67 readme_data = None
67 readme_data = None
68 readme_node = None
68 readme_node = None
69 readme_filename = None
69 readme_filename = None
70 commit = self._get_landing_commit_or_none(db_repo)
70 commit = self._get_landing_commit_or_none(db_repo)
71 if commit:
71 if commit:
72 log.debug("Searching for a README file.")
72 log.debug("Searching for a README file.")
73 readme_node = ReadmeFinder(_renderer_type).search(commit)
73 readme_node = ReadmeFinder(_renderer_type).search(commit)
74 if readme_node:
74 if readme_node:
75 relative_urls = {
75 relative_urls = {
76 'raw': h.route_path(
76 'raw': h.route_path(
77 'repo_file_raw', repo_name=_repo_name,
77 'repo_file_raw', repo_name=_repo_name,
78 commit_id=commit.raw_id, f_path=readme_node.path),
78 commit_id=commit.raw_id, f_path=readme_node.path),
79 'standard': h.route_path(
79 'standard': h.route_path(
80 'repo_files', repo_name=_repo_name,
80 'repo_files', repo_name=_repo_name,
81 commit_id=commit.raw_id, f_path=readme_node.path),
81 commit_id=commit.raw_id, f_path=readme_node.path),
82 }
82 }
83 readme_data = self._render_readme_or_none(
83 readme_data = self._render_readme_or_none(
84 commit, readme_node, relative_urls)
84 commit, readme_node, relative_urls)
85 readme_filename = readme_node.path
85 readme_filename = readme_node.path
86 return readme_data, readme_filename
86 return readme_data, readme_filename
87
87
88 inv_context_manager = rc_cache.InvalidationContext(
88 inv_context_manager = rc_cache.InvalidationContext(
89 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
89 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
90 with inv_context_manager as invalidation_context:
90 with inv_context_manager as invalidation_context:
91 args = (db_repo.repo_id, db_repo.repo_name, renderer_type,)
91 args = (db_repo.repo_id, db_repo.repo_name, renderer_type,)
92 # re-compute and store cache if we get invalidate signal
92 # re-compute and store cache if we get invalidate signal
93 if invalidation_context.should_invalidate():
93 if invalidation_context.should_invalidate():
94 instance = generate_repo_readme.refresh(*args)
94 instance = generate_repo_readme.refresh(*args)
95 else:
95 else:
96 instance = generate_repo_readme(*args)
96 instance = generate_repo_readme(*args)
97
97
98 log.debug(
98 log.debug(
99 'Repo readme generated and computed in %.3fs',
99 'Repo readme generated and computed in %.3fs',
100 inv_context_manager.compute_time)
100 inv_context_manager.compute_time)
101 return instance
101 return instance
102
102
103 def _get_landing_commit_or_none(self, db_repo):
103 def _get_landing_commit_or_none(self, db_repo):
104 log.debug("Getting the landing commit.")
104 log.debug("Getting the landing commit.")
105 try:
105 try:
106 commit = db_repo.get_landing_commit()
106 commit = db_repo.get_landing_commit()
107 if not isinstance(commit, EmptyCommit):
107 if not isinstance(commit, EmptyCommit):
108 return commit
108 return commit
109 else:
109 else:
110 log.debug("Repository is empty, no README to render.")
110 log.debug("Repository is empty, no README to render.")
111 except CommitError:
111 except CommitError:
112 log.exception(
112 log.exception(
113 "Problem getting commit when trying to render the README.")
113 "Problem getting commit when trying to render the README.")
114
114
115 def _render_readme_or_none(self, commit, readme_node, relative_urls):
115 def _render_readme_or_none(self, commit, readme_node, relative_urls):
116 log.debug(
116 log.debug(
117 'Found README file `%s` rendering...', readme_node.path)
117 'Found README file `%s` rendering...', readme_node.path)
118 renderer = MarkupRenderer()
118 renderer = MarkupRenderer()
119 try:
119 try:
120 html_source = renderer.render(
120 html_source = renderer.render(
121 readme_node.content, filename=readme_node.path)
121 readme_node.content, filename=readme_node.path)
122 if relative_urls:
122 if relative_urls:
123 return relative_links(html_source, relative_urls)
123 return relative_links(html_source, relative_urls)
124 return html_source
124 return html_source
125 except Exception:
125 except Exception:
126 log.exception(
126 log.exception(
127 "Exception while trying to render the README")
127 "Exception while trying to render the README")
128
128
129 def _load_commits_context(self, c):
129 def _load_commits_context(self, c):
130 p = safe_int(self.request.GET.get('page'), 1)
130 p = safe_int(self.request.GET.get('page'), 1)
131 size = safe_int(self.request.GET.get('size'), 10)
131 size = safe_int(self.request.GET.get('size'), 10)
132
132
133 def url_generator(**kw):
133 def url_generator(**kw):
134 query_params = {
134 query_params = {
135 'size': size
135 'size': size
136 }
136 }
137 query_params.update(kw)
137 query_params.update(kw)
138 return h.route_path(
138 return h.route_path(
139 'repo_summary_commits',
139 'repo_summary_commits',
140 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
140 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
141
141
142 pre_load = ['author', 'branch', 'date', 'message']
142 pre_load = ['author', 'branch', 'date', 'message']
143 try:
143 try:
144 collection = self.rhodecode_vcs_repo.get_commits(
144 collection = self.rhodecode_vcs_repo.get_commits(
145 pre_load=pre_load, translate_tags=False)
145 pre_load=pre_load, translate_tags=False)
146 except EmptyRepositoryError:
146 except EmptyRepositoryError:
147 collection = self.rhodecode_vcs_repo
147 collection = self.rhodecode_vcs_repo
148
148
149 c.repo_commits = h.RepoPage(
149 c.repo_commits = h.RepoPage(
150 collection, page=p, items_per_page=size, url=url_generator)
150 collection, page=p, items_per_page=size, url=url_generator)
151 page_ids = [x.raw_id for x in c.repo_commits]
151 page_ids = [x.raw_id for x in c.repo_commits]
152 c.comments = self.db_repo.get_comments(page_ids)
152 c.comments = self.db_repo.get_comments(page_ids)
153 c.statuses = self.db_repo.statuses(page_ids)
153 c.statuses = self.db_repo.statuses(page_ids)
154
154
155 def _prepare_and_set_clone_url(self, c):
155 def _prepare_and_set_clone_url(self, c):
156 username = ''
156 username = ''
157 if self._rhodecode_user.username != User.DEFAULT_USER:
157 if self._rhodecode_user.username != User.DEFAULT_USER:
158 username = safe_str(self._rhodecode_user.username)
158 username = safe_str(self._rhodecode_user.username)
159
159
160 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
160 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
161 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
161 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
162
162
163 if '{repo}' in _def_clone_uri:
163 if '{repo}' in _def_clone_uri:
164 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
164 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
165 elif '{repoid}' in _def_clone_uri:
165 elif '{repoid}' in _def_clone_uri:
166 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
166 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
167
167
168 c.clone_repo_url = self.db_repo.clone_url(
168 c.clone_repo_url = self.db_repo.clone_url(
169 user=username, uri_tmpl=_def_clone_uri)
169 user=username, uri_tmpl=_def_clone_uri)
170 c.clone_repo_url_id = self.db_repo.clone_url(
170 c.clone_repo_url_id = self.db_repo.clone_url(
171 user=username, uri_tmpl=_def_clone_uri_id)
171 user=username, uri_tmpl=_def_clone_uri_id)
172 c.clone_repo_url_ssh = self.db_repo.clone_url(
172 c.clone_repo_url_ssh = self.db_repo.clone_url(
173 uri_tmpl=_def_clone_uri_ssh, ssh=True)
173 uri_tmpl=_def_clone_uri_ssh, ssh=True)
174
174
175 @LoginRequired()
175 @LoginRequired()
176 @HasRepoPermissionAnyDecorator(
176 @HasRepoPermissionAnyDecorator(
177 'repository.read', 'repository.write', 'repository.admin')
177 'repository.read', 'repository.write', 'repository.admin')
178 @view_config(
178 @view_config(
179 route_name='repo_summary_commits', request_method='GET',
179 route_name='repo_summary_commits', request_method='GET',
180 renderer='rhodecode:templates/summary/summary_commits.mako')
180 renderer='rhodecode:templates/summary/summary_commits.mako')
181 def summary_commits(self):
181 def summary_commits(self):
182 c = self.load_default_context()
182 c = self.load_default_context()
183 self._prepare_and_set_clone_url(c)
183 self._prepare_and_set_clone_url(c)
184 self._load_commits_context(c)
184 self._load_commits_context(c)
185 return self._get_template_context(c)
185 return self._get_template_context(c)
186
186
187 @LoginRequired()
187 @LoginRequired()
188 @HasRepoPermissionAnyDecorator(
188 @HasRepoPermissionAnyDecorator(
189 'repository.read', 'repository.write', 'repository.admin')
189 'repository.read', 'repository.write', 'repository.admin')
190 @view_config(
190 @view_config(
191 route_name='repo_summary', request_method='GET',
191 route_name='repo_summary', request_method='GET',
192 renderer='rhodecode:templates/summary/summary.mako')
192 renderer='rhodecode:templates/summary/summary.mako')
193 @view_config(
193 @view_config(
194 route_name='repo_summary_slash', request_method='GET',
194 route_name='repo_summary_slash', request_method='GET',
195 renderer='rhodecode:templates/summary/summary.mako')
195 renderer='rhodecode:templates/summary/summary.mako')
196 @view_config(
196 @view_config(
197 route_name='repo_summary_explicit', request_method='GET',
197 route_name='repo_summary_explicit', request_method='GET',
198 renderer='rhodecode:templates/summary/summary.mako')
198 renderer='rhodecode:templates/summary/summary.mako')
199 def summary(self):
199 def summary(self):
200 c = self.load_default_context()
200 c = self.load_default_context()
201
201
202 # Prepare the clone URL
202 # Prepare the clone URL
203 self._prepare_and_set_clone_url(c)
203 self._prepare_and_set_clone_url(c)
204
204
205 # If enabled, get statistics data
205 # If enabled, get statistics data
206
206
207 c.show_stats = bool(self.db_repo.enable_statistics)
207 c.show_stats = bool(self.db_repo.enable_statistics)
208
208
209 stats = Session().query(Statistics) \
209 stats = Session().query(Statistics) \
210 .filter(Statistics.repository == self.db_repo) \
210 .filter(Statistics.repository == self.db_repo) \
211 .scalar()
211 .scalar()
212
212
213 c.stats_percentage = 0
213 c.stats_percentage = 0
214
214
215 if stats and stats.languages:
215 if stats and stats.languages:
216 c.no_data = False is self.db_repo.enable_statistics
216 c.no_data = False is self.db_repo.enable_statistics
217 lang_stats_d = json.loads(stats.languages)
217 lang_stats_d = json.loads(stats.languages)
218
218
219 # Sort first by decreasing count and second by the file extension,
219 # Sort first by decreasing count and second by the file extension,
220 # so we have a consistent output.
220 # so we have a consistent output.
221 lang_stats_items = sorted(lang_stats_d.iteritems(),
221 lang_stats_items = sorted(lang_stats_d.iteritems(),
222 key=lambda k: (-k[1], k[0]))[:10]
222 key=lambda k: (-k[1], k[0]))[:10]
223 lang_stats = [(x, {"count": y,
223 lang_stats = [(x, {"count": y,
224 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
224 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
225 for x, y in lang_stats_items]
225 for x, y in lang_stats_items]
226
226
227 c.trending_languages = json.dumps(lang_stats)
227 c.trending_languages = json.dumps(lang_stats)
228 else:
228 else:
229 c.no_data = True
229 c.no_data = True
230 c.trending_languages = json.dumps({})
230 c.trending_languages = json.dumps({})
231
231
232 scm_model = ScmModel()
232 scm_model = ScmModel()
233 c.enable_downloads = self.db_repo.enable_downloads
233 c.enable_downloads = self.db_repo.enable_downloads
234 c.repository_followers = scm_model.get_followers(self.db_repo)
234 c.repository_followers = scm_model.get_followers(self.db_repo)
235 c.repository_forks = scm_model.get_forks(self.db_repo)
235 c.repository_forks = scm_model.get_forks(self.db_repo)
236 c.repository_is_user_following = scm_model.is_following_repo(
237 self.db_repo_name, self._rhodecode_user.user_id)
238
236
239 # first interaction with the VCS instance after here...
237 # first interaction with the VCS instance after here...
240 if c.repository_requirements_missing:
238 if c.repository_requirements_missing:
241 self.request.override_renderer = \
239 self.request.override_renderer = \
242 'rhodecode:templates/summary/missing_requirements.mako'
240 'rhodecode:templates/summary/missing_requirements.mako'
243 return self._get_template_context(c)
241 return self._get_template_context(c)
244
242
245 c.readme_data, c.readme_file = \
243 c.readme_data, c.readme_file = \
246 self._get_readme_data(self.db_repo, c.visual.default_renderer)
244 self._get_readme_data(self.db_repo, c.visual.default_renderer)
247
245
248 # loads the summary commits template context
246 # loads the summary commits template context
249 self._load_commits_context(c)
247 self._load_commits_context(c)
250
248
251 return self._get_template_context(c)
249 return self._get_template_context(c)
252
250
253 def get_request_commit_id(self):
251 def get_request_commit_id(self):
254 return self.request.matchdict['commit_id']
252 return self.request.matchdict['commit_id']
255
253
256 @LoginRequired()
254 @LoginRequired()
257 @HasRepoPermissionAnyDecorator(
255 @HasRepoPermissionAnyDecorator(
258 'repository.read', 'repository.write', 'repository.admin')
256 'repository.read', 'repository.write', 'repository.admin')
259 @view_config(
257 @view_config(
260 route_name='repo_stats', request_method='GET',
258 route_name='repo_stats', request_method='GET',
261 renderer='json_ext')
259 renderer='json_ext')
262 def repo_stats(self):
260 def repo_stats(self):
263 commit_id = self.get_request_commit_id()
261 commit_id = self.get_request_commit_id()
264 show_stats = bool(self.db_repo.enable_statistics)
262 show_stats = bool(self.db_repo.enable_statistics)
265 repo_id = self.db_repo.repo_id
263 repo_id = self.db_repo.repo_id
266
264
267 cache_seconds = safe_int(
265 cache_seconds = safe_int(
268 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
266 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
269 cache_on = cache_seconds > 0
267 cache_on = cache_seconds > 0
270 log.debug(
268 log.debug(
271 'Computing REPO TREE for repo_id %s commit_id `%s` '
269 'Computing REPO TREE for repo_id %s commit_id `%s` '
272 'with caching: %s[TTL: %ss]' % (
270 'with caching: %s[TTL: %ss]' % (
273 repo_id, commit_id, cache_on, cache_seconds or 0))
271 repo_id, commit_id, cache_on, cache_seconds or 0))
274
272
275 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
273 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
276 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
274 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
277
275
278 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
276 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
279 condition=cache_on)
277 condition=cache_on)
280 def compute_stats(repo_id, commit_id, show_stats):
278 def compute_stats(repo_id, commit_id, show_stats):
281 code_stats = {}
279 code_stats = {}
282 size = 0
280 size = 0
283 try:
281 try:
284 scm_instance = self.db_repo.scm_instance()
282 scm_instance = self.db_repo.scm_instance()
285 commit = scm_instance.get_commit(commit_id)
283 commit = scm_instance.get_commit(commit_id)
286
284
287 for node in commit.get_filenodes_generator():
285 for node in commit.get_filenodes_generator():
288 size += node.size
286 size += node.size
289 if not show_stats:
287 if not show_stats:
290 continue
288 continue
291 ext = string.lower(node.extension)
289 ext = string.lower(node.extension)
292 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
290 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
293 if ext_info:
291 if ext_info:
294 if ext in code_stats:
292 if ext in code_stats:
295 code_stats[ext]['count'] += 1
293 code_stats[ext]['count'] += 1
296 else:
294 else:
297 code_stats[ext] = {"count": 1, "desc": ext_info}
295 code_stats[ext] = {"count": 1, "desc": ext_info}
298 except (EmptyRepositoryError, CommitDoesNotExistError):
296 except (EmptyRepositoryError, CommitDoesNotExistError):
299 pass
297 pass
300 return {'size': h.format_byte_size_binary(size),
298 return {'size': h.format_byte_size_binary(size),
301 'code_stats': code_stats}
299 'code_stats': code_stats}
302
300
303 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
301 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
304 return stats
302 return stats
305
303
306 @LoginRequired()
304 @LoginRequired()
307 @HasRepoPermissionAnyDecorator(
305 @HasRepoPermissionAnyDecorator(
308 'repository.read', 'repository.write', 'repository.admin')
306 'repository.read', 'repository.write', 'repository.admin')
309 @view_config(
307 @view_config(
310 route_name='repo_refs_data', request_method='GET',
308 route_name='repo_refs_data', request_method='GET',
311 renderer='json_ext')
309 renderer='json_ext')
312 def repo_refs_data(self):
310 def repo_refs_data(self):
313 _ = self.request.translate
311 _ = self.request.translate
314 self.load_default_context()
312 self.load_default_context()
315
313
316 repo = self.rhodecode_vcs_repo
314 repo = self.rhodecode_vcs_repo
317 refs_to_create = [
315 refs_to_create = [
318 (_("Branch"), repo.branches, 'branch'),
316 (_("Branch"), repo.branches, 'branch'),
319 (_("Tag"), repo.tags, 'tag'),
317 (_("Tag"), repo.tags, 'tag'),
320 (_("Bookmark"), repo.bookmarks, 'book'),
318 (_("Bookmark"), repo.bookmarks, 'book'),
321 ]
319 ]
322 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
320 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
323 data = {
321 data = {
324 'more': False,
322 'more': False,
325 'results': res
323 'results': res
326 }
324 }
327 return data
325 return data
328
326
329 @LoginRequired()
327 @LoginRequired()
330 @HasRepoPermissionAnyDecorator(
328 @HasRepoPermissionAnyDecorator(
331 'repository.read', 'repository.write', 'repository.admin')
329 'repository.read', 'repository.write', 'repository.admin')
332 @view_config(
330 @view_config(
333 route_name='repo_refs_changelog_data', request_method='GET',
331 route_name='repo_refs_changelog_data', request_method='GET',
334 renderer='json_ext')
332 renderer='json_ext')
335 def repo_refs_changelog_data(self):
333 def repo_refs_changelog_data(self):
336 _ = self.request.translate
334 _ = self.request.translate
337 self.load_default_context()
335 self.load_default_context()
338
336
339 repo = self.rhodecode_vcs_repo
337 repo = self.rhodecode_vcs_repo
340
338
341 refs_to_create = [
339 refs_to_create = [
342 (_("Branches"), repo.branches, 'branch'),
340 (_("Branches"), repo.branches, 'branch'),
343 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
341 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
344 # TODO: enable when vcs can handle bookmarks filters
342 # TODO: enable when vcs can handle bookmarks filters
345 # (_("Bookmarks"), repo.bookmarks, "book"),
343 # (_("Bookmarks"), repo.bookmarks, "book"),
346 ]
344 ]
347 res = self._create_reference_data(
345 res = self._create_reference_data(
348 repo, self.db_repo_name, refs_to_create)
346 repo, self.db_repo_name, refs_to_create)
349 data = {
347 data = {
350 'more': False,
348 'more': False,
351 'results': res
349 'results': res
352 }
350 }
353 return data
351 return data
354
352
355 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
353 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
356 format_ref_id = get_format_ref_id(repo)
354 format_ref_id = get_format_ref_id(repo)
357
355
358 result = []
356 result = []
359 for title, refs, ref_type in refs_to_create:
357 for title, refs, ref_type in refs_to_create:
360 if refs:
358 if refs:
361 result.append({
359 result.append({
362 'text': title,
360 'text': title,
363 'children': self._create_reference_items(
361 'children': self._create_reference_items(
364 repo, full_repo_name, refs, ref_type,
362 repo, full_repo_name, refs, ref_type,
365 format_ref_id),
363 format_ref_id),
366 })
364 })
367 return result
365 return result
368
366
369 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
367 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
370 result = []
368 result = []
371 is_svn = h.is_svn(repo)
369 is_svn = h.is_svn(repo)
372 for ref_name, raw_id in refs.iteritems():
370 for ref_name, raw_id in refs.iteritems():
373 files_url = self._create_files_url(
371 files_url = self._create_files_url(
374 repo, full_repo_name, ref_name, raw_id, is_svn)
372 repo, full_repo_name, ref_name, raw_id, is_svn)
375 result.append({
373 result.append({
376 'text': ref_name,
374 'text': ref_name,
377 'id': format_ref_id(ref_name, raw_id),
375 'id': format_ref_id(ref_name, raw_id),
378 'raw_id': raw_id,
376 'raw_id': raw_id,
379 'type': ref_type,
377 'type': ref_type,
380 'files_url': files_url,
378 'files_url': files_url,
381 'idx': 0,
379 'idx': 0,
382 })
380 })
383 return result
381 return result
384
382
385 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
383 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
386 use_commit_id = '/' in ref_name or is_svn
384 use_commit_id = '/' in ref_name or is_svn
387 return h.route_path(
385 return h.route_path(
388 'repo_files',
386 'repo_files',
389 repo_name=full_repo_name,
387 repo_name=full_repo_name,
390 f_path=ref_name if is_svn else '',
388 f_path=ref_name if is_svn else '',
391 commit_id=raw_id if use_commit_id else ref_name,
389 commit_id=raw_id if use_commit_id else ref_name,
392 _query=dict(at=ref_name))
390 _query=dict(at=ref_name))
@@ -1,1069 +1,1073 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import re
22 import re
23 import shutil
23 import shutil
24 import time
24 import time
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28
28
29 from pyramid.threadlocal import get_current_request
29 from pyramid.threadlocal import get_current_request
30 from zope.cachedescriptors.property import Lazy as LazyProperty
30 from zope.cachedescriptors.property import Lazy as LazyProperty
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 from rhodecode.lib.caching_query import FromCache
34 from rhodecode.lib.caching_query import FromCache
35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
36 from rhodecode.lib.hooks_base import log_delete_repository
36 from rhodecode.lib.hooks_base import log_delete_repository
37 from rhodecode.lib.user_log_filter import user_log_filter
37 from rhodecode.lib.user_log_filter import user_log_filter
38 from rhodecode.lib.utils import make_db_config
38 from rhodecode.lib.utils import make_db_config
39 from rhodecode.lib.utils2 import (
39 from rhodecode.lib.utils2 import (
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 get_current_rhodecode_user, safe_int, datetime_to_time,
41 get_current_rhodecode_user, safe_int, datetime_to_time,
42 action_logger_generic)
42 action_logger_generic)
43 from rhodecode.lib.vcs.backends import get_backend
43 from rhodecode.lib.vcs.backends import get_backend
44 from rhodecode.model import BaseModel
44 from rhodecode.model import BaseModel
45 from rhodecode.model.db import (
45 from rhodecode.model.db import (
46 _hash_key, joinedload, or_, Repository, UserRepoToPerm, UserGroupRepoToPerm,
46 _hash_key, joinedload, or_, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
49
49
50 from rhodecode.model.settings import VcsSettingsModel
50 from rhodecode.model.settings import VcsSettingsModel
51
51
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class RepoModel(BaseModel):
56 class RepoModel(BaseModel):
57
57
58 cls = Repository
58 cls = Repository
59
59
60 def _get_user_group(self, users_group):
60 def _get_user_group(self, users_group):
61 return self._get_instance(UserGroup, users_group,
61 return self._get_instance(UserGroup, users_group,
62 callback=UserGroup.get_by_group_name)
62 callback=UserGroup.get_by_group_name)
63
63
64 def _get_repo_group(self, repo_group):
64 def _get_repo_group(self, repo_group):
65 return self._get_instance(RepoGroup, repo_group,
65 return self._get_instance(RepoGroup, repo_group,
66 callback=RepoGroup.get_by_group_name)
66 callback=RepoGroup.get_by_group_name)
67
67
68 def _create_default_perms(self, repository, private):
68 def _create_default_perms(self, repository, private):
69 # create default permission
69 # create default permission
70 default = 'repository.read'
70 default = 'repository.read'
71 def_user = User.get_default_user()
71 def_user = User.get_default_user()
72 for p in def_user.user_perms:
72 for p in def_user.user_perms:
73 if p.permission.permission_name.startswith('repository.'):
73 if p.permission.permission_name.startswith('repository.'):
74 default = p.permission.permission_name
74 default = p.permission.permission_name
75 break
75 break
76
76
77 default_perm = 'repository.none' if private else default
77 default_perm = 'repository.none' if private else default
78
78
79 repo_to_perm = UserRepoToPerm()
79 repo_to_perm = UserRepoToPerm()
80 repo_to_perm.permission = Permission.get_by_key(default_perm)
80 repo_to_perm.permission = Permission.get_by_key(default_perm)
81
81
82 repo_to_perm.repository = repository
82 repo_to_perm.repository = repository
83 repo_to_perm.user_id = def_user.user_id
83 repo_to_perm.user_id = def_user.user_id
84
84
85 return repo_to_perm
85 return repo_to_perm
86
86
87 @LazyProperty
87 @LazyProperty
88 def repos_path(self):
88 def repos_path(self):
89 """
89 """
90 Gets the repositories root path from database
90 Gets the repositories root path from database
91 """
91 """
92 settings_model = VcsSettingsModel(sa=self.sa)
92 settings_model = VcsSettingsModel(sa=self.sa)
93 return settings_model.get_repos_location()
93 return settings_model.get_repos_location()
94
94
95 def get(self, repo_id):
95 def get(self, repo_id):
96 repo = self.sa.query(Repository) \
96 repo = self.sa.query(Repository) \
97 .filter(Repository.repo_id == repo_id)
97 .filter(Repository.repo_id == repo_id)
98
98
99 return repo.scalar()
99 return repo.scalar()
100
100
101 def get_repo(self, repository):
101 def get_repo(self, repository):
102 return self._get_repo(repository)
102 return self._get_repo(repository)
103
103
104 def get_by_repo_name(self, repo_name, cache=False):
104 def get_by_repo_name(self, repo_name, cache=False):
105 repo = self.sa.query(Repository) \
105 repo = self.sa.query(Repository) \
106 .filter(Repository.repo_name == repo_name)
106 .filter(Repository.repo_name == repo_name)
107
107
108 if cache:
108 if cache:
109 name_key = _hash_key(repo_name)
109 name_key = _hash_key(repo_name)
110 repo = repo.options(
110 repo = repo.options(
111 FromCache("sql_cache_short", "get_repo_%s" % name_key))
111 FromCache("sql_cache_short", "get_repo_%s" % name_key))
112 return repo.scalar()
112 return repo.scalar()
113
113
114 def _extract_id_from_repo_name(self, repo_name):
114 def _extract_id_from_repo_name(self, repo_name):
115 if repo_name.startswith('/'):
115 if repo_name.startswith('/'):
116 repo_name = repo_name.lstrip('/')
116 repo_name = repo_name.lstrip('/')
117 by_id_match = re.match(r'^_(\d{1,})', repo_name)
117 by_id_match = re.match(r'^_(\d{1,})', repo_name)
118 if by_id_match:
118 if by_id_match:
119 return by_id_match.groups()[0]
119 return by_id_match.groups()[0]
120
120
121 def get_repo_by_id(self, repo_name):
121 def get_repo_by_id(self, repo_name):
122 """
122 """
123 Extracts repo_name by id from special urls.
123 Extracts repo_name by id from special urls.
124 Example url is _11/repo_name
124 Example url is _11/repo_name
125
125
126 :param repo_name:
126 :param repo_name:
127 :return: repo object if matched else None
127 :return: repo object if matched else None
128 """
128 """
129
129
130 try:
130 try:
131 _repo_id = self._extract_id_from_repo_name(repo_name)
131 _repo_id = self._extract_id_from_repo_name(repo_name)
132 if _repo_id:
132 if _repo_id:
133 return self.get(_repo_id)
133 return self.get(_repo_id)
134 except Exception:
134 except Exception:
135 log.exception('Failed to extract repo_name from URL')
135 log.exception('Failed to extract repo_name from URL')
136
136
137 return None
137 return None
138
138
139 def get_repos_for_root(self, root, traverse=False):
139 def get_repos_for_root(self, root, traverse=False):
140 if traverse:
140 if traverse:
141 like_expression = u'{}%'.format(safe_unicode(root))
141 like_expression = u'{}%'.format(safe_unicode(root))
142 repos = Repository.query().filter(
142 repos = Repository.query().filter(
143 Repository.repo_name.like(like_expression)).all()
143 Repository.repo_name.like(like_expression)).all()
144 else:
144 else:
145 if root and not isinstance(root, RepoGroup):
145 if root and not isinstance(root, RepoGroup):
146 raise ValueError(
146 raise ValueError(
147 'Root must be an instance '
147 'Root must be an instance '
148 'of RepoGroup, got:{} instead'.format(type(root)))
148 'of RepoGroup, got:{} instead'.format(type(root)))
149 repos = Repository.query().filter(Repository.group == root).all()
149 repos = Repository.query().filter(Repository.group == root).all()
150 return repos
150 return repos
151
151
152 def get_url(self, repo, request=None, permalink=False):
152 def get_url(self, repo, request=None, permalink=False):
153 if not request:
153 if not request:
154 request = get_current_request()
154 request = get_current_request()
155
155
156 if not request:
156 if not request:
157 return
157 return
158
158
159 if permalink:
159 if permalink:
160 return request.route_url(
160 return request.route_url(
161 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
161 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
162 else:
162 else:
163 return request.route_url(
163 return request.route_url(
164 'repo_summary', repo_name=safe_str(repo.repo_name))
164 'repo_summary', repo_name=safe_str(repo.repo_name))
165
165
166 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
166 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
167 if not request:
167 if not request:
168 request = get_current_request()
168 request = get_current_request()
169
169
170 if not request:
170 if not request:
171 return
171 return
172
172
173 if permalink:
173 if permalink:
174 return request.route_url(
174 return request.route_url(
175 'repo_commit', repo_name=safe_str(repo.repo_id),
175 'repo_commit', repo_name=safe_str(repo.repo_id),
176 commit_id=commit_id)
176 commit_id=commit_id)
177
177
178 else:
178 else:
179 return request.route_url(
179 return request.route_url(
180 'repo_commit', repo_name=safe_str(repo.repo_name),
180 'repo_commit', repo_name=safe_str(repo.repo_name),
181 commit_id=commit_id)
181 commit_id=commit_id)
182
182
183 def get_repo_log(self, repo, filter_term):
183 def get_repo_log(self, repo, filter_term):
184 repo_log = UserLog.query()\
184 repo_log = UserLog.query()\
185 .filter(or_(UserLog.repository_id == repo.repo_id,
185 .filter(or_(UserLog.repository_id == repo.repo_id,
186 UserLog.repository_name == repo.repo_name))\
186 UserLog.repository_name == repo.repo_name))\
187 .options(joinedload(UserLog.user))\
187 .options(joinedload(UserLog.user))\
188 .options(joinedload(UserLog.repository))\
188 .options(joinedload(UserLog.repository))\
189 .order_by(UserLog.action_date.desc())
189 .order_by(UserLog.action_date.desc())
190
190
191 repo_log = user_log_filter(repo_log, filter_term)
191 repo_log = user_log_filter(repo_log, filter_term)
192 return repo_log
192 return repo_log
193
193
194 @classmethod
194 @classmethod
195 def update_repoinfo(cls, repositories=None):
195 def update_repoinfo(cls, repositories=None):
196 if not repositories:
196 if not repositories:
197 repositories = Repository.getAll()
197 repositories = Repository.getAll()
198 for repo in repositories:
198 for repo in repositories:
199 repo.update_commit_cache()
199 repo.update_commit_cache()
200
200
201 def get_repos_as_dict(self, repo_list=None, admin=False,
201 def get_repos_as_dict(self, repo_list=None, admin=False,
202 super_user_actions=False):
202 super_user_actions=False, short_name=None):
203 _render = get_current_request().get_partial_renderer(
203 _render = get_current_request().get_partial_renderer(
204 'rhodecode:templates/data_table/_dt_elements.mako')
204 'rhodecode:templates/data_table/_dt_elements.mako')
205 c = _render.get_call_context()
205 c = _render.get_call_context()
206
206
207 def quick_menu(repo_name):
207 def quick_menu(repo_name):
208 return _render('quick_menu', repo_name)
208 return _render('quick_menu', repo_name)
209
209
210 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
210 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
211 if short_name is not None:
212 short_name_var = short_name
213 else:
214 short_name_var = not admin
211 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
215 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
212 short_name=not admin, admin=False)
216 short_name=short_name_var, admin=False)
213
217
214 def last_change(last_change):
218 def last_change(last_change):
215 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
219 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
216 last_change = last_change + datetime.timedelta(seconds=
220 last_change = last_change + datetime.timedelta(seconds=
217 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
221 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
218 return _render("last_change", last_change)
222 return _render("last_change", last_change)
219
223
220 def rss_lnk(repo_name):
224 def rss_lnk(repo_name):
221 return _render("rss", repo_name)
225 return _render("rss", repo_name)
222
226
223 def atom_lnk(repo_name):
227 def atom_lnk(repo_name):
224 return _render("atom", repo_name)
228 return _render("atom", repo_name)
225
229
226 def last_rev(repo_name, cs_cache):
230 def last_rev(repo_name, cs_cache):
227 return _render('revision', repo_name, cs_cache.get('revision'),
231 return _render('revision', repo_name, cs_cache.get('revision'),
228 cs_cache.get('raw_id'), cs_cache.get('author'),
232 cs_cache.get('raw_id'), cs_cache.get('author'),
229 cs_cache.get('message'), cs_cache.get('date'))
233 cs_cache.get('message'), cs_cache.get('date'))
230
234
231 def desc(desc):
235 def desc(desc):
232 return _render('repo_desc', desc, c.visual.stylify_metatags)
236 return _render('repo_desc', desc, c.visual.stylify_metatags)
233
237
234 def state(repo_state):
238 def state(repo_state):
235 return _render("repo_state", repo_state)
239 return _render("repo_state", repo_state)
236
240
237 def repo_actions(repo_name):
241 def repo_actions(repo_name):
238 return _render('repo_actions', repo_name, super_user_actions)
242 return _render('repo_actions', repo_name, super_user_actions)
239
243
240 def user_profile(username):
244 def user_profile(username):
241 return _render('user_profile', username)
245 return _render('user_profile', username)
242
246
243 repos_data = []
247 repos_data = []
244 for repo in repo_list:
248 for repo in repo_list:
245 cs_cache = repo.changeset_cache
249 cs_cache = repo.changeset_cache
246 row = {
250 row = {
247 "menu": quick_menu(repo.repo_name),
251 "menu": quick_menu(repo.repo_name),
248
252
249 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
253 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
250 repo.private, repo.archived, repo.fork),
254 repo.private, repo.archived, repo.fork),
251 "name_raw": repo.repo_name.lower(),
255 "name_raw": repo.repo_name.lower(),
252
256
253 "last_change": last_change(repo.last_db_change),
257 "last_change": last_change(repo.last_db_change),
254 "last_change_raw": datetime_to_time(repo.last_db_change),
258 "last_change_raw": datetime_to_time(repo.last_db_change),
255
259
256 "last_changeset": last_rev(repo.repo_name, cs_cache),
260 "last_changeset": last_rev(repo.repo_name, cs_cache),
257 "last_changeset_raw": cs_cache.get('revision'),
261 "last_changeset_raw": cs_cache.get('revision'),
258
262
259 "desc": desc(repo.description_safe),
263 "desc": desc(repo.description_safe),
260 "owner": user_profile(repo.user.username),
264 "owner": user_profile(repo.user.username),
261
265
262 "state": state(repo.repo_state),
266 "state": state(repo.repo_state),
263 "rss": rss_lnk(repo.repo_name),
267 "rss": rss_lnk(repo.repo_name),
264
268
265 "atom": atom_lnk(repo.repo_name),
269 "atom": atom_lnk(repo.repo_name),
266 }
270 }
267 if admin:
271 if admin:
268 row.update({
272 row.update({
269 "action": repo_actions(repo.repo_name),
273 "action": repo_actions(repo.repo_name),
270 })
274 })
271 repos_data.append(row)
275 repos_data.append(row)
272
276
273 return repos_data
277 return repos_data
274
278
275 def _get_defaults(self, repo_name):
279 def _get_defaults(self, repo_name):
276 """
280 """
277 Gets information about repository, and returns a dict for
281 Gets information about repository, and returns a dict for
278 usage in forms
282 usage in forms
279
283
280 :param repo_name:
284 :param repo_name:
281 """
285 """
282
286
283 repo_info = Repository.get_by_repo_name(repo_name)
287 repo_info = Repository.get_by_repo_name(repo_name)
284
288
285 if repo_info is None:
289 if repo_info is None:
286 return None
290 return None
287
291
288 defaults = repo_info.get_dict()
292 defaults = repo_info.get_dict()
289 defaults['repo_name'] = repo_info.just_name
293 defaults['repo_name'] = repo_info.just_name
290
294
291 groups = repo_info.groups_with_parents
295 groups = repo_info.groups_with_parents
292 parent_group = groups[-1] if groups else None
296 parent_group = groups[-1] if groups else None
293
297
294 # we use -1 as this is how in HTML, we mark an empty group
298 # we use -1 as this is how in HTML, we mark an empty group
295 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
299 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
296
300
297 keys_to_process = (
301 keys_to_process = (
298 {'k': 'repo_type', 'strip': False},
302 {'k': 'repo_type', 'strip': False},
299 {'k': 'repo_enable_downloads', 'strip': True},
303 {'k': 'repo_enable_downloads', 'strip': True},
300 {'k': 'repo_description', 'strip': True},
304 {'k': 'repo_description', 'strip': True},
301 {'k': 'repo_enable_locking', 'strip': True},
305 {'k': 'repo_enable_locking', 'strip': True},
302 {'k': 'repo_landing_rev', 'strip': True},
306 {'k': 'repo_landing_rev', 'strip': True},
303 {'k': 'clone_uri', 'strip': False},
307 {'k': 'clone_uri', 'strip': False},
304 {'k': 'push_uri', 'strip': False},
308 {'k': 'push_uri', 'strip': False},
305 {'k': 'repo_private', 'strip': True},
309 {'k': 'repo_private', 'strip': True},
306 {'k': 'repo_enable_statistics', 'strip': True}
310 {'k': 'repo_enable_statistics', 'strip': True}
307 )
311 )
308
312
309 for item in keys_to_process:
313 for item in keys_to_process:
310 attr = item['k']
314 attr = item['k']
311 if item['strip']:
315 if item['strip']:
312 attr = remove_prefix(item['k'], 'repo_')
316 attr = remove_prefix(item['k'], 'repo_')
313
317
314 val = defaults[attr]
318 val = defaults[attr]
315 if item['k'] == 'repo_landing_rev':
319 if item['k'] == 'repo_landing_rev':
316 val = ':'.join(defaults[attr])
320 val = ':'.join(defaults[attr])
317 defaults[item['k']] = val
321 defaults[item['k']] = val
318 if item['k'] == 'clone_uri':
322 if item['k'] == 'clone_uri':
319 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
323 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
320 if item['k'] == 'push_uri':
324 if item['k'] == 'push_uri':
321 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
325 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
322
326
323 # fill owner
327 # fill owner
324 if repo_info.user:
328 if repo_info.user:
325 defaults.update({'user': repo_info.user.username})
329 defaults.update({'user': repo_info.user.username})
326 else:
330 else:
327 replacement_user = User.get_first_super_admin().username
331 replacement_user = User.get_first_super_admin().username
328 defaults.update({'user': replacement_user})
332 defaults.update({'user': replacement_user})
329
333
330 return defaults
334 return defaults
331
335
332 def update(self, repo, **kwargs):
336 def update(self, repo, **kwargs):
333 try:
337 try:
334 cur_repo = self._get_repo(repo)
338 cur_repo = self._get_repo(repo)
335 source_repo_name = cur_repo.repo_name
339 source_repo_name = cur_repo.repo_name
336 if 'user' in kwargs:
340 if 'user' in kwargs:
337 cur_repo.user = User.get_by_username(kwargs['user'])
341 cur_repo.user = User.get_by_username(kwargs['user'])
338
342
339 if 'repo_group' in kwargs:
343 if 'repo_group' in kwargs:
340 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
344 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
341 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
345 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
342
346
343 update_keys = [
347 update_keys = [
344 (1, 'repo_description'),
348 (1, 'repo_description'),
345 (1, 'repo_landing_rev'),
349 (1, 'repo_landing_rev'),
346 (1, 'repo_private'),
350 (1, 'repo_private'),
347 (1, 'repo_enable_downloads'),
351 (1, 'repo_enable_downloads'),
348 (1, 'repo_enable_locking'),
352 (1, 'repo_enable_locking'),
349 (1, 'repo_enable_statistics'),
353 (1, 'repo_enable_statistics'),
350 (0, 'clone_uri'),
354 (0, 'clone_uri'),
351 (0, 'push_uri'),
355 (0, 'push_uri'),
352 (0, 'fork_id')
356 (0, 'fork_id')
353 ]
357 ]
354 for strip, k in update_keys:
358 for strip, k in update_keys:
355 if k in kwargs:
359 if k in kwargs:
356 val = kwargs[k]
360 val = kwargs[k]
357 if strip:
361 if strip:
358 k = remove_prefix(k, 'repo_')
362 k = remove_prefix(k, 'repo_')
359
363
360 setattr(cur_repo, k, val)
364 setattr(cur_repo, k, val)
361
365
362 new_name = cur_repo.get_new_name(kwargs['repo_name'])
366 new_name = cur_repo.get_new_name(kwargs['repo_name'])
363 cur_repo.repo_name = new_name
367 cur_repo.repo_name = new_name
364
368
365 # if private flag is set, reset default permission to NONE
369 # if private flag is set, reset default permission to NONE
366 if kwargs.get('repo_private'):
370 if kwargs.get('repo_private'):
367 EMPTY_PERM = 'repository.none'
371 EMPTY_PERM = 'repository.none'
368 RepoModel().grant_user_permission(
372 RepoModel().grant_user_permission(
369 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
373 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
370 )
374 )
371
375
372 # handle extra fields
376 # handle extra fields
373 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
377 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
374 kwargs):
378 kwargs):
375 k = RepositoryField.un_prefix_key(field)
379 k = RepositoryField.un_prefix_key(field)
376 ex_field = RepositoryField.get_by_key_name(
380 ex_field = RepositoryField.get_by_key_name(
377 key=k, repo=cur_repo)
381 key=k, repo=cur_repo)
378 if ex_field:
382 if ex_field:
379 ex_field.field_value = kwargs[field]
383 ex_field.field_value = kwargs[field]
380 self.sa.add(ex_field)
384 self.sa.add(ex_field)
381 cur_repo.updated_on = datetime.datetime.now()
385 cur_repo.updated_on = datetime.datetime.now()
382 self.sa.add(cur_repo)
386 self.sa.add(cur_repo)
383
387
384 if source_repo_name != new_name:
388 if source_repo_name != new_name:
385 # rename repository
389 # rename repository
386 self._rename_filesystem_repo(
390 self._rename_filesystem_repo(
387 old=source_repo_name, new=new_name)
391 old=source_repo_name, new=new_name)
388
392
389 return cur_repo
393 return cur_repo
390 except Exception:
394 except Exception:
391 log.error(traceback.format_exc())
395 log.error(traceback.format_exc())
392 raise
396 raise
393
397
394 def _create_repo(self, repo_name, repo_type, description, owner,
398 def _create_repo(self, repo_name, repo_type, description, owner,
395 private=False, clone_uri=None, repo_group=None,
399 private=False, clone_uri=None, repo_group=None,
396 landing_rev='rev:tip', fork_of=None,
400 landing_rev='rev:tip', fork_of=None,
397 copy_fork_permissions=False, enable_statistics=False,
401 copy_fork_permissions=False, enable_statistics=False,
398 enable_locking=False, enable_downloads=False,
402 enable_locking=False, enable_downloads=False,
399 copy_group_permissions=False,
403 copy_group_permissions=False,
400 state=Repository.STATE_PENDING):
404 state=Repository.STATE_PENDING):
401 """
405 """
402 Create repository inside database with PENDING state, this should be
406 Create repository inside database with PENDING state, this should be
403 only executed by create() repo. With exception of importing existing
407 only executed by create() repo. With exception of importing existing
404 repos
408 repos
405 """
409 """
406 from rhodecode.model.scm import ScmModel
410 from rhodecode.model.scm import ScmModel
407
411
408 owner = self._get_user(owner)
412 owner = self._get_user(owner)
409 fork_of = self._get_repo(fork_of)
413 fork_of = self._get_repo(fork_of)
410 repo_group = self._get_repo_group(safe_int(repo_group))
414 repo_group = self._get_repo_group(safe_int(repo_group))
411
415
412 try:
416 try:
413 repo_name = safe_unicode(repo_name)
417 repo_name = safe_unicode(repo_name)
414 description = safe_unicode(description)
418 description = safe_unicode(description)
415 # repo name is just a name of repository
419 # repo name is just a name of repository
416 # while repo_name_full is a full qualified name that is combined
420 # while repo_name_full is a full qualified name that is combined
417 # with name and path of group
421 # with name and path of group
418 repo_name_full = repo_name
422 repo_name_full = repo_name
419 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
423 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
420
424
421 new_repo = Repository()
425 new_repo = Repository()
422 new_repo.repo_state = state
426 new_repo.repo_state = state
423 new_repo.enable_statistics = False
427 new_repo.enable_statistics = False
424 new_repo.repo_name = repo_name_full
428 new_repo.repo_name = repo_name_full
425 new_repo.repo_type = repo_type
429 new_repo.repo_type = repo_type
426 new_repo.user = owner
430 new_repo.user = owner
427 new_repo.group = repo_group
431 new_repo.group = repo_group
428 new_repo.description = description or repo_name
432 new_repo.description = description or repo_name
429 new_repo.private = private
433 new_repo.private = private
430 new_repo.archived = False
434 new_repo.archived = False
431 new_repo.clone_uri = clone_uri
435 new_repo.clone_uri = clone_uri
432 new_repo.landing_rev = landing_rev
436 new_repo.landing_rev = landing_rev
433
437
434 new_repo.enable_statistics = enable_statistics
438 new_repo.enable_statistics = enable_statistics
435 new_repo.enable_locking = enable_locking
439 new_repo.enable_locking = enable_locking
436 new_repo.enable_downloads = enable_downloads
440 new_repo.enable_downloads = enable_downloads
437
441
438 if repo_group:
442 if repo_group:
439 new_repo.enable_locking = repo_group.enable_locking
443 new_repo.enable_locking = repo_group.enable_locking
440
444
441 if fork_of:
445 if fork_of:
442 parent_repo = fork_of
446 parent_repo = fork_of
443 new_repo.fork = parent_repo
447 new_repo.fork = parent_repo
444
448
445 events.trigger(events.RepoPreCreateEvent(new_repo))
449 events.trigger(events.RepoPreCreateEvent(new_repo))
446
450
447 self.sa.add(new_repo)
451 self.sa.add(new_repo)
448
452
449 EMPTY_PERM = 'repository.none'
453 EMPTY_PERM = 'repository.none'
450 if fork_of and copy_fork_permissions:
454 if fork_of and copy_fork_permissions:
451 repo = fork_of
455 repo = fork_of
452 user_perms = UserRepoToPerm.query() \
456 user_perms = UserRepoToPerm.query() \
453 .filter(UserRepoToPerm.repository == repo).all()
457 .filter(UserRepoToPerm.repository == repo).all()
454 group_perms = UserGroupRepoToPerm.query() \
458 group_perms = UserGroupRepoToPerm.query() \
455 .filter(UserGroupRepoToPerm.repository == repo).all()
459 .filter(UserGroupRepoToPerm.repository == repo).all()
456
460
457 for perm in user_perms:
461 for perm in user_perms:
458 UserRepoToPerm.create(
462 UserRepoToPerm.create(
459 perm.user, new_repo, perm.permission)
463 perm.user, new_repo, perm.permission)
460
464
461 for perm in group_perms:
465 for perm in group_perms:
462 UserGroupRepoToPerm.create(
466 UserGroupRepoToPerm.create(
463 perm.users_group, new_repo, perm.permission)
467 perm.users_group, new_repo, perm.permission)
464 # in case we copy permissions and also set this repo to private
468 # in case we copy permissions and also set this repo to private
465 # override the default user permission to make it a private repo
469 # override the default user permission to make it a private repo
466 if private:
470 if private:
467 RepoModel(self.sa).grant_user_permission(
471 RepoModel(self.sa).grant_user_permission(
468 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
472 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
469
473
470 elif repo_group and copy_group_permissions:
474 elif repo_group and copy_group_permissions:
471 user_perms = UserRepoGroupToPerm.query() \
475 user_perms = UserRepoGroupToPerm.query() \
472 .filter(UserRepoGroupToPerm.group == repo_group).all()
476 .filter(UserRepoGroupToPerm.group == repo_group).all()
473
477
474 group_perms = UserGroupRepoGroupToPerm.query() \
478 group_perms = UserGroupRepoGroupToPerm.query() \
475 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
479 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
476
480
477 for perm in user_perms:
481 for perm in user_perms:
478 perm_name = perm.permission.permission_name.replace(
482 perm_name = perm.permission.permission_name.replace(
479 'group.', 'repository.')
483 'group.', 'repository.')
480 perm_obj = Permission.get_by_key(perm_name)
484 perm_obj = Permission.get_by_key(perm_name)
481 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
485 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
482
486
483 for perm in group_perms:
487 for perm in group_perms:
484 perm_name = perm.permission.permission_name.replace(
488 perm_name = perm.permission.permission_name.replace(
485 'group.', 'repository.')
489 'group.', 'repository.')
486 perm_obj = Permission.get_by_key(perm_name)
490 perm_obj = Permission.get_by_key(perm_name)
487 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
491 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
488
492
489 if private:
493 if private:
490 RepoModel(self.sa).grant_user_permission(
494 RepoModel(self.sa).grant_user_permission(
491 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
495 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
492
496
493 else:
497 else:
494 perm_obj = self._create_default_perms(new_repo, private)
498 perm_obj = self._create_default_perms(new_repo, private)
495 self.sa.add(perm_obj)
499 self.sa.add(perm_obj)
496
500
497 # now automatically start following this repository as owner
501 # now automatically start following this repository as owner
498 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
502 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
499
503
500 # we need to flush here, in order to check if database won't
504 # we need to flush here, in order to check if database won't
501 # throw any exceptions, create filesystem dirs at the very end
505 # throw any exceptions, create filesystem dirs at the very end
502 self.sa.flush()
506 self.sa.flush()
503 events.trigger(events.RepoCreateEvent(new_repo))
507 events.trigger(events.RepoCreateEvent(new_repo))
504 return new_repo
508 return new_repo
505
509
506 except Exception:
510 except Exception:
507 log.error(traceback.format_exc())
511 log.error(traceback.format_exc())
508 raise
512 raise
509
513
510 def create(self, form_data, cur_user):
514 def create(self, form_data, cur_user):
511 """
515 """
512 Create repository using celery tasks
516 Create repository using celery tasks
513
517
514 :param form_data:
518 :param form_data:
515 :param cur_user:
519 :param cur_user:
516 """
520 """
517 from rhodecode.lib.celerylib import tasks, run_task
521 from rhodecode.lib.celerylib import tasks, run_task
518 return run_task(tasks.create_repo, form_data, cur_user)
522 return run_task(tasks.create_repo, form_data, cur_user)
519
523
520 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
524 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
521 perm_deletions=None, check_perms=True,
525 perm_deletions=None, check_perms=True,
522 cur_user=None):
526 cur_user=None):
523 if not perm_additions:
527 if not perm_additions:
524 perm_additions = []
528 perm_additions = []
525 if not perm_updates:
529 if not perm_updates:
526 perm_updates = []
530 perm_updates = []
527 if not perm_deletions:
531 if not perm_deletions:
528 perm_deletions = []
532 perm_deletions = []
529
533
530 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
534 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
531
535
532 changes = {
536 changes = {
533 'added': [],
537 'added': [],
534 'updated': [],
538 'updated': [],
535 'deleted': []
539 'deleted': []
536 }
540 }
537 # update permissions
541 # update permissions
538 for member_id, perm, member_type in perm_updates:
542 for member_id, perm, member_type in perm_updates:
539 member_id = int(member_id)
543 member_id = int(member_id)
540 if member_type == 'user':
544 if member_type == 'user':
541 member_name = User.get(member_id).username
545 member_name = User.get(member_id).username
542 # this updates also current one if found
546 # this updates also current one if found
543 self.grant_user_permission(
547 self.grant_user_permission(
544 repo=repo, user=member_id, perm=perm)
548 repo=repo, user=member_id, perm=perm)
545 elif member_type == 'user_group':
549 elif member_type == 'user_group':
546 # check if we have permissions to alter this usergroup
550 # check if we have permissions to alter this usergroup
547 member_name = UserGroup.get(member_id).users_group_name
551 member_name = UserGroup.get(member_id).users_group_name
548 if not check_perms or HasUserGroupPermissionAny(
552 if not check_perms or HasUserGroupPermissionAny(
549 *req_perms)(member_name, user=cur_user):
553 *req_perms)(member_name, user=cur_user):
550 self.grant_user_group_permission(
554 self.grant_user_group_permission(
551 repo=repo, group_name=member_id, perm=perm)
555 repo=repo, group_name=member_id, perm=perm)
552 else:
556 else:
553 raise ValueError("member_type must be 'user' or 'user_group' "
557 raise ValueError("member_type must be 'user' or 'user_group' "
554 "got {} instead".format(member_type))
558 "got {} instead".format(member_type))
555 changes['updated'].append({'type': member_type, 'id': member_id,
559 changes['updated'].append({'type': member_type, 'id': member_id,
556 'name': member_name, 'new_perm': perm})
560 'name': member_name, 'new_perm': perm})
557
561
558 # set new permissions
562 # set new permissions
559 for member_id, perm, member_type in perm_additions:
563 for member_id, perm, member_type in perm_additions:
560 member_id = int(member_id)
564 member_id = int(member_id)
561 if member_type == 'user':
565 if member_type == 'user':
562 member_name = User.get(member_id).username
566 member_name = User.get(member_id).username
563 self.grant_user_permission(
567 self.grant_user_permission(
564 repo=repo, user=member_id, perm=perm)
568 repo=repo, user=member_id, perm=perm)
565 elif member_type == 'user_group':
569 elif member_type == 'user_group':
566 # check if we have permissions to alter this usergroup
570 # check if we have permissions to alter this usergroup
567 member_name = UserGroup.get(member_id).users_group_name
571 member_name = UserGroup.get(member_id).users_group_name
568 if not check_perms or HasUserGroupPermissionAny(
572 if not check_perms or HasUserGroupPermissionAny(
569 *req_perms)(member_name, user=cur_user):
573 *req_perms)(member_name, user=cur_user):
570 self.grant_user_group_permission(
574 self.grant_user_group_permission(
571 repo=repo, group_name=member_id, perm=perm)
575 repo=repo, group_name=member_id, perm=perm)
572 else:
576 else:
573 raise ValueError("member_type must be 'user' or 'user_group' "
577 raise ValueError("member_type must be 'user' or 'user_group' "
574 "got {} instead".format(member_type))
578 "got {} instead".format(member_type))
575
579
576 changes['added'].append({'type': member_type, 'id': member_id,
580 changes['added'].append({'type': member_type, 'id': member_id,
577 'name': member_name, 'new_perm': perm})
581 'name': member_name, 'new_perm': perm})
578 # delete permissions
582 # delete permissions
579 for member_id, perm, member_type in perm_deletions:
583 for member_id, perm, member_type in perm_deletions:
580 member_id = int(member_id)
584 member_id = int(member_id)
581 if member_type == 'user':
585 if member_type == 'user':
582 member_name = User.get(member_id).username
586 member_name = User.get(member_id).username
583 self.revoke_user_permission(repo=repo, user=member_id)
587 self.revoke_user_permission(repo=repo, user=member_id)
584 elif member_type == 'user_group':
588 elif member_type == 'user_group':
585 # check if we have permissions to alter this usergroup
589 # check if we have permissions to alter this usergroup
586 member_name = UserGroup.get(member_id).users_group_name
590 member_name = UserGroup.get(member_id).users_group_name
587 if not check_perms or HasUserGroupPermissionAny(
591 if not check_perms or HasUserGroupPermissionAny(
588 *req_perms)(member_name, user=cur_user):
592 *req_perms)(member_name, user=cur_user):
589 self.revoke_user_group_permission(
593 self.revoke_user_group_permission(
590 repo=repo, group_name=member_id)
594 repo=repo, group_name=member_id)
591 else:
595 else:
592 raise ValueError("member_type must be 'user' or 'user_group' "
596 raise ValueError("member_type must be 'user' or 'user_group' "
593 "got {} instead".format(member_type))
597 "got {} instead".format(member_type))
594
598
595 changes['deleted'].append({'type': member_type, 'id': member_id,
599 changes['deleted'].append({'type': member_type, 'id': member_id,
596 'name': member_name, 'new_perm': perm})
600 'name': member_name, 'new_perm': perm})
597 return changes
601 return changes
598
602
599 def create_fork(self, form_data, cur_user):
603 def create_fork(self, form_data, cur_user):
600 """
604 """
601 Simple wrapper into executing celery task for fork creation
605 Simple wrapper into executing celery task for fork creation
602
606
603 :param form_data:
607 :param form_data:
604 :param cur_user:
608 :param cur_user:
605 """
609 """
606 from rhodecode.lib.celerylib import tasks, run_task
610 from rhodecode.lib.celerylib import tasks, run_task
607 return run_task(tasks.create_repo_fork, form_data, cur_user)
611 return run_task(tasks.create_repo_fork, form_data, cur_user)
608
612
609 def archive(self, repo):
613 def archive(self, repo):
610 """
614 """
611 Archive given repository. Set archive flag.
615 Archive given repository. Set archive flag.
612
616
613 :param repo:
617 :param repo:
614 """
618 """
615 repo = self._get_repo(repo)
619 repo = self._get_repo(repo)
616 if repo:
620 if repo:
617
621
618 try:
622 try:
619 repo.archived = True
623 repo.archived = True
620 self.sa.add(repo)
624 self.sa.add(repo)
621 self.sa.commit()
625 self.sa.commit()
622 except Exception:
626 except Exception:
623 log.error(traceback.format_exc())
627 log.error(traceback.format_exc())
624 raise
628 raise
625
629
626 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
630 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
627 """
631 """
628 Delete given repository, forks parameter defines what do do with
632 Delete given repository, forks parameter defines what do do with
629 attached forks. Throws AttachedForksError if deleted repo has attached
633 attached forks. Throws AttachedForksError if deleted repo has attached
630 forks
634 forks
631
635
632 :param repo:
636 :param repo:
633 :param forks: str 'delete' or 'detach'
637 :param forks: str 'delete' or 'detach'
634 :param pull_requests: str 'delete' or None
638 :param pull_requests: str 'delete' or None
635 :param fs_remove: remove(archive) repo from filesystem
639 :param fs_remove: remove(archive) repo from filesystem
636 """
640 """
637 if not cur_user:
641 if not cur_user:
638 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
642 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
639 repo = self._get_repo(repo)
643 repo = self._get_repo(repo)
640 if repo:
644 if repo:
641 if forks == 'detach':
645 if forks == 'detach':
642 for r in repo.forks:
646 for r in repo.forks:
643 r.fork = None
647 r.fork = None
644 self.sa.add(r)
648 self.sa.add(r)
645 elif forks == 'delete':
649 elif forks == 'delete':
646 for r in repo.forks:
650 for r in repo.forks:
647 self.delete(r, forks='delete')
651 self.delete(r, forks='delete')
648 elif [f for f in repo.forks]:
652 elif [f for f in repo.forks]:
649 raise AttachedForksError()
653 raise AttachedForksError()
650
654
651 # check for pull requests
655 # check for pull requests
652 pr_sources = repo.pull_requests_source
656 pr_sources = repo.pull_requests_source
653 pr_targets = repo.pull_requests_target
657 pr_targets = repo.pull_requests_target
654 if pull_requests != 'delete' and (pr_sources or pr_targets):
658 if pull_requests != 'delete' and (pr_sources or pr_targets):
655 raise AttachedPullRequestsError()
659 raise AttachedPullRequestsError()
656
660
657 old_repo_dict = repo.get_dict()
661 old_repo_dict = repo.get_dict()
658 events.trigger(events.RepoPreDeleteEvent(repo))
662 events.trigger(events.RepoPreDeleteEvent(repo))
659 try:
663 try:
660 self.sa.delete(repo)
664 self.sa.delete(repo)
661 if fs_remove:
665 if fs_remove:
662 self._delete_filesystem_repo(repo)
666 self._delete_filesystem_repo(repo)
663 else:
667 else:
664 log.debug('skipping removal from filesystem')
668 log.debug('skipping removal from filesystem')
665 old_repo_dict.update({
669 old_repo_dict.update({
666 'deleted_by': cur_user,
670 'deleted_by': cur_user,
667 'deleted_on': time.time(),
671 'deleted_on': time.time(),
668 })
672 })
669 log_delete_repository(**old_repo_dict)
673 log_delete_repository(**old_repo_dict)
670 events.trigger(events.RepoDeleteEvent(repo))
674 events.trigger(events.RepoDeleteEvent(repo))
671 except Exception:
675 except Exception:
672 log.error(traceback.format_exc())
676 log.error(traceback.format_exc())
673 raise
677 raise
674
678
675 def grant_user_permission(self, repo, user, perm):
679 def grant_user_permission(self, repo, user, perm):
676 """
680 """
677 Grant permission for user on given repository, or update existing one
681 Grant permission for user on given repository, or update existing one
678 if found
682 if found
679
683
680 :param repo: Instance of Repository, repository_id, or repository name
684 :param repo: Instance of Repository, repository_id, or repository name
681 :param user: Instance of User, user_id or username
685 :param user: Instance of User, user_id or username
682 :param perm: Instance of Permission, or permission_name
686 :param perm: Instance of Permission, or permission_name
683 """
687 """
684 user = self._get_user(user)
688 user = self._get_user(user)
685 repo = self._get_repo(repo)
689 repo = self._get_repo(repo)
686 permission = self._get_perm(perm)
690 permission = self._get_perm(perm)
687
691
688 # check if we have that permission already
692 # check if we have that permission already
689 obj = self.sa.query(UserRepoToPerm) \
693 obj = self.sa.query(UserRepoToPerm) \
690 .filter(UserRepoToPerm.user == user) \
694 .filter(UserRepoToPerm.user == user) \
691 .filter(UserRepoToPerm.repository == repo) \
695 .filter(UserRepoToPerm.repository == repo) \
692 .scalar()
696 .scalar()
693 if obj is None:
697 if obj is None:
694 # create new !
698 # create new !
695 obj = UserRepoToPerm()
699 obj = UserRepoToPerm()
696 obj.repository = repo
700 obj.repository = repo
697 obj.user = user
701 obj.user = user
698 obj.permission = permission
702 obj.permission = permission
699 self.sa.add(obj)
703 self.sa.add(obj)
700 log.debug('Granted perm %s to %s on %s', perm, user, repo)
704 log.debug('Granted perm %s to %s on %s', perm, user, repo)
701 action_logger_generic(
705 action_logger_generic(
702 'granted permission: {} to user: {} on repo: {}'.format(
706 'granted permission: {} to user: {} on repo: {}'.format(
703 perm, user, repo), namespace='security.repo')
707 perm, user, repo), namespace='security.repo')
704 return obj
708 return obj
705
709
706 def revoke_user_permission(self, repo, user):
710 def revoke_user_permission(self, repo, user):
707 """
711 """
708 Revoke permission for user on given repository
712 Revoke permission for user on given repository
709
713
710 :param repo: Instance of Repository, repository_id, or repository name
714 :param repo: Instance of Repository, repository_id, or repository name
711 :param user: Instance of User, user_id or username
715 :param user: Instance of User, user_id or username
712 """
716 """
713
717
714 user = self._get_user(user)
718 user = self._get_user(user)
715 repo = self._get_repo(repo)
719 repo = self._get_repo(repo)
716
720
717 obj = self.sa.query(UserRepoToPerm) \
721 obj = self.sa.query(UserRepoToPerm) \
718 .filter(UserRepoToPerm.repository == repo) \
722 .filter(UserRepoToPerm.repository == repo) \
719 .filter(UserRepoToPerm.user == user) \
723 .filter(UserRepoToPerm.user == user) \
720 .scalar()
724 .scalar()
721 if obj:
725 if obj:
722 self.sa.delete(obj)
726 self.sa.delete(obj)
723 log.debug('Revoked perm on %s on %s', repo, user)
727 log.debug('Revoked perm on %s on %s', repo, user)
724 action_logger_generic(
728 action_logger_generic(
725 'revoked permission from user: {} on repo: {}'.format(
729 'revoked permission from user: {} on repo: {}'.format(
726 user, repo), namespace='security.repo')
730 user, repo), namespace='security.repo')
727
731
728 def grant_user_group_permission(self, repo, group_name, perm):
732 def grant_user_group_permission(self, repo, group_name, perm):
729 """
733 """
730 Grant permission for user group on given repository, or update
734 Grant permission for user group on given repository, or update
731 existing one if found
735 existing one if found
732
736
733 :param repo: Instance of Repository, repository_id, or repository name
737 :param repo: Instance of Repository, repository_id, or repository name
734 :param group_name: Instance of UserGroup, users_group_id,
738 :param group_name: Instance of UserGroup, users_group_id,
735 or user group name
739 or user group name
736 :param perm: Instance of Permission, or permission_name
740 :param perm: Instance of Permission, or permission_name
737 """
741 """
738 repo = self._get_repo(repo)
742 repo = self._get_repo(repo)
739 group_name = self._get_user_group(group_name)
743 group_name = self._get_user_group(group_name)
740 permission = self._get_perm(perm)
744 permission = self._get_perm(perm)
741
745
742 # check if we have that permission already
746 # check if we have that permission already
743 obj = self.sa.query(UserGroupRepoToPerm) \
747 obj = self.sa.query(UserGroupRepoToPerm) \
744 .filter(UserGroupRepoToPerm.users_group == group_name) \
748 .filter(UserGroupRepoToPerm.users_group == group_name) \
745 .filter(UserGroupRepoToPerm.repository == repo) \
749 .filter(UserGroupRepoToPerm.repository == repo) \
746 .scalar()
750 .scalar()
747
751
748 if obj is None:
752 if obj is None:
749 # create new
753 # create new
750 obj = UserGroupRepoToPerm()
754 obj = UserGroupRepoToPerm()
751
755
752 obj.repository = repo
756 obj.repository = repo
753 obj.users_group = group_name
757 obj.users_group = group_name
754 obj.permission = permission
758 obj.permission = permission
755 self.sa.add(obj)
759 self.sa.add(obj)
756 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
760 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
757 action_logger_generic(
761 action_logger_generic(
758 'granted permission: {} to usergroup: {} on repo: {}'.format(
762 'granted permission: {} to usergroup: {} on repo: {}'.format(
759 perm, group_name, repo), namespace='security.repo')
763 perm, group_name, repo), namespace='security.repo')
760
764
761 return obj
765 return obj
762
766
763 def revoke_user_group_permission(self, repo, group_name):
767 def revoke_user_group_permission(self, repo, group_name):
764 """
768 """
765 Revoke permission for user group on given repository
769 Revoke permission for user group on given repository
766
770
767 :param repo: Instance of Repository, repository_id, or repository name
771 :param repo: Instance of Repository, repository_id, or repository name
768 :param group_name: Instance of UserGroup, users_group_id,
772 :param group_name: Instance of UserGroup, users_group_id,
769 or user group name
773 or user group name
770 """
774 """
771 repo = self._get_repo(repo)
775 repo = self._get_repo(repo)
772 group_name = self._get_user_group(group_name)
776 group_name = self._get_user_group(group_name)
773
777
774 obj = self.sa.query(UserGroupRepoToPerm) \
778 obj = self.sa.query(UserGroupRepoToPerm) \
775 .filter(UserGroupRepoToPerm.repository == repo) \
779 .filter(UserGroupRepoToPerm.repository == repo) \
776 .filter(UserGroupRepoToPerm.users_group == group_name) \
780 .filter(UserGroupRepoToPerm.users_group == group_name) \
777 .scalar()
781 .scalar()
778 if obj:
782 if obj:
779 self.sa.delete(obj)
783 self.sa.delete(obj)
780 log.debug('Revoked perm to %s on %s', repo, group_name)
784 log.debug('Revoked perm to %s on %s', repo, group_name)
781 action_logger_generic(
785 action_logger_generic(
782 'revoked permission from usergroup: {} on repo: {}'.format(
786 'revoked permission from usergroup: {} on repo: {}'.format(
783 group_name, repo), namespace='security.repo')
787 group_name, repo), namespace='security.repo')
784
788
785 def delete_stats(self, repo_name):
789 def delete_stats(self, repo_name):
786 """
790 """
787 removes stats for given repo
791 removes stats for given repo
788
792
789 :param repo_name:
793 :param repo_name:
790 """
794 """
791 repo = self._get_repo(repo_name)
795 repo = self._get_repo(repo_name)
792 try:
796 try:
793 obj = self.sa.query(Statistics) \
797 obj = self.sa.query(Statistics) \
794 .filter(Statistics.repository == repo).scalar()
798 .filter(Statistics.repository == repo).scalar()
795 if obj:
799 if obj:
796 self.sa.delete(obj)
800 self.sa.delete(obj)
797 except Exception:
801 except Exception:
798 log.error(traceback.format_exc())
802 log.error(traceback.format_exc())
799 raise
803 raise
800
804
801 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
805 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
802 field_type='str', field_desc=''):
806 field_type='str', field_desc=''):
803
807
804 repo = self._get_repo(repo_name)
808 repo = self._get_repo(repo_name)
805
809
806 new_field = RepositoryField()
810 new_field = RepositoryField()
807 new_field.repository = repo
811 new_field.repository = repo
808 new_field.field_key = field_key
812 new_field.field_key = field_key
809 new_field.field_type = field_type # python type
813 new_field.field_type = field_type # python type
810 new_field.field_value = field_value
814 new_field.field_value = field_value
811 new_field.field_desc = field_desc
815 new_field.field_desc = field_desc
812 new_field.field_label = field_label
816 new_field.field_label = field_label
813 self.sa.add(new_field)
817 self.sa.add(new_field)
814 return new_field
818 return new_field
815
819
816 def delete_repo_field(self, repo_name, field_key):
820 def delete_repo_field(self, repo_name, field_key):
817 repo = self._get_repo(repo_name)
821 repo = self._get_repo(repo_name)
818 field = RepositoryField.get_by_key_name(field_key, repo)
822 field = RepositoryField.get_by_key_name(field_key, repo)
819 if field:
823 if field:
820 self.sa.delete(field)
824 self.sa.delete(field)
821
825
822 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
826 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
823 clone_uri=None, repo_store_location=None,
827 clone_uri=None, repo_store_location=None,
824 use_global_config=False):
828 use_global_config=False):
825 """
829 """
826 makes repository on filesystem. It's group aware means it'll create
830 makes repository on filesystem. It's group aware means it'll create
827 a repository within a group, and alter the paths accordingly of
831 a repository within a group, and alter the paths accordingly of
828 group location
832 group location
829
833
830 :param repo_name:
834 :param repo_name:
831 :param alias:
835 :param alias:
832 :param parent:
836 :param parent:
833 :param clone_uri:
837 :param clone_uri:
834 :param repo_store_location:
838 :param repo_store_location:
835 """
839 """
836 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
840 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
837 from rhodecode.model.scm import ScmModel
841 from rhodecode.model.scm import ScmModel
838
842
839 if Repository.NAME_SEP in repo_name:
843 if Repository.NAME_SEP in repo_name:
840 raise ValueError(
844 raise ValueError(
841 'repo_name must not contain groups got `%s`' % repo_name)
845 'repo_name must not contain groups got `%s`' % repo_name)
842
846
843 if isinstance(repo_group, RepoGroup):
847 if isinstance(repo_group, RepoGroup):
844 new_parent_path = os.sep.join(repo_group.full_path_splitted)
848 new_parent_path = os.sep.join(repo_group.full_path_splitted)
845 else:
849 else:
846 new_parent_path = repo_group or ''
850 new_parent_path = repo_group or ''
847
851
848 if repo_store_location:
852 if repo_store_location:
849 _paths = [repo_store_location]
853 _paths = [repo_store_location]
850 else:
854 else:
851 _paths = [self.repos_path, new_parent_path, repo_name]
855 _paths = [self.repos_path, new_parent_path, repo_name]
852 # we need to make it str for mercurial
856 # we need to make it str for mercurial
853 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
857 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
854
858
855 # check if this path is not a repository
859 # check if this path is not a repository
856 if is_valid_repo(repo_path, self.repos_path):
860 if is_valid_repo(repo_path, self.repos_path):
857 raise Exception('This path %s is a valid repository' % repo_path)
861 raise Exception('This path %s is a valid repository' % repo_path)
858
862
859 # check if this path is a group
863 # check if this path is a group
860 if is_valid_repo_group(repo_path, self.repos_path):
864 if is_valid_repo_group(repo_path, self.repos_path):
861 raise Exception('This path %s is a valid group' % repo_path)
865 raise Exception('This path %s is a valid group' % repo_path)
862
866
863 log.info('creating repo %s in %s from url: `%s`',
867 log.info('creating repo %s in %s from url: `%s`',
864 repo_name, safe_unicode(repo_path),
868 repo_name, safe_unicode(repo_path),
865 obfuscate_url_pw(clone_uri))
869 obfuscate_url_pw(clone_uri))
866
870
867 backend = get_backend(repo_type)
871 backend = get_backend(repo_type)
868
872
869 config_repo = None if use_global_config else repo_name
873 config_repo = None if use_global_config else repo_name
870 if config_repo and new_parent_path:
874 if config_repo and new_parent_path:
871 config_repo = Repository.NAME_SEP.join(
875 config_repo = Repository.NAME_SEP.join(
872 (new_parent_path, config_repo))
876 (new_parent_path, config_repo))
873 config = make_db_config(clear_session=False, repo=config_repo)
877 config = make_db_config(clear_session=False, repo=config_repo)
874 config.set('extensions', 'largefiles', '')
878 config.set('extensions', 'largefiles', '')
875
879
876 # patch and reset hooks section of UI config to not run any
880 # patch and reset hooks section of UI config to not run any
877 # hooks on creating remote repo
881 # hooks on creating remote repo
878 config.clear_section('hooks')
882 config.clear_section('hooks')
879
883
880 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
884 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
881 if repo_type == 'git':
885 if repo_type == 'git':
882 repo = backend(
886 repo = backend(
883 repo_path, config=config, create=True, src_url=clone_uri,
887 repo_path, config=config, create=True, src_url=clone_uri,
884 bare=True)
888 bare=True)
885 else:
889 else:
886 repo = backend(
890 repo = backend(
887 repo_path, config=config, create=True, src_url=clone_uri)
891 repo_path, config=config, create=True, src_url=clone_uri)
888
892
889 repo.install_hooks()
893 repo.install_hooks()
890
894
891 log.debug('Created repo %s with %s backend',
895 log.debug('Created repo %s with %s backend',
892 safe_unicode(repo_name), safe_unicode(repo_type))
896 safe_unicode(repo_name), safe_unicode(repo_type))
893 return repo
897 return repo
894
898
895 def _rename_filesystem_repo(self, old, new):
899 def _rename_filesystem_repo(self, old, new):
896 """
900 """
897 renames repository on filesystem
901 renames repository on filesystem
898
902
899 :param old: old name
903 :param old: old name
900 :param new: new name
904 :param new: new name
901 """
905 """
902 log.info('renaming repo from %s to %s', old, new)
906 log.info('renaming repo from %s to %s', old, new)
903
907
904 old_path = os.path.join(self.repos_path, old)
908 old_path = os.path.join(self.repos_path, old)
905 new_path = os.path.join(self.repos_path, new)
909 new_path = os.path.join(self.repos_path, new)
906 if os.path.isdir(new_path):
910 if os.path.isdir(new_path):
907 raise Exception(
911 raise Exception(
908 'Was trying to rename to already existing dir %s' % new_path
912 'Was trying to rename to already existing dir %s' % new_path
909 )
913 )
910 shutil.move(old_path, new_path)
914 shutil.move(old_path, new_path)
911
915
912 def _delete_filesystem_repo(self, repo):
916 def _delete_filesystem_repo(self, repo):
913 """
917 """
914 removes repo from filesystem, the removal is acctually made by
918 removes repo from filesystem, the removal is acctually made by
915 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
919 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
916 repository is no longer valid for rhodecode, can be undeleted later on
920 repository is no longer valid for rhodecode, can be undeleted later on
917 by reverting the renames on this repository
921 by reverting the renames on this repository
918
922
919 :param repo: repo object
923 :param repo: repo object
920 """
924 """
921 rm_path = os.path.join(self.repos_path, repo.repo_name)
925 rm_path = os.path.join(self.repos_path, repo.repo_name)
922 repo_group = repo.group
926 repo_group = repo.group
923 log.info("Removing repository %s", rm_path)
927 log.info("Removing repository %s", rm_path)
924 # disable hg/git internal that it doesn't get detected as repo
928 # disable hg/git internal that it doesn't get detected as repo
925 alias = repo.repo_type
929 alias = repo.repo_type
926
930
927 config = make_db_config(clear_session=False)
931 config = make_db_config(clear_session=False)
928 config.set('extensions', 'largefiles', '')
932 config.set('extensions', 'largefiles', '')
929 bare = getattr(repo.scm_instance(config=config), 'bare', False)
933 bare = getattr(repo.scm_instance(config=config), 'bare', False)
930
934
931 # skip this for bare git repos
935 # skip this for bare git repos
932 if not bare:
936 if not bare:
933 # disable VCS repo
937 # disable VCS repo
934 vcs_path = os.path.join(rm_path, '.%s' % alias)
938 vcs_path = os.path.join(rm_path, '.%s' % alias)
935 if os.path.exists(vcs_path):
939 if os.path.exists(vcs_path):
936 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
940 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
937
941
938 _now = datetime.datetime.now()
942 _now = datetime.datetime.now()
939 _ms = str(_now.microsecond).rjust(6, '0')
943 _ms = str(_now.microsecond).rjust(6, '0')
940 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
944 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
941 repo.just_name)
945 repo.just_name)
942 if repo_group:
946 if repo_group:
943 # if repository is in group, prefix the removal path with the group
947 # if repository is in group, prefix the removal path with the group
944 args = repo_group.full_path_splitted + [_d]
948 args = repo_group.full_path_splitted + [_d]
945 _d = os.path.join(*args)
949 _d = os.path.join(*args)
946
950
947 if os.path.isdir(rm_path):
951 if os.path.isdir(rm_path):
948 shutil.move(rm_path, os.path.join(self.repos_path, _d))
952 shutil.move(rm_path, os.path.join(self.repos_path, _d))
949
953
950 # finally cleanup diff-cache if it exists
954 # finally cleanup diff-cache if it exists
951 cached_diffs_dir = repo.cached_diffs_dir
955 cached_diffs_dir = repo.cached_diffs_dir
952 if os.path.isdir(cached_diffs_dir):
956 if os.path.isdir(cached_diffs_dir):
953 shutil.rmtree(cached_diffs_dir)
957 shutil.rmtree(cached_diffs_dir)
954
958
955
959
956 class ReadmeFinder:
960 class ReadmeFinder:
957 """
961 """
958 Utility which knows how to find a readme for a specific commit.
962 Utility which knows how to find a readme for a specific commit.
959
963
960 The main idea is that this is a configurable algorithm. When creating an
964 The main idea is that this is a configurable algorithm. When creating an
961 instance you can define parameters, currently only the `default_renderer`.
965 instance you can define parameters, currently only the `default_renderer`.
962 Based on this configuration the method :meth:`search` behaves slightly
966 Based on this configuration the method :meth:`search` behaves slightly
963 different.
967 different.
964 """
968 """
965
969
966 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
970 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
967 path_re = re.compile(r'^docs?', re.IGNORECASE)
971 path_re = re.compile(r'^docs?', re.IGNORECASE)
968
972
969 default_priorities = {
973 default_priorities = {
970 None: 0,
974 None: 0,
971 '.text': 2,
975 '.text': 2,
972 '.txt': 3,
976 '.txt': 3,
973 '.rst': 1,
977 '.rst': 1,
974 '.rest': 2,
978 '.rest': 2,
975 '.md': 1,
979 '.md': 1,
976 '.mkdn': 2,
980 '.mkdn': 2,
977 '.mdown': 3,
981 '.mdown': 3,
978 '.markdown': 4,
982 '.markdown': 4,
979 }
983 }
980
984
981 path_priority = {
985 path_priority = {
982 'doc': 0,
986 'doc': 0,
983 'docs': 1,
987 'docs': 1,
984 }
988 }
985
989
986 FALLBACK_PRIORITY = 99
990 FALLBACK_PRIORITY = 99
987
991
988 RENDERER_TO_EXTENSION = {
992 RENDERER_TO_EXTENSION = {
989 'rst': ['.rst', '.rest'],
993 'rst': ['.rst', '.rest'],
990 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
994 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
991 }
995 }
992
996
993 def __init__(self, default_renderer=None):
997 def __init__(self, default_renderer=None):
994 self._default_renderer = default_renderer
998 self._default_renderer = default_renderer
995 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
999 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
996 default_renderer, [])
1000 default_renderer, [])
997
1001
998 def search(self, commit, path='/'):
1002 def search(self, commit, path='/'):
999 """
1003 """
1000 Find a readme in the given `commit`.
1004 Find a readme in the given `commit`.
1001 """
1005 """
1002 nodes = commit.get_nodes(path)
1006 nodes = commit.get_nodes(path)
1003 matches = self._match_readmes(nodes)
1007 matches = self._match_readmes(nodes)
1004 matches = self._sort_according_to_priority(matches)
1008 matches = self._sort_according_to_priority(matches)
1005 if matches:
1009 if matches:
1006 return matches[0].node
1010 return matches[0].node
1007
1011
1008 paths = self._match_paths(nodes)
1012 paths = self._match_paths(nodes)
1009 paths = self._sort_paths_according_to_priority(paths)
1013 paths = self._sort_paths_according_to_priority(paths)
1010 for path in paths:
1014 for path in paths:
1011 match = self.search(commit, path=path)
1015 match = self.search(commit, path=path)
1012 if match:
1016 if match:
1013 return match
1017 return match
1014
1018
1015 return None
1019 return None
1016
1020
1017 def _match_readmes(self, nodes):
1021 def _match_readmes(self, nodes):
1018 for node in nodes:
1022 for node in nodes:
1019 if not node.is_file():
1023 if not node.is_file():
1020 continue
1024 continue
1021 path = node.path.rsplit('/', 1)[-1]
1025 path = node.path.rsplit('/', 1)[-1]
1022 match = self.readme_re.match(path)
1026 match = self.readme_re.match(path)
1023 if match:
1027 if match:
1024 extension = match.group(1)
1028 extension = match.group(1)
1025 yield ReadmeMatch(node, match, self._priority(extension))
1029 yield ReadmeMatch(node, match, self._priority(extension))
1026
1030
1027 def _match_paths(self, nodes):
1031 def _match_paths(self, nodes):
1028 for node in nodes:
1032 for node in nodes:
1029 if not node.is_dir():
1033 if not node.is_dir():
1030 continue
1034 continue
1031 match = self.path_re.match(node.path)
1035 match = self.path_re.match(node.path)
1032 if match:
1036 if match:
1033 yield node.path
1037 yield node.path
1034
1038
1035 def _priority(self, extension):
1039 def _priority(self, extension):
1036 renderer_priority = (
1040 renderer_priority = (
1037 0 if extension in self._renderer_extensions else 1)
1041 0 if extension in self._renderer_extensions else 1)
1038 extension_priority = self.default_priorities.get(
1042 extension_priority = self.default_priorities.get(
1039 extension, self.FALLBACK_PRIORITY)
1043 extension, self.FALLBACK_PRIORITY)
1040 return (renderer_priority, extension_priority)
1044 return (renderer_priority, extension_priority)
1041
1045
1042 def _sort_according_to_priority(self, matches):
1046 def _sort_according_to_priority(self, matches):
1043
1047
1044 def priority_and_path(match):
1048 def priority_and_path(match):
1045 return (match.priority, match.path)
1049 return (match.priority, match.path)
1046
1050
1047 return sorted(matches, key=priority_and_path)
1051 return sorted(matches, key=priority_and_path)
1048
1052
1049 def _sort_paths_according_to_priority(self, paths):
1053 def _sort_paths_according_to_priority(self, paths):
1050
1054
1051 def priority_and_path(path):
1055 def priority_and_path(path):
1052 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1056 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1053
1057
1054 return sorted(paths, key=priority_and_path)
1058 return sorted(paths, key=priority_and_path)
1055
1059
1056
1060
1057 class ReadmeMatch:
1061 class ReadmeMatch:
1058
1062
1059 def __init__(self, node, match, priority):
1063 def __init__(self, node, match, priority):
1060 self.node = node
1064 self.node = node
1061 self._match = match
1065 self._match = match
1062 self.priority = priority
1066 self.priority = priority
1063
1067
1064 @property
1068 @property
1065 def path(self):
1069 def path(self):
1066 return self.node.path
1070 return self.node.path
1067
1071
1068 def __repr__(self):
1072 def __repr__(self):
1069 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1073 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,74 +1,60 b''
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
1 // # Copyright (C) 2010-2019 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 var onSuccessFollow = function(target){
19 var onSuccessFollow = function (target) {
20 var f = $(target);
20 var targetEl = $(target);
21 var f_cnt = $('#current_followers_count');
22
21
23 if(f.hasClass('follow')){
22 var callback = function () {
24 f.removeClass('follow');
23 targetEl.animate({'opacity': 1.00}, 200);
25 f.addClass('following');
24 if (targetEl.hasClass('watching')) {
26 f.attr('title', _gettext('Stop following this repository'));
25 targetEl.removeClass('watching');
27 $(f).html(_gettext('Unfollow'));
26 targetEl.attr('title', _gettext('Stopped watching this repository'));
28 if(f_cnt.length){
27 $(targetEl).html(_gettext('Watch'));
29 var cnt = Number(f_cnt.html())+1;
28 } else {
30 f_cnt.html(cnt);
29 targetEl.addClass('watching');
30 targetEl.attr('title', _gettext('Started watching this repository'));
31 $(targetEl).html(_gettext('Unwatch'));
31 }
32 }
32 }
33 };
33 else{
34 targetEl.animate({'opacity': 0.15}, 200, callback);
34 f.removeClass('following');
35 f.addClass('follow');
36 f.attr('title', _gettext('Start following this repository'));
37 $(f).html(_gettext('Follow'));
38 if(f_cnt.length){
39 var cnt = Number(f_cnt.html())-1;
40 f_cnt.html(cnt);
41 }
42 }
43 };
35 };
44
36
45 // TODO:: check if the function is needed. 0 usage found
37
46 var toggleFollowingUser = function(target,follows_user_id,token,user_id){
38 var toggleFollowingUser = function (target, follows_user_id) {
47 var args = {
39 var args = {
48 'follows_user_id': follows_user_id,
40 'follows_user_id': follows_user_id,
49 'auth_token': token,
50 'csrf_token': CSRF_TOKEN
41 'csrf_token': CSRF_TOKEN
51 };
42 };
52 if(user_id != undefined){
43
53 args.user_id = user_id
44 ajaxPOST(pyroutes.url('toggle_following'), args, function () {
54 }
55 ajaxPOST(pyroutes.url('toggle_following'), args, function(){
56 onSuccessFollow(target);
45 onSuccessFollow(target);
57 });
46 });
58 return false;
47 return false;
59 };
48 };
60
49
61 var toggleFollowingRepo = function(target,follows_repo_id,token,user_id){
50 var toggleFollowingRepo = function (target, follows_repo_id) {
62 var args = {
51 var args = {
63 'follows_repo_id': follows_repo_id,
52 'follows_repo_id': follows_repo_id,
64 'auth_token': token,
65 'csrf_token': CSRF_TOKEN
53 'csrf_token': CSRF_TOKEN
66 };
54 };
67 if(user_id != undefined){
55
68 args.user_id = user_id
56 ajaxPOST(pyroutes.url('toggle_following'), args, function () {
69 }
70 ajaxPOST(pyroutes.url('toggle_following'), args, function(){
71 onSuccessFollow(target);
57 onSuccessFollow(target);
72 });
58 });
73 return false;
59 return false;
74 };
60 };
@@ -1,951 +1,960 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <%include file="/ejs_templates/templates.html"/>
4 <%include file="/ejs_templates/templates.html"/>
5
5
6 <div class="outerwrapper">
6 <div class="outerwrapper">
7 <!-- HEADER -->
7 <!-- HEADER -->
8 <div class="header">
8 <div class="header">
9 <div id="header-inner" class="wrapper">
9 <div id="header-inner" class="wrapper">
10 <div id="logo">
10 <div id="logo">
11 <div class="logo-wrapper">
11 <div class="logo-wrapper">
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
13 </div>
13 </div>
14 % if c.rhodecode_name:
14 % if c.rhodecode_name:
15 <div class="branding">
15 <div class="branding">
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
17 </div>
17 </div>
18 % endif
18 % endif
19 </div>
19 </div>
20 <!-- MENU BAR NAV -->
20 <!-- MENU BAR NAV -->
21 ${self.menu_bar_nav()}
21 ${self.menu_bar_nav()}
22 <!-- END MENU BAR NAV -->
22 <!-- END MENU BAR NAV -->
23 </div>
23 </div>
24 </div>
24 </div>
25 ${self.menu_bar_subnav()}
25 ${self.menu_bar_subnav()}
26 <!-- END HEADER -->
26 <!-- END HEADER -->
27
27
28 <!-- CONTENT -->
28 <!-- CONTENT -->
29 <div id="content" class="wrapper">
29 <div id="content" class="wrapper">
30
30
31 <rhodecode-toast id="notifications"></rhodecode-toast>
31 <rhodecode-toast id="notifications"></rhodecode-toast>
32
32
33 <div class="main">
33 <div class="main">
34 ${next.main()}
34 ${next.main()}
35 </div>
35 </div>
36 </div>
36 </div>
37 <!-- END CONTENT -->
37 <!-- END CONTENT -->
38
38
39 </div>
39 </div>
40 <!-- FOOTER -->
40 <!-- FOOTER -->
41 <div id="footer">
41 <div id="footer">
42 <div id="footer-inner" class="title wrapper">
42 <div id="footer-inner" class="title wrapper">
43 <div>
43 <div>
44 <p class="footer-link-right">
44 <p class="footer-link-right">
45 % if c.visual.show_version:
45 % if c.visual.show_version:
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
47 % endif
47 % endif
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
49 % if c.visual.rhodecode_support_url:
49 % if c.visual.rhodecode_support_url:
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
51 % endif
51 % endif
52 </p>
52 </p>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
54 <p class="server-instance" style="display:${sid}">
54 <p class="server-instance" style="display:${sid}">
55 ## display hidden instance ID if specially defined
55 ## display hidden instance ID if specially defined
56 % if c.rhodecode_instanceid:
56 % if c.rhodecode_instanceid:
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
58 % endif
58 % endif
59 </p>
59 </p>
60 </div>
60 </div>
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <!-- END FOOTER -->
64 <!-- END FOOTER -->
65
65
66 ### MAKO DEFS ###
66 ### MAKO DEFS ###
67
67
68 <%def name="menu_bar_subnav()">
68 <%def name="menu_bar_subnav()">
69 </%def>
69 </%def>
70
70
71 <%def name="breadcrumbs(class_='breadcrumbs')">
71 <%def name="breadcrumbs(class_='breadcrumbs')">
72 <div class="${class_}">
72 <div class="${class_}">
73 ${self.breadcrumbs_links()}
73 ${self.breadcrumbs_links()}
74 </div>
74 </div>
75 </%def>
75 </%def>
76
76
77 <%def name="admin_menu(active=None)">
77 <%def name="admin_menu(active=None)">
78 <%
78 <%
79 def is_active(selected):
79 def is_active(selected):
80 if selected == active:
80 if selected == active:
81 return "active"
81 return "active"
82 %>
82 %>
83
83
84 <div id="context-bar">
84 <div id="context-bar">
85 <div class="wrapper">
85 <div class="wrapper">
86 <div class="title">
86 <div class="title">
87 <div class="title-content">
87 <div class="title-content">
88 <div class="title-main">
88 <div class="title-main">
89 % if c.is_super_admin:
89 % if c.is_super_admin:
90 ${_('Super Admin Panel')}
90 ${_('Super Admin Panel')}
91 % else:
91 % else:
92 ${_('Delegated Admin Panel')}
92 ${_('Delegated Admin Panel')}
93 % endif
93 % endif
94 </div>
94 </div>
95 </div>
95 </div>
96 </div>
96 </div>
97
97
98 <ul id="context-pages" class="navigation horizontal-list">
98 <ul id="context-pages" class="navigation horizontal-list">
99
99
100 ## super admin case
100 ## super admin case
101 % if c.is_super_admin:
101 % if c.is_super_admin:
102 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
102 <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
103 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
103 <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
104 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
104 <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
105 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
105 <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
106 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
106 <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
107 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
107 <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
108 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
108 <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
109 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
109 <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
110 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
110 <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
111 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
111 <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
112
112
113 ## delegated admin
113 ## delegated admin
114 % elif c.is_delegated_admin:
114 % elif c.is_delegated_admin:
115 <%
115 <%
116 repositories=c.auth_user.repositories_admin or c.can_create_repo
116 repositories=c.auth_user.repositories_admin or c.can_create_repo
117 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
117 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
118 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
118 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
119 %>
119 %>
120
120
121 %if repositories:
121 %if repositories:
122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
122 <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
123 %endif
123 %endif
124 %if repository_groups:
124 %if repository_groups:
125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
125 <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
126 %endif
126 %endif
127 %if user_groups:
127 %if user_groups:
128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
128 <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
129 %endif
129 %endif
130 % endif
130 % endif
131 </ul>
131 </ul>
132
132
133 </div>
133 </div>
134 <div class="clear"></div>
134 <div class="clear"></div>
135 </div>
135 </div>
136 </%def>
136 </%def>
137
137
138 <%def name="dt_info_panel(elements)">
138 <%def name="dt_info_panel(elements)">
139 <dl class="dl-horizontal">
139 <dl class="dl-horizontal">
140 %for dt, dd, title, show_items in elements:
140 %for dt, dd, title, show_items in elements:
141 <dt>${dt}:</dt>
141 <dt>${dt}:</dt>
142 <dd title="${h.tooltip(title)}">
142 <dd title="${h.tooltip(title)}">
143 %if callable(dd):
143 %if callable(dd):
144 ## allow lazy evaluation of elements
144 ## allow lazy evaluation of elements
145 ${dd()}
145 ${dd()}
146 %else:
146 %else:
147 ${dd}
147 ${dd}
148 %endif
148 %endif
149 %if show_items:
149 %if show_items:
150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
150 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
151 %endif
151 %endif
152 </dd>
152 </dd>
153
153
154 %if show_items:
154 %if show_items:
155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
155 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
156 %for item in show_items:
156 %for item in show_items:
157 <dt></dt>
157 <dt></dt>
158 <dd>${item}</dd>
158 <dd>${item}</dd>
159 %endfor
159 %endfor
160 </div>
160 </div>
161 %endif
161 %endif
162
162
163 %endfor
163 %endfor
164 </dl>
164 </dl>
165 </%def>
165 </%def>
166
166
167 <%def name="gravatar(email, size=16)">
167 <%def name="gravatar(email, size=16)">
168 <%
168 <%
169 if (size > 16):
169 if (size > 16):
170 gravatar_class = 'gravatar gravatar-large'
170 gravatar_class = 'gravatar gravatar-large'
171 else:
171 else:
172 gravatar_class = 'gravatar'
172 gravatar_class = 'gravatar'
173 %>
173 %>
174 <%doc>
174 <%doc>
175 TODO: johbo: For now we serve double size images to make it smooth
175 TODO: johbo: For now we serve double size images to make it smooth
176 for retina. This is how it worked until now. Should be replaced
176 for retina. This is how it worked until now. Should be replaced
177 with a better solution at some point.
177 with a better solution at some point.
178 </%doc>
178 </%doc>
179 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
179 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
180 </%def>
180 </%def>
181
181
182
182
183 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
183 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
184 <% email = h.email_or_none(contact) %>
184 <% email = h.email_or_none(contact) %>
185 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
185 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
186 ${self.gravatar(email, size)}
186 ${self.gravatar(email, size)}
187 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
187 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
188 </div>
188 </div>
189 </%def>
189 </%def>
190
190
191
191
192 <%def name="repo_page_title(repo_instance)">
192 <%def name="repo_page_title(repo_instance)">
193 <div class="title-content repo-title">
193 <div class="title-content repo-title">
194 <div class="title-main">
194 <div class="title-main">
195 ## SVN/HG/GIT icons
195 ## SVN/HG/GIT icons
196 %if h.is_hg(repo_instance):
196 %if h.is_hg(repo_instance):
197 <i class="icon-hg"></i>
197 <i class="icon-hg"></i>
198 %endif
198 %endif
199 %if h.is_git(repo_instance):
199 %if h.is_git(repo_instance):
200 <i class="icon-git"></i>
200 <i class="icon-git"></i>
201 %endif
201 %endif
202 %if h.is_svn(repo_instance):
202 %if h.is_svn(repo_instance):
203 <i class="icon-svn"></i>
203 <i class="icon-svn"></i>
204 %endif
204 %endif
205
205
206 ## public/private
206 ## public/private
207 %if repo_instance.private:
207 %if repo_instance.private:
208 <i class="icon-repo-private"></i>
208 <i class="icon-repo-private"></i>
209 %else:
209 %else:
210 <i class="icon-repo-public"></i>
210 <i class="icon-repo-public"></i>
211 %endif
211 %endif
212
212
213 ## repo name with group name
213 ## repo name with group name
214 ${h.breadcrumb_repo_link(repo_instance)}
214 ${h.breadcrumb_repo_link(repo_instance)}
215
215
216 ## Context Actions
216 ## Context Actions
217 <div class="pull-right">
217 <div class="pull-right">
218 %if c.rhodecode_user.username != h.DEFAULT_USER:
218 %if c.rhodecode_user.username != h.DEFAULT_USER:
219 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
219 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
220
221 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
222 % if c.repository_is_user_following:
223 Unwatch
224 % else:
225 Watch
226 % endif
227
228 </a>
220 %else:
229 %else:
221 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
230 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
222 %endif
231 %endif
223 </div>
232 </div>
224
233
225 </div>
234 </div>
226
235
227 ## FORKED
236 ## FORKED
228 %if repo_instance.fork:
237 %if repo_instance.fork:
229 <p class="discreet">
238 <p class="discreet">
230 <i class="icon-code-fork"></i> ${_('Fork of')}
239 <i class="icon-code-fork"></i> ${_('Fork of')}
231 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
240 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
232 </p>
241 </p>
233 %endif
242 %endif
234
243
235 ## IMPORTED FROM REMOTE
244 ## IMPORTED FROM REMOTE
236 %if repo_instance.clone_uri:
245 %if repo_instance.clone_uri:
237 <p class="discreet">
246 <p class="discreet">
238 <i class="icon-code-fork"></i> ${_('Clone from')}
247 <i class="icon-code-fork"></i> ${_('Clone from')}
239 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
248 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
240 </p>
249 </p>
241 %endif
250 %endif
242
251
243 ## LOCKING STATUS
252 ## LOCKING STATUS
244 %if repo_instance.locked[0]:
253 %if repo_instance.locked[0]:
245 <p class="locking_locked discreet">
254 <p class="locking_locked discreet">
246 <i class="icon-repo-lock"></i>
255 <i class="icon-repo-lock"></i>
247 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
256 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
248 </p>
257 </p>
249 %elif repo_instance.enable_locking:
258 %elif repo_instance.enable_locking:
250 <p class="locking_unlocked discreet">
259 <p class="locking_unlocked discreet">
251 <i class="icon-repo-unlock"></i>
260 <i class="icon-repo-unlock"></i>
252 ${_('Repository not locked. Pull repository to lock it.')}
261 ${_('Repository not locked. Pull repository to lock it.')}
253 </p>
262 </p>
254 %endif
263 %endif
255
264
256 </div>
265 </div>
257 </%def>
266 </%def>
258
267
259 <%def name="repo_menu(active=None)">
268 <%def name="repo_menu(active=None)">
260 <%
269 <%
261 def is_active(selected):
270 def is_active(selected):
262 if selected == active:
271 if selected == active:
263 return "active"
272 return "active"
264 %>
273 %>
265
274
266 <!--- REPO CONTEXT BAR -->
275 <!--- REPO CONTEXT BAR -->
267 <div id="context-bar">
276 <div id="context-bar">
268 <div class="wrapper">
277 <div class="wrapper">
269
278
270 <div class="title">
279 <div class="title">
271 ${self.repo_page_title(c.rhodecode_db_repo)}
280 ${self.repo_page_title(c.rhodecode_db_repo)}
272 </div>
281 </div>
273
282
274 <ul id="context-pages" class="navigation horizontal-list">
283 <ul id="context-pages" class="navigation horizontal-list">
275 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
284 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
276 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
285 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
277 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
286 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
278 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
287 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
279
288
280 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
289 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
281 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
290 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
282 <li class="${is_active('showpullrequest')}">
291 <li class="${is_active('showpullrequest')}">
283 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
292 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
284 <div class="menulabel">
293 <div class="menulabel">
285 %if c.repository_pull_requests == 1:
294 %if c.repository_pull_requests == 1:
286 ${c.repository_pull_requests} ${_('Pull Request')}
295 ${c.repository_pull_requests} ${_('Pull Request')}
287 %else:
296 %else:
288 ${c.repository_pull_requests} ${_('Pull Requests')}
297 ${c.repository_pull_requests} ${_('Pull Requests')}
289 %endif
298 %endif
290 </div>
299 </div>
291 </a>
300 </a>
292 </li>
301 </li>
293 %endif
302 %endif
294
303
295 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
304 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
296 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
305 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
297 %endif
306 %endif
298
307
299 <li class="${is_active('options')}">
308 <li class="${is_active('options')}">
300 <a class="menulink dropdown">
309 <a class="menulink dropdown">
301 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
310 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
302 </a>
311 </a>
303 <ul class="submenu">
312 <ul class="submenu">
304
313
305 %if c.rhodecode_db_repo.fork:
314 %if c.rhodecode_db_repo.fork:
306 <li>
315 <li>
307 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
316 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
308 href="${h.route_path('repo_compare',
317 href="${h.route_path('repo_compare',
309 repo_name=c.rhodecode_db_repo.fork.repo_name,
318 repo_name=c.rhodecode_db_repo.fork.repo_name,
310 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
319 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
311 source_ref=c.rhodecode_db_repo.landing_rev[1],
320 source_ref=c.rhodecode_db_repo.landing_rev[1],
312 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
321 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
313 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
322 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
314 _query=dict(merge=1))}"
323 _query=dict(merge=1))}"
315 >
324 >
316 ${_('Compare fork')}
325 ${_('Compare fork')}
317 </a>
326 </a>
318 </li>
327 </li>
319 %endif
328 %endif
320
329
321 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
330 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
322 %if c.rhodecode_db_repo.locked[0]:
331 %if c.rhodecode_db_repo.locked[0]:
323 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
332 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
324 %else:
333 %else:
325 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
334 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
326 %endif
335 %endif
327 %endif
336 %endif
328 %if c.rhodecode_user.username != h.DEFAULT_USER:
337 %if c.rhodecode_user.username != h.DEFAULT_USER:
329 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
338 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
330 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
339 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
331 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
340 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
332 %endif
341 %endif
333 %endif
342 %endif
334 </ul>
343 </ul>
335 </li>
344 </li>
336 </ul>
345 </ul>
337 </div>
346 </div>
338 <div class="clear"></div>
347 <div class="clear"></div>
339 </div>
348 </div>
340 % if c.rhodecode_db_repo.archived:
349 % if c.rhodecode_db_repo.archived:
341 <div class="alert alert-warning text-center">
350 <div class="alert alert-warning text-center">
342 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
351 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
343 </div>
352 </div>
344 % endif
353 % endif
345 <!--- REPO END CONTEXT BAR -->
354 <!--- REPO END CONTEXT BAR -->
346
355
347 </%def>
356 </%def>
348
357
349 <%def name="repo_group_page_title(repo_group_instance)">
358 <%def name="repo_group_page_title(repo_group_instance)">
350 <div class="title-content">
359 <div class="title-content">
351 <div class="title-main">
360 <div class="title-main">
352 ## Repository Group icon
361 ## Repository Group icon
353 <i class="icon-folder-close"></i>
362 <i class="icon-folder-close"></i>
354
363
355 ## repo name with group name
364 ## repo name with group name
356 ${h.breadcrumb_repo_group_link(repo_group_instance)}
365 ${h.breadcrumb_repo_group_link(repo_group_instance)}
357 </div>
366 </div>
358
367
359 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
368 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
360 <div class="repo-group-desc discreet">
369 <div class="repo-group-desc discreet">
361 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
370 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
362 </div>
371 </div>
363
372
364 </div>
373 </div>
365 </%def>
374 </%def>
366
375
367 <%def name="repo_group_menu(active=None)">
376 <%def name="repo_group_menu(active=None)">
368 <%
377 <%
369 def is_active(selected):
378 def is_active(selected):
370 if selected == active:
379 if selected == active:
371 return "active"
380 return "active"
372
381
373 gr_name = c.repo_group.group_name if c.repo_group else None
382 gr_name = c.repo_group.group_name if c.repo_group else None
374 # create repositories with write permission on group is set to true
383 # create repositories with write permission on group is set to true
375 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
384 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
376 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
385 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
377 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
386 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
378
387
379 %>
388 %>
380
389
381 <!--- REPO GROUP CONTEXT BAR -->
390 <!--- REPO GROUP CONTEXT BAR -->
382 <div id="context-bar">
391 <div id="context-bar">
383 <div class="wrapper">
392 <div class="wrapper">
384 <div class="title">
393 <div class="title">
385 ${self.repo_group_page_title(c.repo_group)}
394 ${self.repo_group_page_title(c.repo_group)}
386 </div>
395 </div>
387
396
388 <ul id="context-pages" class="navigation horizontal-list">
397 <ul id="context-pages" class="navigation horizontal-list">
389 <li class="${is_active('home')}"><a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a></li>
398 <li class="${is_active('home')}"><a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a></li>
390 % if c.is_super_admin or group_admin:
399 % if c.is_super_admin or group_admin:
391 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a></li>
400 <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a></li>
392 % endif
401 % endif
393
402
394 <li class="${is_active('options')}">
403 <li class="${is_active('options')}">
395 <a class="menulink dropdown">
404 <a class="menulink dropdown">
396 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
405 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
397 </a>
406 </a>
398 <ul class="submenu">
407 <ul class="submenu">
399 %if c.is_super_admin or group_admin or (group_write and create_on_write):
408 %if c.is_super_admin or group_admin or (group_write and create_on_write):
400 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
409 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
401 %endif
410 %endif
402 %if c.is_super_admin or group_admin:
411 %if c.is_super_admin or group_admin:
403 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Parent Group')}</a></li>
412 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Parent Group')}</a></li>
404 %endif
413 %endif
405 </ul>
414 </ul>
406 </li>
415 </li>
407 </ul>
416 </ul>
408 </div>
417 </div>
409 <div class="clear"></div>
418 <div class="clear"></div>
410 </div>
419 </div>
411
420
412 <!--- REPO GROUP CONTEXT BAR -->
421 <!--- REPO GROUP CONTEXT BAR -->
413
422
414 </%def>
423 </%def>
415
424
416
425
417 <%def name="usermenu(active=False)">
426 <%def name="usermenu(active=False)">
418 ## USER MENU
427 ## USER MENU
419 <li id="quick_login_li" class="${'active' if active else ''}">
428 <li id="quick_login_li" class="${'active' if active else ''}">
420 % if c.rhodecode_user.username == h.DEFAULT_USER:
429 % if c.rhodecode_user.username == h.DEFAULT_USER:
421 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
430 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
422 ${gravatar(c.rhodecode_user.email, 20)}
431 ${gravatar(c.rhodecode_user.email, 20)}
423 <span class="user">
432 <span class="user">
424 <span>${_('Sign in')}</span>
433 <span>${_('Sign in')}</span>
425 </span>
434 </span>
426 </a>
435 </a>
427 % else:
436 % else:
428 ## logged in user
437 ## logged in user
429 <a id="quick_login_link" class="menulink childs">
438 <a id="quick_login_link" class="menulink childs">
430 ${gravatar(c.rhodecode_user.email, 20)}
439 ${gravatar(c.rhodecode_user.email, 20)}
431 <span class="user">
440 <span class="user">
432 <span class="menu_link_user">${c.rhodecode_user.username}</span>
441 <span class="menu_link_user">${c.rhodecode_user.username}</span>
433 <div class="show_more"></div>
442 <div class="show_more"></div>
434 </span>
443 </span>
435 </a>
444 </a>
436 ## subnav with menu for logged in user
445 ## subnav with menu for logged in user
437 <div class="user-menu submenu">
446 <div class="user-menu submenu">
438 <div id="quick_login">
447 <div id="quick_login">
439 %if c.rhodecode_user.username != h.DEFAULT_USER:
448 %if c.rhodecode_user.username != h.DEFAULT_USER:
440 <div class="">
449 <div class="">
441 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
450 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
442 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
451 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
443 <div class="email">${c.rhodecode_user.email}</div>
452 <div class="email">${c.rhodecode_user.email}</div>
444 </div>
453 </div>
445 <div class="">
454 <div class="">
446 <ol class="links">
455 <ol class="links">
447 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
456 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
448 % if c.rhodecode_user.personal_repo_group:
457 % if c.rhodecode_user.personal_repo_group:
449 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
458 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
450 % endif
459 % endif
451 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
460 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
452 ## bookmark-items
461 ## bookmark-items
453 <li class="bookmark-items">
462 <li class="bookmark-items">
454 ${_('Bookmarks')}
463 ${_('Bookmarks')}
455 <div class="pull-right">
464 <div class="pull-right">
456 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
465 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
457 </div>
466 </div>
458 </li>
467 </li>
459 % if not c.bookmark_items:
468 % if not c.bookmark_items:
460 <li>
469 <li>
461 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
470 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
462 </li>
471 </li>
463 % endif
472 % endif
464 % for item in c.bookmark_items:
473 % for item in c.bookmark_items:
465 <li>
474 <li>
466 % if item.repository:
475 % if item.repository:
467 <div>
476 <div>
468 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
477 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
469 <code>${item.position}</code>
478 <code>${item.position}</code>
470 % if item.repository.repo_type == 'hg':
479 % if item.repository.repo_type == 'hg':
471 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
480 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
472 % elif item.repository.repo_type == 'git':
481 % elif item.repository.repo_type == 'git':
473 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
482 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
474 % elif item.repository.repo_type == 'svn':
483 % elif item.repository.repo_type == 'svn':
475 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
484 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
476 % endif
485 % endif
477 ${(item.title or h.shorter(item.repository.repo_name, 30))}
486 ${(item.title or h.shorter(item.repository.repo_name, 30))}
478 </a>
487 </a>
479 </div>
488 </div>
480 % elif item.repository_group:
489 % elif item.repository_group:
481 <div>
490 <div>
482 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
491 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
483 <code>${item.position}</code>
492 <code>${item.position}</code>
484 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
493 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
485 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
494 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
486 </a>
495 </a>
487 </div>
496 </div>
488 % else:
497 % else:
489 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
498 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
490 <code>${item.position}</code>
499 <code>${item.position}</code>
491 ${item.title}
500 ${item.title}
492 </a>
501 </a>
493 % endif
502 % endif
494 </li>
503 </li>
495 % endfor
504 % endfor
496
505
497 <li class="logout">
506 <li class="logout">
498 ${h.secure_form(h.route_path('logout'), request=request)}
507 ${h.secure_form(h.route_path('logout'), request=request)}
499 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
508 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
500 ${h.end_form()}
509 ${h.end_form()}
501 </li>
510 </li>
502 </ol>
511 </ol>
503 </div>
512 </div>
504 %endif
513 %endif
505 </div>
514 </div>
506 </div>
515 </div>
507 ## unread counter
516 ## unread counter
508 <div class="pill_container">
517 <div class="pill_container">
509 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
518 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
510 </div>
519 </div>
511 % endif
520 % endif
512 </li>
521 </li>
513 </%def>
522 </%def>
514
523
515 <%def name="menu_items(active=None)">
524 <%def name="menu_items(active=None)">
516 <%
525 <%
517 def is_active(selected):
526 def is_active(selected):
518 if selected == active:
527 if selected == active:
519 return "active"
528 return "active"
520 return ""
529 return ""
521 %>
530 %>
522
531
523 <ul id="quick" class="main_nav navigation horizontal-list">
532 <ul id="quick" class="main_nav navigation horizontal-list">
524 ## notice box for important system messages
533 ## notice box for important system messages
525 <li style="display: none">
534 <li style="display: none">
526 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
535 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
527 <div class="menulabel-notice" >
536 <div class="menulabel-notice" >
528 0
537 0
529 </div>
538 </div>
530 </a>
539 </a>
531 </li>
540 </li>
532
541
533 ## Main filter
542 ## Main filter
534 <li>
543 <li>
535 <div class="menulabel main_filter_box">
544 <div class="menulabel main_filter_box">
536 <div class="main_filter_input_box">
545 <div class="main_filter_input_box">
537 <ul class="searchItems">
546 <ul class="searchItems">
538
547
539 % if c.template_context['search_context']['repo_id']:
548 % if c.template_context['search_context']['repo_id']:
540 <li class="searchTag searchTagFilter searchTagHidable" >
549 <li class="searchTag searchTagFilter searchTagHidable" >
541 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
550 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
542 <span class="tag">
551 <span class="tag">
543 This repo
552 This repo
544 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
553 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
545 </span>
554 </span>
546 ##</a>
555 ##</a>
547 </li>
556 </li>
548 % elif c.template_context['search_context']['repo_group_id']:
557 % elif c.template_context['search_context']['repo_group_id']:
549 <li class="searchTag searchTagFilter searchTagHidable">
558 <li class="searchTag searchTagFilter searchTagHidable">
550 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
559 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
551 <span class="tag">
560 <span class="tag">
552 This group
561 This group
553 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
562 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
554 </span>
563 </span>
555 ##</a>
564 ##</a>
556 </li>
565 </li>
557 % endif
566 % endif
558
567
559 <li class="searchTagInput">
568 <li class="searchTagInput">
560 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
569 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
561 </li>
570 </li>
562 <li class="searchTag searchTagHelp">
571 <li class="searchTag searchTagHelp">
563 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
572 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
564 </li>
573 </li>
565 </ul>
574 </ul>
566 </div>
575 </div>
567 </div>
576 </div>
568
577
569 <div id="main_filter_help" style="display: none">
578 <div id="main_filter_help" style="display: none">
570 - Use '/' key to quickly access this field.
579 - Use '/' key to quickly access this field.
571
580
572 - Enter a name of repository, or repository group for quick search.
581 - Enter a name of repository, or repository group for quick search.
573
582
574 - Prefix query to allow special search:
583 - Prefix query to allow special search:
575
584
576 user:admin, to search for usernames, always global
585 user:admin, to search for usernames, always global
577
586
578 user_group:devops, to search for user groups, always global
587 user_group:devops, to search for user groups, always global
579
588
580 commit:efced4, to search for commits, scoped to repositories or groups
589 commit:efced4, to search for commits, scoped to repositories or groups
581
590
582 file:models.py, to search for file paths, scoped to repositories or groups
591 file:models.py, to search for file paths, scoped to repositories or groups
583
592
584 % if c.template_context['search_context']['repo_id']:
593 % if c.template_context['search_context']['repo_id']:
585 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
594 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
586 % elif c.template_context['search_context']['repo_group_id']:
595 % elif c.template_context['search_context']['repo_group_id']:
587 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
596 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
588 % else:
597 % else:
589 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
598 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
590 % endif
599 % endif
591 </div>
600 </div>
592 </li>
601 </li>
593
602
594 ## ROOT MENU
603 ## ROOT MENU
595 <li class="${is_active('home')}">
604 <li class="${is_active('home')}">
596 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
605 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
597 <div class="menulabel">${_('Home')}</div>
606 <div class="menulabel">${_('Home')}</div>
598 </a>
607 </a>
599 </li>
608 </li>
600
609
601 %if c.rhodecode_user.username != h.DEFAULT_USER:
610 %if c.rhodecode_user.username != h.DEFAULT_USER:
602 <li class="${is_active('journal')}">
611 <li class="${is_active('journal')}">
603 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
612 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
604 <div class="menulabel">${_('Journal')}</div>
613 <div class="menulabel">${_('Journal')}</div>
605 </a>
614 </a>
606 </li>
615 </li>
607 %else:
616 %else:
608 <li class="${is_active('journal')}">
617 <li class="${is_active('journal')}">
609 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
618 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
610 <div class="menulabel">${_('Public journal')}</div>
619 <div class="menulabel">${_('Public journal')}</div>
611 </a>
620 </a>
612 </li>
621 </li>
613 %endif
622 %endif
614
623
615 <li class="${is_active('gists')}">
624 <li class="${is_active('gists')}">
616 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
625 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
617 <div class="menulabel">${_('Gists')}</div>
626 <div class="menulabel">${_('Gists')}</div>
618 </a>
627 </a>
619 </li>
628 </li>
620
629
621 % if c.is_super_admin or c.is_delegated_admin:
630 % if c.is_super_admin or c.is_delegated_admin:
622 <li class="${is_active('admin')}">
631 <li class="${is_active('admin')}">
623 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
632 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
624 <div class="menulabel">${_('Admin')} </div>
633 <div class="menulabel">${_('Admin')} </div>
625 </a>
634 </a>
626 </li>
635 </li>
627 % endif
636 % endif
628
637
629 ## render extra user menu
638 ## render extra user menu
630 ${usermenu(active=(active=='my_account'))}
639 ${usermenu(active=(active=='my_account'))}
631
640
632 % if c.debug_style:
641 % if c.debug_style:
633 <li>
642 <li>
634 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
643 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
635 <div class="menulabel">${_('[Style]')}</div>
644 <div class="menulabel">${_('[Style]')}</div>
636 </a>
645 </a>
637 </li>
646 </li>
638 % endif
647 % endif
639 </ul>
648 </ul>
640
649
641 <script type="text/javascript">
650 <script type="text/javascript">
642 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
651 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
643
652
644 var formatRepoResult = function(result, container, query, escapeMarkup) {
653 var formatRepoResult = function(result, container, query, escapeMarkup) {
645 return function(data, escapeMarkup) {
654 return function(data, escapeMarkup) {
646 if (!data.repo_id){
655 if (!data.repo_id){
647 return data.text; // optgroup text Repositories
656 return data.text; // optgroup text Repositories
648 }
657 }
649
658
650 var tmpl = '';
659 var tmpl = '';
651 var repoType = data['repo_type'];
660 var repoType = data['repo_type'];
652 var repoName = data['text'];
661 var repoName = data['text'];
653
662
654 if(data && data.type == 'repo'){
663 if(data && data.type == 'repo'){
655 if(repoType === 'hg'){
664 if(repoType === 'hg'){
656 tmpl += '<i class="icon-hg"></i> ';
665 tmpl += '<i class="icon-hg"></i> ';
657 }
666 }
658 else if(repoType === 'git'){
667 else if(repoType === 'git'){
659 tmpl += '<i class="icon-git"></i> ';
668 tmpl += '<i class="icon-git"></i> ';
660 }
669 }
661 else if(repoType === 'svn'){
670 else if(repoType === 'svn'){
662 tmpl += '<i class="icon-svn"></i> ';
671 tmpl += '<i class="icon-svn"></i> ';
663 }
672 }
664 if(data['private']){
673 if(data['private']){
665 tmpl += '<i class="icon-lock" ></i> ';
674 tmpl += '<i class="icon-lock" ></i> ';
666 }
675 }
667 else if(visualShowPublicIcon){
676 else if(visualShowPublicIcon){
668 tmpl += '<i class="icon-unlock-alt"></i> ';
677 tmpl += '<i class="icon-unlock-alt"></i> ';
669 }
678 }
670 }
679 }
671 tmpl += escapeMarkup(repoName);
680 tmpl += escapeMarkup(repoName);
672 return tmpl;
681 return tmpl;
673
682
674 }(result, escapeMarkup);
683 }(result, escapeMarkup);
675 };
684 };
676
685
677 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
686 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
678 return function(data, escapeMarkup) {
687 return function(data, escapeMarkup) {
679 if (!data.repo_group_id){
688 if (!data.repo_group_id){
680 return data.text; // optgroup text Repositories
689 return data.text; // optgroup text Repositories
681 }
690 }
682
691
683 var tmpl = '';
692 var tmpl = '';
684 var repoGroupName = data['text'];
693 var repoGroupName = data['text'];
685
694
686 if(data){
695 if(data){
687
696
688 tmpl += '<i class="icon-folder-close"></i> ';
697 tmpl += '<i class="icon-folder-close"></i> ';
689
698
690 }
699 }
691 tmpl += escapeMarkup(repoGroupName);
700 tmpl += escapeMarkup(repoGroupName);
692 return tmpl;
701 return tmpl;
693
702
694 }(result, escapeMarkup);
703 }(result, escapeMarkup);
695 };
704 };
696
705
697 var escapeRegExChars = function (value) {
706 var escapeRegExChars = function (value) {
698 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
707 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
699 };
708 };
700
709
701 var getRepoIcon = function(repo_type) {
710 var getRepoIcon = function(repo_type) {
702 if (repo_type === 'hg') {
711 if (repo_type === 'hg') {
703 return '<i class="icon-hg"></i> ';
712 return '<i class="icon-hg"></i> ';
704 }
713 }
705 else if (repo_type === 'git') {
714 else if (repo_type === 'git') {
706 return '<i class="icon-git"></i> ';
715 return '<i class="icon-git"></i> ';
707 }
716 }
708 else if (repo_type === 'svn') {
717 else if (repo_type === 'svn') {
709 return '<i class="icon-svn"></i> ';
718 return '<i class="icon-svn"></i> ';
710 }
719 }
711 return ''
720 return ''
712 };
721 };
713
722
714 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
723 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
715
724
716 if (value.split(':').length === 2) {
725 if (value.split(':').length === 2) {
717 value = value.split(':')[1]
726 value = value.split(':')[1]
718 }
727 }
719
728
720 var searchType = data['type'];
729 var searchType = data['type'];
721 var valueDisplay = data['value_display'];
730 var valueDisplay = data['value_display'];
722
731
723 var pattern = '(' + escapeRegExChars(value) + ')';
732 var pattern = '(' + escapeRegExChars(value) + ')';
724
733
725 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
734 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
726
735
727 // highlight match
736 // highlight match
728 if (searchType != 'text') {
737 if (searchType != 'text') {
729 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
738 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
730 }
739 }
731
740
732 var icon = '';
741 var icon = '';
733
742
734 if (searchType === 'hint') {
743 if (searchType === 'hint') {
735 icon += '<i class="icon-folder-close"></i> ';
744 icon += '<i class="icon-folder-close"></i> ';
736 }
745 }
737 // full text search
746 // full text search
738 else if (searchType === 'search') {
747 else if (searchType === 'search') {
739 icon += '<i class="icon-more"></i> ';
748 icon += '<i class="icon-more"></i> ';
740 }
749 }
741 // repository
750 // repository
742 else if (searchType === 'repo') {
751 else if (searchType === 'repo') {
743
752
744 var repoIcon = getRepoIcon(data['repo_type']);
753 var repoIcon = getRepoIcon(data['repo_type']);
745 icon += repoIcon;
754 icon += repoIcon;
746
755
747 if (data['private']) {
756 if (data['private']) {
748 icon += '<i class="icon-lock" ></i> ';
757 icon += '<i class="icon-lock" ></i> ';
749 }
758 }
750 else if (visualShowPublicIcon) {
759 else if (visualShowPublicIcon) {
751 icon += '<i class="icon-unlock-alt"></i> ';
760 icon += '<i class="icon-unlock-alt"></i> ';
752 }
761 }
753 }
762 }
754 // repository groups
763 // repository groups
755 else if (searchType === 'repo_group') {
764 else if (searchType === 'repo_group') {
756 icon += '<i class="icon-folder-close"></i> ';
765 icon += '<i class="icon-folder-close"></i> ';
757 }
766 }
758 // user group
767 // user group
759 else if (searchType === 'user_group') {
768 else if (searchType === 'user_group') {
760 icon += '<i class="icon-group"></i> ';
769 icon += '<i class="icon-group"></i> ';
761 }
770 }
762 // user
771 // user
763 else if (searchType === 'user') {
772 else if (searchType === 'user') {
764 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
773 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
765 }
774 }
766 // commit
775 // commit
767 else if (searchType === 'commit') {
776 else if (searchType === 'commit') {
768 var repo_data = data['repo_data'];
777 var repo_data = data['repo_data'];
769 var repoIcon = getRepoIcon(repo_data['repository_type']);
778 var repoIcon = getRepoIcon(repo_data['repository_type']);
770 if (repoIcon) {
779 if (repoIcon) {
771 icon += repoIcon;
780 icon += repoIcon;
772 } else {
781 } else {
773 icon += '<i class="icon-tag"></i>';
782 icon += '<i class="icon-tag"></i>';
774 }
783 }
775 }
784 }
776 // file
785 // file
777 else if (searchType === 'file') {
786 else if (searchType === 'file') {
778 var repo_data = data['repo_data'];
787 var repo_data = data['repo_data'];
779 var repoIcon = getRepoIcon(repo_data['repository_type']);
788 var repoIcon = getRepoIcon(repo_data['repository_type']);
780 if (repoIcon) {
789 if (repoIcon) {
781 icon += repoIcon;
790 icon += repoIcon;
782 } else {
791 } else {
783 icon += '<i class="icon-tag"></i>';
792 icon += '<i class="icon-tag"></i>';
784 }
793 }
785 }
794 }
786 // generic text
795 // generic text
787 else if (searchType === 'text') {
796 else if (searchType === 'text') {
788 icon = '';
797 icon = '';
789 }
798 }
790
799
791 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
800 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
792 return tmpl.format(icon, valueDisplay);
801 return tmpl.format(icon, valueDisplay);
793 };
802 };
794
803
795 var handleSelect = function(element, suggestion) {
804 var handleSelect = function(element, suggestion) {
796 if (suggestion.type === "hint") {
805 if (suggestion.type === "hint") {
797 // we skip action
806 // we skip action
798 $('#main_filter').focus();
807 $('#main_filter').focus();
799 }
808 }
800 else if (suggestion.type === "text") {
809 else if (suggestion.type === "text") {
801 // we skip action
810 // we skip action
802 $('#main_filter').focus();
811 $('#main_filter').focus();
803
812
804 } else {
813 } else {
805 window.location = suggestion['url'];
814 window.location = suggestion['url'];
806 }
815 }
807 };
816 };
808
817
809 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
818 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
810 if (queryLowerCase.split(':').length === 2) {
819 if (queryLowerCase.split(':').length === 2) {
811 queryLowerCase = queryLowerCase.split(':')[1]
820 queryLowerCase = queryLowerCase.split(':')[1]
812 }
821 }
813 if (suggestion.type === "text") {
822 if (suggestion.type === "text") {
814 // special case we don't want to "skip" display for
823 // special case we don't want to "skip" display for
815 return true
824 return true
816 }
825 }
817 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
826 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
818 };
827 };
819
828
820 var cleanContext = {
829 var cleanContext = {
821 repo_view_type: null,
830 repo_view_type: null,
822
831
823 repo_id: null,
832 repo_id: null,
824 repo_name: "",
833 repo_name: "",
825
834
826 repo_group_id: null,
835 repo_group_id: null,
827 repo_group_name: null
836 repo_group_name: null
828 };
837 };
829 var removeGoToFilter = function () {
838 var removeGoToFilter = function () {
830 $('.searchTagHidable').hide();
839 $('.searchTagHidable').hide();
831 $('#main_filter').autocomplete(
840 $('#main_filter').autocomplete(
832 'setOptions', {params:{search_context: cleanContext}});
841 'setOptions', {params:{search_context: cleanContext}});
833 };
842 };
834
843
835 $('#main_filter').autocomplete({
844 $('#main_filter').autocomplete({
836 serviceUrl: pyroutes.url('goto_switcher_data'),
845 serviceUrl: pyroutes.url('goto_switcher_data'),
837 params: {
846 params: {
838 "search_context": templateContext.search_context
847 "search_context": templateContext.search_context
839 },
848 },
840 minChars:2,
849 minChars:2,
841 maxHeight:400,
850 maxHeight:400,
842 deferRequestBy: 300, //miliseconds
851 deferRequestBy: 300, //miliseconds
843 tabDisabled: true,
852 tabDisabled: true,
844 autoSelectFirst: false,
853 autoSelectFirst: false,
845 formatResult: autocompleteMainFilterFormatResult,
854 formatResult: autocompleteMainFilterFormatResult,
846 lookupFilter: autocompleteMainFilterResult,
855 lookupFilter: autocompleteMainFilterResult,
847 onSelect: function (element, suggestion) {
856 onSelect: function (element, suggestion) {
848 handleSelect(element, suggestion);
857 handleSelect(element, suggestion);
849 return false;
858 return false;
850 },
859 },
851 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
860 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
852 if (jqXHR !== 'abort') {
861 if (jqXHR !== 'abort') {
853 alert("Error during search.\nError code: {0}".format(textStatus));
862 alert("Error during search.\nError code: {0}".format(textStatus));
854 window.location = '';
863 window.location = '';
855 }
864 }
856 }
865 }
857 });
866 });
858
867
859 showMainFilterBox = function () {
868 showMainFilterBox = function () {
860 $('#main_filter_help').toggle();
869 $('#main_filter_help').toggle();
861 };
870 };
862
871
863 $('#main_filter').on('keydown.autocomplete', function (e) {
872 $('#main_filter').on('keydown.autocomplete', function (e) {
864
873
865 var BACKSPACE = 8;
874 var BACKSPACE = 8;
866 var el = $(e.currentTarget);
875 var el = $(e.currentTarget);
867 if(e.which === BACKSPACE){
876 if(e.which === BACKSPACE){
868 var inputVal = el.val();
877 var inputVal = el.val();
869 if (inputVal === ""){
878 if (inputVal === ""){
870 removeGoToFilter()
879 removeGoToFilter()
871 }
880 }
872 }
881 }
873 });
882 });
874
883
875 </script>
884 </script>
876 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
885 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
877 </%def>
886 </%def>
878
887
879 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
888 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
880 <div class="modal-dialog">
889 <div class="modal-dialog">
881 <div class="modal-content">
890 <div class="modal-content">
882 <div class="modal-header">
891 <div class="modal-header">
883 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
892 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
884 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
893 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
885 </div>
894 </div>
886 <div class="modal-body">
895 <div class="modal-body">
887 <div class="block-left">
896 <div class="block-left">
888 <table class="keyboard-mappings">
897 <table class="keyboard-mappings">
889 <tbody>
898 <tbody>
890 <tr>
899 <tr>
891 <th></th>
900 <th></th>
892 <th>${_('Site-wide shortcuts')}</th>
901 <th>${_('Site-wide shortcuts')}</th>
893 </tr>
902 </tr>
894 <%
903 <%
895 elems = [
904 elems = [
896 ('/', 'Use quick search box'),
905 ('/', 'Use quick search box'),
897 ('g h', 'Goto home page'),
906 ('g h', 'Goto home page'),
898 ('g g', 'Goto my private gists page'),
907 ('g g', 'Goto my private gists page'),
899 ('g G', 'Goto my public gists page'),
908 ('g G', 'Goto my public gists page'),
900 ('g 0-9', 'Goto bookmarked items from 0-9'),
909 ('g 0-9', 'Goto bookmarked items from 0-9'),
901 ('n r', 'New repository page'),
910 ('n r', 'New repository page'),
902 ('n g', 'New gist page'),
911 ('n g', 'New gist page'),
903 ]
912 ]
904 %>
913 %>
905 %for key, desc in elems:
914 %for key, desc in elems:
906 <tr>
915 <tr>
907 <td class="keys">
916 <td class="keys">
908 <span class="key tag">${key}</span>
917 <span class="key tag">${key}</span>
909 </td>
918 </td>
910 <td>${desc}</td>
919 <td>${desc}</td>
911 </tr>
920 </tr>
912 %endfor
921 %endfor
913 </tbody>
922 </tbody>
914 </table>
923 </table>
915 </div>
924 </div>
916 <div class="block-left">
925 <div class="block-left">
917 <table class="keyboard-mappings">
926 <table class="keyboard-mappings">
918 <tbody>
927 <tbody>
919 <tr>
928 <tr>
920 <th></th>
929 <th></th>
921 <th>${_('Repositories')}</th>
930 <th>${_('Repositories')}</th>
922 </tr>
931 </tr>
923 <%
932 <%
924 elems = [
933 elems = [
925 ('g s', 'Goto summary page'),
934 ('g s', 'Goto summary page'),
926 ('g c', 'Goto changelog page'),
935 ('g c', 'Goto changelog page'),
927 ('g f', 'Goto files page'),
936 ('g f', 'Goto files page'),
928 ('g F', 'Goto files page with file search activated'),
937 ('g F', 'Goto files page with file search activated'),
929 ('g p', 'Goto pull requests page'),
938 ('g p', 'Goto pull requests page'),
930 ('g o', 'Goto repository settings'),
939 ('g o', 'Goto repository settings'),
931 ('g O', 'Goto repository permissions settings'),
940 ('g O', 'Goto repository permissions settings'),
932 ]
941 ]
933 %>
942 %>
934 %for key, desc in elems:
943 %for key, desc in elems:
935 <tr>
944 <tr>
936 <td class="keys">
945 <td class="keys">
937 <span class="key tag">${key}</span>
946 <span class="key tag">${key}</span>
938 </td>
947 </td>
939 <td>${desc}</td>
948 <td>${desc}</td>
940 </tr>
949 </tr>
941 %endfor
950 %endfor
942 </tbody>
951 </tbody>
943 </table>
952 </table>
944 </div>
953 </div>
945 </div>
954 </div>
946 <div class="modal-footer">
955 <div class="modal-footer">
947 </div>
956 </div>
948 </div><!-- /.modal-content -->
957 </div><!-- /.modal-content -->
949 </div><!-- /.modal-dialog -->
958 </div><!-- /.modal-dialog -->
950 </div><!-- /.modal -->
959 </div><!-- /.modal -->
951
960
General Comments 0
You need to be logged in to leave comments. Login now