##// END OF EJS Templates
repositories: use remote function to check if repo is empty...
marcink -
r3723:267daa27 new-ui
parent child Browse files
Show More
@@ -1,725 +1,728 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(
240 c.repository_is_user_following = ScmModel().is_following_repo(
241 self.db_repo_name, self._rhodecode_user.user_id)
241 self.db_repo_name, self._rhodecode_user.user_id)
242 self.path_filter = PathFilter(None)
242 self.path_filter = PathFilter(None)
243
243
244 c.repository_requirements_missing = {}
244 c.repository_requirements_missing = {}
245 try:
245 try:
246 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
246 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
247 if self.rhodecode_vcs_repo:
247 # NOTE(marcink):
248 # comparison to None since if it's an object __bool__ is expensive to
249 # calculate
250 if self.rhodecode_vcs_repo is not None:
248 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
251 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
249 c.auth_user.username)
252 c.auth_user.username)
250 self.path_filter = PathFilter(path_perms)
253 self.path_filter = PathFilter(path_perms)
251 except RepositoryRequirementError as e:
254 except RepositoryRequirementError as e:
252 c.repository_requirements_missing = {'error': str(e)}
255 c.repository_requirements_missing = {'error': str(e)}
253 self._handle_missing_requirements(e)
256 self._handle_missing_requirements(e)
254 self.rhodecode_vcs_repo = None
257 self.rhodecode_vcs_repo = None
255
258
256 c.path_filter = self.path_filter # used by atom_feed_entry.mako
259 c.path_filter = self.path_filter # used by atom_feed_entry.mako
257
260
258 if self.rhodecode_vcs_repo is None:
261 if self.rhodecode_vcs_repo is None:
259 # unable to fetch this repo as vcs instance, report back to user
262 # unable to fetch this repo as vcs instance, report back to user
260 h.flash(_(
263 h.flash(_(
261 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
264 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
262 "Please check if it exist, or is not damaged.") %
265 "Please check if it exist, or is not damaged.") %
263 {'repo_name': c.repo_name},
266 {'repo_name': c.repo_name},
264 category='error', ignore_duplicate=True)
267 category='error', ignore_duplicate=True)
265 if c.repository_requirements_missing:
268 if c.repository_requirements_missing:
266 route = self.request.matched_route.name
269 route = self.request.matched_route.name
267 if route.startswith(('edit_repo', 'repo_summary')):
270 if route.startswith(('edit_repo', 'repo_summary')):
268 # allow summary and edit repo on missing requirements
271 # allow summary and edit repo on missing requirements
269 return c
272 return c
270
273
271 raise HTTPFound(
274 raise HTTPFound(
272 h.route_path('repo_summary', repo_name=self.db_repo_name))
275 h.route_path('repo_summary', repo_name=self.db_repo_name))
273
276
274 else: # redirect if we don't show missing requirements
277 else: # redirect if we don't show missing requirements
275 raise HTTPFound(h.route_path('home'))
278 raise HTTPFound(h.route_path('home'))
276
279
277 c.has_origin_repo_read_perm = False
280 c.has_origin_repo_read_perm = False
278 if self.db_repo.fork:
281 if self.db_repo.fork:
279 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
282 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
280 'repository.write', 'repository.read', 'repository.admin')(
283 'repository.write', 'repository.read', 'repository.admin')(
281 self.db_repo.fork.repo_name, 'summary fork link')
284 self.db_repo.fork.repo_name, 'summary fork link')
282
285
283 return c
286 return c
284
287
285 def _get_f_path_unchecked(self, matchdict, default=None):
288 def _get_f_path_unchecked(self, matchdict, default=None):
286 """
289 """
287 Should only be used by redirects, everything else should call _get_f_path
290 Should only be used by redirects, everything else should call _get_f_path
288 """
291 """
289 f_path = matchdict.get('f_path')
292 f_path = matchdict.get('f_path')
290 if f_path:
293 if f_path:
291 # fix for multiple initial slashes that causes errors for GIT
294 # fix for multiple initial slashes that causes errors for GIT
292 return f_path.lstrip('/')
295 return f_path.lstrip('/')
293
296
294 return default
297 return default
295
298
296 def _get_f_path(self, matchdict, default=None):
299 def _get_f_path(self, matchdict, default=None):
297 f_path_match = self._get_f_path_unchecked(matchdict, default)
300 f_path_match = self._get_f_path_unchecked(matchdict, default)
298 return self.path_filter.assert_path_permissions(f_path_match)
301 return self.path_filter.assert_path_permissions(f_path_match)
299
302
300 def _get_general_setting(self, target_repo, settings_key, default=False):
303 def _get_general_setting(self, target_repo, settings_key, default=False):
301 settings_model = VcsSettingsModel(repo=target_repo)
304 settings_model = VcsSettingsModel(repo=target_repo)
302 settings = settings_model.get_general_settings()
305 settings = settings_model.get_general_settings()
303 return settings.get(settings_key, default)
306 return settings.get(settings_key, default)
304
307
305 def get_recache_flag(self):
308 def get_recache_flag(self):
306 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
309 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
307 flag_val = self.request.GET.get(flag_name)
310 flag_val = self.request.GET.get(flag_name)
308 if str2bool(flag_val):
311 if str2bool(flag_val):
309 return True
312 return True
310 return False
313 return False
311
314
312
315
313 class PathFilter(object):
316 class PathFilter(object):
314
317
315 # Expects and instance of BasePathPermissionChecker or None
318 # Expects and instance of BasePathPermissionChecker or None
316 def __init__(self, permission_checker):
319 def __init__(self, permission_checker):
317 self.permission_checker = permission_checker
320 self.permission_checker = permission_checker
318
321
319 def assert_path_permissions(self, path):
322 def assert_path_permissions(self, path):
320 if path and self.permission_checker and not self.permission_checker.has_access(path):
323 if path and self.permission_checker and not self.permission_checker.has_access(path):
321 raise HTTPForbidden()
324 raise HTTPForbidden()
322 return path
325 return path
323
326
324 def filter_patchset(self, patchset):
327 def filter_patchset(self, patchset):
325 if not self.permission_checker or not patchset:
328 if not self.permission_checker or not patchset:
326 return patchset, False
329 return patchset, False
327 had_filtered = False
330 had_filtered = False
328 filtered_patchset = []
331 filtered_patchset = []
329 for patch in patchset:
332 for patch in patchset:
330 filename = patch.get('filename', None)
333 filename = patch.get('filename', None)
331 if not filename or self.permission_checker.has_access(filename):
334 if not filename or self.permission_checker.has_access(filename):
332 filtered_patchset.append(patch)
335 filtered_patchset.append(patch)
333 else:
336 else:
334 had_filtered = True
337 had_filtered = True
335 if had_filtered:
338 if had_filtered:
336 if isinstance(patchset, diffs.LimitedDiffContainer):
339 if isinstance(patchset, diffs.LimitedDiffContainer):
337 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
340 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
338 return filtered_patchset, True
341 return filtered_patchset, True
339 else:
342 else:
340 return patchset, False
343 return patchset, False
341
344
342 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
345 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
343 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
346 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
344 result = diffset.render_patchset(
347 result = diffset.render_patchset(
345 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
348 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
346 result.has_hidden_changes = has_hidden_changes
349 result.has_hidden_changes = has_hidden_changes
347 return result
350 return result
348
351
349 def get_raw_patch(self, diff_processor):
352 def get_raw_patch(self, diff_processor):
350 if self.permission_checker is None:
353 if self.permission_checker is None:
351 return diff_processor.as_raw()
354 return diff_processor.as_raw()
352 elif self.permission_checker.has_full_access:
355 elif self.permission_checker.has_full_access:
353 return diff_processor.as_raw()
356 return diff_processor.as_raw()
354 else:
357 else:
355 return '# Repository has user-specific filters, raw patch generation is disabled.'
358 return '# Repository has user-specific filters, raw patch generation is disabled.'
356
359
357 @property
360 @property
358 def is_enabled(self):
361 def is_enabled(self):
359 return self.permission_checker is not None
362 return self.permission_checker is not None
360
363
361
364
362 class RepoGroupAppView(BaseAppView):
365 class RepoGroupAppView(BaseAppView):
363 def __init__(self, context, request):
366 def __init__(self, context, request):
364 super(RepoGroupAppView, self).__init__(context, request)
367 super(RepoGroupAppView, self).__init__(context, request)
365 self.db_repo_group = request.db_repo_group
368 self.db_repo_group = request.db_repo_group
366 self.db_repo_group_name = self.db_repo_group.group_name
369 self.db_repo_group_name = self.db_repo_group.group_name
367
370
368 def _get_local_tmpl_context(self, include_app_defaults=True):
371 def _get_local_tmpl_context(self, include_app_defaults=True):
369 _ = self.request.translate
372 _ = self.request.translate
370 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
373 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
371 include_app_defaults=include_app_defaults)
374 include_app_defaults=include_app_defaults)
372 c.repo_group = self.db_repo_group
375 c.repo_group = self.db_repo_group
373 return c
376 return c
374
377
375 def _revoke_perms_on_yourself(self, form_result):
378 def _revoke_perms_on_yourself(self, form_result):
376 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
379 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
377 form_result['perm_updates'])
380 form_result['perm_updates'])
378 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
381 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
379 form_result['perm_additions'])
382 form_result['perm_additions'])
380 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
383 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
381 form_result['perm_deletions'])
384 form_result['perm_deletions'])
382 admin_perm = 'group.admin'
385 admin_perm = 'group.admin'
383 if _updates and _updates[0][1] != admin_perm or \
386 if _updates and _updates[0][1] != admin_perm or \
384 _additions and _additions[0][1] != admin_perm or \
387 _additions and _additions[0][1] != admin_perm or \
385 _deletions and _deletions[0][1] != admin_perm:
388 _deletions and _deletions[0][1] != admin_perm:
386 return True
389 return True
387 return False
390 return False
388
391
389
392
390 class UserGroupAppView(BaseAppView):
393 class UserGroupAppView(BaseAppView):
391 def __init__(self, context, request):
394 def __init__(self, context, request):
392 super(UserGroupAppView, self).__init__(context, request)
395 super(UserGroupAppView, self).__init__(context, request)
393 self.db_user_group = request.db_user_group
396 self.db_user_group = request.db_user_group
394 self.db_user_group_name = self.db_user_group.users_group_name
397 self.db_user_group_name = self.db_user_group.users_group_name
395
398
396
399
397 class UserAppView(BaseAppView):
400 class UserAppView(BaseAppView):
398 def __init__(self, context, request):
401 def __init__(self, context, request):
399 super(UserAppView, self).__init__(context, request)
402 super(UserAppView, self).__init__(context, request)
400 self.db_user = request.db_user
403 self.db_user = request.db_user
401 self.db_user_id = self.db_user.user_id
404 self.db_user_id = self.db_user.user_id
402
405
403 _ = self.request.translate
406 _ = self.request.translate
404 if not request.db_user_supports_default:
407 if not request.db_user_supports_default:
405 if self.db_user.username == User.DEFAULT_USER:
408 if self.db_user.username == User.DEFAULT_USER:
406 h.flash(_("Editing user `{}` is disabled.".format(
409 h.flash(_("Editing user `{}` is disabled.".format(
407 User.DEFAULT_USER)), category='warning')
410 User.DEFAULT_USER)), category='warning')
408 raise HTTPFound(h.route_path('users'))
411 raise HTTPFound(h.route_path('users'))
409
412
410
413
411 class DataGridAppView(object):
414 class DataGridAppView(object):
412 """
415 """
413 Common class to have re-usable grid rendering components
416 Common class to have re-usable grid rendering components
414 """
417 """
415
418
416 def _extract_ordering(self, request, column_map=None):
419 def _extract_ordering(self, request, column_map=None):
417 column_map = column_map or {}
420 column_map = column_map or {}
418 column_index = safe_int(request.GET.get('order[0][column]'))
421 column_index = safe_int(request.GET.get('order[0][column]'))
419 order_dir = request.GET.get(
422 order_dir = request.GET.get(
420 'order[0][dir]', 'desc')
423 'order[0][dir]', 'desc')
421 order_by = request.GET.get(
424 order_by = request.GET.get(
422 'columns[%s][data][sort]' % column_index, 'name_raw')
425 'columns[%s][data][sort]' % column_index, 'name_raw')
423
426
424 # translate datatable to DB columns
427 # translate datatable to DB columns
425 order_by = column_map.get(order_by) or order_by
428 order_by = column_map.get(order_by) or order_by
426
429
427 search_q = request.GET.get('search[value]')
430 search_q = request.GET.get('search[value]')
428 return search_q, order_by, order_dir
431 return search_q, order_by, order_dir
429
432
430 def _extract_chunk(self, request):
433 def _extract_chunk(self, request):
431 start = safe_int(request.GET.get('start'), 0)
434 start = safe_int(request.GET.get('start'), 0)
432 length = safe_int(request.GET.get('length'), 25)
435 length = safe_int(request.GET.get('length'), 25)
433 draw = safe_int(request.GET.get('draw'))
436 draw = safe_int(request.GET.get('draw'))
434 return draw, start, length
437 return draw, start, length
435
438
436 def _get_order_col(self, order_by, model):
439 def _get_order_col(self, order_by, model):
437 if isinstance(order_by, compat.string_types):
440 if isinstance(order_by, compat.string_types):
438 try:
441 try:
439 return operator.attrgetter(order_by)(model)
442 return operator.attrgetter(order_by)(model)
440 except AttributeError:
443 except AttributeError:
441 return None
444 return None
442 else:
445 else:
443 return order_by
446 return order_by
444
447
445
448
446 class BaseReferencesView(RepoAppView):
449 class BaseReferencesView(RepoAppView):
447 """
450 """
448 Base for reference view for branches, tags and bookmarks.
451 Base for reference view for branches, tags and bookmarks.
449 """
452 """
450 def load_default_context(self):
453 def load_default_context(self):
451 c = self._get_local_tmpl_context()
454 c = self._get_local_tmpl_context()
452
455
453
456
454 return c
457 return c
455
458
456 def load_refs_context(self, ref_items, partials_template):
459 def load_refs_context(self, ref_items, partials_template):
457 _render = self.request.get_partial_renderer(partials_template)
460 _render = self.request.get_partial_renderer(partials_template)
458 pre_load = ["author", "date", "message"]
461 pre_load = ["author", "date", "message"]
459
462
460 is_svn = h.is_svn(self.rhodecode_vcs_repo)
463 is_svn = h.is_svn(self.rhodecode_vcs_repo)
461 is_hg = h.is_hg(self.rhodecode_vcs_repo)
464 is_hg = h.is_hg(self.rhodecode_vcs_repo)
462
465
463 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
466 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
464
467
465 closed_refs = {}
468 closed_refs = {}
466 if is_hg:
469 if is_hg:
467 closed_refs = self.rhodecode_vcs_repo.branches_closed
470 closed_refs = self.rhodecode_vcs_repo.branches_closed
468
471
469 data = []
472 data = []
470 for ref_name, commit_id in ref_items:
473 for ref_name, commit_id in ref_items:
471 commit = self.rhodecode_vcs_repo.get_commit(
474 commit = self.rhodecode_vcs_repo.get_commit(
472 commit_id=commit_id, pre_load=pre_load)
475 commit_id=commit_id, pre_load=pre_load)
473 closed = ref_name in closed_refs
476 closed = ref_name in closed_refs
474
477
475 # TODO: johbo: Unify generation of reference links
478 # TODO: johbo: Unify generation of reference links
476 use_commit_id = '/' in ref_name or is_svn
479 use_commit_id = '/' in ref_name or is_svn
477
480
478 if use_commit_id:
481 if use_commit_id:
479 files_url = h.route_path(
482 files_url = h.route_path(
480 'repo_files',
483 'repo_files',
481 repo_name=self.db_repo_name,
484 repo_name=self.db_repo_name,
482 f_path=ref_name if is_svn else '',
485 f_path=ref_name if is_svn else '',
483 commit_id=commit_id)
486 commit_id=commit_id)
484
487
485 else:
488 else:
486 files_url = h.route_path(
489 files_url = h.route_path(
487 'repo_files',
490 'repo_files',
488 repo_name=self.db_repo_name,
491 repo_name=self.db_repo_name,
489 f_path=ref_name if is_svn else '',
492 f_path=ref_name if is_svn else '',
490 commit_id=ref_name,
493 commit_id=ref_name,
491 _query=dict(at=ref_name))
494 _query=dict(at=ref_name))
492
495
493 data.append({
496 data.append({
494 "name": _render('name', ref_name, files_url, closed),
497 "name": _render('name', ref_name, files_url, closed),
495 "name_raw": ref_name,
498 "name_raw": ref_name,
496 "date": _render('date', commit.date),
499 "date": _render('date', commit.date),
497 "date_raw": datetime_to_time(commit.date),
500 "date_raw": datetime_to_time(commit.date),
498 "author": _render('author', commit.author),
501 "author": _render('author', commit.author),
499 "commit": _render(
502 "commit": _render(
500 'commit', commit.message, commit.raw_id, commit.idx),
503 'commit', commit.message, commit.raw_id, commit.idx),
501 "commit_raw": commit.idx,
504 "commit_raw": commit.idx,
502 "compare": _render(
505 "compare": _render(
503 'compare', format_ref_id(ref_name, commit.raw_id)),
506 'compare', format_ref_id(ref_name, commit.raw_id)),
504 })
507 })
505
508
506 return data
509 return data
507
510
508
511
509 class RepoRoutePredicate(object):
512 class RepoRoutePredicate(object):
510 def __init__(self, val, config):
513 def __init__(self, val, config):
511 self.val = val
514 self.val = val
512
515
513 def text(self):
516 def text(self):
514 return 'repo_route = %s' % self.val
517 return 'repo_route = %s' % self.val
515
518
516 phash = text
519 phash = text
517
520
518 def __call__(self, info, request):
521 def __call__(self, info, request):
519 if hasattr(request, 'vcs_call'):
522 if hasattr(request, 'vcs_call'):
520 # skip vcs calls
523 # skip vcs calls
521 return
524 return
522
525
523 repo_name = info['match']['repo_name']
526 repo_name = info['match']['repo_name']
524 repo_model = repo.RepoModel()
527 repo_model = repo.RepoModel()
525
528
526 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
529 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
527
530
528 def redirect_if_creating(route_info, db_repo):
531 def redirect_if_creating(route_info, db_repo):
529 skip_views = ['edit_repo_advanced_delete']
532 skip_views = ['edit_repo_advanced_delete']
530 route = route_info['route']
533 route = route_info['route']
531 # we should skip delete view so we can actually "remove" repositories
534 # we should skip delete view so we can actually "remove" repositories
532 # if they get stuck in creating state.
535 # if they get stuck in creating state.
533 if route.name in skip_views:
536 if route.name in skip_views:
534 return
537 return
535
538
536 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
539 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
537 repo_creating_url = request.route_path(
540 repo_creating_url = request.route_path(
538 'repo_creating', repo_name=db_repo.repo_name)
541 'repo_creating', repo_name=db_repo.repo_name)
539 raise HTTPFound(repo_creating_url)
542 raise HTTPFound(repo_creating_url)
540
543
541 if by_name_match:
544 if by_name_match:
542 # register this as request object we can re-use later
545 # register this as request object we can re-use later
543 request.db_repo = by_name_match
546 request.db_repo = by_name_match
544 redirect_if_creating(info, by_name_match)
547 redirect_if_creating(info, by_name_match)
545 return True
548 return True
546
549
547 by_id_match = repo_model.get_repo_by_id(repo_name)
550 by_id_match = repo_model.get_repo_by_id(repo_name)
548 if by_id_match:
551 if by_id_match:
549 request.db_repo = by_id_match
552 request.db_repo = by_id_match
550 redirect_if_creating(info, by_id_match)
553 redirect_if_creating(info, by_id_match)
551 return True
554 return True
552
555
553 return False
556 return False
554
557
555
558
556 class RepoForbidArchivedRoutePredicate(object):
559 class RepoForbidArchivedRoutePredicate(object):
557 def __init__(self, val, config):
560 def __init__(self, val, config):
558 self.val = val
561 self.val = val
559
562
560 def text(self):
563 def text(self):
561 return 'repo_forbid_archived = %s' % self.val
564 return 'repo_forbid_archived = %s' % self.val
562
565
563 phash = text
566 phash = text
564
567
565 def __call__(self, info, request):
568 def __call__(self, info, request):
566 _ = request.translate
569 _ = request.translate
567 rhodecode_db_repo = request.db_repo
570 rhodecode_db_repo = request.db_repo
568
571
569 log.debug(
572 log.debug(
570 '%s checking if archived flag for repo for %s',
573 '%s checking if archived flag for repo for %s',
571 self.__class__.__name__, rhodecode_db_repo.repo_name)
574 self.__class__.__name__, rhodecode_db_repo.repo_name)
572
575
573 if rhodecode_db_repo.archived:
576 if rhodecode_db_repo.archived:
574 log.warning('Current view is not supported for archived repo:%s',
577 log.warning('Current view is not supported for archived repo:%s',
575 rhodecode_db_repo.repo_name)
578 rhodecode_db_repo.repo_name)
576
579
577 h.flash(
580 h.flash(
578 h.literal(_('Action not supported for archived repository.')),
581 h.literal(_('Action not supported for archived repository.')),
579 category='warning')
582 category='warning')
580 summary_url = request.route_path(
583 summary_url = request.route_path(
581 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
584 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
582 raise HTTPFound(summary_url)
585 raise HTTPFound(summary_url)
583 return True
586 return True
584
587
585
588
586 class RepoTypeRoutePredicate(object):
589 class RepoTypeRoutePredicate(object):
587 def __init__(self, val, config):
590 def __init__(self, val, config):
588 self.val = val or ['hg', 'git', 'svn']
591 self.val = val or ['hg', 'git', 'svn']
589
592
590 def text(self):
593 def text(self):
591 return 'repo_accepted_type = %s' % self.val
594 return 'repo_accepted_type = %s' % self.val
592
595
593 phash = text
596 phash = text
594
597
595 def __call__(self, info, request):
598 def __call__(self, info, request):
596 if hasattr(request, 'vcs_call'):
599 if hasattr(request, 'vcs_call'):
597 # skip vcs calls
600 # skip vcs calls
598 return
601 return
599
602
600 rhodecode_db_repo = request.db_repo
603 rhodecode_db_repo = request.db_repo
601
604
602 log.debug(
605 log.debug(
603 '%s checking repo type for %s in %s',
606 '%s checking repo type for %s in %s',
604 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
607 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
605
608
606 if rhodecode_db_repo.repo_type in self.val:
609 if rhodecode_db_repo.repo_type in self.val:
607 return True
610 return True
608 else:
611 else:
609 log.warning('Current view is not supported for repo type:%s',
612 log.warning('Current view is not supported for repo type:%s',
610 rhodecode_db_repo.repo_type)
613 rhodecode_db_repo.repo_type)
611 return False
614 return False
612
615
613
616
614 class RepoGroupRoutePredicate(object):
617 class RepoGroupRoutePredicate(object):
615 def __init__(self, val, config):
618 def __init__(self, val, config):
616 self.val = val
619 self.val = val
617
620
618 def text(self):
621 def text(self):
619 return 'repo_group_route = %s' % self.val
622 return 'repo_group_route = %s' % self.val
620
623
621 phash = text
624 phash = text
622
625
623 def __call__(self, info, request):
626 def __call__(self, info, request):
624 if hasattr(request, 'vcs_call'):
627 if hasattr(request, 'vcs_call'):
625 # skip vcs calls
628 # skip vcs calls
626 return
629 return
627
630
628 repo_group_name = info['match']['repo_group_name']
631 repo_group_name = info['match']['repo_group_name']
629 repo_group_model = repo_group.RepoGroupModel()
632 repo_group_model = repo_group.RepoGroupModel()
630 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
633 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
631
634
632 if by_name_match:
635 if by_name_match:
633 # register this as request object we can re-use later
636 # register this as request object we can re-use later
634 request.db_repo_group = by_name_match
637 request.db_repo_group = by_name_match
635 return True
638 return True
636
639
637 return False
640 return False
638
641
639
642
640 class UserGroupRoutePredicate(object):
643 class UserGroupRoutePredicate(object):
641 def __init__(self, val, config):
644 def __init__(self, val, config):
642 self.val = val
645 self.val = val
643
646
644 def text(self):
647 def text(self):
645 return 'user_group_route = %s' % self.val
648 return 'user_group_route = %s' % self.val
646
649
647 phash = text
650 phash = text
648
651
649 def __call__(self, info, request):
652 def __call__(self, info, request):
650 if hasattr(request, 'vcs_call'):
653 if hasattr(request, 'vcs_call'):
651 # skip vcs calls
654 # skip vcs calls
652 return
655 return
653
656
654 user_group_id = info['match']['user_group_id']
657 user_group_id = info['match']['user_group_id']
655 user_group_model = user_group.UserGroup()
658 user_group_model = user_group.UserGroup()
656 by_id_match = user_group_model.get(user_group_id, cache=False)
659 by_id_match = user_group_model.get(user_group_id, cache=False)
657
660
658 if by_id_match:
661 if by_id_match:
659 # register this as request object we can re-use later
662 # register this as request object we can re-use later
660 request.db_user_group = by_id_match
663 request.db_user_group = by_id_match
661 return True
664 return True
662
665
663 return False
666 return False
664
667
665
668
666 class UserRoutePredicateBase(object):
669 class UserRoutePredicateBase(object):
667 supports_default = None
670 supports_default = None
668
671
669 def __init__(self, val, config):
672 def __init__(self, val, config):
670 self.val = val
673 self.val = val
671
674
672 def text(self):
675 def text(self):
673 raise NotImplementedError()
676 raise NotImplementedError()
674
677
675 def __call__(self, info, request):
678 def __call__(self, info, request):
676 if hasattr(request, 'vcs_call'):
679 if hasattr(request, 'vcs_call'):
677 # skip vcs calls
680 # skip vcs calls
678 return
681 return
679
682
680 user_id = info['match']['user_id']
683 user_id = info['match']['user_id']
681 user_model = user.User()
684 user_model = user.User()
682 by_id_match = user_model.get(user_id, cache=False)
685 by_id_match = user_model.get(user_id, cache=False)
683
686
684 if by_id_match:
687 if by_id_match:
685 # register this as request object we can re-use later
688 # register this as request object we can re-use later
686 request.db_user = by_id_match
689 request.db_user = by_id_match
687 request.db_user_supports_default = self.supports_default
690 request.db_user_supports_default = self.supports_default
688 return True
691 return True
689
692
690 return False
693 return False
691
694
692
695
693 class UserRoutePredicate(UserRoutePredicateBase):
696 class UserRoutePredicate(UserRoutePredicateBase):
694 supports_default = False
697 supports_default = False
695
698
696 def text(self):
699 def text(self):
697 return 'user_route = %s' % self.val
700 return 'user_route = %s' % self.val
698
701
699 phash = text
702 phash = text
700
703
701
704
702 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
705 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
703 supports_default = True
706 supports_default = True
704
707
705 def text(self):
708 def text(self):
706 return 'user_with_default_route = %s' % self.val
709 return 'user_with_default_route = %s' % self.val
707
710
708 phash = text
711 phash = text
709
712
710
713
711 def includeme(config):
714 def includeme(config):
712 config.add_route_predicate(
715 config.add_route_predicate(
713 'repo_route', RepoRoutePredicate)
716 'repo_route', RepoRoutePredicate)
714 config.add_route_predicate(
717 config.add_route_predicate(
715 'repo_accepted_types', RepoTypeRoutePredicate)
718 'repo_accepted_types', RepoTypeRoutePredicate)
716 config.add_route_predicate(
719 config.add_route_predicate(
717 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
720 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
718 config.add_route_predicate(
721 config.add_route_predicate(
719 'repo_group_route', RepoGroupRoutePredicate)
722 'repo_group_route', RepoGroupRoutePredicate)
720 config.add_route_predicate(
723 config.add_route_predicate(
721 'user_group_route', UserGroupRoutePredicate)
724 'user_group_route', UserGroupRoutePredicate)
722 config.add_route_predicate(
725 config.add_route_predicate(
723 'user_route_with_default', UserRouteWithDefaultPredicate)
726 'user_route_with_default', UserRouteWithDefaultPredicate)
724 config.add_route_predicate(
727 config.add_route_predicate(
725 'user_route', UserRoutePredicate)
728 'user_route', UserRoutePredicate)
@@ -1,1851 +1,1851 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2019 RhodeCode GmbH
3 # Copyright (C) 2014-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 Base module for all VCS systems
22 Base module for all VCS systems
23 """
23 """
24 import os
24 import os
25 import re
25 import re
26 import time
26 import time
27 import shutil
27 import shutil
28 import datetime
28 import datetime
29 import fnmatch
29 import fnmatch
30 import itertools
30 import itertools
31 import logging
31 import logging
32 import collections
32 import collections
33 import warnings
33 import warnings
34
34
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 from pyramid import compat
36 from pyramid import compat
37
37
38 from rhodecode.translation import lazy_ugettext
38 from rhodecode.translation import lazy_ugettext
39 from rhodecode.lib.utils2 import safe_str, safe_unicode
39 from rhodecode.lib.utils2 import safe_str, safe_unicode
40 from rhodecode.lib.vcs import connection
40 from rhodecode.lib.vcs import connection
41 from rhodecode.lib.vcs.utils import author_name, author_email
41 from rhodecode.lib.vcs.utils import author_name, author_email
42 from rhodecode.lib.vcs.conf import settings
42 from rhodecode.lib.vcs.conf import settings
43 from rhodecode.lib.vcs.exceptions import (
43 from rhodecode.lib.vcs.exceptions import (
44 CommitError, EmptyRepositoryError, NodeAlreadyAddedError,
44 CommitError, EmptyRepositoryError, NodeAlreadyAddedError,
45 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
45 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
46 NodeDoesNotExistError, NodeNotChangedError, VCSError,
46 NodeDoesNotExistError, NodeNotChangedError, VCSError,
47 ImproperArchiveTypeError, BranchDoesNotExistError, CommitDoesNotExistError,
47 ImproperArchiveTypeError, BranchDoesNotExistError, CommitDoesNotExistError,
48 RepositoryError)
48 RepositoryError)
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 FILEMODE_DEFAULT = 0o100644
54 FILEMODE_DEFAULT = 0o100644
55 FILEMODE_EXECUTABLE = 0o100755
55 FILEMODE_EXECUTABLE = 0o100755
56
56
57 Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id'))
57 Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id'))
58
58
59
59
60 class MergeFailureReason(object):
60 class MergeFailureReason(object):
61 """
61 """
62 Enumeration with all the reasons why the server side merge could fail.
62 Enumeration with all the reasons why the server side merge could fail.
63
63
64 DO NOT change the number of the reasons, as they may be stored in the
64 DO NOT change the number of the reasons, as they may be stored in the
65 database.
65 database.
66
66
67 Changing the name of a reason is acceptable and encouraged to deprecate old
67 Changing the name of a reason is acceptable and encouraged to deprecate old
68 reasons.
68 reasons.
69 """
69 """
70
70
71 # Everything went well.
71 # Everything went well.
72 NONE = 0
72 NONE = 0
73
73
74 # An unexpected exception was raised. Check the logs for more details.
74 # An unexpected exception was raised. Check the logs for more details.
75 UNKNOWN = 1
75 UNKNOWN = 1
76
76
77 # The merge was not successful, there are conflicts.
77 # The merge was not successful, there are conflicts.
78 MERGE_FAILED = 2
78 MERGE_FAILED = 2
79
79
80 # The merge succeeded but we could not push it to the target repository.
80 # The merge succeeded but we could not push it to the target repository.
81 PUSH_FAILED = 3
81 PUSH_FAILED = 3
82
82
83 # The specified target is not a head in the target repository.
83 # The specified target is not a head in the target repository.
84 TARGET_IS_NOT_HEAD = 4
84 TARGET_IS_NOT_HEAD = 4
85
85
86 # The source repository contains more branches than the target. Pushing
86 # The source repository contains more branches than the target. Pushing
87 # the merge will create additional branches in the target.
87 # the merge will create additional branches in the target.
88 HG_SOURCE_HAS_MORE_BRANCHES = 5
88 HG_SOURCE_HAS_MORE_BRANCHES = 5
89
89
90 # The target reference has multiple heads. That does not allow to correctly
90 # The target reference has multiple heads. That does not allow to correctly
91 # identify the target location. This could only happen for mercurial
91 # identify the target location. This could only happen for mercurial
92 # branches.
92 # branches.
93 HG_TARGET_HAS_MULTIPLE_HEADS = 6
93 HG_TARGET_HAS_MULTIPLE_HEADS = 6
94
94
95 # The target repository is locked
95 # The target repository is locked
96 TARGET_IS_LOCKED = 7
96 TARGET_IS_LOCKED = 7
97
97
98 # Deprecated, use MISSING_TARGET_REF or MISSING_SOURCE_REF instead.
98 # Deprecated, use MISSING_TARGET_REF or MISSING_SOURCE_REF instead.
99 # A involved commit could not be found.
99 # A involved commit could not be found.
100 _DEPRECATED_MISSING_COMMIT = 8
100 _DEPRECATED_MISSING_COMMIT = 8
101
101
102 # The target repo reference is missing.
102 # The target repo reference is missing.
103 MISSING_TARGET_REF = 9
103 MISSING_TARGET_REF = 9
104
104
105 # The source repo reference is missing.
105 # The source repo reference is missing.
106 MISSING_SOURCE_REF = 10
106 MISSING_SOURCE_REF = 10
107
107
108 # The merge was not successful, there are conflicts related to sub
108 # The merge was not successful, there are conflicts related to sub
109 # repositories.
109 # repositories.
110 SUBREPO_MERGE_FAILED = 11
110 SUBREPO_MERGE_FAILED = 11
111
111
112
112
113 class UpdateFailureReason(object):
113 class UpdateFailureReason(object):
114 """
114 """
115 Enumeration with all the reasons why the pull request update could fail.
115 Enumeration with all the reasons why the pull request update could fail.
116
116
117 DO NOT change the number of the reasons, as they may be stored in the
117 DO NOT change the number of the reasons, as they may be stored in the
118 database.
118 database.
119
119
120 Changing the name of a reason is acceptable and encouraged to deprecate old
120 Changing the name of a reason is acceptable and encouraged to deprecate old
121 reasons.
121 reasons.
122 """
122 """
123
123
124 # Everything went well.
124 # Everything went well.
125 NONE = 0
125 NONE = 0
126
126
127 # An unexpected exception was raised. Check the logs for more details.
127 # An unexpected exception was raised. Check the logs for more details.
128 UNKNOWN = 1
128 UNKNOWN = 1
129
129
130 # The pull request is up to date.
130 # The pull request is up to date.
131 NO_CHANGE = 2
131 NO_CHANGE = 2
132
132
133 # The pull request has a reference type that is not supported for update.
133 # The pull request has a reference type that is not supported for update.
134 WRONG_REF_TYPE = 3
134 WRONG_REF_TYPE = 3
135
135
136 # Update failed because the target reference is missing.
136 # Update failed because the target reference is missing.
137 MISSING_TARGET_REF = 4
137 MISSING_TARGET_REF = 4
138
138
139 # Update failed because the source reference is missing.
139 # Update failed because the source reference is missing.
140 MISSING_SOURCE_REF = 5
140 MISSING_SOURCE_REF = 5
141
141
142
142
143 class MergeResponse(object):
143 class MergeResponse(object):
144
144
145 # uses .format(**metadata) for variables
145 # uses .format(**metadata) for variables
146 MERGE_STATUS_MESSAGES = {
146 MERGE_STATUS_MESSAGES = {
147 MergeFailureReason.NONE: lazy_ugettext(
147 MergeFailureReason.NONE: lazy_ugettext(
148 u'This pull request can be automatically merged.'),
148 u'This pull request can be automatically merged.'),
149 MergeFailureReason.UNKNOWN: lazy_ugettext(
149 MergeFailureReason.UNKNOWN: lazy_ugettext(
150 u'This pull request cannot be merged because of an unhandled exception. '
150 u'This pull request cannot be merged because of an unhandled exception. '
151 u'{exception}'),
151 u'{exception}'),
152 MergeFailureReason.MERGE_FAILED: lazy_ugettext(
152 MergeFailureReason.MERGE_FAILED: lazy_ugettext(
153 u'This pull request cannot be merged because of merge conflicts.'),
153 u'This pull request cannot be merged because of merge conflicts.'),
154 MergeFailureReason.PUSH_FAILED: lazy_ugettext(
154 MergeFailureReason.PUSH_FAILED: lazy_ugettext(
155 u'This pull request could not be merged because push to '
155 u'This pull request could not be merged because push to '
156 u'target:`{target}@{merge_commit}` failed.'),
156 u'target:`{target}@{merge_commit}` failed.'),
157 MergeFailureReason.TARGET_IS_NOT_HEAD: lazy_ugettext(
157 MergeFailureReason.TARGET_IS_NOT_HEAD: lazy_ugettext(
158 u'This pull request cannot be merged because the target '
158 u'This pull request cannot be merged because the target '
159 u'`{target_ref.name}` is not a head.'),
159 u'`{target_ref.name}` is not a head.'),
160 MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES: lazy_ugettext(
160 MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES: lazy_ugettext(
161 u'This pull request cannot be merged because the source contains '
161 u'This pull request cannot be merged because the source contains '
162 u'more branches than the target.'),
162 u'more branches than the target.'),
163 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS: lazy_ugettext(
163 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS: lazy_ugettext(
164 u'This pull request cannot be merged because the target `{target_ref.name}` '
164 u'This pull request cannot be merged because the target `{target_ref.name}` '
165 u'has multiple heads: `{heads}`.'),
165 u'has multiple heads: `{heads}`.'),
166 MergeFailureReason.TARGET_IS_LOCKED: lazy_ugettext(
166 MergeFailureReason.TARGET_IS_LOCKED: lazy_ugettext(
167 u'This pull request cannot be merged because the target repository is '
167 u'This pull request cannot be merged because the target repository is '
168 u'locked by {locked_by}.'),
168 u'locked by {locked_by}.'),
169
169
170 MergeFailureReason.MISSING_TARGET_REF: lazy_ugettext(
170 MergeFailureReason.MISSING_TARGET_REF: lazy_ugettext(
171 u'This pull request cannot be merged because the target '
171 u'This pull request cannot be merged because the target '
172 u'reference `{target_ref.name}` is missing.'),
172 u'reference `{target_ref.name}` is missing.'),
173 MergeFailureReason.MISSING_SOURCE_REF: lazy_ugettext(
173 MergeFailureReason.MISSING_SOURCE_REF: lazy_ugettext(
174 u'This pull request cannot be merged because the source '
174 u'This pull request cannot be merged because the source '
175 u'reference `{source_ref.name}` is missing.'),
175 u'reference `{source_ref.name}` is missing.'),
176 MergeFailureReason.SUBREPO_MERGE_FAILED: lazy_ugettext(
176 MergeFailureReason.SUBREPO_MERGE_FAILED: lazy_ugettext(
177 u'This pull request cannot be merged because of conflicts related '
177 u'This pull request cannot be merged because of conflicts related '
178 u'to sub repositories.'),
178 u'to sub repositories.'),
179
179
180 # Deprecations
180 # Deprecations
181 MergeFailureReason._DEPRECATED_MISSING_COMMIT: lazy_ugettext(
181 MergeFailureReason._DEPRECATED_MISSING_COMMIT: lazy_ugettext(
182 u'This pull request cannot be merged because the target or the '
182 u'This pull request cannot be merged because the target or the '
183 u'source reference is missing.'),
183 u'source reference is missing.'),
184
184
185 }
185 }
186
186
187 def __init__(self, possible, executed, merge_ref, failure_reason, metadata=None):
187 def __init__(self, possible, executed, merge_ref, failure_reason, metadata=None):
188 self.possible = possible
188 self.possible = possible
189 self.executed = executed
189 self.executed = executed
190 self.merge_ref = merge_ref
190 self.merge_ref = merge_ref
191 self.failure_reason = failure_reason
191 self.failure_reason = failure_reason
192 self.metadata = metadata or {}
192 self.metadata = metadata or {}
193
193
194 def __repr__(self):
194 def __repr__(self):
195 return '<MergeResponse:{} {}>'.format(self.label, self.failure_reason)
195 return '<MergeResponse:{} {}>'.format(self.label, self.failure_reason)
196
196
197 def __eq__(self, other):
197 def __eq__(self, other):
198 same_instance = isinstance(other, self.__class__)
198 same_instance = isinstance(other, self.__class__)
199 return same_instance \
199 return same_instance \
200 and self.possible == other.possible \
200 and self.possible == other.possible \
201 and self.executed == other.executed \
201 and self.executed == other.executed \
202 and self.failure_reason == other.failure_reason
202 and self.failure_reason == other.failure_reason
203
203
204 @property
204 @property
205 def label(self):
205 def label(self):
206 label_dict = dict((v, k) for k, v in MergeFailureReason.__dict__.items() if
206 label_dict = dict((v, k) for k, v in MergeFailureReason.__dict__.items() if
207 not k.startswith('_'))
207 not k.startswith('_'))
208 return label_dict.get(self.failure_reason)
208 return label_dict.get(self.failure_reason)
209
209
210 @property
210 @property
211 def merge_status_message(self):
211 def merge_status_message(self):
212 """
212 """
213 Return a human friendly error message for the given merge status code.
213 Return a human friendly error message for the given merge status code.
214 """
214 """
215 msg = safe_unicode(self.MERGE_STATUS_MESSAGES[self.failure_reason])
215 msg = safe_unicode(self.MERGE_STATUS_MESSAGES[self.failure_reason])
216 try:
216 try:
217 return msg.format(**self.metadata)
217 return msg.format(**self.metadata)
218 except Exception:
218 except Exception:
219 log.exception('Failed to format %s message', self)
219 log.exception('Failed to format %s message', self)
220 return msg
220 return msg
221
221
222 def asdict(self):
222 def asdict(self):
223 data = {}
223 data = {}
224 for k in ['possible', 'executed', 'merge_ref', 'failure_reason',
224 for k in ['possible', 'executed', 'merge_ref', 'failure_reason',
225 'merge_status_message']:
225 'merge_status_message']:
226 data[k] = getattr(self, k)
226 data[k] = getattr(self, k)
227 return data
227 return data
228
228
229
229
230 class BaseRepository(object):
230 class BaseRepository(object):
231 """
231 """
232 Base Repository for final backends
232 Base Repository for final backends
233
233
234 .. attribute:: DEFAULT_BRANCH_NAME
234 .. attribute:: DEFAULT_BRANCH_NAME
235
235
236 name of default branch (i.e. "trunk" for svn, "master" for git etc.
236 name of default branch (i.e. "trunk" for svn, "master" for git etc.
237
237
238 .. attribute:: commit_ids
238 .. attribute:: commit_ids
239
239
240 list of all available commit ids, in ascending order
240 list of all available commit ids, in ascending order
241
241
242 .. attribute:: path
242 .. attribute:: path
243
243
244 absolute path to the repository
244 absolute path to the repository
245
245
246 .. attribute:: bookmarks
246 .. attribute:: bookmarks
247
247
248 Mapping from name to :term:`Commit ID` of the bookmark. Empty in case
248 Mapping from name to :term:`Commit ID` of the bookmark. Empty in case
249 there are no bookmarks or the backend implementation does not support
249 there are no bookmarks or the backend implementation does not support
250 bookmarks.
250 bookmarks.
251
251
252 .. attribute:: tags
252 .. attribute:: tags
253
253
254 Mapping from name to :term:`Commit ID` of the tag.
254 Mapping from name to :term:`Commit ID` of the tag.
255
255
256 """
256 """
257
257
258 DEFAULT_BRANCH_NAME = None
258 DEFAULT_BRANCH_NAME = None
259 DEFAULT_CONTACT = u"Unknown"
259 DEFAULT_CONTACT = u"Unknown"
260 DEFAULT_DESCRIPTION = u"unknown"
260 DEFAULT_DESCRIPTION = u"unknown"
261 EMPTY_COMMIT_ID = '0' * 40
261 EMPTY_COMMIT_ID = '0' * 40
262
262
263 path = None
263 path = None
264
264
265 def __init__(self, repo_path, config=None, create=False, **kwargs):
265 def __init__(self, repo_path, config=None, create=False, **kwargs):
266 """
266 """
267 Initializes repository. Raises RepositoryError if repository could
267 Initializes repository. Raises RepositoryError if repository could
268 not be find at the given ``repo_path`` or directory at ``repo_path``
268 not be find at the given ``repo_path`` or directory at ``repo_path``
269 exists and ``create`` is set to True.
269 exists and ``create`` is set to True.
270
270
271 :param repo_path: local path of the repository
271 :param repo_path: local path of the repository
272 :param config: repository configuration
272 :param config: repository configuration
273 :param create=False: if set to True, would try to create repository.
273 :param create=False: if set to True, would try to create repository.
274 :param src_url=None: if set, should be proper url from which repository
274 :param src_url=None: if set, should be proper url from which repository
275 would be cloned; requires ``create`` parameter to be set to True -
275 would be cloned; requires ``create`` parameter to be set to True -
276 raises RepositoryError if src_url is set and create evaluates to
276 raises RepositoryError if src_url is set and create evaluates to
277 False
277 False
278 """
278 """
279 raise NotImplementedError
279 raise NotImplementedError
280
280
281 def __repr__(self):
281 def __repr__(self):
282 return '<%s at %s>' % (self.__class__.__name__, self.path)
282 return '<%s at %s>' % (self.__class__.__name__, self.path)
283
283
284 def __len__(self):
284 def __len__(self):
285 return self.count()
285 return self.count()
286
286
287 def __eq__(self, other):
287 def __eq__(self, other):
288 same_instance = isinstance(other, self.__class__)
288 same_instance = isinstance(other, self.__class__)
289 return same_instance and other.path == self.path
289 return same_instance and other.path == self.path
290
290
291 def __ne__(self, other):
291 def __ne__(self, other):
292 return not self.__eq__(other)
292 return not self.__eq__(other)
293
293
294 def get_create_shadow_cache_pr_path(self, db_repo):
294 def get_create_shadow_cache_pr_path(self, db_repo):
295 path = db_repo.cached_diffs_dir
295 path = db_repo.cached_diffs_dir
296 if not os.path.exists(path):
296 if not os.path.exists(path):
297 os.makedirs(path, 0o755)
297 os.makedirs(path, 0o755)
298 return path
298 return path
299
299
300 @classmethod
300 @classmethod
301 def get_default_config(cls, default=None):
301 def get_default_config(cls, default=None):
302 config = Config()
302 config = Config()
303 if default and isinstance(default, list):
303 if default and isinstance(default, list):
304 for section, key, val in default:
304 for section, key, val in default:
305 config.set(section, key, val)
305 config.set(section, key, val)
306 return config
306 return config
307
307
308 @LazyProperty
308 @LazyProperty
309 def _remote(self):
309 def _remote(self):
310 raise NotImplementedError
310 raise NotImplementedError
311
311
312 def _heads(self, branch=None):
312 def _heads(self, branch=None):
313 return []
313 return []
314
314
315 @LazyProperty
315 @LazyProperty
316 def EMPTY_COMMIT(self):
316 def EMPTY_COMMIT(self):
317 return EmptyCommit(self.EMPTY_COMMIT_ID)
317 return EmptyCommit(self.EMPTY_COMMIT_ID)
318
318
319 @LazyProperty
319 @LazyProperty
320 def alias(self):
320 def alias(self):
321 for k, v in settings.BACKENDS.items():
321 for k, v in settings.BACKENDS.items():
322 if v.split('.')[-1] == str(self.__class__.__name__):
322 if v.split('.')[-1] == str(self.__class__.__name__):
323 return k
323 return k
324
324
325 @LazyProperty
325 @LazyProperty
326 def name(self):
326 def name(self):
327 return safe_unicode(os.path.basename(self.path))
327 return safe_unicode(os.path.basename(self.path))
328
328
329 @LazyProperty
329 @LazyProperty
330 def description(self):
330 def description(self):
331 raise NotImplementedError
331 raise NotImplementedError
332
332
333 def refs(self):
333 def refs(self):
334 """
334 """
335 returns a `dict` with branches, bookmarks, tags, and closed_branches
335 returns a `dict` with branches, bookmarks, tags, and closed_branches
336 for this repository
336 for this repository
337 """
337 """
338 return dict(
338 return dict(
339 branches=self.branches,
339 branches=self.branches,
340 branches_closed=self.branches_closed,
340 branches_closed=self.branches_closed,
341 tags=self.tags,
341 tags=self.tags,
342 bookmarks=self.bookmarks
342 bookmarks=self.bookmarks
343 )
343 )
344
344
345 @LazyProperty
345 @LazyProperty
346 def branches(self):
346 def branches(self):
347 """
347 """
348 A `dict` which maps branch names to commit ids.
348 A `dict` which maps branch names to commit ids.
349 """
349 """
350 raise NotImplementedError
350 raise NotImplementedError
351
351
352 @LazyProperty
352 @LazyProperty
353 def branches_closed(self):
353 def branches_closed(self):
354 """
354 """
355 A `dict` which maps tags names to commit ids.
355 A `dict` which maps tags names to commit ids.
356 """
356 """
357 raise NotImplementedError
357 raise NotImplementedError
358
358
359 @LazyProperty
359 @LazyProperty
360 def bookmarks(self):
360 def bookmarks(self):
361 """
361 """
362 A `dict` which maps tags names to commit ids.
362 A `dict` which maps tags names to commit ids.
363 """
363 """
364 raise NotImplementedError
364 raise NotImplementedError
365
365
366 @LazyProperty
366 @LazyProperty
367 def tags(self):
367 def tags(self):
368 """
368 """
369 A `dict` which maps tags names to commit ids.
369 A `dict` which maps tags names to commit ids.
370 """
370 """
371 raise NotImplementedError
371 raise NotImplementedError
372
372
373 @LazyProperty
373 @LazyProperty
374 def size(self):
374 def size(self):
375 """
375 """
376 Returns combined size in bytes for all repository files
376 Returns combined size in bytes for all repository files
377 """
377 """
378 tip = self.get_commit()
378 tip = self.get_commit()
379 return tip.size
379 return tip.size
380
380
381 def size_at_commit(self, commit_id):
381 def size_at_commit(self, commit_id):
382 commit = self.get_commit(commit_id)
382 commit = self.get_commit(commit_id)
383 return commit.size
383 return commit.size
384
384
385 def is_empty(self):
385 def is_empty(self):
386 return not bool(self.commit_ids)
386 return self._remote.is_empty()
387
387
388 @staticmethod
388 @staticmethod
389 def check_url(url, config):
389 def check_url(url, config):
390 """
390 """
391 Function will check given url and try to verify if it's a valid
391 Function will check given url and try to verify if it's a valid
392 link.
392 link.
393 """
393 """
394 raise NotImplementedError
394 raise NotImplementedError
395
395
396 @staticmethod
396 @staticmethod
397 def is_valid_repository(path):
397 def is_valid_repository(path):
398 """
398 """
399 Check if given `path` contains a valid repository of this backend
399 Check if given `path` contains a valid repository of this backend
400 """
400 """
401 raise NotImplementedError
401 raise NotImplementedError
402
402
403 # ==========================================================================
403 # ==========================================================================
404 # COMMITS
404 # COMMITS
405 # ==========================================================================
405 # ==========================================================================
406
406
407 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, translate_tag=None):
407 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, translate_tag=None):
408 """
408 """
409 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
409 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
410 are both None, most recent commit is returned.
410 are both None, most recent commit is returned.
411
411
412 :param pre_load: Optional. List of commit attributes to load.
412 :param pre_load: Optional. List of commit attributes to load.
413
413
414 :raises ``EmptyRepositoryError``: if there are no commits
414 :raises ``EmptyRepositoryError``: if there are no commits
415 """
415 """
416 raise NotImplementedError
416 raise NotImplementedError
417
417
418 def __iter__(self):
418 def __iter__(self):
419 for commit_id in self.commit_ids:
419 for commit_id in self.commit_ids:
420 yield self.get_commit(commit_id=commit_id)
420 yield self.get_commit(commit_id=commit_id)
421
421
422 def get_commits(
422 def get_commits(
423 self, start_id=None, end_id=None, start_date=None, end_date=None,
423 self, start_id=None, end_id=None, start_date=None, end_date=None,
424 branch_name=None, show_hidden=False, pre_load=None, translate_tags=None):
424 branch_name=None, show_hidden=False, pre_load=None, translate_tags=None):
425 """
425 """
426 Returns iterator of `BaseCommit` objects from start to end
426 Returns iterator of `BaseCommit` objects from start to end
427 not inclusive. This should behave just like a list, ie. end is not
427 not inclusive. This should behave just like a list, ie. end is not
428 inclusive.
428 inclusive.
429
429
430 :param start_id: None or str, must be a valid commit id
430 :param start_id: None or str, must be a valid commit id
431 :param end_id: None or str, must be a valid commit id
431 :param end_id: None or str, must be a valid commit id
432 :param start_date:
432 :param start_date:
433 :param end_date:
433 :param end_date:
434 :param branch_name:
434 :param branch_name:
435 :param show_hidden:
435 :param show_hidden:
436 :param pre_load:
436 :param pre_load:
437 :param translate_tags:
437 :param translate_tags:
438 """
438 """
439 raise NotImplementedError
439 raise NotImplementedError
440
440
441 def __getitem__(self, key):
441 def __getitem__(self, key):
442 """
442 """
443 Allows index based access to the commit objects of this repository.
443 Allows index based access to the commit objects of this repository.
444 """
444 """
445 pre_load = ["author", "branch", "date", "message", "parents"]
445 pre_load = ["author", "branch", "date", "message", "parents"]
446 if isinstance(key, slice):
446 if isinstance(key, slice):
447 return self._get_range(key, pre_load)
447 return self._get_range(key, pre_load)
448 return self.get_commit(commit_idx=key, pre_load=pre_load)
448 return self.get_commit(commit_idx=key, pre_load=pre_load)
449
449
450 def _get_range(self, slice_obj, pre_load):
450 def _get_range(self, slice_obj, pre_load):
451 for commit_id in self.commit_ids.__getitem__(slice_obj):
451 for commit_id in self.commit_ids.__getitem__(slice_obj):
452 yield self.get_commit(commit_id=commit_id, pre_load=pre_load)
452 yield self.get_commit(commit_id=commit_id, pre_load=pre_load)
453
453
454 def count(self):
454 def count(self):
455 return len(self.commit_ids)
455 return len(self.commit_ids)
456
456
457 def tag(self, name, user, commit_id=None, message=None, date=None, **opts):
457 def tag(self, name, user, commit_id=None, message=None, date=None, **opts):
458 """
458 """
459 Creates and returns a tag for the given ``commit_id``.
459 Creates and returns a tag for the given ``commit_id``.
460
460
461 :param name: name for new tag
461 :param name: name for new tag
462 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
462 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
463 :param commit_id: commit id for which new tag would be created
463 :param commit_id: commit id for which new tag would be created
464 :param message: message of the tag's commit
464 :param message: message of the tag's commit
465 :param date: date of tag's commit
465 :param date: date of tag's commit
466
466
467 :raises TagAlreadyExistError: if tag with same name already exists
467 :raises TagAlreadyExistError: if tag with same name already exists
468 """
468 """
469 raise NotImplementedError
469 raise NotImplementedError
470
470
471 def remove_tag(self, name, user, message=None, date=None):
471 def remove_tag(self, name, user, message=None, date=None):
472 """
472 """
473 Removes tag with the given ``name``.
473 Removes tag with the given ``name``.
474
474
475 :param name: name of the tag to be removed
475 :param name: name of the tag to be removed
476 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
476 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
477 :param message: message of the tag's removal commit
477 :param message: message of the tag's removal commit
478 :param date: date of tag's removal commit
478 :param date: date of tag's removal commit
479
479
480 :raises TagDoesNotExistError: if tag with given name does not exists
480 :raises TagDoesNotExistError: if tag with given name does not exists
481 """
481 """
482 raise NotImplementedError
482 raise NotImplementedError
483
483
484 def get_diff(
484 def get_diff(
485 self, commit1, commit2, path=None, ignore_whitespace=False,
485 self, commit1, commit2, path=None, ignore_whitespace=False,
486 context=3, path1=None):
486 context=3, path1=None):
487 """
487 """
488 Returns (git like) *diff*, as plain text. Shows changes introduced by
488 Returns (git like) *diff*, as plain text. Shows changes introduced by
489 `commit2` since `commit1`.
489 `commit2` since `commit1`.
490
490
491 :param commit1: Entry point from which diff is shown. Can be
491 :param commit1: Entry point from which diff is shown. Can be
492 ``self.EMPTY_COMMIT`` - in this case, patch showing all
492 ``self.EMPTY_COMMIT`` - in this case, patch showing all
493 the changes since empty state of the repository until `commit2`
493 the changes since empty state of the repository until `commit2`
494 :param commit2: Until which commit changes should be shown.
494 :param commit2: Until which commit changes should be shown.
495 :param path: Can be set to a path of a file to create a diff of that
495 :param path: Can be set to a path of a file to create a diff of that
496 file. If `path1` is also set, this value is only associated to
496 file. If `path1` is also set, this value is only associated to
497 `commit2`.
497 `commit2`.
498 :param ignore_whitespace: If set to ``True``, would not show whitespace
498 :param ignore_whitespace: If set to ``True``, would not show whitespace
499 changes. Defaults to ``False``.
499 changes. Defaults to ``False``.
500 :param context: How many lines before/after changed lines should be
500 :param context: How many lines before/after changed lines should be
501 shown. Defaults to ``3``.
501 shown. Defaults to ``3``.
502 :param path1: Can be set to a path to associate with `commit1`. This
502 :param path1: Can be set to a path to associate with `commit1`. This
503 parameter works only for backends which support diff generation for
503 parameter works only for backends which support diff generation for
504 different paths. Other backends will raise a `ValueError` if `path1`
504 different paths. Other backends will raise a `ValueError` if `path1`
505 is set and has a different value than `path`.
505 is set and has a different value than `path`.
506 :param file_path: filter this diff by given path pattern
506 :param file_path: filter this diff by given path pattern
507 """
507 """
508 raise NotImplementedError
508 raise NotImplementedError
509
509
510 def strip(self, commit_id, branch=None):
510 def strip(self, commit_id, branch=None):
511 """
511 """
512 Strip given commit_id from the repository
512 Strip given commit_id from the repository
513 """
513 """
514 raise NotImplementedError
514 raise NotImplementedError
515
515
516 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
516 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
517 """
517 """
518 Return a latest common ancestor commit if one exists for this repo
518 Return a latest common ancestor commit if one exists for this repo
519 `commit_id1` vs `commit_id2` from `repo2`.
519 `commit_id1` vs `commit_id2` from `repo2`.
520
520
521 :param commit_id1: Commit it from this repository to use as a
521 :param commit_id1: Commit it from this repository to use as a
522 target for the comparison.
522 target for the comparison.
523 :param commit_id2: Source commit id to use for comparison.
523 :param commit_id2: Source commit id to use for comparison.
524 :param repo2: Source repository to use for comparison.
524 :param repo2: Source repository to use for comparison.
525 """
525 """
526 raise NotImplementedError
526 raise NotImplementedError
527
527
528 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
528 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
529 """
529 """
530 Compare this repository's revision `commit_id1` with `commit_id2`.
530 Compare this repository's revision `commit_id1` with `commit_id2`.
531
531
532 Returns a tuple(commits, ancestor) that would be merged from
532 Returns a tuple(commits, ancestor) that would be merged from
533 `commit_id2`. Doing a normal compare (``merge=False``), ``None``
533 `commit_id2`. Doing a normal compare (``merge=False``), ``None``
534 will be returned as ancestor.
534 will be returned as ancestor.
535
535
536 :param commit_id1: Commit it from this repository to use as a
536 :param commit_id1: Commit it from this repository to use as a
537 target for the comparison.
537 target for the comparison.
538 :param commit_id2: Source commit id to use for comparison.
538 :param commit_id2: Source commit id to use for comparison.
539 :param repo2: Source repository to use for comparison.
539 :param repo2: Source repository to use for comparison.
540 :param merge: If set to ``True`` will do a merge compare which also
540 :param merge: If set to ``True`` will do a merge compare which also
541 returns the common ancestor.
541 returns the common ancestor.
542 :param pre_load: Optional. List of commit attributes to load.
542 :param pre_load: Optional. List of commit attributes to load.
543 """
543 """
544 raise NotImplementedError
544 raise NotImplementedError
545
545
546 def merge(self, repo_id, workspace_id, target_ref, source_repo, source_ref,
546 def merge(self, repo_id, workspace_id, target_ref, source_repo, source_ref,
547 user_name='', user_email='', message='', dry_run=False,
547 user_name='', user_email='', message='', dry_run=False,
548 use_rebase=False, close_branch=False):
548 use_rebase=False, close_branch=False):
549 """
549 """
550 Merge the revisions specified in `source_ref` from `source_repo`
550 Merge the revisions specified in `source_ref` from `source_repo`
551 onto the `target_ref` of this repository.
551 onto the `target_ref` of this repository.
552
552
553 `source_ref` and `target_ref` are named tupls with the following
553 `source_ref` and `target_ref` are named tupls with the following
554 fields `type`, `name` and `commit_id`.
554 fields `type`, `name` and `commit_id`.
555
555
556 Returns a MergeResponse named tuple with the following fields
556 Returns a MergeResponse named tuple with the following fields
557 'possible', 'executed', 'source_commit', 'target_commit',
557 'possible', 'executed', 'source_commit', 'target_commit',
558 'merge_commit'.
558 'merge_commit'.
559
559
560 :param repo_id: `repo_id` target repo id.
560 :param repo_id: `repo_id` target repo id.
561 :param workspace_id: `workspace_id` unique identifier.
561 :param workspace_id: `workspace_id` unique identifier.
562 :param target_ref: `target_ref` points to the commit on top of which
562 :param target_ref: `target_ref` points to the commit on top of which
563 the `source_ref` should be merged.
563 the `source_ref` should be merged.
564 :param source_repo: The repository that contains the commits to be
564 :param source_repo: The repository that contains the commits to be
565 merged.
565 merged.
566 :param source_ref: `source_ref` points to the topmost commit from
566 :param source_ref: `source_ref` points to the topmost commit from
567 the `source_repo` which should be merged.
567 the `source_repo` which should be merged.
568 :param user_name: Merge commit `user_name`.
568 :param user_name: Merge commit `user_name`.
569 :param user_email: Merge commit `user_email`.
569 :param user_email: Merge commit `user_email`.
570 :param message: Merge commit `message`.
570 :param message: Merge commit `message`.
571 :param dry_run: If `True` the merge will not take place.
571 :param dry_run: If `True` the merge will not take place.
572 :param use_rebase: If `True` commits from the source will be rebased
572 :param use_rebase: If `True` commits from the source will be rebased
573 on top of the target instead of being merged.
573 on top of the target instead of being merged.
574 :param close_branch: If `True` branch will be close before merging it
574 :param close_branch: If `True` branch will be close before merging it
575 """
575 """
576 if dry_run:
576 if dry_run:
577 message = message or settings.MERGE_DRY_RUN_MESSAGE
577 message = message or settings.MERGE_DRY_RUN_MESSAGE
578 user_email = user_email or settings.MERGE_DRY_RUN_EMAIL
578 user_email = user_email or settings.MERGE_DRY_RUN_EMAIL
579 user_name = user_name or settings.MERGE_DRY_RUN_USER
579 user_name = user_name or settings.MERGE_DRY_RUN_USER
580 else:
580 else:
581 if not user_name:
581 if not user_name:
582 raise ValueError('user_name cannot be empty')
582 raise ValueError('user_name cannot be empty')
583 if not user_email:
583 if not user_email:
584 raise ValueError('user_email cannot be empty')
584 raise ValueError('user_email cannot be empty')
585 if not message:
585 if not message:
586 raise ValueError('message cannot be empty')
586 raise ValueError('message cannot be empty')
587
587
588 try:
588 try:
589 return self._merge_repo(
589 return self._merge_repo(
590 repo_id, workspace_id, target_ref, source_repo,
590 repo_id, workspace_id, target_ref, source_repo,
591 source_ref, message, user_name, user_email, dry_run=dry_run,
591 source_ref, message, user_name, user_email, dry_run=dry_run,
592 use_rebase=use_rebase, close_branch=close_branch)
592 use_rebase=use_rebase, close_branch=close_branch)
593 except RepositoryError as exc:
593 except RepositoryError as exc:
594 log.exception('Unexpected failure when running merge, dry-run=%s', dry_run)
594 log.exception('Unexpected failure when running merge, dry-run=%s', dry_run)
595 return MergeResponse(
595 return MergeResponse(
596 False, False, None, MergeFailureReason.UNKNOWN,
596 False, False, None, MergeFailureReason.UNKNOWN,
597 metadata={'exception': str(exc)})
597 metadata={'exception': str(exc)})
598
598
599 def _merge_repo(self, repo_id, workspace_id, target_ref,
599 def _merge_repo(self, repo_id, workspace_id, target_ref,
600 source_repo, source_ref, merge_message,
600 source_repo, source_ref, merge_message,
601 merger_name, merger_email, dry_run=False,
601 merger_name, merger_email, dry_run=False,
602 use_rebase=False, close_branch=False):
602 use_rebase=False, close_branch=False):
603 """Internal implementation of merge."""
603 """Internal implementation of merge."""
604 raise NotImplementedError
604 raise NotImplementedError
605
605
606 def _maybe_prepare_merge_workspace(
606 def _maybe_prepare_merge_workspace(
607 self, repo_id, workspace_id, target_ref, source_ref):
607 self, repo_id, workspace_id, target_ref, source_ref):
608 """
608 """
609 Create the merge workspace.
609 Create the merge workspace.
610
610
611 :param workspace_id: `workspace_id` unique identifier.
611 :param workspace_id: `workspace_id` unique identifier.
612 """
612 """
613 raise NotImplementedError
613 raise NotImplementedError
614
614
615 def _get_legacy_shadow_repository_path(self, workspace_id):
615 def _get_legacy_shadow_repository_path(self, workspace_id):
616 """
616 """
617 Legacy version that was used before. We still need it for
617 Legacy version that was used before. We still need it for
618 backward compat
618 backward compat
619 """
619 """
620 return os.path.join(
620 return os.path.join(
621 os.path.dirname(self.path),
621 os.path.dirname(self.path),
622 '.__shadow_%s_%s' % (os.path.basename(self.path), workspace_id))
622 '.__shadow_%s_%s' % (os.path.basename(self.path), workspace_id))
623
623
624 def _get_shadow_repository_path(self, repo_id, workspace_id):
624 def _get_shadow_repository_path(self, repo_id, workspace_id):
625 # The name of the shadow repository must start with '.', so it is
625 # The name of the shadow repository must start with '.', so it is
626 # skipped by 'rhodecode.lib.utils.get_filesystem_repos'.
626 # skipped by 'rhodecode.lib.utils.get_filesystem_repos'.
627 legacy_repository_path = self._get_legacy_shadow_repository_path(workspace_id)
627 legacy_repository_path = self._get_legacy_shadow_repository_path(workspace_id)
628 if os.path.exists(legacy_repository_path):
628 if os.path.exists(legacy_repository_path):
629 return legacy_repository_path
629 return legacy_repository_path
630 else:
630 else:
631 return os.path.join(
631 return os.path.join(
632 os.path.dirname(self.path),
632 os.path.dirname(self.path),
633 '.__shadow_repo_%s_%s' % (repo_id, workspace_id))
633 '.__shadow_repo_%s_%s' % (repo_id, workspace_id))
634
634
635 def cleanup_merge_workspace(self, repo_id, workspace_id):
635 def cleanup_merge_workspace(self, repo_id, workspace_id):
636 """
636 """
637 Remove merge workspace.
637 Remove merge workspace.
638
638
639 This function MUST not fail in case there is no workspace associated to
639 This function MUST not fail in case there is no workspace associated to
640 the given `workspace_id`.
640 the given `workspace_id`.
641
641
642 :param workspace_id: `workspace_id` unique identifier.
642 :param workspace_id: `workspace_id` unique identifier.
643 """
643 """
644 shadow_repository_path = self._get_shadow_repository_path(repo_id, workspace_id)
644 shadow_repository_path = self._get_shadow_repository_path(repo_id, workspace_id)
645 shadow_repository_path_del = '{}.{}.delete'.format(
645 shadow_repository_path_del = '{}.{}.delete'.format(
646 shadow_repository_path, time.time())
646 shadow_repository_path, time.time())
647
647
648 # move the shadow repo, so it never conflicts with the one used.
648 # move the shadow repo, so it never conflicts with the one used.
649 # we use this method because shutil.rmtree had some edge case problems
649 # we use this method because shutil.rmtree had some edge case problems
650 # removing symlinked repositories
650 # removing symlinked repositories
651 if not os.path.isdir(shadow_repository_path):
651 if not os.path.isdir(shadow_repository_path):
652 return
652 return
653
653
654 shutil.move(shadow_repository_path, shadow_repository_path_del)
654 shutil.move(shadow_repository_path, shadow_repository_path_del)
655 try:
655 try:
656 shutil.rmtree(shadow_repository_path_del, ignore_errors=False)
656 shutil.rmtree(shadow_repository_path_del, ignore_errors=False)
657 except Exception:
657 except Exception:
658 log.exception('Failed to gracefully remove shadow repo under %s',
658 log.exception('Failed to gracefully remove shadow repo under %s',
659 shadow_repository_path_del)
659 shadow_repository_path_del)
660 shutil.rmtree(shadow_repository_path_del, ignore_errors=True)
660 shutil.rmtree(shadow_repository_path_del, ignore_errors=True)
661
661
662 # ========== #
662 # ========== #
663 # COMMIT API #
663 # COMMIT API #
664 # ========== #
664 # ========== #
665
665
666 @LazyProperty
666 @LazyProperty
667 def in_memory_commit(self):
667 def in_memory_commit(self):
668 """
668 """
669 Returns :class:`InMemoryCommit` object for this repository.
669 Returns :class:`InMemoryCommit` object for this repository.
670 """
670 """
671 raise NotImplementedError
671 raise NotImplementedError
672
672
673 # ======================== #
673 # ======================== #
674 # UTILITIES FOR SUBCLASSES #
674 # UTILITIES FOR SUBCLASSES #
675 # ======================== #
675 # ======================== #
676
676
677 def _validate_diff_commits(self, commit1, commit2):
677 def _validate_diff_commits(self, commit1, commit2):
678 """
678 """
679 Validates that the given commits are related to this repository.
679 Validates that the given commits are related to this repository.
680
680
681 Intended as a utility for sub classes to have a consistent validation
681 Intended as a utility for sub classes to have a consistent validation
682 of input parameters in methods like :meth:`get_diff`.
682 of input parameters in methods like :meth:`get_diff`.
683 """
683 """
684 self._validate_commit(commit1)
684 self._validate_commit(commit1)
685 self._validate_commit(commit2)
685 self._validate_commit(commit2)
686 if (isinstance(commit1, EmptyCommit) and
686 if (isinstance(commit1, EmptyCommit) and
687 isinstance(commit2, EmptyCommit)):
687 isinstance(commit2, EmptyCommit)):
688 raise ValueError("Cannot compare two empty commits")
688 raise ValueError("Cannot compare two empty commits")
689
689
690 def _validate_commit(self, commit):
690 def _validate_commit(self, commit):
691 if not isinstance(commit, BaseCommit):
691 if not isinstance(commit, BaseCommit):
692 raise TypeError(
692 raise TypeError(
693 "%s is not of type BaseCommit" % repr(commit))
693 "%s is not of type BaseCommit" % repr(commit))
694 if commit.repository != self and not isinstance(commit, EmptyCommit):
694 if commit.repository != self and not isinstance(commit, EmptyCommit):
695 raise ValueError(
695 raise ValueError(
696 "Commit %s must be a valid commit from this repository %s, "
696 "Commit %s must be a valid commit from this repository %s, "
697 "related to this repository instead %s." %
697 "related to this repository instead %s." %
698 (commit, self, commit.repository))
698 (commit, self, commit.repository))
699
699
700 def _validate_commit_id(self, commit_id):
700 def _validate_commit_id(self, commit_id):
701 if not isinstance(commit_id, compat.string_types):
701 if not isinstance(commit_id, compat.string_types):
702 raise TypeError("commit_id must be a string value")
702 raise TypeError("commit_id must be a string value")
703
703
704 def _validate_commit_idx(self, commit_idx):
704 def _validate_commit_idx(self, commit_idx):
705 if not isinstance(commit_idx, (int, long)):
705 if not isinstance(commit_idx, (int, long)):
706 raise TypeError("commit_idx must be a numeric value")
706 raise TypeError("commit_idx must be a numeric value")
707
707
708 def _validate_branch_name(self, branch_name):
708 def _validate_branch_name(self, branch_name):
709 if branch_name and branch_name not in self.branches_all:
709 if branch_name and branch_name not in self.branches_all:
710 msg = ("Branch %s not found in %s" % (branch_name, self))
710 msg = ("Branch %s not found in %s" % (branch_name, self))
711 raise BranchDoesNotExistError(msg)
711 raise BranchDoesNotExistError(msg)
712
712
713 #
713 #
714 # Supporting deprecated API parts
714 # Supporting deprecated API parts
715 # TODO: johbo: consider to move this into a mixin
715 # TODO: johbo: consider to move this into a mixin
716 #
716 #
717
717
718 @property
718 @property
719 def EMPTY_CHANGESET(self):
719 def EMPTY_CHANGESET(self):
720 warnings.warn(
720 warnings.warn(
721 "Use EMPTY_COMMIT or EMPTY_COMMIT_ID instead", DeprecationWarning)
721 "Use EMPTY_COMMIT or EMPTY_COMMIT_ID instead", DeprecationWarning)
722 return self.EMPTY_COMMIT_ID
722 return self.EMPTY_COMMIT_ID
723
723
724 @property
724 @property
725 def revisions(self):
725 def revisions(self):
726 warnings.warn("Use commits attribute instead", DeprecationWarning)
726 warnings.warn("Use commits attribute instead", DeprecationWarning)
727 return self.commit_ids
727 return self.commit_ids
728
728
729 @revisions.setter
729 @revisions.setter
730 def revisions(self, value):
730 def revisions(self, value):
731 warnings.warn("Use commits attribute instead", DeprecationWarning)
731 warnings.warn("Use commits attribute instead", DeprecationWarning)
732 self.commit_ids = value
732 self.commit_ids = value
733
733
734 def get_changeset(self, revision=None, pre_load=None):
734 def get_changeset(self, revision=None, pre_load=None):
735 warnings.warn("Use get_commit instead", DeprecationWarning)
735 warnings.warn("Use get_commit instead", DeprecationWarning)
736 commit_id = None
736 commit_id = None
737 commit_idx = None
737 commit_idx = None
738 if isinstance(revision, compat.string_types):
738 if isinstance(revision, compat.string_types):
739 commit_id = revision
739 commit_id = revision
740 else:
740 else:
741 commit_idx = revision
741 commit_idx = revision
742 return self.get_commit(
742 return self.get_commit(
743 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
743 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
744
744
745 def get_changesets(
745 def get_changesets(
746 self, start=None, end=None, start_date=None, end_date=None,
746 self, start=None, end=None, start_date=None, end_date=None,
747 branch_name=None, pre_load=None):
747 branch_name=None, pre_load=None):
748 warnings.warn("Use get_commits instead", DeprecationWarning)
748 warnings.warn("Use get_commits instead", DeprecationWarning)
749 start_id = self._revision_to_commit(start)
749 start_id = self._revision_to_commit(start)
750 end_id = self._revision_to_commit(end)
750 end_id = self._revision_to_commit(end)
751 return self.get_commits(
751 return self.get_commits(
752 start_id=start_id, end_id=end_id, start_date=start_date,
752 start_id=start_id, end_id=end_id, start_date=start_date,
753 end_date=end_date, branch_name=branch_name, pre_load=pre_load)
753 end_date=end_date, branch_name=branch_name, pre_load=pre_load)
754
754
755 def _revision_to_commit(self, revision):
755 def _revision_to_commit(self, revision):
756 """
756 """
757 Translates a revision to a commit_id
757 Translates a revision to a commit_id
758
758
759 Helps to support the old changeset based API which allows to use
759 Helps to support the old changeset based API which allows to use
760 commit ids and commit indices interchangeable.
760 commit ids and commit indices interchangeable.
761 """
761 """
762 if revision is None:
762 if revision is None:
763 return revision
763 return revision
764
764
765 if isinstance(revision, compat.string_types):
765 if isinstance(revision, compat.string_types):
766 commit_id = revision
766 commit_id = revision
767 else:
767 else:
768 commit_id = self.commit_ids[revision]
768 commit_id = self.commit_ids[revision]
769 return commit_id
769 return commit_id
770
770
771 @property
771 @property
772 def in_memory_changeset(self):
772 def in_memory_changeset(self):
773 warnings.warn("Use in_memory_commit instead", DeprecationWarning)
773 warnings.warn("Use in_memory_commit instead", DeprecationWarning)
774 return self.in_memory_commit
774 return self.in_memory_commit
775
775
776 def get_path_permissions(self, username):
776 def get_path_permissions(self, username):
777 """
777 """
778 Returns a path permission checker or None if not supported
778 Returns a path permission checker or None if not supported
779
779
780 :param username: session user name
780 :param username: session user name
781 :return: an instance of BasePathPermissionChecker or None
781 :return: an instance of BasePathPermissionChecker or None
782 """
782 """
783 return None
783 return None
784
784
785 def install_hooks(self, force=False):
785 def install_hooks(self, force=False):
786 return self._remote.install_hooks(force)
786 return self._remote.install_hooks(force)
787
787
788 def get_hooks_info(self):
788 def get_hooks_info(self):
789 return self._remote.get_hooks_info()
789 return self._remote.get_hooks_info()
790
790
791
791
792 class BaseCommit(object):
792 class BaseCommit(object):
793 """
793 """
794 Each backend should implement it's commit representation.
794 Each backend should implement it's commit representation.
795
795
796 **Attributes**
796 **Attributes**
797
797
798 ``repository``
798 ``repository``
799 repository object within which commit exists
799 repository object within which commit exists
800
800
801 ``id``
801 ``id``
802 The commit id, may be ``raw_id`` or i.e. for mercurial's tip
802 The commit id, may be ``raw_id`` or i.e. for mercurial's tip
803 just ``tip``.
803 just ``tip``.
804
804
805 ``raw_id``
805 ``raw_id``
806 raw commit representation (i.e. full 40 length sha for git
806 raw commit representation (i.e. full 40 length sha for git
807 backend)
807 backend)
808
808
809 ``short_id``
809 ``short_id``
810 shortened (if apply) version of ``raw_id``; it would be simple
810 shortened (if apply) version of ``raw_id``; it would be simple
811 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
811 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
812 as ``raw_id`` for subversion
812 as ``raw_id`` for subversion
813
813
814 ``idx``
814 ``idx``
815 commit index
815 commit index
816
816
817 ``files``
817 ``files``
818 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
818 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
819
819
820 ``dirs``
820 ``dirs``
821 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
821 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
822
822
823 ``nodes``
823 ``nodes``
824 combined list of ``Node`` objects
824 combined list of ``Node`` objects
825
825
826 ``author``
826 ``author``
827 author of the commit, as unicode
827 author of the commit, as unicode
828
828
829 ``message``
829 ``message``
830 message of the commit, as unicode
830 message of the commit, as unicode
831
831
832 ``parents``
832 ``parents``
833 list of parent commits
833 list of parent commits
834
834
835 """
835 """
836
836
837 branch = None
837 branch = None
838 """
838 """
839 Depending on the backend this should be set to the branch name of the
839 Depending on the backend this should be set to the branch name of the
840 commit. Backends not supporting branches on commits should leave this
840 commit. Backends not supporting branches on commits should leave this
841 value as ``None``.
841 value as ``None``.
842 """
842 """
843
843
844 _ARCHIVE_PREFIX_TEMPLATE = b'{repo_name}-{short_id}'
844 _ARCHIVE_PREFIX_TEMPLATE = b'{repo_name}-{short_id}'
845 """
845 """
846 This template is used to generate a default prefix for repository archives
846 This template is used to generate a default prefix for repository archives
847 if no prefix has been specified.
847 if no prefix has been specified.
848 """
848 """
849
849
850 def __str__(self):
850 def __str__(self):
851 return '<%s at %s:%s>' % (
851 return '<%s at %s:%s>' % (
852 self.__class__.__name__, self.idx, self.short_id)
852 self.__class__.__name__, self.idx, self.short_id)
853
853
854 def __repr__(self):
854 def __repr__(self):
855 return self.__str__()
855 return self.__str__()
856
856
857 def __unicode__(self):
857 def __unicode__(self):
858 return u'%s:%s' % (self.idx, self.short_id)
858 return u'%s:%s' % (self.idx, self.short_id)
859
859
860 def __eq__(self, other):
860 def __eq__(self, other):
861 same_instance = isinstance(other, self.__class__)
861 same_instance = isinstance(other, self.__class__)
862 return same_instance and self.raw_id == other.raw_id
862 return same_instance and self.raw_id == other.raw_id
863
863
864 def __json__(self):
864 def __json__(self):
865 parents = []
865 parents = []
866 try:
866 try:
867 for parent in self.parents:
867 for parent in self.parents:
868 parents.append({'raw_id': parent.raw_id})
868 parents.append({'raw_id': parent.raw_id})
869 except NotImplementedError:
869 except NotImplementedError:
870 # empty commit doesn't have parents implemented
870 # empty commit doesn't have parents implemented
871 pass
871 pass
872
872
873 return {
873 return {
874 'short_id': self.short_id,
874 'short_id': self.short_id,
875 'raw_id': self.raw_id,
875 'raw_id': self.raw_id,
876 'revision': self.idx,
876 'revision': self.idx,
877 'message': self.message,
877 'message': self.message,
878 'date': self.date,
878 'date': self.date,
879 'author': self.author,
879 'author': self.author,
880 'parents': parents,
880 'parents': parents,
881 'branch': self.branch
881 'branch': self.branch
882 }
882 }
883
883
884 def __getstate__(self):
884 def __getstate__(self):
885 d = self.__dict__.copy()
885 d = self.__dict__.copy()
886 d.pop('_remote', None)
886 d.pop('_remote', None)
887 d.pop('repository', None)
887 d.pop('repository', None)
888 return d
888 return d
889
889
890 def _get_refs(self):
890 def _get_refs(self):
891 return {
891 return {
892 'branches': [self.branch] if self.branch else [],
892 'branches': [self.branch] if self.branch else [],
893 'bookmarks': getattr(self, 'bookmarks', []),
893 'bookmarks': getattr(self, 'bookmarks', []),
894 'tags': self.tags
894 'tags': self.tags
895 }
895 }
896
896
897 @LazyProperty
897 @LazyProperty
898 def last(self):
898 def last(self):
899 """
899 """
900 ``True`` if this is last commit in repository, ``False``
900 ``True`` if this is last commit in repository, ``False``
901 otherwise; trying to access this attribute while there is no
901 otherwise; trying to access this attribute while there is no
902 commits would raise `EmptyRepositoryError`
902 commits would raise `EmptyRepositoryError`
903 """
903 """
904 if self.repository is None:
904 if self.repository is None:
905 raise CommitError("Cannot check if it's most recent commit")
905 raise CommitError("Cannot check if it's most recent commit")
906 return self.raw_id == self.repository.commit_ids[-1]
906 return self.raw_id == self.repository.commit_ids[-1]
907
907
908 @LazyProperty
908 @LazyProperty
909 def parents(self):
909 def parents(self):
910 """
910 """
911 Returns list of parent commits.
911 Returns list of parent commits.
912 """
912 """
913 raise NotImplementedError
913 raise NotImplementedError
914
914
915 @LazyProperty
915 @LazyProperty
916 def first_parent(self):
916 def first_parent(self):
917 """
917 """
918 Returns list of parent commits.
918 Returns list of parent commits.
919 """
919 """
920 return self.parents[0] if self.parents else EmptyCommit()
920 return self.parents[0] if self.parents else EmptyCommit()
921
921
922 @property
922 @property
923 def merge(self):
923 def merge(self):
924 """
924 """
925 Returns boolean if commit is a merge.
925 Returns boolean if commit is a merge.
926 """
926 """
927 return len(self.parents) > 1
927 return len(self.parents) > 1
928
928
929 @LazyProperty
929 @LazyProperty
930 def children(self):
930 def children(self):
931 """
931 """
932 Returns list of child commits.
932 Returns list of child commits.
933 """
933 """
934 raise NotImplementedError
934 raise NotImplementedError
935
935
936 @LazyProperty
936 @LazyProperty
937 def id(self):
937 def id(self):
938 """
938 """
939 Returns string identifying this commit.
939 Returns string identifying this commit.
940 """
940 """
941 raise NotImplementedError
941 raise NotImplementedError
942
942
943 @LazyProperty
943 @LazyProperty
944 def raw_id(self):
944 def raw_id(self):
945 """
945 """
946 Returns raw string identifying this commit.
946 Returns raw string identifying this commit.
947 """
947 """
948 raise NotImplementedError
948 raise NotImplementedError
949
949
950 @LazyProperty
950 @LazyProperty
951 def short_id(self):
951 def short_id(self):
952 """
952 """
953 Returns shortened version of ``raw_id`` attribute, as string,
953 Returns shortened version of ``raw_id`` attribute, as string,
954 identifying this commit, useful for presentation to users.
954 identifying this commit, useful for presentation to users.
955 """
955 """
956 raise NotImplementedError
956 raise NotImplementedError
957
957
958 @LazyProperty
958 @LazyProperty
959 def idx(self):
959 def idx(self):
960 """
960 """
961 Returns integer identifying this commit.
961 Returns integer identifying this commit.
962 """
962 """
963 raise NotImplementedError
963 raise NotImplementedError
964
964
965 @LazyProperty
965 @LazyProperty
966 def committer(self):
966 def committer(self):
967 """
967 """
968 Returns committer for this commit
968 Returns committer for this commit
969 """
969 """
970 raise NotImplementedError
970 raise NotImplementedError
971
971
972 @LazyProperty
972 @LazyProperty
973 def committer_name(self):
973 def committer_name(self):
974 """
974 """
975 Returns committer name for this commit
975 Returns committer name for this commit
976 """
976 """
977
977
978 return author_name(self.committer)
978 return author_name(self.committer)
979
979
980 @LazyProperty
980 @LazyProperty
981 def committer_email(self):
981 def committer_email(self):
982 """
982 """
983 Returns committer email address for this commit
983 Returns committer email address for this commit
984 """
984 """
985
985
986 return author_email(self.committer)
986 return author_email(self.committer)
987
987
988 @LazyProperty
988 @LazyProperty
989 def author(self):
989 def author(self):
990 """
990 """
991 Returns author for this commit
991 Returns author for this commit
992 """
992 """
993
993
994 raise NotImplementedError
994 raise NotImplementedError
995
995
996 @LazyProperty
996 @LazyProperty
997 def author_name(self):
997 def author_name(self):
998 """
998 """
999 Returns author name for this commit
999 Returns author name for this commit
1000 """
1000 """
1001
1001
1002 return author_name(self.author)
1002 return author_name(self.author)
1003
1003
1004 @LazyProperty
1004 @LazyProperty
1005 def author_email(self):
1005 def author_email(self):
1006 """
1006 """
1007 Returns author email address for this commit
1007 Returns author email address for this commit
1008 """
1008 """
1009
1009
1010 return author_email(self.author)
1010 return author_email(self.author)
1011
1011
1012 def get_file_mode(self, path):
1012 def get_file_mode(self, path):
1013 """
1013 """
1014 Returns stat mode of the file at `path`.
1014 Returns stat mode of the file at `path`.
1015 """
1015 """
1016 raise NotImplementedError
1016 raise NotImplementedError
1017
1017
1018 def is_link(self, path):
1018 def is_link(self, path):
1019 """
1019 """
1020 Returns ``True`` if given `path` is a symlink
1020 Returns ``True`` if given `path` is a symlink
1021 """
1021 """
1022 raise NotImplementedError
1022 raise NotImplementedError
1023
1023
1024 def get_file_content(self, path):
1024 def get_file_content(self, path):
1025 """
1025 """
1026 Returns content of the file at the given `path`.
1026 Returns content of the file at the given `path`.
1027 """
1027 """
1028 raise NotImplementedError
1028 raise NotImplementedError
1029
1029
1030 def get_file_size(self, path):
1030 def get_file_size(self, path):
1031 """
1031 """
1032 Returns size of the file at the given `path`.
1032 Returns size of the file at the given `path`.
1033 """
1033 """
1034 raise NotImplementedError
1034 raise NotImplementedError
1035
1035
1036 def get_path_commit(self, path, pre_load=None):
1036 def get_path_commit(self, path, pre_load=None):
1037 """
1037 """
1038 Returns last commit of the file at the given `path`.
1038 Returns last commit of the file at the given `path`.
1039
1039
1040 :param pre_load: Optional. List of commit attributes to load.
1040 :param pre_load: Optional. List of commit attributes to load.
1041 """
1041 """
1042 commits = self.get_path_history(path, limit=1, pre_load=pre_load)
1042 commits = self.get_path_history(path, limit=1, pre_load=pre_load)
1043 if not commits:
1043 if not commits:
1044 raise RepositoryError(
1044 raise RepositoryError(
1045 'Failed to fetch history for path {}. '
1045 'Failed to fetch history for path {}. '
1046 'Please check if such path exists in your repository'.format(
1046 'Please check if such path exists in your repository'.format(
1047 path))
1047 path))
1048 return commits[0]
1048 return commits[0]
1049
1049
1050 def get_path_history(self, path, limit=None, pre_load=None):
1050 def get_path_history(self, path, limit=None, pre_load=None):
1051 """
1051 """
1052 Returns history of file as reversed list of :class:`BaseCommit`
1052 Returns history of file as reversed list of :class:`BaseCommit`
1053 objects for which file at given `path` has been modified.
1053 objects for which file at given `path` has been modified.
1054
1054
1055 :param limit: Optional. Allows to limit the size of the returned
1055 :param limit: Optional. Allows to limit the size of the returned
1056 history. This is intended as a hint to the underlying backend, so
1056 history. This is intended as a hint to the underlying backend, so
1057 that it can apply optimizations depending on the limit.
1057 that it can apply optimizations depending on the limit.
1058 :param pre_load: Optional. List of commit attributes to load.
1058 :param pre_load: Optional. List of commit attributes to load.
1059 """
1059 """
1060 raise NotImplementedError
1060 raise NotImplementedError
1061
1061
1062 def get_file_annotate(self, path, pre_load=None):
1062 def get_file_annotate(self, path, pre_load=None):
1063 """
1063 """
1064 Returns a generator of four element tuples with
1064 Returns a generator of four element tuples with
1065 lineno, sha, commit lazy loader and line
1065 lineno, sha, commit lazy loader and line
1066
1066
1067 :param pre_load: Optional. List of commit attributes to load.
1067 :param pre_load: Optional. List of commit attributes to load.
1068 """
1068 """
1069 raise NotImplementedError
1069 raise NotImplementedError
1070
1070
1071 def get_nodes(self, path):
1071 def get_nodes(self, path):
1072 """
1072 """
1073 Returns combined ``DirNode`` and ``FileNode`` objects list representing
1073 Returns combined ``DirNode`` and ``FileNode`` objects list representing
1074 state of commit at the given ``path``.
1074 state of commit at the given ``path``.
1075
1075
1076 :raises ``CommitError``: if node at the given ``path`` is not
1076 :raises ``CommitError``: if node at the given ``path`` is not
1077 instance of ``DirNode``
1077 instance of ``DirNode``
1078 """
1078 """
1079 raise NotImplementedError
1079 raise NotImplementedError
1080
1080
1081 def get_node(self, path):
1081 def get_node(self, path):
1082 """
1082 """
1083 Returns ``Node`` object from the given ``path``.
1083 Returns ``Node`` object from the given ``path``.
1084
1084
1085 :raises ``NodeDoesNotExistError``: if there is no node at the given
1085 :raises ``NodeDoesNotExistError``: if there is no node at the given
1086 ``path``
1086 ``path``
1087 """
1087 """
1088 raise NotImplementedError
1088 raise NotImplementedError
1089
1089
1090 def get_largefile_node(self, path):
1090 def get_largefile_node(self, path):
1091 """
1091 """
1092 Returns the path to largefile from Mercurial/Git-lfs storage.
1092 Returns the path to largefile from Mercurial/Git-lfs storage.
1093 or None if it's not a largefile node
1093 or None if it's not a largefile node
1094 """
1094 """
1095 return None
1095 return None
1096
1096
1097 def archive_repo(self, archive_dest_path, kind='tgz', subrepos=None,
1097 def archive_repo(self, archive_dest_path, kind='tgz', subrepos=None,
1098 prefix=None, write_metadata=False, mtime=None, archive_at_path='/'):
1098 prefix=None, write_metadata=False, mtime=None, archive_at_path='/'):
1099 """
1099 """
1100 Creates an archive containing the contents of the repository.
1100 Creates an archive containing the contents of the repository.
1101
1101
1102 :param archive_dest_path: path to the file which to create the archive.
1102 :param archive_dest_path: path to the file which to create the archive.
1103 :param kind: one of following: ``"tbz2"``, ``"tgz"``, ``"zip"``.
1103 :param kind: one of following: ``"tbz2"``, ``"tgz"``, ``"zip"``.
1104 :param prefix: name of root directory in archive.
1104 :param prefix: name of root directory in archive.
1105 Default is repository name and commit's short_id joined with dash:
1105 Default is repository name and commit's short_id joined with dash:
1106 ``"{repo_name}-{short_id}"``.
1106 ``"{repo_name}-{short_id}"``.
1107 :param write_metadata: write a metadata file into archive.
1107 :param write_metadata: write a metadata file into archive.
1108 :param mtime: custom modification time for archive creation, defaults
1108 :param mtime: custom modification time for archive creation, defaults
1109 to time.time() if not given.
1109 to time.time() if not given.
1110 :param archive_at_path: pack files at this path (default '/')
1110 :param archive_at_path: pack files at this path (default '/')
1111
1111
1112 :raise VCSError: If prefix has a problem.
1112 :raise VCSError: If prefix has a problem.
1113 """
1113 """
1114 allowed_kinds = settings.ARCHIVE_SPECS.keys()
1114 allowed_kinds = settings.ARCHIVE_SPECS.keys()
1115 if kind not in allowed_kinds:
1115 if kind not in allowed_kinds:
1116 raise ImproperArchiveTypeError(
1116 raise ImproperArchiveTypeError(
1117 'Archive kind (%s) not supported use one of %s' %
1117 'Archive kind (%s) not supported use one of %s' %
1118 (kind, allowed_kinds))
1118 (kind, allowed_kinds))
1119
1119
1120 prefix = self._validate_archive_prefix(prefix)
1120 prefix = self._validate_archive_prefix(prefix)
1121
1121
1122 mtime = mtime or time.mktime(self.date.timetuple())
1122 mtime = mtime or time.mktime(self.date.timetuple())
1123
1123
1124 file_info = []
1124 file_info = []
1125 cur_rev = self.repository.get_commit(commit_id=self.raw_id)
1125 cur_rev = self.repository.get_commit(commit_id=self.raw_id)
1126 for _r, _d, files in cur_rev.walk(archive_at_path):
1126 for _r, _d, files in cur_rev.walk(archive_at_path):
1127 for f in files:
1127 for f in files:
1128 f_path = os.path.join(prefix, f.path)
1128 f_path = os.path.join(prefix, f.path)
1129 file_info.append(
1129 file_info.append(
1130 (f_path, f.mode, f.is_link(), f.raw_bytes))
1130 (f_path, f.mode, f.is_link(), f.raw_bytes))
1131
1131
1132 if write_metadata:
1132 if write_metadata:
1133 metadata = [
1133 metadata = [
1134 ('repo_name', self.repository.name),
1134 ('repo_name', self.repository.name),
1135 ('commit_id', self.raw_id),
1135 ('commit_id', self.raw_id),
1136 ('rev', self.raw_id),
1136 ('rev', self.raw_id),
1137 ('create_time', mtime),
1137 ('create_time', mtime),
1138 ('branch', self.branch),
1138 ('branch', self.branch),
1139 ('tags', ','.join(self.tags)),
1139 ('tags', ','.join(self.tags)),
1140 ]
1140 ]
1141 meta = ["%s:%s" % (f_name, value) for f_name, value in metadata]
1141 meta = ["%s:%s" % (f_name, value) for f_name, value in metadata]
1142 file_info.append(('.archival.txt', 0o644, False, '\n'.join(meta)))
1142 file_info.append(('.archival.txt', 0o644, False, '\n'.join(meta)))
1143
1143
1144 connection.Hg.archive_repo(archive_dest_path, mtime, file_info, kind)
1144 connection.Hg.archive_repo(archive_dest_path, mtime, file_info, kind)
1145
1145
1146 def _validate_archive_prefix(self, prefix):
1146 def _validate_archive_prefix(self, prefix):
1147 if prefix is None:
1147 if prefix is None:
1148 prefix = self._ARCHIVE_PREFIX_TEMPLATE.format(
1148 prefix = self._ARCHIVE_PREFIX_TEMPLATE.format(
1149 repo_name=safe_str(self.repository.name),
1149 repo_name=safe_str(self.repository.name),
1150 short_id=self.short_id)
1150 short_id=self.short_id)
1151 elif not isinstance(prefix, str):
1151 elif not isinstance(prefix, str):
1152 raise ValueError("prefix not a bytes object: %s" % repr(prefix))
1152 raise ValueError("prefix not a bytes object: %s" % repr(prefix))
1153 elif prefix.startswith('/'):
1153 elif prefix.startswith('/'):
1154 raise VCSError("Prefix cannot start with leading slash")
1154 raise VCSError("Prefix cannot start with leading slash")
1155 elif prefix.strip() == '':
1155 elif prefix.strip() == '':
1156 raise VCSError("Prefix cannot be empty")
1156 raise VCSError("Prefix cannot be empty")
1157 return prefix
1157 return prefix
1158
1158
1159 @LazyProperty
1159 @LazyProperty
1160 def root(self):
1160 def root(self):
1161 """
1161 """
1162 Returns ``RootNode`` object for this commit.
1162 Returns ``RootNode`` object for this commit.
1163 """
1163 """
1164 return self.get_node('')
1164 return self.get_node('')
1165
1165
1166 def next(self, branch=None):
1166 def next(self, branch=None):
1167 """
1167 """
1168 Returns next commit from current, if branch is gives it will return
1168 Returns next commit from current, if branch is gives it will return
1169 next commit belonging to this branch
1169 next commit belonging to this branch
1170
1170
1171 :param branch: show commits within the given named branch
1171 :param branch: show commits within the given named branch
1172 """
1172 """
1173 indexes = xrange(self.idx + 1, self.repository.count())
1173 indexes = xrange(self.idx + 1, self.repository.count())
1174 return self._find_next(indexes, branch)
1174 return self._find_next(indexes, branch)
1175
1175
1176 def prev(self, branch=None):
1176 def prev(self, branch=None):
1177 """
1177 """
1178 Returns previous commit from current, if branch is gives it will
1178 Returns previous commit from current, if branch is gives it will
1179 return previous commit belonging to this branch
1179 return previous commit belonging to this branch
1180
1180
1181 :param branch: show commit within the given named branch
1181 :param branch: show commit within the given named branch
1182 """
1182 """
1183 indexes = xrange(self.idx - 1, -1, -1)
1183 indexes = xrange(self.idx - 1, -1, -1)
1184 return self._find_next(indexes, branch)
1184 return self._find_next(indexes, branch)
1185
1185
1186 def _find_next(self, indexes, branch=None):
1186 def _find_next(self, indexes, branch=None):
1187 if branch and self.branch != branch:
1187 if branch and self.branch != branch:
1188 raise VCSError('Branch option used on commit not belonging '
1188 raise VCSError('Branch option used on commit not belonging '
1189 'to that branch')
1189 'to that branch')
1190
1190
1191 for next_idx in indexes:
1191 for next_idx in indexes:
1192 commit = self.repository.get_commit(commit_idx=next_idx)
1192 commit = self.repository.get_commit(commit_idx=next_idx)
1193 if branch and branch != commit.branch:
1193 if branch and branch != commit.branch:
1194 continue
1194 continue
1195 return commit
1195 return commit
1196 raise CommitDoesNotExistError
1196 raise CommitDoesNotExistError
1197
1197
1198 def diff(self, ignore_whitespace=True, context=3):
1198 def diff(self, ignore_whitespace=True, context=3):
1199 """
1199 """
1200 Returns a `Diff` object representing the change made by this commit.
1200 Returns a `Diff` object representing the change made by this commit.
1201 """
1201 """
1202 parent = self.first_parent
1202 parent = self.first_parent
1203 diff = self.repository.get_diff(
1203 diff = self.repository.get_diff(
1204 parent, self,
1204 parent, self,
1205 ignore_whitespace=ignore_whitespace,
1205 ignore_whitespace=ignore_whitespace,
1206 context=context)
1206 context=context)
1207 return diff
1207 return diff
1208
1208
1209 @LazyProperty
1209 @LazyProperty
1210 def added(self):
1210 def added(self):
1211 """
1211 """
1212 Returns list of added ``FileNode`` objects.
1212 Returns list of added ``FileNode`` objects.
1213 """
1213 """
1214 raise NotImplementedError
1214 raise NotImplementedError
1215
1215
1216 @LazyProperty
1216 @LazyProperty
1217 def changed(self):
1217 def changed(self):
1218 """
1218 """
1219 Returns list of modified ``FileNode`` objects.
1219 Returns list of modified ``FileNode`` objects.
1220 """
1220 """
1221 raise NotImplementedError
1221 raise NotImplementedError
1222
1222
1223 @LazyProperty
1223 @LazyProperty
1224 def removed(self):
1224 def removed(self):
1225 """
1225 """
1226 Returns list of removed ``FileNode`` objects.
1226 Returns list of removed ``FileNode`` objects.
1227 """
1227 """
1228 raise NotImplementedError
1228 raise NotImplementedError
1229
1229
1230 @LazyProperty
1230 @LazyProperty
1231 def size(self):
1231 def size(self):
1232 """
1232 """
1233 Returns total number of bytes from contents of all filenodes.
1233 Returns total number of bytes from contents of all filenodes.
1234 """
1234 """
1235 return sum((node.size for node in self.get_filenodes_generator()))
1235 return sum((node.size for node in self.get_filenodes_generator()))
1236
1236
1237 def walk(self, topurl=''):
1237 def walk(self, topurl=''):
1238 """
1238 """
1239 Similar to os.walk method. Insted of filesystem it walks through
1239 Similar to os.walk method. Insted of filesystem it walks through
1240 commit starting at given ``topurl``. Returns generator of tuples
1240 commit starting at given ``topurl``. Returns generator of tuples
1241 (topnode, dirnodes, filenodes).
1241 (topnode, dirnodes, filenodes).
1242 """
1242 """
1243 topnode = self.get_node(topurl)
1243 topnode = self.get_node(topurl)
1244 if not topnode.is_dir():
1244 if not topnode.is_dir():
1245 return
1245 return
1246 yield (topnode, topnode.dirs, topnode.files)
1246 yield (topnode, topnode.dirs, topnode.files)
1247 for dirnode in topnode.dirs:
1247 for dirnode in topnode.dirs:
1248 for tup in self.walk(dirnode.path):
1248 for tup in self.walk(dirnode.path):
1249 yield tup
1249 yield tup
1250
1250
1251 def get_filenodes_generator(self):
1251 def get_filenodes_generator(self):
1252 """
1252 """
1253 Returns generator that yields *all* file nodes.
1253 Returns generator that yields *all* file nodes.
1254 """
1254 """
1255 for topnode, dirs, files in self.walk():
1255 for topnode, dirs, files in self.walk():
1256 for node in files:
1256 for node in files:
1257 yield node
1257 yield node
1258
1258
1259 #
1259 #
1260 # Utilities for sub classes to support consistent behavior
1260 # Utilities for sub classes to support consistent behavior
1261 #
1261 #
1262
1262
1263 def no_node_at_path(self, path):
1263 def no_node_at_path(self, path):
1264 return NodeDoesNotExistError(
1264 return NodeDoesNotExistError(
1265 u"There is no file nor directory at the given path: "
1265 u"There is no file nor directory at the given path: "
1266 u"`%s` at commit %s" % (safe_unicode(path), self.short_id))
1266 u"`%s` at commit %s" % (safe_unicode(path), self.short_id))
1267
1267
1268 def _fix_path(self, path):
1268 def _fix_path(self, path):
1269 """
1269 """
1270 Paths are stored without trailing slash so we need to get rid off it if
1270 Paths are stored without trailing slash so we need to get rid off it if
1271 needed.
1271 needed.
1272 """
1272 """
1273 return path.rstrip('/')
1273 return path.rstrip('/')
1274
1274
1275 #
1275 #
1276 # Deprecated API based on changesets
1276 # Deprecated API based on changesets
1277 #
1277 #
1278
1278
1279 @property
1279 @property
1280 def revision(self):
1280 def revision(self):
1281 warnings.warn("Use idx instead", DeprecationWarning)
1281 warnings.warn("Use idx instead", DeprecationWarning)
1282 return self.idx
1282 return self.idx
1283
1283
1284 @revision.setter
1284 @revision.setter
1285 def revision(self, value):
1285 def revision(self, value):
1286 warnings.warn("Use idx instead", DeprecationWarning)
1286 warnings.warn("Use idx instead", DeprecationWarning)
1287 self.idx = value
1287 self.idx = value
1288
1288
1289 def get_file_changeset(self, path):
1289 def get_file_changeset(self, path):
1290 warnings.warn("Use get_path_commit instead", DeprecationWarning)
1290 warnings.warn("Use get_path_commit instead", DeprecationWarning)
1291 return self.get_path_commit(path)
1291 return self.get_path_commit(path)
1292
1292
1293
1293
1294 class BaseChangesetClass(type):
1294 class BaseChangesetClass(type):
1295
1295
1296 def __instancecheck__(self, instance):
1296 def __instancecheck__(self, instance):
1297 return isinstance(instance, BaseCommit)
1297 return isinstance(instance, BaseCommit)
1298
1298
1299
1299
1300 class BaseChangeset(BaseCommit):
1300 class BaseChangeset(BaseCommit):
1301
1301
1302 __metaclass__ = BaseChangesetClass
1302 __metaclass__ = BaseChangesetClass
1303
1303
1304 def __new__(cls, *args, **kwargs):
1304 def __new__(cls, *args, **kwargs):
1305 warnings.warn(
1305 warnings.warn(
1306 "Use BaseCommit instead of BaseChangeset", DeprecationWarning)
1306 "Use BaseCommit instead of BaseChangeset", DeprecationWarning)
1307 return super(BaseChangeset, cls).__new__(cls, *args, **kwargs)
1307 return super(BaseChangeset, cls).__new__(cls, *args, **kwargs)
1308
1308
1309
1309
1310 class BaseInMemoryCommit(object):
1310 class BaseInMemoryCommit(object):
1311 """
1311 """
1312 Represents differences between repository's state (most recent head) and
1312 Represents differences between repository's state (most recent head) and
1313 changes made *in place*.
1313 changes made *in place*.
1314
1314
1315 **Attributes**
1315 **Attributes**
1316
1316
1317 ``repository``
1317 ``repository``
1318 repository object for this in-memory-commit
1318 repository object for this in-memory-commit
1319
1319
1320 ``added``
1320 ``added``
1321 list of ``FileNode`` objects marked as *added*
1321 list of ``FileNode`` objects marked as *added*
1322
1322
1323 ``changed``
1323 ``changed``
1324 list of ``FileNode`` objects marked as *changed*
1324 list of ``FileNode`` objects marked as *changed*
1325
1325
1326 ``removed``
1326 ``removed``
1327 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
1327 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
1328 *removed*
1328 *removed*
1329
1329
1330 ``parents``
1330 ``parents``
1331 list of :class:`BaseCommit` instances representing parents of
1331 list of :class:`BaseCommit` instances representing parents of
1332 in-memory commit. Should always be 2-element sequence.
1332 in-memory commit. Should always be 2-element sequence.
1333
1333
1334 """
1334 """
1335
1335
1336 def __init__(self, repository):
1336 def __init__(self, repository):
1337 self.repository = repository
1337 self.repository = repository
1338 self.added = []
1338 self.added = []
1339 self.changed = []
1339 self.changed = []
1340 self.removed = []
1340 self.removed = []
1341 self.parents = []
1341 self.parents = []
1342
1342
1343 def add(self, *filenodes):
1343 def add(self, *filenodes):
1344 """
1344 """
1345 Marks given ``FileNode`` objects as *to be committed*.
1345 Marks given ``FileNode`` objects as *to be committed*.
1346
1346
1347 :raises ``NodeAlreadyExistsError``: if node with same path exists at
1347 :raises ``NodeAlreadyExistsError``: if node with same path exists at
1348 latest commit
1348 latest commit
1349 :raises ``NodeAlreadyAddedError``: if node with same path is already
1349 :raises ``NodeAlreadyAddedError``: if node with same path is already
1350 marked as *added*
1350 marked as *added*
1351 """
1351 """
1352 # Check if not already marked as *added* first
1352 # Check if not already marked as *added* first
1353 for node in filenodes:
1353 for node in filenodes:
1354 if node.path in (n.path for n in self.added):
1354 if node.path in (n.path for n in self.added):
1355 raise NodeAlreadyAddedError(
1355 raise NodeAlreadyAddedError(
1356 "Such FileNode %s is already marked for addition"
1356 "Such FileNode %s is already marked for addition"
1357 % node.path)
1357 % node.path)
1358 for node in filenodes:
1358 for node in filenodes:
1359 self.added.append(node)
1359 self.added.append(node)
1360
1360
1361 def change(self, *filenodes):
1361 def change(self, *filenodes):
1362 """
1362 """
1363 Marks given ``FileNode`` objects to be *changed* in next commit.
1363 Marks given ``FileNode`` objects to be *changed* in next commit.
1364
1364
1365 :raises ``EmptyRepositoryError``: if there are no commits yet
1365 :raises ``EmptyRepositoryError``: if there are no commits yet
1366 :raises ``NodeAlreadyExistsError``: if node with same path is already
1366 :raises ``NodeAlreadyExistsError``: if node with same path is already
1367 marked to be *changed*
1367 marked to be *changed*
1368 :raises ``NodeAlreadyRemovedError``: if node with same path is already
1368 :raises ``NodeAlreadyRemovedError``: if node with same path is already
1369 marked to be *removed*
1369 marked to be *removed*
1370 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
1370 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
1371 commit
1371 commit
1372 :raises ``NodeNotChangedError``: if node hasn't really be changed
1372 :raises ``NodeNotChangedError``: if node hasn't really be changed
1373 """
1373 """
1374 for node in filenodes:
1374 for node in filenodes:
1375 if node.path in (n.path for n in self.removed):
1375 if node.path in (n.path for n in self.removed):
1376 raise NodeAlreadyRemovedError(
1376 raise NodeAlreadyRemovedError(
1377 "Node at %s is already marked as removed" % node.path)
1377 "Node at %s is already marked as removed" % node.path)
1378 try:
1378 try:
1379 self.repository.get_commit()
1379 self.repository.get_commit()
1380 except EmptyRepositoryError:
1380 except EmptyRepositoryError:
1381 raise EmptyRepositoryError(
1381 raise EmptyRepositoryError(
1382 "Nothing to change - try to *add* new nodes rather than "
1382 "Nothing to change - try to *add* new nodes rather than "
1383 "changing them")
1383 "changing them")
1384 for node in filenodes:
1384 for node in filenodes:
1385 if node.path in (n.path for n in self.changed):
1385 if node.path in (n.path for n in self.changed):
1386 raise NodeAlreadyChangedError(
1386 raise NodeAlreadyChangedError(
1387 "Node at '%s' is already marked as changed" % node.path)
1387 "Node at '%s' is already marked as changed" % node.path)
1388 self.changed.append(node)
1388 self.changed.append(node)
1389
1389
1390 def remove(self, *filenodes):
1390 def remove(self, *filenodes):
1391 """
1391 """
1392 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
1392 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
1393 *removed* in next commit.
1393 *removed* in next commit.
1394
1394
1395 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
1395 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
1396 be *removed*
1396 be *removed*
1397 :raises ``NodeAlreadyChangedError``: if node has been already marked to
1397 :raises ``NodeAlreadyChangedError``: if node has been already marked to
1398 be *changed*
1398 be *changed*
1399 """
1399 """
1400 for node in filenodes:
1400 for node in filenodes:
1401 if node.path in (n.path for n in self.removed):
1401 if node.path in (n.path for n in self.removed):
1402 raise NodeAlreadyRemovedError(
1402 raise NodeAlreadyRemovedError(
1403 "Node is already marked to for removal at %s" % node.path)
1403 "Node is already marked to for removal at %s" % node.path)
1404 if node.path in (n.path for n in self.changed):
1404 if node.path in (n.path for n in self.changed):
1405 raise NodeAlreadyChangedError(
1405 raise NodeAlreadyChangedError(
1406 "Node is already marked to be changed at %s" % node.path)
1406 "Node is already marked to be changed at %s" % node.path)
1407 # We only mark node as *removed* - real removal is done by
1407 # We only mark node as *removed* - real removal is done by
1408 # commit method
1408 # commit method
1409 self.removed.append(node)
1409 self.removed.append(node)
1410
1410
1411 def reset(self):
1411 def reset(self):
1412 """
1412 """
1413 Resets this instance to initial state (cleans ``added``, ``changed``
1413 Resets this instance to initial state (cleans ``added``, ``changed``
1414 and ``removed`` lists).
1414 and ``removed`` lists).
1415 """
1415 """
1416 self.added = []
1416 self.added = []
1417 self.changed = []
1417 self.changed = []
1418 self.removed = []
1418 self.removed = []
1419 self.parents = []
1419 self.parents = []
1420
1420
1421 def get_ipaths(self):
1421 def get_ipaths(self):
1422 """
1422 """
1423 Returns generator of paths from nodes marked as added, changed or
1423 Returns generator of paths from nodes marked as added, changed or
1424 removed.
1424 removed.
1425 """
1425 """
1426 for node in itertools.chain(self.added, self.changed, self.removed):
1426 for node in itertools.chain(self.added, self.changed, self.removed):
1427 yield node.path
1427 yield node.path
1428
1428
1429 def get_paths(self):
1429 def get_paths(self):
1430 """
1430 """
1431 Returns list of paths from nodes marked as added, changed or removed.
1431 Returns list of paths from nodes marked as added, changed or removed.
1432 """
1432 """
1433 return list(self.get_ipaths())
1433 return list(self.get_ipaths())
1434
1434
1435 def check_integrity(self, parents=None):
1435 def check_integrity(self, parents=None):
1436 """
1436 """
1437 Checks in-memory commit's integrity. Also, sets parents if not
1437 Checks in-memory commit's integrity. Also, sets parents if not
1438 already set.
1438 already set.
1439
1439
1440 :raises CommitError: if any error occurs (i.e.
1440 :raises CommitError: if any error occurs (i.e.
1441 ``NodeDoesNotExistError``).
1441 ``NodeDoesNotExistError``).
1442 """
1442 """
1443 if not self.parents:
1443 if not self.parents:
1444 parents = parents or []
1444 parents = parents or []
1445 if len(parents) == 0:
1445 if len(parents) == 0:
1446 try:
1446 try:
1447 parents = [self.repository.get_commit(), None]
1447 parents = [self.repository.get_commit(), None]
1448 except EmptyRepositoryError:
1448 except EmptyRepositoryError:
1449 parents = [None, None]
1449 parents = [None, None]
1450 elif len(parents) == 1:
1450 elif len(parents) == 1:
1451 parents += [None]
1451 parents += [None]
1452 self.parents = parents
1452 self.parents = parents
1453
1453
1454 # Local parents, only if not None
1454 # Local parents, only if not None
1455 parents = [p for p in self.parents if p]
1455 parents = [p for p in self.parents if p]
1456
1456
1457 # Check nodes marked as added
1457 # Check nodes marked as added
1458 for p in parents:
1458 for p in parents:
1459 for node in self.added:
1459 for node in self.added:
1460 try:
1460 try:
1461 p.get_node(node.path)
1461 p.get_node(node.path)
1462 except NodeDoesNotExistError:
1462 except NodeDoesNotExistError:
1463 pass
1463 pass
1464 else:
1464 else:
1465 raise NodeAlreadyExistsError(
1465 raise NodeAlreadyExistsError(
1466 "Node `%s` already exists at %s" % (node.path, p))
1466 "Node `%s` already exists at %s" % (node.path, p))
1467
1467
1468 # Check nodes marked as changed
1468 # Check nodes marked as changed
1469 missing = set(self.changed)
1469 missing = set(self.changed)
1470 not_changed = set(self.changed)
1470 not_changed = set(self.changed)
1471 if self.changed and not parents:
1471 if self.changed and not parents:
1472 raise NodeDoesNotExistError(str(self.changed[0].path))
1472 raise NodeDoesNotExistError(str(self.changed[0].path))
1473 for p in parents:
1473 for p in parents:
1474 for node in self.changed:
1474 for node in self.changed:
1475 try:
1475 try:
1476 old = p.get_node(node.path)
1476 old = p.get_node(node.path)
1477 missing.remove(node)
1477 missing.remove(node)
1478 # if content actually changed, remove node from not_changed
1478 # if content actually changed, remove node from not_changed
1479 if old.content != node.content:
1479 if old.content != node.content:
1480 not_changed.remove(node)
1480 not_changed.remove(node)
1481 except NodeDoesNotExistError:
1481 except NodeDoesNotExistError:
1482 pass
1482 pass
1483 if self.changed and missing:
1483 if self.changed and missing:
1484 raise NodeDoesNotExistError(
1484 raise NodeDoesNotExistError(
1485 "Node `%s` marked as modified but missing in parents: %s"
1485 "Node `%s` marked as modified but missing in parents: %s"
1486 % (node.path, parents))
1486 % (node.path, parents))
1487
1487
1488 if self.changed and not_changed:
1488 if self.changed and not_changed:
1489 raise NodeNotChangedError(
1489 raise NodeNotChangedError(
1490 "Node `%s` wasn't actually changed (parents: %s)"
1490 "Node `%s` wasn't actually changed (parents: %s)"
1491 % (not_changed.pop().path, parents))
1491 % (not_changed.pop().path, parents))
1492
1492
1493 # Check nodes marked as removed
1493 # Check nodes marked as removed
1494 if self.removed and not parents:
1494 if self.removed and not parents:
1495 raise NodeDoesNotExistError(
1495 raise NodeDoesNotExistError(
1496 "Cannot remove node at %s as there "
1496 "Cannot remove node at %s as there "
1497 "were no parents specified" % self.removed[0].path)
1497 "were no parents specified" % self.removed[0].path)
1498 really_removed = set()
1498 really_removed = set()
1499 for p in parents:
1499 for p in parents:
1500 for node in self.removed:
1500 for node in self.removed:
1501 try:
1501 try:
1502 p.get_node(node.path)
1502 p.get_node(node.path)
1503 really_removed.add(node)
1503 really_removed.add(node)
1504 except CommitError:
1504 except CommitError:
1505 pass
1505 pass
1506 not_removed = set(self.removed) - really_removed
1506 not_removed = set(self.removed) - really_removed
1507 if not_removed:
1507 if not_removed:
1508 # TODO: johbo: This code branch does not seem to be covered
1508 # TODO: johbo: This code branch does not seem to be covered
1509 raise NodeDoesNotExistError(
1509 raise NodeDoesNotExistError(
1510 "Cannot remove node at %s from "
1510 "Cannot remove node at %s from "
1511 "following parents: %s" % (not_removed, parents))
1511 "following parents: %s" % (not_removed, parents))
1512
1512
1513 def commit(
1513 def commit(
1514 self, message, author, parents=None, branch=None, date=None,
1514 self, message, author, parents=None, branch=None, date=None,
1515 **kwargs):
1515 **kwargs):
1516 """
1516 """
1517 Performs in-memory commit (doesn't check workdir in any way) and
1517 Performs in-memory commit (doesn't check workdir in any way) and
1518 returns newly created :class:`BaseCommit`. Updates repository's
1518 returns newly created :class:`BaseCommit`. Updates repository's
1519 attribute `commits`.
1519 attribute `commits`.
1520
1520
1521 .. note::
1521 .. note::
1522
1522
1523 While overriding this method each backend's should call
1523 While overriding this method each backend's should call
1524 ``self.check_integrity(parents)`` in the first place.
1524 ``self.check_integrity(parents)`` in the first place.
1525
1525
1526 :param message: message of the commit
1526 :param message: message of the commit
1527 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
1527 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
1528 :param parents: single parent or sequence of parents from which commit
1528 :param parents: single parent or sequence of parents from which commit
1529 would be derived
1529 would be derived
1530 :param date: ``datetime.datetime`` instance. Defaults to
1530 :param date: ``datetime.datetime`` instance. Defaults to
1531 ``datetime.datetime.now()``.
1531 ``datetime.datetime.now()``.
1532 :param branch: branch name, as string. If none given, default backend's
1532 :param branch: branch name, as string. If none given, default backend's
1533 branch would be used.
1533 branch would be used.
1534
1534
1535 :raises ``CommitError``: if any error occurs while committing
1535 :raises ``CommitError``: if any error occurs while committing
1536 """
1536 """
1537 raise NotImplementedError
1537 raise NotImplementedError
1538
1538
1539
1539
1540 class BaseInMemoryChangesetClass(type):
1540 class BaseInMemoryChangesetClass(type):
1541
1541
1542 def __instancecheck__(self, instance):
1542 def __instancecheck__(self, instance):
1543 return isinstance(instance, BaseInMemoryCommit)
1543 return isinstance(instance, BaseInMemoryCommit)
1544
1544
1545
1545
1546 class BaseInMemoryChangeset(BaseInMemoryCommit):
1546 class BaseInMemoryChangeset(BaseInMemoryCommit):
1547
1547
1548 __metaclass__ = BaseInMemoryChangesetClass
1548 __metaclass__ = BaseInMemoryChangesetClass
1549
1549
1550 def __new__(cls, *args, **kwargs):
1550 def __new__(cls, *args, **kwargs):
1551 warnings.warn(
1551 warnings.warn(
1552 "Use BaseCommit instead of BaseInMemoryCommit", DeprecationWarning)
1552 "Use BaseCommit instead of BaseInMemoryCommit", DeprecationWarning)
1553 return super(BaseInMemoryChangeset, cls).__new__(cls, *args, **kwargs)
1553 return super(BaseInMemoryChangeset, cls).__new__(cls, *args, **kwargs)
1554
1554
1555
1555
1556 class EmptyCommit(BaseCommit):
1556 class EmptyCommit(BaseCommit):
1557 """
1557 """
1558 An dummy empty commit. It's possible to pass hash when creating
1558 An dummy empty commit. It's possible to pass hash when creating
1559 an EmptyCommit
1559 an EmptyCommit
1560 """
1560 """
1561
1561
1562 def __init__(
1562 def __init__(
1563 self, commit_id='0' * 40, repo=None, alias=None, idx=-1,
1563 self, commit_id='0' * 40, repo=None, alias=None, idx=-1,
1564 message='', author='', date=None):
1564 message='', author='', date=None):
1565 self._empty_commit_id = commit_id
1565 self._empty_commit_id = commit_id
1566 # TODO: johbo: Solve idx parameter, default value does not make
1566 # TODO: johbo: Solve idx parameter, default value does not make
1567 # too much sense
1567 # too much sense
1568 self.idx = idx
1568 self.idx = idx
1569 self.message = message
1569 self.message = message
1570 self.author = author
1570 self.author = author
1571 self.date = date or datetime.datetime.fromtimestamp(0)
1571 self.date = date or datetime.datetime.fromtimestamp(0)
1572 self.repository = repo
1572 self.repository = repo
1573 self.alias = alias
1573 self.alias = alias
1574
1574
1575 @LazyProperty
1575 @LazyProperty
1576 def raw_id(self):
1576 def raw_id(self):
1577 """
1577 """
1578 Returns raw string identifying this commit, useful for web
1578 Returns raw string identifying this commit, useful for web
1579 representation.
1579 representation.
1580 """
1580 """
1581
1581
1582 return self._empty_commit_id
1582 return self._empty_commit_id
1583
1583
1584 @LazyProperty
1584 @LazyProperty
1585 def branch(self):
1585 def branch(self):
1586 if self.alias:
1586 if self.alias:
1587 from rhodecode.lib.vcs.backends import get_backend
1587 from rhodecode.lib.vcs.backends import get_backend
1588 return get_backend(self.alias).DEFAULT_BRANCH_NAME
1588 return get_backend(self.alias).DEFAULT_BRANCH_NAME
1589
1589
1590 @LazyProperty
1590 @LazyProperty
1591 def short_id(self):
1591 def short_id(self):
1592 return self.raw_id[:12]
1592 return self.raw_id[:12]
1593
1593
1594 @LazyProperty
1594 @LazyProperty
1595 def id(self):
1595 def id(self):
1596 return self.raw_id
1596 return self.raw_id
1597
1597
1598 def get_path_commit(self, path):
1598 def get_path_commit(self, path):
1599 return self
1599 return self
1600
1600
1601 def get_file_content(self, path):
1601 def get_file_content(self, path):
1602 return u''
1602 return u''
1603
1603
1604 def get_file_size(self, path):
1604 def get_file_size(self, path):
1605 return 0
1605 return 0
1606
1606
1607
1607
1608 class EmptyChangesetClass(type):
1608 class EmptyChangesetClass(type):
1609
1609
1610 def __instancecheck__(self, instance):
1610 def __instancecheck__(self, instance):
1611 return isinstance(instance, EmptyCommit)
1611 return isinstance(instance, EmptyCommit)
1612
1612
1613
1613
1614 class EmptyChangeset(EmptyCommit):
1614 class EmptyChangeset(EmptyCommit):
1615
1615
1616 __metaclass__ = EmptyChangesetClass
1616 __metaclass__ = EmptyChangesetClass
1617
1617
1618 def __new__(cls, *args, **kwargs):
1618 def __new__(cls, *args, **kwargs):
1619 warnings.warn(
1619 warnings.warn(
1620 "Use EmptyCommit instead of EmptyChangeset", DeprecationWarning)
1620 "Use EmptyCommit instead of EmptyChangeset", DeprecationWarning)
1621 return super(EmptyCommit, cls).__new__(cls, *args, **kwargs)
1621 return super(EmptyCommit, cls).__new__(cls, *args, **kwargs)
1622
1622
1623 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
1623 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
1624 alias=None, revision=-1, message='', author='', date=None):
1624 alias=None, revision=-1, message='', author='', date=None):
1625 if requested_revision is not None:
1625 if requested_revision is not None:
1626 warnings.warn(
1626 warnings.warn(
1627 "Parameter requested_revision not supported anymore",
1627 "Parameter requested_revision not supported anymore",
1628 DeprecationWarning)
1628 DeprecationWarning)
1629 super(EmptyChangeset, self).__init__(
1629 super(EmptyChangeset, self).__init__(
1630 commit_id=cs, repo=repo, alias=alias, idx=revision,
1630 commit_id=cs, repo=repo, alias=alias, idx=revision,
1631 message=message, author=author, date=date)
1631 message=message, author=author, date=date)
1632
1632
1633 @property
1633 @property
1634 def revision(self):
1634 def revision(self):
1635 warnings.warn("Use idx instead", DeprecationWarning)
1635 warnings.warn("Use idx instead", DeprecationWarning)
1636 return self.idx
1636 return self.idx
1637
1637
1638 @revision.setter
1638 @revision.setter
1639 def revision(self, value):
1639 def revision(self, value):
1640 warnings.warn("Use idx instead", DeprecationWarning)
1640 warnings.warn("Use idx instead", DeprecationWarning)
1641 self.idx = value
1641 self.idx = value
1642
1642
1643
1643
1644 class EmptyRepository(BaseRepository):
1644 class EmptyRepository(BaseRepository):
1645 def __init__(self, repo_path=None, config=None, create=False, **kwargs):
1645 def __init__(self, repo_path=None, config=None, create=False, **kwargs):
1646 pass
1646 pass
1647
1647
1648 def get_diff(self, *args, **kwargs):
1648 def get_diff(self, *args, **kwargs):
1649 from rhodecode.lib.vcs.backends.git.diff import GitDiff
1649 from rhodecode.lib.vcs.backends.git.diff import GitDiff
1650 return GitDiff('')
1650 return GitDiff('')
1651
1651
1652
1652
1653 class CollectionGenerator(object):
1653 class CollectionGenerator(object):
1654
1654
1655 def __init__(self, repo, commit_ids, collection_size=None, pre_load=None, translate_tag=None):
1655 def __init__(self, repo, commit_ids, collection_size=None, pre_load=None, translate_tag=None):
1656 self.repo = repo
1656 self.repo = repo
1657 self.commit_ids = commit_ids
1657 self.commit_ids = commit_ids
1658 # TODO: (oliver) this isn't currently hooked up
1658 # TODO: (oliver) this isn't currently hooked up
1659 self.collection_size = None
1659 self.collection_size = None
1660 self.pre_load = pre_load
1660 self.pre_load = pre_load
1661 self.translate_tag = translate_tag
1661 self.translate_tag = translate_tag
1662
1662
1663 def __len__(self):
1663 def __len__(self):
1664 if self.collection_size is not None:
1664 if self.collection_size is not None:
1665 return self.collection_size
1665 return self.collection_size
1666 return self.commit_ids.__len__()
1666 return self.commit_ids.__len__()
1667
1667
1668 def __iter__(self):
1668 def __iter__(self):
1669 for commit_id in self.commit_ids:
1669 for commit_id in self.commit_ids:
1670 # TODO: johbo: Mercurial passes in commit indices or commit ids
1670 # TODO: johbo: Mercurial passes in commit indices or commit ids
1671 yield self._commit_factory(commit_id)
1671 yield self._commit_factory(commit_id)
1672
1672
1673 def _commit_factory(self, commit_id):
1673 def _commit_factory(self, commit_id):
1674 """
1674 """
1675 Allows backends to override the way commits are generated.
1675 Allows backends to override the way commits are generated.
1676 """
1676 """
1677 return self.repo.get_commit(
1677 return self.repo.get_commit(
1678 commit_id=commit_id, pre_load=self.pre_load,
1678 commit_id=commit_id, pre_load=self.pre_load,
1679 translate_tag=self.translate_tag)
1679 translate_tag=self.translate_tag)
1680
1680
1681 def __getslice__(self, i, j):
1681 def __getslice__(self, i, j):
1682 """
1682 """
1683 Returns an iterator of sliced repository
1683 Returns an iterator of sliced repository
1684 """
1684 """
1685 commit_ids = self.commit_ids[i:j]
1685 commit_ids = self.commit_ids[i:j]
1686 return self.__class__(
1686 return self.__class__(
1687 self.repo, commit_ids, pre_load=self.pre_load,
1687 self.repo, commit_ids, pre_load=self.pre_load,
1688 translate_tag=self.translate_tag)
1688 translate_tag=self.translate_tag)
1689
1689
1690 def __repr__(self):
1690 def __repr__(self):
1691 return '<CollectionGenerator[len:%s]>' % (self.__len__())
1691 return '<CollectionGenerator[len:%s]>' % (self.__len__())
1692
1692
1693
1693
1694 class Config(object):
1694 class Config(object):
1695 """
1695 """
1696 Represents the configuration for a repository.
1696 Represents the configuration for a repository.
1697
1697
1698 The API is inspired by :class:`ConfigParser.ConfigParser` from the
1698 The API is inspired by :class:`ConfigParser.ConfigParser` from the
1699 standard library. It implements only the needed subset.
1699 standard library. It implements only the needed subset.
1700 """
1700 """
1701
1701
1702 def __init__(self):
1702 def __init__(self):
1703 self._values = {}
1703 self._values = {}
1704
1704
1705 def copy(self):
1705 def copy(self):
1706 clone = Config()
1706 clone = Config()
1707 for section, values in self._values.items():
1707 for section, values in self._values.items():
1708 clone._values[section] = values.copy()
1708 clone._values[section] = values.copy()
1709 return clone
1709 return clone
1710
1710
1711 def __repr__(self):
1711 def __repr__(self):
1712 return '<Config(%s sections) at %s>' % (
1712 return '<Config(%s sections) at %s>' % (
1713 len(self._values), hex(id(self)))
1713 len(self._values), hex(id(self)))
1714
1714
1715 def items(self, section):
1715 def items(self, section):
1716 return self._values.get(section, {}).iteritems()
1716 return self._values.get(section, {}).iteritems()
1717
1717
1718 def get(self, section, option):
1718 def get(self, section, option):
1719 return self._values.get(section, {}).get(option)
1719 return self._values.get(section, {}).get(option)
1720
1720
1721 def set(self, section, option, value):
1721 def set(self, section, option, value):
1722 section_values = self._values.setdefault(section, {})
1722 section_values = self._values.setdefault(section, {})
1723 section_values[option] = value
1723 section_values[option] = value
1724
1724
1725 def clear_section(self, section):
1725 def clear_section(self, section):
1726 self._values[section] = {}
1726 self._values[section] = {}
1727
1727
1728 def serialize(self):
1728 def serialize(self):
1729 """
1729 """
1730 Creates a list of three tuples (section, key, value) representing
1730 Creates a list of three tuples (section, key, value) representing
1731 this config object.
1731 this config object.
1732 """
1732 """
1733 items = []
1733 items = []
1734 for section in self._values:
1734 for section in self._values:
1735 for option, value in self._values[section].items():
1735 for option, value in self._values[section].items():
1736 items.append(
1736 items.append(
1737 (safe_str(section), safe_str(option), safe_str(value)))
1737 (safe_str(section), safe_str(option), safe_str(value)))
1738 return items
1738 return items
1739
1739
1740
1740
1741 class Diff(object):
1741 class Diff(object):
1742 """
1742 """
1743 Represents a diff result from a repository backend.
1743 Represents a diff result from a repository backend.
1744
1744
1745 Subclasses have to provide a backend specific value for
1745 Subclasses have to provide a backend specific value for
1746 :attr:`_header_re` and :attr:`_meta_re`.
1746 :attr:`_header_re` and :attr:`_meta_re`.
1747 """
1747 """
1748 _meta_re = None
1748 _meta_re = None
1749 _header_re = None
1749 _header_re = None
1750
1750
1751 def __init__(self, raw_diff):
1751 def __init__(self, raw_diff):
1752 self.raw = raw_diff
1752 self.raw = raw_diff
1753
1753
1754 def chunks(self):
1754 def chunks(self):
1755 """
1755 """
1756 split the diff in chunks of separate --git a/file b/file chunks
1756 split the diff in chunks of separate --git a/file b/file chunks
1757 to make diffs consistent we must prepend with \n, and make sure
1757 to make diffs consistent we must prepend with \n, and make sure
1758 we can detect last chunk as this was also has special rule
1758 we can detect last chunk as this was also has special rule
1759 """
1759 """
1760
1760
1761 diff_parts = ('\n' + self.raw).split('\ndiff --git')
1761 diff_parts = ('\n' + self.raw).split('\ndiff --git')
1762 header = diff_parts[0]
1762 header = diff_parts[0]
1763
1763
1764 if self._meta_re:
1764 if self._meta_re:
1765 match = self._meta_re.match(header)
1765 match = self._meta_re.match(header)
1766
1766
1767 chunks = diff_parts[1:]
1767 chunks = diff_parts[1:]
1768 total_chunks = len(chunks)
1768 total_chunks = len(chunks)
1769
1769
1770 return (
1770 return (
1771 DiffChunk(chunk, self, cur_chunk == total_chunks)
1771 DiffChunk(chunk, self, cur_chunk == total_chunks)
1772 for cur_chunk, chunk in enumerate(chunks, start=1))
1772 for cur_chunk, chunk in enumerate(chunks, start=1))
1773
1773
1774
1774
1775 class DiffChunk(object):
1775 class DiffChunk(object):
1776
1776
1777 def __init__(self, chunk, diff, last_chunk):
1777 def __init__(self, chunk, diff, last_chunk):
1778 self._diff = diff
1778 self._diff = diff
1779
1779
1780 # since we split by \ndiff --git that part is lost from original diff
1780 # since we split by \ndiff --git that part is lost from original diff
1781 # we need to re-apply it at the end, EXCEPT ! if it's last chunk
1781 # we need to re-apply it at the end, EXCEPT ! if it's last chunk
1782 if not last_chunk:
1782 if not last_chunk:
1783 chunk += '\n'
1783 chunk += '\n'
1784
1784
1785 match = self._diff._header_re.match(chunk)
1785 match = self._diff._header_re.match(chunk)
1786 self.header = match.groupdict()
1786 self.header = match.groupdict()
1787 self.diff = chunk[match.end():]
1787 self.diff = chunk[match.end():]
1788 self.raw = chunk
1788 self.raw = chunk
1789
1789
1790
1790
1791 class BasePathPermissionChecker(object):
1791 class BasePathPermissionChecker(object):
1792
1792
1793 @staticmethod
1793 @staticmethod
1794 def create_from_patterns(includes, excludes):
1794 def create_from_patterns(includes, excludes):
1795 if includes and '*' in includes and not excludes:
1795 if includes and '*' in includes and not excludes:
1796 return AllPathPermissionChecker()
1796 return AllPathPermissionChecker()
1797 elif excludes and '*' in excludes:
1797 elif excludes and '*' in excludes:
1798 return NonePathPermissionChecker()
1798 return NonePathPermissionChecker()
1799 else:
1799 else:
1800 return PatternPathPermissionChecker(includes, excludes)
1800 return PatternPathPermissionChecker(includes, excludes)
1801
1801
1802 @property
1802 @property
1803 def has_full_access(self):
1803 def has_full_access(self):
1804 raise NotImplemented()
1804 raise NotImplemented()
1805
1805
1806 def has_access(self, path):
1806 def has_access(self, path):
1807 raise NotImplemented()
1807 raise NotImplemented()
1808
1808
1809
1809
1810 class AllPathPermissionChecker(BasePathPermissionChecker):
1810 class AllPathPermissionChecker(BasePathPermissionChecker):
1811
1811
1812 @property
1812 @property
1813 def has_full_access(self):
1813 def has_full_access(self):
1814 return True
1814 return True
1815
1815
1816 def has_access(self, path):
1816 def has_access(self, path):
1817 return True
1817 return True
1818
1818
1819
1819
1820 class NonePathPermissionChecker(BasePathPermissionChecker):
1820 class NonePathPermissionChecker(BasePathPermissionChecker):
1821
1821
1822 @property
1822 @property
1823 def has_full_access(self):
1823 def has_full_access(self):
1824 return False
1824 return False
1825
1825
1826 def has_access(self, path):
1826 def has_access(self, path):
1827 return False
1827 return False
1828
1828
1829
1829
1830 class PatternPathPermissionChecker(BasePathPermissionChecker):
1830 class PatternPathPermissionChecker(BasePathPermissionChecker):
1831
1831
1832 def __init__(self, includes, excludes):
1832 def __init__(self, includes, excludes):
1833 self.includes = includes
1833 self.includes = includes
1834 self.excludes = excludes
1834 self.excludes = excludes
1835 self.includes_re = [] if not includes else [
1835 self.includes_re = [] if not includes else [
1836 re.compile(fnmatch.translate(pattern)) for pattern in includes]
1836 re.compile(fnmatch.translate(pattern)) for pattern in includes]
1837 self.excludes_re = [] if not excludes else [
1837 self.excludes_re = [] if not excludes else [
1838 re.compile(fnmatch.translate(pattern)) for pattern in excludes]
1838 re.compile(fnmatch.translate(pattern)) for pattern in excludes]
1839
1839
1840 @property
1840 @property
1841 def has_full_access(self):
1841 def has_full_access(self):
1842 return '*' in self.includes and not self.excludes
1842 return '*' in self.includes and not self.excludes
1843
1843
1844 def has_access(self, path):
1844 def has_access(self, path):
1845 for regex in self.excludes_re:
1845 for regex in self.excludes_re:
1846 if regex.match(path):
1846 if regex.match(path):
1847 return False
1847 return False
1848 for regex in self.includes_re:
1848 for regex in self.includes_re:
1849 if regex.match(path):
1849 if regex.match(path):
1850 return True
1850 return True
1851 return False
1851 return False
@@ -1,5149 +1,5149 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 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import warnings
32 import warnings
33 import ipaddress
33 import ipaddress
34 import functools
34 import functools
35 import traceback
35 import traceback
36 import collections
36 import collections
37
37
38 from sqlalchemy import (
38 from sqlalchemy import (
39 or_, and_, not_, func, TypeDecorator, event,
39 or_, and_, not_, func, TypeDecorator, event,
40 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Text, Float, PickleType)
42 Text, Float, PickleType)
43 from sqlalchemy.sql.expression import true, false, case
43 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.orm import (
45 from sqlalchemy.orm import (
46 relationship, joinedload, class_mapper, validates, aliased)
46 relationship, joinedload, class_mapper, validates, aliased)
47 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.dialects.mysql import LONGTEXT
50 from sqlalchemy.dialects.mysql import LONGTEXT
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from pyramid import compat
52 from pyramid import compat
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54 from webhelpers.text import collapse, remove_formatting
54 from webhelpers.text import collapse, remove_formatting
55
55
56 from rhodecode.translation import _
56 from rhodecode.translation import _
57 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs import get_vcs_instance
58 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.utils2 import (
59 from rhodecode.lib.utils2 import (
60 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
60 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
62 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 JsonRaw
64 JsonRaw
65 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
67 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt2 import Encryptor
68 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.model.meta import Base, Session
69 from rhodecode.model.meta import Base, Session
70
70
71 URL_SEP = '/'
71 URL_SEP = '/'
72 log = logging.getLogger(__name__)
72 log = logging.getLogger(__name__)
73
73
74 # =============================================================================
74 # =============================================================================
75 # BASE CLASSES
75 # BASE CLASSES
76 # =============================================================================
76 # =============================================================================
77
77
78 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # this is propagated from .ini file rhodecode.encrypted_values.secret or
79 # beaker.session.secret if first is not set.
79 # beaker.session.secret if first is not set.
80 # and initialized at environment.py
80 # and initialized at environment.py
81 ENCRYPTION_KEY = None
81 ENCRYPTION_KEY = None
82
82
83 # used to sort permissions by types, '#' used here is not allowed to be in
83 # used to sort permissions by types, '#' used here is not allowed to be in
84 # usernames, and it's very early in sorted string.printable table.
84 # usernames, and it's very early in sorted string.printable table.
85 PERMISSION_TYPE_SORT = {
85 PERMISSION_TYPE_SORT = {
86 'admin': '####',
86 'admin': '####',
87 'write': '###',
87 'write': '###',
88 'read': '##',
88 'read': '##',
89 'none': '#',
89 'none': '#',
90 }
90 }
91
91
92
92
93 def display_user_sort(obj):
93 def display_user_sort(obj):
94 """
94 """
95 Sort function used to sort permissions in .permissions() function of
95 Sort function used to sort permissions in .permissions() function of
96 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 Repository, RepoGroup, UserGroup. Also it put the default user in front
97 of all other resources
97 of all other resources
98 """
98 """
99
99
100 if obj.username == User.DEFAULT_USER:
100 if obj.username == User.DEFAULT_USER:
101 return '#####'
101 return '#####'
102 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
103 return prefix + obj.username
103 return prefix + obj.username
104
104
105
105
106 def display_user_group_sort(obj):
106 def display_user_group_sort(obj):
107 """
107 """
108 Sort function used to sort permissions in .permissions() function of
108 Sort function used to sort permissions in .permissions() function of
109 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 Repository, RepoGroup, UserGroup. Also it put the default user in front
110 of all other resources
110 of all other resources
111 """
111 """
112
112
113 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
114 return prefix + obj.users_group_name
114 return prefix + obj.users_group_name
115
115
116
116
117 def _hash_key(k):
117 def _hash_key(k):
118 return sha1_safe(k)
118 return sha1_safe(k)
119
119
120
120
121 def in_filter_generator(qry, items, limit=500):
121 def in_filter_generator(qry, items, limit=500):
122 """
122 """
123 Splits IN() into multiple with OR
123 Splits IN() into multiple with OR
124 e.g.::
124 e.g.::
125 cnt = Repository.query().filter(
125 cnt = Repository.query().filter(
126 or_(
126 or_(
127 *in_filter_generator(Repository.repo_id, range(100000))
127 *in_filter_generator(Repository.repo_id, range(100000))
128 )).count()
128 )).count()
129 """
129 """
130 if not items:
130 if not items:
131 # empty list will cause empty query which might cause security issues
131 # empty list will cause empty query which might cause security issues
132 # this can lead to hidden unpleasant results
132 # this can lead to hidden unpleasant results
133 items = [-1]
133 items = [-1]
134
134
135 parts = []
135 parts = []
136 for chunk in xrange(0, len(items), limit):
136 for chunk in xrange(0, len(items), limit):
137 parts.append(
137 parts.append(
138 qry.in_(items[chunk: chunk + limit])
138 qry.in_(items[chunk: chunk + limit])
139 )
139 )
140
140
141 return parts
141 return parts
142
142
143
143
144 base_table_args = {
144 base_table_args = {
145 'extend_existing': True,
145 'extend_existing': True,
146 'mysql_engine': 'InnoDB',
146 'mysql_engine': 'InnoDB',
147 'mysql_charset': 'utf8',
147 'mysql_charset': 'utf8',
148 'sqlite_autoincrement': True
148 'sqlite_autoincrement': True
149 }
149 }
150
150
151
151
152 class EncryptedTextValue(TypeDecorator):
152 class EncryptedTextValue(TypeDecorator):
153 """
153 """
154 Special column for encrypted long text data, use like::
154 Special column for encrypted long text data, use like::
155
155
156 value = Column("encrypted_value", EncryptedValue(), nullable=False)
156 value = Column("encrypted_value", EncryptedValue(), nullable=False)
157
157
158 This column is intelligent so if value is in unencrypted form it return
158 This column is intelligent so if value is in unencrypted form it return
159 unencrypted form, but on save it always encrypts
159 unencrypted form, but on save it always encrypts
160 """
160 """
161 impl = Text
161 impl = Text
162
162
163 def process_bind_param(self, value, dialect):
163 def process_bind_param(self, value, dialect):
164 """
164 """
165 Setter for storing value
165 Setter for storing value
166 """
166 """
167 import rhodecode
167 import rhodecode
168 if not value:
168 if not value:
169 return value
169 return value
170
170
171 # protect against double encrypting if values is already encrypted
171 # protect against double encrypting if values is already encrypted
172 if value.startswith('enc$aes$') \
172 if value.startswith('enc$aes$') \
173 or value.startswith('enc$aes_hmac$') \
173 or value.startswith('enc$aes_hmac$') \
174 or value.startswith('enc2$'):
174 or value.startswith('enc2$'):
175 raise ValueError('value needs to be in unencrypted format, '
175 raise ValueError('value needs to be in unencrypted format, '
176 'ie. not starting with enc$ or enc2$')
176 'ie. not starting with enc$ or enc2$')
177
177
178 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
178 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
179 if algo == 'aes':
179 if algo == 'aes':
180 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
180 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
181 elif algo == 'fernet':
181 elif algo == 'fernet':
182 return Encryptor(ENCRYPTION_KEY).encrypt(value)
182 return Encryptor(ENCRYPTION_KEY).encrypt(value)
183 else:
183 else:
184 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
184 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
185
185
186 def process_result_value(self, value, dialect):
186 def process_result_value(self, value, dialect):
187 """
187 """
188 Getter for retrieving value
188 Getter for retrieving value
189 """
189 """
190
190
191 import rhodecode
191 import rhodecode
192 if not value:
192 if not value:
193 return value
193 return value
194
194
195 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
195 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
196 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
196 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
197 if algo == 'aes':
197 if algo == 'aes':
198 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
198 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
199 elif algo == 'fernet':
199 elif algo == 'fernet':
200 return Encryptor(ENCRYPTION_KEY).decrypt(value)
200 return Encryptor(ENCRYPTION_KEY).decrypt(value)
201 else:
201 else:
202 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
202 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
203 return decrypted_data
203 return decrypted_data
204
204
205
205
206 class BaseModel(object):
206 class BaseModel(object):
207 """
207 """
208 Base Model for all classes
208 Base Model for all classes
209 """
209 """
210
210
211 @classmethod
211 @classmethod
212 def _get_keys(cls):
212 def _get_keys(cls):
213 """return column names for this model """
213 """return column names for this model """
214 return class_mapper(cls).c.keys()
214 return class_mapper(cls).c.keys()
215
215
216 def get_dict(self):
216 def get_dict(self):
217 """
217 """
218 return dict with keys and values corresponding
218 return dict with keys and values corresponding
219 to this model data """
219 to this model data """
220
220
221 d = {}
221 d = {}
222 for k in self._get_keys():
222 for k in self._get_keys():
223 d[k] = getattr(self, k)
223 d[k] = getattr(self, k)
224
224
225 # also use __json__() if present to get additional fields
225 # also use __json__() if present to get additional fields
226 _json_attr = getattr(self, '__json__', None)
226 _json_attr = getattr(self, '__json__', None)
227 if _json_attr:
227 if _json_attr:
228 # update with attributes from __json__
228 # update with attributes from __json__
229 if callable(_json_attr):
229 if callable(_json_attr):
230 _json_attr = _json_attr()
230 _json_attr = _json_attr()
231 for k, val in _json_attr.iteritems():
231 for k, val in _json_attr.iteritems():
232 d[k] = val
232 d[k] = val
233 return d
233 return d
234
234
235 def get_appstruct(self):
235 def get_appstruct(self):
236 """return list with keys and values tuples corresponding
236 """return list with keys and values tuples corresponding
237 to this model data """
237 to this model data """
238
238
239 lst = []
239 lst = []
240 for k in self._get_keys():
240 for k in self._get_keys():
241 lst.append((k, getattr(self, k),))
241 lst.append((k, getattr(self, k),))
242 return lst
242 return lst
243
243
244 def populate_obj(self, populate_dict):
244 def populate_obj(self, populate_dict):
245 """populate model with data from given populate_dict"""
245 """populate model with data from given populate_dict"""
246
246
247 for k in self._get_keys():
247 for k in self._get_keys():
248 if k in populate_dict:
248 if k in populate_dict:
249 setattr(self, k, populate_dict[k])
249 setattr(self, k, populate_dict[k])
250
250
251 @classmethod
251 @classmethod
252 def query(cls):
252 def query(cls):
253 return Session().query(cls)
253 return Session().query(cls)
254
254
255 @classmethod
255 @classmethod
256 def get(cls, id_):
256 def get(cls, id_):
257 if id_:
257 if id_:
258 return cls.query().get(id_)
258 return cls.query().get(id_)
259
259
260 @classmethod
260 @classmethod
261 def get_or_404(cls, id_):
261 def get_or_404(cls, id_):
262 from pyramid.httpexceptions import HTTPNotFound
262 from pyramid.httpexceptions import HTTPNotFound
263
263
264 try:
264 try:
265 id_ = int(id_)
265 id_ = int(id_)
266 except (TypeError, ValueError):
266 except (TypeError, ValueError):
267 raise HTTPNotFound()
267 raise HTTPNotFound()
268
268
269 res = cls.query().get(id_)
269 res = cls.query().get(id_)
270 if not res:
270 if not res:
271 raise HTTPNotFound()
271 raise HTTPNotFound()
272 return res
272 return res
273
273
274 @classmethod
274 @classmethod
275 def getAll(cls):
275 def getAll(cls):
276 # deprecated and left for backward compatibility
276 # deprecated and left for backward compatibility
277 return cls.get_all()
277 return cls.get_all()
278
278
279 @classmethod
279 @classmethod
280 def get_all(cls):
280 def get_all(cls):
281 return cls.query().all()
281 return cls.query().all()
282
282
283 @classmethod
283 @classmethod
284 def delete(cls, id_):
284 def delete(cls, id_):
285 obj = cls.query().get(id_)
285 obj = cls.query().get(id_)
286 Session().delete(obj)
286 Session().delete(obj)
287
287
288 @classmethod
288 @classmethod
289 def identity_cache(cls, session, attr_name, value):
289 def identity_cache(cls, session, attr_name, value):
290 exist_in_session = []
290 exist_in_session = []
291 for (item_cls, pkey), instance in session.identity_map.items():
291 for (item_cls, pkey), instance in session.identity_map.items():
292 if cls == item_cls and getattr(instance, attr_name) == value:
292 if cls == item_cls and getattr(instance, attr_name) == value:
293 exist_in_session.append(instance)
293 exist_in_session.append(instance)
294 if exist_in_session:
294 if exist_in_session:
295 if len(exist_in_session) == 1:
295 if len(exist_in_session) == 1:
296 return exist_in_session[0]
296 return exist_in_session[0]
297 log.exception(
297 log.exception(
298 'multiple objects with attr %s and '
298 'multiple objects with attr %s and '
299 'value %s found with same name: %r',
299 'value %s found with same name: %r',
300 attr_name, value, exist_in_session)
300 attr_name, value, exist_in_session)
301
301
302 def __repr__(self):
302 def __repr__(self):
303 if hasattr(self, '__unicode__'):
303 if hasattr(self, '__unicode__'):
304 # python repr needs to return str
304 # python repr needs to return str
305 try:
305 try:
306 return safe_str(self.__unicode__())
306 return safe_str(self.__unicode__())
307 except UnicodeDecodeError:
307 except UnicodeDecodeError:
308 pass
308 pass
309 return '<DB:%s>' % (self.__class__.__name__)
309 return '<DB:%s>' % (self.__class__.__name__)
310
310
311
311
312 class RhodeCodeSetting(Base, BaseModel):
312 class RhodeCodeSetting(Base, BaseModel):
313 __tablename__ = 'rhodecode_settings'
313 __tablename__ = 'rhodecode_settings'
314 __table_args__ = (
314 __table_args__ = (
315 UniqueConstraint('app_settings_name'),
315 UniqueConstraint('app_settings_name'),
316 base_table_args
316 base_table_args
317 )
317 )
318
318
319 SETTINGS_TYPES = {
319 SETTINGS_TYPES = {
320 'str': safe_str,
320 'str': safe_str,
321 'int': safe_int,
321 'int': safe_int,
322 'unicode': safe_unicode,
322 'unicode': safe_unicode,
323 'bool': str2bool,
323 'bool': str2bool,
324 'list': functools.partial(aslist, sep=',')
324 'list': functools.partial(aslist, sep=',')
325 }
325 }
326 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
326 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
327 GLOBAL_CONF_KEY = 'app_settings'
327 GLOBAL_CONF_KEY = 'app_settings'
328
328
329 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
329 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
330 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
330 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
331 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
331 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
332 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
332 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
333
333
334 def __init__(self, key='', val='', type='unicode'):
334 def __init__(self, key='', val='', type='unicode'):
335 self.app_settings_name = key
335 self.app_settings_name = key
336 self.app_settings_type = type
336 self.app_settings_type = type
337 self.app_settings_value = val
337 self.app_settings_value = val
338
338
339 @validates('_app_settings_value')
339 @validates('_app_settings_value')
340 def validate_settings_value(self, key, val):
340 def validate_settings_value(self, key, val):
341 assert type(val) == unicode
341 assert type(val) == unicode
342 return val
342 return val
343
343
344 @hybrid_property
344 @hybrid_property
345 def app_settings_value(self):
345 def app_settings_value(self):
346 v = self._app_settings_value
346 v = self._app_settings_value
347 _type = self.app_settings_type
347 _type = self.app_settings_type
348 if _type:
348 if _type:
349 _type = self.app_settings_type.split('.')[0]
349 _type = self.app_settings_type.split('.')[0]
350 # decode the encrypted value
350 # decode the encrypted value
351 if 'encrypted' in self.app_settings_type:
351 if 'encrypted' in self.app_settings_type:
352 cipher = EncryptedTextValue()
352 cipher = EncryptedTextValue()
353 v = safe_unicode(cipher.process_result_value(v, None))
353 v = safe_unicode(cipher.process_result_value(v, None))
354
354
355 converter = self.SETTINGS_TYPES.get(_type) or \
355 converter = self.SETTINGS_TYPES.get(_type) or \
356 self.SETTINGS_TYPES['unicode']
356 self.SETTINGS_TYPES['unicode']
357 return converter(v)
357 return converter(v)
358
358
359 @app_settings_value.setter
359 @app_settings_value.setter
360 def app_settings_value(self, val):
360 def app_settings_value(self, val):
361 """
361 """
362 Setter that will always make sure we use unicode in app_settings_value
362 Setter that will always make sure we use unicode in app_settings_value
363
363
364 :param val:
364 :param val:
365 """
365 """
366 val = safe_unicode(val)
366 val = safe_unicode(val)
367 # encode the encrypted value
367 # encode the encrypted value
368 if 'encrypted' in self.app_settings_type:
368 if 'encrypted' in self.app_settings_type:
369 cipher = EncryptedTextValue()
369 cipher = EncryptedTextValue()
370 val = safe_unicode(cipher.process_bind_param(val, None))
370 val = safe_unicode(cipher.process_bind_param(val, None))
371 self._app_settings_value = val
371 self._app_settings_value = val
372
372
373 @hybrid_property
373 @hybrid_property
374 def app_settings_type(self):
374 def app_settings_type(self):
375 return self._app_settings_type
375 return self._app_settings_type
376
376
377 @app_settings_type.setter
377 @app_settings_type.setter
378 def app_settings_type(self, val):
378 def app_settings_type(self, val):
379 if val.split('.')[0] not in self.SETTINGS_TYPES:
379 if val.split('.')[0] not in self.SETTINGS_TYPES:
380 raise Exception('type must be one of %s got %s'
380 raise Exception('type must be one of %s got %s'
381 % (self.SETTINGS_TYPES.keys(), val))
381 % (self.SETTINGS_TYPES.keys(), val))
382 self._app_settings_type = val
382 self._app_settings_type = val
383
383
384 @classmethod
384 @classmethod
385 def get_by_prefix(cls, prefix):
385 def get_by_prefix(cls, prefix):
386 return RhodeCodeSetting.query()\
386 return RhodeCodeSetting.query()\
387 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
387 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
388 .all()
388 .all()
389
389
390 def __unicode__(self):
390 def __unicode__(self):
391 return u"<%s('%s:%s[%s]')>" % (
391 return u"<%s('%s:%s[%s]')>" % (
392 self.__class__.__name__,
392 self.__class__.__name__,
393 self.app_settings_name, self.app_settings_value,
393 self.app_settings_name, self.app_settings_value,
394 self.app_settings_type
394 self.app_settings_type
395 )
395 )
396
396
397
397
398 class RhodeCodeUi(Base, BaseModel):
398 class RhodeCodeUi(Base, BaseModel):
399 __tablename__ = 'rhodecode_ui'
399 __tablename__ = 'rhodecode_ui'
400 __table_args__ = (
400 __table_args__ = (
401 UniqueConstraint('ui_key'),
401 UniqueConstraint('ui_key'),
402 base_table_args
402 base_table_args
403 )
403 )
404
404
405 HOOK_REPO_SIZE = 'changegroup.repo_size'
405 HOOK_REPO_SIZE = 'changegroup.repo_size'
406 # HG
406 # HG
407 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
407 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
408 HOOK_PULL = 'outgoing.pull_logger'
408 HOOK_PULL = 'outgoing.pull_logger'
409 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
409 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
410 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
410 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
411 HOOK_PUSH = 'changegroup.push_logger'
411 HOOK_PUSH = 'changegroup.push_logger'
412 HOOK_PUSH_KEY = 'pushkey.key_push'
412 HOOK_PUSH_KEY = 'pushkey.key_push'
413
413
414 HOOKS_BUILTIN = [
414 HOOKS_BUILTIN = [
415 HOOK_PRE_PULL,
415 HOOK_PRE_PULL,
416 HOOK_PULL,
416 HOOK_PULL,
417 HOOK_PRE_PUSH,
417 HOOK_PRE_PUSH,
418 HOOK_PRETX_PUSH,
418 HOOK_PRETX_PUSH,
419 HOOK_PUSH,
419 HOOK_PUSH,
420 HOOK_PUSH_KEY,
420 HOOK_PUSH_KEY,
421 ]
421 ]
422
422
423 # TODO: johbo: Unify way how hooks are configured for git and hg,
423 # TODO: johbo: Unify way how hooks are configured for git and hg,
424 # git part is currently hardcoded.
424 # git part is currently hardcoded.
425
425
426 # SVN PATTERNS
426 # SVN PATTERNS
427 SVN_BRANCH_ID = 'vcs_svn_branch'
427 SVN_BRANCH_ID = 'vcs_svn_branch'
428 SVN_TAG_ID = 'vcs_svn_tag'
428 SVN_TAG_ID = 'vcs_svn_tag'
429
429
430 ui_id = Column(
430 ui_id = Column(
431 "ui_id", Integer(), nullable=False, unique=True, default=None,
431 "ui_id", Integer(), nullable=False, unique=True, default=None,
432 primary_key=True)
432 primary_key=True)
433 ui_section = Column(
433 ui_section = Column(
434 "ui_section", String(255), nullable=True, unique=None, default=None)
434 "ui_section", String(255), nullable=True, unique=None, default=None)
435 ui_key = Column(
435 ui_key = Column(
436 "ui_key", String(255), nullable=True, unique=None, default=None)
436 "ui_key", String(255), nullable=True, unique=None, default=None)
437 ui_value = Column(
437 ui_value = Column(
438 "ui_value", String(255), nullable=True, unique=None, default=None)
438 "ui_value", String(255), nullable=True, unique=None, default=None)
439 ui_active = Column(
439 ui_active = Column(
440 "ui_active", Boolean(), nullable=True, unique=None, default=True)
440 "ui_active", Boolean(), nullable=True, unique=None, default=True)
441
441
442 def __repr__(self):
442 def __repr__(self):
443 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
443 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
444 self.ui_key, self.ui_value)
444 self.ui_key, self.ui_value)
445
445
446
446
447 class RepoRhodeCodeSetting(Base, BaseModel):
447 class RepoRhodeCodeSetting(Base, BaseModel):
448 __tablename__ = 'repo_rhodecode_settings'
448 __tablename__ = 'repo_rhodecode_settings'
449 __table_args__ = (
449 __table_args__ = (
450 UniqueConstraint(
450 UniqueConstraint(
451 'app_settings_name', 'repository_id',
451 'app_settings_name', 'repository_id',
452 name='uq_repo_rhodecode_setting_name_repo_id'),
452 name='uq_repo_rhodecode_setting_name_repo_id'),
453 base_table_args
453 base_table_args
454 )
454 )
455
455
456 repository_id = Column(
456 repository_id = Column(
457 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
457 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
458 nullable=False)
458 nullable=False)
459 app_settings_id = Column(
459 app_settings_id = Column(
460 "app_settings_id", Integer(), nullable=False, unique=True,
460 "app_settings_id", Integer(), nullable=False, unique=True,
461 default=None, primary_key=True)
461 default=None, primary_key=True)
462 app_settings_name = Column(
462 app_settings_name = Column(
463 "app_settings_name", String(255), nullable=True, unique=None,
463 "app_settings_name", String(255), nullable=True, unique=None,
464 default=None)
464 default=None)
465 _app_settings_value = Column(
465 _app_settings_value = Column(
466 "app_settings_value", String(4096), nullable=True, unique=None,
466 "app_settings_value", String(4096), nullable=True, unique=None,
467 default=None)
467 default=None)
468 _app_settings_type = Column(
468 _app_settings_type = Column(
469 "app_settings_type", String(255), nullable=True, unique=None,
469 "app_settings_type", String(255), nullable=True, unique=None,
470 default=None)
470 default=None)
471
471
472 repository = relationship('Repository')
472 repository = relationship('Repository')
473
473
474 def __init__(self, repository_id, key='', val='', type='unicode'):
474 def __init__(self, repository_id, key='', val='', type='unicode'):
475 self.repository_id = repository_id
475 self.repository_id = repository_id
476 self.app_settings_name = key
476 self.app_settings_name = key
477 self.app_settings_type = type
477 self.app_settings_type = type
478 self.app_settings_value = val
478 self.app_settings_value = val
479
479
480 @validates('_app_settings_value')
480 @validates('_app_settings_value')
481 def validate_settings_value(self, key, val):
481 def validate_settings_value(self, key, val):
482 assert type(val) == unicode
482 assert type(val) == unicode
483 return val
483 return val
484
484
485 @hybrid_property
485 @hybrid_property
486 def app_settings_value(self):
486 def app_settings_value(self):
487 v = self._app_settings_value
487 v = self._app_settings_value
488 type_ = self.app_settings_type
488 type_ = self.app_settings_type
489 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
489 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
490 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
490 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
491 return converter(v)
491 return converter(v)
492
492
493 @app_settings_value.setter
493 @app_settings_value.setter
494 def app_settings_value(self, val):
494 def app_settings_value(self, val):
495 """
495 """
496 Setter that will always make sure we use unicode in app_settings_value
496 Setter that will always make sure we use unicode in app_settings_value
497
497
498 :param val:
498 :param val:
499 """
499 """
500 self._app_settings_value = safe_unicode(val)
500 self._app_settings_value = safe_unicode(val)
501
501
502 @hybrid_property
502 @hybrid_property
503 def app_settings_type(self):
503 def app_settings_type(self):
504 return self._app_settings_type
504 return self._app_settings_type
505
505
506 @app_settings_type.setter
506 @app_settings_type.setter
507 def app_settings_type(self, val):
507 def app_settings_type(self, val):
508 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
508 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
509 if val not in SETTINGS_TYPES:
509 if val not in SETTINGS_TYPES:
510 raise Exception('type must be one of %s got %s'
510 raise Exception('type must be one of %s got %s'
511 % (SETTINGS_TYPES.keys(), val))
511 % (SETTINGS_TYPES.keys(), val))
512 self._app_settings_type = val
512 self._app_settings_type = val
513
513
514 def __unicode__(self):
514 def __unicode__(self):
515 return u"<%s('%s:%s:%s[%s]')>" % (
515 return u"<%s('%s:%s:%s[%s]')>" % (
516 self.__class__.__name__, self.repository.repo_name,
516 self.__class__.__name__, self.repository.repo_name,
517 self.app_settings_name, self.app_settings_value,
517 self.app_settings_name, self.app_settings_value,
518 self.app_settings_type
518 self.app_settings_type
519 )
519 )
520
520
521
521
522 class RepoRhodeCodeUi(Base, BaseModel):
522 class RepoRhodeCodeUi(Base, BaseModel):
523 __tablename__ = 'repo_rhodecode_ui'
523 __tablename__ = 'repo_rhodecode_ui'
524 __table_args__ = (
524 __table_args__ = (
525 UniqueConstraint(
525 UniqueConstraint(
526 'repository_id', 'ui_section', 'ui_key',
526 'repository_id', 'ui_section', 'ui_key',
527 name='uq_repo_rhodecode_ui_repository_id_section_key'),
527 name='uq_repo_rhodecode_ui_repository_id_section_key'),
528 base_table_args
528 base_table_args
529 )
529 )
530
530
531 repository_id = Column(
531 repository_id = Column(
532 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
532 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
533 nullable=False)
533 nullable=False)
534 ui_id = Column(
534 ui_id = Column(
535 "ui_id", Integer(), nullable=False, unique=True, default=None,
535 "ui_id", Integer(), nullable=False, unique=True, default=None,
536 primary_key=True)
536 primary_key=True)
537 ui_section = Column(
537 ui_section = Column(
538 "ui_section", String(255), nullable=True, unique=None, default=None)
538 "ui_section", String(255), nullable=True, unique=None, default=None)
539 ui_key = Column(
539 ui_key = Column(
540 "ui_key", String(255), nullable=True, unique=None, default=None)
540 "ui_key", String(255), nullable=True, unique=None, default=None)
541 ui_value = Column(
541 ui_value = Column(
542 "ui_value", String(255), nullable=True, unique=None, default=None)
542 "ui_value", String(255), nullable=True, unique=None, default=None)
543 ui_active = Column(
543 ui_active = Column(
544 "ui_active", Boolean(), nullable=True, unique=None, default=True)
544 "ui_active", Boolean(), nullable=True, unique=None, default=True)
545
545
546 repository = relationship('Repository')
546 repository = relationship('Repository')
547
547
548 def __repr__(self):
548 def __repr__(self):
549 return '<%s[%s:%s]%s=>%s]>' % (
549 return '<%s[%s:%s]%s=>%s]>' % (
550 self.__class__.__name__, self.repository.repo_name,
550 self.__class__.__name__, self.repository.repo_name,
551 self.ui_section, self.ui_key, self.ui_value)
551 self.ui_section, self.ui_key, self.ui_value)
552
552
553
553
554 class User(Base, BaseModel):
554 class User(Base, BaseModel):
555 __tablename__ = 'users'
555 __tablename__ = 'users'
556 __table_args__ = (
556 __table_args__ = (
557 UniqueConstraint('username'), UniqueConstraint('email'),
557 UniqueConstraint('username'), UniqueConstraint('email'),
558 Index('u_username_idx', 'username'),
558 Index('u_username_idx', 'username'),
559 Index('u_email_idx', 'email'),
559 Index('u_email_idx', 'email'),
560 base_table_args
560 base_table_args
561 )
561 )
562
562
563 DEFAULT_USER = 'default'
563 DEFAULT_USER = 'default'
564 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
564 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
565 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
565 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
566
566
567 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
567 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
568 username = Column("username", String(255), nullable=True, unique=None, default=None)
568 username = Column("username", String(255), nullable=True, unique=None, default=None)
569 password = Column("password", String(255), nullable=True, unique=None, default=None)
569 password = Column("password", String(255), nullable=True, unique=None, default=None)
570 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
570 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
571 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
571 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
572 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
572 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
573 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
573 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
574 _email = Column("email", String(255), nullable=True, unique=None, default=None)
574 _email = Column("email", String(255), nullable=True, unique=None, default=None)
575 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
575 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
576 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
576 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
577
577
578 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
578 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
579 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
579 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
580 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
580 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
581 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
581 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
582 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
582 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
583 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
583 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
584
584
585 user_log = relationship('UserLog')
585 user_log = relationship('UserLog')
586 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
586 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
587
587
588 repositories = relationship('Repository')
588 repositories = relationship('Repository')
589 repository_groups = relationship('RepoGroup')
589 repository_groups = relationship('RepoGroup')
590 user_groups = relationship('UserGroup')
590 user_groups = relationship('UserGroup')
591
591
592 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
592 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
593 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
593 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
594
594
595 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
595 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
596 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
596 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
597 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
597 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
598
598
599 group_member = relationship('UserGroupMember', cascade='all')
599 group_member = relationship('UserGroupMember', cascade='all')
600
600
601 notifications = relationship('UserNotification', cascade='all')
601 notifications = relationship('UserNotification', cascade='all')
602 # notifications assigned to this user
602 # notifications assigned to this user
603 user_created_notifications = relationship('Notification', cascade='all')
603 user_created_notifications = relationship('Notification', cascade='all')
604 # comments created by this user
604 # comments created by this user
605 user_comments = relationship('ChangesetComment', cascade='all')
605 user_comments = relationship('ChangesetComment', cascade='all')
606 # user profile extra info
606 # user profile extra info
607 user_emails = relationship('UserEmailMap', cascade='all')
607 user_emails = relationship('UserEmailMap', cascade='all')
608 user_ip_map = relationship('UserIpMap', cascade='all')
608 user_ip_map = relationship('UserIpMap', cascade='all')
609 user_auth_tokens = relationship('UserApiKeys', cascade='all')
609 user_auth_tokens = relationship('UserApiKeys', cascade='all')
610 user_ssh_keys = relationship('UserSshKeys', cascade='all')
610 user_ssh_keys = relationship('UserSshKeys', cascade='all')
611
611
612 # gists
612 # gists
613 user_gists = relationship('Gist', cascade='all')
613 user_gists = relationship('Gist', cascade='all')
614 # user pull requests
614 # user pull requests
615 user_pull_requests = relationship('PullRequest', cascade='all')
615 user_pull_requests = relationship('PullRequest', cascade='all')
616 # external identities
616 # external identities
617 extenal_identities = relationship(
617 extenal_identities = relationship(
618 'ExternalIdentity',
618 'ExternalIdentity',
619 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
619 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
620 cascade='all')
620 cascade='all')
621 # review rules
621 # review rules
622 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
622 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
623
623
624 def __unicode__(self):
624 def __unicode__(self):
625 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
625 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
626 self.user_id, self.username)
626 self.user_id, self.username)
627
627
628 @hybrid_property
628 @hybrid_property
629 def email(self):
629 def email(self):
630 return self._email
630 return self._email
631
631
632 @email.setter
632 @email.setter
633 def email(self, val):
633 def email(self, val):
634 self._email = val.lower() if val else None
634 self._email = val.lower() if val else None
635
635
636 @hybrid_property
636 @hybrid_property
637 def first_name(self):
637 def first_name(self):
638 from rhodecode.lib import helpers as h
638 from rhodecode.lib import helpers as h
639 if self.name:
639 if self.name:
640 return h.escape(self.name)
640 return h.escape(self.name)
641 return self.name
641 return self.name
642
642
643 @hybrid_property
643 @hybrid_property
644 def last_name(self):
644 def last_name(self):
645 from rhodecode.lib import helpers as h
645 from rhodecode.lib import helpers as h
646 if self.lastname:
646 if self.lastname:
647 return h.escape(self.lastname)
647 return h.escape(self.lastname)
648 return self.lastname
648 return self.lastname
649
649
650 @hybrid_property
650 @hybrid_property
651 def api_key(self):
651 def api_key(self):
652 """
652 """
653 Fetch if exist an auth-token with role ALL connected to this user
653 Fetch if exist an auth-token with role ALL connected to this user
654 """
654 """
655 user_auth_token = UserApiKeys.query()\
655 user_auth_token = UserApiKeys.query()\
656 .filter(UserApiKeys.user_id == self.user_id)\
656 .filter(UserApiKeys.user_id == self.user_id)\
657 .filter(or_(UserApiKeys.expires == -1,
657 .filter(or_(UserApiKeys.expires == -1,
658 UserApiKeys.expires >= time.time()))\
658 UserApiKeys.expires >= time.time()))\
659 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
659 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
660 if user_auth_token:
660 if user_auth_token:
661 user_auth_token = user_auth_token.api_key
661 user_auth_token = user_auth_token.api_key
662
662
663 return user_auth_token
663 return user_auth_token
664
664
665 @api_key.setter
665 @api_key.setter
666 def api_key(self, val):
666 def api_key(self, val):
667 # don't allow to set API key this is deprecated for now
667 # don't allow to set API key this is deprecated for now
668 self._api_key = None
668 self._api_key = None
669
669
670 @property
670 @property
671 def reviewer_pull_requests(self):
671 def reviewer_pull_requests(self):
672 return PullRequestReviewers.query() \
672 return PullRequestReviewers.query() \
673 .options(joinedload(PullRequestReviewers.pull_request)) \
673 .options(joinedload(PullRequestReviewers.pull_request)) \
674 .filter(PullRequestReviewers.user_id == self.user_id) \
674 .filter(PullRequestReviewers.user_id == self.user_id) \
675 .all()
675 .all()
676
676
677 @property
677 @property
678 def firstname(self):
678 def firstname(self):
679 # alias for future
679 # alias for future
680 return self.name
680 return self.name
681
681
682 @property
682 @property
683 def emails(self):
683 def emails(self):
684 other = UserEmailMap.query()\
684 other = UserEmailMap.query()\
685 .filter(UserEmailMap.user == self) \
685 .filter(UserEmailMap.user == self) \
686 .order_by(UserEmailMap.email_id.asc()) \
686 .order_by(UserEmailMap.email_id.asc()) \
687 .all()
687 .all()
688 return [self.email] + [x.email for x in other]
688 return [self.email] + [x.email for x in other]
689
689
690 @property
690 @property
691 def auth_tokens(self):
691 def auth_tokens(self):
692 auth_tokens = self.get_auth_tokens()
692 auth_tokens = self.get_auth_tokens()
693 return [x.api_key for x in auth_tokens]
693 return [x.api_key for x in auth_tokens]
694
694
695 def get_auth_tokens(self):
695 def get_auth_tokens(self):
696 return UserApiKeys.query()\
696 return UserApiKeys.query()\
697 .filter(UserApiKeys.user == self)\
697 .filter(UserApiKeys.user == self)\
698 .order_by(UserApiKeys.user_api_key_id.asc())\
698 .order_by(UserApiKeys.user_api_key_id.asc())\
699 .all()
699 .all()
700
700
701 @LazyProperty
701 @LazyProperty
702 def feed_token(self):
702 def feed_token(self):
703 return self.get_feed_token()
703 return self.get_feed_token()
704
704
705 def get_feed_token(self, cache=True):
705 def get_feed_token(self, cache=True):
706 feed_tokens = UserApiKeys.query()\
706 feed_tokens = UserApiKeys.query()\
707 .filter(UserApiKeys.user == self)\
707 .filter(UserApiKeys.user == self)\
708 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
708 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
709 if cache:
709 if cache:
710 feed_tokens = feed_tokens.options(
710 feed_tokens = feed_tokens.options(
711 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
711 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
712
712
713 feed_tokens = feed_tokens.all()
713 feed_tokens = feed_tokens.all()
714 if feed_tokens:
714 if feed_tokens:
715 return feed_tokens[0].api_key
715 return feed_tokens[0].api_key
716 return 'NO_FEED_TOKEN_AVAILABLE'
716 return 'NO_FEED_TOKEN_AVAILABLE'
717
717
718 @classmethod
718 @classmethod
719 def get(cls, user_id, cache=False):
719 def get(cls, user_id, cache=False):
720 if not user_id:
720 if not user_id:
721 return
721 return
722
722
723 user = cls.query()
723 user = cls.query()
724 if cache:
724 if cache:
725 user = user.options(
725 user = user.options(
726 FromCache("sql_cache_short", "get_users_%s" % user_id))
726 FromCache("sql_cache_short", "get_users_%s" % user_id))
727 return user.get(user_id)
727 return user.get(user_id)
728
728
729 @classmethod
729 @classmethod
730 def extra_valid_auth_tokens(cls, user, role=None):
730 def extra_valid_auth_tokens(cls, user, role=None):
731 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
731 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
732 .filter(or_(UserApiKeys.expires == -1,
732 .filter(or_(UserApiKeys.expires == -1,
733 UserApiKeys.expires >= time.time()))
733 UserApiKeys.expires >= time.time()))
734 if role:
734 if role:
735 tokens = tokens.filter(or_(UserApiKeys.role == role,
735 tokens = tokens.filter(or_(UserApiKeys.role == role,
736 UserApiKeys.role == UserApiKeys.ROLE_ALL))
736 UserApiKeys.role == UserApiKeys.ROLE_ALL))
737 return tokens.all()
737 return tokens.all()
738
738
739 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
739 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
740 from rhodecode.lib import auth
740 from rhodecode.lib import auth
741
741
742 log.debug('Trying to authenticate user: %s via auth-token, '
742 log.debug('Trying to authenticate user: %s via auth-token, '
743 'and roles: %s', self, roles)
743 'and roles: %s', self, roles)
744
744
745 if not auth_token:
745 if not auth_token:
746 return False
746 return False
747
747
748 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
748 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
749 tokens_q = UserApiKeys.query()\
749 tokens_q = UserApiKeys.query()\
750 .filter(UserApiKeys.user_id == self.user_id)\
750 .filter(UserApiKeys.user_id == self.user_id)\
751 .filter(or_(UserApiKeys.expires == -1,
751 .filter(or_(UserApiKeys.expires == -1,
752 UserApiKeys.expires >= time.time()))
752 UserApiKeys.expires >= time.time()))
753
753
754 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
754 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
755
755
756 crypto_backend = auth.crypto_backend()
756 crypto_backend = auth.crypto_backend()
757 enc_token_map = {}
757 enc_token_map = {}
758 plain_token_map = {}
758 plain_token_map = {}
759 for token in tokens_q:
759 for token in tokens_q:
760 if token.api_key.startswith(crypto_backend.ENC_PREF):
760 if token.api_key.startswith(crypto_backend.ENC_PREF):
761 enc_token_map[token.api_key] = token
761 enc_token_map[token.api_key] = token
762 else:
762 else:
763 plain_token_map[token.api_key] = token
763 plain_token_map[token.api_key] = token
764 log.debug(
764 log.debug(
765 'Found %s plain and %s encrypted user tokens to check for authentication',
765 'Found %s plain and %s encrypted user tokens to check for authentication',
766 len(plain_token_map), len(enc_token_map))
766 len(plain_token_map), len(enc_token_map))
767
767
768 # plain token match comes first
768 # plain token match comes first
769 match = plain_token_map.get(auth_token)
769 match = plain_token_map.get(auth_token)
770
770
771 # check encrypted tokens now
771 # check encrypted tokens now
772 if not match:
772 if not match:
773 for token_hash, token in enc_token_map.items():
773 for token_hash, token in enc_token_map.items():
774 # NOTE(marcink): this is expensive to calculate, but most secure
774 # NOTE(marcink): this is expensive to calculate, but most secure
775 if crypto_backend.hash_check(auth_token, token_hash):
775 if crypto_backend.hash_check(auth_token, token_hash):
776 match = token
776 match = token
777 break
777 break
778
778
779 if match:
779 if match:
780 log.debug('Found matching token %s', match)
780 log.debug('Found matching token %s', match)
781 if match.repo_id:
781 if match.repo_id:
782 log.debug('Found scope, checking for scope match of token %s', match)
782 log.debug('Found scope, checking for scope match of token %s', match)
783 if match.repo_id == scope_repo_id:
783 if match.repo_id == scope_repo_id:
784 return True
784 return True
785 else:
785 else:
786 log.debug(
786 log.debug(
787 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
787 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
788 'and calling scope is:%s, skipping further checks',
788 'and calling scope is:%s, skipping further checks',
789 match.repo, scope_repo_id)
789 match.repo, scope_repo_id)
790 return False
790 return False
791 else:
791 else:
792 return True
792 return True
793
793
794 return False
794 return False
795
795
796 @property
796 @property
797 def ip_addresses(self):
797 def ip_addresses(self):
798 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
798 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
799 return [x.ip_addr for x in ret]
799 return [x.ip_addr for x in ret]
800
800
801 @property
801 @property
802 def username_and_name(self):
802 def username_and_name(self):
803 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
803 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
804
804
805 @property
805 @property
806 def username_or_name_or_email(self):
806 def username_or_name_or_email(self):
807 full_name = self.full_name if self.full_name is not ' ' else None
807 full_name = self.full_name if self.full_name is not ' ' else None
808 return self.username or full_name or self.email
808 return self.username or full_name or self.email
809
809
810 @property
810 @property
811 def full_name(self):
811 def full_name(self):
812 return '%s %s' % (self.first_name, self.last_name)
812 return '%s %s' % (self.first_name, self.last_name)
813
813
814 @property
814 @property
815 def full_name_or_username(self):
815 def full_name_or_username(self):
816 return ('%s %s' % (self.first_name, self.last_name)
816 return ('%s %s' % (self.first_name, self.last_name)
817 if (self.first_name and self.last_name) else self.username)
817 if (self.first_name and self.last_name) else self.username)
818
818
819 @property
819 @property
820 def full_contact(self):
820 def full_contact(self):
821 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
821 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
822
822
823 @property
823 @property
824 def short_contact(self):
824 def short_contact(self):
825 return '%s %s' % (self.first_name, self.last_name)
825 return '%s %s' % (self.first_name, self.last_name)
826
826
827 @property
827 @property
828 def is_admin(self):
828 def is_admin(self):
829 return self.admin
829 return self.admin
830
830
831 def AuthUser(self, **kwargs):
831 def AuthUser(self, **kwargs):
832 """
832 """
833 Returns instance of AuthUser for this user
833 Returns instance of AuthUser for this user
834 """
834 """
835 from rhodecode.lib.auth import AuthUser
835 from rhodecode.lib.auth import AuthUser
836 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
836 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
837
837
838 @hybrid_property
838 @hybrid_property
839 def user_data(self):
839 def user_data(self):
840 if not self._user_data:
840 if not self._user_data:
841 return {}
841 return {}
842
842
843 try:
843 try:
844 return json.loads(self._user_data)
844 return json.loads(self._user_data)
845 except TypeError:
845 except TypeError:
846 return {}
846 return {}
847
847
848 @user_data.setter
848 @user_data.setter
849 def user_data(self, val):
849 def user_data(self, val):
850 if not isinstance(val, dict):
850 if not isinstance(val, dict):
851 raise Exception('user_data must be dict, got %s' % type(val))
851 raise Exception('user_data must be dict, got %s' % type(val))
852 try:
852 try:
853 self._user_data = json.dumps(val)
853 self._user_data = json.dumps(val)
854 except Exception:
854 except Exception:
855 log.error(traceback.format_exc())
855 log.error(traceback.format_exc())
856
856
857 @classmethod
857 @classmethod
858 def get_by_username(cls, username, case_insensitive=False,
858 def get_by_username(cls, username, case_insensitive=False,
859 cache=False, identity_cache=False):
859 cache=False, identity_cache=False):
860 session = Session()
860 session = Session()
861
861
862 if case_insensitive:
862 if case_insensitive:
863 q = cls.query().filter(
863 q = cls.query().filter(
864 func.lower(cls.username) == func.lower(username))
864 func.lower(cls.username) == func.lower(username))
865 else:
865 else:
866 q = cls.query().filter(cls.username == username)
866 q = cls.query().filter(cls.username == username)
867
867
868 if cache:
868 if cache:
869 if identity_cache:
869 if identity_cache:
870 val = cls.identity_cache(session, 'username', username)
870 val = cls.identity_cache(session, 'username', username)
871 if val:
871 if val:
872 return val
872 return val
873 else:
873 else:
874 cache_key = "get_user_by_name_%s" % _hash_key(username)
874 cache_key = "get_user_by_name_%s" % _hash_key(username)
875 q = q.options(
875 q = q.options(
876 FromCache("sql_cache_short", cache_key))
876 FromCache("sql_cache_short", cache_key))
877
877
878 return q.scalar()
878 return q.scalar()
879
879
880 @classmethod
880 @classmethod
881 def get_by_auth_token(cls, auth_token, cache=False):
881 def get_by_auth_token(cls, auth_token, cache=False):
882 q = UserApiKeys.query()\
882 q = UserApiKeys.query()\
883 .filter(UserApiKeys.api_key == auth_token)\
883 .filter(UserApiKeys.api_key == auth_token)\
884 .filter(or_(UserApiKeys.expires == -1,
884 .filter(or_(UserApiKeys.expires == -1,
885 UserApiKeys.expires >= time.time()))
885 UserApiKeys.expires >= time.time()))
886 if cache:
886 if cache:
887 q = q.options(
887 q = q.options(
888 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
888 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
889
889
890 match = q.first()
890 match = q.first()
891 if match:
891 if match:
892 return match.user
892 return match.user
893
893
894 @classmethod
894 @classmethod
895 def get_by_email(cls, email, case_insensitive=False, cache=False):
895 def get_by_email(cls, email, case_insensitive=False, cache=False):
896
896
897 if case_insensitive:
897 if case_insensitive:
898 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
898 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
899
899
900 else:
900 else:
901 q = cls.query().filter(cls.email == email)
901 q = cls.query().filter(cls.email == email)
902
902
903 email_key = _hash_key(email)
903 email_key = _hash_key(email)
904 if cache:
904 if cache:
905 q = q.options(
905 q = q.options(
906 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
906 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
907
907
908 ret = q.scalar()
908 ret = q.scalar()
909 if ret is None:
909 if ret is None:
910 q = UserEmailMap.query()
910 q = UserEmailMap.query()
911 # try fetching in alternate email map
911 # try fetching in alternate email map
912 if case_insensitive:
912 if case_insensitive:
913 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
913 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
914 else:
914 else:
915 q = q.filter(UserEmailMap.email == email)
915 q = q.filter(UserEmailMap.email == email)
916 q = q.options(joinedload(UserEmailMap.user))
916 q = q.options(joinedload(UserEmailMap.user))
917 if cache:
917 if cache:
918 q = q.options(
918 q = q.options(
919 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
919 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
920 ret = getattr(q.scalar(), 'user', None)
920 ret = getattr(q.scalar(), 'user', None)
921
921
922 return ret
922 return ret
923
923
924 @classmethod
924 @classmethod
925 def get_from_cs_author(cls, author):
925 def get_from_cs_author(cls, author):
926 """
926 """
927 Tries to get User objects out of commit author string
927 Tries to get User objects out of commit author string
928
928
929 :param author:
929 :param author:
930 """
930 """
931 from rhodecode.lib.helpers import email, author_name
931 from rhodecode.lib.helpers import email, author_name
932 # Valid email in the attribute passed, see if they're in the system
932 # Valid email in the attribute passed, see if they're in the system
933 _email = email(author)
933 _email = email(author)
934 if _email:
934 if _email:
935 user = cls.get_by_email(_email, case_insensitive=True)
935 user = cls.get_by_email(_email, case_insensitive=True)
936 if user:
936 if user:
937 return user
937 return user
938 # Maybe we can match by username?
938 # Maybe we can match by username?
939 _author = author_name(author)
939 _author = author_name(author)
940 user = cls.get_by_username(_author, case_insensitive=True)
940 user = cls.get_by_username(_author, case_insensitive=True)
941 if user:
941 if user:
942 return user
942 return user
943
943
944 def update_userdata(self, **kwargs):
944 def update_userdata(self, **kwargs):
945 usr = self
945 usr = self
946 old = usr.user_data
946 old = usr.user_data
947 old.update(**kwargs)
947 old.update(**kwargs)
948 usr.user_data = old
948 usr.user_data = old
949 Session().add(usr)
949 Session().add(usr)
950 log.debug('updated userdata with ', kwargs)
950 log.debug('updated userdata with ', kwargs)
951
951
952 def update_lastlogin(self):
952 def update_lastlogin(self):
953 """Update user lastlogin"""
953 """Update user lastlogin"""
954 self.last_login = datetime.datetime.now()
954 self.last_login = datetime.datetime.now()
955 Session().add(self)
955 Session().add(self)
956 log.debug('updated user %s lastlogin', self.username)
956 log.debug('updated user %s lastlogin', self.username)
957
957
958 def update_password(self, new_password):
958 def update_password(self, new_password):
959 from rhodecode.lib.auth import get_crypt_password
959 from rhodecode.lib.auth import get_crypt_password
960
960
961 self.password = get_crypt_password(new_password)
961 self.password = get_crypt_password(new_password)
962 Session().add(self)
962 Session().add(self)
963
963
964 @classmethod
964 @classmethod
965 def get_first_super_admin(cls):
965 def get_first_super_admin(cls):
966 user = User.query()\
966 user = User.query()\
967 .filter(User.admin == true()) \
967 .filter(User.admin == true()) \
968 .order_by(User.user_id.asc()) \
968 .order_by(User.user_id.asc()) \
969 .first()
969 .first()
970
970
971 if user is None:
971 if user is None:
972 raise Exception('FATAL: Missing administrative account!')
972 raise Exception('FATAL: Missing administrative account!')
973 return user
973 return user
974
974
975 @classmethod
975 @classmethod
976 def get_all_super_admins(cls, only_active=False):
976 def get_all_super_admins(cls, only_active=False):
977 """
977 """
978 Returns all admin accounts sorted by username
978 Returns all admin accounts sorted by username
979 """
979 """
980 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
980 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
981 if only_active:
981 if only_active:
982 qry = qry.filter(User.active == true())
982 qry = qry.filter(User.active == true())
983 return qry.all()
983 return qry.all()
984
984
985 @classmethod
985 @classmethod
986 def get_default_user(cls, cache=False, refresh=False):
986 def get_default_user(cls, cache=False, refresh=False):
987 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
987 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
988 if user is None:
988 if user is None:
989 raise Exception('FATAL: Missing default account!')
989 raise Exception('FATAL: Missing default account!')
990 if refresh:
990 if refresh:
991 # The default user might be based on outdated state which
991 # The default user might be based on outdated state which
992 # has been loaded from the cache.
992 # has been loaded from the cache.
993 # A call to refresh() ensures that the
993 # A call to refresh() ensures that the
994 # latest state from the database is used.
994 # latest state from the database is used.
995 Session().refresh(user)
995 Session().refresh(user)
996 return user
996 return user
997
997
998 def _get_default_perms(self, user, suffix=''):
998 def _get_default_perms(self, user, suffix=''):
999 from rhodecode.model.permission import PermissionModel
999 from rhodecode.model.permission import PermissionModel
1000 return PermissionModel().get_default_perms(user.user_perms, suffix)
1000 return PermissionModel().get_default_perms(user.user_perms, suffix)
1001
1001
1002 def get_default_perms(self, suffix=''):
1002 def get_default_perms(self, suffix=''):
1003 return self._get_default_perms(self, suffix)
1003 return self._get_default_perms(self, suffix)
1004
1004
1005 def get_api_data(self, include_secrets=False, details='full'):
1005 def get_api_data(self, include_secrets=False, details='full'):
1006 """
1006 """
1007 Common function for generating user related data for API
1007 Common function for generating user related data for API
1008
1008
1009 :param include_secrets: By default secrets in the API data will be replaced
1009 :param include_secrets: By default secrets in the API data will be replaced
1010 by a placeholder value to prevent exposing this data by accident. In case
1010 by a placeholder value to prevent exposing this data by accident. In case
1011 this data shall be exposed, set this flag to ``True``.
1011 this data shall be exposed, set this flag to ``True``.
1012
1012
1013 :param details: details can be 'basic|full' basic gives only a subset of
1013 :param details: details can be 'basic|full' basic gives only a subset of
1014 the available user information that includes user_id, name and emails.
1014 the available user information that includes user_id, name and emails.
1015 """
1015 """
1016 user = self
1016 user = self
1017 user_data = self.user_data
1017 user_data = self.user_data
1018 data = {
1018 data = {
1019 'user_id': user.user_id,
1019 'user_id': user.user_id,
1020 'username': user.username,
1020 'username': user.username,
1021 'firstname': user.name,
1021 'firstname': user.name,
1022 'lastname': user.lastname,
1022 'lastname': user.lastname,
1023 'email': user.email,
1023 'email': user.email,
1024 'emails': user.emails,
1024 'emails': user.emails,
1025 }
1025 }
1026 if details == 'basic':
1026 if details == 'basic':
1027 return data
1027 return data
1028
1028
1029 auth_token_length = 40
1029 auth_token_length = 40
1030 auth_token_replacement = '*' * auth_token_length
1030 auth_token_replacement = '*' * auth_token_length
1031
1031
1032 extras = {
1032 extras = {
1033 'auth_tokens': [auth_token_replacement],
1033 'auth_tokens': [auth_token_replacement],
1034 'active': user.active,
1034 'active': user.active,
1035 'admin': user.admin,
1035 'admin': user.admin,
1036 'extern_type': user.extern_type,
1036 'extern_type': user.extern_type,
1037 'extern_name': user.extern_name,
1037 'extern_name': user.extern_name,
1038 'last_login': user.last_login,
1038 'last_login': user.last_login,
1039 'last_activity': user.last_activity,
1039 'last_activity': user.last_activity,
1040 'ip_addresses': user.ip_addresses,
1040 'ip_addresses': user.ip_addresses,
1041 'language': user_data.get('language')
1041 'language': user_data.get('language')
1042 }
1042 }
1043 data.update(extras)
1043 data.update(extras)
1044
1044
1045 if include_secrets:
1045 if include_secrets:
1046 data['auth_tokens'] = user.auth_tokens
1046 data['auth_tokens'] = user.auth_tokens
1047 return data
1047 return data
1048
1048
1049 def __json__(self):
1049 def __json__(self):
1050 data = {
1050 data = {
1051 'full_name': self.full_name,
1051 'full_name': self.full_name,
1052 'full_name_or_username': self.full_name_or_username,
1052 'full_name_or_username': self.full_name_or_username,
1053 'short_contact': self.short_contact,
1053 'short_contact': self.short_contact,
1054 'full_contact': self.full_contact,
1054 'full_contact': self.full_contact,
1055 }
1055 }
1056 data.update(self.get_api_data())
1056 data.update(self.get_api_data())
1057 return data
1057 return data
1058
1058
1059
1059
1060 class UserApiKeys(Base, BaseModel):
1060 class UserApiKeys(Base, BaseModel):
1061 __tablename__ = 'user_api_keys'
1061 __tablename__ = 'user_api_keys'
1062 __table_args__ = (
1062 __table_args__ = (
1063 Index('uak_api_key_idx', 'api_key', unique=True),
1063 Index('uak_api_key_idx', 'api_key', unique=True),
1064 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1064 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1065 base_table_args
1065 base_table_args
1066 )
1066 )
1067 __mapper_args__ = {}
1067 __mapper_args__ = {}
1068
1068
1069 # ApiKey role
1069 # ApiKey role
1070 ROLE_ALL = 'token_role_all'
1070 ROLE_ALL = 'token_role_all'
1071 ROLE_HTTP = 'token_role_http'
1071 ROLE_HTTP = 'token_role_http'
1072 ROLE_VCS = 'token_role_vcs'
1072 ROLE_VCS = 'token_role_vcs'
1073 ROLE_API = 'token_role_api'
1073 ROLE_API = 'token_role_api'
1074 ROLE_FEED = 'token_role_feed'
1074 ROLE_FEED = 'token_role_feed'
1075 ROLE_PASSWORD_RESET = 'token_password_reset'
1075 ROLE_PASSWORD_RESET = 'token_password_reset'
1076
1076
1077 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1077 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1078
1078
1079 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1079 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1080 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1080 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1081 api_key = Column("api_key", String(255), nullable=False, unique=True)
1081 api_key = Column("api_key", String(255), nullable=False, unique=True)
1082 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1082 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1083 expires = Column('expires', Float(53), nullable=False)
1083 expires = Column('expires', Float(53), nullable=False)
1084 role = Column('role', String(255), nullable=True)
1084 role = Column('role', String(255), nullable=True)
1085 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1085 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1086
1086
1087 # scope columns
1087 # scope columns
1088 repo_id = Column(
1088 repo_id = Column(
1089 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1089 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1090 nullable=True, unique=None, default=None)
1090 nullable=True, unique=None, default=None)
1091 repo = relationship('Repository', lazy='joined')
1091 repo = relationship('Repository', lazy='joined')
1092
1092
1093 repo_group_id = Column(
1093 repo_group_id = Column(
1094 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1094 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1095 nullable=True, unique=None, default=None)
1095 nullable=True, unique=None, default=None)
1096 repo_group = relationship('RepoGroup', lazy='joined')
1096 repo_group = relationship('RepoGroup', lazy='joined')
1097
1097
1098 user = relationship('User', lazy='joined')
1098 user = relationship('User', lazy='joined')
1099
1099
1100 def __unicode__(self):
1100 def __unicode__(self):
1101 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1101 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1102
1102
1103 def __json__(self):
1103 def __json__(self):
1104 data = {
1104 data = {
1105 'auth_token': self.api_key,
1105 'auth_token': self.api_key,
1106 'role': self.role,
1106 'role': self.role,
1107 'scope': self.scope_humanized,
1107 'scope': self.scope_humanized,
1108 'expired': self.expired
1108 'expired': self.expired
1109 }
1109 }
1110 return data
1110 return data
1111
1111
1112 def get_api_data(self, include_secrets=False):
1112 def get_api_data(self, include_secrets=False):
1113 data = self.__json__()
1113 data = self.__json__()
1114 if include_secrets:
1114 if include_secrets:
1115 return data
1115 return data
1116 else:
1116 else:
1117 data['auth_token'] = self.token_obfuscated
1117 data['auth_token'] = self.token_obfuscated
1118 return data
1118 return data
1119
1119
1120 @hybrid_property
1120 @hybrid_property
1121 def description_safe(self):
1121 def description_safe(self):
1122 from rhodecode.lib import helpers as h
1122 from rhodecode.lib import helpers as h
1123 return h.escape(self.description)
1123 return h.escape(self.description)
1124
1124
1125 @property
1125 @property
1126 def expired(self):
1126 def expired(self):
1127 if self.expires == -1:
1127 if self.expires == -1:
1128 return False
1128 return False
1129 return time.time() > self.expires
1129 return time.time() > self.expires
1130
1130
1131 @classmethod
1131 @classmethod
1132 def _get_role_name(cls, role):
1132 def _get_role_name(cls, role):
1133 return {
1133 return {
1134 cls.ROLE_ALL: _('all'),
1134 cls.ROLE_ALL: _('all'),
1135 cls.ROLE_HTTP: _('http/web interface'),
1135 cls.ROLE_HTTP: _('http/web interface'),
1136 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1136 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1137 cls.ROLE_API: _('api calls'),
1137 cls.ROLE_API: _('api calls'),
1138 cls.ROLE_FEED: _('feed access'),
1138 cls.ROLE_FEED: _('feed access'),
1139 }.get(role, role)
1139 }.get(role, role)
1140
1140
1141 @property
1141 @property
1142 def role_humanized(self):
1142 def role_humanized(self):
1143 return self._get_role_name(self.role)
1143 return self._get_role_name(self.role)
1144
1144
1145 def _get_scope(self):
1145 def _get_scope(self):
1146 if self.repo:
1146 if self.repo:
1147 return 'Repository: {}'.format(self.repo.repo_name)
1147 return 'Repository: {}'.format(self.repo.repo_name)
1148 if self.repo_group:
1148 if self.repo_group:
1149 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1149 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1150 return 'Global'
1150 return 'Global'
1151
1151
1152 @property
1152 @property
1153 def scope_humanized(self):
1153 def scope_humanized(self):
1154 return self._get_scope()
1154 return self._get_scope()
1155
1155
1156 @property
1156 @property
1157 def token_obfuscated(self):
1157 def token_obfuscated(self):
1158 if self.api_key:
1158 if self.api_key:
1159 return self.api_key[:4] + "****"
1159 return self.api_key[:4] + "****"
1160
1160
1161
1161
1162 class UserEmailMap(Base, BaseModel):
1162 class UserEmailMap(Base, BaseModel):
1163 __tablename__ = 'user_email_map'
1163 __tablename__ = 'user_email_map'
1164 __table_args__ = (
1164 __table_args__ = (
1165 Index('uem_email_idx', 'email'),
1165 Index('uem_email_idx', 'email'),
1166 UniqueConstraint('email'),
1166 UniqueConstraint('email'),
1167 base_table_args
1167 base_table_args
1168 )
1168 )
1169 __mapper_args__ = {}
1169 __mapper_args__ = {}
1170
1170
1171 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1171 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1172 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1172 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1173 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1173 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1174 user = relationship('User', lazy='joined')
1174 user = relationship('User', lazy='joined')
1175
1175
1176 @validates('_email')
1176 @validates('_email')
1177 def validate_email(self, key, email):
1177 def validate_email(self, key, email):
1178 # check if this email is not main one
1178 # check if this email is not main one
1179 main_email = Session().query(User).filter(User.email == email).scalar()
1179 main_email = Session().query(User).filter(User.email == email).scalar()
1180 if main_email is not None:
1180 if main_email is not None:
1181 raise AttributeError('email %s is present is user table' % email)
1181 raise AttributeError('email %s is present is user table' % email)
1182 return email
1182 return email
1183
1183
1184 @hybrid_property
1184 @hybrid_property
1185 def email(self):
1185 def email(self):
1186 return self._email
1186 return self._email
1187
1187
1188 @email.setter
1188 @email.setter
1189 def email(self, val):
1189 def email(self, val):
1190 self._email = val.lower() if val else None
1190 self._email = val.lower() if val else None
1191
1191
1192
1192
1193 class UserIpMap(Base, BaseModel):
1193 class UserIpMap(Base, BaseModel):
1194 __tablename__ = 'user_ip_map'
1194 __tablename__ = 'user_ip_map'
1195 __table_args__ = (
1195 __table_args__ = (
1196 UniqueConstraint('user_id', 'ip_addr'),
1196 UniqueConstraint('user_id', 'ip_addr'),
1197 base_table_args
1197 base_table_args
1198 )
1198 )
1199 __mapper_args__ = {}
1199 __mapper_args__ = {}
1200
1200
1201 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1201 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1202 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1202 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1203 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1203 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1204 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1204 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1205 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1205 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1206 user = relationship('User', lazy='joined')
1206 user = relationship('User', lazy='joined')
1207
1207
1208 @hybrid_property
1208 @hybrid_property
1209 def description_safe(self):
1209 def description_safe(self):
1210 from rhodecode.lib import helpers as h
1210 from rhodecode.lib import helpers as h
1211 return h.escape(self.description)
1211 return h.escape(self.description)
1212
1212
1213 @classmethod
1213 @classmethod
1214 def _get_ip_range(cls, ip_addr):
1214 def _get_ip_range(cls, ip_addr):
1215 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1215 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1216 return [str(net.network_address), str(net.broadcast_address)]
1216 return [str(net.network_address), str(net.broadcast_address)]
1217
1217
1218 def __json__(self):
1218 def __json__(self):
1219 return {
1219 return {
1220 'ip_addr': self.ip_addr,
1220 'ip_addr': self.ip_addr,
1221 'ip_range': self._get_ip_range(self.ip_addr),
1221 'ip_range': self._get_ip_range(self.ip_addr),
1222 }
1222 }
1223
1223
1224 def __unicode__(self):
1224 def __unicode__(self):
1225 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1225 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1226 self.user_id, self.ip_addr)
1226 self.user_id, self.ip_addr)
1227
1227
1228
1228
1229 class UserSshKeys(Base, BaseModel):
1229 class UserSshKeys(Base, BaseModel):
1230 __tablename__ = 'user_ssh_keys'
1230 __tablename__ = 'user_ssh_keys'
1231 __table_args__ = (
1231 __table_args__ = (
1232 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1232 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1233
1233
1234 UniqueConstraint('ssh_key_fingerprint'),
1234 UniqueConstraint('ssh_key_fingerprint'),
1235
1235
1236 base_table_args
1236 base_table_args
1237 )
1237 )
1238 __mapper_args__ = {}
1238 __mapper_args__ = {}
1239
1239
1240 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1240 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1241 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1241 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1242 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1242 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1243
1243
1244 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1244 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1245
1245
1246 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1246 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1247 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1247 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1248 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1248 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1249
1249
1250 user = relationship('User', lazy='joined')
1250 user = relationship('User', lazy='joined')
1251
1251
1252 def __json__(self):
1252 def __json__(self):
1253 data = {
1253 data = {
1254 'ssh_fingerprint': self.ssh_key_fingerprint,
1254 'ssh_fingerprint': self.ssh_key_fingerprint,
1255 'description': self.description,
1255 'description': self.description,
1256 'created_on': self.created_on
1256 'created_on': self.created_on
1257 }
1257 }
1258 return data
1258 return data
1259
1259
1260 def get_api_data(self):
1260 def get_api_data(self):
1261 data = self.__json__()
1261 data = self.__json__()
1262 return data
1262 return data
1263
1263
1264
1264
1265 class UserLog(Base, BaseModel):
1265 class UserLog(Base, BaseModel):
1266 __tablename__ = 'user_logs'
1266 __tablename__ = 'user_logs'
1267 __table_args__ = (
1267 __table_args__ = (
1268 base_table_args,
1268 base_table_args,
1269 )
1269 )
1270
1270
1271 VERSION_1 = 'v1'
1271 VERSION_1 = 'v1'
1272 VERSION_2 = 'v2'
1272 VERSION_2 = 'v2'
1273 VERSIONS = [VERSION_1, VERSION_2]
1273 VERSIONS = [VERSION_1, VERSION_2]
1274
1274
1275 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1275 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1276 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1276 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1277 username = Column("username", String(255), nullable=True, unique=None, default=None)
1277 username = Column("username", String(255), nullable=True, unique=None, default=None)
1278 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1278 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1279 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1279 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1280 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1280 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1281 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1281 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1282 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1282 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1283
1283
1284 version = Column("version", String(255), nullable=True, default=VERSION_1)
1284 version = Column("version", String(255), nullable=True, default=VERSION_1)
1285 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1285 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1286 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1286 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1287
1287
1288 def __unicode__(self):
1288 def __unicode__(self):
1289 return u"<%s('id:%s:%s')>" % (
1289 return u"<%s('id:%s:%s')>" % (
1290 self.__class__.__name__, self.repository_name, self.action)
1290 self.__class__.__name__, self.repository_name, self.action)
1291
1291
1292 def __json__(self):
1292 def __json__(self):
1293 return {
1293 return {
1294 'user_id': self.user_id,
1294 'user_id': self.user_id,
1295 'username': self.username,
1295 'username': self.username,
1296 'repository_id': self.repository_id,
1296 'repository_id': self.repository_id,
1297 'repository_name': self.repository_name,
1297 'repository_name': self.repository_name,
1298 'user_ip': self.user_ip,
1298 'user_ip': self.user_ip,
1299 'action_date': self.action_date,
1299 'action_date': self.action_date,
1300 'action': self.action,
1300 'action': self.action,
1301 }
1301 }
1302
1302
1303 @hybrid_property
1303 @hybrid_property
1304 def entry_id(self):
1304 def entry_id(self):
1305 return self.user_log_id
1305 return self.user_log_id
1306
1306
1307 @property
1307 @property
1308 def action_as_day(self):
1308 def action_as_day(self):
1309 return datetime.date(*self.action_date.timetuple()[:3])
1309 return datetime.date(*self.action_date.timetuple()[:3])
1310
1310
1311 user = relationship('User')
1311 user = relationship('User')
1312 repository = relationship('Repository', cascade='')
1312 repository = relationship('Repository', cascade='')
1313
1313
1314
1314
1315 class UserGroup(Base, BaseModel):
1315 class UserGroup(Base, BaseModel):
1316 __tablename__ = 'users_groups'
1316 __tablename__ = 'users_groups'
1317 __table_args__ = (
1317 __table_args__ = (
1318 base_table_args,
1318 base_table_args,
1319 )
1319 )
1320
1320
1321 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1321 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1322 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1322 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1323 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1323 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1324 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1324 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1325 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1325 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1326 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1326 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1327 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1327 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1328 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1328 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1329
1329
1330 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1330 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1331 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1331 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1332 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1332 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1333 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1333 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1334 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1334 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1335 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1335 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1336
1336
1337 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1337 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1338 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1338 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1339
1339
1340 @classmethod
1340 @classmethod
1341 def _load_group_data(cls, column):
1341 def _load_group_data(cls, column):
1342 if not column:
1342 if not column:
1343 return {}
1343 return {}
1344
1344
1345 try:
1345 try:
1346 return json.loads(column) or {}
1346 return json.loads(column) or {}
1347 except TypeError:
1347 except TypeError:
1348 return {}
1348 return {}
1349
1349
1350 @hybrid_property
1350 @hybrid_property
1351 def description_safe(self):
1351 def description_safe(self):
1352 from rhodecode.lib import helpers as h
1352 from rhodecode.lib import helpers as h
1353 return h.escape(self.user_group_description)
1353 return h.escape(self.user_group_description)
1354
1354
1355 @hybrid_property
1355 @hybrid_property
1356 def group_data(self):
1356 def group_data(self):
1357 return self._load_group_data(self._group_data)
1357 return self._load_group_data(self._group_data)
1358
1358
1359 @group_data.expression
1359 @group_data.expression
1360 def group_data(self, **kwargs):
1360 def group_data(self, **kwargs):
1361 return self._group_data
1361 return self._group_data
1362
1362
1363 @group_data.setter
1363 @group_data.setter
1364 def group_data(self, val):
1364 def group_data(self, val):
1365 try:
1365 try:
1366 self._group_data = json.dumps(val)
1366 self._group_data = json.dumps(val)
1367 except Exception:
1367 except Exception:
1368 log.error(traceback.format_exc())
1368 log.error(traceback.format_exc())
1369
1369
1370 @classmethod
1370 @classmethod
1371 def _load_sync(cls, group_data):
1371 def _load_sync(cls, group_data):
1372 if group_data:
1372 if group_data:
1373 return group_data.get('extern_type')
1373 return group_data.get('extern_type')
1374
1374
1375 @property
1375 @property
1376 def sync(self):
1376 def sync(self):
1377 return self._load_sync(self.group_data)
1377 return self._load_sync(self.group_data)
1378
1378
1379 def __unicode__(self):
1379 def __unicode__(self):
1380 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1380 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1381 self.users_group_id,
1381 self.users_group_id,
1382 self.users_group_name)
1382 self.users_group_name)
1383
1383
1384 @classmethod
1384 @classmethod
1385 def get_by_group_name(cls, group_name, cache=False,
1385 def get_by_group_name(cls, group_name, cache=False,
1386 case_insensitive=False):
1386 case_insensitive=False):
1387 if case_insensitive:
1387 if case_insensitive:
1388 q = cls.query().filter(func.lower(cls.users_group_name) ==
1388 q = cls.query().filter(func.lower(cls.users_group_name) ==
1389 func.lower(group_name))
1389 func.lower(group_name))
1390
1390
1391 else:
1391 else:
1392 q = cls.query().filter(cls.users_group_name == group_name)
1392 q = cls.query().filter(cls.users_group_name == group_name)
1393 if cache:
1393 if cache:
1394 q = q.options(
1394 q = q.options(
1395 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1395 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1396 return q.scalar()
1396 return q.scalar()
1397
1397
1398 @classmethod
1398 @classmethod
1399 def get(cls, user_group_id, cache=False):
1399 def get(cls, user_group_id, cache=False):
1400 if not user_group_id:
1400 if not user_group_id:
1401 return
1401 return
1402
1402
1403 user_group = cls.query()
1403 user_group = cls.query()
1404 if cache:
1404 if cache:
1405 user_group = user_group.options(
1405 user_group = user_group.options(
1406 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1406 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1407 return user_group.get(user_group_id)
1407 return user_group.get(user_group_id)
1408
1408
1409 def permissions(self, with_admins=True, with_owner=True,
1409 def permissions(self, with_admins=True, with_owner=True,
1410 expand_from_user_groups=False):
1410 expand_from_user_groups=False):
1411 """
1411 """
1412 Permissions for user groups
1412 Permissions for user groups
1413 """
1413 """
1414 _admin_perm = 'usergroup.admin'
1414 _admin_perm = 'usergroup.admin'
1415
1415
1416 owner_row = []
1416 owner_row = []
1417 if with_owner:
1417 if with_owner:
1418 usr = AttributeDict(self.user.get_dict())
1418 usr = AttributeDict(self.user.get_dict())
1419 usr.owner_row = True
1419 usr.owner_row = True
1420 usr.permission = _admin_perm
1420 usr.permission = _admin_perm
1421 owner_row.append(usr)
1421 owner_row.append(usr)
1422
1422
1423 super_admin_ids = []
1423 super_admin_ids = []
1424 super_admin_rows = []
1424 super_admin_rows = []
1425 if with_admins:
1425 if with_admins:
1426 for usr in User.get_all_super_admins():
1426 for usr in User.get_all_super_admins():
1427 super_admin_ids.append(usr.user_id)
1427 super_admin_ids.append(usr.user_id)
1428 # if this admin is also owner, don't double the record
1428 # if this admin is also owner, don't double the record
1429 if usr.user_id == owner_row[0].user_id:
1429 if usr.user_id == owner_row[0].user_id:
1430 owner_row[0].admin_row = True
1430 owner_row[0].admin_row = True
1431 else:
1431 else:
1432 usr = AttributeDict(usr.get_dict())
1432 usr = AttributeDict(usr.get_dict())
1433 usr.admin_row = True
1433 usr.admin_row = True
1434 usr.permission = _admin_perm
1434 usr.permission = _admin_perm
1435 super_admin_rows.append(usr)
1435 super_admin_rows.append(usr)
1436
1436
1437 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1437 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1438 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1438 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1439 joinedload(UserUserGroupToPerm.user),
1439 joinedload(UserUserGroupToPerm.user),
1440 joinedload(UserUserGroupToPerm.permission),)
1440 joinedload(UserUserGroupToPerm.permission),)
1441
1441
1442 # get owners and admins and permissions. We do a trick of re-writing
1442 # get owners and admins and permissions. We do a trick of re-writing
1443 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1443 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1444 # has a global reference and changing one object propagates to all
1444 # has a global reference and changing one object propagates to all
1445 # others. This means if admin is also an owner admin_row that change
1445 # others. This means if admin is also an owner admin_row that change
1446 # would propagate to both objects
1446 # would propagate to both objects
1447 perm_rows = []
1447 perm_rows = []
1448 for _usr in q.all():
1448 for _usr in q.all():
1449 usr = AttributeDict(_usr.user.get_dict())
1449 usr = AttributeDict(_usr.user.get_dict())
1450 # if this user is also owner/admin, mark as duplicate record
1450 # if this user is also owner/admin, mark as duplicate record
1451 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1451 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1452 usr.duplicate_perm = True
1452 usr.duplicate_perm = True
1453 usr.permission = _usr.permission.permission_name
1453 usr.permission = _usr.permission.permission_name
1454 perm_rows.append(usr)
1454 perm_rows.append(usr)
1455
1455
1456 # filter the perm rows by 'default' first and then sort them by
1456 # filter the perm rows by 'default' first and then sort them by
1457 # admin,write,read,none permissions sorted again alphabetically in
1457 # admin,write,read,none permissions sorted again alphabetically in
1458 # each group
1458 # each group
1459 perm_rows = sorted(perm_rows, key=display_user_sort)
1459 perm_rows = sorted(perm_rows, key=display_user_sort)
1460
1460
1461 user_groups_rows = []
1461 user_groups_rows = []
1462 if expand_from_user_groups:
1462 if expand_from_user_groups:
1463 for ug in self.permission_user_groups(with_members=True):
1463 for ug in self.permission_user_groups(with_members=True):
1464 for user_data in ug.members:
1464 for user_data in ug.members:
1465 user_groups_rows.append(user_data)
1465 user_groups_rows.append(user_data)
1466
1466
1467 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1467 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1468
1468
1469 def permission_user_groups(self, with_members=False):
1469 def permission_user_groups(self, with_members=False):
1470 q = UserGroupUserGroupToPerm.query()\
1470 q = UserGroupUserGroupToPerm.query()\
1471 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1471 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1472 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1472 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1473 joinedload(UserGroupUserGroupToPerm.target_user_group),
1473 joinedload(UserGroupUserGroupToPerm.target_user_group),
1474 joinedload(UserGroupUserGroupToPerm.permission),)
1474 joinedload(UserGroupUserGroupToPerm.permission),)
1475
1475
1476 perm_rows = []
1476 perm_rows = []
1477 for _user_group in q.all():
1477 for _user_group in q.all():
1478 entry = AttributeDict(_user_group.user_group.get_dict())
1478 entry = AttributeDict(_user_group.user_group.get_dict())
1479 entry.permission = _user_group.permission.permission_name
1479 entry.permission = _user_group.permission.permission_name
1480 if with_members:
1480 if with_members:
1481 entry.members = [x.user.get_dict()
1481 entry.members = [x.user.get_dict()
1482 for x in _user_group.user_group.members]
1482 for x in _user_group.user_group.members]
1483 perm_rows.append(entry)
1483 perm_rows.append(entry)
1484
1484
1485 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1485 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1486 return perm_rows
1486 return perm_rows
1487
1487
1488 def _get_default_perms(self, user_group, suffix=''):
1488 def _get_default_perms(self, user_group, suffix=''):
1489 from rhodecode.model.permission import PermissionModel
1489 from rhodecode.model.permission import PermissionModel
1490 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1490 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1491
1491
1492 def get_default_perms(self, suffix=''):
1492 def get_default_perms(self, suffix=''):
1493 return self._get_default_perms(self, suffix)
1493 return self._get_default_perms(self, suffix)
1494
1494
1495 def get_api_data(self, with_group_members=True, include_secrets=False):
1495 def get_api_data(self, with_group_members=True, include_secrets=False):
1496 """
1496 """
1497 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1497 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1498 basically forwarded.
1498 basically forwarded.
1499
1499
1500 """
1500 """
1501 user_group = self
1501 user_group = self
1502 data = {
1502 data = {
1503 'users_group_id': user_group.users_group_id,
1503 'users_group_id': user_group.users_group_id,
1504 'group_name': user_group.users_group_name,
1504 'group_name': user_group.users_group_name,
1505 'group_description': user_group.user_group_description,
1505 'group_description': user_group.user_group_description,
1506 'active': user_group.users_group_active,
1506 'active': user_group.users_group_active,
1507 'owner': user_group.user.username,
1507 'owner': user_group.user.username,
1508 'sync': user_group.sync,
1508 'sync': user_group.sync,
1509 'owner_email': user_group.user.email,
1509 'owner_email': user_group.user.email,
1510 }
1510 }
1511
1511
1512 if with_group_members:
1512 if with_group_members:
1513 users = []
1513 users = []
1514 for user in user_group.members:
1514 for user in user_group.members:
1515 user = user.user
1515 user = user.user
1516 users.append(user.get_api_data(include_secrets=include_secrets))
1516 users.append(user.get_api_data(include_secrets=include_secrets))
1517 data['users'] = users
1517 data['users'] = users
1518
1518
1519 return data
1519 return data
1520
1520
1521
1521
1522 class UserGroupMember(Base, BaseModel):
1522 class UserGroupMember(Base, BaseModel):
1523 __tablename__ = 'users_groups_members'
1523 __tablename__ = 'users_groups_members'
1524 __table_args__ = (
1524 __table_args__ = (
1525 base_table_args,
1525 base_table_args,
1526 )
1526 )
1527
1527
1528 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1528 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1529 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1529 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1530 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1530 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1531
1531
1532 user = relationship('User', lazy='joined')
1532 user = relationship('User', lazy='joined')
1533 users_group = relationship('UserGroup')
1533 users_group = relationship('UserGroup')
1534
1534
1535 def __init__(self, gr_id='', u_id=''):
1535 def __init__(self, gr_id='', u_id=''):
1536 self.users_group_id = gr_id
1536 self.users_group_id = gr_id
1537 self.user_id = u_id
1537 self.user_id = u_id
1538
1538
1539
1539
1540 class RepositoryField(Base, BaseModel):
1540 class RepositoryField(Base, BaseModel):
1541 __tablename__ = 'repositories_fields'
1541 __tablename__ = 'repositories_fields'
1542 __table_args__ = (
1542 __table_args__ = (
1543 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1543 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1544 base_table_args,
1544 base_table_args,
1545 )
1545 )
1546
1546
1547 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1547 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1548
1548
1549 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1549 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1550 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1550 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1551 field_key = Column("field_key", String(250))
1551 field_key = Column("field_key", String(250))
1552 field_label = Column("field_label", String(1024), nullable=False)
1552 field_label = Column("field_label", String(1024), nullable=False)
1553 field_value = Column("field_value", String(10000), nullable=False)
1553 field_value = Column("field_value", String(10000), nullable=False)
1554 field_desc = Column("field_desc", String(1024), nullable=False)
1554 field_desc = Column("field_desc", String(1024), nullable=False)
1555 field_type = Column("field_type", String(255), nullable=False, unique=None)
1555 field_type = Column("field_type", String(255), nullable=False, unique=None)
1556 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1556 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1557
1557
1558 repository = relationship('Repository')
1558 repository = relationship('Repository')
1559
1559
1560 @property
1560 @property
1561 def field_key_prefixed(self):
1561 def field_key_prefixed(self):
1562 return 'ex_%s' % self.field_key
1562 return 'ex_%s' % self.field_key
1563
1563
1564 @classmethod
1564 @classmethod
1565 def un_prefix_key(cls, key):
1565 def un_prefix_key(cls, key):
1566 if key.startswith(cls.PREFIX):
1566 if key.startswith(cls.PREFIX):
1567 return key[len(cls.PREFIX):]
1567 return key[len(cls.PREFIX):]
1568 return key
1568 return key
1569
1569
1570 @classmethod
1570 @classmethod
1571 def get_by_key_name(cls, key, repo):
1571 def get_by_key_name(cls, key, repo):
1572 row = cls.query()\
1572 row = cls.query()\
1573 .filter(cls.repository == repo)\
1573 .filter(cls.repository == repo)\
1574 .filter(cls.field_key == key).scalar()
1574 .filter(cls.field_key == key).scalar()
1575 return row
1575 return row
1576
1576
1577
1577
1578 class Repository(Base, BaseModel):
1578 class Repository(Base, BaseModel):
1579 __tablename__ = 'repositories'
1579 __tablename__ = 'repositories'
1580 __table_args__ = (
1580 __table_args__ = (
1581 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1581 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1582 base_table_args,
1582 base_table_args,
1583 )
1583 )
1584 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1584 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1585 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1585 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1586 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1586 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1587
1587
1588 STATE_CREATED = 'repo_state_created'
1588 STATE_CREATED = 'repo_state_created'
1589 STATE_PENDING = 'repo_state_pending'
1589 STATE_PENDING = 'repo_state_pending'
1590 STATE_ERROR = 'repo_state_error'
1590 STATE_ERROR = 'repo_state_error'
1591
1591
1592 LOCK_AUTOMATIC = 'lock_auto'
1592 LOCK_AUTOMATIC = 'lock_auto'
1593 LOCK_API = 'lock_api'
1593 LOCK_API = 'lock_api'
1594 LOCK_WEB = 'lock_web'
1594 LOCK_WEB = 'lock_web'
1595 LOCK_PULL = 'lock_pull'
1595 LOCK_PULL = 'lock_pull'
1596
1596
1597 NAME_SEP = URL_SEP
1597 NAME_SEP = URL_SEP
1598
1598
1599 repo_id = Column(
1599 repo_id = Column(
1600 "repo_id", Integer(), nullable=False, unique=True, default=None,
1600 "repo_id", Integer(), nullable=False, unique=True, default=None,
1601 primary_key=True)
1601 primary_key=True)
1602 _repo_name = Column(
1602 _repo_name = Column(
1603 "repo_name", Text(), nullable=False, default=None)
1603 "repo_name", Text(), nullable=False, default=None)
1604 _repo_name_hash = Column(
1604 _repo_name_hash = Column(
1605 "repo_name_hash", String(255), nullable=False, unique=True)
1605 "repo_name_hash", String(255), nullable=False, unique=True)
1606 repo_state = Column("repo_state", String(255), nullable=True)
1606 repo_state = Column("repo_state", String(255), nullable=True)
1607
1607
1608 clone_uri = Column(
1608 clone_uri = Column(
1609 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1609 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1610 default=None)
1610 default=None)
1611 push_uri = Column(
1611 push_uri = Column(
1612 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1612 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1613 default=None)
1613 default=None)
1614 repo_type = Column(
1614 repo_type = Column(
1615 "repo_type", String(255), nullable=False, unique=False, default=None)
1615 "repo_type", String(255), nullable=False, unique=False, default=None)
1616 user_id = Column(
1616 user_id = Column(
1617 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1617 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1618 unique=False, default=None)
1618 unique=False, default=None)
1619 private = Column(
1619 private = Column(
1620 "private", Boolean(), nullable=True, unique=None, default=None)
1620 "private", Boolean(), nullable=True, unique=None, default=None)
1621 archived = Column(
1621 archived = Column(
1622 "archived", Boolean(), nullable=True, unique=None, default=None)
1622 "archived", Boolean(), nullable=True, unique=None, default=None)
1623 enable_statistics = Column(
1623 enable_statistics = Column(
1624 "statistics", Boolean(), nullable=True, unique=None, default=True)
1624 "statistics", Boolean(), nullable=True, unique=None, default=True)
1625 enable_downloads = Column(
1625 enable_downloads = Column(
1626 "downloads", Boolean(), nullable=True, unique=None, default=True)
1626 "downloads", Boolean(), nullable=True, unique=None, default=True)
1627 description = Column(
1627 description = Column(
1628 "description", String(10000), nullable=True, unique=None, default=None)
1628 "description", String(10000), nullable=True, unique=None, default=None)
1629 created_on = Column(
1629 created_on = Column(
1630 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1630 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1631 default=datetime.datetime.now)
1631 default=datetime.datetime.now)
1632 updated_on = Column(
1632 updated_on = Column(
1633 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1633 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1634 default=datetime.datetime.now)
1634 default=datetime.datetime.now)
1635 _landing_revision = Column(
1635 _landing_revision = Column(
1636 "landing_revision", String(255), nullable=False, unique=False,
1636 "landing_revision", String(255), nullable=False, unique=False,
1637 default=None)
1637 default=None)
1638 enable_locking = Column(
1638 enable_locking = Column(
1639 "enable_locking", Boolean(), nullable=False, unique=None,
1639 "enable_locking", Boolean(), nullable=False, unique=None,
1640 default=False)
1640 default=False)
1641 _locked = Column(
1641 _locked = Column(
1642 "locked", String(255), nullable=True, unique=False, default=None)
1642 "locked", String(255), nullable=True, unique=False, default=None)
1643 _changeset_cache = Column(
1643 _changeset_cache = Column(
1644 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1644 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1645
1645
1646 fork_id = Column(
1646 fork_id = Column(
1647 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1647 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1648 nullable=True, unique=False, default=None)
1648 nullable=True, unique=False, default=None)
1649 group_id = Column(
1649 group_id = Column(
1650 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1650 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1651 unique=False, default=None)
1651 unique=False, default=None)
1652
1652
1653 user = relationship('User', lazy='joined')
1653 user = relationship('User', lazy='joined')
1654 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1654 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1655 group = relationship('RepoGroup', lazy='joined')
1655 group = relationship('RepoGroup', lazy='joined')
1656 repo_to_perm = relationship(
1656 repo_to_perm = relationship(
1657 'UserRepoToPerm', cascade='all',
1657 'UserRepoToPerm', cascade='all',
1658 order_by='UserRepoToPerm.repo_to_perm_id')
1658 order_by='UserRepoToPerm.repo_to_perm_id')
1659 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1659 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1660 stats = relationship('Statistics', cascade='all', uselist=False)
1660 stats = relationship('Statistics', cascade='all', uselist=False)
1661
1661
1662 followers = relationship(
1662 followers = relationship(
1663 'UserFollowing',
1663 'UserFollowing',
1664 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1664 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1665 cascade='all')
1665 cascade='all')
1666 extra_fields = relationship(
1666 extra_fields = relationship(
1667 'RepositoryField', cascade="all, delete, delete-orphan")
1667 'RepositoryField', cascade="all, delete, delete-orphan")
1668 logs = relationship('UserLog')
1668 logs = relationship('UserLog')
1669 comments = relationship(
1669 comments = relationship(
1670 'ChangesetComment', cascade="all, delete, delete-orphan")
1670 'ChangesetComment', cascade="all, delete, delete-orphan")
1671 pull_requests_source = relationship(
1671 pull_requests_source = relationship(
1672 'PullRequest',
1672 'PullRequest',
1673 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1673 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1674 cascade="all, delete, delete-orphan")
1674 cascade="all, delete, delete-orphan")
1675 pull_requests_target = relationship(
1675 pull_requests_target = relationship(
1676 'PullRequest',
1676 'PullRequest',
1677 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1677 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1678 cascade="all, delete, delete-orphan")
1678 cascade="all, delete, delete-orphan")
1679 ui = relationship('RepoRhodeCodeUi', cascade="all")
1679 ui = relationship('RepoRhodeCodeUi', cascade="all")
1680 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1680 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1681 integrations = relationship('Integration',
1681 integrations = relationship('Integration',
1682 cascade="all, delete, delete-orphan")
1682 cascade="all, delete, delete-orphan")
1683
1683
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1685
1685
1686 def __unicode__(self):
1686 def __unicode__(self):
1687 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1687 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1688 safe_unicode(self.repo_name))
1688 safe_unicode(self.repo_name))
1689
1689
1690 @hybrid_property
1690 @hybrid_property
1691 def description_safe(self):
1691 def description_safe(self):
1692 from rhodecode.lib import helpers as h
1692 from rhodecode.lib import helpers as h
1693 return h.escape(self.description)
1693 return h.escape(self.description)
1694
1694
1695 @hybrid_property
1695 @hybrid_property
1696 def landing_rev(self):
1696 def landing_rev(self):
1697 # always should return [rev_type, rev]
1697 # always should return [rev_type, rev]
1698 if self._landing_revision:
1698 if self._landing_revision:
1699 _rev_info = self._landing_revision.split(':')
1699 _rev_info = self._landing_revision.split(':')
1700 if len(_rev_info) < 2:
1700 if len(_rev_info) < 2:
1701 _rev_info.insert(0, 'rev')
1701 _rev_info.insert(0, 'rev')
1702 return [_rev_info[0], _rev_info[1]]
1702 return [_rev_info[0], _rev_info[1]]
1703 return [None, None]
1703 return [None, None]
1704
1704
1705 @landing_rev.setter
1705 @landing_rev.setter
1706 def landing_rev(self, val):
1706 def landing_rev(self, val):
1707 if ':' not in val:
1707 if ':' not in val:
1708 raise ValueError('value must be delimited with `:` and consist '
1708 raise ValueError('value must be delimited with `:` and consist '
1709 'of <rev_type>:<rev>, got %s instead' % val)
1709 'of <rev_type>:<rev>, got %s instead' % val)
1710 self._landing_revision = val
1710 self._landing_revision = val
1711
1711
1712 @hybrid_property
1712 @hybrid_property
1713 def locked(self):
1713 def locked(self):
1714 if self._locked:
1714 if self._locked:
1715 user_id, timelocked, reason = self._locked.split(':')
1715 user_id, timelocked, reason = self._locked.split(':')
1716 lock_values = int(user_id), timelocked, reason
1716 lock_values = int(user_id), timelocked, reason
1717 else:
1717 else:
1718 lock_values = [None, None, None]
1718 lock_values = [None, None, None]
1719 return lock_values
1719 return lock_values
1720
1720
1721 @locked.setter
1721 @locked.setter
1722 def locked(self, val):
1722 def locked(self, val):
1723 if val and isinstance(val, (list, tuple)):
1723 if val and isinstance(val, (list, tuple)):
1724 self._locked = ':'.join(map(str, val))
1724 self._locked = ':'.join(map(str, val))
1725 else:
1725 else:
1726 self._locked = None
1726 self._locked = None
1727
1727
1728 @hybrid_property
1728 @hybrid_property
1729 def changeset_cache(self):
1729 def changeset_cache(self):
1730 from rhodecode.lib.vcs.backends.base import EmptyCommit
1730 from rhodecode.lib.vcs.backends.base import EmptyCommit
1731 dummy = EmptyCommit().__json__()
1731 dummy = EmptyCommit().__json__()
1732 if not self._changeset_cache:
1732 if not self._changeset_cache:
1733 dummy['source_repo_id'] = self.repo_id
1733 dummy['source_repo_id'] = self.repo_id
1734 return json.loads(json.dumps(dummy))
1734 return json.loads(json.dumps(dummy))
1735
1735
1736 try:
1736 try:
1737 return json.loads(self._changeset_cache)
1737 return json.loads(self._changeset_cache)
1738 except TypeError:
1738 except TypeError:
1739 return dummy
1739 return dummy
1740 except Exception:
1740 except Exception:
1741 log.error(traceback.format_exc())
1741 log.error(traceback.format_exc())
1742 return dummy
1742 return dummy
1743
1743
1744 @changeset_cache.setter
1744 @changeset_cache.setter
1745 def changeset_cache(self, val):
1745 def changeset_cache(self, val):
1746 try:
1746 try:
1747 self._changeset_cache = json.dumps(val)
1747 self._changeset_cache = json.dumps(val)
1748 except Exception:
1748 except Exception:
1749 log.error(traceback.format_exc())
1749 log.error(traceback.format_exc())
1750
1750
1751 @hybrid_property
1751 @hybrid_property
1752 def repo_name(self):
1752 def repo_name(self):
1753 return self._repo_name
1753 return self._repo_name
1754
1754
1755 @repo_name.setter
1755 @repo_name.setter
1756 def repo_name(self, value):
1756 def repo_name(self, value):
1757 self._repo_name = value
1757 self._repo_name = value
1758 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1758 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1759
1759
1760 @classmethod
1760 @classmethod
1761 def normalize_repo_name(cls, repo_name):
1761 def normalize_repo_name(cls, repo_name):
1762 """
1762 """
1763 Normalizes os specific repo_name to the format internally stored inside
1763 Normalizes os specific repo_name to the format internally stored inside
1764 database using URL_SEP
1764 database using URL_SEP
1765
1765
1766 :param cls:
1766 :param cls:
1767 :param repo_name:
1767 :param repo_name:
1768 """
1768 """
1769 return cls.NAME_SEP.join(repo_name.split(os.sep))
1769 return cls.NAME_SEP.join(repo_name.split(os.sep))
1770
1770
1771 @classmethod
1771 @classmethod
1772 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1772 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1773 session = Session()
1773 session = Session()
1774 q = session.query(cls).filter(cls.repo_name == repo_name)
1774 q = session.query(cls).filter(cls.repo_name == repo_name)
1775
1775
1776 if cache:
1776 if cache:
1777 if identity_cache:
1777 if identity_cache:
1778 val = cls.identity_cache(session, 'repo_name', repo_name)
1778 val = cls.identity_cache(session, 'repo_name', repo_name)
1779 if val:
1779 if val:
1780 return val
1780 return val
1781 else:
1781 else:
1782 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1782 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1783 q = q.options(
1783 q = q.options(
1784 FromCache("sql_cache_short", cache_key))
1784 FromCache("sql_cache_short", cache_key))
1785
1785
1786 return q.scalar()
1786 return q.scalar()
1787
1787
1788 @classmethod
1788 @classmethod
1789 def get_by_id_or_repo_name(cls, repoid):
1789 def get_by_id_or_repo_name(cls, repoid):
1790 if isinstance(repoid, (int, long)):
1790 if isinstance(repoid, (int, long)):
1791 try:
1791 try:
1792 repo = cls.get(repoid)
1792 repo = cls.get(repoid)
1793 except ValueError:
1793 except ValueError:
1794 repo = None
1794 repo = None
1795 else:
1795 else:
1796 repo = cls.get_by_repo_name(repoid)
1796 repo = cls.get_by_repo_name(repoid)
1797 return repo
1797 return repo
1798
1798
1799 @classmethod
1799 @classmethod
1800 def get_by_full_path(cls, repo_full_path):
1800 def get_by_full_path(cls, repo_full_path):
1801 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1801 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1802 repo_name = cls.normalize_repo_name(repo_name)
1802 repo_name = cls.normalize_repo_name(repo_name)
1803 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1803 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1804
1804
1805 @classmethod
1805 @classmethod
1806 def get_repo_forks(cls, repo_id):
1806 def get_repo_forks(cls, repo_id):
1807 return cls.query().filter(Repository.fork_id == repo_id)
1807 return cls.query().filter(Repository.fork_id == repo_id)
1808
1808
1809 @classmethod
1809 @classmethod
1810 def base_path(cls):
1810 def base_path(cls):
1811 """
1811 """
1812 Returns base path when all repos are stored
1812 Returns base path when all repos are stored
1813
1813
1814 :param cls:
1814 :param cls:
1815 """
1815 """
1816 q = Session().query(RhodeCodeUi)\
1816 q = Session().query(RhodeCodeUi)\
1817 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1817 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1818 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1818 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1819 return q.one().ui_value
1819 return q.one().ui_value
1820
1820
1821 @classmethod
1821 @classmethod
1822 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1822 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1823 case_insensitive=True, archived=False):
1823 case_insensitive=True, archived=False):
1824 q = Repository.query()
1824 q = Repository.query()
1825
1825
1826 if not archived:
1826 if not archived:
1827 q = q.filter(Repository.archived.isnot(true()))
1827 q = q.filter(Repository.archived.isnot(true()))
1828
1828
1829 if not isinstance(user_id, Optional):
1829 if not isinstance(user_id, Optional):
1830 q = q.filter(Repository.user_id == user_id)
1830 q = q.filter(Repository.user_id == user_id)
1831
1831
1832 if not isinstance(group_id, Optional):
1832 if not isinstance(group_id, Optional):
1833 q = q.filter(Repository.group_id == group_id)
1833 q = q.filter(Repository.group_id == group_id)
1834
1834
1835 if case_insensitive:
1835 if case_insensitive:
1836 q = q.order_by(func.lower(Repository.repo_name))
1836 q = q.order_by(func.lower(Repository.repo_name))
1837 else:
1837 else:
1838 q = q.order_by(Repository.repo_name)
1838 q = q.order_by(Repository.repo_name)
1839
1839
1840 return q.all()
1840 return q.all()
1841
1841
1842 @property
1842 @property
1843 def forks(self):
1843 def forks(self):
1844 """
1844 """
1845 Return forks of this repo
1845 Return forks of this repo
1846 """
1846 """
1847 return Repository.get_repo_forks(self.repo_id)
1847 return Repository.get_repo_forks(self.repo_id)
1848
1848
1849 @property
1849 @property
1850 def parent(self):
1850 def parent(self):
1851 """
1851 """
1852 Returns fork parent
1852 Returns fork parent
1853 """
1853 """
1854 return self.fork
1854 return self.fork
1855
1855
1856 @property
1856 @property
1857 def just_name(self):
1857 def just_name(self):
1858 return self.repo_name.split(self.NAME_SEP)[-1]
1858 return self.repo_name.split(self.NAME_SEP)[-1]
1859
1859
1860 @property
1860 @property
1861 def groups_with_parents(self):
1861 def groups_with_parents(self):
1862 groups = []
1862 groups = []
1863 if self.group is None:
1863 if self.group is None:
1864 return groups
1864 return groups
1865
1865
1866 cur_gr = self.group
1866 cur_gr = self.group
1867 groups.insert(0, cur_gr)
1867 groups.insert(0, cur_gr)
1868 while 1:
1868 while 1:
1869 gr = getattr(cur_gr, 'parent_group', None)
1869 gr = getattr(cur_gr, 'parent_group', None)
1870 cur_gr = cur_gr.parent_group
1870 cur_gr = cur_gr.parent_group
1871 if gr is None:
1871 if gr is None:
1872 break
1872 break
1873 groups.insert(0, gr)
1873 groups.insert(0, gr)
1874
1874
1875 return groups
1875 return groups
1876
1876
1877 @property
1877 @property
1878 def groups_and_repo(self):
1878 def groups_and_repo(self):
1879 return self.groups_with_parents, self
1879 return self.groups_with_parents, self
1880
1880
1881 @LazyProperty
1881 @LazyProperty
1882 def repo_path(self):
1882 def repo_path(self):
1883 """
1883 """
1884 Returns base full path for that repository means where it actually
1884 Returns base full path for that repository means where it actually
1885 exists on a filesystem
1885 exists on a filesystem
1886 """
1886 """
1887 q = Session().query(RhodeCodeUi).filter(
1887 q = Session().query(RhodeCodeUi).filter(
1888 RhodeCodeUi.ui_key == self.NAME_SEP)
1888 RhodeCodeUi.ui_key == self.NAME_SEP)
1889 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1889 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1890 return q.one().ui_value
1890 return q.one().ui_value
1891
1891
1892 @property
1892 @property
1893 def repo_full_path(self):
1893 def repo_full_path(self):
1894 p = [self.repo_path]
1894 p = [self.repo_path]
1895 # we need to split the name by / since this is how we store the
1895 # we need to split the name by / since this is how we store the
1896 # names in the database, but that eventually needs to be converted
1896 # names in the database, but that eventually needs to be converted
1897 # into a valid system path
1897 # into a valid system path
1898 p += self.repo_name.split(self.NAME_SEP)
1898 p += self.repo_name.split(self.NAME_SEP)
1899 return os.path.join(*map(safe_unicode, p))
1899 return os.path.join(*map(safe_unicode, p))
1900
1900
1901 @property
1901 @property
1902 def cache_keys(self):
1902 def cache_keys(self):
1903 """
1903 """
1904 Returns associated cache keys for that repo
1904 Returns associated cache keys for that repo
1905 """
1905 """
1906 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1906 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1907 repo_id=self.repo_id)
1907 repo_id=self.repo_id)
1908 return CacheKey.query()\
1908 return CacheKey.query()\
1909 .filter(CacheKey.cache_args == invalidation_namespace)\
1909 .filter(CacheKey.cache_args == invalidation_namespace)\
1910 .order_by(CacheKey.cache_key)\
1910 .order_by(CacheKey.cache_key)\
1911 .all()
1911 .all()
1912
1912
1913 @property
1913 @property
1914 def cached_diffs_relative_dir(self):
1914 def cached_diffs_relative_dir(self):
1915 """
1915 """
1916 Return a relative to the repository store path of cached diffs
1916 Return a relative to the repository store path of cached diffs
1917 used for safe display for users, who shouldn't know the absolute store
1917 used for safe display for users, who shouldn't know the absolute store
1918 path
1918 path
1919 """
1919 """
1920 return os.path.join(
1920 return os.path.join(
1921 os.path.dirname(self.repo_name),
1921 os.path.dirname(self.repo_name),
1922 self.cached_diffs_dir.split(os.path.sep)[-1])
1922 self.cached_diffs_dir.split(os.path.sep)[-1])
1923
1923
1924 @property
1924 @property
1925 def cached_diffs_dir(self):
1925 def cached_diffs_dir(self):
1926 path = self.repo_full_path
1926 path = self.repo_full_path
1927 return os.path.join(
1927 return os.path.join(
1928 os.path.dirname(path),
1928 os.path.dirname(path),
1929 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1929 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1930
1930
1931 def cached_diffs(self):
1931 def cached_diffs(self):
1932 diff_cache_dir = self.cached_diffs_dir
1932 diff_cache_dir = self.cached_diffs_dir
1933 if os.path.isdir(diff_cache_dir):
1933 if os.path.isdir(diff_cache_dir):
1934 return os.listdir(diff_cache_dir)
1934 return os.listdir(diff_cache_dir)
1935 return []
1935 return []
1936
1936
1937 def shadow_repos(self):
1937 def shadow_repos(self):
1938 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1938 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1939 return [
1939 return [
1940 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1940 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1941 if x.startswith(shadow_repos_pattern)]
1941 if x.startswith(shadow_repos_pattern)]
1942
1942
1943 def get_new_name(self, repo_name):
1943 def get_new_name(self, repo_name):
1944 """
1944 """
1945 returns new full repository name based on assigned group and new new
1945 returns new full repository name based on assigned group and new new
1946
1946
1947 :param group_name:
1947 :param group_name:
1948 """
1948 """
1949 path_prefix = self.group.full_path_splitted if self.group else []
1949 path_prefix = self.group.full_path_splitted if self.group else []
1950 return self.NAME_SEP.join(path_prefix + [repo_name])
1950 return self.NAME_SEP.join(path_prefix + [repo_name])
1951
1951
1952 @property
1952 @property
1953 def _config(self):
1953 def _config(self):
1954 """
1954 """
1955 Returns db based config object.
1955 Returns db based config object.
1956 """
1956 """
1957 from rhodecode.lib.utils import make_db_config
1957 from rhodecode.lib.utils import make_db_config
1958 return make_db_config(clear_session=False, repo=self)
1958 return make_db_config(clear_session=False, repo=self)
1959
1959
1960 def permissions(self, with_admins=True, with_owner=True,
1960 def permissions(self, with_admins=True, with_owner=True,
1961 expand_from_user_groups=False):
1961 expand_from_user_groups=False):
1962 """
1962 """
1963 Permissions for repositories
1963 Permissions for repositories
1964 """
1964 """
1965 _admin_perm = 'repository.admin'
1965 _admin_perm = 'repository.admin'
1966
1966
1967 owner_row = []
1967 owner_row = []
1968 if with_owner:
1968 if with_owner:
1969 usr = AttributeDict(self.user.get_dict())
1969 usr = AttributeDict(self.user.get_dict())
1970 usr.owner_row = True
1970 usr.owner_row = True
1971 usr.permission = _admin_perm
1971 usr.permission = _admin_perm
1972 usr.permission_id = None
1972 usr.permission_id = None
1973 owner_row.append(usr)
1973 owner_row.append(usr)
1974
1974
1975 super_admin_ids = []
1975 super_admin_ids = []
1976 super_admin_rows = []
1976 super_admin_rows = []
1977 if with_admins:
1977 if with_admins:
1978 for usr in User.get_all_super_admins():
1978 for usr in User.get_all_super_admins():
1979 super_admin_ids.append(usr.user_id)
1979 super_admin_ids.append(usr.user_id)
1980 # if this admin is also owner, don't double the record
1980 # if this admin is also owner, don't double the record
1981 if usr.user_id == owner_row[0].user_id:
1981 if usr.user_id == owner_row[0].user_id:
1982 owner_row[0].admin_row = True
1982 owner_row[0].admin_row = True
1983 else:
1983 else:
1984 usr = AttributeDict(usr.get_dict())
1984 usr = AttributeDict(usr.get_dict())
1985 usr.admin_row = True
1985 usr.admin_row = True
1986 usr.permission = _admin_perm
1986 usr.permission = _admin_perm
1987 usr.permission_id = None
1987 usr.permission_id = None
1988 super_admin_rows.append(usr)
1988 super_admin_rows.append(usr)
1989
1989
1990 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1990 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1991 q = q.options(joinedload(UserRepoToPerm.repository),
1991 q = q.options(joinedload(UserRepoToPerm.repository),
1992 joinedload(UserRepoToPerm.user),
1992 joinedload(UserRepoToPerm.user),
1993 joinedload(UserRepoToPerm.permission),)
1993 joinedload(UserRepoToPerm.permission),)
1994
1994
1995 # get owners and admins and permissions. We do a trick of re-writing
1995 # get owners and admins and permissions. We do a trick of re-writing
1996 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1996 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1997 # has a global reference and changing one object propagates to all
1997 # has a global reference and changing one object propagates to all
1998 # others. This means if admin is also an owner admin_row that change
1998 # others. This means if admin is also an owner admin_row that change
1999 # would propagate to both objects
1999 # would propagate to both objects
2000 perm_rows = []
2000 perm_rows = []
2001 for _usr in q.all():
2001 for _usr in q.all():
2002 usr = AttributeDict(_usr.user.get_dict())
2002 usr = AttributeDict(_usr.user.get_dict())
2003 # if this user is also owner/admin, mark as duplicate record
2003 # if this user is also owner/admin, mark as duplicate record
2004 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2004 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2005 usr.duplicate_perm = True
2005 usr.duplicate_perm = True
2006 # also check if this permission is maybe used by branch_permissions
2006 # also check if this permission is maybe used by branch_permissions
2007 if _usr.branch_perm_entry:
2007 if _usr.branch_perm_entry:
2008 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2008 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2009
2009
2010 usr.permission = _usr.permission.permission_name
2010 usr.permission = _usr.permission.permission_name
2011 usr.permission_id = _usr.repo_to_perm_id
2011 usr.permission_id = _usr.repo_to_perm_id
2012 perm_rows.append(usr)
2012 perm_rows.append(usr)
2013
2013
2014 # filter the perm rows by 'default' first and then sort them by
2014 # filter the perm rows by 'default' first and then sort them by
2015 # admin,write,read,none permissions sorted again alphabetically in
2015 # admin,write,read,none permissions sorted again alphabetically in
2016 # each group
2016 # each group
2017 perm_rows = sorted(perm_rows, key=display_user_sort)
2017 perm_rows = sorted(perm_rows, key=display_user_sort)
2018
2018
2019 user_groups_rows = []
2019 user_groups_rows = []
2020 if expand_from_user_groups:
2020 if expand_from_user_groups:
2021 for ug in self.permission_user_groups(with_members=True):
2021 for ug in self.permission_user_groups(with_members=True):
2022 for user_data in ug.members:
2022 for user_data in ug.members:
2023 user_groups_rows.append(user_data)
2023 user_groups_rows.append(user_data)
2024
2024
2025 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2025 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2026
2026
2027 def permission_user_groups(self, with_members=True):
2027 def permission_user_groups(self, with_members=True):
2028 q = UserGroupRepoToPerm.query()\
2028 q = UserGroupRepoToPerm.query()\
2029 .filter(UserGroupRepoToPerm.repository == self)
2029 .filter(UserGroupRepoToPerm.repository == self)
2030 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2030 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2031 joinedload(UserGroupRepoToPerm.users_group),
2031 joinedload(UserGroupRepoToPerm.users_group),
2032 joinedload(UserGroupRepoToPerm.permission),)
2032 joinedload(UserGroupRepoToPerm.permission),)
2033
2033
2034 perm_rows = []
2034 perm_rows = []
2035 for _user_group in q.all():
2035 for _user_group in q.all():
2036 entry = AttributeDict(_user_group.users_group.get_dict())
2036 entry = AttributeDict(_user_group.users_group.get_dict())
2037 entry.permission = _user_group.permission.permission_name
2037 entry.permission = _user_group.permission.permission_name
2038 if with_members:
2038 if with_members:
2039 entry.members = [x.user.get_dict()
2039 entry.members = [x.user.get_dict()
2040 for x in _user_group.users_group.members]
2040 for x in _user_group.users_group.members]
2041 perm_rows.append(entry)
2041 perm_rows.append(entry)
2042
2042
2043 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2043 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2044 return perm_rows
2044 return perm_rows
2045
2045
2046 def get_api_data(self, include_secrets=False):
2046 def get_api_data(self, include_secrets=False):
2047 """
2047 """
2048 Common function for generating repo api data
2048 Common function for generating repo api data
2049
2049
2050 :param include_secrets: See :meth:`User.get_api_data`.
2050 :param include_secrets: See :meth:`User.get_api_data`.
2051
2051
2052 """
2052 """
2053 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2053 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2054 # move this methods on models level.
2054 # move this methods on models level.
2055 from rhodecode.model.settings import SettingsModel
2055 from rhodecode.model.settings import SettingsModel
2056 from rhodecode.model.repo import RepoModel
2056 from rhodecode.model.repo import RepoModel
2057
2057
2058 repo = self
2058 repo = self
2059 _user_id, _time, _reason = self.locked
2059 _user_id, _time, _reason = self.locked
2060
2060
2061 data = {
2061 data = {
2062 'repo_id': repo.repo_id,
2062 'repo_id': repo.repo_id,
2063 'repo_name': repo.repo_name,
2063 'repo_name': repo.repo_name,
2064 'repo_type': repo.repo_type,
2064 'repo_type': repo.repo_type,
2065 'clone_uri': repo.clone_uri or '',
2065 'clone_uri': repo.clone_uri or '',
2066 'push_uri': repo.push_uri or '',
2066 'push_uri': repo.push_uri or '',
2067 'url': RepoModel().get_url(self),
2067 'url': RepoModel().get_url(self),
2068 'private': repo.private,
2068 'private': repo.private,
2069 'created_on': repo.created_on,
2069 'created_on': repo.created_on,
2070 'description': repo.description_safe,
2070 'description': repo.description_safe,
2071 'landing_rev': repo.landing_rev,
2071 'landing_rev': repo.landing_rev,
2072 'owner': repo.user.username,
2072 'owner': repo.user.username,
2073 'fork_of': repo.fork.repo_name if repo.fork else None,
2073 'fork_of': repo.fork.repo_name if repo.fork else None,
2074 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2074 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2075 'enable_statistics': repo.enable_statistics,
2075 'enable_statistics': repo.enable_statistics,
2076 'enable_locking': repo.enable_locking,
2076 'enable_locking': repo.enable_locking,
2077 'enable_downloads': repo.enable_downloads,
2077 'enable_downloads': repo.enable_downloads,
2078 'last_changeset': repo.changeset_cache,
2078 'last_changeset': repo.changeset_cache,
2079 'locked_by': User.get(_user_id).get_api_data(
2079 'locked_by': User.get(_user_id).get_api_data(
2080 include_secrets=include_secrets) if _user_id else None,
2080 include_secrets=include_secrets) if _user_id else None,
2081 'locked_date': time_to_datetime(_time) if _time else None,
2081 'locked_date': time_to_datetime(_time) if _time else None,
2082 'lock_reason': _reason if _reason else None,
2082 'lock_reason': _reason if _reason else None,
2083 }
2083 }
2084
2084
2085 # TODO: mikhail: should be per-repo settings here
2085 # TODO: mikhail: should be per-repo settings here
2086 rc_config = SettingsModel().get_all_settings()
2086 rc_config = SettingsModel().get_all_settings()
2087 repository_fields = str2bool(
2087 repository_fields = str2bool(
2088 rc_config.get('rhodecode_repository_fields'))
2088 rc_config.get('rhodecode_repository_fields'))
2089 if repository_fields:
2089 if repository_fields:
2090 for f in self.extra_fields:
2090 for f in self.extra_fields:
2091 data[f.field_key_prefixed] = f.field_value
2091 data[f.field_key_prefixed] = f.field_value
2092
2092
2093 return data
2093 return data
2094
2094
2095 @classmethod
2095 @classmethod
2096 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2096 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2097 if not lock_time:
2097 if not lock_time:
2098 lock_time = time.time()
2098 lock_time = time.time()
2099 if not lock_reason:
2099 if not lock_reason:
2100 lock_reason = cls.LOCK_AUTOMATIC
2100 lock_reason = cls.LOCK_AUTOMATIC
2101 repo.locked = [user_id, lock_time, lock_reason]
2101 repo.locked = [user_id, lock_time, lock_reason]
2102 Session().add(repo)
2102 Session().add(repo)
2103 Session().commit()
2103 Session().commit()
2104
2104
2105 @classmethod
2105 @classmethod
2106 def unlock(cls, repo):
2106 def unlock(cls, repo):
2107 repo.locked = None
2107 repo.locked = None
2108 Session().add(repo)
2108 Session().add(repo)
2109 Session().commit()
2109 Session().commit()
2110
2110
2111 @classmethod
2111 @classmethod
2112 def getlock(cls, repo):
2112 def getlock(cls, repo):
2113 return repo.locked
2113 return repo.locked
2114
2114
2115 def is_user_lock(self, user_id):
2115 def is_user_lock(self, user_id):
2116 if self.lock[0]:
2116 if self.lock[0]:
2117 lock_user_id = safe_int(self.lock[0])
2117 lock_user_id = safe_int(self.lock[0])
2118 user_id = safe_int(user_id)
2118 user_id = safe_int(user_id)
2119 # both are ints, and they are equal
2119 # both are ints, and they are equal
2120 return all([lock_user_id, user_id]) and lock_user_id == user_id
2120 return all([lock_user_id, user_id]) and lock_user_id == user_id
2121
2121
2122 return False
2122 return False
2123
2123
2124 def get_locking_state(self, action, user_id, only_when_enabled=True):
2124 def get_locking_state(self, action, user_id, only_when_enabled=True):
2125 """
2125 """
2126 Checks locking on this repository, if locking is enabled and lock is
2126 Checks locking on this repository, if locking is enabled and lock is
2127 present returns a tuple of make_lock, locked, locked_by.
2127 present returns a tuple of make_lock, locked, locked_by.
2128 make_lock can have 3 states None (do nothing) True, make lock
2128 make_lock can have 3 states None (do nothing) True, make lock
2129 False release lock, This value is later propagated to hooks, which
2129 False release lock, This value is later propagated to hooks, which
2130 do the locking. Think about this as signals passed to hooks what to do.
2130 do the locking. Think about this as signals passed to hooks what to do.
2131
2131
2132 """
2132 """
2133 # TODO: johbo: This is part of the business logic and should be moved
2133 # TODO: johbo: This is part of the business logic and should be moved
2134 # into the RepositoryModel.
2134 # into the RepositoryModel.
2135
2135
2136 if action not in ('push', 'pull'):
2136 if action not in ('push', 'pull'):
2137 raise ValueError("Invalid action value: %s" % repr(action))
2137 raise ValueError("Invalid action value: %s" % repr(action))
2138
2138
2139 # defines if locked error should be thrown to user
2139 # defines if locked error should be thrown to user
2140 currently_locked = False
2140 currently_locked = False
2141 # defines if new lock should be made, tri-state
2141 # defines if new lock should be made, tri-state
2142 make_lock = None
2142 make_lock = None
2143 repo = self
2143 repo = self
2144 user = User.get(user_id)
2144 user = User.get(user_id)
2145
2145
2146 lock_info = repo.locked
2146 lock_info = repo.locked
2147
2147
2148 if repo and (repo.enable_locking or not only_when_enabled):
2148 if repo and (repo.enable_locking or not only_when_enabled):
2149 if action == 'push':
2149 if action == 'push':
2150 # check if it's already locked !, if it is compare users
2150 # check if it's already locked !, if it is compare users
2151 locked_by_user_id = lock_info[0]
2151 locked_by_user_id = lock_info[0]
2152 if user.user_id == locked_by_user_id:
2152 if user.user_id == locked_by_user_id:
2153 log.debug(
2153 log.debug(
2154 'Got `push` action from user %s, now unlocking', user)
2154 'Got `push` action from user %s, now unlocking', user)
2155 # unlock if we have push from user who locked
2155 # unlock if we have push from user who locked
2156 make_lock = False
2156 make_lock = False
2157 else:
2157 else:
2158 # we're not the same user who locked, ban with
2158 # we're not the same user who locked, ban with
2159 # code defined in settings (default is 423 HTTP Locked) !
2159 # code defined in settings (default is 423 HTTP Locked) !
2160 log.debug('Repo %s is currently locked by %s', repo, user)
2160 log.debug('Repo %s is currently locked by %s', repo, user)
2161 currently_locked = True
2161 currently_locked = True
2162 elif action == 'pull':
2162 elif action == 'pull':
2163 # [0] user [1] date
2163 # [0] user [1] date
2164 if lock_info[0] and lock_info[1]:
2164 if lock_info[0] and lock_info[1]:
2165 log.debug('Repo %s is currently locked by %s', repo, user)
2165 log.debug('Repo %s is currently locked by %s', repo, user)
2166 currently_locked = True
2166 currently_locked = True
2167 else:
2167 else:
2168 log.debug('Setting lock on repo %s by %s', repo, user)
2168 log.debug('Setting lock on repo %s by %s', repo, user)
2169 make_lock = True
2169 make_lock = True
2170
2170
2171 else:
2171 else:
2172 log.debug('Repository %s do not have locking enabled', repo)
2172 log.debug('Repository %s do not have locking enabled', repo)
2173
2173
2174 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2174 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2175 make_lock, currently_locked, lock_info)
2175 make_lock, currently_locked, lock_info)
2176
2176
2177 from rhodecode.lib.auth import HasRepoPermissionAny
2177 from rhodecode.lib.auth import HasRepoPermissionAny
2178 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2178 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2179 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2179 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2180 # if we don't have at least write permission we cannot make a lock
2180 # if we don't have at least write permission we cannot make a lock
2181 log.debug('lock state reset back to FALSE due to lack '
2181 log.debug('lock state reset back to FALSE due to lack '
2182 'of at least read permission')
2182 'of at least read permission')
2183 make_lock = False
2183 make_lock = False
2184
2184
2185 return make_lock, currently_locked, lock_info
2185 return make_lock, currently_locked, lock_info
2186
2186
2187 @property
2187 @property
2188 def last_commit_cache_update_diff(self):
2188 def last_commit_cache_update_diff(self):
2189 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2189 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2190
2190
2191 @property
2191 @property
2192 def last_commit_change(self):
2192 def last_commit_change(self):
2193 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2193 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2194 empty_date = datetime.datetime.fromtimestamp(0)
2194 empty_date = datetime.datetime.fromtimestamp(0)
2195 date_latest = self.changeset_cache.get('date', empty_date)
2195 date_latest = self.changeset_cache.get('date', empty_date)
2196 try:
2196 try:
2197 return parse_datetime(date_latest)
2197 return parse_datetime(date_latest)
2198 except Exception:
2198 except Exception:
2199 return empty_date
2199 return empty_date
2200
2200
2201 @property
2201 @property
2202 def last_db_change(self):
2202 def last_db_change(self):
2203 return self.updated_on
2203 return self.updated_on
2204
2204
2205 @property
2205 @property
2206 def clone_uri_hidden(self):
2206 def clone_uri_hidden(self):
2207 clone_uri = self.clone_uri
2207 clone_uri = self.clone_uri
2208 if clone_uri:
2208 if clone_uri:
2209 import urlobject
2209 import urlobject
2210 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2210 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2211 if url_obj.password:
2211 if url_obj.password:
2212 clone_uri = url_obj.with_password('*****')
2212 clone_uri = url_obj.with_password('*****')
2213 return clone_uri
2213 return clone_uri
2214
2214
2215 @property
2215 @property
2216 def push_uri_hidden(self):
2216 def push_uri_hidden(self):
2217 push_uri = self.push_uri
2217 push_uri = self.push_uri
2218 if push_uri:
2218 if push_uri:
2219 import urlobject
2219 import urlobject
2220 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2220 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2221 if url_obj.password:
2221 if url_obj.password:
2222 push_uri = url_obj.with_password('*****')
2222 push_uri = url_obj.with_password('*****')
2223 return push_uri
2223 return push_uri
2224
2224
2225 def clone_url(self, **override):
2225 def clone_url(self, **override):
2226 from rhodecode.model.settings import SettingsModel
2226 from rhodecode.model.settings import SettingsModel
2227
2227
2228 uri_tmpl = None
2228 uri_tmpl = None
2229 if 'with_id' in override:
2229 if 'with_id' in override:
2230 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2230 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2231 del override['with_id']
2231 del override['with_id']
2232
2232
2233 if 'uri_tmpl' in override:
2233 if 'uri_tmpl' in override:
2234 uri_tmpl = override['uri_tmpl']
2234 uri_tmpl = override['uri_tmpl']
2235 del override['uri_tmpl']
2235 del override['uri_tmpl']
2236
2236
2237 ssh = False
2237 ssh = False
2238 if 'ssh' in override:
2238 if 'ssh' in override:
2239 ssh = True
2239 ssh = True
2240 del override['ssh']
2240 del override['ssh']
2241
2241
2242 # we didn't override our tmpl from **overrides
2242 # we didn't override our tmpl from **overrides
2243 if not uri_tmpl:
2243 if not uri_tmpl:
2244 rc_config = SettingsModel().get_all_settings(cache=True)
2244 rc_config = SettingsModel().get_all_settings(cache=True)
2245 if ssh:
2245 if ssh:
2246 uri_tmpl = rc_config.get(
2246 uri_tmpl = rc_config.get(
2247 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2247 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2248 else:
2248 else:
2249 uri_tmpl = rc_config.get(
2249 uri_tmpl = rc_config.get(
2250 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2250 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2251
2251
2252 request = get_current_request()
2252 request = get_current_request()
2253 return get_clone_url(request=request,
2253 return get_clone_url(request=request,
2254 uri_tmpl=uri_tmpl,
2254 uri_tmpl=uri_tmpl,
2255 repo_name=self.repo_name,
2255 repo_name=self.repo_name,
2256 repo_id=self.repo_id, **override)
2256 repo_id=self.repo_id, **override)
2257
2257
2258 def set_state(self, state):
2258 def set_state(self, state):
2259 self.repo_state = state
2259 self.repo_state = state
2260 Session().add(self)
2260 Session().add(self)
2261 #==========================================================================
2261 #==========================================================================
2262 # SCM PROPERTIES
2262 # SCM PROPERTIES
2263 #==========================================================================
2263 #==========================================================================
2264
2264
2265 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2265 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2266 return get_commit_safe(
2266 return get_commit_safe(
2267 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2267 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2268
2268
2269 def get_changeset(self, rev=None, pre_load=None):
2269 def get_changeset(self, rev=None, pre_load=None):
2270 warnings.warn("Use get_commit", DeprecationWarning)
2270 warnings.warn("Use get_commit", DeprecationWarning)
2271 commit_id = None
2271 commit_id = None
2272 commit_idx = None
2272 commit_idx = None
2273 if isinstance(rev, compat.string_types):
2273 if isinstance(rev, compat.string_types):
2274 commit_id = rev
2274 commit_id = rev
2275 else:
2275 else:
2276 commit_idx = rev
2276 commit_idx = rev
2277 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2277 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2278 pre_load=pre_load)
2278 pre_load=pre_load)
2279
2279
2280 def get_landing_commit(self):
2280 def get_landing_commit(self):
2281 """
2281 """
2282 Returns landing commit, or if that doesn't exist returns the tip
2282 Returns landing commit, or if that doesn't exist returns the tip
2283 """
2283 """
2284 _rev_type, _rev = self.landing_rev
2284 _rev_type, _rev = self.landing_rev
2285 commit = self.get_commit(_rev)
2285 commit = self.get_commit(_rev)
2286 if isinstance(commit, EmptyCommit):
2286 if isinstance(commit, EmptyCommit):
2287 return self.get_commit()
2287 return self.get_commit()
2288 return commit
2288 return commit
2289
2289
2290 def update_commit_cache(self, cs_cache=None, config=None):
2290 def update_commit_cache(self, cs_cache=None, config=None):
2291 """
2291 """
2292 Update cache of last changeset for repository, keys should be::
2292 Update cache of last changeset for repository, keys should be::
2293
2293
2294 source_repo_id
2294 source_repo_id
2295 short_id
2295 short_id
2296 raw_id
2296 raw_id
2297 revision
2297 revision
2298 parents
2298 parents
2299 message
2299 message
2300 date
2300 date
2301 author
2301 author
2302 updated_on
2302 updated_on
2303
2303
2304 """
2304 """
2305 from rhodecode.lib.vcs.backends.base import BaseChangeset
2305 from rhodecode.lib.vcs.backends.base import BaseChangeset
2306 if cs_cache is None:
2306 if cs_cache is None:
2307 # use no-cache version here
2307 # use no-cache version here
2308 scm_repo = self.scm_instance(cache=False, config=config)
2308 scm_repo = self.scm_instance(cache=False, config=config)
2309
2309
2310 empty = not scm_repo or scm_repo.is_empty()
2310 empty = scm_repo is None or scm_repo.is_empty()
2311 if not empty:
2311 if not empty:
2312 cs_cache = scm_repo.get_commit(
2312 cs_cache = scm_repo.get_commit(
2313 pre_load=["author", "date", "message", "parents"])
2313 pre_load=["author", "date", "message", "parents"])
2314 else:
2314 else:
2315 cs_cache = EmptyCommit()
2315 cs_cache = EmptyCommit()
2316
2316
2317 if isinstance(cs_cache, BaseChangeset):
2317 if isinstance(cs_cache, BaseChangeset):
2318 cs_cache = cs_cache.__json__()
2318 cs_cache = cs_cache.__json__()
2319
2319
2320 def is_outdated(new_cs_cache):
2320 def is_outdated(new_cs_cache):
2321 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2321 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2322 new_cs_cache['revision'] != self.changeset_cache['revision']):
2322 new_cs_cache['revision'] != self.changeset_cache['revision']):
2323 return True
2323 return True
2324 return False
2324 return False
2325
2325
2326 # check if we have maybe already latest cached revision
2326 # check if we have maybe already latest cached revision
2327 if is_outdated(cs_cache) or not self.changeset_cache:
2327 if is_outdated(cs_cache) or not self.changeset_cache:
2328 _default = datetime.datetime.utcnow()
2328 _default = datetime.datetime.utcnow()
2329 last_change = cs_cache.get('date') or _default
2329 last_change = cs_cache.get('date') or _default
2330 # we check if last update is newer than the new value
2330 # we check if last update is newer than the new value
2331 # if yes, we use the current timestamp instead. Imagine you get
2331 # if yes, we use the current timestamp instead. Imagine you get
2332 # old commit pushed 1y ago, we'd set last update 1y to ago.
2332 # old commit pushed 1y ago, we'd set last update 1y to ago.
2333 last_change_timestamp = datetime_to_time(last_change)
2333 last_change_timestamp = datetime_to_time(last_change)
2334 current_timestamp = datetime_to_time(last_change)
2334 current_timestamp = datetime_to_time(last_change)
2335 if last_change_timestamp > current_timestamp:
2335 if last_change_timestamp > current_timestamp:
2336 cs_cache['date'] = _default
2336 cs_cache['date'] = _default
2337
2337
2338 cs_cache['updated_on'] = time.time()
2338 cs_cache['updated_on'] = time.time()
2339 self.changeset_cache = cs_cache
2339 self.changeset_cache = cs_cache
2340 Session().add(self)
2340 Session().add(self)
2341 Session().commit()
2341 Session().commit()
2342
2342
2343 log.debug('updated repo %s with new commit cache %s',
2343 log.debug('updated repo %s with new commit cache %s',
2344 self.repo_name, cs_cache)
2344 self.repo_name, cs_cache)
2345 else:
2345 else:
2346 log.debug('Skipping update_commit_cache for repo:`%s` '
2346 log.debug('Skipping update_commit_cache for repo:`%s` '
2347 'commit already with latest changes', self.repo_name)
2347 'commit already with latest changes', self.repo_name)
2348
2348
2349 @property
2349 @property
2350 def tip(self):
2350 def tip(self):
2351 return self.get_commit('tip')
2351 return self.get_commit('tip')
2352
2352
2353 @property
2353 @property
2354 def author(self):
2354 def author(self):
2355 return self.tip.author
2355 return self.tip.author
2356
2356
2357 @property
2357 @property
2358 def last_change(self):
2358 def last_change(self):
2359 return self.scm_instance().last_change
2359 return self.scm_instance().last_change
2360
2360
2361 def get_comments(self, revisions=None):
2361 def get_comments(self, revisions=None):
2362 """
2362 """
2363 Returns comments for this repository grouped by revisions
2363 Returns comments for this repository grouped by revisions
2364
2364
2365 :param revisions: filter query by revisions only
2365 :param revisions: filter query by revisions only
2366 """
2366 """
2367 cmts = ChangesetComment.query()\
2367 cmts = ChangesetComment.query()\
2368 .filter(ChangesetComment.repo == self)
2368 .filter(ChangesetComment.repo == self)
2369 if revisions:
2369 if revisions:
2370 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2370 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2371 grouped = collections.defaultdict(list)
2371 grouped = collections.defaultdict(list)
2372 for cmt in cmts.all():
2372 for cmt in cmts.all():
2373 grouped[cmt.revision].append(cmt)
2373 grouped[cmt.revision].append(cmt)
2374 return grouped
2374 return grouped
2375
2375
2376 def statuses(self, revisions=None):
2376 def statuses(self, revisions=None):
2377 """
2377 """
2378 Returns statuses for this repository
2378 Returns statuses for this repository
2379
2379
2380 :param revisions: list of revisions to get statuses for
2380 :param revisions: list of revisions to get statuses for
2381 """
2381 """
2382 statuses = ChangesetStatus.query()\
2382 statuses = ChangesetStatus.query()\
2383 .filter(ChangesetStatus.repo == self)\
2383 .filter(ChangesetStatus.repo == self)\
2384 .filter(ChangesetStatus.version == 0)
2384 .filter(ChangesetStatus.version == 0)
2385
2385
2386 if revisions:
2386 if revisions:
2387 # Try doing the filtering in chunks to avoid hitting limits
2387 # Try doing the filtering in chunks to avoid hitting limits
2388 size = 500
2388 size = 500
2389 status_results = []
2389 status_results = []
2390 for chunk in xrange(0, len(revisions), size):
2390 for chunk in xrange(0, len(revisions), size):
2391 status_results += statuses.filter(
2391 status_results += statuses.filter(
2392 ChangesetStatus.revision.in_(
2392 ChangesetStatus.revision.in_(
2393 revisions[chunk: chunk+size])
2393 revisions[chunk: chunk+size])
2394 ).all()
2394 ).all()
2395 else:
2395 else:
2396 status_results = statuses.all()
2396 status_results = statuses.all()
2397
2397
2398 grouped = {}
2398 grouped = {}
2399
2399
2400 # maybe we have open new pullrequest without a status?
2400 # maybe we have open new pullrequest without a status?
2401 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2401 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2402 status_lbl = ChangesetStatus.get_status_lbl(stat)
2402 status_lbl = ChangesetStatus.get_status_lbl(stat)
2403 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2403 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2404 for rev in pr.revisions:
2404 for rev in pr.revisions:
2405 pr_id = pr.pull_request_id
2405 pr_id = pr.pull_request_id
2406 pr_repo = pr.target_repo.repo_name
2406 pr_repo = pr.target_repo.repo_name
2407 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2407 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2408
2408
2409 for stat in status_results:
2409 for stat in status_results:
2410 pr_id = pr_repo = None
2410 pr_id = pr_repo = None
2411 if stat.pull_request:
2411 if stat.pull_request:
2412 pr_id = stat.pull_request.pull_request_id
2412 pr_id = stat.pull_request.pull_request_id
2413 pr_repo = stat.pull_request.target_repo.repo_name
2413 pr_repo = stat.pull_request.target_repo.repo_name
2414 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2414 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2415 pr_id, pr_repo]
2415 pr_id, pr_repo]
2416 return grouped
2416 return grouped
2417
2417
2418 # ==========================================================================
2418 # ==========================================================================
2419 # SCM CACHE INSTANCE
2419 # SCM CACHE INSTANCE
2420 # ==========================================================================
2420 # ==========================================================================
2421
2421
2422 def scm_instance(self, **kwargs):
2422 def scm_instance(self, **kwargs):
2423 import rhodecode
2423 import rhodecode
2424
2424
2425 # Passing a config will not hit the cache currently only used
2425 # Passing a config will not hit the cache currently only used
2426 # for repo2dbmapper
2426 # for repo2dbmapper
2427 config = kwargs.pop('config', None)
2427 config = kwargs.pop('config', None)
2428 cache = kwargs.pop('cache', None)
2428 cache = kwargs.pop('cache', None)
2429 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2429 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2430 # if cache is NOT defined use default global, else we have a full
2430 # if cache is NOT defined use default global, else we have a full
2431 # control over cache behaviour
2431 # control over cache behaviour
2432 if cache is None and full_cache and not config:
2432 if cache is None and full_cache and not config:
2433 return self._get_instance_cached()
2433 return self._get_instance_cached()
2434 return self._get_instance(cache=bool(cache), config=config)
2434 return self._get_instance(cache=bool(cache), config=config)
2435
2435
2436 def _get_instance_cached(self):
2436 def _get_instance_cached(self):
2437 from rhodecode.lib import rc_cache
2437 from rhodecode.lib import rc_cache
2438
2438
2439 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2439 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2440 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2440 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2441 repo_id=self.repo_id)
2441 repo_id=self.repo_id)
2442 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2442 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2443
2443
2444 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2444 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2445 def get_instance_cached(repo_id, context_id):
2445 def get_instance_cached(repo_id, context_id):
2446 return self._get_instance()
2446 return self._get_instance()
2447
2447
2448 # we must use thread scoped cache here,
2448 # we must use thread scoped cache here,
2449 # because each thread of gevent needs it's own not shared connection and cache
2449 # because each thread of gevent needs it's own not shared connection and cache
2450 # we also alter `args` so the cache key is individual for every green thread.
2450 # we also alter `args` so the cache key is individual for every green thread.
2451 inv_context_manager = rc_cache.InvalidationContext(
2451 inv_context_manager = rc_cache.InvalidationContext(
2452 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2452 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2453 thread_scoped=True)
2453 thread_scoped=True)
2454 with inv_context_manager as invalidation_context:
2454 with inv_context_manager as invalidation_context:
2455 args = (self.repo_id, inv_context_manager.cache_key)
2455 args = (self.repo_id, inv_context_manager.cache_key)
2456 # re-compute and store cache if we get invalidate signal
2456 # re-compute and store cache if we get invalidate signal
2457 if invalidation_context.should_invalidate():
2457 if invalidation_context.should_invalidate():
2458 instance = get_instance_cached.refresh(*args)
2458 instance = get_instance_cached.refresh(*args)
2459 else:
2459 else:
2460 instance = get_instance_cached(*args)
2460 instance = get_instance_cached(*args)
2461
2461
2462 log.debug(
2462 log.debug(
2463 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2463 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2464 return instance
2464 return instance
2465
2465
2466 def _get_instance(self, cache=True, config=None):
2466 def _get_instance(self, cache=True, config=None):
2467 config = config or self._config
2467 config = config or self._config
2468 custom_wire = {
2468 custom_wire = {
2469 'cache': cache # controls the vcs.remote cache
2469 'cache': cache # controls the vcs.remote cache
2470 }
2470 }
2471 repo = get_vcs_instance(
2471 repo = get_vcs_instance(
2472 repo_path=safe_str(self.repo_full_path),
2472 repo_path=safe_str(self.repo_full_path),
2473 config=config,
2473 config=config,
2474 with_wire=custom_wire,
2474 with_wire=custom_wire,
2475 create=False,
2475 create=False,
2476 _vcs_alias=self.repo_type)
2476 _vcs_alias=self.repo_type)
2477
2477
2478 return repo
2478 return repo
2479
2479
2480 def __json__(self):
2480 def __json__(self):
2481 return {'landing_rev': self.landing_rev}
2481 return {'landing_rev': self.landing_rev}
2482
2482
2483 def get_dict(self):
2483 def get_dict(self):
2484
2484
2485 # Since we transformed `repo_name` to a hybrid property, we need to
2485 # Since we transformed `repo_name` to a hybrid property, we need to
2486 # keep compatibility with the code which uses `repo_name` field.
2486 # keep compatibility with the code which uses `repo_name` field.
2487
2487
2488 result = super(Repository, self).get_dict()
2488 result = super(Repository, self).get_dict()
2489 result['repo_name'] = result.pop('_repo_name', None)
2489 result['repo_name'] = result.pop('_repo_name', None)
2490 return result
2490 return result
2491
2491
2492
2492
2493 class RepoGroup(Base, BaseModel):
2493 class RepoGroup(Base, BaseModel):
2494 __tablename__ = 'groups'
2494 __tablename__ = 'groups'
2495 __table_args__ = (
2495 __table_args__ = (
2496 UniqueConstraint('group_name', 'group_parent_id'),
2496 UniqueConstraint('group_name', 'group_parent_id'),
2497 base_table_args,
2497 base_table_args,
2498 )
2498 )
2499 __mapper_args__ = {'order_by': 'group_name'}
2499 __mapper_args__ = {'order_by': 'group_name'}
2500
2500
2501 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2501 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2502
2502
2503 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2503 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2504 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2504 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2505 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2505 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2506 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2506 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2507 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2507 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2508 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2508 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2510 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2510 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2511 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2511 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2512 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2512 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2513 _changeset_cache = Column(
2513 _changeset_cache = Column(
2514 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2514 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2515
2515
2516 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2516 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2517 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2517 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2518 parent_group = relationship('RepoGroup', remote_side=group_id)
2518 parent_group = relationship('RepoGroup', remote_side=group_id)
2519 user = relationship('User')
2519 user = relationship('User')
2520 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2520 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2521
2521
2522 def __init__(self, group_name='', parent_group=None):
2522 def __init__(self, group_name='', parent_group=None):
2523 self.group_name = group_name
2523 self.group_name = group_name
2524 self.parent_group = parent_group
2524 self.parent_group = parent_group
2525
2525
2526 def __unicode__(self):
2526 def __unicode__(self):
2527 return u"<%s('id:%s:%s')>" % (
2527 return u"<%s('id:%s:%s')>" % (
2528 self.__class__.__name__, self.group_id, self.group_name)
2528 self.__class__.__name__, self.group_id, self.group_name)
2529
2529
2530 @hybrid_property
2530 @hybrid_property
2531 def group_name(self):
2531 def group_name(self):
2532 return self._group_name
2532 return self._group_name
2533
2533
2534 @group_name.setter
2534 @group_name.setter
2535 def group_name(self, value):
2535 def group_name(self, value):
2536 self._group_name = value
2536 self._group_name = value
2537 self.group_name_hash = self.hash_repo_group_name(value)
2537 self.group_name_hash = self.hash_repo_group_name(value)
2538
2538
2539 @hybrid_property
2539 @hybrid_property
2540 def changeset_cache(self):
2540 def changeset_cache(self):
2541 from rhodecode.lib.vcs.backends.base import EmptyCommit
2541 from rhodecode.lib.vcs.backends.base import EmptyCommit
2542 dummy = EmptyCommit().__json__()
2542 dummy = EmptyCommit().__json__()
2543 if not self._changeset_cache:
2543 if not self._changeset_cache:
2544 dummy['source_repo_id'] = ''
2544 dummy['source_repo_id'] = ''
2545 return json.loads(json.dumps(dummy))
2545 return json.loads(json.dumps(dummy))
2546
2546
2547 try:
2547 try:
2548 return json.loads(self._changeset_cache)
2548 return json.loads(self._changeset_cache)
2549 except TypeError:
2549 except TypeError:
2550 return dummy
2550 return dummy
2551 except Exception:
2551 except Exception:
2552 log.error(traceback.format_exc())
2552 log.error(traceback.format_exc())
2553 return dummy
2553 return dummy
2554
2554
2555 @changeset_cache.setter
2555 @changeset_cache.setter
2556 def changeset_cache(self, val):
2556 def changeset_cache(self, val):
2557 try:
2557 try:
2558 self._changeset_cache = json.dumps(val)
2558 self._changeset_cache = json.dumps(val)
2559 except Exception:
2559 except Exception:
2560 log.error(traceback.format_exc())
2560 log.error(traceback.format_exc())
2561
2561
2562 @validates('group_parent_id')
2562 @validates('group_parent_id')
2563 def validate_group_parent_id(self, key, val):
2563 def validate_group_parent_id(self, key, val):
2564 """
2564 """
2565 Check cycle references for a parent group to self
2565 Check cycle references for a parent group to self
2566 """
2566 """
2567 if self.group_id and val:
2567 if self.group_id and val:
2568 assert val != self.group_id
2568 assert val != self.group_id
2569
2569
2570 return val
2570 return val
2571
2571
2572 @hybrid_property
2572 @hybrid_property
2573 def description_safe(self):
2573 def description_safe(self):
2574 from rhodecode.lib import helpers as h
2574 from rhodecode.lib import helpers as h
2575 return h.escape(self.group_description)
2575 return h.escape(self.group_description)
2576
2576
2577 @classmethod
2577 @classmethod
2578 def hash_repo_group_name(cls, repo_group_name):
2578 def hash_repo_group_name(cls, repo_group_name):
2579 val = remove_formatting(repo_group_name)
2579 val = remove_formatting(repo_group_name)
2580 val = safe_str(val).lower()
2580 val = safe_str(val).lower()
2581 chars = []
2581 chars = []
2582 for c in val:
2582 for c in val:
2583 if c not in string.ascii_letters:
2583 if c not in string.ascii_letters:
2584 c = str(ord(c))
2584 c = str(ord(c))
2585 chars.append(c)
2585 chars.append(c)
2586
2586
2587 return ''.join(chars)
2587 return ''.join(chars)
2588
2588
2589 @classmethod
2589 @classmethod
2590 def _generate_choice(cls, repo_group):
2590 def _generate_choice(cls, repo_group):
2591 from webhelpers.html import literal as _literal
2591 from webhelpers.html import literal as _literal
2592 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2592 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2593 return repo_group.group_id, _name(repo_group.full_path_splitted)
2593 return repo_group.group_id, _name(repo_group.full_path_splitted)
2594
2594
2595 @classmethod
2595 @classmethod
2596 def groups_choices(cls, groups=None, show_empty_group=True):
2596 def groups_choices(cls, groups=None, show_empty_group=True):
2597 if not groups:
2597 if not groups:
2598 groups = cls.query().all()
2598 groups = cls.query().all()
2599
2599
2600 repo_groups = []
2600 repo_groups = []
2601 if show_empty_group:
2601 if show_empty_group:
2602 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2602 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2603
2603
2604 repo_groups.extend([cls._generate_choice(x) for x in groups])
2604 repo_groups.extend([cls._generate_choice(x) for x in groups])
2605
2605
2606 repo_groups = sorted(
2606 repo_groups = sorted(
2607 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2607 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2608 return repo_groups
2608 return repo_groups
2609
2609
2610 @classmethod
2610 @classmethod
2611 def url_sep(cls):
2611 def url_sep(cls):
2612 return URL_SEP
2612 return URL_SEP
2613
2613
2614 @classmethod
2614 @classmethod
2615 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2615 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2616 if case_insensitive:
2616 if case_insensitive:
2617 gr = cls.query().filter(func.lower(cls.group_name)
2617 gr = cls.query().filter(func.lower(cls.group_name)
2618 == func.lower(group_name))
2618 == func.lower(group_name))
2619 else:
2619 else:
2620 gr = cls.query().filter(cls.group_name == group_name)
2620 gr = cls.query().filter(cls.group_name == group_name)
2621 if cache:
2621 if cache:
2622 name_key = _hash_key(group_name)
2622 name_key = _hash_key(group_name)
2623 gr = gr.options(
2623 gr = gr.options(
2624 FromCache("sql_cache_short", "get_group_%s" % name_key))
2624 FromCache("sql_cache_short", "get_group_%s" % name_key))
2625 return gr.scalar()
2625 return gr.scalar()
2626
2626
2627 @classmethod
2627 @classmethod
2628 def get_user_personal_repo_group(cls, user_id):
2628 def get_user_personal_repo_group(cls, user_id):
2629 user = User.get(user_id)
2629 user = User.get(user_id)
2630 if user.username == User.DEFAULT_USER:
2630 if user.username == User.DEFAULT_USER:
2631 return None
2631 return None
2632
2632
2633 return cls.query()\
2633 return cls.query()\
2634 .filter(cls.personal == true()) \
2634 .filter(cls.personal == true()) \
2635 .filter(cls.user == user) \
2635 .filter(cls.user == user) \
2636 .order_by(cls.group_id.asc()) \
2636 .order_by(cls.group_id.asc()) \
2637 .first()
2637 .first()
2638
2638
2639 @classmethod
2639 @classmethod
2640 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2640 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2641 case_insensitive=True):
2641 case_insensitive=True):
2642 q = RepoGroup.query()
2642 q = RepoGroup.query()
2643
2643
2644 if not isinstance(user_id, Optional):
2644 if not isinstance(user_id, Optional):
2645 q = q.filter(RepoGroup.user_id == user_id)
2645 q = q.filter(RepoGroup.user_id == user_id)
2646
2646
2647 if not isinstance(group_id, Optional):
2647 if not isinstance(group_id, Optional):
2648 q = q.filter(RepoGroup.group_parent_id == group_id)
2648 q = q.filter(RepoGroup.group_parent_id == group_id)
2649
2649
2650 if case_insensitive:
2650 if case_insensitive:
2651 q = q.order_by(func.lower(RepoGroup.group_name))
2651 q = q.order_by(func.lower(RepoGroup.group_name))
2652 else:
2652 else:
2653 q = q.order_by(RepoGroup.group_name)
2653 q = q.order_by(RepoGroup.group_name)
2654 return q.all()
2654 return q.all()
2655
2655
2656 @property
2656 @property
2657 def parents(self, parents_recursion_limit = 10):
2657 def parents(self, parents_recursion_limit = 10):
2658 groups = []
2658 groups = []
2659 if self.parent_group is None:
2659 if self.parent_group is None:
2660 return groups
2660 return groups
2661 cur_gr = self.parent_group
2661 cur_gr = self.parent_group
2662 groups.insert(0, cur_gr)
2662 groups.insert(0, cur_gr)
2663 cnt = 0
2663 cnt = 0
2664 while 1:
2664 while 1:
2665 cnt += 1
2665 cnt += 1
2666 gr = getattr(cur_gr, 'parent_group', None)
2666 gr = getattr(cur_gr, 'parent_group', None)
2667 cur_gr = cur_gr.parent_group
2667 cur_gr = cur_gr.parent_group
2668 if gr is None:
2668 if gr is None:
2669 break
2669 break
2670 if cnt == parents_recursion_limit:
2670 if cnt == parents_recursion_limit:
2671 # this will prevent accidental infinit loops
2671 # this will prevent accidental infinit loops
2672 log.error('more than %s parents found for group %s, stopping '
2672 log.error('more than %s parents found for group %s, stopping '
2673 'recursive parent fetching', parents_recursion_limit, self)
2673 'recursive parent fetching', parents_recursion_limit, self)
2674 break
2674 break
2675
2675
2676 groups.insert(0, gr)
2676 groups.insert(0, gr)
2677 return groups
2677 return groups
2678
2678
2679 @property
2679 @property
2680 def last_commit_cache_update_diff(self):
2680 def last_commit_cache_update_diff(self):
2681 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2681 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2682
2682
2683 @property
2683 @property
2684 def last_commit_change(self):
2684 def last_commit_change(self):
2685 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2685 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2686 empty_date = datetime.datetime.fromtimestamp(0)
2686 empty_date = datetime.datetime.fromtimestamp(0)
2687 date_latest = self.changeset_cache.get('date', empty_date)
2687 date_latest = self.changeset_cache.get('date', empty_date)
2688 try:
2688 try:
2689 return parse_datetime(date_latest)
2689 return parse_datetime(date_latest)
2690 except Exception:
2690 except Exception:
2691 return empty_date
2691 return empty_date
2692
2692
2693 @property
2693 @property
2694 def last_db_change(self):
2694 def last_db_change(self):
2695 return self.updated_on
2695 return self.updated_on
2696
2696
2697 @property
2697 @property
2698 def children(self):
2698 def children(self):
2699 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2699 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2700
2700
2701 @property
2701 @property
2702 def name(self):
2702 def name(self):
2703 return self.group_name.split(RepoGroup.url_sep())[-1]
2703 return self.group_name.split(RepoGroup.url_sep())[-1]
2704
2704
2705 @property
2705 @property
2706 def full_path(self):
2706 def full_path(self):
2707 return self.group_name
2707 return self.group_name
2708
2708
2709 @property
2709 @property
2710 def full_path_splitted(self):
2710 def full_path_splitted(self):
2711 return self.group_name.split(RepoGroup.url_sep())
2711 return self.group_name.split(RepoGroup.url_sep())
2712
2712
2713 @property
2713 @property
2714 def repositories(self):
2714 def repositories(self):
2715 return Repository.query()\
2715 return Repository.query()\
2716 .filter(Repository.group == self)\
2716 .filter(Repository.group == self)\
2717 .order_by(Repository.repo_name)
2717 .order_by(Repository.repo_name)
2718
2718
2719 @property
2719 @property
2720 def repositories_recursive_count(self):
2720 def repositories_recursive_count(self):
2721 cnt = self.repositories.count()
2721 cnt = self.repositories.count()
2722
2722
2723 def children_count(group):
2723 def children_count(group):
2724 cnt = 0
2724 cnt = 0
2725 for child in group.children:
2725 for child in group.children:
2726 cnt += child.repositories.count()
2726 cnt += child.repositories.count()
2727 cnt += children_count(child)
2727 cnt += children_count(child)
2728 return cnt
2728 return cnt
2729
2729
2730 return cnt + children_count(self)
2730 return cnt + children_count(self)
2731
2731
2732 def _recursive_objects(self, include_repos=True, include_groups=True):
2732 def _recursive_objects(self, include_repos=True, include_groups=True):
2733 all_ = []
2733 all_ = []
2734
2734
2735 def _get_members(root_gr):
2735 def _get_members(root_gr):
2736 if include_repos:
2736 if include_repos:
2737 for r in root_gr.repositories:
2737 for r in root_gr.repositories:
2738 all_.append(r)
2738 all_.append(r)
2739 childs = root_gr.children.all()
2739 childs = root_gr.children.all()
2740 if childs:
2740 if childs:
2741 for gr in childs:
2741 for gr in childs:
2742 if include_groups:
2742 if include_groups:
2743 all_.append(gr)
2743 all_.append(gr)
2744 _get_members(gr)
2744 _get_members(gr)
2745
2745
2746 root_group = []
2746 root_group = []
2747 if include_groups:
2747 if include_groups:
2748 root_group = [self]
2748 root_group = [self]
2749
2749
2750 _get_members(self)
2750 _get_members(self)
2751 return root_group + all_
2751 return root_group + all_
2752
2752
2753 def recursive_groups_and_repos(self):
2753 def recursive_groups_and_repos(self):
2754 """
2754 """
2755 Recursive return all groups, with repositories in those groups
2755 Recursive return all groups, with repositories in those groups
2756 """
2756 """
2757 return self._recursive_objects()
2757 return self._recursive_objects()
2758
2758
2759 def recursive_groups(self):
2759 def recursive_groups(self):
2760 """
2760 """
2761 Returns all children groups for this group including children of children
2761 Returns all children groups for this group including children of children
2762 """
2762 """
2763 return self._recursive_objects(include_repos=False)
2763 return self._recursive_objects(include_repos=False)
2764
2764
2765 def recursive_repos(self):
2765 def recursive_repos(self):
2766 """
2766 """
2767 Returns all children repositories for this group
2767 Returns all children repositories for this group
2768 """
2768 """
2769 return self._recursive_objects(include_groups=False)
2769 return self._recursive_objects(include_groups=False)
2770
2770
2771 def get_new_name(self, group_name):
2771 def get_new_name(self, group_name):
2772 """
2772 """
2773 returns new full group name based on parent and new name
2773 returns new full group name based on parent and new name
2774
2774
2775 :param group_name:
2775 :param group_name:
2776 """
2776 """
2777 path_prefix = (self.parent_group.full_path_splitted if
2777 path_prefix = (self.parent_group.full_path_splitted if
2778 self.parent_group else [])
2778 self.parent_group else [])
2779 return RepoGroup.url_sep().join(path_prefix + [group_name])
2779 return RepoGroup.url_sep().join(path_prefix + [group_name])
2780
2780
2781 def update_commit_cache(self, config=None):
2781 def update_commit_cache(self, config=None):
2782 """
2782 """
2783 Update cache of last changeset for newest repository inside this group, keys should be::
2783 Update cache of last changeset for newest repository inside this group, keys should be::
2784
2784
2785 source_repo_id
2785 source_repo_id
2786 short_id
2786 short_id
2787 raw_id
2787 raw_id
2788 revision
2788 revision
2789 parents
2789 parents
2790 message
2790 message
2791 date
2791 date
2792 author
2792 author
2793
2793
2794 """
2794 """
2795 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2795 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2796
2796
2797 def repo_groups_and_repos():
2797 def repo_groups_and_repos():
2798 all_entries = OrderedDefaultDict(list)
2798 all_entries = OrderedDefaultDict(list)
2799
2799
2800 def _get_members(root_gr, pos=0):
2800 def _get_members(root_gr, pos=0):
2801
2801
2802 for repo in root_gr.repositories:
2802 for repo in root_gr.repositories:
2803 all_entries[root_gr].append(repo)
2803 all_entries[root_gr].append(repo)
2804
2804
2805 # fill in all parent positions
2805 # fill in all parent positions
2806 for parent_group in root_gr.parents:
2806 for parent_group in root_gr.parents:
2807 all_entries[parent_group].extend(all_entries[root_gr])
2807 all_entries[parent_group].extend(all_entries[root_gr])
2808
2808
2809 children_groups = root_gr.children.all()
2809 children_groups = root_gr.children.all()
2810 if children_groups:
2810 if children_groups:
2811 for cnt, gr in enumerate(children_groups, 1):
2811 for cnt, gr in enumerate(children_groups, 1):
2812 _get_members(gr, pos=pos+cnt)
2812 _get_members(gr, pos=pos+cnt)
2813
2813
2814 _get_members(root_gr=self)
2814 _get_members(root_gr=self)
2815 return all_entries
2815 return all_entries
2816
2816
2817 empty_date = datetime.datetime.fromtimestamp(0)
2817 empty_date = datetime.datetime.fromtimestamp(0)
2818 for repo_group, repos in repo_groups_and_repos().items():
2818 for repo_group, repos in repo_groups_and_repos().items():
2819
2819
2820 latest_repo_cs_cache = {}
2820 latest_repo_cs_cache = {}
2821 for repo in repos:
2821 for repo in repos:
2822 repo_cs_cache = repo.changeset_cache
2822 repo_cs_cache = repo.changeset_cache
2823 date_latest = latest_repo_cs_cache.get('date', empty_date)
2823 date_latest = latest_repo_cs_cache.get('date', empty_date)
2824 date_current = repo_cs_cache.get('date', empty_date)
2824 date_current = repo_cs_cache.get('date', empty_date)
2825 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2825 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2826 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2826 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2827 latest_repo_cs_cache = repo_cs_cache
2827 latest_repo_cs_cache = repo_cs_cache
2828 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2828 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2829
2829
2830 latest_repo_cs_cache['updated_on'] = time.time()
2830 latest_repo_cs_cache['updated_on'] = time.time()
2831 repo_group.changeset_cache = latest_repo_cs_cache
2831 repo_group.changeset_cache = latest_repo_cs_cache
2832 Session().add(repo_group)
2832 Session().add(repo_group)
2833 Session().commit()
2833 Session().commit()
2834
2834
2835 log.debug('updated repo group %s with new commit cache %s',
2835 log.debug('updated repo group %s with new commit cache %s',
2836 repo_group.group_name, latest_repo_cs_cache)
2836 repo_group.group_name, latest_repo_cs_cache)
2837
2837
2838 def permissions(self, with_admins=True, with_owner=True,
2838 def permissions(self, with_admins=True, with_owner=True,
2839 expand_from_user_groups=False):
2839 expand_from_user_groups=False):
2840 """
2840 """
2841 Permissions for repository groups
2841 Permissions for repository groups
2842 """
2842 """
2843 _admin_perm = 'group.admin'
2843 _admin_perm = 'group.admin'
2844
2844
2845 owner_row = []
2845 owner_row = []
2846 if with_owner:
2846 if with_owner:
2847 usr = AttributeDict(self.user.get_dict())
2847 usr = AttributeDict(self.user.get_dict())
2848 usr.owner_row = True
2848 usr.owner_row = True
2849 usr.permission = _admin_perm
2849 usr.permission = _admin_perm
2850 owner_row.append(usr)
2850 owner_row.append(usr)
2851
2851
2852 super_admin_ids = []
2852 super_admin_ids = []
2853 super_admin_rows = []
2853 super_admin_rows = []
2854 if with_admins:
2854 if with_admins:
2855 for usr in User.get_all_super_admins():
2855 for usr in User.get_all_super_admins():
2856 super_admin_ids.append(usr.user_id)
2856 super_admin_ids.append(usr.user_id)
2857 # if this admin is also owner, don't double the record
2857 # if this admin is also owner, don't double the record
2858 if usr.user_id == owner_row[0].user_id:
2858 if usr.user_id == owner_row[0].user_id:
2859 owner_row[0].admin_row = True
2859 owner_row[0].admin_row = True
2860 else:
2860 else:
2861 usr = AttributeDict(usr.get_dict())
2861 usr = AttributeDict(usr.get_dict())
2862 usr.admin_row = True
2862 usr.admin_row = True
2863 usr.permission = _admin_perm
2863 usr.permission = _admin_perm
2864 super_admin_rows.append(usr)
2864 super_admin_rows.append(usr)
2865
2865
2866 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2866 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2867 q = q.options(joinedload(UserRepoGroupToPerm.group),
2867 q = q.options(joinedload(UserRepoGroupToPerm.group),
2868 joinedload(UserRepoGroupToPerm.user),
2868 joinedload(UserRepoGroupToPerm.user),
2869 joinedload(UserRepoGroupToPerm.permission),)
2869 joinedload(UserRepoGroupToPerm.permission),)
2870
2870
2871 # get owners and admins and permissions. We do a trick of re-writing
2871 # get owners and admins and permissions. We do a trick of re-writing
2872 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2872 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2873 # has a global reference and changing one object propagates to all
2873 # has a global reference and changing one object propagates to all
2874 # others. This means if admin is also an owner admin_row that change
2874 # others. This means if admin is also an owner admin_row that change
2875 # would propagate to both objects
2875 # would propagate to both objects
2876 perm_rows = []
2876 perm_rows = []
2877 for _usr in q.all():
2877 for _usr in q.all():
2878 usr = AttributeDict(_usr.user.get_dict())
2878 usr = AttributeDict(_usr.user.get_dict())
2879 # if this user is also owner/admin, mark as duplicate record
2879 # if this user is also owner/admin, mark as duplicate record
2880 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2880 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2881 usr.duplicate_perm = True
2881 usr.duplicate_perm = True
2882 usr.permission = _usr.permission.permission_name
2882 usr.permission = _usr.permission.permission_name
2883 perm_rows.append(usr)
2883 perm_rows.append(usr)
2884
2884
2885 # filter the perm rows by 'default' first and then sort them by
2885 # filter the perm rows by 'default' first and then sort them by
2886 # admin,write,read,none permissions sorted again alphabetically in
2886 # admin,write,read,none permissions sorted again alphabetically in
2887 # each group
2887 # each group
2888 perm_rows = sorted(perm_rows, key=display_user_sort)
2888 perm_rows = sorted(perm_rows, key=display_user_sort)
2889
2889
2890 user_groups_rows = []
2890 user_groups_rows = []
2891 if expand_from_user_groups:
2891 if expand_from_user_groups:
2892 for ug in self.permission_user_groups(with_members=True):
2892 for ug in self.permission_user_groups(with_members=True):
2893 for user_data in ug.members:
2893 for user_data in ug.members:
2894 user_groups_rows.append(user_data)
2894 user_groups_rows.append(user_data)
2895
2895
2896 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2896 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2897
2897
2898 def permission_user_groups(self, with_members=False):
2898 def permission_user_groups(self, with_members=False):
2899 q = UserGroupRepoGroupToPerm.query()\
2899 q = UserGroupRepoGroupToPerm.query()\
2900 .filter(UserGroupRepoGroupToPerm.group == self)
2900 .filter(UserGroupRepoGroupToPerm.group == self)
2901 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2901 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2902 joinedload(UserGroupRepoGroupToPerm.users_group),
2902 joinedload(UserGroupRepoGroupToPerm.users_group),
2903 joinedload(UserGroupRepoGroupToPerm.permission),)
2903 joinedload(UserGroupRepoGroupToPerm.permission),)
2904
2904
2905 perm_rows = []
2905 perm_rows = []
2906 for _user_group in q.all():
2906 for _user_group in q.all():
2907 entry = AttributeDict(_user_group.users_group.get_dict())
2907 entry = AttributeDict(_user_group.users_group.get_dict())
2908 entry.permission = _user_group.permission.permission_name
2908 entry.permission = _user_group.permission.permission_name
2909 if with_members:
2909 if with_members:
2910 entry.members = [x.user.get_dict()
2910 entry.members = [x.user.get_dict()
2911 for x in _user_group.users_group.members]
2911 for x in _user_group.users_group.members]
2912 perm_rows.append(entry)
2912 perm_rows.append(entry)
2913
2913
2914 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2914 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2915 return perm_rows
2915 return perm_rows
2916
2916
2917 def get_api_data(self):
2917 def get_api_data(self):
2918 """
2918 """
2919 Common function for generating api data
2919 Common function for generating api data
2920
2920
2921 """
2921 """
2922 group = self
2922 group = self
2923 data = {
2923 data = {
2924 'group_id': group.group_id,
2924 'group_id': group.group_id,
2925 'group_name': group.group_name,
2925 'group_name': group.group_name,
2926 'group_description': group.description_safe,
2926 'group_description': group.description_safe,
2927 'parent_group': group.parent_group.group_name if group.parent_group else None,
2927 'parent_group': group.parent_group.group_name if group.parent_group else None,
2928 'repositories': [x.repo_name for x in group.repositories],
2928 'repositories': [x.repo_name for x in group.repositories],
2929 'owner': group.user.username,
2929 'owner': group.user.username,
2930 }
2930 }
2931 return data
2931 return data
2932
2932
2933 def get_dict(self):
2933 def get_dict(self):
2934 # Since we transformed `group_name` to a hybrid property, we need to
2934 # Since we transformed `group_name` to a hybrid property, we need to
2935 # keep compatibility with the code which uses `group_name` field.
2935 # keep compatibility with the code which uses `group_name` field.
2936 result = super(RepoGroup, self).get_dict()
2936 result = super(RepoGroup, self).get_dict()
2937 result['group_name'] = result.pop('_group_name', None)
2937 result['group_name'] = result.pop('_group_name', None)
2938 return result
2938 return result
2939
2939
2940
2940
2941 class Permission(Base, BaseModel):
2941 class Permission(Base, BaseModel):
2942 __tablename__ = 'permissions'
2942 __tablename__ = 'permissions'
2943 __table_args__ = (
2943 __table_args__ = (
2944 Index('p_perm_name_idx', 'permission_name'),
2944 Index('p_perm_name_idx', 'permission_name'),
2945 base_table_args,
2945 base_table_args,
2946 )
2946 )
2947
2947
2948 PERMS = [
2948 PERMS = [
2949 ('hg.admin', _('RhodeCode Super Administrator')),
2949 ('hg.admin', _('RhodeCode Super Administrator')),
2950
2950
2951 ('repository.none', _('Repository no access')),
2951 ('repository.none', _('Repository no access')),
2952 ('repository.read', _('Repository read access')),
2952 ('repository.read', _('Repository read access')),
2953 ('repository.write', _('Repository write access')),
2953 ('repository.write', _('Repository write access')),
2954 ('repository.admin', _('Repository admin access')),
2954 ('repository.admin', _('Repository admin access')),
2955
2955
2956 ('group.none', _('Repository group no access')),
2956 ('group.none', _('Repository group no access')),
2957 ('group.read', _('Repository group read access')),
2957 ('group.read', _('Repository group read access')),
2958 ('group.write', _('Repository group write access')),
2958 ('group.write', _('Repository group write access')),
2959 ('group.admin', _('Repository group admin access')),
2959 ('group.admin', _('Repository group admin access')),
2960
2960
2961 ('usergroup.none', _('User group no access')),
2961 ('usergroup.none', _('User group no access')),
2962 ('usergroup.read', _('User group read access')),
2962 ('usergroup.read', _('User group read access')),
2963 ('usergroup.write', _('User group write access')),
2963 ('usergroup.write', _('User group write access')),
2964 ('usergroup.admin', _('User group admin access')),
2964 ('usergroup.admin', _('User group admin access')),
2965
2965
2966 ('branch.none', _('Branch no permissions')),
2966 ('branch.none', _('Branch no permissions')),
2967 ('branch.merge', _('Branch access by web merge')),
2967 ('branch.merge', _('Branch access by web merge')),
2968 ('branch.push', _('Branch access by push')),
2968 ('branch.push', _('Branch access by push')),
2969 ('branch.push_force', _('Branch access by push with force')),
2969 ('branch.push_force', _('Branch access by push with force')),
2970
2970
2971 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2971 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2972 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2972 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2973
2973
2974 ('hg.usergroup.create.false', _('User Group creation disabled')),
2974 ('hg.usergroup.create.false', _('User Group creation disabled')),
2975 ('hg.usergroup.create.true', _('User Group creation enabled')),
2975 ('hg.usergroup.create.true', _('User Group creation enabled')),
2976
2976
2977 ('hg.create.none', _('Repository creation disabled')),
2977 ('hg.create.none', _('Repository creation disabled')),
2978 ('hg.create.repository', _('Repository creation enabled')),
2978 ('hg.create.repository', _('Repository creation enabled')),
2979 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2979 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2980 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2980 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2981
2981
2982 ('hg.fork.none', _('Repository forking disabled')),
2982 ('hg.fork.none', _('Repository forking disabled')),
2983 ('hg.fork.repository', _('Repository forking enabled')),
2983 ('hg.fork.repository', _('Repository forking enabled')),
2984
2984
2985 ('hg.register.none', _('Registration disabled')),
2985 ('hg.register.none', _('Registration disabled')),
2986 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2986 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2987 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2987 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2988
2988
2989 ('hg.password_reset.enabled', _('Password reset enabled')),
2989 ('hg.password_reset.enabled', _('Password reset enabled')),
2990 ('hg.password_reset.hidden', _('Password reset hidden')),
2990 ('hg.password_reset.hidden', _('Password reset hidden')),
2991 ('hg.password_reset.disabled', _('Password reset disabled')),
2991 ('hg.password_reset.disabled', _('Password reset disabled')),
2992
2992
2993 ('hg.extern_activate.manual', _('Manual activation of external account')),
2993 ('hg.extern_activate.manual', _('Manual activation of external account')),
2994 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2994 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2995
2995
2996 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2996 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2997 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2997 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2998 ]
2998 ]
2999
2999
3000 # definition of system default permissions for DEFAULT user, created on
3000 # definition of system default permissions for DEFAULT user, created on
3001 # system setup
3001 # system setup
3002 DEFAULT_USER_PERMISSIONS = [
3002 DEFAULT_USER_PERMISSIONS = [
3003 # object perms
3003 # object perms
3004 'repository.read',
3004 'repository.read',
3005 'group.read',
3005 'group.read',
3006 'usergroup.read',
3006 'usergroup.read',
3007 # branch, for backward compat we need same value as before so forced pushed
3007 # branch, for backward compat we need same value as before so forced pushed
3008 'branch.push_force',
3008 'branch.push_force',
3009 # global
3009 # global
3010 'hg.create.repository',
3010 'hg.create.repository',
3011 'hg.repogroup.create.false',
3011 'hg.repogroup.create.false',
3012 'hg.usergroup.create.false',
3012 'hg.usergroup.create.false',
3013 'hg.create.write_on_repogroup.true',
3013 'hg.create.write_on_repogroup.true',
3014 'hg.fork.repository',
3014 'hg.fork.repository',
3015 'hg.register.manual_activate',
3015 'hg.register.manual_activate',
3016 'hg.password_reset.enabled',
3016 'hg.password_reset.enabled',
3017 'hg.extern_activate.auto',
3017 'hg.extern_activate.auto',
3018 'hg.inherit_default_perms.true',
3018 'hg.inherit_default_perms.true',
3019 ]
3019 ]
3020
3020
3021 # defines which permissions are more important higher the more important
3021 # defines which permissions are more important higher the more important
3022 # Weight defines which permissions are more important.
3022 # Weight defines which permissions are more important.
3023 # The higher number the more important.
3023 # The higher number the more important.
3024 PERM_WEIGHTS = {
3024 PERM_WEIGHTS = {
3025 'repository.none': 0,
3025 'repository.none': 0,
3026 'repository.read': 1,
3026 'repository.read': 1,
3027 'repository.write': 3,
3027 'repository.write': 3,
3028 'repository.admin': 4,
3028 'repository.admin': 4,
3029
3029
3030 'group.none': 0,
3030 'group.none': 0,
3031 'group.read': 1,
3031 'group.read': 1,
3032 'group.write': 3,
3032 'group.write': 3,
3033 'group.admin': 4,
3033 'group.admin': 4,
3034
3034
3035 'usergroup.none': 0,
3035 'usergroup.none': 0,
3036 'usergroup.read': 1,
3036 'usergroup.read': 1,
3037 'usergroup.write': 3,
3037 'usergroup.write': 3,
3038 'usergroup.admin': 4,
3038 'usergroup.admin': 4,
3039
3039
3040 'branch.none': 0,
3040 'branch.none': 0,
3041 'branch.merge': 1,
3041 'branch.merge': 1,
3042 'branch.push': 3,
3042 'branch.push': 3,
3043 'branch.push_force': 4,
3043 'branch.push_force': 4,
3044
3044
3045 'hg.repogroup.create.false': 0,
3045 'hg.repogroup.create.false': 0,
3046 'hg.repogroup.create.true': 1,
3046 'hg.repogroup.create.true': 1,
3047
3047
3048 'hg.usergroup.create.false': 0,
3048 'hg.usergroup.create.false': 0,
3049 'hg.usergroup.create.true': 1,
3049 'hg.usergroup.create.true': 1,
3050
3050
3051 'hg.fork.none': 0,
3051 'hg.fork.none': 0,
3052 'hg.fork.repository': 1,
3052 'hg.fork.repository': 1,
3053 'hg.create.none': 0,
3053 'hg.create.none': 0,
3054 'hg.create.repository': 1
3054 'hg.create.repository': 1
3055 }
3055 }
3056
3056
3057 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3057 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3058 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3058 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3059 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3059 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3060
3060
3061 def __unicode__(self):
3061 def __unicode__(self):
3062 return u"<%s('%s:%s')>" % (
3062 return u"<%s('%s:%s')>" % (
3063 self.__class__.__name__, self.permission_id, self.permission_name
3063 self.__class__.__name__, self.permission_id, self.permission_name
3064 )
3064 )
3065
3065
3066 @classmethod
3066 @classmethod
3067 def get_by_key(cls, key):
3067 def get_by_key(cls, key):
3068 return cls.query().filter(cls.permission_name == key).scalar()
3068 return cls.query().filter(cls.permission_name == key).scalar()
3069
3069
3070 @classmethod
3070 @classmethod
3071 def get_default_repo_perms(cls, user_id, repo_id=None):
3071 def get_default_repo_perms(cls, user_id, repo_id=None):
3072 q = Session().query(UserRepoToPerm, Repository, Permission)\
3072 q = Session().query(UserRepoToPerm, Repository, Permission)\
3073 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3073 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3074 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3074 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3075 .filter(UserRepoToPerm.user_id == user_id)
3075 .filter(UserRepoToPerm.user_id == user_id)
3076 if repo_id:
3076 if repo_id:
3077 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3077 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3078 return q.all()
3078 return q.all()
3079
3079
3080 @classmethod
3080 @classmethod
3081 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3081 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3082 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3082 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3083 .join(
3083 .join(
3084 Permission,
3084 Permission,
3085 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3085 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3086 .join(
3086 .join(
3087 UserRepoToPerm,
3087 UserRepoToPerm,
3088 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3088 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3089 .filter(UserRepoToPerm.user_id == user_id)
3089 .filter(UserRepoToPerm.user_id == user_id)
3090
3090
3091 if repo_id:
3091 if repo_id:
3092 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3092 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3093 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3093 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3094
3094
3095 @classmethod
3095 @classmethod
3096 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3096 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3097 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3097 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3098 .join(
3098 .join(
3099 Permission,
3099 Permission,
3100 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3100 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3101 .join(
3101 .join(
3102 Repository,
3102 Repository,
3103 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3103 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3104 .join(
3104 .join(
3105 UserGroup,
3105 UserGroup,
3106 UserGroupRepoToPerm.users_group_id ==
3106 UserGroupRepoToPerm.users_group_id ==
3107 UserGroup.users_group_id)\
3107 UserGroup.users_group_id)\
3108 .join(
3108 .join(
3109 UserGroupMember,
3109 UserGroupMember,
3110 UserGroupRepoToPerm.users_group_id ==
3110 UserGroupRepoToPerm.users_group_id ==
3111 UserGroupMember.users_group_id)\
3111 UserGroupMember.users_group_id)\
3112 .filter(
3112 .filter(
3113 UserGroupMember.user_id == user_id,
3113 UserGroupMember.user_id == user_id,
3114 UserGroup.users_group_active == true())
3114 UserGroup.users_group_active == true())
3115 if repo_id:
3115 if repo_id:
3116 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3116 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3117 return q.all()
3117 return q.all()
3118
3118
3119 @classmethod
3119 @classmethod
3120 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3120 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3121 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3121 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3122 .join(
3122 .join(
3123 Permission,
3123 Permission,
3124 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3124 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3125 .join(
3125 .join(
3126 UserGroupRepoToPerm,
3126 UserGroupRepoToPerm,
3127 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3127 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3128 .join(
3128 .join(
3129 UserGroup,
3129 UserGroup,
3130 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3130 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3131 .join(
3131 .join(
3132 UserGroupMember,
3132 UserGroupMember,
3133 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3133 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3134 .filter(
3134 .filter(
3135 UserGroupMember.user_id == user_id,
3135 UserGroupMember.user_id == user_id,
3136 UserGroup.users_group_active == true())
3136 UserGroup.users_group_active == true())
3137
3137
3138 if repo_id:
3138 if repo_id:
3139 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3139 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3140 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3140 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3141
3141
3142 @classmethod
3142 @classmethod
3143 def get_default_group_perms(cls, user_id, repo_group_id=None):
3143 def get_default_group_perms(cls, user_id, repo_group_id=None):
3144 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3144 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3145 .join(
3145 .join(
3146 Permission,
3146 Permission,
3147 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3147 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3148 .join(
3148 .join(
3149 RepoGroup,
3149 RepoGroup,
3150 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3150 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3151 .filter(UserRepoGroupToPerm.user_id == user_id)
3151 .filter(UserRepoGroupToPerm.user_id == user_id)
3152 if repo_group_id:
3152 if repo_group_id:
3153 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3153 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3154 return q.all()
3154 return q.all()
3155
3155
3156 @classmethod
3156 @classmethod
3157 def get_default_group_perms_from_user_group(
3157 def get_default_group_perms_from_user_group(
3158 cls, user_id, repo_group_id=None):
3158 cls, user_id, repo_group_id=None):
3159 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3159 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3160 .join(
3160 .join(
3161 Permission,
3161 Permission,
3162 UserGroupRepoGroupToPerm.permission_id ==
3162 UserGroupRepoGroupToPerm.permission_id ==
3163 Permission.permission_id)\
3163 Permission.permission_id)\
3164 .join(
3164 .join(
3165 RepoGroup,
3165 RepoGroup,
3166 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3166 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3167 .join(
3167 .join(
3168 UserGroup,
3168 UserGroup,
3169 UserGroupRepoGroupToPerm.users_group_id ==
3169 UserGroupRepoGroupToPerm.users_group_id ==
3170 UserGroup.users_group_id)\
3170 UserGroup.users_group_id)\
3171 .join(
3171 .join(
3172 UserGroupMember,
3172 UserGroupMember,
3173 UserGroupRepoGroupToPerm.users_group_id ==
3173 UserGroupRepoGroupToPerm.users_group_id ==
3174 UserGroupMember.users_group_id)\
3174 UserGroupMember.users_group_id)\
3175 .filter(
3175 .filter(
3176 UserGroupMember.user_id == user_id,
3176 UserGroupMember.user_id == user_id,
3177 UserGroup.users_group_active == true())
3177 UserGroup.users_group_active == true())
3178 if repo_group_id:
3178 if repo_group_id:
3179 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3179 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3180 return q.all()
3180 return q.all()
3181
3181
3182 @classmethod
3182 @classmethod
3183 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3183 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3184 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3184 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3185 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3185 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3186 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3186 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3187 .filter(UserUserGroupToPerm.user_id == user_id)
3187 .filter(UserUserGroupToPerm.user_id == user_id)
3188 if user_group_id:
3188 if user_group_id:
3189 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3189 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3190 return q.all()
3190 return q.all()
3191
3191
3192 @classmethod
3192 @classmethod
3193 def get_default_user_group_perms_from_user_group(
3193 def get_default_user_group_perms_from_user_group(
3194 cls, user_id, user_group_id=None):
3194 cls, user_id, user_group_id=None):
3195 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3195 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3196 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3196 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3197 .join(
3197 .join(
3198 Permission,
3198 Permission,
3199 UserGroupUserGroupToPerm.permission_id ==
3199 UserGroupUserGroupToPerm.permission_id ==
3200 Permission.permission_id)\
3200 Permission.permission_id)\
3201 .join(
3201 .join(
3202 TargetUserGroup,
3202 TargetUserGroup,
3203 UserGroupUserGroupToPerm.target_user_group_id ==
3203 UserGroupUserGroupToPerm.target_user_group_id ==
3204 TargetUserGroup.users_group_id)\
3204 TargetUserGroup.users_group_id)\
3205 .join(
3205 .join(
3206 UserGroup,
3206 UserGroup,
3207 UserGroupUserGroupToPerm.user_group_id ==
3207 UserGroupUserGroupToPerm.user_group_id ==
3208 UserGroup.users_group_id)\
3208 UserGroup.users_group_id)\
3209 .join(
3209 .join(
3210 UserGroupMember,
3210 UserGroupMember,
3211 UserGroupUserGroupToPerm.user_group_id ==
3211 UserGroupUserGroupToPerm.user_group_id ==
3212 UserGroupMember.users_group_id)\
3212 UserGroupMember.users_group_id)\
3213 .filter(
3213 .filter(
3214 UserGroupMember.user_id == user_id,
3214 UserGroupMember.user_id == user_id,
3215 UserGroup.users_group_active == true())
3215 UserGroup.users_group_active == true())
3216 if user_group_id:
3216 if user_group_id:
3217 q = q.filter(
3217 q = q.filter(
3218 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3218 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3219
3219
3220 return q.all()
3220 return q.all()
3221
3221
3222
3222
3223 class UserRepoToPerm(Base, BaseModel):
3223 class UserRepoToPerm(Base, BaseModel):
3224 __tablename__ = 'repo_to_perm'
3224 __tablename__ = 'repo_to_perm'
3225 __table_args__ = (
3225 __table_args__ = (
3226 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3226 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3227 base_table_args
3227 base_table_args
3228 )
3228 )
3229
3229
3230 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3230 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3231 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3231 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3232 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3232 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3233 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3233 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3234
3234
3235 user = relationship('User')
3235 user = relationship('User')
3236 repository = relationship('Repository')
3236 repository = relationship('Repository')
3237 permission = relationship('Permission')
3237 permission = relationship('Permission')
3238
3238
3239 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3239 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3240
3240
3241 @classmethod
3241 @classmethod
3242 def create(cls, user, repository, permission):
3242 def create(cls, user, repository, permission):
3243 n = cls()
3243 n = cls()
3244 n.user = user
3244 n.user = user
3245 n.repository = repository
3245 n.repository = repository
3246 n.permission = permission
3246 n.permission = permission
3247 Session().add(n)
3247 Session().add(n)
3248 return n
3248 return n
3249
3249
3250 def __unicode__(self):
3250 def __unicode__(self):
3251 return u'<%s => %s >' % (self.user, self.repository)
3251 return u'<%s => %s >' % (self.user, self.repository)
3252
3252
3253
3253
3254 class UserUserGroupToPerm(Base, BaseModel):
3254 class UserUserGroupToPerm(Base, BaseModel):
3255 __tablename__ = 'user_user_group_to_perm'
3255 __tablename__ = 'user_user_group_to_perm'
3256 __table_args__ = (
3256 __table_args__ = (
3257 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3257 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3258 base_table_args
3258 base_table_args
3259 )
3259 )
3260
3260
3261 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3261 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3262 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3262 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3263 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3263 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3264 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3264 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3265
3265
3266 user = relationship('User')
3266 user = relationship('User')
3267 user_group = relationship('UserGroup')
3267 user_group = relationship('UserGroup')
3268 permission = relationship('Permission')
3268 permission = relationship('Permission')
3269
3269
3270 @classmethod
3270 @classmethod
3271 def create(cls, user, user_group, permission):
3271 def create(cls, user, user_group, permission):
3272 n = cls()
3272 n = cls()
3273 n.user = user
3273 n.user = user
3274 n.user_group = user_group
3274 n.user_group = user_group
3275 n.permission = permission
3275 n.permission = permission
3276 Session().add(n)
3276 Session().add(n)
3277 return n
3277 return n
3278
3278
3279 def __unicode__(self):
3279 def __unicode__(self):
3280 return u'<%s => %s >' % (self.user, self.user_group)
3280 return u'<%s => %s >' % (self.user, self.user_group)
3281
3281
3282
3282
3283 class UserToPerm(Base, BaseModel):
3283 class UserToPerm(Base, BaseModel):
3284 __tablename__ = 'user_to_perm'
3284 __tablename__ = 'user_to_perm'
3285 __table_args__ = (
3285 __table_args__ = (
3286 UniqueConstraint('user_id', 'permission_id'),
3286 UniqueConstraint('user_id', 'permission_id'),
3287 base_table_args
3287 base_table_args
3288 )
3288 )
3289
3289
3290 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3290 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3291 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3291 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3292 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3292 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3293
3293
3294 user = relationship('User')
3294 user = relationship('User')
3295 permission = relationship('Permission', lazy='joined')
3295 permission = relationship('Permission', lazy='joined')
3296
3296
3297 def __unicode__(self):
3297 def __unicode__(self):
3298 return u'<%s => %s >' % (self.user, self.permission)
3298 return u'<%s => %s >' % (self.user, self.permission)
3299
3299
3300
3300
3301 class UserGroupRepoToPerm(Base, BaseModel):
3301 class UserGroupRepoToPerm(Base, BaseModel):
3302 __tablename__ = 'users_group_repo_to_perm'
3302 __tablename__ = 'users_group_repo_to_perm'
3303 __table_args__ = (
3303 __table_args__ = (
3304 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3304 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3305 base_table_args
3305 base_table_args
3306 )
3306 )
3307
3307
3308 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3308 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3309 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3309 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3310 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3310 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3311 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3311 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3312
3312
3313 users_group = relationship('UserGroup')
3313 users_group = relationship('UserGroup')
3314 permission = relationship('Permission')
3314 permission = relationship('Permission')
3315 repository = relationship('Repository')
3315 repository = relationship('Repository')
3316 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3316 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3317
3317
3318 @classmethod
3318 @classmethod
3319 def create(cls, users_group, repository, permission):
3319 def create(cls, users_group, repository, permission):
3320 n = cls()
3320 n = cls()
3321 n.users_group = users_group
3321 n.users_group = users_group
3322 n.repository = repository
3322 n.repository = repository
3323 n.permission = permission
3323 n.permission = permission
3324 Session().add(n)
3324 Session().add(n)
3325 return n
3325 return n
3326
3326
3327 def __unicode__(self):
3327 def __unicode__(self):
3328 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3328 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3329
3329
3330
3330
3331 class UserGroupUserGroupToPerm(Base, BaseModel):
3331 class UserGroupUserGroupToPerm(Base, BaseModel):
3332 __tablename__ = 'user_group_user_group_to_perm'
3332 __tablename__ = 'user_group_user_group_to_perm'
3333 __table_args__ = (
3333 __table_args__ = (
3334 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3334 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3335 CheckConstraint('target_user_group_id != user_group_id'),
3335 CheckConstraint('target_user_group_id != user_group_id'),
3336 base_table_args
3336 base_table_args
3337 )
3337 )
3338
3338
3339 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3339 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3340 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3340 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3341 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3341 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3342 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3342 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3343
3343
3344 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3344 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3345 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3345 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3346 permission = relationship('Permission')
3346 permission = relationship('Permission')
3347
3347
3348 @classmethod
3348 @classmethod
3349 def create(cls, target_user_group, user_group, permission):
3349 def create(cls, target_user_group, user_group, permission):
3350 n = cls()
3350 n = cls()
3351 n.target_user_group = target_user_group
3351 n.target_user_group = target_user_group
3352 n.user_group = user_group
3352 n.user_group = user_group
3353 n.permission = permission
3353 n.permission = permission
3354 Session().add(n)
3354 Session().add(n)
3355 return n
3355 return n
3356
3356
3357 def __unicode__(self):
3357 def __unicode__(self):
3358 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3358 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3359
3359
3360
3360
3361 class UserGroupToPerm(Base, BaseModel):
3361 class UserGroupToPerm(Base, BaseModel):
3362 __tablename__ = 'users_group_to_perm'
3362 __tablename__ = 'users_group_to_perm'
3363 __table_args__ = (
3363 __table_args__ = (
3364 UniqueConstraint('users_group_id', 'permission_id',),
3364 UniqueConstraint('users_group_id', 'permission_id',),
3365 base_table_args
3365 base_table_args
3366 )
3366 )
3367
3367
3368 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3368 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3369 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3369 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3370 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3370 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3371
3371
3372 users_group = relationship('UserGroup')
3372 users_group = relationship('UserGroup')
3373 permission = relationship('Permission')
3373 permission = relationship('Permission')
3374
3374
3375
3375
3376 class UserRepoGroupToPerm(Base, BaseModel):
3376 class UserRepoGroupToPerm(Base, BaseModel):
3377 __tablename__ = 'user_repo_group_to_perm'
3377 __tablename__ = 'user_repo_group_to_perm'
3378 __table_args__ = (
3378 __table_args__ = (
3379 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3379 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3380 base_table_args
3380 base_table_args
3381 )
3381 )
3382
3382
3383 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3383 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3384 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3384 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3385 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3385 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3386 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3386 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3387
3387
3388 user = relationship('User')
3388 user = relationship('User')
3389 group = relationship('RepoGroup')
3389 group = relationship('RepoGroup')
3390 permission = relationship('Permission')
3390 permission = relationship('Permission')
3391
3391
3392 @classmethod
3392 @classmethod
3393 def create(cls, user, repository_group, permission):
3393 def create(cls, user, repository_group, permission):
3394 n = cls()
3394 n = cls()
3395 n.user = user
3395 n.user = user
3396 n.group = repository_group
3396 n.group = repository_group
3397 n.permission = permission
3397 n.permission = permission
3398 Session().add(n)
3398 Session().add(n)
3399 return n
3399 return n
3400
3400
3401
3401
3402 class UserGroupRepoGroupToPerm(Base, BaseModel):
3402 class UserGroupRepoGroupToPerm(Base, BaseModel):
3403 __tablename__ = 'users_group_repo_group_to_perm'
3403 __tablename__ = 'users_group_repo_group_to_perm'
3404 __table_args__ = (
3404 __table_args__ = (
3405 UniqueConstraint('users_group_id', 'group_id'),
3405 UniqueConstraint('users_group_id', 'group_id'),
3406 base_table_args
3406 base_table_args
3407 )
3407 )
3408
3408
3409 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3409 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3410 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3410 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3411 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3411 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3412 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3412 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3413
3413
3414 users_group = relationship('UserGroup')
3414 users_group = relationship('UserGroup')
3415 permission = relationship('Permission')
3415 permission = relationship('Permission')
3416 group = relationship('RepoGroup')
3416 group = relationship('RepoGroup')
3417
3417
3418 @classmethod
3418 @classmethod
3419 def create(cls, user_group, repository_group, permission):
3419 def create(cls, user_group, repository_group, permission):
3420 n = cls()
3420 n = cls()
3421 n.users_group = user_group
3421 n.users_group = user_group
3422 n.group = repository_group
3422 n.group = repository_group
3423 n.permission = permission
3423 n.permission = permission
3424 Session().add(n)
3424 Session().add(n)
3425 return n
3425 return n
3426
3426
3427 def __unicode__(self):
3427 def __unicode__(self):
3428 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3428 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3429
3429
3430
3430
3431 class Statistics(Base, BaseModel):
3431 class Statistics(Base, BaseModel):
3432 __tablename__ = 'statistics'
3432 __tablename__ = 'statistics'
3433 __table_args__ = (
3433 __table_args__ = (
3434 base_table_args
3434 base_table_args
3435 )
3435 )
3436
3436
3437 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3437 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3438 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3438 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3439 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3439 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3440 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3440 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3441 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3441 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3442 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3442 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3443
3443
3444 repository = relationship('Repository', single_parent=True)
3444 repository = relationship('Repository', single_parent=True)
3445
3445
3446
3446
3447 class UserFollowing(Base, BaseModel):
3447 class UserFollowing(Base, BaseModel):
3448 __tablename__ = 'user_followings'
3448 __tablename__ = 'user_followings'
3449 __table_args__ = (
3449 __table_args__ = (
3450 UniqueConstraint('user_id', 'follows_repository_id'),
3450 UniqueConstraint('user_id', 'follows_repository_id'),
3451 UniqueConstraint('user_id', 'follows_user_id'),
3451 UniqueConstraint('user_id', 'follows_user_id'),
3452 base_table_args
3452 base_table_args
3453 )
3453 )
3454
3454
3455 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3455 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3456 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3456 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3457 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3457 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3458 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3458 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3459 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3459 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3460
3460
3461 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3461 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3462
3462
3463 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3463 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3464 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3464 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3465
3465
3466 @classmethod
3466 @classmethod
3467 def get_repo_followers(cls, repo_id):
3467 def get_repo_followers(cls, repo_id):
3468 return cls.query().filter(cls.follows_repo_id == repo_id)
3468 return cls.query().filter(cls.follows_repo_id == repo_id)
3469
3469
3470
3470
3471 class CacheKey(Base, BaseModel):
3471 class CacheKey(Base, BaseModel):
3472 __tablename__ = 'cache_invalidation'
3472 __tablename__ = 'cache_invalidation'
3473 __table_args__ = (
3473 __table_args__ = (
3474 UniqueConstraint('cache_key'),
3474 UniqueConstraint('cache_key'),
3475 Index('key_idx', 'cache_key'),
3475 Index('key_idx', 'cache_key'),
3476 base_table_args,
3476 base_table_args,
3477 )
3477 )
3478
3478
3479 CACHE_TYPE_FEED = 'FEED'
3479 CACHE_TYPE_FEED = 'FEED'
3480 CACHE_TYPE_README = 'README'
3480 CACHE_TYPE_README = 'README'
3481 # namespaces used to register process/thread aware caches
3481 # namespaces used to register process/thread aware caches
3482 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3482 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3483 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3483 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3484
3484
3485 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3485 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3486 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3486 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3487 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3487 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3488 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3488 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3489
3489
3490 def __init__(self, cache_key, cache_args=''):
3490 def __init__(self, cache_key, cache_args=''):
3491 self.cache_key = cache_key
3491 self.cache_key = cache_key
3492 self.cache_args = cache_args
3492 self.cache_args = cache_args
3493 self.cache_active = False
3493 self.cache_active = False
3494
3494
3495 def __unicode__(self):
3495 def __unicode__(self):
3496 return u"<%s('%s:%s[%s]')>" % (
3496 return u"<%s('%s:%s[%s]')>" % (
3497 self.__class__.__name__,
3497 self.__class__.__name__,
3498 self.cache_id, self.cache_key, self.cache_active)
3498 self.cache_id, self.cache_key, self.cache_active)
3499
3499
3500 def _cache_key_partition(self):
3500 def _cache_key_partition(self):
3501 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3501 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3502 return prefix, repo_name, suffix
3502 return prefix, repo_name, suffix
3503
3503
3504 def get_prefix(self):
3504 def get_prefix(self):
3505 """
3505 """
3506 Try to extract prefix from existing cache key. The key could consist
3506 Try to extract prefix from existing cache key. The key could consist
3507 of prefix, repo_name, suffix
3507 of prefix, repo_name, suffix
3508 """
3508 """
3509 # this returns prefix, repo_name, suffix
3509 # this returns prefix, repo_name, suffix
3510 return self._cache_key_partition()[0]
3510 return self._cache_key_partition()[0]
3511
3511
3512 def get_suffix(self):
3512 def get_suffix(self):
3513 """
3513 """
3514 get suffix that might have been used in _get_cache_key to
3514 get suffix that might have been used in _get_cache_key to
3515 generate self.cache_key. Only used for informational purposes
3515 generate self.cache_key. Only used for informational purposes
3516 in repo_edit.mako.
3516 in repo_edit.mako.
3517 """
3517 """
3518 # prefix, repo_name, suffix
3518 # prefix, repo_name, suffix
3519 return self._cache_key_partition()[2]
3519 return self._cache_key_partition()[2]
3520
3520
3521 @classmethod
3521 @classmethod
3522 def delete_all_cache(cls):
3522 def delete_all_cache(cls):
3523 """
3523 """
3524 Delete all cache keys from database.
3524 Delete all cache keys from database.
3525 Should only be run when all instances are down and all entries
3525 Should only be run when all instances are down and all entries
3526 thus stale.
3526 thus stale.
3527 """
3527 """
3528 cls.query().delete()
3528 cls.query().delete()
3529 Session().commit()
3529 Session().commit()
3530
3530
3531 @classmethod
3531 @classmethod
3532 def set_invalidate(cls, cache_uid, delete=False):
3532 def set_invalidate(cls, cache_uid, delete=False):
3533 """
3533 """
3534 Mark all caches of a repo as invalid in the database.
3534 Mark all caches of a repo as invalid in the database.
3535 """
3535 """
3536
3536
3537 try:
3537 try:
3538 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3538 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3539 if delete:
3539 if delete:
3540 qry.delete()
3540 qry.delete()
3541 log.debug('cache objects deleted for cache args %s',
3541 log.debug('cache objects deleted for cache args %s',
3542 safe_str(cache_uid))
3542 safe_str(cache_uid))
3543 else:
3543 else:
3544 qry.update({"cache_active": False})
3544 qry.update({"cache_active": False})
3545 log.debug('cache objects marked as invalid for cache args %s',
3545 log.debug('cache objects marked as invalid for cache args %s',
3546 safe_str(cache_uid))
3546 safe_str(cache_uid))
3547
3547
3548 Session().commit()
3548 Session().commit()
3549 except Exception:
3549 except Exception:
3550 log.exception(
3550 log.exception(
3551 'Cache key invalidation failed for cache args %s',
3551 'Cache key invalidation failed for cache args %s',
3552 safe_str(cache_uid))
3552 safe_str(cache_uid))
3553 Session().rollback()
3553 Session().rollback()
3554
3554
3555 @classmethod
3555 @classmethod
3556 def get_active_cache(cls, cache_key):
3556 def get_active_cache(cls, cache_key):
3557 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3557 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3558 if inv_obj:
3558 if inv_obj:
3559 return inv_obj
3559 return inv_obj
3560 return None
3560 return None
3561
3561
3562
3562
3563 class ChangesetComment(Base, BaseModel):
3563 class ChangesetComment(Base, BaseModel):
3564 __tablename__ = 'changeset_comments'
3564 __tablename__ = 'changeset_comments'
3565 __table_args__ = (
3565 __table_args__ = (
3566 Index('cc_revision_idx', 'revision'),
3566 Index('cc_revision_idx', 'revision'),
3567 base_table_args,
3567 base_table_args,
3568 )
3568 )
3569
3569
3570 COMMENT_OUTDATED = u'comment_outdated'
3570 COMMENT_OUTDATED = u'comment_outdated'
3571 COMMENT_TYPE_NOTE = u'note'
3571 COMMENT_TYPE_NOTE = u'note'
3572 COMMENT_TYPE_TODO = u'todo'
3572 COMMENT_TYPE_TODO = u'todo'
3573 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3573 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3574
3574
3575 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3575 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3576 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3576 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3577 revision = Column('revision', String(40), nullable=True)
3577 revision = Column('revision', String(40), nullable=True)
3578 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3578 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3579 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3579 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3580 line_no = Column('line_no', Unicode(10), nullable=True)
3580 line_no = Column('line_no', Unicode(10), nullable=True)
3581 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3581 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3582 f_path = Column('f_path', Unicode(1000), nullable=True)
3582 f_path = Column('f_path', Unicode(1000), nullable=True)
3583 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3583 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3584 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3584 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3585 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3585 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3586 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3586 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3587 renderer = Column('renderer', Unicode(64), nullable=True)
3587 renderer = Column('renderer', Unicode(64), nullable=True)
3588 display_state = Column('display_state', Unicode(128), nullable=True)
3588 display_state = Column('display_state', Unicode(128), nullable=True)
3589
3589
3590 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3590 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3591 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3591 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3592
3592
3593 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3593 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3594 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3594 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3595
3595
3596 author = relationship('User', lazy='joined')
3596 author = relationship('User', lazy='joined')
3597 repo = relationship('Repository')
3597 repo = relationship('Repository')
3598 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3598 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3599 pull_request = relationship('PullRequest', lazy='joined')
3599 pull_request = relationship('PullRequest', lazy='joined')
3600 pull_request_version = relationship('PullRequestVersion')
3600 pull_request_version = relationship('PullRequestVersion')
3601
3601
3602 @classmethod
3602 @classmethod
3603 def get_users(cls, revision=None, pull_request_id=None):
3603 def get_users(cls, revision=None, pull_request_id=None):
3604 """
3604 """
3605 Returns user associated with this ChangesetComment. ie those
3605 Returns user associated with this ChangesetComment. ie those
3606 who actually commented
3606 who actually commented
3607
3607
3608 :param cls:
3608 :param cls:
3609 :param revision:
3609 :param revision:
3610 """
3610 """
3611 q = Session().query(User)\
3611 q = Session().query(User)\
3612 .join(ChangesetComment.author)
3612 .join(ChangesetComment.author)
3613 if revision:
3613 if revision:
3614 q = q.filter(cls.revision == revision)
3614 q = q.filter(cls.revision == revision)
3615 elif pull_request_id:
3615 elif pull_request_id:
3616 q = q.filter(cls.pull_request_id == pull_request_id)
3616 q = q.filter(cls.pull_request_id == pull_request_id)
3617 return q.all()
3617 return q.all()
3618
3618
3619 @classmethod
3619 @classmethod
3620 def get_index_from_version(cls, pr_version, versions):
3620 def get_index_from_version(cls, pr_version, versions):
3621 num_versions = [x.pull_request_version_id for x in versions]
3621 num_versions = [x.pull_request_version_id for x in versions]
3622 try:
3622 try:
3623 return num_versions.index(pr_version) +1
3623 return num_versions.index(pr_version) +1
3624 except (IndexError, ValueError):
3624 except (IndexError, ValueError):
3625 return
3625 return
3626
3626
3627 @property
3627 @property
3628 def outdated(self):
3628 def outdated(self):
3629 return self.display_state == self.COMMENT_OUTDATED
3629 return self.display_state == self.COMMENT_OUTDATED
3630
3630
3631 def outdated_at_version(self, version):
3631 def outdated_at_version(self, version):
3632 """
3632 """
3633 Checks if comment is outdated for given pull request version
3633 Checks if comment is outdated for given pull request version
3634 """
3634 """
3635 return self.outdated and self.pull_request_version_id != version
3635 return self.outdated and self.pull_request_version_id != version
3636
3636
3637 def older_than_version(self, version):
3637 def older_than_version(self, version):
3638 """
3638 """
3639 Checks if comment is made from previous version than given
3639 Checks if comment is made from previous version than given
3640 """
3640 """
3641 if version is None:
3641 if version is None:
3642 return self.pull_request_version_id is not None
3642 return self.pull_request_version_id is not None
3643
3643
3644 return self.pull_request_version_id < version
3644 return self.pull_request_version_id < version
3645
3645
3646 @property
3646 @property
3647 def resolved(self):
3647 def resolved(self):
3648 return self.resolved_by[0] if self.resolved_by else None
3648 return self.resolved_by[0] if self.resolved_by else None
3649
3649
3650 @property
3650 @property
3651 def is_todo(self):
3651 def is_todo(self):
3652 return self.comment_type == self.COMMENT_TYPE_TODO
3652 return self.comment_type == self.COMMENT_TYPE_TODO
3653
3653
3654 @property
3654 @property
3655 def is_inline(self):
3655 def is_inline(self):
3656 return self.line_no and self.f_path
3656 return self.line_no and self.f_path
3657
3657
3658 def get_index_version(self, versions):
3658 def get_index_version(self, versions):
3659 return self.get_index_from_version(
3659 return self.get_index_from_version(
3660 self.pull_request_version_id, versions)
3660 self.pull_request_version_id, versions)
3661
3661
3662 def __repr__(self):
3662 def __repr__(self):
3663 if self.comment_id:
3663 if self.comment_id:
3664 return '<DB:Comment #%s>' % self.comment_id
3664 return '<DB:Comment #%s>' % self.comment_id
3665 else:
3665 else:
3666 return '<DB:Comment at %#x>' % id(self)
3666 return '<DB:Comment at %#x>' % id(self)
3667
3667
3668 def get_api_data(self):
3668 def get_api_data(self):
3669 comment = self
3669 comment = self
3670 data = {
3670 data = {
3671 'comment_id': comment.comment_id,
3671 'comment_id': comment.comment_id,
3672 'comment_type': comment.comment_type,
3672 'comment_type': comment.comment_type,
3673 'comment_text': comment.text,
3673 'comment_text': comment.text,
3674 'comment_status': comment.status_change,
3674 'comment_status': comment.status_change,
3675 'comment_f_path': comment.f_path,
3675 'comment_f_path': comment.f_path,
3676 'comment_lineno': comment.line_no,
3676 'comment_lineno': comment.line_no,
3677 'comment_author': comment.author,
3677 'comment_author': comment.author,
3678 'comment_created_on': comment.created_on,
3678 'comment_created_on': comment.created_on,
3679 'comment_resolved_by': self.resolved
3679 'comment_resolved_by': self.resolved
3680 }
3680 }
3681 return data
3681 return data
3682
3682
3683 def __json__(self):
3683 def __json__(self):
3684 data = dict()
3684 data = dict()
3685 data.update(self.get_api_data())
3685 data.update(self.get_api_data())
3686 return data
3686 return data
3687
3687
3688
3688
3689 class ChangesetStatus(Base, BaseModel):
3689 class ChangesetStatus(Base, BaseModel):
3690 __tablename__ = 'changeset_statuses'
3690 __tablename__ = 'changeset_statuses'
3691 __table_args__ = (
3691 __table_args__ = (
3692 Index('cs_revision_idx', 'revision'),
3692 Index('cs_revision_idx', 'revision'),
3693 Index('cs_version_idx', 'version'),
3693 Index('cs_version_idx', 'version'),
3694 UniqueConstraint('repo_id', 'revision', 'version'),
3694 UniqueConstraint('repo_id', 'revision', 'version'),
3695 base_table_args
3695 base_table_args
3696 )
3696 )
3697
3697
3698 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3698 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3699 STATUS_APPROVED = 'approved'
3699 STATUS_APPROVED = 'approved'
3700 STATUS_REJECTED = 'rejected'
3700 STATUS_REJECTED = 'rejected'
3701 STATUS_UNDER_REVIEW = 'under_review'
3701 STATUS_UNDER_REVIEW = 'under_review'
3702
3702
3703 STATUSES = [
3703 STATUSES = [
3704 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3704 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3705 (STATUS_APPROVED, _("Approved")),
3705 (STATUS_APPROVED, _("Approved")),
3706 (STATUS_REJECTED, _("Rejected")),
3706 (STATUS_REJECTED, _("Rejected")),
3707 (STATUS_UNDER_REVIEW, _("Under Review")),
3707 (STATUS_UNDER_REVIEW, _("Under Review")),
3708 ]
3708 ]
3709
3709
3710 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3710 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3711 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3711 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3712 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3712 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3713 revision = Column('revision', String(40), nullable=False)
3713 revision = Column('revision', String(40), nullable=False)
3714 status = Column('status', String(128), nullable=False, default=DEFAULT)
3714 status = Column('status', String(128), nullable=False, default=DEFAULT)
3715 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3715 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3716 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3716 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3717 version = Column('version', Integer(), nullable=False, default=0)
3717 version = Column('version', Integer(), nullable=False, default=0)
3718 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3718 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3719
3719
3720 author = relationship('User', lazy='joined')
3720 author = relationship('User', lazy='joined')
3721 repo = relationship('Repository')
3721 repo = relationship('Repository')
3722 comment = relationship('ChangesetComment', lazy='joined')
3722 comment = relationship('ChangesetComment', lazy='joined')
3723 pull_request = relationship('PullRequest', lazy='joined')
3723 pull_request = relationship('PullRequest', lazy='joined')
3724
3724
3725 def __unicode__(self):
3725 def __unicode__(self):
3726 return u"<%s('%s[v%s]:%s')>" % (
3726 return u"<%s('%s[v%s]:%s')>" % (
3727 self.__class__.__name__,
3727 self.__class__.__name__,
3728 self.status, self.version, self.author
3728 self.status, self.version, self.author
3729 )
3729 )
3730
3730
3731 @classmethod
3731 @classmethod
3732 def get_status_lbl(cls, value):
3732 def get_status_lbl(cls, value):
3733 return dict(cls.STATUSES).get(value)
3733 return dict(cls.STATUSES).get(value)
3734
3734
3735 @property
3735 @property
3736 def status_lbl(self):
3736 def status_lbl(self):
3737 return ChangesetStatus.get_status_lbl(self.status)
3737 return ChangesetStatus.get_status_lbl(self.status)
3738
3738
3739 def get_api_data(self):
3739 def get_api_data(self):
3740 status = self
3740 status = self
3741 data = {
3741 data = {
3742 'status_id': status.changeset_status_id,
3742 'status_id': status.changeset_status_id,
3743 'status': status.status,
3743 'status': status.status,
3744 }
3744 }
3745 return data
3745 return data
3746
3746
3747 def __json__(self):
3747 def __json__(self):
3748 data = dict()
3748 data = dict()
3749 data.update(self.get_api_data())
3749 data.update(self.get_api_data())
3750 return data
3750 return data
3751
3751
3752
3752
3753 class _SetState(object):
3753 class _SetState(object):
3754 """
3754 """
3755 Context processor allowing changing state for sensitive operation such as
3755 Context processor allowing changing state for sensitive operation such as
3756 pull request update or merge
3756 pull request update or merge
3757 """
3757 """
3758
3758
3759 def __init__(self, pull_request, pr_state, back_state=None):
3759 def __init__(self, pull_request, pr_state, back_state=None):
3760 self._pr = pull_request
3760 self._pr = pull_request
3761 self._org_state = back_state or pull_request.pull_request_state
3761 self._org_state = back_state or pull_request.pull_request_state
3762 self._pr_state = pr_state
3762 self._pr_state = pr_state
3763
3763
3764 def __enter__(self):
3764 def __enter__(self):
3765 log.debug('StateLock: entering set state context, setting state to: `%s`',
3765 log.debug('StateLock: entering set state context, setting state to: `%s`',
3766 self._pr_state)
3766 self._pr_state)
3767 self._pr.pull_request_state = self._pr_state
3767 self._pr.pull_request_state = self._pr_state
3768 Session().add(self._pr)
3768 Session().add(self._pr)
3769 Session().commit()
3769 Session().commit()
3770
3770
3771 def __exit__(self, exc_type, exc_val, exc_tb):
3771 def __exit__(self, exc_type, exc_val, exc_tb):
3772 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3772 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3773 self._org_state)
3773 self._org_state)
3774 self._pr.pull_request_state = self._org_state
3774 self._pr.pull_request_state = self._org_state
3775 Session().add(self._pr)
3775 Session().add(self._pr)
3776 Session().commit()
3776 Session().commit()
3777
3777
3778
3778
3779 class _PullRequestBase(BaseModel):
3779 class _PullRequestBase(BaseModel):
3780 """
3780 """
3781 Common attributes of pull request and version entries.
3781 Common attributes of pull request and version entries.
3782 """
3782 """
3783
3783
3784 # .status values
3784 # .status values
3785 STATUS_NEW = u'new'
3785 STATUS_NEW = u'new'
3786 STATUS_OPEN = u'open'
3786 STATUS_OPEN = u'open'
3787 STATUS_CLOSED = u'closed'
3787 STATUS_CLOSED = u'closed'
3788
3788
3789 # available states
3789 # available states
3790 STATE_CREATING = u'creating'
3790 STATE_CREATING = u'creating'
3791 STATE_UPDATING = u'updating'
3791 STATE_UPDATING = u'updating'
3792 STATE_MERGING = u'merging'
3792 STATE_MERGING = u'merging'
3793 STATE_CREATED = u'created'
3793 STATE_CREATED = u'created'
3794
3794
3795 title = Column('title', Unicode(255), nullable=True)
3795 title = Column('title', Unicode(255), nullable=True)
3796 description = Column(
3796 description = Column(
3797 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3797 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3798 nullable=True)
3798 nullable=True)
3799 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3799 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3800
3800
3801 # new/open/closed status of pull request (not approve/reject/etc)
3801 # new/open/closed status of pull request (not approve/reject/etc)
3802 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3802 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3803 created_on = Column(
3803 created_on = Column(
3804 'created_on', DateTime(timezone=False), nullable=False,
3804 'created_on', DateTime(timezone=False), nullable=False,
3805 default=datetime.datetime.now)
3805 default=datetime.datetime.now)
3806 updated_on = Column(
3806 updated_on = Column(
3807 'updated_on', DateTime(timezone=False), nullable=False,
3807 'updated_on', DateTime(timezone=False), nullable=False,
3808 default=datetime.datetime.now)
3808 default=datetime.datetime.now)
3809
3809
3810 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3810 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3811
3811
3812 @declared_attr
3812 @declared_attr
3813 def user_id(cls):
3813 def user_id(cls):
3814 return Column(
3814 return Column(
3815 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3815 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3816 unique=None)
3816 unique=None)
3817
3817
3818 # 500 revisions max
3818 # 500 revisions max
3819 _revisions = Column(
3819 _revisions = Column(
3820 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3820 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3821
3821
3822 @declared_attr
3822 @declared_attr
3823 def source_repo_id(cls):
3823 def source_repo_id(cls):
3824 # TODO: dan: rename column to source_repo_id
3824 # TODO: dan: rename column to source_repo_id
3825 return Column(
3825 return Column(
3826 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3826 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3827 nullable=False)
3827 nullable=False)
3828
3828
3829 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3829 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3830
3830
3831 @hybrid_property
3831 @hybrid_property
3832 def source_ref(self):
3832 def source_ref(self):
3833 return self._source_ref
3833 return self._source_ref
3834
3834
3835 @source_ref.setter
3835 @source_ref.setter
3836 def source_ref(self, val):
3836 def source_ref(self, val):
3837 parts = (val or '').split(':')
3837 parts = (val or '').split(':')
3838 if len(parts) != 3:
3838 if len(parts) != 3:
3839 raise ValueError(
3839 raise ValueError(
3840 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3840 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3841 self._source_ref = safe_unicode(val)
3841 self._source_ref = safe_unicode(val)
3842
3842
3843 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3843 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3844
3844
3845 @hybrid_property
3845 @hybrid_property
3846 def target_ref(self):
3846 def target_ref(self):
3847 return self._target_ref
3847 return self._target_ref
3848
3848
3849 @target_ref.setter
3849 @target_ref.setter
3850 def target_ref(self, val):
3850 def target_ref(self, val):
3851 parts = (val or '').split(':')
3851 parts = (val or '').split(':')
3852 if len(parts) != 3:
3852 if len(parts) != 3:
3853 raise ValueError(
3853 raise ValueError(
3854 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3854 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3855 self._target_ref = safe_unicode(val)
3855 self._target_ref = safe_unicode(val)
3856
3856
3857 @declared_attr
3857 @declared_attr
3858 def target_repo_id(cls):
3858 def target_repo_id(cls):
3859 # TODO: dan: rename column to target_repo_id
3859 # TODO: dan: rename column to target_repo_id
3860 return Column(
3860 return Column(
3861 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3861 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3862 nullable=False)
3862 nullable=False)
3863
3863
3864 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3864 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3865
3865
3866 # TODO: dan: rename column to last_merge_source_rev
3866 # TODO: dan: rename column to last_merge_source_rev
3867 _last_merge_source_rev = Column(
3867 _last_merge_source_rev = Column(
3868 'last_merge_org_rev', String(40), nullable=True)
3868 'last_merge_org_rev', String(40), nullable=True)
3869 # TODO: dan: rename column to last_merge_target_rev
3869 # TODO: dan: rename column to last_merge_target_rev
3870 _last_merge_target_rev = Column(
3870 _last_merge_target_rev = Column(
3871 'last_merge_other_rev', String(40), nullable=True)
3871 'last_merge_other_rev', String(40), nullable=True)
3872 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3872 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3873 merge_rev = Column('merge_rev', String(40), nullable=True)
3873 merge_rev = Column('merge_rev', String(40), nullable=True)
3874
3874
3875 reviewer_data = Column(
3875 reviewer_data = Column(
3876 'reviewer_data_json', MutationObj.as_mutable(
3876 'reviewer_data_json', MutationObj.as_mutable(
3877 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3877 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3878
3878
3879 @property
3879 @property
3880 def reviewer_data_json(self):
3880 def reviewer_data_json(self):
3881 return json.dumps(self.reviewer_data)
3881 return json.dumps(self.reviewer_data)
3882
3882
3883 @hybrid_property
3883 @hybrid_property
3884 def description_safe(self):
3884 def description_safe(self):
3885 from rhodecode.lib import helpers as h
3885 from rhodecode.lib import helpers as h
3886 return h.escape(self.description)
3886 return h.escape(self.description)
3887
3887
3888 @hybrid_property
3888 @hybrid_property
3889 def revisions(self):
3889 def revisions(self):
3890 return self._revisions.split(':') if self._revisions else []
3890 return self._revisions.split(':') if self._revisions else []
3891
3891
3892 @revisions.setter
3892 @revisions.setter
3893 def revisions(self, val):
3893 def revisions(self, val):
3894 self._revisions = ':'.join(val)
3894 self._revisions = ':'.join(val)
3895
3895
3896 @hybrid_property
3896 @hybrid_property
3897 def last_merge_status(self):
3897 def last_merge_status(self):
3898 return safe_int(self._last_merge_status)
3898 return safe_int(self._last_merge_status)
3899
3899
3900 @last_merge_status.setter
3900 @last_merge_status.setter
3901 def last_merge_status(self, val):
3901 def last_merge_status(self, val):
3902 self._last_merge_status = val
3902 self._last_merge_status = val
3903
3903
3904 @declared_attr
3904 @declared_attr
3905 def author(cls):
3905 def author(cls):
3906 return relationship('User', lazy='joined')
3906 return relationship('User', lazy='joined')
3907
3907
3908 @declared_attr
3908 @declared_attr
3909 def source_repo(cls):
3909 def source_repo(cls):
3910 return relationship(
3910 return relationship(
3911 'Repository',
3911 'Repository',
3912 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3912 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3913
3913
3914 @property
3914 @property
3915 def source_ref_parts(self):
3915 def source_ref_parts(self):
3916 return self.unicode_to_reference(self.source_ref)
3916 return self.unicode_to_reference(self.source_ref)
3917
3917
3918 @declared_attr
3918 @declared_attr
3919 def target_repo(cls):
3919 def target_repo(cls):
3920 return relationship(
3920 return relationship(
3921 'Repository',
3921 'Repository',
3922 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3922 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3923
3923
3924 @property
3924 @property
3925 def target_ref_parts(self):
3925 def target_ref_parts(self):
3926 return self.unicode_to_reference(self.target_ref)
3926 return self.unicode_to_reference(self.target_ref)
3927
3927
3928 @property
3928 @property
3929 def shadow_merge_ref(self):
3929 def shadow_merge_ref(self):
3930 return self.unicode_to_reference(self._shadow_merge_ref)
3930 return self.unicode_to_reference(self._shadow_merge_ref)
3931
3931
3932 @shadow_merge_ref.setter
3932 @shadow_merge_ref.setter
3933 def shadow_merge_ref(self, ref):
3933 def shadow_merge_ref(self, ref):
3934 self._shadow_merge_ref = self.reference_to_unicode(ref)
3934 self._shadow_merge_ref = self.reference_to_unicode(ref)
3935
3935
3936 @staticmethod
3936 @staticmethod
3937 def unicode_to_reference(raw):
3937 def unicode_to_reference(raw):
3938 """
3938 """
3939 Convert a unicode (or string) to a reference object.
3939 Convert a unicode (or string) to a reference object.
3940 If unicode evaluates to False it returns None.
3940 If unicode evaluates to False it returns None.
3941 """
3941 """
3942 if raw:
3942 if raw:
3943 refs = raw.split(':')
3943 refs = raw.split(':')
3944 return Reference(*refs)
3944 return Reference(*refs)
3945 else:
3945 else:
3946 return None
3946 return None
3947
3947
3948 @staticmethod
3948 @staticmethod
3949 def reference_to_unicode(ref):
3949 def reference_to_unicode(ref):
3950 """
3950 """
3951 Convert a reference object to unicode.
3951 Convert a reference object to unicode.
3952 If reference is None it returns None.
3952 If reference is None it returns None.
3953 """
3953 """
3954 if ref:
3954 if ref:
3955 return u':'.join(ref)
3955 return u':'.join(ref)
3956 else:
3956 else:
3957 return None
3957 return None
3958
3958
3959 def get_api_data(self, with_merge_state=True):
3959 def get_api_data(self, with_merge_state=True):
3960 from rhodecode.model.pull_request import PullRequestModel
3960 from rhodecode.model.pull_request import PullRequestModel
3961
3961
3962 pull_request = self
3962 pull_request = self
3963 if with_merge_state:
3963 if with_merge_state:
3964 merge_status = PullRequestModel().merge_status(pull_request)
3964 merge_status = PullRequestModel().merge_status(pull_request)
3965 merge_state = {
3965 merge_state = {
3966 'status': merge_status[0],
3966 'status': merge_status[0],
3967 'message': safe_unicode(merge_status[1]),
3967 'message': safe_unicode(merge_status[1]),
3968 }
3968 }
3969 else:
3969 else:
3970 merge_state = {'status': 'not_available',
3970 merge_state = {'status': 'not_available',
3971 'message': 'not_available'}
3971 'message': 'not_available'}
3972
3972
3973 merge_data = {
3973 merge_data = {
3974 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3974 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3975 'reference': (
3975 'reference': (
3976 pull_request.shadow_merge_ref._asdict()
3976 pull_request.shadow_merge_ref._asdict()
3977 if pull_request.shadow_merge_ref else None),
3977 if pull_request.shadow_merge_ref else None),
3978 }
3978 }
3979
3979
3980 data = {
3980 data = {
3981 'pull_request_id': pull_request.pull_request_id,
3981 'pull_request_id': pull_request.pull_request_id,
3982 'url': PullRequestModel().get_url(pull_request),
3982 'url': PullRequestModel().get_url(pull_request),
3983 'title': pull_request.title,
3983 'title': pull_request.title,
3984 'description': pull_request.description,
3984 'description': pull_request.description,
3985 'status': pull_request.status,
3985 'status': pull_request.status,
3986 'state': pull_request.pull_request_state,
3986 'state': pull_request.pull_request_state,
3987 'created_on': pull_request.created_on,
3987 'created_on': pull_request.created_on,
3988 'updated_on': pull_request.updated_on,
3988 'updated_on': pull_request.updated_on,
3989 'commit_ids': pull_request.revisions,
3989 'commit_ids': pull_request.revisions,
3990 'review_status': pull_request.calculated_review_status(),
3990 'review_status': pull_request.calculated_review_status(),
3991 'mergeable': merge_state,
3991 'mergeable': merge_state,
3992 'source': {
3992 'source': {
3993 'clone_url': pull_request.source_repo.clone_url(),
3993 'clone_url': pull_request.source_repo.clone_url(),
3994 'repository': pull_request.source_repo.repo_name,
3994 'repository': pull_request.source_repo.repo_name,
3995 'reference': {
3995 'reference': {
3996 'name': pull_request.source_ref_parts.name,
3996 'name': pull_request.source_ref_parts.name,
3997 'type': pull_request.source_ref_parts.type,
3997 'type': pull_request.source_ref_parts.type,
3998 'commit_id': pull_request.source_ref_parts.commit_id,
3998 'commit_id': pull_request.source_ref_parts.commit_id,
3999 },
3999 },
4000 },
4000 },
4001 'target': {
4001 'target': {
4002 'clone_url': pull_request.target_repo.clone_url(),
4002 'clone_url': pull_request.target_repo.clone_url(),
4003 'repository': pull_request.target_repo.repo_name,
4003 'repository': pull_request.target_repo.repo_name,
4004 'reference': {
4004 'reference': {
4005 'name': pull_request.target_ref_parts.name,
4005 'name': pull_request.target_ref_parts.name,
4006 'type': pull_request.target_ref_parts.type,
4006 'type': pull_request.target_ref_parts.type,
4007 'commit_id': pull_request.target_ref_parts.commit_id,
4007 'commit_id': pull_request.target_ref_parts.commit_id,
4008 },
4008 },
4009 },
4009 },
4010 'merge': merge_data,
4010 'merge': merge_data,
4011 'author': pull_request.author.get_api_data(include_secrets=False,
4011 'author': pull_request.author.get_api_data(include_secrets=False,
4012 details='basic'),
4012 details='basic'),
4013 'reviewers': [
4013 'reviewers': [
4014 {
4014 {
4015 'user': reviewer.get_api_data(include_secrets=False,
4015 'user': reviewer.get_api_data(include_secrets=False,
4016 details='basic'),
4016 details='basic'),
4017 'reasons': reasons,
4017 'reasons': reasons,
4018 'review_status': st[0][1].status if st else 'not_reviewed',
4018 'review_status': st[0][1].status if st else 'not_reviewed',
4019 }
4019 }
4020 for obj, reviewer, reasons, mandatory, st in
4020 for obj, reviewer, reasons, mandatory, st in
4021 pull_request.reviewers_statuses()
4021 pull_request.reviewers_statuses()
4022 ]
4022 ]
4023 }
4023 }
4024
4024
4025 return data
4025 return data
4026
4026
4027 def set_state(self, pull_request_state, final_state=None):
4027 def set_state(self, pull_request_state, final_state=None):
4028 """
4028 """
4029 # goes from initial state to updating to initial state.
4029 # goes from initial state to updating to initial state.
4030 # initial state can be changed by specifying back_state=
4030 # initial state can be changed by specifying back_state=
4031 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4031 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4032 pull_request.merge()
4032 pull_request.merge()
4033
4033
4034 :param pull_request_state:
4034 :param pull_request_state:
4035 :param final_state:
4035 :param final_state:
4036
4036
4037 """
4037 """
4038
4038
4039 return _SetState(self, pull_request_state, back_state=final_state)
4039 return _SetState(self, pull_request_state, back_state=final_state)
4040
4040
4041
4041
4042 class PullRequest(Base, _PullRequestBase):
4042 class PullRequest(Base, _PullRequestBase):
4043 __tablename__ = 'pull_requests'
4043 __tablename__ = 'pull_requests'
4044 __table_args__ = (
4044 __table_args__ = (
4045 base_table_args,
4045 base_table_args,
4046 )
4046 )
4047
4047
4048 pull_request_id = Column(
4048 pull_request_id = Column(
4049 'pull_request_id', Integer(), nullable=False, primary_key=True)
4049 'pull_request_id', Integer(), nullable=False, primary_key=True)
4050
4050
4051 def __repr__(self):
4051 def __repr__(self):
4052 if self.pull_request_id:
4052 if self.pull_request_id:
4053 return '<DB:PullRequest #%s>' % self.pull_request_id
4053 return '<DB:PullRequest #%s>' % self.pull_request_id
4054 else:
4054 else:
4055 return '<DB:PullRequest at %#x>' % id(self)
4055 return '<DB:PullRequest at %#x>' % id(self)
4056
4056
4057 reviewers = relationship('PullRequestReviewers',
4057 reviewers = relationship('PullRequestReviewers',
4058 cascade="all, delete, delete-orphan")
4058 cascade="all, delete, delete-orphan")
4059 statuses = relationship('ChangesetStatus',
4059 statuses = relationship('ChangesetStatus',
4060 cascade="all, delete, delete-orphan")
4060 cascade="all, delete, delete-orphan")
4061 comments = relationship('ChangesetComment',
4061 comments = relationship('ChangesetComment',
4062 cascade="all, delete, delete-orphan")
4062 cascade="all, delete, delete-orphan")
4063 versions = relationship('PullRequestVersion',
4063 versions = relationship('PullRequestVersion',
4064 cascade="all, delete, delete-orphan",
4064 cascade="all, delete, delete-orphan",
4065 lazy='dynamic')
4065 lazy='dynamic')
4066
4066
4067 @classmethod
4067 @classmethod
4068 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4068 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4069 internal_methods=None):
4069 internal_methods=None):
4070
4070
4071 class PullRequestDisplay(object):
4071 class PullRequestDisplay(object):
4072 """
4072 """
4073 Special object wrapper for showing PullRequest data via Versions
4073 Special object wrapper for showing PullRequest data via Versions
4074 It mimics PR object as close as possible. This is read only object
4074 It mimics PR object as close as possible. This is read only object
4075 just for display
4075 just for display
4076 """
4076 """
4077
4077
4078 def __init__(self, attrs, internal=None):
4078 def __init__(self, attrs, internal=None):
4079 self.attrs = attrs
4079 self.attrs = attrs
4080 # internal have priority over the given ones via attrs
4080 # internal have priority over the given ones via attrs
4081 self.internal = internal or ['versions']
4081 self.internal = internal or ['versions']
4082
4082
4083 def __getattr__(self, item):
4083 def __getattr__(self, item):
4084 if item in self.internal:
4084 if item in self.internal:
4085 return getattr(self, item)
4085 return getattr(self, item)
4086 try:
4086 try:
4087 return self.attrs[item]
4087 return self.attrs[item]
4088 except KeyError:
4088 except KeyError:
4089 raise AttributeError(
4089 raise AttributeError(
4090 '%s object has no attribute %s' % (self, item))
4090 '%s object has no attribute %s' % (self, item))
4091
4091
4092 def __repr__(self):
4092 def __repr__(self):
4093 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4093 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4094
4094
4095 def versions(self):
4095 def versions(self):
4096 return pull_request_obj.versions.order_by(
4096 return pull_request_obj.versions.order_by(
4097 PullRequestVersion.pull_request_version_id).all()
4097 PullRequestVersion.pull_request_version_id).all()
4098
4098
4099 def is_closed(self):
4099 def is_closed(self):
4100 return pull_request_obj.is_closed()
4100 return pull_request_obj.is_closed()
4101
4101
4102 @property
4102 @property
4103 def pull_request_version_id(self):
4103 def pull_request_version_id(self):
4104 return getattr(pull_request_obj, 'pull_request_version_id', None)
4104 return getattr(pull_request_obj, 'pull_request_version_id', None)
4105
4105
4106 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
4106 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
4107
4107
4108 attrs.author = StrictAttributeDict(
4108 attrs.author = StrictAttributeDict(
4109 pull_request_obj.author.get_api_data())
4109 pull_request_obj.author.get_api_data())
4110 if pull_request_obj.target_repo:
4110 if pull_request_obj.target_repo:
4111 attrs.target_repo = StrictAttributeDict(
4111 attrs.target_repo = StrictAttributeDict(
4112 pull_request_obj.target_repo.get_api_data())
4112 pull_request_obj.target_repo.get_api_data())
4113 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4113 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4114
4114
4115 if pull_request_obj.source_repo:
4115 if pull_request_obj.source_repo:
4116 attrs.source_repo = StrictAttributeDict(
4116 attrs.source_repo = StrictAttributeDict(
4117 pull_request_obj.source_repo.get_api_data())
4117 pull_request_obj.source_repo.get_api_data())
4118 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4118 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4119
4119
4120 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4120 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4121 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4121 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4122 attrs.revisions = pull_request_obj.revisions
4122 attrs.revisions = pull_request_obj.revisions
4123
4123
4124 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4124 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4125 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4125 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4126 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4126 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4127
4127
4128 return PullRequestDisplay(attrs, internal=internal_methods)
4128 return PullRequestDisplay(attrs, internal=internal_methods)
4129
4129
4130 def is_closed(self):
4130 def is_closed(self):
4131 return self.status == self.STATUS_CLOSED
4131 return self.status == self.STATUS_CLOSED
4132
4132
4133 def __json__(self):
4133 def __json__(self):
4134 return {
4134 return {
4135 'revisions': self.revisions,
4135 'revisions': self.revisions,
4136 }
4136 }
4137
4137
4138 def calculated_review_status(self):
4138 def calculated_review_status(self):
4139 from rhodecode.model.changeset_status import ChangesetStatusModel
4139 from rhodecode.model.changeset_status import ChangesetStatusModel
4140 return ChangesetStatusModel().calculated_review_status(self)
4140 return ChangesetStatusModel().calculated_review_status(self)
4141
4141
4142 def reviewers_statuses(self):
4142 def reviewers_statuses(self):
4143 from rhodecode.model.changeset_status import ChangesetStatusModel
4143 from rhodecode.model.changeset_status import ChangesetStatusModel
4144 return ChangesetStatusModel().reviewers_statuses(self)
4144 return ChangesetStatusModel().reviewers_statuses(self)
4145
4145
4146 @property
4146 @property
4147 def workspace_id(self):
4147 def workspace_id(self):
4148 from rhodecode.model.pull_request import PullRequestModel
4148 from rhodecode.model.pull_request import PullRequestModel
4149 return PullRequestModel()._workspace_id(self)
4149 return PullRequestModel()._workspace_id(self)
4150
4150
4151 def get_shadow_repo(self):
4151 def get_shadow_repo(self):
4152 workspace_id = self.workspace_id
4152 workspace_id = self.workspace_id
4153 vcs_obj = self.target_repo.scm_instance()
4153 vcs_obj = self.target_repo.scm_instance()
4154 shadow_repository_path = vcs_obj._get_shadow_repository_path(
4154 shadow_repository_path = vcs_obj._get_shadow_repository_path(
4155 self.target_repo.repo_id, workspace_id)
4155 self.target_repo.repo_id, workspace_id)
4156 if os.path.isdir(shadow_repository_path):
4156 if os.path.isdir(shadow_repository_path):
4157 return vcs_obj._get_shadow_instance(shadow_repository_path)
4157 return vcs_obj._get_shadow_instance(shadow_repository_path)
4158
4158
4159
4159
4160 class PullRequestVersion(Base, _PullRequestBase):
4160 class PullRequestVersion(Base, _PullRequestBase):
4161 __tablename__ = 'pull_request_versions'
4161 __tablename__ = 'pull_request_versions'
4162 __table_args__ = (
4162 __table_args__ = (
4163 base_table_args,
4163 base_table_args,
4164 )
4164 )
4165
4165
4166 pull_request_version_id = Column(
4166 pull_request_version_id = Column(
4167 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4167 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4168 pull_request_id = Column(
4168 pull_request_id = Column(
4169 'pull_request_id', Integer(),
4169 'pull_request_id', Integer(),
4170 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4170 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4171 pull_request = relationship('PullRequest')
4171 pull_request = relationship('PullRequest')
4172
4172
4173 def __repr__(self):
4173 def __repr__(self):
4174 if self.pull_request_version_id:
4174 if self.pull_request_version_id:
4175 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4175 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4176 else:
4176 else:
4177 return '<DB:PullRequestVersion at %#x>' % id(self)
4177 return '<DB:PullRequestVersion at %#x>' % id(self)
4178
4178
4179 @property
4179 @property
4180 def reviewers(self):
4180 def reviewers(self):
4181 return self.pull_request.reviewers
4181 return self.pull_request.reviewers
4182
4182
4183 @property
4183 @property
4184 def versions(self):
4184 def versions(self):
4185 return self.pull_request.versions
4185 return self.pull_request.versions
4186
4186
4187 def is_closed(self):
4187 def is_closed(self):
4188 # calculate from original
4188 # calculate from original
4189 return self.pull_request.status == self.STATUS_CLOSED
4189 return self.pull_request.status == self.STATUS_CLOSED
4190
4190
4191 def calculated_review_status(self):
4191 def calculated_review_status(self):
4192 return self.pull_request.calculated_review_status()
4192 return self.pull_request.calculated_review_status()
4193
4193
4194 def reviewers_statuses(self):
4194 def reviewers_statuses(self):
4195 return self.pull_request.reviewers_statuses()
4195 return self.pull_request.reviewers_statuses()
4196
4196
4197
4197
4198 class PullRequestReviewers(Base, BaseModel):
4198 class PullRequestReviewers(Base, BaseModel):
4199 __tablename__ = 'pull_request_reviewers'
4199 __tablename__ = 'pull_request_reviewers'
4200 __table_args__ = (
4200 __table_args__ = (
4201 base_table_args,
4201 base_table_args,
4202 )
4202 )
4203
4203
4204 @hybrid_property
4204 @hybrid_property
4205 def reasons(self):
4205 def reasons(self):
4206 if not self._reasons:
4206 if not self._reasons:
4207 return []
4207 return []
4208 return self._reasons
4208 return self._reasons
4209
4209
4210 @reasons.setter
4210 @reasons.setter
4211 def reasons(self, val):
4211 def reasons(self, val):
4212 val = val or []
4212 val = val or []
4213 if any(not isinstance(x, compat.string_types) for x in val):
4213 if any(not isinstance(x, compat.string_types) for x in val):
4214 raise Exception('invalid reasons type, must be list of strings')
4214 raise Exception('invalid reasons type, must be list of strings')
4215 self._reasons = val
4215 self._reasons = val
4216
4216
4217 pull_requests_reviewers_id = Column(
4217 pull_requests_reviewers_id = Column(
4218 'pull_requests_reviewers_id', Integer(), nullable=False,
4218 'pull_requests_reviewers_id', Integer(), nullable=False,
4219 primary_key=True)
4219 primary_key=True)
4220 pull_request_id = Column(
4220 pull_request_id = Column(
4221 "pull_request_id", Integer(),
4221 "pull_request_id", Integer(),
4222 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4222 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4223 user_id = Column(
4223 user_id = Column(
4224 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4224 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4225 _reasons = Column(
4225 _reasons = Column(
4226 'reason', MutationList.as_mutable(
4226 'reason', MutationList.as_mutable(
4227 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4227 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4228
4228
4229 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4229 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4230 user = relationship('User')
4230 user = relationship('User')
4231 pull_request = relationship('PullRequest')
4231 pull_request = relationship('PullRequest')
4232
4232
4233 rule_data = Column(
4233 rule_data = Column(
4234 'rule_data_json',
4234 'rule_data_json',
4235 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4235 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4236
4236
4237 def rule_user_group_data(self):
4237 def rule_user_group_data(self):
4238 """
4238 """
4239 Returns the voting user group rule data for this reviewer
4239 Returns the voting user group rule data for this reviewer
4240 """
4240 """
4241
4241
4242 if self.rule_data and 'vote_rule' in self.rule_data:
4242 if self.rule_data and 'vote_rule' in self.rule_data:
4243 user_group_data = {}
4243 user_group_data = {}
4244 if 'rule_user_group_entry_id' in self.rule_data:
4244 if 'rule_user_group_entry_id' in self.rule_data:
4245 # means a group with voting rules !
4245 # means a group with voting rules !
4246 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4246 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4247 user_group_data['name'] = self.rule_data['rule_name']
4247 user_group_data['name'] = self.rule_data['rule_name']
4248 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4248 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4249
4249
4250 return user_group_data
4250 return user_group_data
4251
4251
4252 def __unicode__(self):
4252 def __unicode__(self):
4253 return u"<%s('id:%s')>" % (self.__class__.__name__,
4253 return u"<%s('id:%s')>" % (self.__class__.__name__,
4254 self.pull_requests_reviewers_id)
4254 self.pull_requests_reviewers_id)
4255
4255
4256
4256
4257 class Notification(Base, BaseModel):
4257 class Notification(Base, BaseModel):
4258 __tablename__ = 'notifications'
4258 __tablename__ = 'notifications'
4259 __table_args__ = (
4259 __table_args__ = (
4260 Index('notification_type_idx', 'type'),
4260 Index('notification_type_idx', 'type'),
4261 base_table_args,
4261 base_table_args,
4262 )
4262 )
4263
4263
4264 TYPE_CHANGESET_COMMENT = u'cs_comment'
4264 TYPE_CHANGESET_COMMENT = u'cs_comment'
4265 TYPE_MESSAGE = u'message'
4265 TYPE_MESSAGE = u'message'
4266 TYPE_MENTION = u'mention'
4266 TYPE_MENTION = u'mention'
4267 TYPE_REGISTRATION = u'registration'
4267 TYPE_REGISTRATION = u'registration'
4268 TYPE_PULL_REQUEST = u'pull_request'
4268 TYPE_PULL_REQUEST = u'pull_request'
4269 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4269 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4270
4270
4271 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4271 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4272 subject = Column('subject', Unicode(512), nullable=True)
4272 subject = Column('subject', Unicode(512), nullable=True)
4273 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4273 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4274 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4274 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4275 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4275 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4276 type_ = Column('type', Unicode(255))
4276 type_ = Column('type', Unicode(255))
4277
4277
4278 created_by_user = relationship('User')
4278 created_by_user = relationship('User')
4279 notifications_to_users = relationship('UserNotification', lazy='joined',
4279 notifications_to_users = relationship('UserNotification', lazy='joined',
4280 cascade="all, delete, delete-orphan")
4280 cascade="all, delete, delete-orphan")
4281
4281
4282 @property
4282 @property
4283 def recipients(self):
4283 def recipients(self):
4284 return [x.user for x in UserNotification.query()\
4284 return [x.user for x in UserNotification.query()\
4285 .filter(UserNotification.notification == self)\
4285 .filter(UserNotification.notification == self)\
4286 .order_by(UserNotification.user_id.asc()).all()]
4286 .order_by(UserNotification.user_id.asc()).all()]
4287
4287
4288 @classmethod
4288 @classmethod
4289 def create(cls, created_by, subject, body, recipients, type_=None):
4289 def create(cls, created_by, subject, body, recipients, type_=None):
4290 if type_ is None:
4290 if type_ is None:
4291 type_ = Notification.TYPE_MESSAGE
4291 type_ = Notification.TYPE_MESSAGE
4292
4292
4293 notification = cls()
4293 notification = cls()
4294 notification.created_by_user = created_by
4294 notification.created_by_user = created_by
4295 notification.subject = subject
4295 notification.subject = subject
4296 notification.body = body
4296 notification.body = body
4297 notification.type_ = type_
4297 notification.type_ = type_
4298 notification.created_on = datetime.datetime.now()
4298 notification.created_on = datetime.datetime.now()
4299
4299
4300 # For each recipient link the created notification to his account
4300 # For each recipient link the created notification to his account
4301 for u in recipients:
4301 for u in recipients:
4302 assoc = UserNotification()
4302 assoc = UserNotification()
4303 assoc.user_id = u.user_id
4303 assoc.user_id = u.user_id
4304 assoc.notification = notification
4304 assoc.notification = notification
4305
4305
4306 # if created_by is inside recipients mark his notification
4306 # if created_by is inside recipients mark his notification
4307 # as read
4307 # as read
4308 if u.user_id == created_by.user_id:
4308 if u.user_id == created_by.user_id:
4309 assoc.read = True
4309 assoc.read = True
4310 Session().add(assoc)
4310 Session().add(assoc)
4311
4311
4312 Session().add(notification)
4312 Session().add(notification)
4313
4313
4314 return notification
4314 return notification
4315
4315
4316
4316
4317 class UserNotification(Base, BaseModel):
4317 class UserNotification(Base, BaseModel):
4318 __tablename__ = 'user_to_notification'
4318 __tablename__ = 'user_to_notification'
4319 __table_args__ = (
4319 __table_args__ = (
4320 UniqueConstraint('user_id', 'notification_id'),
4320 UniqueConstraint('user_id', 'notification_id'),
4321 base_table_args
4321 base_table_args
4322 )
4322 )
4323
4323
4324 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4324 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4325 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4325 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4326 read = Column('read', Boolean, default=False)
4326 read = Column('read', Boolean, default=False)
4327 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4327 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4328
4328
4329 user = relationship('User', lazy="joined")
4329 user = relationship('User', lazy="joined")
4330 notification = relationship('Notification', lazy="joined",
4330 notification = relationship('Notification', lazy="joined",
4331 order_by=lambda: Notification.created_on.desc(),)
4331 order_by=lambda: Notification.created_on.desc(),)
4332
4332
4333 def mark_as_read(self):
4333 def mark_as_read(self):
4334 self.read = True
4334 self.read = True
4335 Session().add(self)
4335 Session().add(self)
4336
4336
4337
4337
4338 class Gist(Base, BaseModel):
4338 class Gist(Base, BaseModel):
4339 __tablename__ = 'gists'
4339 __tablename__ = 'gists'
4340 __table_args__ = (
4340 __table_args__ = (
4341 Index('g_gist_access_id_idx', 'gist_access_id'),
4341 Index('g_gist_access_id_idx', 'gist_access_id'),
4342 Index('g_created_on_idx', 'created_on'),
4342 Index('g_created_on_idx', 'created_on'),
4343 base_table_args
4343 base_table_args
4344 )
4344 )
4345
4345
4346 GIST_PUBLIC = u'public'
4346 GIST_PUBLIC = u'public'
4347 GIST_PRIVATE = u'private'
4347 GIST_PRIVATE = u'private'
4348 DEFAULT_FILENAME = u'gistfile1.txt'
4348 DEFAULT_FILENAME = u'gistfile1.txt'
4349
4349
4350 ACL_LEVEL_PUBLIC = u'acl_public'
4350 ACL_LEVEL_PUBLIC = u'acl_public'
4351 ACL_LEVEL_PRIVATE = u'acl_private'
4351 ACL_LEVEL_PRIVATE = u'acl_private'
4352
4352
4353 gist_id = Column('gist_id', Integer(), primary_key=True)
4353 gist_id = Column('gist_id', Integer(), primary_key=True)
4354 gist_access_id = Column('gist_access_id', Unicode(250))
4354 gist_access_id = Column('gist_access_id', Unicode(250))
4355 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4355 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4356 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4356 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4357 gist_expires = Column('gist_expires', Float(53), nullable=False)
4357 gist_expires = Column('gist_expires', Float(53), nullable=False)
4358 gist_type = Column('gist_type', Unicode(128), nullable=False)
4358 gist_type = Column('gist_type', Unicode(128), nullable=False)
4359 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4359 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4360 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4360 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4361 acl_level = Column('acl_level', Unicode(128), nullable=True)
4361 acl_level = Column('acl_level', Unicode(128), nullable=True)
4362
4362
4363 owner = relationship('User')
4363 owner = relationship('User')
4364
4364
4365 def __repr__(self):
4365 def __repr__(self):
4366 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4366 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4367
4367
4368 @hybrid_property
4368 @hybrid_property
4369 def description_safe(self):
4369 def description_safe(self):
4370 from rhodecode.lib import helpers as h
4370 from rhodecode.lib import helpers as h
4371 return h.escape(self.gist_description)
4371 return h.escape(self.gist_description)
4372
4372
4373 @classmethod
4373 @classmethod
4374 def get_or_404(cls, id_):
4374 def get_or_404(cls, id_):
4375 from pyramid.httpexceptions import HTTPNotFound
4375 from pyramid.httpexceptions import HTTPNotFound
4376
4376
4377 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4377 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4378 if not res:
4378 if not res:
4379 raise HTTPNotFound()
4379 raise HTTPNotFound()
4380 return res
4380 return res
4381
4381
4382 @classmethod
4382 @classmethod
4383 def get_by_access_id(cls, gist_access_id):
4383 def get_by_access_id(cls, gist_access_id):
4384 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4384 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4385
4385
4386 def gist_url(self):
4386 def gist_url(self):
4387 from rhodecode.model.gist import GistModel
4387 from rhodecode.model.gist import GistModel
4388 return GistModel().get_url(self)
4388 return GistModel().get_url(self)
4389
4389
4390 @classmethod
4390 @classmethod
4391 def base_path(cls):
4391 def base_path(cls):
4392 """
4392 """
4393 Returns base path when all gists are stored
4393 Returns base path when all gists are stored
4394
4394
4395 :param cls:
4395 :param cls:
4396 """
4396 """
4397 from rhodecode.model.gist import GIST_STORE_LOC
4397 from rhodecode.model.gist import GIST_STORE_LOC
4398 q = Session().query(RhodeCodeUi)\
4398 q = Session().query(RhodeCodeUi)\
4399 .filter(RhodeCodeUi.ui_key == URL_SEP)
4399 .filter(RhodeCodeUi.ui_key == URL_SEP)
4400 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4400 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4401 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4401 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4402
4402
4403 def get_api_data(self):
4403 def get_api_data(self):
4404 """
4404 """
4405 Common function for generating gist related data for API
4405 Common function for generating gist related data for API
4406 """
4406 """
4407 gist = self
4407 gist = self
4408 data = {
4408 data = {
4409 'gist_id': gist.gist_id,
4409 'gist_id': gist.gist_id,
4410 'type': gist.gist_type,
4410 'type': gist.gist_type,
4411 'access_id': gist.gist_access_id,
4411 'access_id': gist.gist_access_id,
4412 'description': gist.gist_description,
4412 'description': gist.gist_description,
4413 'url': gist.gist_url(),
4413 'url': gist.gist_url(),
4414 'expires': gist.gist_expires,
4414 'expires': gist.gist_expires,
4415 'created_on': gist.created_on,
4415 'created_on': gist.created_on,
4416 'modified_at': gist.modified_at,
4416 'modified_at': gist.modified_at,
4417 'content': None,
4417 'content': None,
4418 'acl_level': gist.acl_level,
4418 'acl_level': gist.acl_level,
4419 }
4419 }
4420 return data
4420 return data
4421
4421
4422 def __json__(self):
4422 def __json__(self):
4423 data = dict(
4423 data = dict(
4424 )
4424 )
4425 data.update(self.get_api_data())
4425 data.update(self.get_api_data())
4426 return data
4426 return data
4427 # SCM functions
4427 # SCM functions
4428
4428
4429 def scm_instance(self, **kwargs):
4429 def scm_instance(self, **kwargs):
4430 """
4430 """
4431 Get explicit Mercurial repository used
4431 Get explicit Mercurial repository used
4432 :param kwargs:
4432 :param kwargs:
4433 :return:
4433 :return:
4434 """
4434 """
4435 from rhodecode.model.gist import GistModel
4435 from rhodecode.model.gist import GistModel
4436 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4436 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4437 return get_vcs_instance(
4437 return get_vcs_instance(
4438 repo_path=safe_str(full_repo_path), create=False,
4438 repo_path=safe_str(full_repo_path), create=False,
4439 _vcs_alias=GistModel.vcs_backend)
4439 _vcs_alias=GistModel.vcs_backend)
4440
4440
4441
4441
4442 class ExternalIdentity(Base, BaseModel):
4442 class ExternalIdentity(Base, BaseModel):
4443 __tablename__ = 'external_identities'
4443 __tablename__ = 'external_identities'
4444 __table_args__ = (
4444 __table_args__ = (
4445 Index('local_user_id_idx', 'local_user_id'),
4445 Index('local_user_id_idx', 'local_user_id'),
4446 Index('external_id_idx', 'external_id'),
4446 Index('external_id_idx', 'external_id'),
4447 base_table_args
4447 base_table_args
4448 )
4448 )
4449
4449
4450 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4450 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4451 external_username = Column('external_username', Unicode(1024), default=u'')
4451 external_username = Column('external_username', Unicode(1024), default=u'')
4452 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4452 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4453 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4453 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4454 access_token = Column('access_token', String(1024), default=u'')
4454 access_token = Column('access_token', String(1024), default=u'')
4455 alt_token = Column('alt_token', String(1024), default=u'')
4455 alt_token = Column('alt_token', String(1024), default=u'')
4456 token_secret = Column('token_secret', String(1024), default=u'')
4456 token_secret = Column('token_secret', String(1024), default=u'')
4457
4457
4458 @classmethod
4458 @classmethod
4459 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4459 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4460 """
4460 """
4461 Returns ExternalIdentity instance based on search params
4461 Returns ExternalIdentity instance based on search params
4462
4462
4463 :param external_id:
4463 :param external_id:
4464 :param provider_name:
4464 :param provider_name:
4465 :return: ExternalIdentity
4465 :return: ExternalIdentity
4466 """
4466 """
4467 query = cls.query()
4467 query = cls.query()
4468 query = query.filter(cls.external_id == external_id)
4468 query = query.filter(cls.external_id == external_id)
4469 query = query.filter(cls.provider_name == provider_name)
4469 query = query.filter(cls.provider_name == provider_name)
4470 if local_user_id:
4470 if local_user_id:
4471 query = query.filter(cls.local_user_id == local_user_id)
4471 query = query.filter(cls.local_user_id == local_user_id)
4472 return query.first()
4472 return query.first()
4473
4473
4474 @classmethod
4474 @classmethod
4475 def user_by_external_id_and_provider(cls, external_id, provider_name):
4475 def user_by_external_id_and_provider(cls, external_id, provider_name):
4476 """
4476 """
4477 Returns User instance based on search params
4477 Returns User instance based on search params
4478
4478
4479 :param external_id:
4479 :param external_id:
4480 :param provider_name:
4480 :param provider_name:
4481 :return: User
4481 :return: User
4482 """
4482 """
4483 query = User.query()
4483 query = User.query()
4484 query = query.filter(cls.external_id == external_id)
4484 query = query.filter(cls.external_id == external_id)
4485 query = query.filter(cls.provider_name == provider_name)
4485 query = query.filter(cls.provider_name == provider_name)
4486 query = query.filter(User.user_id == cls.local_user_id)
4486 query = query.filter(User.user_id == cls.local_user_id)
4487 return query.first()
4487 return query.first()
4488
4488
4489 @classmethod
4489 @classmethod
4490 def by_local_user_id(cls, local_user_id):
4490 def by_local_user_id(cls, local_user_id):
4491 """
4491 """
4492 Returns all tokens for user
4492 Returns all tokens for user
4493
4493
4494 :param local_user_id:
4494 :param local_user_id:
4495 :return: ExternalIdentity
4495 :return: ExternalIdentity
4496 """
4496 """
4497 query = cls.query()
4497 query = cls.query()
4498 query = query.filter(cls.local_user_id == local_user_id)
4498 query = query.filter(cls.local_user_id == local_user_id)
4499 return query
4499 return query
4500
4500
4501 @classmethod
4501 @classmethod
4502 def load_provider_plugin(cls, plugin_id):
4502 def load_provider_plugin(cls, plugin_id):
4503 from rhodecode.authentication.base import loadplugin
4503 from rhodecode.authentication.base import loadplugin
4504 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4504 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4505 auth_plugin = loadplugin(_plugin_id)
4505 auth_plugin = loadplugin(_plugin_id)
4506 return auth_plugin
4506 return auth_plugin
4507
4507
4508
4508
4509 class Integration(Base, BaseModel):
4509 class Integration(Base, BaseModel):
4510 __tablename__ = 'integrations'
4510 __tablename__ = 'integrations'
4511 __table_args__ = (
4511 __table_args__ = (
4512 base_table_args
4512 base_table_args
4513 )
4513 )
4514
4514
4515 integration_id = Column('integration_id', Integer(), primary_key=True)
4515 integration_id = Column('integration_id', Integer(), primary_key=True)
4516 integration_type = Column('integration_type', String(255))
4516 integration_type = Column('integration_type', String(255))
4517 enabled = Column('enabled', Boolean(), nullable=False)
4517 enabled = Column('enabled', Boolean(), nullable=False)
4518 name = Column('name', String(255), nullable=False)
4518 name = Column('name', String(255), nullable=False)
4519 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4519 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4520 default=False)
4520 default=False)
4521
4521
4522 settings = Column(
4522 settings = Column(
4523 'settings_json', MutationObj.as_mutable(
4523 'settings_json', MutationObj.as_mutable(
4524 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4524 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4525 repo_id = Column(
4525 repo_id = Column(
4526 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4526 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4527 nullable=True, unique=None, default=None)
4527 nullable=True, unique=None, default=None)
4528 repo = relationship('Repository', lazy='joined')
4528 repo = relationship('Repository', lazy='joined')
4529
4529
4530 repo_group_id = Column(
4530 repo_group_id = Column(
4531 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4531 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4532 nullable=True, unique=None, default=None)
4532 nullable=True, unique=None, default=None)
4533 repo_group = relationship('RepoGroup', lazy='joined')
4533 repo_group = relationship('RepoGroup', lazy='joined')
4534
4534
4535 @property
4535 @property
4536 def scope(self):
4536 def scope(self):
4537 if self.repo:
4537 if self.repo:
4538 return repr(self.repo)
4538 return repr(self.repo)
4539 if self.repo_group:
4539 if self.repo_group:
4540 if self.child_repos_only:
4540 if self.child_repos_only:
4541 return repr(self.repo_group) + ' (child repos only)'
4541 return repr(self.repo_group) + ' (child repos only)'
4542 else:
4542 else:
4543 return repr(self.repo_group) + ' (recursive)'
4543 return repr(self.repo_group) + ' (recursive)'
4544 if self.child_repos_only:
4544 if self.child_repos_only:
4545 return 'root_repos'
4545 return 'root_repos'
4546 return 'global'
4546 return 'global'
4547
4547
4548 def __repr__(self):
4548 def __repr__(self):
4549 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4549 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4550
4550
4551
4551
4552 class RepoReviewRuleUser(Base, BaseModel):
4552 class RepoReviewRuleUser(Base, BaseModel):
4553 __tablename__ = 'repo_review_rules_users'
4553 __tablename__ = 'repo_review_rules_users'
4554 __table_args__ = (
4554 __table_args__ = (
4555 base_table_args
4555 base_table_args
4556 )
4556 )
4557
4557
4558 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4558 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4559 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4559 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4560 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4560 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4561 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4561 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4562 user = relationship('User')
4562 user = relationship('User')
4563
4563
4564 def rule_data(self):
4564 def rule_data(self):
4565 return {
4565 return {
4566 'mandatory': self.mandatory
4566 'mandatory': self.mandatory
4567 }
4567 }
4568
4568
4569
4569
4570 class RepoReviewRuleUserGroup(Base, BaseModel):
4570 class RepoReviewRuleUserGroup(Base, BaseModel):
4571 __tablename__ = 'repo_review_rules_users_groups'
4571 __tablename__ = 'repo_review_rules_users_groups'
4572 __table_args__ = (
4572 __table_args__ = (
4573 base_table_args
4573 base_table_args
4574 )
4574 )
4575
4575
4576 VOTE_RULE_ALL = -1
4576 VOTE_RULE_ALL = -1
4577
4577
4578 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4578 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4579 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4579 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4580 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4580 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4581 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4581 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4582 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4582 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4583 users_group = relationship('UserGroup')
4583 users_group = relationship('UserGroup')
4584
4584
4585 def rule_data(self):
4585 def rule_data(self):
4586 return {
4586 return {
4587 'mandatory': self.mandatory,
4587 'mandatory': self.mandatory,
4588 'vote_rule': self.vote_rule
4588 'vote_rule': self.vote_rule
4589 }
4589 }
4590
4590
4591 @property
4591 @property
4592 def vote_rule_label(self):
4592 def vote_rule_label(self):
4593 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4593 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4594 return 'all must vote'
4594 return 'all must vote'
4595 else:
4595 else:
4596 return 'min. vote {}'.format(self.vote_rule)
4596 return 'min. vote {}'.format(self.vote_rule)
4597
4597
4598
4598
4599 class RepoReviewRule(Base, BaseModel):
4599 class RepoReviewRule(Base, BaseModel):
4600 __tablename__ = 'repo_review_rules'
4600 __tablename__ = 'repo_review_rules'
4601 __table_args__ = (
4601 __table_args__ = (
4602 base_table_args
4602 base_table_args
4603 )
4603 )
4604
4604
4605 repo_review_rule_id = Column(
4605 repo_review_rule_id = Column(
4606 'repo_review_rule_id', Integer(), primary_key=True)
4606 'repo_review_rule_id', Integer(), primary_key=True)
4607 repo_id = Column(
4607 repo_id = Column(
4608 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4608 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4609 repo = relationship('Repository', backref='review_rules')
4609 repo = relationship('Repository', backref='review_rules')
4610
4610
4611 review_rule_name = Column('review_rule_name', String(255))
4611 review_rule_name = Column('review_rule_name', String(255))
4612 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4612 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4613 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4613 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4614 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4614 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4615
4615
4616 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4616 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4617 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4617 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4618 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4618 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4619 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4619 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4620
4620
4621 rule_users = relationship('RepoReviewRuleUser')
4621 rule_users = relationship('RepoReviewRuleUser')
4622 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4622 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4623
4623
4624 def _validate_pattern(self, value):
4624 def _validate_pattern(self, value):
4625 re.compile('^' + glob2re(value) + '$')
4625 re.compile('^' + glob2re(value) + '$')
4626
4626
4627 @hybrid_property
4627 @hybrid_property
4628 def source_branch_pattern(self):
4628 def source_branch_pattern(self):
4629 return self._branch_pattern or '*'
4629 return self._branch_pattern or '*'
4630
4630
4631 @source_branch_pattern.setter
4631 @source_branch_pattern.setter
4632 def source_branch_pattern(self, value):
4632 def source_branch_pattern(self, value):
4633 self._validate_pattern(value)
4633 self._validate_pattern(value)
4634 self._branch_pattern = value or '*'
4634 self._branch_pattern = value or '*'
4635
4635
4636 @hybrid_property
4636 @hybrid_property
4637 def target_branch_pattern(self):
4637 def target_branch_pattern(self):
4638 return self._target_branch_pattern or '*'
4638 return self._target_branch_pattern or '*'
4639
4639
4640 @target_branch_pattern.setter
4640 @target_branch_pattern.setter
4641 def target_branch_pattern(self, value):
4641 def target_branch_pattern(self, value):
4642 self._validate_pattern(value)
4642 self._validate_pattern(value)
4643 self._target_branch_pattern = value or '*'
4643 self._target_branch_pattern = value or '*'
4644
4644
4645 @hybrid_property
4645 @hybrid_property
4646 def file_pattern(self):
4646 def file_pattern(self):
4647 return self._file_pattern or '*'
4647 return self._file_pattern or '*'
4648
4648
4649 @file_pattern.setter
4649 @file_pattern.setter
4650 def file_pattern(self, value):
4650 def file_pattern(self, value):
4651 self._validate_pattern(value)
4651 self._validate_pattern(value)
4652 self._file_pattern = value or '*'
4652 self._file_pattern = value or '*'
4653
4653
4654 def matches(self, source_branch, target_branch, files_changed):
4654 def matches(self, source_branch, target_branch, files_changed):
4655 """
4655 """
4656 Check if this review rule matches a branch/files in a pull request
4656 Check if this review rule matches a branch/files in a pull request
4657
4657
4658 :param source_branch: source branch name for the commit
4658 :param source_branch: source branch name for the commit
4659 :param target_branch: target branch name for the commit
4659 :param target_branch: target branch name for the commit
4660 :param files_changed: list of file paths changed in the pull request
4660 :param files_changed: list of file paths changed in the pull request
4661 """
4661 """
4662
4662
4663 source_branch = source_branch or ''
4663 source_branch = source_branch or ''
4664 target_branch = target_branch or ''
4664 target_branch = target_branch or ''
4665 files_changed = files_changed or []
4665 files_changed = files_changed or []
4666
4666
4667 branch_matches = True
4667 branch_matches = True
4668 if source_branch or target_branch:
4668 if source_branch or target_branch:
4669 if self.source_branch_pattern == '*':
4669 if self.source_branch_pattern == '*':
4670 source_branch_match = True
4670 source_branch_match = True
4671 else:
4671 else:
4672 if self.source_branch_pattern.startswith('re:'):
4672 if self.source_branch_pattern.startswith('re:'):
4673 source_pattern = self.source_branch_pattern[3:]
4673 source_pattern = self.source_branch_pattern[3:]
4674 else:
4674 else:
4675 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4675 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4676 source_branch_regex = re.compile(source_pattern)
4676 source_branch_regex = re.compile(source_pattern)
4677 source_branch_match = bool(source_branch_regex.search(source_branch))
4677 source_branch_match = bool(source_branch_regex.search(source_branch))
4678 if self.target_branch_pattern == '*':
4678 if self.target_branch_pattern == '*':
4679 target_branch_match = True
4679 target_branch_match = True
4680 else:
4680 else:
4681 if self.target_branch_pattern.startswith('re:'):
4681 if self.target_branch_pattern.startswith('re:'):
4682 target_pattern = self.target_branch_pattern[3:]
4682 target_pattern = self.target_branch_pattern[3:]
4683 else:
4683 else:
4684 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4684 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4685 target_branch_regex = re.compile(target_pattern)
4685 target_branch_regex = re.compile(target_pattern)
4686 target_branch_match = bool(target_branch_regex.search(target_branch))
4686 target_branch_match = bool(target_branch_regex.search(target_branch))
4687
4687
4688 branch_matches = source_branch_match and target_branch_match
4688 branch_matches = source_branch_match and target_branch_match
4689
4689
4690 files_matches = True
4690 files_matches = True
4691 if self.file_pattern != '*':
4691 if self.file_pattern != '*':
4692 files_matches = False
4692 files_matches = False
4693 if self.file_pattern.startswith('re:'):
4693 if self.file_pattern.startswith('re:'):
4694 file_pattern = self.file_pattern[3:]
4694 file_pattern = self.file_pattern[3:]
4695 else:
4695 else:
4696 file_pattern = glob2re(self.file_pattern)
4696 file_pattern = glob2re(self.file_pattern)
4697 file_regex = re.compile(file_pattern)
4697 file_regex = re.compile(file_pattern)
4698 for filename in files_changed:
4698 for filename in files_changed:
4699 if file_regex.search(filename):
4699 if file_regex.search(filename):
4700 files_matches = True
4700 files_matches = True
4701 break
4701 break
4702
4702
4703 return branch_matches and files_matches
4703 return branch_matches and files_matches
4704
4704
4705 @property
4705 @property
4706 def review_users(self):
4706 def review_users(self):
4707 """ Returns the users which this rule applies to """
4707 """ Returns the users which this rule applies to """
4708
4708
4709 users = collections.OrderedDict()
4709 users = collections.OrderedDict()
4710
4710
4711 for rule_user in self.rule_users:
4711 for rule_user in self.rule_users:
4712 if rule_user.user.active:
4712 if rule_user.user.active:
4713 if rule_user.user not in users:
4713 if rule_user.user not in users:
4714 users[rule_user.user.username] = {
4714 users[rule_user.user.username] = {
4715 'user': rule_user.user,
4715 'user': rule_user.user,
4716 'source': 'user',
4716 'source': 'user',
4717 'source_data': {},
4717 'source_data': {},
4718 'data': rule_user.rule_data()
4718 'data': rule_user.rule_data()
4719 }
4719 }
4720
4720
4721 for rule_user_group in self.rule_user_groups:
4721 for rule_user_group in self.rule_user_groups:
4722 source_data = {
4722 source_data = {
4723 'user_group_id': rule_user_group.users_group.users_group_id,
4723 'user_group_id': rule_user_group.users_group.users_group_id,
4724 'name': rule_user_group.users_group.users_group_name,
4724 'name': rule_user_group.users_group.users_group_name,
4725 'members': len(rule_user_group.users_group.members)
4725 'members': len(rule_user_group.users_group.members)
4726 }
4726 }
4727 for member in rule_user_group.users_group.members:
4727 for member in rule_user_group.users_group.members:
4728 if member.user.active:
4728 if member.user.active:
4729 key = member.user.username
4729 key = member.user.username
4730 if key in users:
4730 if key in users:
4731 # skip this member as we have him already
4731 # skip this member as we have him already
4732 # this prevents from override the "first" matched
4732 # this prevents from override the "first" matched
4733 # users with duplicates in multiple groups
4733 # users with duplicates in multiple groups
4734 continue
4734 continue
4735
4735
4736 users[key] = {
4736 users[key] = {
4737 'user': member.user,
4737 'user': member.user,
4738 'source': 'user_group',
4738 'source': 'user_group',
4739 'source_data': source_data,
4739 'source_data': source_data,
4740 'data': rule_user_group.rule_data()
4740 'data': rule_user_group.rule_data()
4741 }
4741 }
4742
4742
4743 return users
4743 return users
4744
4744
4745 def user_group_vote_rule(self, user_id):
4745 def user_group_vote_rule(self, user_id):
4746
4746
4747 rules = []
4747 rules = []
4748 if not self.rule_user_groups:
4748 if not self.rule_user_groups:
4749 return rules
4749 return rules
4750
4750
4751 for user_group in self.rule_user_groups:
4751 for user_group in self.rule_user_groups:
4752 user_group_members = [x.user_id for x in user_group.users_group.members]
4752 user_group_members = [x.user_id for x in user_group.users_group.members]
4753 if user_id in user_group_members:
4753 if user_id in user_group_members:
4754 rules.append(user_group)
4754 rules.append(user_group)
4755 return rules
4755 return rules
4756
4756
4757 def __repr__(self):
4757 def __repr__(self):
4758 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4758 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4759 self.repo_review_rule_id, self.repo)
4759 self.repo_review_rule_id, self.repo)
4760
4760
4761
4761
4762 class ScheduleEntry(Base, BaseModel):
4762 class ScheduleEntry(Base, BaseModel):
4763 __tablename__ = 'schedule_entries'
4763 __tablename__ = 'schedule_entries'
4764 __table_args__ = (
4764 __table_args__ = (
4765 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4765 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4766 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4766 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4767 base_table_args,
4767 base_table_args,
4768 )
4768 )
4769
4769
4770 schedule_types = ['crontab', 'timedelta', 'integer']
4770 schedule_types = ['crontab', 'timedelta', 'integer']
4771 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4771 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4772
4772
4773 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4773 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4774 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4774 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4775 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4775 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4776
4776
4777 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4777 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4778 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4778 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4779
4779
4780 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4780 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4781 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4781 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4782
4782
4783 # task
4783 # task
4784 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4784 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4785 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4785 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4786 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4786 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4787 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4787 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4788
4788
4789 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4789 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4790 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4790 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4791
4791
4792 @hybrid_property
4792 @hybrid_property
4793 def schedule_type(self):
4793 def schedule_type(self):
4794 return self._schedule_type
4794 return self._schedule_type
4795
4795
4796 @schedule_type.setter
4796 @schedule_type.setter
4797 def schedule_type(self, val):
4797 def schedule_type(self, val):
4798 if val not in self.schedule_types:
4798 if val not in self.schedule_types:
4799 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4799 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4800 val, self.schedule_type))
4800 val, self.schedule_type))
4801
4801
4802 self._schedule_type = val
4802 self._schedule_type = val
4803
4803
4804 @classmethod
4804 @classmethod
4805 def get_uid(cls, obj):
4805 def get_uid(cls, obj):
4806 args = obj.task_args
4806 args = obj.task_args
4807 kwargs = obj.task_kwargs
4807 kwargs = obj.task_kwargs
4808 if isinstance(args, JsonRaw):
4808 if isinstance(args, JsonRaw):
4809 try:
4809 try:
4810 args = json.loads(args)
4810 args = json.loads(args)
4811 except ValueError:
4811 except ValueError:
4812 args = tuple()
4812 args = tuple()
4813
4813
4814 if isinstance(kwargs, JsonRaw):
4814 if isinstance(kwargs, JsonRaw):
4815 try:
4815 try:
4816 kwargs = json.loads(kwargs)
4816 kwargs = json.loads(kwargs)
4817 except ValueError:
4817 except ValueError:
4818 kwargs = dict()
4818 kwargs = dict()
4819
4819
4820 dot_notation = obj.task_dot_notation
4820 dot_notation = obj.task_dot_notation
4821 val = '.'.join(map(safe_str, [
4821 val = '.'.join(map(safe_str, [
4822 sorted(dot_notation), args, sorted(kwargs.items())]))
4822 sorted(dot_notation), args, sorted(kwargs.items())]))
4823 return hashlib.sha1(val).hexdigest()
4823 return hashlib.sha1(val).hexdigest()
4824
4824
4825 @classmethod
4825 @classmethod
4826 def get_by_schedule_name(cls, schedule_name):
4826 def get_by_schedule_name(cls, schedule_name):
4827 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4827 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4828
4828
4829 @classmethod
4829 @classmethod
4830 def get_by_schedule_id(cls, schedule_id):
4830 def get_by_schedule_id(cls, schedule_id):
4831 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4831 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4832
4832
4833 @property
4833 @property
4834 def task(self):
4834 def task(self):
4835 return self.task_dot_notation
4835 return self.task_dot_notation
4836
4836
4837 @property
4837 @property
4838 def schedule(self):
4838 def schedule(self):
4839 from rhodecode.lib.celerylib.utils import raw_2_schedule
4839 from rhodecode.lib.celerylib.utils import raw_2_schedule
4840 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4840 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4841 return schedule
4841 return schedule
4842
4842
4843 @property
4843 @property
4844 def args(self):
4844 def args(self):
4845 try:
4845 try:
4846 return list(self.task_args or [])
4846 return list(self.task_args or [])
4847 except ValueError:
4847 except ValueError:
4848 return list()
4848 return list()
4849
4849
4850 @property
4850 @property
4851 def kwargs(self):
4851 def kwargs(self):
4852 try:
4852 try:
4853 return dict(self.task_kwargs or {})
4853 return dict(self.task_kwargs or {})
4854 except ValueError:
4854 except ValueError:
4855 return dict()
4855 return dict()
4856
4856
4857 def _as_raw(self, val):
4857 def _as_raw(self, val):
4858 if hasattr(val, 'de_coerce'):
4858 if hasattr(val, 'de_coerce'):
4859 val = val.de_coerce()
4859 val = val.de_coerce()
4860 if val:
4860 if val:
4861 val = json.dumps(val)
4861 val = json.dumps(val)
4862
4862
4863 return val
4863 return val
4864
4864
4865 @property
4865 @property
4866 def schedule_definition_raw(self):
4866 def schedule_definition_raw(self):
4867 return self._as_raw(self.schedule_definition)
4867 return self._as_raw(self.schedule_definition)
4868
4868
4869 @property
4869 @property
4870 def args_raw(self):
4870 def args_raw(self):
4871 return self._as_raw(self.task_args)
4871 return self._as_raw(self.task_args)
4872
4872
4873 @property
4873 @property
4874 def kwargs_raw(self):
4874 def kwargs_raw(self):
4875 return self._as_raw(self.task_kwargs)
4875 return self._as_raw(self.task_kwargs)
4876
4876
4877 def __repr__(self):
4877 def __repr__(self):
4878 return '<DB:ScheduleEntry({}:{})>'.format(
4878 return '<DB:ScheduleEntry({}:{})>'.format(
4879 self.schedule_entry_id, self.schedule_name)
4879 self.schedule_entry_id, self.schedule_name)
4880
4880
4881
4881
4882 @event.listens_for(ScheduleEntry, 'before_update')
4882 @event.listens_for(ScheduleEntry, 'before_update')
4883 def update_task_uid(mapper, connection, target):
4883 def update_task_uid(mapper, connection, target):
4884 target.task_uid = ScheduleEntry.get_uid(target)
4884 target.task_uid = ScheduleEntry.get_uid(target)
4885
4885
4886
4886
4887 @event.listens_for(ScheduleEntry, 'before_insert')
4887 @event.listens_for(ScheduleEntry, 'before_insert')
4888 def set_task_uid(mapper, connection, target):
4888 def set_task_uid(mapper, connection, target):
4889 target.task_uid = ScheduleEntry.get_uid(target)
4889 target.task_uid = ScheduleEntry.get_uid(target)
4890
4890
4891
4891
4892 class _BaseBranchPerms(BaseModel):
4892 class _BaseBranchPerms(BaseModel):
4893 @classmethod
4893 @classmethod
4894 def compute_hash(cls, value):
4894 def compute_hash(cls, value):
4895 return sha1_safe(value)
4895 return sha1_safe(value)
4896
4896
4897 @hybrid_property
4897 @hybrid_property
4898 def branch_pattern(self):
4898 def branch_pattern(self):
4899 return self._branch_pattern or '*'
4899 return self._branch_pattern or '*'
4900
4900
4901 @hybrid_property
4901 @hybrid_property
4902 def branch_hash(self):
4902 def branch_hash(self):
4903 return self._branch_hash
4903 return self._branch_hash
4904
4904
4905 def _validate_glob(self, value):
4905 def _validate_glob(self, value):
4906 re.compile('^' + glob2re(value) + '$')
4906 re.compile('^' + glob2re(value) + '$')
4907
4907
4908 @branch_pattern.setter
4908 @branch_pattern.setter
4909 def branch_pattern(self, value):
4909 def branch_pattern(self, value):
4910 self._validate_glob(value)
4910 self._validate_glob(value)
4911 self._branch_pattern = value or '*'
4911 self._branch_pattern = value or '*'
4912 # set the Hash when setting the branch pattern
4912 # set the Hash when setting the branch pattern
4913 self._branch_hash = self.compute_hash(self._branch_pattern)
4913 self._branch_hash = self.compute_hash(self._branch_pattern)
4914
4914
4915 def matches(self, branch):
4915 def matches(self, branch):
4916 """
4916 """
4917 Check if this the branch matches entry
4917 Check if this the branch matches entry
4918
4918
4919 :param branch: branch name for the commit
4919 :param branch: branch name for the commit
4920 """
4920 """
4921
4921
4922 branch = branch or ''
4922 branch = branch or ''
4923
4923
4924 branch_matches = True
4924 branch_matches = True
4925 if branch:
4925 if branch:
4926 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4926 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4927 branch_matches = bool(branch_regex.search(branch))
4927 branch_matches = bool(branch_regex.search(branch))
4928
4928
4929 return branch_matches
4929 return branch_matches
4930
4930
4931
4931
4932 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4932 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4933 __tablename__ = 'user_to_repo_branch_permissions'
4933 __tablename__ = 'user_to_repo_branch_permissions'
4934 __table_args__ = (
4934 __table_args__ = (
4935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4937 )
4937 )
4938
4938
4939 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4939 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4940
4940
4941 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4941 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4942 repo = relationship('Repository', backref='user_branch_perms')
4942 repo = relationship('Repository', backref='user_branch_perms')
4943
4943
4944 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4944 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4945 permission = relationship('Permission')
4945 permission = relationship('Permission')
4946
4946
4947 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4947 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4948 user_repo_to_perm = relationship('UserRepoToPerm')
4948 user_repo_to_perm = relationship('UserRepoToPerm')
4949
4949
4950 rule_order = Column('rule_order', Integer(), nullable=False)
4950 rule_order = Column('rule_order', Integer(), nullable=False)
4951 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4951 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4952 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4952 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4953
4953
4954 def __unicode__(self):
4954 def __unicode__(self):
4955 return u'<UserBranchPermission(%s => %r)>' % (
4955 return u'<UserBranchPermission(%s => %r)>' % (
4956 self.user_repo_to_perm, self.branch_pattern)
4956 self.user_repo_to_perm, self.branch_pattern)
4957
4957
4958
4958
4959 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4959 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4960 __tablename__ = 'user_group_to_repo_branch_permissions'
4960 __tablename__ = 'user_group_to_repo_branch_permissions'
4961 __table_args__ = (
4961 __table_args__ = (
4962 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4962 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4963 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4963 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4964 )
4964 )
4965
4965
4966 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4966 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4967
4967
4968 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4968 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4969 repo = relationship('Repository', backref='user_group_branch_perms')
4969 repo = relationship('Repository', backref='user_group_branch_perms')
4970
4970
4971 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4971 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4972 permission = relationship('Permission')
4972 permission = relationship('Permission')
4973
4973
4974 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4974 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4975 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4975 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4976
4976
4977 rule_order = Column('rule_order', Integer(), nullable=False)
4977 rule_order = Column('rule_order', Integer(), nullable=False)
4978 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4978 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4979 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4979 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4980
4980
4981 def __unicode__(self):
4981 def __unicode__(self):
4982 return u'<UserBranchPermission(%s => %r)>' % (
4982 return u'<UserBranchPermission(%s => %r)>' % (
4983 self.user_group_repo_to_perm, self.branch_pattern)
4983 self.user_group_repo_to_perm, self.branch_pattern)
4984
4984
4985
4985
4986 class UserBookmark(Base, BaseModel):
4986 class UserBookmark(Base, BaseModel):
4987 __tablename__ = 'user_bookmarks'
4987 __tablename__ = 'user_bookmarks'
4988 __table_args__ = (
4988 __table_args__ = (
4989 UniqueConstraint('user_id', 'bookmark_repo_id'),
4989 UniqueConstraint('user_id', 'bookmark_repo_id'),
4990 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4990 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4991 UniqueConstraint('user_id', 'bookmark_position'),
4991 UniqueConstraint('user_id', 'bookmark_position'),
4992 base_table_args
4992 base_table_args
4993 )
4993 )
4994
4994
4995 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4995 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4996 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4996 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4997 position = Column("bookmark_position", Integer(), nullable=False)
4997 position = Column("bookmark_position", Integer(), nullable=False)
4998 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4998 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4999 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
4999 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5000 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5000 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5001
5001
5002 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5002 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5003 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5003 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5004
5004
5005 user = relationship("User")
5005 user = relationship("User")
5006
5006
5007 repository = relationship("Repository")
5007 repository = relationship("Repository")
5008 repository_group = relationship("RepoGroup")
5008 repository_group = relationship("RepoGroup")
5009
5009
5010 @classmethod
5010 @classmethod
5011 def get_by_position_for_user(cls, position, user_id):
5011 def get_by_position_for_user(cls, position, user_id):
5012 return cls.query() \
5012 return cls.query() \
5013 .filter(UserBookmark.user_id == user_id) \
5013 .filter(UserBookmark.user_id == user_id) \
5014 .filter(UserBookmark.position == position).scalar()
5014 .filter(UserBookmark.position == position).scalar()
5015
5015
5016 @classmethod
5016 @classmethod
5017 def get_bookmarks_for_user(cls, user_id):
5017 def get_bookmarks_for_user(cls, user_id):
5018 return cls.query() \
5018 return cls.query() \
5019 .filter(UserBookmark.user_id == user_id) \
5019 .filter(UserBookmark.user_id == user_id) \
5020 .options(joinedload(UserBookmark.repository)) \
5020 .options(joinedload(UserBookmark.repository)) \
5021 .options(joinedload(UserBookmark.repository_group)) \
5021 .options(joinedload(UserBookmark.repository_group)) \
5022 .order_by(UserBookmark.position.asc()) \
5022 .order_by(UserBookmark.position.asc()) \
5023 .all()
5023 .all()
5024
5024
5025 def __unicode__(self):
5025 def __unicode__(self):
5026 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
5026 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
5027
5027
5028
5028
5029 class FileStore(Base, BaseModel):
5029 class FileStore(Base, BaseModel):
5030 __tablename__ = 'file_store'
5030 __tablename__ = 'file_store'
5031 __table_args__ = (
5031 __table_args__ = (
5032 base_table_args
5032 base_table_args
5033 )
5033 )
5034
5034
5035 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5035 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5036 file_uid = Column('file_uid', String(1024), nullable=False)
5036 file_uid = Column('file_uid', String(1024), nullable=False)
5037 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5037 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5038 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5038 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5039 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5039 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5040
5040
5041 # sha256 hash
5041 # sha256 hash
5042 file_hash = Column('file_hash', String(512), nullable=False)
5042 file_hash = Column('file_hash', String(512), nullable=False)
5043 file_size = Column('file_size', Integer(), nullable=False)
5043 file_size = Column('file_size', Integer(), nullable=False)
5044
5044
5045 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5045 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5046 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5046 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5047 accessed_count = Column('accessed_count', Integer(), default=0)
5047 accessed_count = Column('accessed_count', Integer(), default=0)
5048
5048
5049 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5049 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5050
5050
5051 # if repo/repo_group reference is set, check for permissions
5051 # if repo/repo_group reference is set, check for permissions
5052 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5052 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5053
5053
5054 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5054 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5055 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5055 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5056
5056
5057 # scope limited to user, which requester have access to
5057 # scope limited to user, which requester have access to
5058 scope_user_id = Column(
5058 scope_user_id = Column(
5059 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5059 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5060 nullable=True, unique=None, default=None)
5060 nullable=True, unique=None, default=None)
5061 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5061 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5062
5062
5063 # scope limited to user group, which requester have access to
5063 # scope limited to user group, which requester have access to
5064 scope_user_group_id = Column(
5064 scope_user_group_id = Column(
5065 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5065 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5066 nullable=True, unique=None, default=None)
5066 nullable=True, unique=None, default=None)
5067 user_group = relationship('UserGroup', lazy='joined')
5067 user_group = relationship('UserGroup', lazy='joined')
5068
5068
5069 # scope limited to repo, which requester have access to
5069 # scope limited to repo, which requester have access to
5070 scope_repo_id = Column(
5070 scope_repo_id = Column(
5071 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5071 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5072 nullable=True, unique=None, default=None)
5072 nullable=True, unique=None, default=None)
5073 repo = relationship('Repository', lazy='joined')
5073 repo = relationship('Repository', lazy='joined')
5074
5074
5075 # scope limited to repo group, which requester have access to
5075 # scope limited to repo group, which requester have access to
5076 scope_repo_group_id = Column(
5076 scope_repo_group_id = Column(
5077 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5077 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5078 nullable=True, unique=None, default=None)
5078 nullable=True, unique=None, default=None)
5079 repo_group = relationship('RepoGroup', lazy='joined')
5079 repo_group = relationship('RepoGroup', lazy='joined')
5080
5080
5081 @classmethod
5081 @classmethod
5082 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5082 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5083 file_description='', enabled=True, check_acl=True, user_id=None,
5083 file_description='', enabled=True, check_acl=True, user_id=None,
5084 scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5084 scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5085
5085
5086 store_entry = FileStore()
5086 store_entry = FileStore()
5087 store_entry.file_uid = file_uid
5087 store_entry.file_uid = file_uid
5088 store_entry.file_display_name = file_display_name
5088 store_entry.file_display_name = file_display_name
5089 store_entry.file_org_name = filename
5089 store_entry.file_org_name = filename
5090 store_entry.file_size = file_size
5090 store_entry.file_size = file_size
5091 store_entry.file_hash = file_hash
5091 store_entry.file_hash = file_hash
5092 store_entry.file_description = file_description
5092 store_entry.file_description = file_description
5093
5093
5094 store_entry.check_acl = check_acl
5094 store_entry.check_acl = check_acl
5095 store_entry.enabled = enabled
5095 store_entry.enabled = enabled
5096
5096
5097 store_entry.user_id = user_id
5097 store_entry.user_id = user_id
5098 store_entry.scope_user_id = scope_user_id
5098 store_entry.scope_user_id = scope_user_id
5099 store_entry.scope_repo_id = scope_repo_id
5099 store_entry.scope_repo_id = scope_repo_id
5100 store_entry.scope_repo_group_id = scope_repo_group_id
5100 store_entry.scope_repo_group_id = scope_repo_group_id
5101 return store_entry
5101 return store_entry
5102
5102
5103 @classmethod
5103 @classmethod
5104 def bump_access_counter(cls, file_uid, commit=True):
5104 def bump_access_counter(cls, file_uid, commit=True):
5105 FileStore().query()\
5105 FileStore().query()\
5106 .filter(FileStore.file_uid == file_uid)\
5106 .filter(FileStore.file_uid == file_uid)\
5107 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5107 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5108 FileStore.accessed_on: datetime.datetime.now()})
5108 FileStore.accessed_on: datetime.datetime.now()})
5109 if commit:
5109 if commit:
5110 Session().commit()
5110 Session().commit()
5111
5111
5112 def __repr__(self):
5112 def __repr__(self):
5113 return '<FileStore({})>'.format(self.file_store_id)
5113 return '<FileStore({})>'.format(self.file_store_id)
5114
5114
5115
5115
5116 class DbMigrateVersion(Base, BaseModel):
5116 class DbMigrateVersion(Base, BaseModel):
5117 __tablename__ = 'db_migrate_version'
5117 __tablename__ = 'db_migrate_version'
5118 __table_args__ = (
5118 __table_args__ = (
5119 base_table_args,
5119 base_table_args,
5120 )
5120 )
5121
5121
5122 repository_id = Column('repository_id', String(250), primary_key=True)
5122 repository_id = Column('repository_id', String(250), primary_key=True)
5123 repository_path = Column('repository_path', Text)
5123 repository_path = Column('repository_path', Text)
5124 version = Column('version', Integer)
5124 version = Column('version', Integer)
5125
5125
5126 @classmethod
5126 @classmethod
5127 def set_version(cls, version):
5127 def set_version(cls, version):
5128 """
5128 """
5129 Helper for forcing a different version, usually for debugging purposes via ishell.
5129 Helper for forcing a different version, usually for debugging purposes via ishell.
5130 """
5130 """
5131 ver = DbMigrateVersion.query().first()
5131 ver = DbMigrateVersion.query().first()
5132 ver.version = version
5132 ver.version = version
5133 Session().commit()
5133 Session().commit()
5134
5134
5135
5135
5136 class DbSession(Base, BaseModel):
5136 class DbSession(Base, BaseModel):
5137 __tablename__ = 'db_session'
5137 __tablename__ = 'db_session'
5138 __table_args__ = (
5138 __table_args__ = (
5139 base_table_args,
5139 base_table_args,
5140 )
5140 )
5141
5141
5142 def __repr__(self):
5142 def __repr__(self):
5143 return '<DB:DbSession({})>'.format(self.id)
5143 return '<DB:DbSession({})>'.format(self.id)
5144
5144
5145 id = Column('id', Integer())
5145 id = Column('id', Integer())
5146 namespace = Column('namespace', String(255), primary_key=True)
5146 namespace = Column('namespace', String(255), primary_key=True)
5147 accessed = Column('accessed', DateTime, nullable=False)
5147 accessed = Column('accessed', DateTime, nullable=False)
5148 created = Column('created', DateTime, nullable=False)
5148 created = Column('created', DateTime, nullable=False)
5149 data = Column('data', PickleType, nullable=False)
5149 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now