##// END OF EJS Templates
diff-caches: show count and size in caches view per repository.
marcink -
r2687:040668bd default
parent child Browse files
Show More
@@ -1,75 +1,80 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import logging
22 import logging
22
23
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25 from pyramid.view import view_config
25
26
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib.auth import (
28 from rhodecode.lib.auth import (
28 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
29 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib import system_info
30 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
31 from rhodecode.model.scm import ScmModel
33 from rhodecode.model.scm import ScmModel
32
34
33 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
34
36
35
37
36 class RepoCachesView(RepoAppView):
38 class RepoCachesView(RepoAppView):
37 def load_default_context(self):
39 def load_default_context(self):
38 c = self._get_local_tmpl_context()
40 c = self._get_local_tmpl_context()
39
40
41 return c
41 return c
42
42
43 @LoginRequired()
43 @LoginRequired()
44 @HasRepoPermissionAnyDecorator('repository.admin')
44 @HasRepoPermissionAnyDecorator('repository.admin')
45 @view_config(
45 @view_config(
46 route_name='edit_repo_caches', request_method='GET',
46 route_name='edit_repo_caches', request_method='GET',
47 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
47 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
48 def repo_caches(self):
48 def repo_caches(self):
49 c = self.load_default_context()
49 c = self.load_default_context()
50 c.active = 'caches'
50 c.active = 'caches'
51 cached_diffs_dir = c.rhodecode_db_repo.cached_diffs_dir
52 c.cached_diff_count = len(c.rhodecode_db_repo.cached_diffs())
53 c.cached_diff_size = 0
54 if os.path.isdir(cached_diffs_dir):
55 c.cached_diff_size = system_info.get_storage_size(cached_diffs_dir)
51
56
52 return self._get_template_context(c)
57 return self._get_template_context(c)
53
58
54 @LoginRequired()
59 @LoginRequired()
55 @HasRepoPermissionAnyDecorator('repository.admin')
60 @HasRepoPermissionAnyDecorator('repository.admin')
56 @CSRFRequired()
61 @CSRFRequired()
57 @view_config(
62 @view_config(
58 route_name='edit_repo_caches', request_method='POST')
63 route_name='edit_repo_caches', request_method='POST')
59 def repo_caches_purge(self):
64 def repo_caches_purge(self):
60 _ = self.request.translate
65 _ = self.request.translate
61 c = self.load_default_context()
66 c = self.load_default_context()
62 c.active = 'caches'
67 c.active = 'caches'
63
68
64 try:
69 try:
65 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
70 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
66 Session().commit()
71 Session().commit()
67 h.flash(_('Cache invalidation successful'),
72 h.flash(_('Cache invalidation successful'),
68 category='success')
73 category='success')
69 except Exception:
74 except Exception:
70 log.exception("Exception during cache invalidation")
75 log.exception("Exception during cache invalidation")
71 h.flash(_('An error occurred during cache invalidation'),
76 h.flash(_('An error occurred during cache invalidation'),
72 category='error')
77 category='error')
73
78
74 raise HTTPFound(h.route_path(
79 raise HTTPFound(h.route_path(
75 'edit_repo_caches', repo_name=self.db_repo_name)) No newline at end of file
80 'edit_repo_caches', repo_name=self.db_repo_name))
@@ -1,1712 +1,1710 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2018 RhodeCode GmbH
3 # Copyright (C) 2014-2018 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
24
25 import collections
25 import collections
26 import datetime
26 import datetime
27 import fnmatch
27 import fnmatch
28 import itertools
28 import itertools
29 import logging
29 import logging
30 import os
30 import os
31 import re
31 import re
32 import time
32 import time
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
36
37 from rhodecode.lib.utils2 import safe_str, safe_unicode
37 from rhodecode.lib.utils2 import safe_str, safe_unicode
38 from rhodecode.lib.vcs import connection
38 from rhodecode.lib.vcs import connection
39 from rhodecode.lib.vcs.utils import author_name, author_email
39 from rhodecode.lib.vcs.utils import author_name, author_email
40 from rhodecode.lib.vcs.conf import settings
40 from rhodecode.lib.vcs.conf import settings
41 from rhodecode.lib.vcs.exceptions import (
41 from rhodecode.lib.vcs.exceptions import (
42 CommitError, EmptyRepositoryError, NodeAlreadyAddedError,
42 CommitError, EmptyRepositoryError, NodeAlreadyAddedError,
43 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
43 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
44 NodeDoesNotExistError, NodeNotChangedError, VCSError,
44 NodeDoesNotExistError, NodeNotChangedError, VCSError,
45 ImproperArchiveTypeError, BranchDoesNotExistError, CommitDoesNotExistError,
45 ImproperArchiveTypeError, BranchDoesNotExistError, CommitDoesNotExistError,
46 RepositoryError)
46 RepositoryError)
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 FILEMODE_DEFAULT = 0100644
52 FILEMODE_DEFAULT = 0100644
53 FILEMODE_EXECUTABLE = 0100755
53 FILEMODE_EXECUTABLE = 0100755
54
54
55 Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id'))
55 Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id'))
56 MergeResponse = collections.namedtuple(
56 MergeResponse = collections.namedtuple(
57 'MergeResponse',
57 'MergeResponse',
58 ('possible', 'executed', 'merge_ref', 'failure_reason'))
58 ('possible', 'executed', 'merge_ref', 'failure_reason'))
59
59
60
60
61 class MergeFailureReason(object):
61 class MergeFailureReason(object):
62 """
62 """
63 Enumeration with all the reasons why the server side merge could fail.
63 Enumeration with all the reasons why the server side merge could fail.
64
64
65 DO NOT change the number of the reasons, as they may be stored in the
65 DO NOT change the number of the reasons, as they may be stored in the
66 database.
66 database.
67
67
68 Changing the name of a reason is acceptable and encouraged to deprecate old
68 Changing the name of a reason is acceptable and encouraged to deprecate old
69 reasons.
69 reasons.
70 """
70 """
71
71
72 # Everything went well.
72 # Everything went well.
73 NONE = 0
73 NONE = 0
74
74
75 # An unexpected exception was raised. Check the logs for more details.
75 # An unexpected exception was raised. Check the logs for more details.
76 UNKNOWN = 1
76 UNKNOWN = 1
77
77
78 # The merge was not successful, there are conflicts.
78 # The merge was not successful, there are conflicts.
79 MERGE_FAILED = 2
79 MERGE_FAILED = 2
80
80
81 # The merge succeeded but we could not push it to the target repository.
81 # The merge succeeded but we could not push it to the target repository.
82 PUSH_FAILED = 3
82 PUSH_FAILED = 3
83
83
84 # The specified target is not a head in the target repository.
84 # The specified target is not a head in the target repository.
85 TARGET_IS_NOT_HEAD = 4
85 TARGET_IS_NOT_HEAD = 4
86
86
87 # The source repository contains more branches than the target. Pushing
87 # The source repository contains more branches than the target. Pushing
88 # the merge will create additional branches in the target.
88 # the merge will create additional branches in the target.
89 HG_SOURCE_HAS_MORE_BRANCHES = 5
89 HG_SOURCE_HAS_MORE_BRANCHES = 5
90
90
91 # The target reference has multiple heads. That does not allow to correctly
91 # The target reference has multiple heads. That does not allow to correctly
92 # identify the target location. This could only happen for mercurial
92 # identify the target location. This could only happen for mercurial
93 # branches.
93 # branches.
94 HG_TARGET_HAS_MULTIPLE_HEADS = 6
94 HG_TARGET_HAS_MULTIPLE_HEADS = 6
95
95
96 # The target repository is locked
96 # The target repository is locked
97 TARGET_IS_LOCKED = 7
97 TARGET_IS_LOCKED = 7
98
98
99 # Deprecated, use MISSING_TARGET_REF or MISSING_SOURCE_REF instead.
99 # Deprecated, use MISSING_TARGET_REF or MISSING_SOURCE_REF instead.
100 # A involved commit could not be found.
100 # A involved commit could not be found.
101 _DEPRECATED_MISSING_COMMIT = 8
101 _DEPRECATED_MISSING_COMMIT = 8
102
102
103 # The target repo reference is missing.
103 # The target repo reference is missing.
104 MISSING_TARGET_REF = 9
104 MISSING_TARGET_REF = 9
105
105
106 # The source repo reference is missing.
106 # The source repo reference is missing.
107 MISSING_SOURCE_REF = 10
107 MISSING_SOURCE_REF = 10
108
108
109 # The merge was not successful, there are conflicts related to sub
109 # The merge was not successful, there are conflicts related to sub
110 # repositories.
110 # repositories.
111 SUBREPO_MERGE_FAILED = 11
111 SUBREPO_MERGE_FAILED = 11
112
112
113
113
114 class UpdateFailureReason(object):
114 class UpdateFailureReason(object):
115 """
115 """
116 Enumeration with all the reasons why the pull request update could fail.
116 Enumeration with all the reasons why the pull request update could fail.
117
117
118 DO NOT change the number of the reasons, as they may be stored in the
118 DO NOT change the number of the reasons, as they may be stored in the
119 database.
119 database.
120
120
121 Changing the name of a reason is acceptable and encouraged to deprecate old
121 Changing the name of a reason is acceptable and encouraged to deprecate old
122 reasons.
122 reasons.
123 """
123 """
124
124
125 # Everything went well.
125 # Everything went well.
126 NONE = 0
126 NONE = 0
127
127
128 # An unexpected exception was raised. Check the logs for more details.
128 # An unexpected exception was raised. Check the logs for more details.
129 UNKNOWN = 1
129 UNKNOWN = 1
130
130
131 # The pull request is up to date.
131 # The pull request is up to date.
132 NO_CHANGE = 2
132 NO_CHANGE = 2
133
133
134 # The pull request has a reference type that is not supported for update.
134 # The pull request has a reference type that is not supported for update.
135 WRONG_REF_TYPE = 3
135 WRONG_REF_TYPE = 3
136
136
137 # Update failed because the target reference is missing.
137 # Update failed because the target reference is missing.
138 MISSING_TARGET_REF = 4
138 MISSING_TARGET_REF = 4
139
139
140 # Update failed because the source reference is missing.
140 # Update failed because the source reference is missing.
141 MISSING_SOURCE_REF = 5
141 MISSING_SOURCE_REF = 5
142
142
143
143
144 class BaseRepository(object):
144 class BaseRepository(object):
145 """
145 """
146 Base Repository for final backends
146 Base Repository for final backends
147
147
148 .. attribute:: DEFAULT_BRANCH_NAME
148 .. attribute:: DEFAULT_BRANCH_NAME
149
149
150 name of default branch (i.e. "trunk" for svn, "master" for git etc.
150 name of default branch (i.e. "trunk" for svn, "master" for git etc.
151
151
152 .. attribute:: commit_ids
152 .. attribute:: commit_ids
153
153
154 list of all available commit ids, in ascending order
154 list of all available commit ids, in ascending order
155
155
156 .. attribute:: path
156 .. attribute:: path
157
157
158 absolute path to the repository
158 absolute path to the repository
159
159
160 .. attribute:: bookmarks
160 .. attribute:: bookmarks
161
161
162 Mapping from name to :term:`Commit ID` of the bookmark. Empty in case
162 Mapping from name to :term:`Commit ID` of the bookmark. Empty in case
163 there are no bookmarks or the backend implementation does not support
163 there are no bookmarks or the backend implementation does not support
164 bookmarks.
164 bookmarks.
165
165
166 .. attribute:: tags
166 .. attribute:: tags
167
167
168 Mapping from name to :term:`Commit ID` of the tag.
168 Mapping from name to :term:`Commit ID` of the tag.
169
169
170 """
170 """
171
171
172 DEFAULT_BRANCH_NAME = None
172 DEFAULT_BRANCH_NAME = None
173 DEFAULT_CONTACT = u"Unknown"
173 DEFAULT_CONTACT = u"Unknown"
174 DEFAULT_DESCRIPTION = u"unknown"
174 DEFAULT_DESCRIPTION = u"unknown"
175 EMPTY_COMMIT_ID = '0' * 40
175 EMPTY_COMMIT_ID = '0' * 40
176
176
177 path = None
177 path = None
178 _remote = None
178 _remote = None
179
179
180 def __init__(self, repo_path, config=None, create=False, **kwargs):
180 def __init__(self, repo_path, config=None, create=False, **kwargs):
181 """
181 """
182 Initializes repository. Raises RepositoryError if repository could
182 Initializes repository. Raises RepositoryError if repository could
183 not be find at the given ``repo_path`` or directory at ``repo_path``
183 not be find at the given ``repo_path`` or directory at ``repo_path``
184 exists and ``create`` is set to True.
184 exists and ``create`` is set to True.
185
185
186 :param repo_path: local path of the repository
186 :param repo_path: local path of the repository
187 :param config: repository configuration
187 :param config: repository configuration
188 :param create=False: if set to True, would try to create repository.
188 :param create=False: if set to True, would try to create repository.
189 :param src_url=None: if set, should be proper url from which repository
189 :param src_url=None: if set, should be proper url from which repository
190 would be cloned; requires ``create`` parameter to be set to True -
190 would be cloned; requires ``create`` parameter to be set to True -
191 raises RepositoryError if src_url is set and create evaluates to
191 raises RepositoryError if src_url is set and create evaluates to
192 False
192 False
193 """
193 """
194 raise NotImplementedError
194 raise NotImplementedError
195
195
196 def __repr__(self):
196 def __repr__(self):
197 return '<%s at %s>' % (self.__class__.__name__, self.path)
197 return '<%s at %s>' % (self.__class__.__name__, self.path)
198
198
199 def __len__(self):
199 def __len__(self):
200 return self.count()
200 return self.count()
201
201
202 def __eq__(self, other):
202 def __eq__(self, other):
203 same_instance = isinstance(other, self.__class__)
203 same_instance = isinstance(other, self.__class__)
204 return same_instance and other.path == self.path
204 return same_instance and other.path == self.path
205
205
206 def __ne__(self, other):
206 def __ne__(self, other):
207 return not self.__eq__(other)
207 return not self.__eq__(other)
208
208
209 def get_create_shadow_cache_pr_path(self, repo):
209 def get_create_shadow_cache_pr_path(self, db_repo):
210 path = os.path.join(
210 path = db_repo.cached_diffs_dir
211 os.path.dirname(self.path),
212 '.__shadow_diff_cache_repo_{}/'.format(repo.repo_id))
213 if not os.path.exists(path):
211 if not os.path.exists(path):
214 os.makedirs(path, 0755)
212 os.makedirs(path, 0755)
215 return path
213 return path
216
214
217 @classmethod
215 @classmethod
218 def get_default_config(cls, default=None):
216 def get_default_config(cls, default=None):
219 config = Config()
217 config = Config()
220 if default and isinstance(default, list):
218 if default and isinstance(default, list):
221 for section, key, val in default:
219 for section, key, val in default:
222 config.set(section, key, val)
220 config.set(section, key, val)
223 return config
221 return config
224
222
225 @LazyProperty
223 @LazyProperty
226 def EMPTY_COMMIT(self):
224 def EMPTY_COMMIT(self):
227 return EmptyCommit(self.EMPTY_COMMIT_ID)
225 return EmptyCommit(self.EMPTY_COMMIT_ID)
228
226
229 @LazyProperty
227 @LazyProperty
230 def alias(self):
228 def alias(self):
231 for k, v in settings.BACKENDS.items():
229 for k, v in settings.BACKENDS.items():
232 if v.split('.')[-1] == str(self.__class__.__name__):
230 if v.split('.')[-1] == str(self.__class__.__name__):
233 return k
231 return k
234
232
235 @LazyProperty
233 @LazyProperty
236 def name(self):
234 def name(self):
237 return safe_unicode(os.path.basename(self.path))
235 return safe_unicode(os.path.basename(self.path))
238
236
239 @LazyProperty
237 @LazyProperty
240 def description(self):
238 def description(self):
241 raise NotImplementedError
239 raise NotImplementedError
242
240
243 def refs(self):
241 def refs(self):
244 """
242 """
245 returns a `dict` with branches, bookmarks, tags, and closed_branches
243 returns a `dict` with branches, bookmarks, tags, and closed_branches
246 for this repository
244 for this repository
247 """
245 """
248 return dict(
246 return dict(
249 branches=self.branches,
247 branches=self.branches,
250 branches_closed=self.branches_closed,
248 branches_closed=self.branches_closed,
251 tags=self.tags,
249 tags=self.tags,
252 bookmarks=self.bookmarks
250 bookmarks=self.bookmarks
253 )
251 )
254
252
255 @LazyProperty
253 @LazyProperty
256 def branches(self):
254 def branches(self):
257 """
255 """
258 A `dict` which maps branch names to commit ids.
256 A `dict` which maps branch names to commit ids.
259 """
257 """
260 raise NotImplementedError
258 raise NotImplementedError
261
259
262 @LazyProperty
260 @LazyProperty
263 def branches_closed(self):
261 def branches_closed(self):
264 """
262 """
265 A `dict` which maps tags names to commit ids.
263 A `dict` which maps tags names to commit ids.
266 """
264 """
267 raise NotImplementedError
265 raise NotImplementedError
268
266
269 @LazyProperty
267 @LazyProperty
270 def bookmarks(self):
268 def bookmarks(self):
271 """
269 """
272 A `dict` which maps tags names to commit ids.
270 A `dict` which maps tags names to commit ids.
273 """
271 """
274 raise NotImplementedError
272 raise NotImplementedError
275
273
276 @LazyProperty
274 @LazyProperty
277 def tags(self):
275 def tags(self):
278 """
276 """
279 A `dict` which maps tags names to commit ids.
277 A `dict` which maps tags names to commit ids.
280 """
278 """
281 raise NotImplementedError
279 raise NotImplementedError
282
280
283 @LazyProperty
281 @LazyProperty
284 def size(self):
282 def size(self):
285 """
283 """
286 Returns combined size in bytes for all repository files
284 Returns combined size in bytes for all repository files
287 """
285 """
288 tip = self.get_commit()
286 tip = self.get_commit()
289 return tip.size
287 return tip.size
290
288
291 def size_at_commit(self, commit_id):
289 def size_at_commit(self, commit_id):
292 commit = self.get_commit(commit_id)
290 commit = self.get_commit(commit_id)
293 return commit.size
291 return commit.size
294
292
295 def is_empty(self):
293 def is_empty(self):
296 return not bool(self.commit_ids)
294 return not bool(self.commit_ids)
297
295
298 @staticmethod
296 @staticmethod
299 def check_url(url, config):
297 def check_url(url, config):
300 """
298 """
301 Function will check given url and try to verify if it's a valid
299 Function will check given url and try to verify if it's a valid
302 link.
300 link.
303 """
301 """
304 raise NotImplementedError
302 raise NotImplementedError
305
303
306 @staticmethod
304 @staticmethod
307 def is_valid_repository(path):
305 def is_valid_repository(path):
308 """
306 """
309 Check if given `path` contains a valid repository of this backend
307 Check if given `path` contains a valid repository of this backend
310 """
308 """
311 raise NotImplementedError
309 raise NotImplementedError
312
310
313 # ==========================================================================
311 # ==========================================================================
314 # COMMITS
312 # COMMITS
315 # ==========================================================================
313 # ==========================================================================
316
314
317 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
315 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
318 """
316 """
319 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
317 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
320 are both None, most recent commit is returned.
318 are both None, most recent commit is returned.
321
319
322 :param pre_load: Optional. List of commit attributes to load.
320 :param pre_load: Optional. List of commit attributes to load.
323
321
324 :raises ``EmptyRepositoryError``: if there are no commits
322 :raises ``EmptyRepositoryError``: if there are no commits
325 """
323 """
326 raise NotImplementedError
324 raise NotImplementedError
327
325
328 def __iter__(self):
326 def __iter__(self):
329 for commit_id in self.commit_ids:
327 for commit_id in self.commit_ids:
330 yield self.get_commit(commit_id=commit_id)
328 yield self.get_commit(commit_id=commit_id)
331
329
332 def get_commits(
330 def get_commits(
333 self, start_id=None, end_id=None, start_date=None, end_date=None,
331 self, start_id=None, end_id=None, start_date=None, end_date=None,
334 branch_name=None, show_hidden=False, pre_load=None):
332 branch_name=None, show_hidden=False, pre_load=None):
335 """
333 """
336 Returns iterator of `BaseCommit` objects from start to end
334 Returns iterator of `BaseCommit` objects from start to end
337 not inclusive. This should behave just like a list, ie. end is not
335 not inclusive. This should behave just like a list, ie. end is not
338 inclusive.
336 inclusive.
339
337
340 :param start_id: None or str, must be a valid commit id
338 :param start_id: None or str, must be a valid commit id
341 :param end_id: None or str, must be a valid commit id
339 :param end_id: None or str, must be a valid commit id
342 :param start_date:
340 :param start_date:
343 :param end_date:
341 :param end_date:
344 :param branch_name:
342 :param branch_name:
345 :param show_hidden:
343 :param show_hidden:
346 :param pre_load:
344 :param pre_load:
347 """
345 """
348 raise NotImplementedError
346 raise NotImplementedError
349
347
350 def __getitem__(self, key):
348 def __getitem__(self, key):
351 """
349 """
352 Allows index based access to the commit objects of this repository.
350 Allows index based access to the commit objects of this repository.
353 """
351 """
354 pre_load = ["author", "branch", "date", "message", "parents"]
352 pre_load = ["author", "branch", "date", "message", "parents"]
355 if isinstance(key, slice):
353 if isinstance(key, slice):
356 return self._get_range(key, pre_load)
354 return self._get_range(key, pre_load)
357 return self.get_commit(commit_idx=key, pre_load=pre_load)
355 return self.get_commit(commit_idx=key, pre_load=pre_load)
358
356
359 def _get_range(self, slice_obj, pre_load):
357 def _get_range(self, slice_obj, pre_load):
360 for commit_id in self.commit_ids.__getitem__(slice_obj):
358 for commit_id in self.commit_ids.__getitem__(slice_obj):
361 yield self.get_commit(commit_id=commit_id, pre_load=pre_load)
359 yield self.get_commit(commit_id=commit_id, pre_load=pre_load)
362
360
363 def count(self):
361 def count(self):
364 return len(self.commit_ids)
362 return len(self.commit_ids)
365
363
366 def tag(self, name, user, commit_id=None, message=None, date=None, **opts):
364 def tag(self, name, user, commit_id=None, message=None, date=None, **opts):
367 """
365 """
368 Creates and returns a tag for the given ``commit_id``.
366 Creates and returns a tag for the given ``commit_id``.
369
367
370 :param name: name for new tag
368 :param name: name for new tag
371 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
369 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
372 :param commit_id: commit id for which new tag would be created
370 :param commit_id: commit id for which new tag would be created
373 :param message: message of the tag's commit
371 :param message: message of the tag's commit
374 :param date: date of tag's commit
372 :param date: date of tag's commit
375
373
376 :raises TagAlreadyExistError: if tag with same name already exists
374 :raises TagAlreadyExistError: if tag with same name already exists
377 """
375 """
378 raise NotImplementedError
376 raise NotImplementedError
379
377
380 def remove_tag(self, name, user, message=None, date=None):
378 def remove_tag(self, name, user, message=None, date=None):
381 """
379 """
382 Removes tag with the given ``name``.
380 Removes tag with the given ``name``.
383
381
384 :param name: name of the tag to be removed
382 :param name: name of the tag to be removed
385 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
383 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
386 :param message: message of the tag's removal commit
384 :param message: message of the tag's removal commit
387 :param date: date of tag's removal commit
385 :param date: date of tag's removal commit
388
386
389 :raises TagDoesNotExistError: if tag with given name does not exists
387 :raises TagDoesNotExistError: if tag with given name does not exists
390 """
388 """
391 raise NotImplementedError
389 raise NotImplementedError
392
390
393 def get_diff(
391 def get_diff(
394 self, commit1, commit2, path=None, ignore_whitespace=False,
392 self, commit1, commit2, path=None, ignore_whitespace=False,
395 context=3, path1=None):
393 context=3, path1=None):
396 """
394 """
397 Returns (git like) *diff*, as plain text. Shows changes introduced by
395 Returns (git like) *diff*, as plain text. Shows changes introduced by
398 `commit2` since `commit1`.
396 `commit2` since `commit1`.
399
397
400 :param commit1: Entry point from which diff is shown. Can be
398 :param commit1: Entry point from which diff is shown. Can be
401 ``self.EMPTY_COMMIT`` - in this case, patch showing all
399 ``self.EMPTY_COMMIT`` - in this case, patch showing all
402 the changes since empty state of the repository until `commit2`
400 the changes since empty state of the repository until `commit2`
403 :param commit2: Until which commit changes should be shown.
401 :param commit2: Until which commit changes should be shown.
404 :param path: Can be set to a path of a file to create a diff of that
402 :param path: Can be set to a path of a file to create a diff of that
405 file. If `path1` is also set, this value is only associated to
403 file. If `path1` is also set, this value is only associated to
406 `commit2`.
404 `commit2`.
407 :param ignore_whitespace: If set to ``True``, would not show whitespace
405 :param ignore_whitespace: If set to ``True``, would not show whitespace
408 changes. Defaults to ``False``.
406 changes. Defaults to ``False``.
409 :param context: How many lines before/after changed lines should be
407 :param context: How many lines before/after changed lines should be
410 shown. Defaults to ``3``.
408 shown. Defaults to ``3``.
411 :param path1: Can be set to a path to associate with `commit1`. This
409 :param path1: Can be set to a path to associate with `commit1`. This
412 parameter works only for backends which support diff generation for
410 parameter works only for backends which support diff generation for
413 different paths. Other backends will raise a `ValueError` if `path1`
411 different paths. Other backends will raise a `ValueError` if `path1`
414 is set and has a different value than `path`.
412 is set and has a different value than `path`.
415 :param file_path: filter this diff by given path pattern
413 :param file_path: filter this diff by given path pattern
416 """
414 """
417 raise NotImplementedError
415 raise NotImplementedError
418
416
419 def strip(self, commit_id, branch=None):
417 def strip(self, commit_id, branch=None):
420 """
418 """
421 Strip given commit_id from the repository
419 Strip given commit_id from the repository
422 """
420 """
423 raise NotImplementedError
421 raise NotImplementedError
424
422
425 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
423 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
426 """
424 """
427 Return a latest common ancestor commit if one exists for this repo
425 Return a latest common ancestor commit if one exists for this repo
428 `commit_id1` vs `commit_id2` from `repo2`.
426 `commit_id1` vs `commit_id2` from `repo2`.
429
427
430 :param commit_id1: Commit it from this repository to use as a
428 :param commit_id1: Commit it from this repository to use as a
431 target for the comparison.
429 target for the comparison.
432 :param commit_id2: Source commit id to use for comparison.
430 :param commit_id2: Source commit id to use for comparison.
433 :param repo2: Source repository to use for comparison.
431 :param repo2: Source repository to use for comparison.
434 """
432 """
435 raise NotImplementedError
433 raise NotImplementedError
436
434
437 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
435 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
438 """
436 """
439 Compare this repository's revision `commit_id1` with `commit_id2`.
437 Compare this repository's revision `commit_id1` with `commit_id2`.
440
438
441 Returns a tuple(commits, ancestor) that would be merged from
439 Returns a tuple(commits, ancestor) that would be merged from
442 `commit_id2`. Doing a normal compare (``merge=False``), ``None``
440 `commit_id2`. Doing a normal compare (``merge=False``), ``None``
443 will be returned as ancestor.
441 will be returned as ancestor.
444
442
445 :param commit_id1: Commit it from this repository to use as a
443 :param commit_id1: Commit it from this repository to use as a
446 target for the comparison.
444 target for the comparison.
447 :param commit_id2: Source commit id to use for comparison.
445 :param commit_id2: Source commit id to use for comparison.
448 :param repo2: Source repository to use for comparison.
446 :param repo2: Source repository to use for comparison.
449 :param merge: If set to ``True`` will do a merge compare which also
447 :param merge: If set to ``True`` will do a merge compare which also
450 returns the common ancestor.
448 returns the common ancestor.
451 :param pre_load: Optional. List of commit attributes to load.
449 :param pre_load: Optional. List of commit attributes to load.
452 """
450 """
453 raise NotImplementedError
451 raise NotImplementedError
454
452
455 def merge(self, target_ref, source_repo, source_ref, workspace_id,
453 def merge(self, target_ref, source_repo, source_ref, workspace_id,
456 user_name='', user_email='', message='', dry_run=False,
454 user_name='', user_email='', message='', dry_run=False,
457 use_rebase=False, close_branch=False):
455 use_rebase=False, close_branch=False):
458 """
456 """
459 Merge the revisions specified in `source_ref` from `source_repo`
457 Merge the revisions specified in `source_ref` from `source_repo`
460 onto the `target_ref` of this repository.
458 onto the `target_ref` of this repository.
461
459
462 `source_ref` and `target_ref` are named tupls with the following
460 `source_ref` and `target_ref` are named tupls with the following
463 fields `type`, `name` and `commit_id`.
461 fields `type`, `name` and `commit_id`.
464
462
465 Returns a MergeResponse named tuple with the following fields
463 Returns a MergeResponse named tuple with the following fields
466 'possible', 'executed', 'source_commit', 'target_commit',
464 'possible', 'executed', 'source_commit', 'target_commit',
467 'merge_commit'.
465 'merge_commit'.
468
466
469 :param target_ref: `target_ref` points to the commit on top of which
467 :param target_ref: `target_ref` points to the commit on top of which
470 the `source_ref` should be merged.
468 the `source_ref` should be merged.
471 :param source_repo: The repository that contains the commits to be
469 :param source_repo: The repository that contains the commits to be
472 merged.
470 merged.
473 :param source_ref: `source_ref` points to the topmost commit from
471 :param source_ref: `source_ref` points to the topmost commit from
474 the `source_repo` which should be merged.
472 the `source_repo` which should be merged.
475 :param workspace_id: `workspace_id` unique identifier.
473 :param workspace_id: `workspace_id` unique identifier.
476 :param user_name: Merge commit `user_name`.
474 :param user_name: Merge commit `user_name`.
477 :param user_email: Merge commit `user_email`.
475 :param user_email: Merge commit `user_email`.
478 :param message: Merge commit `message`.
476 :param message: Merge commit `message`.
479 :param dry_run: If `True` the merge will not take place.
477 :param dry_run: If `True` the merge will not take place.
480 :param use_rebase: If `True` commits from the source will be rebased
478 :param use_rebase: If `True` commits from the source will be rebased
481 on top of the target instead of being merged.
479 on top of the target instead of being merged.
482 :param close_branch: If `True` branch will be close before merging it
480 :param close_branch: If `True` branch will be close before merging it
483 """
481 """
484 if dry_run:
482 if dry_run:
485 message = message or 'dry_run_merge_message'
483 message = message or 'dry_run_merge_message'
486 user_email = user_email or 'dry-run-merge@rhodecode.com'
484 user_email = user_email or 'dry-run-merge@rhodecode.com'
487 user_name = user_name or 'Dry-Run User'
485 user_name = user_name or 'Dry-Run User'
488 else:
486 else:
489 if not user_name:
487 if not user_name:
490 raise ValueError('user_name cannot be empty')
488 raise ValueError('user_name cannot be empty')
491 if not user_email:
489 if not user_email:
492 raise ValueError('user_email cannot be empty')
490 raise ValueError('user_email cannot be empty')
493 if not message:
491 if not message:
494 raise ValueError('message cannot be empty')
492 raise ValueError('message cannot be empty')
495
493
496 shadow_repository_path = self._maybe_prepare_merge_workspace(
494 shadow_repository_path = self._maybe_prepare_merge_workspace(
497 workspace_id, target_ref, source_ref)
495 workspace_id, target_ref, source_ref)
498
496
499 try:
497 try:
500 return self._merge_repo(
498 return self._merge_repo(
501 shadow_repository_path, target_ref, source_repo,
499 shadow_repository_path, target_ref, source_repo,
502 source_ref, message, user_name, user_email, dry_run=dry_run,
500 source_ref, message, user_name, user_email, dry_run=dry_run,
503 use_rebase=use_rebase, close_branch=close_branch)
501 use_rebase=use_rebase, close_branch=close_branch)
504 except RepositoryError:
502 except RepositoryError:
505 log.exception(
503 log.exception(
506 'Unexpected failure when running merge, dry-run=%s',
504 'Unexpected failure when running merge, dry-run=%s',
507 dry_run)
505 dry_run)
508 return MergeResponse(
506 return MergeResponse(
509 False, False, None, MergeFailureReason.UNKNOWN)
507 False, False, None, MergeFailureReason.UNKNOWN)
510
508
511 def _merge_repo(self, shadow_repository_path, target_ref,
509 def _merge_repo(self, shadow_repository_path, target_ref,
512 source_repo, source_ref, merge_message,
510 source_repo, source_ref, merge_message,
513 merger_name, merger_email, dry_run=False,
511 merger_name, merger_email, dry_run=False,
514 use_rebase=False, close_branch=False):
512 use_rebase=False, close_branch=False):
515 """Internal implementation of merge."""
513 """Internal implementation of merge."""
516 raise NotImplementedError
514 raise NotImplementedError
517
515
518 def _maybe_prepare_merge_workspace(self, workspace_id, target_ref, source_ref):
516 def _maybe_prepare_merge_workspace(self, workspace_id, target_ref, source_ref):
519 """
517 """
520 Create the merge workspace.
518 Create the merge workspace.
521
519
522 :param workspace_id: `workspace_id` unique identifier.
520 :param workspace_id: `workspace_id` unique identifier.
523 """
521 """
524 raise NotImplementedError
522 raise NotImplementedError
525
523
526 def cleanup_merge_workspace(self, workspace_id):
524 def cleanup_merge_workspace(self, workspace_id):
527 """
525 """
528 Remove merge workspace.
526 Remove merge workspace.
529
527
530 This function MUST not fail in case there is no workspace associated to
528 This function MUST not fail in case there is no workspace associated to
531 the given `workspace_id`.
529 the given `workspace_id`.
532
530
533 :param workspace_id: `workspace_id` unique identifier.
531 :param workspace_id: `workspace_id` unique identifier.
534 """
532 """
535 raise NotImplementedError
533 raise NotImplementedError
536
534
537 # ========== #
535 # ========== #
538 # COMMIT API #
536 # COMMIT API #
539 # ========== #
537 # ========== #
540
538
541 @LazyProperty
539 @LazyProperty
542 def in_memory_commit(self):
540 def in_memory_commit(self):
543 """
541 """
544 Returns :class:`InMemoryCommit` object for this repository.
542 Returns :class:`InMemoryCommit` object for this repository.
545 """
543 """
546 raise NotImplementedError
544 raise NotImplementedError
547
545
548 # ======================== #
546 # ======================== #
549 # UTILITIES FOR SUBCLASSES #
547 # UTILITIES FOR SUBCLASSES #
550 # ======================== #
548 # ======================== #
551
549
552 def _validate_diff_commits(self, commit1, commit2):
550 def _validate_diff_commits(self, commit1, commit2):
553 """
551 """
554 Validates that the given commits are related to this repository.
552 Validates that the given commits are related to this repository.
555
553
556 Intended as a utility for sub classes to have a consistent validation
554 Intended as a utility for sub classes to have a consistent validation
557 of input parameters in methods like :meth:`get_diff`.
555 of input parameters in methods like :meth:`get_diff`.
558 """
556 """
559 self._validate_commit(commit1)
557 self._validate_commit(commit1)
560 self._validate_commit(commit2)
558 self._validate_commit(commit2)
561 if (isinstance(commit1, EmptyCommit) and
559 if (isinstance(commit1, EmptyCommit) and
562 isinstance(commit2, EmptyCommit)):
560 isinstance(commit2, EmptyCommit)):
563 raise ValueError("Cannot compare two empty commits")
561 raise ValueError("Cannot compare two empty commits")
564
562
565 def _validate_commit(self, commit):
563 def _validate_commit(self, commit):
566 if not isinstance(commit, BaseCommit):
564 if not isinstance(commit, BaseCommit):
567 raise TypeError(
565 raise TypeError(
568 "%s is not of type BaseCommit" % repr(commit))
566 "%s is not of type BaseCommit" % repr(commit))
569 if commit.repository != self and not isinstance(commit, EmptyCommit):
567 if commit.repository != self and not isinstance(commit, EmptyCommit):
570 raise ValueError(
568 raise ValueError(
571 "Commit %s must be a valid commit from this repository %s, "
569 "Commit %s must be a valid commit from this repository %s, "
572 "related to this repository instead %s." %
570 "related to this repository instead %s." %
573 (commit, self, commit.repository))
571 (commit, self, commit.repository))
574
572
575 def _validate_commit_id(self, commit_id):
573 def _validate_commit_id(self, commit_id):
576 if not isinstance(commit_id, basestring):
574 if not isinstance(commit_id, basestring):
577 raise TypeError("commit_id must be a string value")
575 raise TypeError("commit_id must be a string value")
578
576
579 def _validate_commit_idx(self, commit_idx):
577 def _validate_commit_idx(self, commit_idx):
580 if not isinstance(commit_idx, (int, long)):
578 if not isinstance(commit_idx, (int, long)):
581 raise TypeError("commit_idx must be a numeric value")
579 raise TypeError("commit_idx must be a numeric value")
582
580
583 def _validate_branch_name(self, branch_name):
581 def _validate_branch_name(self, branch_name):
584 if branch_name and branch_name not in self.branches_all:
582 if branch_name and branch_name not in self.branches_all:
585 msg = ("Branch %s not found in %s" % (branch_name, self))
583 msg = ("Branch %s not found in %s" % (branch_name, self))
586 raise BranchDoesNotExistError(msg)
584 raise BranchDoesNotExistError(msg)
587
585
588 #
586 #
589 # Supporting deprecated API parts
587 # Supporting deprecated API parts
590 # TODO: johbo: consider to move this into a mixin
588 # TODO: johbo: consider to move this into a mixin
591 #
589 #
592
590
593 @property
591 @property
594 def EMPTY_CHANGESET(self):
592 def EMPTY_CHANGESET(self):
595 warnings.warn(
593 warnings.warn(
596 "Use EMPTY_COMMIT or EMPTY_COMMIT_ID instead", DeprecationWarning)
594 "Use EMPTY_COMMIT or EMPTY_COMMIT_ID instead", DeprecationWarning)
597 return self.EMPTY_COMMIT_ID
595 return self.EMPTY_COMMIT_ID
598
596
599 @property
597 @property
600 def revisions(self):
598 def revisions(self):
601 warnings.warn("Use commits attribute instead", DeprecationWarning)
599 warnings.warn("Use commits attribute instead", DeprecationWarning)
602 return self.commit_ids
600 return self.commit_ids
603
601
604 @revisions.setter
602 @revisions.setter
605 def revisions(self, value):
603 def revisions(self, value):
606 warnings.warn("Use commits attribute instead", DeprecationWarning)
604 warnings.warn("Use commits attribute instead", DeprecationWarning)
607 self.commit_ids = value
605 self.commit_ids = value
608
606
609 def get_changeset(self, revision=None, pre_load=None):
607 def get_changeset(self, revision=None, pre_load=None):
610 warnings.warn("Use get_commit instead", DeprecationWarning)
608 warnings.warn("Use get_commit instead", DeprecationWarning)
611 commit_id = None
609 commit_id = None
612 commit_idx = None
610 commit_idx = None
613 if isinstance(revision, basestring):
611 if isinstance(revision, basestring):
614 commit_id = revision
612 commit_id = revision
615 else:
613 else:
616 commit_idx = revision
614 commit_idx = revision
617 return self.get_commit(
615 return self.get_commit(
618 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
616 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
619
617
620 def get_changesets(
618 def get_changesets(
621 self, start=None, end=None, start_date=None, end_date=None,
619 self, start=None, end=None, start_date=None, end_date=None,
622 branch_name=None, pre_load=None):
620 branch_name=None, pre_load=None):
623 warnings.warn("Use get_commits instead", DeprecationWarning)
621 warnings.warn("Use get_commits instead", DeprecationWarning)
624 start_id = self._revision_to_commit(start)
622 start_id = self._revision_to_commit(start)
625 end_id = self._revision_to_commit(end)
623 end_id = self._revision_to_commit(end)
626 return self.get_commits(
624 return self.get_commits(
627 start_id=start_id, end_id=end_id, start_date=start_date,
625 start_id=start_id, end_id=end_id, start_date=start_date,
628 end_date=end_date, branch_name=branch_name, pre_load=pre_load)
626 end_date=end_date, branch_name=branch_name, pre_load=pre_load)
629
627
630 def _revision_to_commit(self, revision):
628 def _revision_to_commit(self, revision):
631 """
629 """
632 Translates a revision to a commit_id
630 Translates a revision to a commit_id
633
631
634 Helps to support the old changeset based API which allows to use
632 Helps to support the old changeset based API which allows to use
635 commit ids and commit indices interchangeable.
633 commit ids and commit indices interchangeable.
636 """
634 """
637 if revision is None:
635 if revision is None:
638 return revision
636 return revision
639
637
640 if isinstance(revision, basestring):
638 if isinstance(revision, basestring):
641 commit_id = revision
639 commit_id = revision
642 else:
640 else:
643 commit_id = self.commit_ids[revision]
641 commit_id = self.commit_ids[revision]
644 return commit_id
642 return commit_id
645
643
646 @property
644 @property
647 def in_memory_changeset(self):
645 def in_memory_changeset(self):
648 warnings.warn("Use in_memory_commit instead", DeprecationWarning)
646 warnings.warn("Use in_memory_commit instead", DeprecationWarning)
649 return self.in_memory_commit
647 return self.in_memory_commit
650
648
651 def get_path_permissions(self, username):
649 def get_path_permissions(self, username):
652 """
650 """
653 Returns a path permission checker or None if not supported
651 Returns a path permission checker or None if not supported
654
652
655 :param username: session user name
653 :param username: session user name
656 :return: an instance of BasePathPermissionChecker or None
654 :return: an instance of BasePathPermissionChecker or None
657 """
655 """
658 return None
656 return None
659
657
660 def install_hooks(self, force=False):
658 def install_hooks(self, force=False):
661 return self._remote.install_hooks(force)
659 return self._remote.install_hooks(force)
662
660
663
661
664 class BaseCommit(object):
662 class BaseCommit(object):
665 """
663 """
666 Each backend should implement it's commit representation.
664 Each backend should implement it's commit representation.
667
665
668 **Attributes**
666 **Attributes**
669
667
670 ``repository``
668 ``repository``
671 repository object within which commit exists
669 repository object within which commit exists
672
670
673 ``id``
671 ``id``
674 The commit id, may be ``raw_id`` or i.e. for mercurial's tip
672 The commit id, may be ``raw_id`` or i.e. for mercurial's tip
675 just ``tip``.
673 just ``tip``.
676
674
677 ``raw_id``
675 ``raw_id``
678 raw commit representation (i.e. full 40 length sha for git
676 raw commit representation (i.e. full 40 length sha for git
679 backend)
677 backend)
680
678
681 ``short_id``
679 ``short_id``
682 shortened (if apply) version of ``raw_id``; it would be simple
680 shortened (if apply) version of ``raw_id``; it would be simple
683 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
681 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
684 as ``raw_id`` for subversion
682 as ``raw_id`` for subversion
685
683
686 ``idx``
684 ``idx``
687 commit index
685 commit index
688
686
689 ``files``
687 ``files``
690 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
688 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
691
689
692 ``dirs``
690 ``dirs``
693 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
691 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
694
692
695 ``nodes``
693 ``nodes``
696 combined list of ``Node`` objects
694 combined list of ``Node`` objects
697
695
698 ``author``
696 ``author``
699 author of the commit, as unicode
697 author of the commit, as unicode
700
698
701 ``message``
699 ``message``
702 message of the commit, as unicode
700 message of the commit, as unicode
703
701
704 ``parents``
702 ``parents``
705 list of parent commits
703 list of parent commits
706
704
707 """
705 """
708
706
709 branch = None
707 branch = None
710 """
708 """
711 Depending on the backend this should be set to the branch name of the
709 Depending on the backend this should be set to the branch name of the
712 commit. Backends not supporting branches on commits should leave this
710 commit. Backends not supporting branches on commits should leave this
713 value as ``None``.
711 value as ``None``.
714 """
712 """
715
713
716 _ARCHIVE_PREFIX_TEMPLATE = b'{repo_name}-{short_id}'
714 _ARCHIVE_PREFIX_TEMPLATE = b'{repo_name}-{short_id}'
717 """
715 """
718 This template is used to generate a default prefix for repository archives
716 This template is used to generate a default prefix for repository archives
719 if no prefix has been specified.
717 if no prefix has been specified.
720 """
718 """
721
719
722 def __str__(self):
720 def __str__(self):
723 return '<%s at %s:%s>' % (
721 return '<%s at %s:%s>' % (
724 self.__class__.__name__, self.idx, self.short_id)
722 self.__class__.__name__, self.idx, self.short_id)
725
723
726 def __repr__(self):
724 def __repr__(self):
727 return self.__str__()
725 return self.__str__()
728
726
729 def __unicode__(self):
727 def __unicode__(self):
730 return u'%s:%s' % (self.idx, self.short_id)
728 return u'%s:%s' % (self.idx, self.short_id)
731
729
732 def __eq__(self, other):
730 def __eq__(self, other):
733 same_instance = isinstance(other, self.__class__)
731 same_instance = isinstance(other, self.__class__)
734 return same_instance and self.raw_id == other.raw_id
732 return same_instance and self.raw_id == other.raw_id
735
733
736 def __json__(self):
734 def __json__(self):
737 parents = []
735 parents = []
738 try:
736 try:
739 for parent in self.parents:
737 for parent in self.parents:
740 parents.append({'raw_id': parent.raw_id})
738 parents.append({'raw_id': parent.raw_id})
741 except NotImplementedError:
739 except NotImplementedError:
742 # empty commit doesn't have parents implemented
740 # empty commit doesn't have parents implemented
743 pass
741 pass
744
742
745 return {
743 return {
746 'short_id': self.short_id,
744 'short_id': self.short_id,
747 'raw_id': self.raw_id,
745 'raw_id': self.raw_id,
748 'revision': self.idx,
746 'revision': self.idx,
749 'message': self.message,
747 'message': self.message,
750 'date': self.date,
748 'date': self.date,
751 'author': self.author,
749 'author': self.author,
752 'parents': parents,
750 'parents': parents,
753 'branch': self.branch
751 'branch': self.branch
754 }
752 }
755
753
756 def __getstate__(self):
754 def __getstate__(self):
757 d = self.__dict__.copy()
755 d = self.__dict__.copy()
758 d.pop('_remote', None)
756 d.pop('_remote', None)
759 d.pop('repository', None)
757 d.pop('repository', None)
760 return d
758 return d
761
759
762 def _get_refs(self):
760 def _get_refs(self):
763 return {
761 return {
764 'branches': [self.branch] if self.branch else [],
762 'branches': [self.branch] if self.branch else [],
765 'bookmarks': getattr(self, 'bookmarks', []),
763 'bookmarks': getattr(self, 'bookmarks', []),
766 'tags': self.tags
764 'tags': self.tags
767 }
765 }
768
766
769 @LazyProperty
767 @LazyProperty
770 def last(self):
768 def last(self):
771 """
769 """
772 ``True`` if this is last commit in repository, ``False``
770 ``True`` if this is last commit in repository, ``False``
773 otherwise; trying to access this attribute while there is no
771 otherwise; trying to access this attribute while there is no
774 commits would raise `EmptyRepositoryError`
772 commits would raise `EmptyRepositoryError`
775 """
773 """
776 if self.repository is None:
774 if self.repository is None:
777 raise CommitError("Cannot check if it's most recent commit")
775 raise CommitError("Cannot check if it's most recent commit")
778 return self.raw_id == self.repository.commit_ids[-1]
776 return self.raw_id == self.repository.commit_ids[-1]
779
777
780 @LazyProperty
778 @LazyProperty
781 def parents(self):
779 def parents(self):
782 """
780 """
783 Returns list of parent commits.
781 Returns list of parent commits.
784 """
782 """
785 raise NotImplementedError
783 raise NotImplementedError
786
784
787 @property
785 @property
788 def merge(self):
786 def merge(self):
789 """
787 """
790 Returns boolean if commit is a merge.
788 Returns boolean if commit is a merge.
791 """
789 """
792 return len(self.parents) > 1
790 return len(self.parents) > 1
793
791
794 @LazyProperty
792 @LazyProperty
795 def children(self):
793 def children(self):
796 """
794 """
797 Returns list of child commits.
795 Returns list of child commits.
798 """
796 """
799 raise NotImplementedError
797 raise NotImplementedError
800
798
801 @LazyProperty
799 @LazyProperty
802 def id(self):
800 def id(self):
803 """
801 """
804 Returns string identifying this commit.
802 Returns string identifying this commit.
805 """
803 """
806 raise NotImplementedError
804 raise NotImplementedError
807
805
808 @LazyProperty
806 @LazyProperty
809 def raw_id(self):
807 def raw_id(self):
810 """
808 """
811 Returns raw string identifying this commit.
809 Returns raw string identifying this commit.
812 """
810 """
813 raise NotImplementedError
811 raise NotImplementedError
814
812
815 @LazyProperty
813 @LazyProperty
816 def short_id(self):
814 def short_id(self):
817 """
815 """
818 Returns shortened version of ``raw_id`` attribute, as string,
816 Returns shortened version of ``raw_id`` attribute, as string,
819 identifying this commit, useful for presentation to users.
817 identifying this commit, useful for presentation to users.
820 """
818 """
821 raise NotImplementedError
819 raise NotImplementedError
822
820
823 @LazyProperty
821 @LazyProperty
824 def idx(self):
822 def idx(self):
825 """
823 """
826 Returns integer identifying this commit.
824 Returns integer identifying this commit.
827 """
825 """
828 raise NotImplementedError
826 raise NotImplementedError
829
827
830 @LazyProperty
828 @LazyProperty
831 def committer(self):
829 def committer(self):
832 """
830 """
833 Returns committer for this commit
831 Returns committer for this commit
834 """
832 """
835 raise NotImplementedError
833 raise NotImplementedError
836
834
837 @LazyProperty
835 @LazyProperty
838 def committer_name(self):
836 def committer_name(self):
839 """
837 """
840 Returns committer name for this commit
838 Returns committer name for this commit
841 """
839 """
842
840
843 return author_name(self.committer)
841 return author_name(self.committer)
844
842
845 @LazyProperty
843 @LazyProperty
846 def committer_email(self):
844 def committer_email(self):
847 """
845 """
848 Returns committer email address for this commit
846 Returns committer email address for this commit
849 """
847 """
850
848
851 return author_email(self.committer)
849 return author_email(self.committer)
852
850
853 @LazyProperty
851 @LazyProperty
854 def author(self):
852 def author(self):
855 """
853 """
856 Returns author for this commit
854 Returns author for this commit
857 """
855 """
858
856
859 raise NotImplementedError
857 raise NotImplementedError
860
858
861 @LazyProperty
859 @LazyProperty
862 def author_name(self):
860 def author_name(self):
863 """
861 """
864 Returns author name for this commit
862 Returns author name for this commit
865 """
863 """
866
864
867 return author_name(self.author)
865 return author_name(self.author)
868
866
869 @LazyProperty
867 @LazyProperty
870 def author_email(self):
868 def author_email(self):
871 """
869 """
872 Returns author email address for this commit
870 Returns author email address for this commit
873 """
871 """
874
872
875 return author_email(self.author)
873 return author_email(self.author)
876
874
877 def get_file_mode(self, path):
875 def get_file_mode(self, path):
878 """
876 """
879 Returns stat mode of the file at `path`.
877 Returns stat mode of the file at `path`.
880 """
878 """
881 raise NotImplementedError
879 raise NotImplementedError
882
880
883 def is_link(self, path):
881 def is_link(self, path):
884 """
882 """
885 Returns ``True`` if given `path` is a symlink
883 Returns ``True`` if given `path` is a symlink
886 """
884 """
887 raise NotImplementedError
885 raise NotImplementedError
888
886
889 def get_file_content(self, path):
887 def get_file_content(self, path):
890 """
888 """
891 Returns content of the file at the given `path`.
889 Returns content of the file at the given `path`.
892 """
890 """
893 raise NotImplementedError
891 raise NotImplementedError
894
892
895 def get_file_size(self, path):
893 def get_file_size(self, path):
896 """
894 """
897 Returns size of the file at the given `path`.
895 Returns size of the file at the given `path`.
898 """
896 """
899 raise NotImplementedError
897 raise NotImplementedError
900
898
901 def get_file_commit(self, path, pre_load=None):
899 def get_file_commit(self, path, pre_load=None):
902 """
900 """
903 Returns last commit of the file at the given `path`.
901 Returns last commit of the file at the given `path`.
904
902
905 :param pre_load: Optional. List of commit attributes to load.
903 :param pre_load: Optional. List of commit attributes to load.
906 """
904 """
907 commits = self.get_file_history(path, limit=1, pre_load=pre_load)
905 commits = self.get_file_history(path, limit=1, pre_load=pre_load)
908 if not commits:
906 if not commits:
909 raise RepositoryError(
907 raise RepositoryError(
910 'Failed to fetch history for path {}. '
908 'Failed to fetch history for path {}. '
911 'Please check if such path exists in your repository'.format(
909 'Please check if such path exists in your repository'.format(
912 path))
910 path))
913 return commits[0]
911 return commits[0]
914
912
915 def get_file_history(self, path, limit=None, pre_load=None):
913 def get_file_history(self, path, limit=None, pre_load=None):
916 """
914 """
917 Returns history of file as reversed list of :class:`BaseCommit`
915 Returns history of file as reversed list of :class:`BaseCommit`
918 objects for which file at given `path` has been modified.
916 objects for which file at given `path` has been modified.
919
917
920 :param limit: Optional. Allows to limit the size of the returned
918 :param limit: Optional. Allows to limit the size of the returned
921 history. This is intended as a hint to the underlying backend, so
919 history. This is intended as a hint to the underlying backend, so
922 that it can apply optimizations depending on the limit.
920 that it can apply optimizations depending on the limit.
923 :param pre_load: Optional. List of commit attributes to load.
921 :param pre_load: Optional. List of commit attributes to load.
924 """
922 """
925 raise NotImplementedError
923 raise NotImplementedError
926
924
927 def get_file_annotate(self, path, pre_load=None):
925 def get_file_annotate(self, path, pre_load=None):
928 """
926 """
929 Returns a generator of four element tuples with
927 Returns a generator of four element tuples with
930 lineno, sha, commit lazy loader and line
928 lineno, sha, commit lazy loader and line
931
929
932 :param pre_load: Optional. List of commit attributes to load.
930 :param pre_load: Optional. List of commit attributes to load.
933 """
931 """
934 raise NotImplementedError
932 raise NotImplementedError
935
933
936 def get_nodes(self, path):
934 def get_nodes(self, path):
937 """
935 """
938 Returns combined ``DirNode`` and ``FileNode`` objects list representing
936 Returns combined ``DirNode`` and ``FileNode`` objects list representing
939 state of commit at the given ``path``.
937 state of commit at the given ``path``.
940
938
941 :raises ``CommitError``: if node at the given ``path`` is not
939 :raises ``CommitError``: if node at the given ``path`` is not
942 instance of ``DirNode``
940 instance of ``DirNode``
943 """
941 """
944 raise NotImplementedError
942 raise NotImplementedError
945
943
946 def get_node(self, path):
944 def get_node(self, path):
947 """
945 """
948 Returns ``Node`` object from the given ``path``.
946 Returns ``Node`` object from the given ``path``.
949
947
950 :raises ``NodeDoesNotExistError``: if there is no node at the given
948 :raises ``NodeDoesNotExistError``: if there is no node at the given
951 ``path``
949 ``path``
952 """
950 """
953 raise NotImplementedError
951 raise NotImplementedError
954
952
955 def get_largefile_node(self, path):
953 def get_largefile_node(self, path):
956 """
954 """
957 Returns the path to largefile from Mercurial/Git-lfs storage.
955 Returns the path to largefile from Mercurial/Git-lfs storage.
958 or None if it's not a largefile node
956 or None if it's not a largefile node
959 """
957 """
960 return None
958 return None
961
959
962 def archive_repo(self, file_path, kind='tgz', subrepos=None,
960 def archive_repo(self, file_path, kind='tgz', subrepos=None,
963 prefix=None, write_metadata=False, mtime=None):
961 prefix=None, write_metadata=False, mtime=None):
964 """
962 """
965 Creates an archive containing the contents of the repository.
963 Creates an archive containing the contents of the repository.
966
964
967 :param file_path: path to the file which to create the archive.
965 :param file_path: path to the file which to create the archive.
968 :param kind: one of following: ``"tbz2"``, ``"tgz"``, ``"zip"``.
966 :param kind: one of following: ``"tbz2"``, ``"tgz"``, ``"zip"``.
969 :param prefix: name of root directory in archive.
967 :param prefix: name of root directory in archive.
970 Default is repository name and commit's short_id joined with dash:
968 Default is repository name and commit's short_id joined with dash:
971 ``"{repo_name}-{short_id}"``.
969 ``"{repo_name}-{short_id}"``.
972 :param write_metadata: write a metadata file into archive.
970 :param write_metadata: write a metadata file into archive.
973 :param mtime: custom modification time for archive creation, defaults
971 :param mtime: custom modification time for archive creation, defaults
974 to time.time() if not given.
972 to time.time() if not given.
975
973
976 :raise VCSError: If prefix has a problem.
974 :raise VCSError: If prefix has a problem.
977 """
975 """
978 allowed_kinds = settings.ARCHIVE_SPECS.keys()
976 allowed_kinds = settings.ARCHIVE_SPECS.keys()
979 if kind not in allowed_kinds:
977 if kind not in allowed_kinds:
980 raise ImproperArchiveTypeError(
978 raise ImproperArchiveTypeError(
981 'Archive kind (%s) not supported use one of %s' %
979 'Archive kind (%s) not supported use one of %s' %
982 (kind, allowed_kinds))
980 (kind, allowed_kinds))
983
981
984 prefix = self._validate_archive_prefix(prefix)
982 prefix = self._validate_archive_prefix(prefix)
985
983
986 mtime = mtime or time.mktime(self.date.timetuple())
984 mtime = mtime or time.mktime(self.date.timetuple())
987
985
988 file_info = []
986 file_info = []
989 cur_rev = self.repository.get_commit(commit_id=self.raw_id)
987 cur_rev = self.repository.get_commit(commit_id=self.raw_id)
990 for _r, _d, files in cur_rev.walk('/'):
988 for _r, _d, files in cur_rev.walk('/'):
991 for f in files:
989 for f in files:
992 f_path = os.path.join(prefix, f.path)
990 f_path = os.path.join(prefix, f.path)
993 file_info.append(
991 file_info.append(
994 (f_path, f.mode, f.is_link(), f.raw_bytes))
992 (f_path, f.mode, f.is_link(), f.raw_bytes))
995
993
996 if write_metadata:
994 if write_metadata:
997 metadata = [
995 metadata = [
998 ('repo_name', self.repository.name),
996 ('repo_name', self.repository.name),
999 ('rev', self.raw_id),
997 ('rev', self.raw_id),
1000 ('create_time', mtime),
998 ('create_time', mtime),
1001 ('branch', self.branch),
999 ('branch', self.branch),
1002 ('tags', ','.join(self.tags)),
1000 ('tags', ','.join(self.tags)),
1003 ]
1001 ]
1004 meta = ["%s:%s" % (f_name, value) for f_name, value in metadata]
1002 meta = ["%s:%s" % (f_name, value) for f_name, value in metadata]
1005 file_info.append(('.archival.txt', 0644, False, '\n'.join(meta)))
1003 file_info.append(('.archival.txt', 0644, False, '\n'.join(meta)))
1006
1004
1007 connection.Hg.archive_repo(file_path, mtime, file_info, kind)
1005 connection.Hg.archive_repo(file_path, mtime, file_info, kind)
1008
1006
1009 def _validate_archive_prefix(self, prefix):
1007 def _validate_archive_prefix(self, prefix):
1010 if prefix is None:
1008 if prefix is None:
1011 prefix = self._ARCHIVE_PREFIX_TEMPLATE.format(
1009 prefix = self._ARCHIVE_PREFIX_TEMPLATE.format(
1012 repo_name=safe_str(self.repository.name),
1010 repo_name=safe_str(self.repository.name),
1013 short_id=self.short_id)
1011 short_id=self.short_id)
1014 elif not isinstance(prefix, str):
1012 elif not isinstance(prefix, str):
1015 raise ValueError("prefix not a bytes object: %s" % repr(prefix))
1013 raise ValueError("prefix not a bytes object: %s" % repr(prefix))
1016 elif prefix.startswith('/'):
1014 elif prefix.startswith('/'):
1017 raise VCSError("Prefix cannot start with leading slash")
1015 raise VCSError("Prefix cannot start with leading slash")
1018 elif prefix.strip() == '':
1016 elif prefix.strip() == '':
1019 raise VCSError("Prefix cannot be empty")
1017 raise VCSError("Prefix cannot be empty")
1020 return prefix
1018 return prefix
1021
1019
1022 @LazyProperty
1020 @LazyProperty
1023 def root(self):
1021 def root(self):
1024 """
1022 """
1025 Returns ``RootNode`` object for this commit.
1023 Returns ``RootNode`` object for this commit.
1026 """
1024 """
1027 return self.get_node('')
1025 return self.get_node('')
1028
1026
1029 def next(self, branch=None):
1027 def next(self, branch=None):
1030 """
1028 """
1031 Returns next commit from current, if branch is gives it will return
1029 Returns next commit from current, if branch is gives it will return
1032 next commit belonging to this branch
1030 next commit belonging to this branch
1033
1031
1034 :param branch: show commits within the given named branch
1032 :param branch: show commits within the given named branch
1035 """
1033 """
1036 indexes = xrange(self.idx + 1, self.repository.count())
1034 indexes = xrange(self.idx + 1, self.repository.count())
1037 return self._find_next(indexes, branch)
1035 return self._find_next(indexes, branch)
1038
1036
1039 def prev(self, branch=None):
1037 def prev(self, branch=None):
1040 """
1038 """
1041 Returns previous commit from current, if branch is gives it will
1039 Returns previous commit from current, if branch is gives it will
1042 return previous commit belonging to this branch
1040 return previous commit belonging to this branch
1043
1041
1044 :param branch: show commit within the given named branch
1042 :param branch: show commit within the given named branch
1045 """
1043 """
1046 indexes = xrange(self.idx - 1, -1, -1)
1044 indexes = xrange(self.idx - 1, -1, -1)
1047 return self._find_next(indexes, branch)
1045 return self._find_next(indexes, branch)
1048
1046
1049 def _find_next(self, indexes, branch=None):
1047 def _find_next(self, indexes, branch=None):
1050 if branch and self.branch != branch:
1048 if branch and self.branch != branch:
1051 raise VCSError('Branch option used on commit not belonging '
1049 raise VCSError('Branch option used on commit not belonging '
1052 'to that branch')
1050 'to that branch')
1053
1051
1054 for next_idx in indexes:
1052 for next_idx in indexes:
1055 commit = self.repository.get_commit(commit_idx=next_idx)
1053 commit = self.repository.get_commit(commit_idx=next_idx)
1056 if branch and branch != commit.branch:
1054 if branch and branch != commit.branch:
1057 continue
1055 continue
1058 return commit
1056 return commit
1059 raise CommitDoesNotExistError
1057 raise CommitDoesNotExistError
1060
1058
1061 def diff(self, ignore_whitespace=True, context=3):
1059 def diff(self, ignore_whitespace=True, context=3):
1062 """
1060 """
1063 Returns a `Diff` object representing the change made by this commit.
1061 Returns a `Diff` object representing the change made by this commit.
1064 """
1062 """
1065 parent = (
1063 parent = (
1066 self.parents[0] if self.parents else self.repository.EMPTY_COMMIT)
1064 self.parents[0] if self.parents else self.repository.EMPTY_COMMIT)
1067 diff = self.repository.get_diff(
1065 diff = self.repository.get_diff(
1068 parent, self,
1066 parent, self,
1069 ignore_whitespace=ignore_whitespace,
1067 ignore_whitespace=ignore_whitespace,
1070 context=context)
1068 context=context)
1071 return diff
1069 return diff
1072
1070
1073 @LazyProperty
1071 @LazyProperty
1074 def added(self):
1072 def added(self):
1075 """
1073 """
1076 Returns list of added ``FileNode`` objects.
1074 Returns list of added ``FileNode`` objects.
1077 """
1075 """
1078 raise NotImplementedError
1076 raise NotImplementedError
1079
1077
1080 @LazyProperty
1078 @LazyProperty
1081 def changed(self):
1079 def changed(self):
1082 """
1080 """
1083 Returns list of modified ``FileNode`` objects.
1081 Returns list of modified ``FileNode`` objects.
1084 """
1082 """
1085 raise NotImplementedError
1083 raise NotImplementedError
1086
1084
1087 @LazyProperty
1085 @LazyProperty
1088 def removed(self):
1086 def removed(self):
1089 """
1087 """
1090 Returns list of removed ``FileNode`` objects.
1088 Returns list of removed ``FileNode`` objects.
1091 """
1089 """
1092 raise NotImplementedError
1090 raise NotImplementedError
1093
1091
1094 @LazyProperty
1092 @LazyProperty
1095 def size(self):
1093 def size(self):
1096 """
1094 """
1097 Returns total number of bytes from contents of all filenodes.
1095 Returns total number of bytes from contents of all filenodes.
1098 """
1096 """
1099 return sum((node.size for node in self.get_filenodes_generator()))
1097 return sum((node.size for node in self.get_filenodes_generator()))
1100
1098
1101 def walk(self, topurl=''):
1099 def walk(self, topurl=''):
1102 """
1100 """
1103 Similar to os.walk method. Insted of filesystem it walks through
1101 Similar to os.walk method. Insted of filesystem it walks through
1104 commit starting at given ``topurl``. Returns generator of tuples
1102 commit starting at given ``topurl``. Returns generator of tuples
1105 (topnode, dirnodes, filenodes).
1103 (topnode, dirnodes, filenodes).
1106 """
1104 """
1107 topnode = self.get_node(topurl)
1105 topnode = self.get_node(topurl)
1108 if not topnode.is_dir():
1106 if not topnode.is_dir():
1109 return
1107 return
1110 yield (topnode, topnode.dirs, topnode.files)
1108 yield (topnode, topnode.dirs, topnode.files)
1111 for dirnode in topnode.dirs:
1109 for dirnode in topnode.dirs:
1112 for tup in self.walk(dirnode.path):
1110 for tup in self.walk(dirnode.path):
1113 yield tup
1111 yield tup
1114
1112
1115 def get_filenodes_generator(self):
1113 def get_filenodes_generator(self):
1116 """
1114 """
1117 Returns generator that yields *all* file nodes.
1115 Returns generator that yields *all* file nodes.
1118 """
1116 """
1119 for topnode, dirs, files in self.walk():
1117 for topnode, dirs, files in self.walk():
1120 for node in files:
1118 for node in files:
1121 yield node
1119 yield node
1122
1120
1123 #
1121 #
1124 # Utilities for sub classes to support consistent behavior
1122 # Utilities for sub classes to support consistent behavior
1125 #
1123 #
1126
1124
1127 def no_node_at_path(self, path):
1125 def no_node_at_path(self, path):
1128 return NodeDoesNotExistError(
1126 return NodeDoesNotExistError(
1129 u"There is no file nor directory at the given path: "
1127 u"There is no file nor directory at the given path: "
1130 u"`%s` at commit %s" % (safe_unicode(path), self.short_id))
1128 u"`%s` at commit %s" % (safe_unicode(path), self.short_id))
1131
1129
1132 def _fix_path(self, path):
1130 def _fix_path(self, path):
1133 """
1131 """
1134 Paths are stored without trailing slash so we need to get rid off it if
1132 Paths are stored without trailing slash so we need to get rid off it if
1135 needed.
1133 needed.
1136 """
1134 """
1137 return path.rstrip('/')
1135 return path.rstrip('/')
1138
1136
1139 #
1137 #
1140 # Deprecated API based on changesets
1138 # Deprecated API based on changesets
1141 #
1139 #
1142
1140
1143 @property
1141 @property
1144 def revision(self):
1142 def revision(self):
1145 warnings.warn("Use idx instead", DeprecationWarning)
1143 warnings.warn("Use idx instead", DeprecationWarning)
1146 return self.idx
1144 return self.idx
1147
1145
1148 @revision.setter
1146 @revision.setter
1149 def revision(self, value):
1147 def revision(self, value):
1150 warnings.warn("Use idx instead", DeprecationWarning)
1148 warnings.warn("Use idx instead", DeprecationWarning)
1151 self.idx = value
1149 self.idx = value
1152
1150
1153 def get_file_changeset(self, path):
1151 def get_file_changeset(self, path):
1154 warnings.warn("Use get_file_commit instead", DeprecationWarning)
1152 warnings.warn("Use get_file_commit instead", DeprecationWarning)
1155 return self.get_file_commit(path)
1153 return self.get_file_commit(path)
1156
1154
1157
1155
1158 class BaseChangesetClass(type):
1156 class BaseChangesetClass(type):
1159
1157
1160 def __instancecheck__(self, instance):
1158 def __instancecheck__(self, instance):
1161 return isinstance(instance, BaseCommit)
1159 return isinstance(instance, BaseCommit)
1162
1160
1163
1161
1164 class BaseChangeset(BaseCommit):
1162 class BaseChangeset(BaseCommit):
1165
1163
1166 __metaclass__ = BaseChangesetClass
1164 __metaclass__ = BaseChangesetClass
1167
1165
1168 def __new__(cls, *args, **kwargs):
1166 def __new__(cls, *args, **kwargs):
1169 warnings.warn(
1167 warnings.warn(
1170 "Use BaseCommit instead of BaseChangeset", DeprecationWarning)
1168 "Use BaseCommit instead of BaseChangeset", DeprecationWarning)
1171 return super(BaseChangeset, cls).__new__(cls, *args, **kwargs)
1169 return super(BaseChangeset, cls).__new__(cls, *args, **kwargs)
1172
1170
1173
1171
1174 class BaseInMemoryCommit(object):
1172 class BaseInMemoryCommit(object):
1175 """
1173 """
1176 Represents differences between repository's state (most recent head) and
1174 Represents differences between repository's state (most recent head) and
1177 changes made *in place*.
1175 changes made *in place*.
1178
1176
1179 **Attributes**
1177 **Attributes**
1180
1178
1181 ``repository``
1179 ``repository``
1182 repository object for this in-memory-commit
1180 repository object for this in-memory-commit
1183
1181
1184 ``added``
1182 ``added``
1185 list of ``FileNode`` objects marked as *added*
1183 list of ``FileNode`` objects marked as *added*
1186
1184
1187 ``changed``
1185 ``changed``
1188 list of ``FileNode`` objects marked as *changed*
1186 list of ``FileNode`` objects marked as *changed*
1189
1187
1190 ``removed``
1188 ``removed``
1191 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
1189 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
1192 *removed*
1190 *removed*
1193
1191
1194 ``parents``
1192 ``parents``
1195 list of :class:`BaseCommit` instances representing parents of
1193 list of :class:`BaseCommit` instances representing parents of
1196 in-memory commit. Should always be 2-element sequence.
1194 in-memory commit. Should always be 2-element sequence.
1197
1195
1198 """
1196 """
1199
1197
1200 def __init__(self, repository):
1198 def __init__(self, repository):
1201 self.repository = repository
1199 self.repository = repository
1202 self.added = []
1200 self.added = []
1203 self.changed = []
1201 self.changed = []
1204 self.removed = []
1202 self.removed = []
1205 self.parents = []
1203 self.parents = []
1206
1204
1207 def add(self, *filenodes):
1205 def add(self, *filenodes):
1208 """
1206 """
1209 Marks given ``FileNode`` objects as *to be committed*.
1207 Marks given ``FileNode`` objects as *to be committed*.
1210
1208
1211 :raises ``NodeAlreadyExistsError``: if node with same path exists at
1209 :raises ``NodeAlreadyExistsError``: if node with same path exists at
1212 latest commit
1210 latest commit
1213 :raises ``NodeAlreadyAddedError``: if node with same path is already
1211 :raises ``NodeAlreadyAddedError``: if node with same path is already
1214 marked as *added*
1212 marked as *added*
1215 """
1213 """
1216 # Check if not already marked as *added* first
1214 # Check if not already marked as *added* first
1217 for node in filenodes:
1215 for node in filenodes:
1218 if node.path in (n.path for n in self.added):
1216 if node.path in (n.path for n in self.added):
1219 raise NodeAlreadyAddedError(
1217 raise NodeAlreadyAddedError(
1220 "Such FileNode %s is already marked for addition"
1218 "Such FileNode %s is already marked for addition"
1221 % node.path)
1219 % node.path)
1222 for node in filenodes:
1220 for node in filenodes:
1223 self.added.append(node)
1221 self.added.append(node)
1224
1222
1225 def change(self, *filenodes):
1223 def change(self, *filenodes):
1226 """
1224 """
1227 Marks given ``FileNode`` objects to be *changed* in next commit.
1225 Marks given ``FileNode`` objects to be *changed* in next commit.
1228
1226
1229 :raises ``EmptyRepositoryError``: if there are no commits yet
1227 :raises ``EmptyRepositoryError``: if there are no commits yet
1230 :raises ``NodeAlreadyExistsError``: if node with same path is already
1228 :raises ``NodeAlreadyExistsError``: if node with same path is already
1231 marked to be *changed*
1229 marked to be *changed*
1232 :raises ``NodeAlreadyRemovedError``: if node with same path is already
1230 :raises ``NodeAlreadyRemovedError``: if node with same path is already
1233 marked to be *removed*
1231 marked to be *removed*
1234 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
1232 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
1235 commit
1233 commit
1236 :raises ``NodeNotChangedError``: if node hasn't really be changed
1234 :raises ``NodeNotChangedError``: if node hasn't really be changed
1237 """
1235 """
1238 for node in filenodes:
1236 for node in filenodes:
1239 if node.path in (n.path for n in self.removed):
1237 if node.path in (n.path for n in self.removed):
1240 raise NodeAlreadyRemovedError(
1238 raise NodeAlreadyRemovedError(
1241 "Node at %s is already marked as removed" % node.path)
1239 "Node at %s is already marked as removed" % node.path)
1242 try:
1240 try:
1243 self.repository.get_commit()
1241 self.repository.get_commit()
1244 except EmptyRepositoryError:
1242 except EmptyRepositoryError:
1245 raise EmptyRepositoryError(
1243 raise EmptyRepositoryError(
1246 "Nothing to change - try to *add* new nodes rather than "
1244 "Nothing to change - try to *add* new nodes rather than "
1247 "changing them")
1245 "changing them")
1248 for node in filenodes:
1246 for node in filenodes:
1249 if node.path in (n.path for n in self.changed):
1247 if node.path in (n.path for n in self.changed):
1250 raise NodeAlreadyChangedError(
1248 raise NodeAlreadyChangedError(
1251 "Node at '%s' is already marked as changed" % node.path)
1249 "Node at '%s' is already marked as changed" % node.path)
1252 self.changed.append(node)
1250 self.changed.append(node)
1253
1251
1254 def remove(self, *filenodes):
1252 def remove(self, *filenodes):
1255 """
1253 """
1256 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
1254 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
1257 *removed* in next commit.
1255 *removed* in next commit.
1258
1256
1259 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
1257 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
1260 be *removed*
1258 be *removed*
1261 :raises ``NodeAlreadyChangedError``: if node has been already marked to
1259 :raises ``NodeAlreadyChangedError``: if node has been already marked to
1262 be *changed*
1260 be *changed*
1263 """
1261 """
1264 for node in filenodes:
1262 for node in filenodes:
1265 if node.path in (n.path for n in self.removed):
1263 if node.path in (n.path for n in self.removed):
1266 raise NodeAlreadyRemovedError(
1264 raise NodeAlreadyRemovedError(
1267 "Node is already marked to for removal at %s" % node.path)
1265 "Node is already marked to for removal at %s" % node.path)
1268 if node.path in (n.path for n in self.changed):
1266 if node.path in (n.path for n in self.changed):
1269 raise NodeAlreadyChangedError(
1267 raise NodeAlreadyChangedError(
1270 "Node is already marked to be changed at %s" % node.path)
1268 "Node is already marked to be changed at %s" % node.path)
1271 # We only mark node as *removed* - real removal is done by
1269 # We only mark node as *removed* - real removal is done by
1272 # commit method
1270 # commit method
1273 self.removed.append(node)
1271 self.removed.append(node)
1274
1272
1275 def reset(self):
1273 def reset(self):
1276 """
1274 """
1277 Resets this instance to initial state (cleans ``added``, ``changed``
1275 Resets this instance to initial state (cleans ``added``, ``changed``
1278 and ``removed`` lists).
1276 and ``removed`` lists).
1279 """
1277 """
1280 self.added = []
1278 self.added = []
1281 self.changed = []
1279 self.changed = []
1282 self.removed = []
1280 self.removed = []
1283 self.parents = []
1281 self.parents = []
1284
1282
1285 def get_ipaths(self):
1283 def get_ipaths(self):
1286 """
1284 """
1287 Returns generator of paths from nodes marked as added, changed or
1285 Returns generator of paths from nodes marked as added, changed or
1288 removed.
1286 removed.
1289 """
1287 """
1290 for node in itertools.chain(self.added, self.changed, self.removed):
1288 for node in itertools.chain(self.added, self.changed, self.removed):
1291 yield node.path
1289 yield node.path
1292
1290
1293 def get_paths(self):
1291 def get_paths(self):
1294 """
1292 """
1295 Returns list of paths from nodes marked as added, changed or removed.
1293 Returns list of paths from nodes marked as added, changed or removed.
1296 """
1294 """
1297 return list(self.get_ipaths())
1295 return list(self.get_ipaths())
1298
1296
1299 def check_integrity(self, parents=None):
1297 def check_integrity(self, parents=None):
1300 """
1298 """
1301 Checks in-memory commit's integrity. Also, sets parents if not
1299 Checks in-memory commit's integrity. Also, sets parents if not
1302 already set.
1300 already set.
1303
1301
1304 :raises CommitError: if any error occurs (i.e.
1302 :raises CommitError: if any error occurs (i.e.
1305 ``NodeDoesNotExistError``).
1303 ``NodeDoesNotExistError``).
1306 """
1304 """
1307 if not self.parents:
1305 if not self.parents:
1308 parents = parents or []
1306 parents = parents or []
1309 if len(parents) == 0:
1307 if len(parents) == 0:
1310 try:
1308 try:
1311 parents = [self.repository.get_commit(), None]
1309 parents = [self.repository.get_commit(), None]
1312 except EmptyRepositoryError:
1310 except EmptyRepositoryError:
1313 parents = [None, None]
1311 parents = [None, None]
1314 elif len(parents) == 1:
1312 elif len(parents) == 1:
1315 parents += [None]
1313 parents += [None]
1316 self.parents = parents
1314 self.parents = parents
1317
1315
1318 # Local parents, only if not None
1316 # Local parents, only if not None
1319 parents = [p for p in self.parents if p]
1317 parents = [p for p in self.parents if p]
1320
1318
1321 # Check nodes marked as added
1319 # Check nodes marked as added
1322 for p in parents:
1320 for p in parents:
1323 for node in self.added:
1321 for node in self.added:
1324 try:
1322 try:
1325 p.get_node(node.path)
1323 p.get_node(node.path)
1326 except NodeDoesNotExistError:
1324 except NodeDoesNotExistError:
1327 pass
1325 pass
1328 else:
1326 else:
1329 raise NodeAlreadyExistsError(
1327 raise NodeAlreadyExistsError(
1330 "Node `%s` already exists at %s" % (node.path, p))
1328 "Node `%s` already exists at %s" % (node.path, p))
1331
1329
1332 # Check nodes marked as changed
1330 # Check nodes marked as changed
1333 missing = set(self.changed)
1331 missing = set(self.changed)
1334 not_changed = set(self.changed)
1332 not_changed = set(self.changed)
1335 if self.changed and not parents:
1333 if self.changed and not parents:
1336 raise NodeDoesNotExistError(str(self.changed[0].path))
1334 raise NodeDoesNotExistError(str(self.changed[0].path))
1337 for p in parents:
1335 for p in parents:
1338 for node in self.changed:
1336 for node in self.changed:
1339 try:
1337 try:
1340 old = p.get_node(node.path)
1338 old = p.get_node(node.path)
1341 missing.remove(node)
1339 missing.remove(node)
1342 # if content actually changed, remove node from not_changed
1340 # if content actually changed, remove node from not_changed
1343 if old.content != node.content:
1341 if old.content != node.content:
1344 not_changed.remove(node)
1342 not_changed.remove(node)
1345 except NodeDoesNotExistError:
1343 except NodeDoesNotExistError:
1346 pass
1344 pass
1347 if self.changed and missing:
1345 if self.changed and missing:
1348 raise NodeDoesNotExistError(
1346 raise NodeDoesNotExistError(
1349 "Node `%s` marked as modified but missing in parents: %s"
1347 "Node `%s` marked as modified but missing in parents: %s"
1350 % (node.path, parents))
1348 % (node.path, parents))
1351
1349
1352 if self.changed and not_changed:
1350 if self.changed and not_changed:
1353 raise NodeNotChangedError(
1351 raise NodeNotChangedError(
1354 "Node `%s` wasn't actually changed (parents: %s)"
1352 "Node `%s` wasn't actually changed (parents: %s)"
1355 % (not_changed.pop().path, parents))
1353 % (not_changed.pop().path, parents))
1356
1354
1357 # Check nodes marked as removed
1355 # Check nodes marked as removed
1358 if self.removed and not parents:
1356 if self.removed and not parents:
1359 raise NodeDoesNotExistError(
1357 raise NodeDoesNotExistError(
1360 "Cannot remove node at %s as there "
1358 "Cannot remove node at %s as there "
1361 "were no parents specified" % self.removed[0].path)
1359 "were no parents specified" % self.removed[0].path)
1362 really_removed = set()
1360 really_removed = set()
1363 for p in parents:
1361 for p in parents:
1364 for node in self.removed:
1362 for node in self.removed:
1365 try:
1363 try:
1366 p.get_node(node.path)
1364 p.get_node(node.path)
1367 really_removed.add(node)
1365 really_removed.add(node)
1368 except CommitError:
1366 except CommitError:
1369 pass
1367 pass
1370 not_removed = set(self.removed) - really_removed
1368 not_removed = set(self.removed) - really_removed
1371 if not_removed:
1369 if not_removed:
1372 # TODO: johbo: This code branch does not seem to be covered
1370 # TODO: johbo: This code branch does not seem to be covered
1373 raise NodeDoesNotExistError(
1371 raise NodeDoesNotExistError(
1374 "Cannot remove node at %s from "
1372 "Cannot remove node at %s from "
1375 "following parents: %s" % (not_removed, parents))
1373 "following parents: %s" % (not_removed, parents))
1376
1374
1377 def commit(
1375 def commit(
1378 self, message, author, parents=None, branch=None, date=None,
1376 self, message, author, parents=None, branch=None, date=None,
1379 **kwargs):
1377 **kwargs):
1380 """
1378 """
1381 Performs in-memory commit (doesn't check workdir in any way) and
1379 Performs in-memory commit (doesn't check workdir in any way) and
1382 returns newly created :class:`BaseCommit`. Updates repository's
1380 returns newly created :class:`BaseCommit`. Updates repository's
1383 attribute `commits`.
1381 attribute `commits`.
1384
1382
1385 .. note::
1383 .. note::
1386
1384
1387 While overriding this method each backend's should call
1385 While overriding this method each backend's should call
1388 ``self.check_integrity(parents)`` in the first place.
1386 ``self.check_integrity(parents)`` in the first place.
1389
1387
1390 :param message: message of the commit
1388 :param message: message of the commit
1391 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
1389 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
1392 :param parents: single parent or sequence of parents from which commit
1390 :param parents: single parent or sequence of parents from which commit
1393 would be derived
1391 would be derived
1394 :param date: ``datetime.datetime`` instance. Defaults to
1392 :param date: ``datetime.datetime`` instance. Defaults to
1395 ``datetime.datetime.now()``.
1393 ``datetime.datetime.now()``.
1396 :param branch: branch name, as string. If none given, default backend's
1394 :param branch: branch name, as string. If none given, default backend's
1397 branch would be used.
1395 branch would be used.
1398
1396
1399 :raises ``CommitError``: if any error occurs while committing
1397 :raises ``CommitError``: if any error occurs while committing
1400 """
1398 """
1401 raise NotImplementedError
1399 raise NotImplementedError
1402
1400
1403
1401
1404 class BaseInMemoryChangesetClass(type):
1402 class BaseInMemoryChangesetClass(type):
1405
1403
1406 def __instancecheck__(self, instance):
1404 def __instancecheck__(self, instance):
1407 return isinstance(instance, BaseInMemoryCommit)
1405 return isinstance(instance, BaseInMemoryCommit)
1408
1406
1409
1407
1410 class BaseInMemoryChangeset(BaseInMemoryCommit):
1408 class BaseInMemoryChangeset(BaseInMemoryCommit):
1411
1409
1412 __metaclass__ = BaseInMemoryChangesetClass
1410 __metaclass__ = BaseInMemoryChangesetClass
1413
1411
1414 def __new__(cls, *args, **kwargs):
1412 def __new__(cls, *args, **kwargs):
1415 warnings.warn(
1413 warnings.warn(
1416 "Use BaseCommit instead of BaseInMemoryCommit", DeprecationWarning)
1414 "Use BaseCommit instead of BaseInMemoryCommit", DeprecationWarning)
1417 return super(BaseInMemoryChangeset, cls).__new__(cls, *args, **kwargs)
1415 return super(BaseInMemoryChangeset, cls).__new__(cls, *args, **kwargs)
1418
1416
1419
1417
1420 class EmptyCommit(BaseCommit):
1418 class EmptyCommit(BaseCommit):
1421 """
1419 """
1422 An dummy empty commit. It's possible to pass hash when creating
1420 An dummy empty commit. It's possible to pass hash when creating
1423 an EmptyCommit
1421 an EmptyCommit
1424 """
1422 """
1425
1423
1426 def __init__(
1424 def __init__(
1427 self, commit_id='0' * 40, repo=None, alias=None, idx=-1,
1425 self, commit_id='0' * 40, repo=None, alias=None, idx=-1,
1428 message='', author='', date=None):
1426 message='', author='', date=None):
1429 self._empty_commit_id = commit_id
1427 self._empty_commit_id = commit_id
1430 # TODO: johbo: Solve idx parameter, default value does not make
1428 # TODO: johbo: Solve idx parameter, default value does not make
1431 # too much sense
1429 # too much sense
1432 self.idx = idx
1430 self.idx = idx
1433 self.message = message
1431 self.message = message
1434 self.author = author
1432 self.author = author
1435 self.date = date or datetime.datetime.fromtimestamp(0)
1433 self.date = date or datetime.datetime.fromtimestamp(0)
1436 self.repository = repo
1434 self.repository = repo
1437 self.alias = alias
1435 self.alias = alias
1438
1436
1439 @LazyProperty
1437 @LazyProperty
1440 def raw_id(self):
1438 def raw_id(self):
1441 """
1439 """
1442 Returns raw string identifying this commit, useful for web
1440 Returns raw string identifying this commit, useful for web
1443 representation.
1441 representation.
1444 """
1442 """
1445
1443
1446 return self._empty_commit_id
1444 return self._empty_commit_id
1447
1445
1448 @LazyProperty
1446 @LazyProperty
1449 def branch(self):
1447 def branch(self):
1450 if self.alias:
1448 if self.alias:
1451 from rhodecode.lib.vcs.backends import get_backend
1449 from rhodecode.lib.vcs.backends import get_backend
1452 return get_backend(self.alias).DEFAULT_BRANCH_NAME
1450 return get_backend(self.alias).DEFAULT_BRANCH_NAME
1453
1451
1454 @LazyProperty
1452 @LazyProperty
1455 def short_id(self):
1453 def short_id(self):
1456 return self.raw_id[:12]
1454 return self.raw_id[:12]
1457
1455
1458 @LazyProperty
1456 @LazyProperty
1459 def id(self):
1457 def id(self):
1460 return self.raw_id
1458 return self.raw_id
1461
1459
1462 def get_file_commit(self, path):
1460 def get_file_commit(self, path):
1463 return self
1461 return self
1464
1462
1465 def get_file_content(self, path):
1463 def get_file_content(self, path):
1466 return u''
1464 return u''
1467
1465
1468 def get_file_size(self, path):
1466 def get_file_size(self, path):
1469 return 0
1467 return 0
1470
1468
1471
1469
1472 class EmptyChangesetClass(type):
1470 class EmptyChangesetClass(type):
1473
1471
1474 def __instancecheck__(self, instance):
1472 def __instancecheck__(self, instance):
1475 return isinstance(instance, EmptyCommit)
1473 return isinstance(instance, EmptyCommit)
1476
1474
1477
1475
1478 class EmptyChangeset(EmptyCommit):
1476 class EmptyChangeset(EmptyCommit):
1479
1477
1480 __metaclass__ = EmptyChangesetClass
1478 __metaclass__ = EmptyChangesetClass
1481
1479
1482 def __new__(cls, *args, **kwargs):
1480 def __new__(cls, *args, **kwargs):
1483 warnings.warn(
1481 warnings.warn(
1484 "Use EmptyCommit instead of EmptyChangeset", DeprecationWarning)
1482 "Use EmptyCommit instead of EmptyChangeset", DeprecationWarning)
1485 return super(EmptyCommit, cls).__new__(cls, *args, **kwargs)
1483 return super(EmptyCommit, cls).__new__(cls, *args, **kwargs)
1486
1484
1487 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
1485 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
1488 alias=None, revision=-1, message='', author='', date=None):
1486 alias=None, revision=-1, message='', author='', date=None):
1489 if requested_revision is not None:
1487 if requested_revision is not None:
1490 warnings.warn(
1488 warnings.warn(
1491 "Parameter requested_revision not supported anymore",
1489 "Parameter requested_revision not supported anymore",
1492 DeprecationWarning)
1490 DeprecationWarning)
1493 super(EmptyChangeset, self).__init__(
1491 super(EmptyChangeset, self).__init__(
1494 commit_id=cs, repo=repo, alias=alias, idx=revision,
1492 commit_id=cs, repo=repo, alias=alias, idx=revision,
1495 message=message, author=author, date=date)
1493 message=message, author=author, date=date)
1496
1494
1497 @property
1495 @property
1498 def revision(self):
1496 def revision(self):
1499 warnings.warn("Use idx instead", DeprecationWarning)
1497 warnings.warn("Use idx instead", DeprecationWarning)
1500 return self.idx
1498 return self.idx
1501
1499
1502 @revision.setter
1500 @revision.setter
1503 def revision(self, value):
1501 def revision(self, value):
1504 warnings.warn("Use idx instead", DeprecationWarning)
1502 warnings.warn("Use idx instead", DeprecationWarning)
1505 self.idx = value
1503 self.idx = value
1506
1504
1507
1505
1508 class EmptyRepository(BaseRepository):
1506 class EmptyRepository(BaseRepository):
1509 def __init__(self, repo_path=None, config=None, create=False, **kwargs):
1507 def __init__(self, repo_path=None, config=None, create=False, **kwargs):
1510 pass
1508 pass
1511
1509
1512 def get_diff(self, *args, **kwargs):
1510 def get_diff(self, *args, **kwargs):
1513 from rhodecode.lib.vcs.backends.git.diff import GitDiff
1511 from rhodecode.lib.vcs.backends.git.diff import GitDiff
1514 return GitDiff('')
1512 return GitDiff('')
1515
1513
1516
1514
1517 class CollectionGenerator(object):
1515 class CollectionGenerator(object):
1518
1516
1519 def __init__(self, repo, commit_ids, collection_size=None, pre_load=None):
1517 def __init__(self, repo, commit_ids, collection_size=None, pre_load=None):
1520 self.repo = repo
1518 self.repo = repo
1521 self.commit_ids = commit_ids
1519 self.commit_ids = commit_ids
1522 # TODO: (oliver) this isn't currently hooked up
1520 # TODO: (oliver) this isn't currently hooked up
1523 self.collection_size = None
1521 self.collection_size = None
1524 self.pre_load = pre_load
1522 self.pre_load = pre_load
1525
1523
1526 def __len__(self):
1524 def __len__(self):
1527 if self.collection_size is not None:
1525 if self.collection_size is not None:
1528 return self.collection_size
1526 return self.collection_size
1529 return self.commit_ids.__len__()
1527 return self.commit_ids.__len__()
1530
1528
1531 def __iter__(self):
1529 def __iter__(self):
1532 for commit_id in self.commit_ids:
1530 for commit_id in self.commit_ids:
1533 # TODO: johbo: Mercurial passes in commit indices or commit ids
1531 # TODO: johbo: Mercurial passes in commit indices or commit ids
1534 yield self._commit_factory(commit_id)
1532 yield self._commit_factory(commit_id)
1535
1533
1536 def _commit_factory(self, commit_id):
1534 def _commit_factory(self, commit_id):
1537 """
1535 """
1538 Allows backends to override the way commits are generated.
1536 Allows backends to override the way commits are generated.
1539 """
1537 """
1540 return self.repo.get_commit(commit_id=commit_id,
1538 return self.repo.get_commit(commit_id=commit_id,
1541 pre_load=self.pre_load)
1539 pre_load=self.pre_load)
1542
1540
1543 def __getslice__(self, i, j):
1541 def __getslice__(self, i, j):
1544 """
1542 """
1545 Returns an iterator of sliced repository
1543 Returns an iterator of sliced repository
1546 """
1544 """
1547 commit_ids = self.commit_ids[i:j]
1545 commit_ids = self.commit_ids[i:j]
1548 return self.__class__(
1546 return self.__class__(
1549 self.repo, commit_ids, pre_load=self.pre_load)
1547 self.repo, commit_ids, pre_load=self.pre_load)
1550
1548
1551 def __repr__(self):
1549 def __repr__(self):
1552 return '<CollectionGenerator[len:%s]>' % (self.__len__())
1550 return '<CollectionGenerator[len:%s]>' % (self.__len__())
1553
1551
1554
1552
1555 class Config(object):
1553 class Config(object):
1556 """
1554 """
1557 Represents the configuration for a repository.
1555 Represents the configuration for a repository.
1558
1556
1559 The API is inspired by :class:`ConfigParser.ConfigParser` from the
1557 The API is inspired by :class:`ConfigParser.ConfigParser` from the
1560 standard library. It implements only the needed subset.
1558 standard library. It implements only the needed subset.
1561 """
1559 """
1562
1560
1563 def __init__(self):
1561 def __init__(self):
1564 self._values = {}
1562 self._values = {}
1565
1563
1566 def copy(self):
1564 def copy(self):
1567 clone = Config()
1565 clone = Config()
1568 for section, values in self._values.items():
1566 for section, values in self._values.items():
1569 clone._values[section] = values.copy()
1567 clone._values[section] = values.copy()
1570 return clone
1568 return clone
1571
1569
1572 def __repr__(self):
1570 def __repr__(self):
1573 return '<Config(%s sections) at %s>' % (
1571 return '<Config(%s sections) at %s>' % (
1574 len(self._values), hex(id(self)))
1572 len(self._values), hex(id(self)))
1575
1573
1576 def items(self, section):
1574 def items(self, section):
1577 return self._values.get(section, {}).iteritems()
1575 return self._values.get(section, {}).iteritems()
1578
1576
1579 def get(self, section, option):
1577 def get(self, section, option):
1580 return self._values.get(section, {}).get(option)
1578 return self._values.get(section, {}).get(option)
1581
1579
1582 def set(self, section, option, value):
1580 def set(self, section, option, value):
1583 section_values = self._values.setdefault(section, {})
1581 section_values = self._values.setdefault(section, {})
1584 section_values[option] = value
1582 section_values[option] = value
1585
1583
1586 def clear_section(self, section):
1584 def clear_section(self, section):
1587 self._values[section] = {}
1585 self._values[section] = {}
1588
1586
1589 def serialize(self):
1587 def serialize(self):
1590 """
1588 """
1591 Creates a list of three tuples (section, key, value) representing
1589 Creates a list of three tuples (section, key, value) representing
1592 this config object.
1590 this config object.
1593 """
1591 """
1594 items = []
1592 items = []
1595 for section in self._values:
1593 for section in self._values:
1596 for option, value in self._values[section].items():
1594 for option, value in self._values[section].items():
1597 items.append(
1595 items.append(
1598 (safe_str(section), safe_str(option), safe_str(value)))
1596 (safe_str(section), safe_str(option), safe_str(value)))
1599 return items
1597 return items
1600
1598
1601
1599
1602 class Diff(object):
1600 class Diff(object):
1603 """
1601 """
1604 Represents a diff result from a repository backend.
1602 Represents a diff result from a repository backend.
1605
1603
1606 Subclasses have to provide a backend specific value for
1604 Subclasses have to provide a backend specific value for
1607 :attr:`_header_re` and :attr:`_meta_re`.
1605 :attr:`_header_re` and :attr:`_meta_re`.
1608 """
1606 """
1609 _meta_re = None
1607 _meta_re = None
1610 _header_re = None
1608 _header_re = None
1611
1609
1612 def __init__(self, raw_diff):
1610 def __init__(self, raw_diff):
1613 self.raw = raw_diff
1611 self.raw = raw_diff
1614
1612
1615 def chunks(self):
1613 def chunks(self):
1616 """
1614 """
1617 split the diff in chunks of separate --git a/file b/file chunks
1615 split the diff in chunks of separate --git a/file b/file chunks
1618 to make diffs consistent we must prepend with \n, and make sure
1616 to make diffs consistent we must prepend with \n, and make sure
1619 we can detect last chunk as this was also has special rule
1617 we can detect last chunk as this was also has special rule
1620 """
1618 """
1621
1619
1622 diff_parts = ('\n' + self.raw).split('\ndiff --git')
1620 diff_parts = ('\n' + self.raw).split('\ndiff --git')
1623 header = diff_parts[0]
1621 header = diff_parts[0]
1624
1622
1625 if self._meta_re:
1623 if self._meta_re:
1626 match = self._meta_re.match(header)
1624 match = self._meta_re.match(header)
1627
1625
1628 chunks = diff_parts[1:]
1626 chunks = diff_parts[1:]
1629 total_chunks = len(chunks)
1627 total_chunks = len(chunks)
1630
1628
1631 return (
1629 return (
1632 DiffChunk(chunk, self, cur_chunk == total_chunks)
1630 DiffChunk(chunk, self, cur_chunk == total_chunks)
1633 for cur_chunk, chunk in enumerate(chunks, start=1))
1631 for cur_chunk, chunk in enumerate(chunks, start=1))
1634
1632
1635
1633
1636 class DiffChunk(object):
1634 class DiffChunk(object):
1637
1635
1638 def __init__(self, chunk, diff, last_chunk):
1636 def __init__(self, chunk, diff, last_chunk):
1639 self._diff = diff
1637 self._diff = diff
1640
1638
1641 # since we split by \ndiff --git that part is lost from original diff
1639 # since we split by \ndiff --git that part is lost from original diff
1642 # we need to re-apply it at the end, EXCEPT ! if it's last chunk
1640 # we need to re-apply it at the end, EXCEPT ! if it's last chunk
1643 if not last_chunk:
1641 if not last_chunk:
1644 chunk += '\n'
1642 chunk += '\n'
1645
1643
1646 match = self._diff._header_re.match(chunk)
1644 match = self._diff._header_re.match(chunk)
1647 self.header = match.groupdict()
1645 self.header = match.groupdict()
1648 self.diff = chunk[match.end():]
1646 self.diff = chunk[match.end():]
1649 self.raw = chunk
1647 self.raw = chunk
1650
1648
1651
1649
1652 class BasePathPermissionChecker(object):
1650 class BasePathPermissionChecker(object):
1653
1651
1654 @staticmethod
1652 @staticmethod
1655 def create_from_patterns(includes, excludes):
1653 def create_from_patterns(includes, excludes):
1656 if includes and '*' in includes and not excludes:
1654 if includes and '*' in includes and not excludes:
1657 return AllPathPermissionChecker()
1655 return AllPathPermissionChecker()
1658 elif excludes and '*' in excludes:
1656 elif excludes and '*' in excludes:
1659 return NonePathPermissionChecker()
1657 return NonePathPermissionChecker()
1660 else:
1658 else:
1661 return PatternPathPermissionChecker(includes, excludes)
1659 return PatternPathPermissionChecker(includes, excludes)
1662
1660
1663 @property
1661 @property
1664 def has_full_access(self):
1662 def has_full_access(self):
1665 raise NotImplemented()
1663 raise NotImplemented()
1666
1664
1667 def has_access(self, path):
1665 def has_access(self, path):
1668 raise NotImplemented()
1666 raise NotImplemented()
1669
1667
1670
1668
1671 class AllPathPermissionChecker(BasePathPermissionChecker):
1669 class AllPathPermissionChecker(BasePathPermissionChecker):
1672
1670
1673 @property
1671 @property
1674 def has_full_access(self):
1672 def has_full_access(self):
1675 return True
1673 return True
1676
1674
1677 def has_access(self, path):
1675 def has_access(self, path):
1678 return True
1676 return True
1679
1677
1680
1678
1681 class NonePathPermissionChecker(BasePathPermissionChecker):
1679 class NonePathPermissionChecker(BasePathPermissionChecker):
1682
1680
1683 @property
1681 @property
1684 def has_full_access(self):
1682 def has_full_access(self):
1685 return False
1683 return False
1686
1684
1687 def has_access(self, path):
1685 def has_access(self, path):
1688 return False
1686 return False
1689
1687
1690
1688
1691 class PatternPathPermissionChecker(BasePathPermissionChecker):
1689 class PatternPathPermissionChecker(BasePathPermissionChecker):
1692
1690
1693 def __init__(self, includes, excludes):
1691 def __init__(self, includes, excludes):
1694 self.includes = includes
1692 self.includes = includes
1695 self.excludes = excludes
1693 self.excludes = excludes
1696 self.includes_re = [] if not includes else [
1694 self.includes_re = [] if not includes else [
1697 re.compile(fnmatch.translate(pattern)) for pattern in includes]
1695 re.compile(fnmatch.translate(pattern)) for pattern in includes]
1698 self.excludes_re = [] if not excludes else [
1696 self.excludes_re = [] if not excludes else [
1699 re.compile(fnmatch.translate(pattern)) for pattern in excludes]
1697 re.compile(fnmatch.translate(pattern)) for pattern in excludes]
1700
1698
1701 @property
1699 @property
1702 def has_full_access(self):
1700 def has_full_access(self):
1703 return '*' in self.includes and not self.excludes
1701 return '*' in self.includes and not self.excludes
1704
1702
1705 def has_access(self, path):
1703 def has_access(self, path):
1706 for regex in self.excludes_re:
1704 for regex in self.excludes_re:
1707 if regex.match(path):
1705 if regex.match(path):
1708 return False
1706 return False
1709 for regex in self.includes_re:
1707 for regex in self.includes_re:
1710 if regex.match(path):
1708 if regex.match(path):
1711 return True
1709 return True
1712 return False
1710 return False
@@ -1,4481 +1,4494 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37 from sqlalchemy import (
37 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
38 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
41 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false
42 from sqlalchemy.sql.expression import true, false
43 from sqlalchemy.sql.functions import coalesce, count # noqa
43 from sqlalchemy.sql.functions import coalesce, count # noqa
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
45 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # noqa
48 from sqlalchemy.exc import IntegrityError # noqa
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from beaker.cache import cache_region
50 from beaker.cache import cache_region
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
52
52
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54
54
55 from rhodecode.translation import _
55 from rhodecode.translation import _
56 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.utils2 import (
58 from rhodecode.lib.utils2 import (
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 glob2re, StrictAttributeDict, cleaned_uri)
61 glob2re, StrictAttributeDict, cleaned_uri)
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 JsonRaw
63 JsonRaw
64 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.encrypt import AESCipher
66 from rhodecode.lib.encrypt import AESCipher
67
67
68 from rhodecode.model.meta import Base, Session
68 from rhodecode.model.meta import Base, Session
69
69
70 URL_SEP = '/'
70 URL_SEP = '/'
71 log = logging.getLogger(__name__)
71 log = logging.getLogger(__name__)
72
72
73 # =============================================================================
73 # =============================================================================
74 # BASE CLASSES
74 # BASE CLASSES
75 # =============================================================================
75 # =============================================================================
76
76
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # beaker.session.secret if first is not set.
78 # beaker.session.secret if first is not set.
79 # and initialized at environment.py
79 # and initialized at environment.py
80 ENCRYPTION_KEY = None
80 ENCRYPTION_KEY = None
81
81
82 # used to sort permissions by types, '#' used here is not allowed to be in
82 # used to sort permissions by types, '#' used here is not allowed to be in
83 # usernames, and it's very early in sorted string.printable table.
83 # usernames, and it's very early in sorted string.printable table.
84 PERMISSION_TYPE_SORT = {
84 PERMISSION_TYPE_SORT = {
85 'admin': '####',
85 'admin': '####',
86 'write': '###',
86 'write': '###',
87 'read': '##',
87 'read': '##',
88 'none': '#',
88 'none': '#',
89 }
89 }
90
90
91
91
92 def display_user_sort(obj):
92 def display_user_sort(obj):
93 """
93 """
94 Sort function used to sort permissions in .permissions() function of
94 Sort function used to sort permissions in .permissions() function of
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 of all other resources
96 of all other resources
97 """
97 """
98
98
99 if obj.username == User.DEFAULT_USER:
99 if obj.username == User.DEFAULT_USER:
100 return '#####'
100 return '#####'
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 return prefix + obj.username
102 return prefix + obj.username
103
103
104
104
105 def display_user_group_sort(obj):
105 def display_user_group_sort(obj):
106 """
106 """
107 Sort function used to sort permissions in .permissions() function of
107 Sort function used to sort permissions in .permissions() function of
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 of all other resources
109 of all other resources
110 """
110 """
111
111
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 return prefix + obj.users_group_name
113 return prefix + obj.users_group_name
114
114
115
115
116 def _hash_key(k):
116 def _hash_key(k):
117 return md5_safe(k)
117 return md5_safe(k)
118
118
119
119
120 def in_filter_generator(qry, items, limit=500):
120 def in_filter_generator(qry, items, limit=500):
121 """
121 """
122 Splits IN() into multiple with OR
122 Splits IN() into multiple with OR
123 e.g.::
123 e.g.::
124 cnt = Repository.query().filter(
124 cnt = Repository.query().filter(
125 or_(
125 or_(
126 *in_filter_generator(Repository.repo_id, range(100000))
126 *in_filter_generator(Repository.repo_id, range(100000))
127 )).count()
127 )).count()
128 """
128 """
129 if not items:
129 if not items:
130 # empty list will cause empty query which might cause security issues
130 # empty list will cause empty query which might cause security issues
131 # this can lead to hidden unpleasant results
131 # this can lead to hidden unpleasant results
132 items = [-1]
132 items = [-1]
133
133
134 parts = []
134 parts = []
135 for chunk in xrange(0, len(items), limit):
135 for chunk in xrange(0, len(items), limit):
136 parts.append(
136 parts.append(
137 qry.in_(items[chunk: chunk + limit])
137 qry.in_(items[chunk: chunk + limit])
138 )
138 )
139
139
140 return parts
140 return parts
141
141
142
142
143 class EncryptedTextValue(TypeDecorator):
143 class EncryptedTextValue(TypeDecorator):
144 """
144 """
145 Special column for encrypted long text data, use like::
145 Special column for encrypted long text data, use like::
146
146
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
148
148
149 This column is intelligent so if value is in unencrypted form it return
149 This column is intelligent so if value is in unencrypted form it return
150 unencrypted form, but on save it always encrypts
150 unencrypted form, but on save it always encrypts
151 """
151 """
152 impl = Text
152 impl = Text
153
153
154 def process_bind_param(self, value, dialect):
154 def process_bind_param(self, value, dialect):
155 if not value:
155 if not value:
156 return value
156 return value
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
158 # protect against double encrypting if someone manually starts
158 # protect against double encrypting if someone manually starts
159 # doing
159 # doing
160 raise ValueError('value needs to be in unencrypted format, ie. '
160 raise ValueError('value needs to be in unencrypted format, ie. '
161 'not starting with enc$aes')
161 'not starting with enc$aes')
162 return 'enc$aes_hmac$%s' % AESCipher(
162 return 'enc$aes_hmac$%s' % AESCipher(
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
164
164
165 def process_result_value(self, value, dialect):
165 def process_result_value(self, value, dialect):
166 import rhodecode
166 import rhodecode
167
167
168 if not value:
168 if not value:
169 return value
169 return value
170
170
171 parts = value.split('$', 3)
171 parts = value.split('$', 3)
172 if not len(parts) == 3:
172 if not len(parts) == 3:
173 # probably not encrypted values
173 # probably not encrypted values
174 return value
174 return value
175 else:
175 else:
176 if parts[0] != 'enc':
176 if parts[0] != 'enc':
177 # parts ok but without our header ?
177 # parts ok but without our header ?
178 return value
178 return value
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
180 'rhodecode.encrypted_values.strict') or True)
180 'rhodecode.encrypted_values.strict') or True)
181 # at that stage we know it's our encryption
181 # at that stage we know it's our encryption
182 if parts[1] == 'aes':
182 if parts[1] == 'aes':
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
184 elif parts[1] == 'aes_hmac':
184 elif parts[1] == 'aes_hmac':
185 decrypted_data = AESCipher(
185 decrypted_data = AESCipher(
186 ENCRYPTION_KEY, hmac=True,
186 ENCRYPTION_KEY, hmac=True,
187 strict_verification=enc_strict_mode).decrypt(parts[2])
187 strict_verification=enc_strict_mode).decrypt(parts[2])
188 else:
188 else:
189 raise ValueError(
189 raise ValueError(
190 'Encryption type part is wrong, must be `aes` '
190 'Encryption type part is wrong, must be `aes` '
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
192 return decrypted_data
192 return decrypted_data
193
193
194
194
195 class BaseModel(object):
195 class BaseModel(object):
196 """
196 """
197 Base Model for all classes
197 Base Model for all classes
198 """
198 """
199
199
200 @classmethod
200 @classmethod
201 def _get_keys(cls):
201 def _get_keys(cls):
202 """return column names for this model """
202 """return column names for this model """
203 return class_mapper(cls).c.keys()
203 return class_mapper(cls).c.keys()
204
204
205 def get_dict(self):
205 def get_dict(self):
206 """
206 """
207 return dict with keys and values corresponding
207 return dict with keys and values corresponding
208 to this model data """
208 to this model data """
209
209
210 d = {}
210 d = {}
211 for k in self._get_keys():
211 for k in self._get_keys():
212 d[k] = getattr(self, k)
212 d[k] = getattr(self, k)
213
213
214 # also use __json__() if present to get additional fields
214 # also use __json__() if present to get additional fields
215 _json_attr = getattr(self, '__json__', None)
215 _json_attr = getattr(self, '__json__', None)
216 if _json_attr:
216 if _json_attr:
217 # update with attributes from __json__
217 # update with attributes from __json__
218 if callable(_json_attr):
218 if callable(_json_attr):
219 _json_attr = _json_attr()
219 _json_attr = _json_attr()
220 for k, val in _json_attr.iteritems():
220 for k, val in _json_attr.iteritems():
221 d[k] = val
221 d[k] = val
222 return d
222 return d
223
223
224 def get_appstruct(self):
224 def get_appstruct(self):
225 """return list with keys and values tuples corresponding
225 """return list with keys and values tuples corresponding
226 to this model data """
226 to this model data """
227
227
228 lst = []
228 lst = []
229 for k in self._get_keys():
229 for k in self._get_keys():
230 lst.append((k, getattr(self, k),))
230 lst.append((k, getattr(self, k),))
231 return lst
231 return lst
232
232
233 def populate_obj(self, populate_dict):
233 def populate_obj(self, populate_dict):
234 """populate model with data from given populate_dict"""
234 """populate model with data from given populate_dict"""
235
235
236 for k in self._get_keys():
236 for k in self._get_keys():
237 if k in populate_dict:
237 if k in populate_dict:
238 setattr(self, k, populate_dict[k])
238 setattr(self, k, populate_dict[k])
239
239
240 @classmethod
240 @classmethod
241 def query(cls):
241 def query(cls):
242 return Session().query(cls)
242 return Session().query(cls)
243
243
244 @classmethod
244 @classmethod
245 def get(cls, id_):
245 def get(cls, id_):
246 if id_:
246 if id_:
247 return cls.query().get(id_)
247 return cls.query().get(id_)
248
248
249 @classmethod
249 @classmethod
250 def get_or_404(cls, id_):
250 def get_or_404(cls, id_):
251 from pyramid.httpexceptions import HTTPNotFound
251 from pyramid.httpexceptions import HTTPNotFound
252
252
253 try:
253 try:
254 id_ = int(id_)
254 id_ = int(id_)
255 except (TypeError, ValueError):
255 except (TypeError, ValueError):
256 raise HTTPNotFound()
256 raise HTTPNotFound()
257
257
258 res = cls.query().get(id_)
258 res = cls.query().get(id_)
259 if not res:
259 if not res:
260 raise HTTPNotFound()
260 raise HTTPNotFound()
261 return res
261 return res
262
262
263 @classmethod
263 @classmethod
264 def getAll(cls):
264 def getAll(cls):
265 # deprecated and left for backward compatibility
265 # deprecated and left for backward compatibility
266 return cls.get_all()
266 return cls.get_all()
267
267
268 @classmethod
268 @classmethod
269 def get_all(cls):
269 def get_all(cls):
270 return cls.query().all()
270 return cls.query().all()
271
271
272 @classmethod
272 @classmethod
273 def delete(cls, id_):
273 def delete(cls, id_):
274 obj = cls.query().get(id_)
274 obj = cls.query().get(id_)
275 Session().delete(obj)
275 Session().delete(obj)
276
276
277 @classmethod
277 @classmethod
278 def identity_cache(cls, session, attr_name, value):
278 def identity_cache(cls, session, attr_name, value):
279 exist_in_session = []
279 exist_in_session = []
280 for (item_cls, pkey), instance in session.identity_map.items():
280 for (item_cls, pkey), instance in session.identity_map.items():
281 if cls == item_cls and getattr(instance, attr_name) == value:
281 if cls == item_cls and getattr(instance, attr_name) == value:
282 exist_in_session.append(instance)
282 exist_in_session.append(instance)
283 if exist_in_session:
283 if exist_in_session:
284 if len(exist_in_session) == 1:
284 if len(exist_in_session) == 1:
285 return exist_in_session[0]
285 return exist_in_session[0]
286 log.exception(
286 log.exception(
287 'multiple objects with attr %s and '
287 'multiple objects with attr %s and '
288 'value %s found with same name: %r',
288 'value %s found with same name: %r',
289 attr_name, value, exist_in_session)
289 attr_name, value, exist_in_session)
290
290
291 def __repr__(self):
291 def __repr__(self):
292 if hasattr(self, '__unicode__'):
292 if hasattr(self, '__unicode__'):
293 # python repr needs to return str
293 # python repr needs to return str
294 try:
294 try:
295 return safe_str(self.__unicode__())
295 return safe_str(self.__unicode__())
296 except UnicodeDecodeError:
296 except UnicodeDecodeError:
297 pass
297 pass
298 return '<DB:%s>' % (self.__class__.__name__)
298 return '<DB:%s>' % (self.__class__.__name__)
299
299
300
300
301 class RhodeCodeSetting(Base, BaseModel):
301 class RhodeCodeSetting(Base, BaseModel):
302 __tablename__ = 'rhodecode_settings'
302 __tablename__ = 'rhodecode_settings'
303 __table_args__ = (
303 __table_args__ = (
304 UniqueConstraint('app_settings_name'),
304 UniqueConstraint('app_settings_name'),
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
307 )
307 )
308
308
309 SETTINGS_TYPES = {
309 SETTINGS_TYPES = {
310 'str': safe_str,
310 'str': safe_str,
311 'int': safe_int,
311 'int': safe_int,
312 'unicode': safe_unicode,
312 'unicode': safe_unicode,
313 'bool': str2bool,
313 'bool': str2bool,
314 'list': functools.partial(aslist, sep=',')
314 'list': functools.partial(aslist, sep=',')
315 }
315 }
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
317 GLOBAL_CONF_KEY = 'app_settings'
317 GLOBAL_CONF_KEY = 'app_settings'
318
318
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
323
323
324 def __init__(self, key='', val='', type='unicode'):
324 def __init__(self, key='', val='', type='unicode'):
325 self.app_settings_name = key
325 self.app_settings_name = key
326 self.app_settings_type = type
326 self.app_settings_type = type
327 self.app_settings_value = val
327 self.app_settings_value = val
328
328
329 @validates('_app_settings_value')
329 @validates('_app_settings_value')
330 def validate_settings_value(self, key, val):
330 def validate_settings_value(self, key, val):
331 assert type(val) == unicode
331 assert type(val) == unicode
332 return val
332 return val
333
333
334 @hybrid_property
334 @hybrid_property
335 def app_settings_value(self):
335 def app_settings_value(self):
336 v = self._app_settings_value
336 v = self._app_settings_value
337 _type = self.app_settings_type
337 _type = self.app_settings_type
338 if _type:
338 if _type:
339 _type = self.app_settings_type.split('.')[0]
339 _type = self.app_settings_type.split('.')[0]
340 # decode the encrypted value
340 # decode the encrypted value
341 if 'encrypted' in self.app_settings_type:
341 if 'encrypted' in self.app_settings_type:
342 cipher = EncryptedTextValue()
342 cipher = EncryptedTextValue()
343 v = safe_unicode(cipher.process_result_value(v, None))
343 v = safe_unicode(cipher.process_result_value(v, None))
344
344
345 converter = self.SETTINGS_TYPES.get(_type) or \
345 converter = self.SETTINGS_TYPES.get(_type) or \
346 self.SETTINGS_TYPES['unicode']
346 self.SETTINGS_TYPES['unicode']
347 return converter(v)
347 return converter(v)
348
348
349 @app_settings_value.setter
349 @app_settings_value.setter
350 def app_settings_value(self, val):
350 def app_settings_value(self, val):
351 """
351 """
352 Setter that will always make sure we use unicode in app_settings_value
352 Setter that will always make sure we use unicode in app_settings_value
353
353
354 :param val:
354 :param val:
355 """
355 """
356 val = safe_unicode(val)
356 val = safe_unicode(val)
357 # encode the encrypted value
357 # encode the encrypted value
358 if 'encrypted' in self.app_settings_type:
358 if 'encrypted' in self.app_settings_type:
359 cipher = EncryptedTextValue()
359 cipher = EncryptedTextValue()
360 val = safe_unicode(cipher.process_bind_param(val, None))
360 val = safe_unicode(cipher.process_bind_param(val, None))
361 self._app_settings_value = val
361 self._app_settings_value = val
362
362
363 @hybrid_property
363 @hybrid_property
364 def app_settings_type(self):
364 def app_settings_type(self):
365 return self._app_settings_type
365 return self._app_settings_type
366
366
367 @app_settings_type.setter
367 @app_settings_type.setter
368 def app_settings_type(self, val):
368 def app_settings_type(self, val):
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
370 raise Exception('type must be one of %s got %s'
370 raise Exception('type must be one of %s got %s'
371 % (self.SETTINGS_TYPES.keys(), val))
371 % (self.SETTINGS_TYPES.keys(), val))
372 self._app_settings_type = val
372 self._app_settings_type = val
373
373
374 def __unicode__(self):
374 def __unicode__(self):
375 return u"<%s('%s:%s[%s]')>" % (
375 return u"<%s('%s:%s[%s]')>" % (
376 self.__class__.__name__,
376 self.__class__.__name__,
377 self.app_settings_name, self.app_settings_value,
377 self.app_settings_name, self.app_settings_value,
378 self.app_settings_type
378 self.app_settings_type
379 )
379 )
380
380
381
381
382 class RhodeCodeUi(Base, BaseModel):
382 class RhodeCodeUi(Base, BaseModel):
383 __tablename__ = 'rhodecode_ui'
383 __tablename__ = 'rhodecode_ui'
384 __table_args__ = (
384 __table_args__ = (
385 UniqueConstraint('ui_key'),
385 UniqueConstraint('ui_key'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
391 # HG
391 # HG
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
393 HOOK_PULL = 'outgoing.pull_logger'
393 HOOK_PULL = 'outgoing.pull_logger'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
396 HOOK_PUSH = 'changegroup.push_logger'
396 HOOK_PUSH = 'changegroup.push_logger'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
398
398
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
400 # git part is currently hardcoded.
400 # git part is currently hardcoded.
401
401
402 # SVN PATTERNS
402 # SVN PATTERNS
403 SVN_BRANCH_ID = 'vcs_svn_branch'
403 SVN_BRANCH_ID = 'vcs_svn_branch'
404 SVN_TAG_ID = 'vcs_svn_tag'
404 SVN_TAG_ID = 'vcs_svn_tag'
405
405
406 ui_id = Column(
406 ui_id = Column(
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
408 primary_key=True)
408 primary_key=True)
409 ui_section = Column(
409 ui_section = Column(
410 "ui_section", String(255), nullable=True, unique=None, default=None)
410 "ui_section", String(255), nullable=True, unique=None, default=None)
411 ui_key = Column(
411 ui_key = Column(
412 "ui_key", String(255), nullable=True, unique=None, default=None)
412 "ui_key", String(255), nullable=True, unique=None, default=None)
413 ui_value = Column(
413 ui_value = Column(
414 "ui_value", String(255), nullable=True, unique=None, default=None)
414 "ui_value", String(255), nullable=True, unique=None, default=None)
415 ui_active = Column(
415 ui_active = Column(
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
417
417
418 def __repr__(self):
418 def __repr__(self):
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
420 self.ui_key, self.ui_value)
420 self.ui_key, self.ui_value)
421
421
422
422
423 class RepoRhodeCodeSetting(Base, BaseModel):
423 class RepoRhodeCodeSetting(Base, BaseModel):
424 __tablename__ = 'repo_rhodecode_settings'
424 __tablename__ = 'repo_rhodecode_settings'
425 __table_args__ = (
425 __table_args__ = (
426 UniqueConstraint(
426 UniqueConstraint(
427 'app_settings_name', 'repository_id',
427 'app_settings_name', 'repository_id',
428 name='uq_repo_rhodecode_setting_name_repo_id'),
428 name='uq_repo_rhodecode_setting_name_repo_id'),
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
431 )
431 )
432
432
433 repository_id = Column(
433 repository_id = Column(
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
435 nullable=False)
435 nullable=False)
436 app_settings_id = Column(
436 app_settings_id = Column(
437 "app_settings_id", Integer(), nullable=False, unique=True,
437 "app_settings_id", Integer(), nullable=False, unique=True,
438 default=None, primary_key=True)
438 default=None, primary_key=True)
439 app_settings_name = Column(
439 app_settings_name = Column(
440 "app_settings_name", String(255), nullable=True, unique=None,
440 "app_settings_name", String(255), nullable=True, unique=None,
441 default=None)
441 default=None)
442 _app_settings_value = Column(
442 _app_settings_value = Column(
443 "app_settings_value", String(4096), nullable=True, unique=None,
443 "app_settings_value", String(4096), nullable=True, unique=None,
444 default=None)
444 default=None)
445 _app_settings_type = Column(
445 _app_settings_type = Column(
446 "app_settings_type", String(255), nullable=True, unique=None,
446 "app_settings_type", String(255), nullable=True, unique=None,
447 default=None)
447 default=None)
448
448
449 repository = relationship('Repository')
449 repository = relationship('Repository')
450
450
451 def __init__(self, repository_id, key='', val='', type='unicode'):
451 def __init__(self, repository_id, key='', val='', type='unicode'):
452 self.repository_id = repository_id
452 self.repository_id = repository_id
453 self.app_settings_name = key
453 self.app_settings_name = key
454 self.app_settings_type = type
454 self.app_settings_type = type
455 self.app_settings_value = val
455 self.app_settings_value = val
456
456
457 @validates('_app_settings_value')
457 @validates('_app_settings_value')
458 def validate_settings_value(self, key, val):
458 def validate_settings_value(self, key, val):
459 assert type(val) == unicode
459 assert type(val) == unicode
460 return val
460 return val
461
461
462 @hybrid_property
462 @hybrid_property
463 def app_settings_value(self):
463 def app_settings_value(self):
464 v = self._app_settings_value
464 v = self._app_settings_value
465 type_ = self.app_settings_type
465 type_ = self.app_settings_type
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
468 return converter(v)
468 return converter(v)
469
469
470 @app_settings_value.setter
470 @app_settings_value.setter
471 def app_settings_value(self, val):
471 def app_settings_value(self, val):
472 """
472 """
473 Setter that will always make sure we use unicode in app_settings_value
473 Setter that will always make sure we use unicode in app_settings_value
474
474
475 :param val:
475 :param val:
476 """
476 """
477 self._app_settings_value = safe_unicode(val)
477 self._app_settings_value = safe_unicode(val)
478
478
479 @hybrid_property
479 @hybrid_property
480 def app_settings_type(self):
480 def app_settings_type(self):
481 return self._app_settings_type
481 return self._app_settings_type
482
482
483 @app_settings_type.setter
483 @app_settings_type.setter
484 def app_settings_type(self, val):
484 def app_settings_type(self, val):
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
486 if val not in SETTINGS_TYPES:
486 if val not in SETTINGS_TYPES:
487 raise Exception('type must be one of %s got %s'
487 raise Exception('type must be one of %s got %s'
488 % (SETTINGS_TYPES.keys(), val))
488 % (SETTINGS_TYPES.keys(), val))
489 self._app_settings_type = val
489 self._app_settings_type = val
490
490
491 def __unicode__(self):
491 def __unicode__(self):
492 return u"<%s('%s:%s:%s[%s]')>" % (
492 return u"<%s('%s:%s:%s[%s]')>" % (
493 self.__class__.__name__, self.repository.repo_name,
493 self.__class__.__name__, self.repository.repo_name,
494 self.app_settings_name, self.app_settings_value,
494 self.app_settings_name, self.app_settings_value,
495 self.app_settings_type
495 self.app_settings_type
496 )
496 )
497
497
498
498
499 class RepoRhodeCodeUi(Base, BaseModel):
499 class RepoRhodeCodeUi(Base, BaseModel):
500 __tablename__ = 'repo_rhodecode_ui'
500 __tablename__ = 'repo_rhodecode_ui'
501 __table_args__ = (
501 __table_args__ = (
502 UniqueConstraint(
502 UniqueConstraint(
503 'repository_id', 'ui_section', 'ui_key',
503 'repository_id', 'ui_section', 'ui_key',
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
507 )
507 )
508
508
509 repository_id = Column(
509 repository_id = Column(
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
511 nullable=False)
511 nullable=False)
512 ui_id = Column(
512 ui_id = Column(
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
514 primary_key=True)
514 primary_key=True)
515 ui_section = Column(
515 ui_section = Column(
516 "ui_section", String(255), nullable=True, unique=None, default=None)
516 "ui_section", String(255), nullable=True, unique=None, default=None)
517 ui_key = Column(
517 ui_key = Column(
518 "ui_key", String(255), nullable=True, unique=None, default=None)
518 "ui_key", String(255), nullable=True, unique=None, default=None)
519 ui_value = Column(
519 ui_value = Column(
520 "ui_value", String(255), nullable=True, unique=None, default=None)
520 "ui_value", String(255), nullable=True, unique=None, default=None)
521 ui_active = Column(
521 ui_active = Column(
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
523
523
524 repository = relationship('Repository')
524 repository = relationship('Repository')
525
525
526 def __repr__(self):
526 def __repr__(self):
527 return '<%s[%s:%s]%s=>%s]>' % (
527 return '<%s[%s:%s]%s=>%s]>' % (
528 self.__class__.__name__, self.repository.repo_name,
528 self.__class__.__name__, self.repository.repo_name,
529 self.ui_section, self.ui_key, self.ui_value)
529 self.ui_section, self.ui_key, self.ui_value)
530
530
531
531
532 class User(Base, BaseModel):
532 class User(Base, BaseModel):
533 __tablename__ = 'users'
533 __tablename__ = 'users'
534 __table_args__ = (
534 __table_args__ = (
535 UniqueConstraint('username'), UniqueConstraint('email'),
535 UniqueConstraint('username'), UniqueConstraint('email'),
536 Index('u_username_idx', 'username'),
536 Index('u_username_idx', 'username'),
537 Index('u_email_idx', 'email'),
537 Index('u_email_idx', 'email'),
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
540 )
540 )
541 DEFAULT_USER = 'default'
541 DEFAULT_USER = 'default'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
544
544
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
555
555
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
562
562
563 user_log = relationship('UserLog')
563 user_log = relationship('UserLog')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
565
565
566 repositories = relationship('Repository')
566 repositories = relationship('Repository')
567 repository_groups = relationship('RepoGroup')
567 repository_groups = relationship('RepoGroup')
568 user_groups = relationship('UserGroup')
568 user_groups = relationship('UserGroup')
569
569
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
572
572
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
576
576
577 group_member = relationship('UserGroupMember', cascade='all')
577 group_member = relationship('UserGroupMember', cascade='all')
578
578
579 notifications = relationship('UserNotification', cascade='all')
579 notifications = relationship('UserNotification', cascade='all')
580 # notifications assigned to this user
580 # notifications assigned to this user
581 user_created_notifications = relationship('Notification', cascade='all')
581 user_created_notifications = relationship('Notification', cascade='all')
582 # comments created by this user
582 # comments created by this user
583 user_comments = relationship('ChangesetComment', cascade='all')
583 user_comments = relationship('ChangesetComment', cascade='all')
584 # user profile extra info
584 # user profile extra info
585 user_emails = relationship('UserEmailMap', cascade='all')
585 user_emails = relationship('UserEmailMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
589
589
590 # gists
590 # gists
591 user_gists = relationship('Gist', cascade='all')
591 user_gists = relationship('Gist', cascade='all')
592 # user pull requests
592 # user pull requests
593 user_pull_requests = relationship('PullRequest', cascade='all')
593 user_pull_requests = relationship('PullRequest', cascade='all')
594 # external identities
594 # external identities
595 extenal_identities = relationship(
595 extenal_identities = relationship(
596 'ExternalIdentity',
596 'ExternalIdentity',
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
598 cascade='all')
598 cascade='all')
599 # review rules
599 # review rules
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
601
601
602 def __unicode__(self):
602 def __unicode__(self):
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
604 self.user_id, self.username)
604 self.user_id, self.username)
605
605
606 @hybrid_property
606 @hybrid_property
607 def email(self):
607 def email(self):
608 return self._email
608 return self._email
609
609
610 @email.setter
610 @email.setter
611 def email(self, val):
611 def email(self, val):
612 self._email = val.lower() if val else None
612 self._email = val.lower() if val else None
613
613
614 @hybrid_property
614 @hybrid_property
615 def first_name(self):
615 def first_name(self):
616 from rhodecode.lib import helpers as h
616 from rhodecode.lib import helpers as h
617 if self.name:
617 if self.name:
618 return h.escape(self.name)
618 return h.escape(self.name)
619 return self.name
619 return self.name
620
620
621 @hybrid_property
621 @hybrid_property
622 def last_name(self):
622 def last_name(self):
623 from rhodecode.lib import helpers as h
623 from rhodecode.lib import helpers as h
624 if self.lastname:
624 if self.lastname:
625 return h.escape(self.lastname)
625 return h.escape(self.lastname)
626 return self.lastname
626 return self.lastname
627
627
628 @hybrid_property
628 @hybrid_property
629 def api_key(self):
629 def api_key(self):
630 """
630 """
631 Fetch if exist an auth-token with role ALL connected to this user
631 Fetch if exist an auth-token with role ALL connected to this user
632 """
632 """
633 user_auth_token = UserApiKeys.query()\
633 user_auth_token = UserApiKeys.query()\
634 .filter(UserApiKeys.user_id == self.user_id)\
634 .filter(UserApiKeys.user_id == self.user_id)\
635 .filter(or_(UserApiKeys.expires == -1,
635 .filter(or_(UserApiKeys.expires == -1,
636 UserApiKeys.expires >= time.time()))\
636 UserApiKeys.expires >= time.time()))\
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
638 if user_auth_token:
638 if user_auth_token:
639 user_auth_token = user_auth_token.api_key
639 user_auth_token = user_auth_token.api_key
640
640
641 return user_auth_token
641 return user_auth_token
642
642
643 @api_key.setter
643 @api_key.setter
644 def api_key(self, val):
644 def api_key(self, val):
645 # don't allow to set API key this is deprecated for now
645 # don't allow to set API key this is deprecated for now
646 self._api_key = None
646 self._api_key = None
647
647
648 @property
648 @property
649 def reviewer_pull_requests(self):
649 def reviewer_pull_requests(self):
650 return PullRequestReviewers.query() \
650 return PullRequestReviewers.query() \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
653 .all()
653 .all()
654
654
655 @property
655 @property
656 def firstname(self):
656 def firstname(self):
657 # alias for future
657 # alias for future
658 return self.name
658 return self.name
659
659
660 @property
660 @property
661 def emails(self):
661 def emails(self):
662 other = UserEmailMap.query()\
662 other = UserEmailMap.query()\
663 .filter(UserEmailMap.user == self) \
663 .filter(UserEmailMap.user == self) \
664 .order_by(UserEmailMap.email_id.asc()) \
664 .order_by(UserEmailMap.email_id.asc()) \
665 .all()
665 .all()
666 return [self.email] + [x.email for x in other]
666 return [self.email] + [x.email for x in other]
667
667
668 @property
668 @property
669 def auth_tokens(self):
669 def auth_tokens(self):
670 auth_tokens = self.get_auth_tokens()
670 auth_tokens = self.get_auth_tokens()
671 return [x.api_key for x in auth_tokens]
671 return [x.api_key for x in auth_tokens]
672
672
673 def get_auth_tokens(self):
673 def get_auth_tokens(self):
674 return UserApiKeys.query()\
674 return UserApiKeys.query()\
675 .filter(UserApiKeys.user == self)\
675 .filter(UserApiKeys.user == self)\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
677 .all()
677 .all()
678
678
679 @LazyProperty
679 @LazyProperty
680 def feed_token(self):
680 def feed_token(self):
681 return self.get_feed_token()
681 return self.get_feed_token()
682
682
683 def get_feed_token(self, cache=True):
683 def get_feed_token(self, cache=True):
684 feed_tokens = UserApiKeys.query()\
684 feed_tokens = UserApiKeys.query()\
685 .filter(UserApiKeys.user == self)\
685 .filter(UserApiKeys.user == self)\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
687 if cache:
687 if cache:
688 feed_tokens = feed_tokens.options(
688 feed_tokens = feed_tokens.options(
689 FromCache("long_term", "get_user_feed_token_%s" % self.user_id))
689 FromCache("long_term", "get_user_feed_token_%s" % self.user_id))
690
690
691 feed_tokens = feed_tokens.all()
691 feed_tokens = feed_tokens.all()
692 if feed_tokens:
692 if feed_tokens:
693 return feed_tokens[0].api_key
693 return feed_tokens[0].api_key
694 return 'NO_FEED_TOKEN_AVAILABLE'
694 return 'NO_FEED_TOKEN_AVAILABLE'
695
695
696 @classmethod
696 @classmethod
697 def get(cls, user_id, cache=False):
697 def get(cls, user_id, cache=False):
698 if not user_id:
698 if not user_id:
699 return
699 return
700
700
701 user = cls.query()
701 user = cls.query()
702 if cache:
702 if cache:
703 user = user.options(
703 user = user.options(
704 FromCache("sql_cache_short", "get_users_%s" % user_id))
704 FromCache("sql_cache_short", "get_users_%s" % user_id))
705 return user.get(user_id)
705 return user.get(user_id)
706
706
707 @classmethod
707 @classmethod
708 def extra_valid_auth_tokens(cls, user, role=None):
708 def extra_valid_auth_tokens(cls, user, role=None):
709 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
709 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
710 .filter(or_(UserApiKeys.expires == -1,
710 .filter(or_(UserApiKeys.expires == -1,
711 UserApiKeys.expires >= time.time()))
711 UserApiKeys.expires >= time.time()))
712 if role:
712 if role:
713 tokens = tokens.filter(or_(UserApiKeys.role == role,
713 tokens = tokens.filter(or_(UserApiKeys.role == role,
714 UserApiKeys.role == UserApiKeys.ROLE_ALL))
714 UserApiKeys.role == UserApiKeys.ROLE_ALL))
715 return tokens.all()
715 return tokens.all()
716
716
717 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
717 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
718 from rhodecode.lib import auth
718 from rhodecode.lib import auth
719
719
720 log.debug('Trying to authenticate user: %s via auth-token, '
720 log.debug('Trying to authenticate user: %s via auth-token, '
721 'and roles: %s', self, roles)
721 'and roles: %s', self, roles)
722
722
723 if not auth_token:
723 if not auth_token:
724 return False
724 return False
725
725
726 crypto_backend = auth.crypto_backend()
726 crypto_backend = auth.crypto_backend()
727
727
728 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
728 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
729 tokens_q = UserApiKeys.query()\
729 tokens_q = UserApiKeys.query()\
730 .filter(UserApiKeys.user_id == self.user_id)\
730 .filter(UserApiKeys.user_id == self.user_id)\
731 .filter(or_(UserApiKeys.expires == -1,
731 .filter(or_(UserApiKeys.expires == -1,
732 UserApiKeys.expires >= time.time()))
732 UserApiKeys.expires >= time.time()))
733
733
734 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
734 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
735
735
736 plain_tokens = []
736 plain_tokens = []
737 hash_tokens = []
737 hash_tokens = []
738
738
739 for token in tokens_q.all():
739 for token in tokens_q.all():
740 # verify scope first
740 # verify scope first
741 if token.repo_id:
741 if token.repo_id:
742 # token has a scope, we need to verify it
742 # token has a scope, we need to verify it
743 if scope_repo_id != token.repo_id:
743 if scope_repo_id != token.repo_id:
744 log.debug(
744 log.debug(
745 'Scope mismatch: token has a set repo scope: %s, '
745 'Scope mismatch: token has a set repo scope: %s, '
746 'and calling scope is:%s, skipping further checks',
746 'and calling scope is:%s, skipping further checks',
747 token.repo, scope_repo_id)
747 token.repo, scope_repo_id)
748 # token has a scope, and it doesn't match, skip token
748 # token has a scope, and it doesn't match, skip token
749 continue
749 continue
750
750
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
752 hash_tokens.append(token.api_key)
752 hash_tokens.append(token.api_key)
753 else:
753 else:
754 plain_tokens.append(token.api_key)
754 plain_tokens.append(token.api_key)
755
755
756 is_plain_match = auth_token in plain_tokens
756 is_plain_match = auth_token in plain_tokens
757 if is_plain_match:
757 if is_plain_match:
758 return True
758 return True
759
759
760 for hashed in hash_tokens:
760 for hashed in hash_tokens:
761 # TODO(marcink): this is expensive to calculate, but most secure
761 # TODO(marcink): this is expensive to calculate, but most secure
762 match = crypto_backend.hash_check(auth_token, hashed)
762 match = crypto_backend.hash_check(auth_token, hashed)
763 if match:
763 if match:
764 return True
764 return True
765
765
766 return False
766 return False
767
767
768 @property
768 @property
769 def ip_addresses(self):
769 def ip_addresses(self):
770 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
770 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
771 return [x.ip_addr for x in ret]
771 return [x.ip_addr for x in ret]
772
772
773 @property
773 @property
774 def username_and_name(self):
774 def username_and_name(self):
775 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
775 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
776
776
777 @property
777 @property
778 def username_or_name_or_email(self):
778 def username_or_name_or_email(self):
779 full_name = self.full_name if self.full_name is not ' ' else None
779 full_name = self.full_name if self.full_name is not ' ' else None
780 return self.username or full_name or self.email
780 return self.username or full_name or self.email
781
781
782 @property
782 @property
783 def full_name(self):
783 def full_name(self):
784 return '%s %s' % (self.first_name, self.last_name)
784 return '%s %s' % (self.first_name, self.last_name)
785
785
786 @property
786 @property
787 def full_name_or_username(self):
787 def full_name_or_username(self):
788 return ('%s %s' % (self.first_name, self.last_name)
788 return ('%s %s' % (self.first_name, self.last_name)
789 if (self.first_name and self.last_name) else self.username)
789 if (self.first_name and self.last_name) else self.username)
790
790
791 @property
791 @property
792 def full_contact(self):
792 def full_contact(self):
793 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
793 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
794
794
795 @property
795 @property
796 def short_contact(self):
796 def short_contact(self):
797 return '%s %s' % (self.first_name, self.last_name)
797 return '%s %s' % (self.first_name, self.last_name)
798
798
799 @property
799 @property
800 def is_admin(self):
800 def is_admin(self):
801 return self.admin
801 return self.admin
802
802
803 def AuthUser(self, **kwargs):
803 def AuthUser(self, **kwargs):
804 """
804 """
805 Returns instance of AuthUser for this user
805 Returns instance of AuthUser for this user
806 """
806 """
807 from rhodecode.lib.auth import AuthUser
807 from rhodecode.lib.auth import AuthUser
808 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
808 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
809
809
810 @hybrid_property
810 @hybrid_property
811 def user_data(self):
811 def user_data(self):
812 if not self._user_data:
812 if not self._user_data:
813 return {}
813 return {}
814
814
815 try:
815 try:
816 return json.loads(self._user_data)
816 return json.loads(self._user_data)
817 except TypeError:
817 except TypeError:
818 return {}
818 return {}
819
819
820 @user_data.setter
820 @user_data.setter
821 def user_data(self, val):
821 def user_data(self, val):
822 if not isinstance(val, dict):
822 if not isinstance(val, dict):
823 raise Exception('user_data must be dict, got %s' % type(val))
823 raise Exception('user_data must be dict, got %s' % type(val))
824 try:
824 try:
825 self._user_data = json.dumps(val)
825 self._user_data = json.dumps(val)
826 except Exception:
826 except Exception:
827 log.error(traceback.format_exc())
827 log.error(traceback.format_exc())
828
828
829 @classmethod
829 @classmethod
830 def get_by_username(cls, username, case_insensitive=False,
830 def get_by_username(cls, username, case_insensitive=False,
831 cache=False, identity_cache=False):
831 cache=False, identity_cache=False):
832 session = Session()
832 session = Session()
833
833
834 if case_insensitive:
834 if case_insensitive:
835 q = cls.query().filter(
835 q = cls.query().filter(
836 func.lower(cls.username) == func.lower(username))
836 func.lower(cls.username) == func.lower(username))
837 else:
837 else:
838 q = cls.query().filter(cls.username == username)
838 q = cls.query().filter(cls.username == username)
839
839
840 if cache:
840 if cache:
841 if identity_cache:
841 if identity_cache:
842 val = cls.identity_cache(session, 'username', username)
842 val = cls.identity_cache(session, 'username', username)
843 if val:
843 if val:
844 return val
844 return val
845 else:
845 else:
846 cache_key = "get_user_by_name_%s" % _hash_key(username)
846 cache_key = "get_user_by_name_%s" % _hash_key(username)
847 q = q.options(
847 q = q.options(
848 FromCache("sql_cache_short", cache_key))
848 FromCache("sql_cache_short", cache_key))
849
849
850 return q.scalar()
850 return q.scalar()
851
851
852 @classmethod
852 @classmethod
853 def get_by_auth_token(cls, auth_token, cache=False):
853 def get_by_auth_token(cls, auth_token, cache=False):
854 q = UserApiKeys.query()\
854 q = UserApiKeys.query()\
855 .filter(UserApiKeys.api_key == auth_token)\
855 .filter(UserApiKeys.api_key == auth_token)\
856 .filter(or_(UserApiKeys.expires == -1,
856 .filter(or_(UserApiKeys.expires == -1,
857 UserApiKeys.expires >= time.time()))
857 UserApiKeys.expires >= time.time()))
858 if cache:
858 if cache:
859 q = q.options(
859 q = q.options(
860 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
860 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
861
861
862 match = q.first()
862 match = q.first()
863 if match:
863 if match:
864 return match.user
864 return match.user
865
865
866 @classmethod
866 @classmethod
867 def get_by_email(cls, email, case_insensitive=False, cache=False):
867 def get_by_email(cls, email, case_insensitive=False, cache=False):
868
868
869 if case_insensitive:
869 if case_insensitive:
870 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
870 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
871
871
872 else:
872 else:
873 q = cls.query().filter(cls.email == email)
873 q = cls.query().filter(cls.email == email)
874
874
875 email_key = _hash_key(email)
875 email_key = _hash_key(email)
876 if cache:
876 if cache:
877 q = q.options(
877 q = q.options(
878 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
878 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
879
879
880 ret = q.scalar()
880 ret = q.scalar()
881 if ret is None:
881 if ret is None:
882 q = UserEmailMap.query()
882 q = UserEmailMap.query()
883 # try fetching in alternate email map
883 # try fetching in alternate email map
884 if case_insensitive:
884 if case_insensitive:
885 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
885 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
886 else:
886 else:
887 q = q.filter(UserEmailMap.email == email)
887 q = q.filter(UserEmailMap.email == email)
888 q = q.options(joinedload(UserEmailMap.user))
888 q = q.options(joinedload(UserEmailMap.user))
889 if cache:
889 if cache:
890 q = q.options(
890 q = q.options(
891 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
891 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
892 ret = getattr(q.scalar(), 'user', None)
892 ret = getattr(q.scalar(), 'user', None)
893
893
894 return ret
894 return ret
895
895
896 @classmethod
896 @classmethod
897 def get_from_cs_author(cls, author):
897 def get_from_cs_author(cls, author):
898 """
898 """
899 Tries to get User objects out of commit author string
899 Tries to get User objects out of commit author string
900
900
901 :param author:
901 :param author:
902 """
902 """
903 from rhodecode.lib.helpers import email, author_name
903 from rhodecode.lib.helpers import email, author_name
904 # Valid email in the attribute passed, see if they're in the system
904 # Valid email in the attribute passed, see if they're in the system
905 _email = email(author)
905 _email = email(author)
906 if _email:
906 if _email:
907 user = cls.get_by_email(_email, case_insensitive=True)
907 user = cls.get_by_email(_email, case_insensitive=True)
908 if user:
908 if user:
909 return user
909 return user
910 # Maybe we can match by username?
910 # Maybe we can match by username?
911 _author = author_name(author)
911 _author = author_name(author)
912 user = cls.get_by_username(_author, case_insensitive=True)
912 user = cls.get_by_username(_author, case_insensitive=True)
913 if user:
913 if user:
914 return user
914 return user
915
915
916 def update_userdata(self, **kwargs):
916 def update_userdata(self, **kwargs):
917 usr = self
917 usr = self
918 old = usr.user_data
918 old = usr.user_data
919 old.update(**kwargs)
919 old.update(**kwargs)
920 usr.user_data = old
920 usr.user_data = old
921 Session().add(usr)
921 Session().add(usr)
922 log.debug('updated userdata with ', kwargs)
922 log.debug('updated userdata with ', kwargs)
923
923
924 def update_lastlogin(self):
924 def update_lastlogin(self):
925 """Update user lastlogin"""
925 """Update user lastlogin"""
926 self.last_login = datetime.datetime.now()
926 self.last_login = datetime.datetime.now()
927 Session().add(self)
927 Session().add(self)
928 log.debug('updated user %s lastlogin', self.username)
928 log.debug('updated user %s lastlogin', self.username)
929
929
930 def update_lastactivity(self):
930 def update_lastactivity(self):
931 """Update user lastactivity"""
931 """Update user lastactivity"""
932 self.last_activity = datetime.datetime.now()
932 self.last_activity = datetime.datetime.now()
933 Session().add(self)
933 Session().add(self)
934 log.debug('updated user `%s` last activity', self.username)
934 log.debug('updated user `%s` last activity', self.username)
935
935
936 def update_password(self, new_password):
936 def update_password(self, new_password):
937 from rhodecode.lib.auth import get_crypt_password
937 from rhodecode.lib.auth import get_crypt_password
938
938
939 self.password = get_crypt_password(new_password)
939 self.password = get_crypt_password(new_password)
940 Session().add(self)
940 Session().add(self)
941
941
942 @classmethod
942 @classmethod
943 def get_first_super_admin(cls):
943 def get_first_super_admin(cls):
944 user = User.query().filter(User.admin == true()).first()
944 user = User.query().filter(User.admin == true()).first()
945 if user is None:
945 if user is None:
946 raise Exception('FATAL: Missing administrative account!')
946 raise Exception('FATAL: Missing administrative account!')
947 return user
947 return user
948
948
949 @classmethod
949 @classmethod
950 def get_all_super_admins(cls):
950 def get_all_super_admins(cls):
951 """
951 """
952 Returns all admin accounts sorted by username
952 Returns all admin accounts sorted by username
953 """
953 """
954 return User.query().filter(User.admin == true())\
954 return User.query().filter(User.admin == true())\
955 .order_by(User.username.asc()).all()
955 .order_by(User.username.asc()).all()
956
956
957 @classmethod
957 @classmethod
958 def get_default_user(cls, cache=False, refresh=False):
958 def get_default_user(cls, cache=False, refresh=False):
959 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
959 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
960 if user is None:
960 if user is None:
961 raise Exception('FATAL: Missing default account!')
961 raise Exception('FATAL: Missing default account!')
962 if refresh:
962 if refresh:
963 # The default user might be based on outdated state which
963 # The default user might be based on outdated state which
964 # has been loaded from the cache.
964 # has been loaded from the cache.
965 # A call to refresh() ensures that the
965 # A call to refresh() ensures that the
966 # latest state from the database is used.
966 # latest state from the database is used.
967 Session().refresh(user)
967 Session().refresh(user)
968 return user
968 return user
969
969
970 def _get_default_perms(self, user, suffix=''):
970 def _get_default_perms(self, user, suffix=''):
971 from rhodecode.model.permission import PermissionModel
971 from rhodecode.model.permission import PermissionModel
972 return PermissionModel().get_default_perms(user.user_perms, suffix)
972 return PermissionModel().get_default_perms(user.user_perms, suffix)
973
973
974 def get_default_perms(self, suffix=''):
974 def get_default_perms(self, suffix=''):
975 return self._get_default_perms(self, suffix)
975 return self._get_default_perms(self, suffix)
976
976
977 def get_api_data(self, include_secrets=False, details='full'):
977 def get_api_data(self, include_secrets=False, details='full'):
978 """
978 """
979 Common function for generating user related data for API
979 Common function for generating user related data for API
980
980
981 :param include_secrets: By default secrets in the API data will be replaced
981 :param include_secrets: By default secrets in the API data will be replaced
982 by a placeholder value to prevent exposing this data by accident. In case
982 by a placeholder value to prevent exposing this data by accident. In case
983 this data shall be exposed, set this flag to ``True``.
983 this data shall be exposed, set this flag to ``True``.
984
984
985 :param details: details can be 'basic|full' basic gives only a subset of
985 :param details: details can be 'basic|full' basic gives only a subset of
986 the available user information that includes user_id, name and emails.
986 the available user information that includes user_id, name and emails.
987 """
987 """
988 user = self
988 user = self
989 user_data = self.user_data
989 user_data = self.user_data
990 data = {
990 data = {
991 'user_id': user.user_id,
991 'user_id': user.user_id,
992 'username': user.username,
992 'username': user.username,
993 'firstname': user.name,
993 'firstname': user.name,
994 'lastname': user.lastname,
994 'lastname': user.lastname,
995 'email': user.email,
995 'email': user.email,
996 'emails': user.emails,
996 'emails': user.emails,
997 }
997 }
998 if details == 'basic':
998 if details == 'basic':
999 return data
999 return data
1000
1000
1001 auth_token_length = 40
1001 auth_token_length = 40
1002 auth_token_replacement = '*' * auth_token_length
1002 auth_token_replacement = '*' * auth_token_length
1003
1003
1004 extras = {
1004 extras = {
1005 'auth_tokens': [auth_token_replacement],
1005 'auth_tokens': [auth_token_replacement],
1006 'active': user.active,
1006 'active': user.active,
1007 'admin': user.admin,
1007 'admin': user.admin,
1008 'extern_type': user.extern_type,
1008 'extern_type': user.extern_type,
1009 'extern_name': user.extern_name,
1009 'extern_name': user.extern_name,
1010 'last_login': user.last_login,
1010 'last_login': user.last_login,
1011 'last_activity': user.last_activity,
1011 'last_activity': user.last_activity,
1012 'ip_addresses': user.ip_addresses,
1012 'ip_addresses': user.ip_addresses,
1013 'language': user_data.get('language')
1013 'language': user_data.get('language')
1014 }
1014 }
1015 data.update(extras)
1015 data.update(extras)
1016
1016
1017 if include_secrets:
1017 if include_secrets:
1018 data['auth_tokens'] = user.auth_tokens
1018 data['auth_tokens'] = user.auth_tokens
1019 return data
1019 return data
1020
1020
1021 def __json__(self):
1021 def __json__(self):
1022 data = {
1022 data = {
1023 'full_name': self.full_name,
1023 'full_name': self.full_name,
1024 'full_name_or_username': self.full_name_or_username,
1024 'full_name_or_username': self.full_name_or_username,
1025 'short_contact': self.short_contact,
1025 'short_contact': self.short_contact,
1026 'full_contact': self.full_contact,
1026 'full_contact': self.full_contact,
1027 }
1027 }
1028 data.update(self.get_api_data())
1028 data.update(self.get_api_data())
1029 return data
1029 return data
1030
1030
1031
1031
1032 class UserApiKeys(Base, BaseModel):
1032 class UserApiKeys(Base, BaseModel):
1033 __tablename__ = 'user_api_keys'
1033 __tablename__ = 'user_api_keys'
1034 __table_args__ = (
1034 __table_args__ = (
1035 Index('uak_api_key_idx', 'api_key', unique=True),
1035 Index('uak_api_key_idx', 'api_key', unique=True),
1036 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1036 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1039 )
1039 )
1040 __mapper_args__ = {}
1040 __mapper_args__ = {}
1041
1041
1042 # ApiKey role
1042 # ApiKey role
1043 ROLE_ALL = 'token_role_all'
1043 ROLE_ALL = 'token_role_all'
1044 ROLE_HTTP = 'token_role_http'
1044 ROLE_HTTP = 'token_role_http'
1045 ROLE_VCS = 'token_role_vcs'
1045 ROLE_VCS = 'token_role_vcs'
1046 ROLE_API = 'token_role_api'
1046 ROLE_API = 'token_role_api'
1047 ROLE_FEED = 'token_role_feed'
1047 ROLE_FEED = 'token_role_feed'
1048 ROLE_PASSWORD_RESET = 'token_password_reset'
1048 ROLE_PASSWORD_RESET = 'token_password_reset'
1049
1049
1050 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1050 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1051
1051
1052 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1052 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1054 api_key = Column("api_key", String(255), nullable=False, unique=True)
1054 api_key = Column("api_key", String(255), nullable=False, unique=True)
1055 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1055 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1056 expires = Column('expires', Float(53), nullable=False)
1056 expires = Column('expires', Float(53), nullable=False)
1057 role = Column('role', String(255), nullable=True)
1057 role = Column('role', String(255), nullable=True)
1058 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1058 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1059
1059
1060 # scope columns
1060 # scope columns
1061 repo_id = Column(
1061 repo_id = Column(
1062 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1062 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1063 nullable=True, unique=None, default=None)
1063 nullable=True, unique=None, default=None)
1064 repo = relationship('Repository', lazy='joined')
1064 repo = relationship('Repository', lazy='joined')
1065
1065
1066 repo_group_id = Column(
1066 repo_group_id = Column(
1067 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1067 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1068 nullable=True, unique=None, default=None)
1068 nullable=True, unique=None, default=None)
1069 repo_group = relationship('RepoGroup', lazy='joined')
1069 repo_group = relationship('RepoGroup', lazy='joined')
1070
1070
1071 user = relationship('User', lazy='joined')
1071 user = relationship('User', lazy='joined')
1072
1072
1073 def __unicode__(self):
1073 def __unicode__(self):
1074 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1074 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1075
1075
1076 def __json__(self):
1076 def __json__(self):
1077 data = {
1077 data = {
1078 'auth_token': self.api_key,
1078 'auth_token': self.api_key,
1079 'role': self.role,
1079 'role': self.role,
1080 'scope': self.scope_humanized,
1080 'scope': self.scope_humanized,
1081 'expired': self.expired
1081 'expired': self.expired
1082 }
1082 }
1083 return data
1083 return data
1084
1084
1085 def get_api_data(self, include_secrets=False):
1085 def get_api_data(self, include_secrets=False):
1086 data = self.__json__()
1086 data = self.__json__()
1087 if include_secrets:
1087 if include_secrets:
1088 return data
1088 return data
1089 else:
1089 else:
1090 data['auth_token'] = self.token_obfuscated
1090 data['auth_token'] = self.token_obfuscated
1091 return data
1091 return data
1092
1092
1093 @hybrid_property
1093 @hybrid_property
1094 def description_safe(self):
1094 def description_safe(self):
1095 from rhodecode.lib import helpers as h
1095 from rhodecode.lib import helpers as h
1096 return h.escape(self.description)
1096 return h.escape(self.description)
1097
1097
1098 @property
1098 @property
1099 def expired(self):
1099 def expired(self):
1100 if self.expires == -1:
1100 if self.expires == -1:
1101 return False
1101 return False
1102 return time.time() > self.expires
1102 return time.time() > self.expires
1103
1103
1104 @classmethod
1104 @classmethod
1105 def _get_role_name(cls, role):
1105 def _get_role_name(cls, role):
1106 return {
1106 return {
1107 cls.ROLE_ALL: _('all'),
1107 cls.ROLE_ALL: _('all'),
1108 cls.ROLE_HTTP: _('http/web interface'),
1108 cls.ROLE_HTTP: _('http/web interface'),
1109 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1109 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1110 cls.ROLE_API: _('api calls'),
1110 cls.ROLE_API: _('api calls'),
1111 cls.ROLE_FEED: _('feed access'),
1111 cls.ROLE_FEED: _('feed access'),
1112 }.get(role, role)
1112 }.get(role, role)
1113
1113
1114 @property
1114 @property
1115 def role_humanized(self):
1115 def role_humanized(self):
1116 return self._get_role_name(self.role)
1116 return self._get_role_name(self.role)
1117
1117
1118 def _get_scope(self):
1118 def _get_scope(self):
1119 if self.repo:
1119 if self.repo:
1120 return repr(self.repo)
1120 return repr(self.repo)
1121 if self.repo_group:
1121 if self.repo_group:
1122 return repr(self.repo_group) + ' (recursive)'
1122 return repr(self.repo_group) + ' (recursive)'
1123 return 'global'
1123 return 'global'
1124
1124
1125 @property
1125 @property
1126 def scope_humanized(self):
1126 def scope_humanized(self):
1127 return self._get_scope()
1127 return self._get_scope()
1128
1128
1129 @property
1129 @property
1130 def token_obfuscated(self):
1130 def token_obfuscated(self):
1131 if self.api_key:
1131 if self.api_key:
1132 return self.api_key[:4] + "****"
1132 return self.api_key[:4] + "****"
1133
1133
1134
1134
1135 class UserEmailMap(Base, BaseModel):
1135 class UserEmailMap(Base, BaseModel):
1136 __tablename__ = 'user_email_map'
1136 __tablename__ = 'user_email_map'
1137 __table_args__ = (
1137 __table_args__ = (
1138 Index('uem_email_idx', 'email'),
1138 Index('uem_email_idx', 'email'),
1139 UniqueConstraint('email'),
1139 UniqueConstraint('email'),
1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1141 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1141 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1142 )
1142 )
1143 __mapper_args__ = {}
1143 __mapper_args__ = {}
1144
1144
1145 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1145 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1147 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1147 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1148 user = relationship('User', lazy='joined')
1148 user = relationship('User', lazy='joined')
1149
1149
1150 @validates('_email')
1150 @validates('_email')
1151 def validate_email(self, key, email):
1151 def validate_email(self, key, email):
1152 # check if this email is not main one
1152 # check if this email is not main one
1153 main_email = Session().query(User).filter(User.email == email).scalar()
1153 main_email = Session().query(User).filter(User.email == email).scalar()
1154 if main_email is not None:
1154 if main_email is not None:
1155 raise AttributeError('email %s is present is user table' % email)
1155 raise AttributeError('email %s is present is user table' % email)
1156 return email
1156 return email
1157
1157
1158 @hybrid_property
1158 @hybrid_property
1159 def email(self):
1159 def email(self):
1160 return self._email
1160 return self._email
1161
1161
1162 @email.setter
1162 @email.setter
1163 def email(self, val):
1163 def email(self, val):
1164 self._email = val.lower() if val else None
1164 self._email = val.lower() if val else None
1165
1165
1166
1166
1167 class UserIpMap(Base, BaseModel):
1167 class UserIpMap(Base, BaseModel):
1168 __tablename__ = 'user_ip_map'
1168 __tablename__ = 'user_ip_map'
1169 __table_args__ = (
1169 __table_args__ = (
1170 UniqueConstraint('user_id', 'ip_addr'),
1170 UniqueConstraint('user_id', 'ip_addr'),
1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1172 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1172 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1173 )
1173 )
1174 __mapper_args__ = {}
1174 __mapper_args__ = {}
1175
1175
1176 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1176 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1177 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1177 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1178 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1178 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1179 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1179 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1180 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1180 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1181 user = relationship('User', lazy='joined')
1181 user = relationship('User', lazy='joined')
1182
1182
1183 @hybrid_property
1183 @hybrid_property
1184 def description_safe(self):
1184 def description_safe(self):
1185 from rhodecode.lib import helpers as h
1185 from rhodecode.lib import helpers as h
1186 return h.escape(self.description)
1186 return h.escape(self.description)
1187
1187
1188 @classmethod
1188 @classmethod
1189 def _get_ip_range(cls, ip_addr):
1189 def _get_ip_range(cls, ip_addr):
1190 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1190 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1191 return [str(net.network_address), str(net.broadcast_address)]
1191 return [str(net.network_address), str(net.broadcast_address)]
1192
1192
1193 def __json__(self):
1193 def __json__(self):
1194 return {
1194 return {
1195 'ip_addr': self.ip_addr,
1195 'ip_addr': self.ip_addr,
1196 'ip_range': self._get_ip_range(self.ip_addr),
1196 'ip_range': self._get_ip_range(self.ip_addr),
1197 }
1197 }
1198
1198
1199 def __unicode__(self):
1199 def __unicode__(self):
1200 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1200 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1201 self.user_id, self.ip_addr)
1201 self.user_id, self.ip_addr)
1202
1202
1203
1203
1204 class UserSshKeys(Base, BaseModel):
1204 class UserSshKeys(Base, BaseModel):
1205 __tablename__ = 'user_ssh_keys'
1205 __tablename__ = 'user_ssh_keys'
1206 __table_args__ = (
1206 __table_args__ = (
1207 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1207 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1208
1208
1209 UniqueConstraint('ssh_key_fingerprint'),
1209 UniqueConstraint('ssh_key_fingerprint'),
1210
1210
1211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1213 )
1213 )
1214 __mapper_args__ = {}
1214 __mapper_args__ = {}
1215
1215
1216 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1216 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1217 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1217 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1218 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1218 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1219
1219
1220 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1220 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1221
1221
1222 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1222 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1223 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1223 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1224 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1224 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1225
1225
1226 user = relationship('User', lazy='joined')
1226 user = relationship('User', lazy='joined')
1227
1227
1228 def __json__(self):
1228 def __json__(self):
1229 data = {
1229 data = {
1230 'ssh_fingerprint': self.ssh_key_fingerprint,
1230 'ssh_fingerprint': self.ssh_key_fingerprint,
1231 'description': self.description,
1231 'description': self.description,
1232 'created_on': self.created_on
1232 'created_on': self.created_on
1233 }
1233 }
1234 return data
1234 return data
1235
1235
1236 def get_api_data(self):
1236 def get_api_data(self):
1237 data = self.__json__()
1237 data = self.__json__()
1238 return data
1238 return data
1239
1239
1240
1240
1241 class UserLog(Base, BaseModel):
1241 class UserLog(Base, BaseModel):
1242 __tablename__ = 'user_logs'
1242 __tablename__ = 'user_logs'
1243 __table_args__ = (
1243 __table_args__ = (
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1246 )
1246 )
1247 VERSION_1 = 'v1'
1247 VERSION_1 = 'v1'
1248 VERSION_2 = 'v2'
1248 VERSION_2 = 'v2'
1249 VERSIONS = [VERSION_1, VERSION_2]
1249 VERSIONS = [VERSION_1, VERSION_2]
1250
1250
1251 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1251 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1253 username = Column("username", String(255), nullable=True, unique=None, default=None)
1253 username = Column("username", String(255), nullable=True, unique=None, default=None)
1254 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1254 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1255 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1255 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1256 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1256 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1257 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1257 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1258 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1258 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1259
1259
1260 version = Column("version", String(255), nullable=True, default=VERSION_1)
1260 version = Column("version", String(255), nullable=True, default=VERSION_1)
1261 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1261 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1262 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1262 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1263
1263
1264 def __unicode__(self):
1264 def __unicode__(self):
1265 return u"<%s('id:%s:%s')>" % (
1265 return u"<%s('id:%s:%s')>" % (
1266 self.__class__.__name__, self.repository_name, self.action)
1266 self.__class__.__name__, self.repository_name, self.action)
1267
1267
1268 def __json__(self):
1268 def __json__(self):
1269 return {
1269 return {
1270 'user_id': self.user_id,
1270 'user_id': self.user_id,
1271 'username': self.username,
1271 'username': self.username,
1272 'repository_id': self.repository_id,
1272 'repository_id': self.repository_id,
1273 'repository_name': self.repository_name,
1273 'repository_name': self.repository_name,
1274 'user_ip': self.user_ip,
1274 'user_ip': self.user_ip,
1275 'action_date': self.action_date,
1275 'action_date': self.action_date,
1276 'action': self.action,
1276 'action': self.action,
1277 }
1277 }
1278
1278
1279 @hybrid_property
1279 @hybrid_property
1280 def entry_id(self):
1280 def entry_id(self):
1281 return self.user_log_id
1281 return self.user_log_id
1282
1282
1283 @property
1283 @property
1284 def action_as_day(self):
1284 def action_as_day(self):
1285 return datetime.date(*self.action_date.timetuple()[:3])
1285 return datetime.date(*self.action_date.timetuple()[:3])
1286
1286
1287 user = relationship('User')
1287 user = relationship('User')
1288 repository = relationship('Repository', cascade='')
1288 repository = relationship('Repository', cascade='')
1289
1289
1290
1290
1291 class UserGroup(Base, BaseModel):
1291 class UserGroup(Base, BaseModel):
1292 __tablename__ = 'users_groups'
1292 __tablename__ = 'users_groups'
1293 __table_args__ = (
1293 __table_args__ = (
1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1295 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1295 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1296 )
1296 )
1297
1297
1298 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1298 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1299 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1299 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1300 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1300 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1301 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1301 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1302 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1302 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1304 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1304 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1305 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1305 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1306
1306
1307 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1307 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1308 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1308 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1309 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1309 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1310 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1310 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1311 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1311 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1312 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1312 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1313
1313
1314 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1314 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1315 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1315 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1316
1316
1317 @classmethod
1317 @classmethod
1318 def _load_group_data(cls, column):
1318 def _load_group_data(cls, column):
1319 if not column:
1319 if not column:
1320 return {}
1320 return {}
1321
1321
1322 try:
1322 try:
1323 return json.loads(column) or {}
1323 return json.loads(column) or {}
1324 except TypeError:
1324 except TypeError:
1325 return {}
1325 return {}
1326
1326
1327 @hybrid_property
1327 @hybrid_property
1328 def description_safe(self):
1328 def description_safe(self):
1329 from rhodecode.lib import helpers as h
1329 from rhodecode.lib import helpers as h
1330 return h.escape(self.user_group_description)
1330 return h.escape(self.user_group_description)
1331
1331
1332 @hybrid_property
1332 @hybrid_property
1333 def group_data(self):
1333 def group_data(self):
1334 return self._load_group_data(self._group_data)
1334 return self._load_group_data(self._group_data)
1335
1335
1336 @group_data.expression
1336 @group_data.expression
1337 def group_data(self, **kwargs):
1337 def group_data(self, **kwargs):
1338 return self._group_data
1338 return self._group_data
1339
1339
1340 @group_data.setter
1340 @group_data.setter
1341 def group_data(self, val):
1341 def group_data(self, val):
1342 try:
1342 try:
1343 self._group_data = json.dumps(val)
1343 self._group_data = json.dumps(val)
1344 except Exception:
1344 except Exception:
1345 log.error(traceback.format_exc())
1345 log.error(traceback.format_exc())
1346
1346
1347 @classmethod
1347 @classmethod
1348 def _load_sync(cls, group_data):
1348 def _load_sync(cls, group_data):
1349 if group_data:
1349 if group_data:
1350 return group_data.get('extern_type')
1350 return group_data.get('extern_type')
1351
1351
1352 @property
1352 @property
1353 def sync(self):
1353 def sync(self):
1354 return self._load_sync(self.group_data)
1354 return self._load_sync(self.group_data)
1355
1355
1356 def __unicode__(self):
1356 def __unicode__(self):
1357 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1357 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1358 self.users_group_id,
1358 self.users_group_id,
1359 self.users_group_name)
1359 self.users_group_name)
1360
1360
1361 @classmethod
1361 @classmethod
1362 def get_by_group_name(cls, group_name, cache=False,
1362 def get_by_group_name(cls, group_name, cache=False,
1363 case_insensitive=False):
1363 case_insensitive=False):
1364 if case_insensitive:
1364 if case_insensitive:
1365 q = cls.query().filter(func.lower(cls.users_group_name) ==
1365 q = cls.query().filter(func.lower(cls.users_group_name) ==
1366 func.lower(group_name))
1366 func.lower(group_name))
1367
1367
1368 else:
1368 else:
1369 q = cls.query().filter(cls.users_group_name == group_name)
1369 q = cls.query().filter(cls.users_group_name == group_name)
1370 if cache:
1370 if cache:
1371 q = q.options(
1371 q = q.options(
1372 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1372 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1373 return q.scalar()
1373 return q.scalar()
1374
1374
1375 @classmethod
1375 @classmethod
1376 def get(cls, user_group_id, cache=False):
1376 def get(cls, user_group_id, cache=False):
1377 if not user_group_id:
1377 if not user_group_id:
1378 return
1378 return
1379
1379
1380 user_group = cls.query()
1380 user_group = cls.query()
1381 if cache:
1381 if cache:
1382 user_group = user_group.options(
1382 user_group = user_group.options(
1383 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1383 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1384 return user_group.get(user_group_id)
1384 return user_group.get(user_group_id)
1385
1385
1386 def permissions(self, with_admins=True, with_owner=True):
1386 def permissions(self, with_admins=True, with_owner=True):
1387 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1387 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1388 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1388 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1389 joinedload(UserUserGroupToPerm.user),
1389 joinedload(UserUserGroupToPerm.user),
1390 joinedload(UserUserGroupToPerm.permission),)
1390 joinedload(UserUserGroupToPerm.permission),)
1391
1391
1392 # get owners and admins and permissions. We do a trick of re-writing
1392 # get owners and admins and permissions. We do a trick of re-writing
1393 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1393 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1394 # has a global reference and changing one object propagates to all
1394 # has a global reference and changing one object propagates to all
1395 # others. This means if admin is also an owner admin_row that change
1395 # others. This means if admin is also an owner admin_row that change
1396 # would propagate to both objects
1396 # would propagate to both objects
1397 perm_rows = []
1397 perm_rows = []
1398 for _usr in q.all():
1398 for _usr in q.all():
1399 usr = AttributeDict(_usr.user.get_dict())
1399 usr = AttributeDict(_usr.user.get_dict())
1400 usr.permission = _usr.permission.permission_name
1400 usr.permission = _usr.permission.permission_name
1401 perm_rows.append(usr)
1401 perm_rows.append(usr)
1402
1402
1403 # filter the perm rows by 'default' first and then sort them by
1403 # filter the perm rows by 'default' first and then sort them by
1404 # admin,write,read,none permissions sorted again alphabetically in
1404 # admin,write,read,none permissions sorted again alphabetically in
1405 # each group
1405 # each group
1406 perm_rows = sorted(perm_rows, key=display_user_sort)
1406 perm_rows = sorted(perm_rows, key=display_user_sort)
1407
1407
1408 _admin_perm = 'usergroup.admin'
1408 _admin_perm = 'usergroup.admin'
1409 owner_row = []
1409 owner_row = []
1410 if with_owner:
1410 if with_owner:
1411 usr = AttributeDict(self.user.get_dict())
1411 usr = AttributeDict(self.user.get_dict())
1412 usr.owner_row = True
1412 usr.owner_row = True
1413 usr.permission = _admin_perm
1413 usr.permission = _admin_perm
1414 owner_row.append(usr)
1414 owner_row.append(usr)
1415
1415
1416 super_admin_rows = []
1416 super_admin_rows = []
1417 if with_admins:
1417 if with_admins:
1418 for usr in User.get_all_super_admins():
1418 for usr in User.get_all_super_admins():
1419 # if this admin is also owner, don't double the record
1419 # if this admin is also owner, don't double the record
1420 if usr.user_id == owner_row[0].user_id:
1420 if usr.user_id == owner_row[0].user_id:
1421 owner_row[0].admin_row = True
1421 owner_row[0].admin_row = True
1422 else:
1422 else:
1423 usr = AttributeDict(usr.get_dict())
1423 usr = AttributeDict(usr.get_dict())
1424 usr.admin_row = True
1424 usr.admin_row = True
1425 usr.permission = _admin_perm
1425 usr.permission = _admin_perm
1426 super_admin_rows.append(usr)
1426 super_admin_rows.append(usr)
1427
1427
1428 return super_admin_rows + owner_row + perm_rows
1428 return super_admin_rows + owner_row + perm_rows
1429
1429
1430 def permission_user_groups(self):
1430 def permission_user_groups(self):
1431 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1431 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1432 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1432 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1433 joinedload(UserGroupUserGroupToPerm.target_user_group),
1433 joinedload(UserGroupUserGroupToPerm.target_user_group),
1434 joinedload(UserGroupUserGroupToPerm.permission),)
1434 joinedload(UserGroupUserGroupToPerm.permission),)
1435
1435
1436 perm_rows = []
1436 perm_rows = []
1437 for _user_group in q.all():
1437 for _user_group in q.all():
1438 usr = AttributeDict(_user_group.user_group.get_dict())
1438 usr = AttributeDict(_user_group.user_group.get_dict())
1439 usr.permission = _user_group.permission.permission_name
1439 usr.permission = _user_group.permission.permission_name
1440 perm_rows.append(usr)
1440 perm_rows.append(usr)
1441
1441
1442 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1442 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1443 return perm_rows
1443 return perm_rows
1444
1444
1445 def _get_default_perms(self, user_group, suffix=''):
1445 def _get_default_perms(self, user_group, suffix=''):
1446 from rhodecode.model.permission import PermissionModel
1446 from rhodecode.model.permission import PermissionModel
1447 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1447 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1448
1448
1449 def get_default_perms(self, suffix=''):
1449 def get_default_perms(self, suffix=''):
1450 return self._get_default_perms(self, suffix)
1450 return self._get_default_perms(self, suffix)
1451
1451
1452 def get_api_data(self, with_group_members=True, include_secrets=False):
1452 def get_api_data(self, with_group_members=True, include_secrets=False):
1453 """
1453 """
1454 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1454 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1455 basically forwarded.
1455 basically forwarded.
1456
1456
1457 """
1457 """
1458 user_group = self
1458 user_group = self
1459 data = {
1459 data = {
1460 'users_group_id': user_group.users_group_id,
1460 'users_group_id': user_group.users_group_id,
1461 'group_name': user_group.users_group_name,
1461 'group_name': user_group.users_group_name,
1462 'group_description': user_group.user_group_description,
1462 'group_description': user_group.user_group_description,
1463 'active': user_group.users_group_active,
1463 'active': user_group.users_group_active,
1464 'owner': user_group.user.username,
1464 'owner': user_group.user.username,
1465 'sync': user_group.sync,
1465 'sync': user_group.sync,
1466 'owner_email': user_group.user.email,
1466 'owner_email': user_group.user.email,
1467 }
1467 }
1468
1468
1469 if with_group_members:
1469 if with_group_members:
1470 users = []
1470 users = []
1471 for user in user_group.members:
1471 for user in user_group.members:
1472 user = user.user
1472 user = user.user
1473 users.append(user.get_api_data(include_secrets=include_secrets))
1473 users.append(user.get_api_data(include_secrets=include_secrets))
1474 data['users'] = users
1474 data['users'] = users
1475
1475
1476 return data
1476 return data
1477
1477
1478
1478
1479 class UserGroupMember(Base, BaseModel):
1479 class UserGroupMember(Base, BaseModel):
1480 __tablename__ = 'users_groups_members'
1480 __tablename__ = 'users_groups_members'
1481 __table_args__ = (
1481 __table_args__ = (
1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1484 )
1484 )
1485
1485
1486 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1486 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1487 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1487 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1488 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1488 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1489
1489
1490 user = relationship('User', lazy='joined')
1490 user = relationship('User', lazy='joined')
1491 users_group = relationship('UserGroup')
1491 users_group = relationship('UserGroup')
1492
1492
1493 def __init__(self, gr_id='', u_id=''):
1493 def __init__(self, gr_id='', u_id=''):
1494 self.users_group_id = gr_id
1494 self.users_group_id = gr_id
1495 self.user_id = u_id
1495 self.user_id = u_id
1496
1496
1497
1497
1498 class RepositoryField(Base, BaseModel):
1498 class RepositoryField(Base, BaseModel):
1499 __tablename__ = 'repositories_fields'
1499 __tablename__ = 'repositories_fields'
1500 __table_args__ = (
1500 __table_args__ = (
1501 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1501 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1504 )
1504 )
1505 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1505 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1506
1506
1507 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1507 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1508 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1508 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1509 field_key = Column("field_key", String(250))
1509 field_key = Column("field_key", String(250))
1510 field_label = Column("field_label", String(1024), nullable=False)
1510 field_label = Column("field_label", String(1024), nullable=False)
1511 field_value = Column("field_value", String(10000), nullable=False)
1511 field_value = Column("field_value", String(10000), nullable=False)
1512 field_desc = Column("field_desc", String(1024), nullable=False)
1512 field_desc = Column("field_desc", String(1024), nullable=False)
1513 field_type = Column("field_type", String(255), nullable=False, unique=None)
1513 field_type = Column("field_type", String(255), nullable=False, unique=None)
1514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1515
1515
1516 repository = relationship('Repository')
1516 repository = relationship('Repository')
1517
1517
1518 @property
1518 @property
1519 def field_key_prefixed(self):
1519 def field_key_prefixed(self):
1520 return 'ex_%s' % self.field_key
1520 return 'ex_%s' % self.field_key
1521
1521
1522 @classmethod
1522 @classmethod
1523 def un_prefix_key(cls, key):
1523 def un_prefix_key(cls, key):
1524 if key.startswith(cls.PREFIX):
1524 if key.startswith(cls.PREFIX):
1525 return key[len(cls.PREFIX):]
1525 return key[len(cls.PREFIX):]
1526 return key
1526 return key
1527
1527
1528 @classmethod
1528 @classmethod
1529 def get_by_key_name(cls, key, repo):
1529 def get_by_key_name(cls, key, repo):
1530 row = cls.query()\
1530 row = cls.query()\
1531 .filter(cls.repository == repo)\
1531 .filter(cls.repository == repo)\
1532 .filter(cls.field_key == key).scalar()
1532 .filter(cls.field_key == key).scalar()
1533 return row
1533 return row
1534
1534
1535
1535
1536 class Repository(Base, BaseModel):
1536 class Repository(Base, BaseModel):
1537 __tablename__ = 'repositories'
1537 __tablename__ = 'repositories'
1538 __table_args__ = (
1538 __table_args__ = (
1539 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1539 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1540 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1540 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1541 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1541 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1542 )
1542 )
1543 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1543 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1544 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1544 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1545 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1545 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1546
1546
1547 STATE_CREATED = 'repo_state_created'
1547 STATE_CREATED = 'repo_state_created'
1548 STATE_PENDING = 'repo_state_pending'
1548 STATE_PENDING = 'repo_state_pending'
1549 STATE_ERROR = 'repo_state_error'
1549 STATE_ERROR = 'repo_state_error'
1550
1550
1551 LOCK_AUTOMATIC = 'lock_auto'
1551 LOCK_AUTOMATIC = 'lock_auto'
1552 LOCK_API = 'lock_api'
1552 LOCK_API = 'lock_api'
1553 LOCK_WEB = 'lock_web'
1553 LOCK_WEB = 'lock_web'
1554 LOCK_PULL = 'lock_pull'
1554 LOCK_PULL = 'lock_pull'
1555
1555
1556 NAME_SEP = URL_SEP
1556 NAME_SEP = URL_SEP
1557
1557
1558 repo_id = Column(
1558 repo_id = Column(
1559 "repo_id", Integer(), nullable=False, unique=True, default=None,
1559 "repo_id", Integer(), nullable=False, unique=True, default=None,
1560 primary_key=True)
1560 primary_key=True)
1561 _repo_name = Column(
1561 _repo_name = Column(
1562 "repo_name", Text(), nullable=False, default=None)
1562 "repo_name", Text(), nullable=False, default=None)
1563 _repo_name_hash = Column(
1563 _repo_name_hash = Column(
1564 "repo_name_hash", String(255), nullable=False, unique=True)
1564 "repo_name_hash", String(255), nullable=False, unique=True)
1565 repo_state = Column("repo_state", String(255), nullable=True)
1565 repo_state = Column("repo_state", String(255), nullable=True)
1566
1566
1567 clone_uri = Column(
1567 clone_uri = Column(
1568 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1568 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1569 default=None)
1569 default=None)
1570 push_uri = Column(
1570 push_uri = Column(
1571 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1571 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1572 default=None)
1572 default=None)
1573 repo_type = Column(
1573 repo_type = Column(
1574 "repo_type", String(255), nullable=False, unique=False, default=None)
1574 "repo_type", String(255), nullable=False, unique=False, default=None)
1575 user_id = Column(
1575 user_id = Column(
1576 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1576 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1577 unique=False, default=None)
1577 unique=False, default=None)
1578 private = Column(
1578 private = Column(
1579 "private", Boolean(), nullable=True, unique=None, default=None)
1579 "private", Boolean(), nullable=True, unique=None, default=None)
1580 enable_statistics = Column(
1580 enable_statistics = Column(
1581 "statistics", Boolean(), nullable=True, unique=None, default=True)
1581 "statistics", Boolean(), nullable=True, unique=None, default=True)
1582 enable_downloads = Column(
1582 enable_downloads = Column(
1583 "downloads", Boolean(), nullable=True, unique=None, default=True)
1583 "downloads", Boolean(), nullable=True, unique=None, default=True)
1584 description = Column(
1584 description = Column(
1585 "description", String(10000), nullable=True, unique=None, default=None)
1585 "description", String(10000), nullable=True, unique=None, default=None)
1586 created_on = Column(
1586 created_on = Column(
1587 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1587 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1588 default=datetime.datetime.now)
1588 default=datetime.datetime.now)
1589 updated_on = Column(
1589 updated_on = Column(
1590 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1590 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1591 default=datetime.datetime.now)
1591 default=datetime.datetime.now)
1592 _landing_revision = Column(
1592 _landing_revision = Column(
1593 "landing_revision", String(255), nullable=False, unique=False,
1593 "landing_revision", String(255), nullable=False, unique=False,
1594 default=None)
1594 default=None)
1595 enable_locking = Column(
1595 enable_locking = Column(
1596 "enable_locking", Boolean(), nullable=False, unique=None,
1596 "enable_locking", Boolean(), nullable=False, unique=None,
1597 default=False)
1597 default=False)
1598 _locked = Column(
1598 _locked = Column(
1599 "locked", String(255), nullable=True, unique=False, default=None)
1599 "locked", String(255), nullable=True, unique=False, default=None)
1600 _changeset_cache = Column(
1600 _changeset_cache = Column(
1601 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1601 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1602
1602
1603 fork_id = Column(
1603 fork_id = Column(
1604 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1604 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1605 nullable=True, unique=False, default=None)
1605 nullable=True, unique=False, default=None)
1606 group_id = Column(
1606 group_id = Column(
1607 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1607 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1608 unique=False, default=None)
1608 unique=False, default=None)
1609
1609
1610 user = relationship('User', lazy='joined')
1610 user = relationship('User', lazy='joined')
1611 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1611 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1612 group = relationship('RepoGroup', lazy='joined')
1612 group = relationship('RepoGroup', lazy='joined')
1613 repo_to_perm = relationship(
1613 repo_to_perm = relationship(
1614 'UserRepoToPerm', cascade='all',
1614 'UserRepoToPerm', cascade='all',
1615 order_by='UserRepoToPerm.repo_to_perm_id')
1615 order_by='UserRepoToPerm.repo_to_perm_id')
1616 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1616 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1617 stats = relationship('Statistics', cascade='all', uselist=False)
1617 stats = relationship('Statistics', cascade='all', uselist=False)
1618
1618
1619 followers = relationship(
1619 followers = relationship(
1620 'UserFollowing',
1620 'UserFollowing',
1621 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1621 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1622 cascade='all')
1622 cascade='all')
1623 extra_fields = relationship(
1623 extra_fields = relationship(
1624 'RepositoryField', cascade="all, delete, delete-orphan")
1624 'RepositoryField', cascade="all, delete, delete-orphan")
1625 logs = relationship('UserLog')
1625 logs = relationship('UserLog')
1626 comments = relationship(
1626 comments = relationship(
1627 'ChangesetComment', cascade="all, delete, delete-orphan")
1627 'ChangesetComment', cascade="all, delete, delete-orphan")
1628 pull_requests_source = relationship(
1628 pull_requests_source = relationship(
1629 'PullRequest',
1629 'PullRequest',
1630 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1630 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1631 cascade="all, delete, delete-orphan")
1631 cascade="all, delete, delete-orphan")
1632 pull_requests_target = relationship(
1632 pull_requests_target = relationship(
1633 'PullRequest',
1633 'PullRequest',
1634 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1634 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1635 cascade="all, delete, delete-orphan")
1635 cascade="all, delete, delete-orphan")
1636 ui = relationship('RepoRhodeCodeUi', cascade="all")
1636 ui = relationship('RepoRhodeCodeUi', cascade="all")
1637 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1637 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1638 integrations = relationship('Integration',
1638 integrations = relationship('Integration',
1639 cascade="all, delete, delete-orphan")
1639 cascade="all, delete, delete-orphan")
1640
1640
1641 scoped_tokens = relationship('UserApiKeys', cascade="all")
1641 scoped_tokens = relationship('UserApiKeys', cascade="all")
1642
1642
1643 def __unicode__(self):
1643 def __unicode__(self):
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1645 safe_unicode(self.repo_name))
1645 safe_unicode(self.repo_name))
1646
1646
1647 @hybrid_property
1647 @hybrid_property
1648 def description_safe(self):
1648 def description_safe(self):
1649 from rhodecode.lib import helpers as h
1649 from rhodecode.lib import helpers as h
1650 return h.escape(self.description)
1650 return h.escape(self.description)
1651
1651
1652 @hybrid_property
1652 @hybrid_property
1653 def landing_rev(self):
1653 def landing_rev(self):
1654 # always should return [rev_type, rev]
1654 # always should return [rev_type, rev]
1655 if self._landing_revision:
1655 if self._landing_revision:
1656 _rev_info = self._landing_revision.split(':')
1656 _rev_info = self._landing_revision.split(':')
1657 if len(_rev_info) < 2:
1657 if len(_rev_info) < 2:
1658 _rev_info.insert(0, 'rev')
1658 _rev_info.insert(0, 'rev')
1659 return [_rev_info[0], _rev_info[1]]
1659 return [_rev_info[0], _rev_info[1]]
1660 return [None, None]
1660 return [None, None]
1661
1661
1662 @landing_rev.setter
1662 @landing_rev.setter
1663 def landing_rev(self, val):
1663 def landing_rev(self, val):
1664 if ':' not in val:
1664 if ':' not in val:
1665 raise ValueError('value must be delimited with `:` and consist '
1665 raise ValueError('value must be delimited with `:` and consist '
1666 'of <rev_type>:<rev>, got %s instead' % val)
1666 'of <rev_type>:<rev>, got %s instead' % val)
1667 self._landing_revision = val
1667 self._landing_revision = val
1668
1668
1669 @hybrid_property
1669 @hybrid_property
1670 def locked(self):
1670 def locked(self):
1671 if self._locked:
1671 if self._locked:
1672 user_id, timelocked, reason = self._locked.split(':')
1672 user_id, timelocked, reason = self._locked.split(':')
1673 lock_values = int(user_id), timelocked, reason
1673 lock_values = int(user_id), timelocked, reason
1674 else:
1674 else:
1675 lock_values = [None, None, None]
1675 lock_values = [None, None, None]
1676 return lock_values
1676 return lock_values
1677
1677
1678 @locked.setter
1678 @locked.setter
1679 def locked(self, val):
1679 def locked(self, val):
1680 if val and isinstance(val, (list, tuple)):
1680 if val and isinstance(val, (list, tuple)):
1681 self._locked = ':'.join(map(str, val))
1681 self._locked = ':'.join(map(str, val))
1682 else:
1682 else:
1683 self._locked = None
1683 self._locked = None
1684
1684
1685 @hybrid_property
1685 @hybrid_property
1686 def changeset_cache(self):
1686 def changeset_cache(self):
1687 from rhodecode.lib.vcs.backends.base import EmptyCommit
1687 from rhodecode.lib.vcs.backends.base import EmptyCommit
1688 dummy = EmptyCommit().__json__()
1688 dummy = EmptyCommit().__json__()
1689 if not self._changeset_cache:
1689 if not self._changeset_cache:
1690 return dummy
1690 return dummy
1691 try:
1691 try:
1692 return json.loads(self._changeset_cache)
1692 return json.loads(self._changeset_cache)
1693 except TypeError:
1693 except TypeError:
1694 return dummy
1694 return dummy
1695 except Exception:
1695 except Exception:
1696 log.error(traceback.format_exc())
1696 log.error(traceback.format_exc())
1697 return dummy
1697 return dummy
1698
1698
1699 @changeset_cache.setter
1699 @changeset_cache.setter
1700 def changeset_cache(self, val):
1700 def changeset_cache(self, val):
1701 try:
1701 try:
1702 self._changeset_cache = json.dumps(val)
1702 self._changeset_cache = json.dumps(val)
1703 except Exception:
1703 except Exception:
1704 log.error(traceback.format_exc())
1704 log.error(traceback.format_exc())
1705
1705
1706 @hybrid_property
1706 @hybrid_property
1707 def repo_name(self):
1707 def repo_name(self):
1708 return self._repo_name
1708 return self._repo_name
1709
1709
1710 @repo_name.setter
1710 @repo_name.setter
1711 def repo_name(self, value):
1711 def repo_name(self, value):
1712 self._repo_name = value
1712 self._repo_name = value
1713 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1713 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1714
1714
1715 @classmethod
1715 @classmethod
1716 def normalize_repo_name(cls, repo_name):
1716 def normalize_repo_name(cls, repo_name):
1717 """
1717 """
1718 Normalizes os specific repo_name to the format internally stored inside
1718 Normalizes os specific repo_name to the format internally stored inside
1719 database using URL_SEP
1719 database using URL_SEP
1720
1720
1721 :param cls:
1721 :param cls:
1722 :param repo_name:
1722 :param repo_name:
1723 """
1723 """
1724 return cls.NAME_SEP.join(repo_name.split(os.sep))
1724 return cls.NAME_SEP.join(repo_name.split(os.sep))
1725
1725
1726 @classmethod
1726 @classmethod
1727 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1727 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1728 session = Session()
1728 session = Session()
1729 q = session.query(cls).filter(cls.repo_name == repo_name)
1729 q = session.query(cls).filter(cls.repo_name == repo_name)
1730
1730
1731 if cache:
1731 if cache:
1732 if identity_cache:
1732 if identity_cache:
1733 val = cls.identity_cache(session, 'repo_name', repo_name)
1733 val = cls.identity_cache(session, 'repo_name', repo_name)
1734 if val:
1734 if val:
1735 return val
1735 return val
1736 else:
1736 else:
1737 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1737 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1738 q = q.options(
1738 q = q.options(
1739 FromCache("sql_cache_short", cache_key))
1739 FromCache("sql_cache_short", cache_key))
1740
1740
1741 return q.scalar()
1741 return q.scalar()
1742
1742
1743 @classmethod
1743 @classmethod
1744 def get_by_id_or_repo_name(cls, repoid):
1744 def get_by_id_or_repo_name(cls, repoid):
1745 if isinstance(repoid, (int, long)):
1745 if isinstance(repoid, (int, long)):
1746 try:
1746 try:
1747 repo = cls.get(repoid)
1747 repo = cls.get(repoid)
1748 except ValueError:
1748 except ValueError:
1749 repo = None
1749 repo = None
1750 else:
1750 else:
1751 repo = cls.get_by_repo_name(repoid)
1751 repo = cls.get_by_repo_name(repoid)
1752 return repo
1752 return repo
1753
1753
1754 @classmethod
1754 @classmethod
1755 def get_by_full_path(cls, repo_full_path):
1755 def get_by_full_path(cls, repo_full_path):
1756 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1756 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1757 repo_name = cls.normalize_repo_name(repo_name)
1757 repo_name = cls.normalize_repo_name(repo_name)
1758 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1758 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1759
1759
1760 @classmethod
1760 @classmethod
1761 def get_repo_forks(cls, repo_id):
1761 def get_repo_forks(cls, repo_id):
1762 return cls.query().filter(Repository.fork_id == repo_id)
1762 return cls.query().filter(Repository.fork_id == repo_id)
1763
1763
1764 @classmethod
1764 @classmethod
1765 def base_path(cls):
1765 def base_path(cls):
1766 """
1766 """
1767 Returns base path when all repos are stored
1767 Returns base path when all repos are stored
1768
1768
1769 :param cls:
1769 :param cls:
1770 """
1770 """
1771 q = Session().query(RhodeCodeUi)\
1771 q = Session().query(RhodeCodeUi)\
1772 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1772 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1773 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1773 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1774 return q.one().ui_value
1774 return q.one().ui_value
1775
1775
1776 @classmethod
1776 @classmethod
1777 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1777 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1778 case_insensitive=True):
1778 case_insensitive=True):
1779 q = Repository.query()
1779 q = Repository.query()
1780
1780
1781 if not isinstance(user_id, Optional):
1781 if not isinstance(user_id, Optional):
1782 q = q.filter(Repository.user_id == user_id)
1782 q = q.filter(Repository.user_id == user_id)
1783
1783
1784 if not isinstance(group_id, Optional):
1784 if not isinstance(group_id, Optional):
1785 q = q.filter(Repository.group_id == group_id)
1785 q = q.filter(Repository.group_id == group_id)
1786
1786
1787 if case_insensitive:
1787 if case_insensitive:
1788 q = q.order_by(func.lower(Repository.repo_name))
1788 q = q.order_by(func.lower(Repository.repo_name))
1789 else:
1789 else:
1790 q = q.order_by(Repository.repo_name)
1790 q = q.order_by(Repository.repo_name)
1791 return q.all()
1791 return q.all()
1792
1792
1793 @property
1793 @property
1794 def forks(self):
1794 def forks(self):
1795 """
1795 """
1796 Return forks of this repo
1796 Return forks of this repo
1797 """
1797 """
1798 return Repository.get_repo_forks(self.repo_id)
1798 return Repository.get_repo_forks(self.repo_id)
1799
1799
1800 @property
1800 @property
1801 def parent(self):
1801 def parent(self):
1802 """
1802 """
1803 Returns fork parent
1803 Returns fork parent
1804 """
1804 """
1805 return self.fork
1805 return self.fork
1806
1806
1807 @property
1807 @property
1808 def just_name(self):
1808 def just_name(self):
1809 return self.repo_name.split(self.NAME_SEP)[-1]
1809 return self.repo_name.split(self.NAME_SEP)[-1]
1810
1810
1811 @property
1811 @property
1812 def groups_with_parents(self):
1812 def groups_with_parents(self):
1813 groups = []
1813 groups = []
1814 if self.group is None:
1814 if self.group is None:
1815 return groups
1815 return groups
1816
1816
1817 cur_gr = self.group
1817 cur_gr = self.group
1818 groups.insert(0, cur_gr)
1818 groups.insert(0, cur_gr)
1819 while 1:
1819 while 1:
1820 gr = getattr(cur_gr, 'parent_group', None)
1820 gr = getattr(cur_gr, 'parent_group', None)
1821 cur_gr = cur_gr.parent_group
1821 cur_gr = cur_gr.parent_group
1822 if gr is None:
1822 if gr is None:
1823 break
1823 break
1824 groups.insert(0, gr)
1824 groups.insert(0, gr)
1825
1825
1826 return groups
1826 return groups
1827
1827
1828 @property
1828 @property
1829 def groups_and_repo(self):
1829 def groups_and_repo(self):
1830 return self.groups_with_parents, self
1830 return self.groups_with_parents, self
1831
1831
1832 @LazyProperty
1832 @LazyProperty
1833 def repo_path(self):
1833 def repo_path(self):
1834 """
1834 """
1835 Returns base full path for that repository means where it actually
1835 Returns base full path for that repository means where it actually
1836 exists on a filesystem
1836 exists on a filesystem
1837 """
1837 """
1838 q = Session().query(RhodeCodeUi).filter(
1838 q = Session().query(RhodeCodeUi).filter(
1839 RhodeCodeUi.ui_key == self.NAME_SEP)
1839 RhodeCodeUi.ui_key == self.NAME_SEP)
1840 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1840 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1841 return q.one().ui_value
1841 return q.one().ui_value
1842
1842
1843 @property
1843 @property
1844 def repo_full_path(self):
1844 def repo_full_path(self):
1845 p = [self.repo_path]
1845 p = [self.repo_path]
1846 # we need to split the name by / since this is how we store the
1846 # we need to split the name by / since this is how we store the
1847 # names in the database, but that eventually needs to be converted
1847 # names in the database, but that eventually needs to be converted
1848 # into a valid system path
1848 # into a valid system path
1849 p += self.repo_name.split(self.NAME_SEP)
1849 p += self.repo_name.split(self.NAME_SEP)
1850 return os.path.join(*map(safe_unicode, p))
1850 return os.path.join(*map(safe_unicode, p))
1851
1851
1852 @property
1852 @property
1853 def cache_keys(self):
1853 def cache_keys(self):
1854 """
1854 """
1855 Returns associated cache keys for that repo
1855 Returns associated cache keys for that repo
1856 """
1856 """
1857 return CacheKey.query()\
1857 return CacheKey.query()\
1858 .filter(CacheKey.cache_args == self.repo_name)\
1858 .filter(CacheKey.cache_args == self.repo_name)\
1859 .order_by(CacheKey.cache_key)\
1859 .order_by(CacheKey.cache_key)\
1860 .all()
1860 .all()
1861
1861
1862 @property
1863 def cached_diffs_dir(self):
1864 path = self.repo_full_path
1865 return os.path.join(
1866 os.path.dirname(path),
1867 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1868
1869 def cached_diffs(self):
1870 diff_cache_dir = self.cached_diffs_dir
1871 if os.path.isdir(diff_cache_dir):
1872 return os.listdir(diff_cache_dir)
1873 return []
1874
1862 def get_new_name(self, repo_name):
1875 def get_new_name(self, repo_name):
1863 """
1876 """
1864 returns new full repository name based on assigned group and new new
1877 returns new full repository name based on assigned group and new new
1865
1878
1866 :param group_name:
1879 :param group_name:
1867 """
1880 """
1868 path_prefix = self.group.full_path_splitted if self.group else []
1881 path_prefix = self.group.full_path_splitted if self.group else []
1869 return self.NAME_SEP.join(path_prefix + [repo_name])
1882 return self.NAME_SEP.join(path_prefix + [repo_name])
1870
1883
1871 @property
1884 @property
1872 def _config(self):
1885 def _config(self):
1873 """
1886 """
1874 Returns db based config object.
1887 Returns db based config object.
1875 """
1888 """
1876 from rhodecode.lib.utils import make_db_config
1889 from rhodecode.lib.utils import make_db_config
1877 return make_db_config(clear_session=False, repo=self)
1890 return make_db_config(clear_session=False, repo=self)
1878
1891
1879 def permissions(self, with_admins=True, with_owner=True):
1892 def permissions(self, with_admins=True, with_owner=True):
1880 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1893 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1881 q = q.options(joinedload(UserRepoToPerm.repository),
1894 q = q.options(joinedload(UserRepoToPerm.repository),
1882 joinedload(UserRepoToPerm.user),
1895 joinedload(UserRepoToPerm.user),
1883 joinedload(UserRepoToPerm.permission),)
1896 joinedload(UserRepoToPerm.permission),)
1884
1897
1885 # get owners and admins and permissions. We do a trick of re-writing
1898 # get owners and admins and permissions. We do a trick of re-writing
1886 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1899 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1887 # has a global reference and changing one object propagates to all
1900 # has a global reference and changing one object propagates to all
1888 # others. This means if admin is also an owner admin_row that change
1901 # others. This means if admin is also an owner admin_row that change
1889 # would propagate to both objects
1902 # would propagate to both objects
1890 perm_rows = []
1903 perm_rows = []
1891 for _usr in q.all():
1904 for _usr in q.all():
1892 usr = AttributeDict(_usr.user.get_dict())
1905 usr = AttributeDict(_usr.user.get_dict())
1893 usr.permission = _usr.permission.permission_name
1906 usr.permission = _usr.permission.permission_name
1894 perm_rows.append(usr)
1907 perm_rows.append(usr)
1895
1908
1896 # filter the perm rows by 'default' first and then sort them by
1909 # filter the perm rows by 'default' first and then sort them by
1897 # admin,write,read,none permissions sorted again alphabetically in
1910 # admin,write,read,none permissions sorted again alphabetically in
1898 # each group
1911 # each group
1899 perm_rows = sorted(perm_rows, key=display_user_sort)
1912 perm_rows = sorted(perm_rows, key=display_user_sort)
1900
1913
1901 _admin_perm = 'repository.admin'
1914 _admin_perm = 'repository.admin'
1902 owner_row = []
1915 owner_row = []
1903 if with_owner:
1916 if with_owner:
1904 usr = AttributeDict(self.user.get_dict())
1917 usr = AttributeDict(self.user.get_dict())
1905 usr.owner_row = True
1918 usr.owner_row = True
1906 usr.permission = _admin_perm
1919 usr.permission = _admin_perm
1907 owner_row.append(usr)
1920 owner_row.append(usr)
1908
1921
1909 super_admin_rows = []
1922 super_admin_rows = []
1910 if with_admins:
1923 if with_admins:
1911 for usr in User.get_all_super_admins():
1924 for usr in User.get_all_super_admins():
1912 # if this admin is also owner, don't double the record
1925 # if this admin is also owner, don't double the record
1913 if usr.user_id == owner_row[0].user_id:
1926 if usr.user_id == owner_row[0].user_id:
1914 owner_row[0].admin_row = True
1927 owner_row[0].admin_row = True
1915 else:
1928 else:
1916 usr = AttributeDict(usr.get_dict())
1929 usr = AttributeDict(usr.get_dict())
1917 usr.admin_row = True
1930 usr.admin_row = True
1918 usr.permission = _admin_perm
1931 usr.permission = _admin_perm
1919 super_admin_rows.append(usr)
1932 super_admin_rows.append(usr)
1920
1933
1921 return super_admin_rows + owner_row + perm_rows
1934 return super_admin_rows + owner_row + perm_rows
1922
1935
1923 def permission_user_groups(self):
1936 def permission_user_groups(self):
1924 q = UserGroupRepoToPerm.query().filter(
1937 q = UserGroupRepoToPerm.query().filter(
1925 UserGroupRepoToPerm.repository == self)
1938 UserGroupRepoToPerm.repository == self)
1926 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1939 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1927 joinedload(UserGroupRepoToPerm.users_group),
1940 joinedload(UserGroupRepoToPerm.users_group),
1928 joinedload(UserGroupRepoToPerm.permission),)
1941 joinedload(UserGroupRepoToPerm.permission),)
1929
1942
1930 perm_rows = []
1943 perm_rows = []
1931 for _user_group in q.all():
1944 for _user_group in q.all():
1932 usr = AttributeDict(_user_group.users_group.get_dict())
1945 usr = AttributeDict(_user_group.users_group.get_dict())
1933 usr.permission = _user_group.permission.permission_name
1946 usr.permission = _user_group.permission.permission_name
1934 perm_rows.append(usr)
1947 perm_rows.append(usr)
1935
1948
1936 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1949 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1937 return perm_rows
1950 return perm_rows
1938
1951
1939 def get_api_data(self, include_secrets=False):
1952 def get_api_data(self, include_secrets=False):
1940 """
1953 """
1941 Common function for generating repo api data
1954 Common function for generating repo api data
1942
1955
1943 :param include_secrets: See :meth:`User.get_api_data`.
1956 :param include_secrets: See :meth:`User.get_api_data`.
1944
1957
1945 """
1958 """
1946 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1959 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1947 # move this methods on models level.
1960 # move this methods on models level.
1948 from rhodecode.model.settings import SettingsModel
1961 from rhodecode.model.settings import SettingsModel
1949 from rhodecode.model.repo import RepoModel
1962 from rhodecode.model.repo import RepoModel
1950
1963
1951 repo = self
1964 repo = self
1952 _user_id, _time, _reason = self.locked
1965 _user_id, _time, _reason = self.locked
1953
1966
1954 data = {
1967 data = {
1955 'repo_id': repo.repo_id,
1968 'repo_id': repo.repo_id,
1956 'repo_name': repo.repo_name,
1969 'repo_name': repo.repo_name,
1957 'repo_type': repo.repo_type,
1970 'repo_type': repo.repo_type,
1958 'clone_uri': repo.clone_uri or '',
1971 'clone_uri': repo.clone_uri or '',
1959 'push_uri': repo.push_uri or '',
1972 'push_uri': repo.push_uri or '',
1960 'url': RepoModel().get_url(self),
1973 'url': RepoModel().get_url(self),
1961 'private': repo.private,
1974 'private': repo.private,
1962 'created_on': repo.created_on,
1975 'created_on': repo.created_on,
1963 'description': repo.description_safe,
1976 'description': repo.description_safe,
1964 'landing_rev': repo.landing_rev,
1977 'landing_rev': repo.landing_rev,
1965 'owner': repo.user.username,
1978 'owner': repo.user.username,
1966 'fork_of': repo.fork.repo_name if repo.fork else None,
1979 'fork_of': repo.fork.repo_name if repo.fork else None,
1967 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1980 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1968 'enable_statistics': repo.enable_statistics,
1981 'enable_statistics': repo.enable_statistics,
1969 'enable_locking': repo.enable_locking,
1982 'enable_locking': repo.enable_locking,
1970 'enable_downloads': repo.enable_downloads,
1983 'enable_downloads': repo.enable_downloads,
1971 'last_changeset': repo.changeset_cache,
1984 'last_changeset': repo.changeset_cache,
1972 'locked_by': User.get(_user_id).get_api_data(
1985 'locked_by': User.get(_user_id).get_api_data(
1973 include_secrets=include_secrets) if _user_id else None,
1986 include_secrets=include_secrets) if _user_id else None,
1974 'locked_date': time_to_datetime(_time) if _time else None,
1987 'locked_date': time_to_datetime(_time) if _time else None,
1975 'lock_reason': _reason if _reason else None,
1988 'lock_reason': _reason if _reason else None,
1976 }
1989 }
1977
1990
1978 # TODO: mikhail: should be per-repo settings here
1991 # TODO: mikhail: should be per-repo settings here
1979 rc_config = SettingsModel().get_all_settings()
1992 rc_config = SettingsModel().get_all_settings()
1980 repository_fields = str2bool(
1993 repository_fields = str2bool(
1981 rc_config.get('rhodecode_repository_fields'))
1994 rc_config.get('rhodecode_repository_fields'))
1982 if repository_fields:
1995 if repository_fields:
1983 for f in self.extra_fields:
1996 for f in self.extra_fields:
1984 data[f.field_key_prefixed] = f.field_value
1997 data[f.field_key_prefixed] = f.field_value
1985
1998
1986 return data
1999 return data
1987
2000
1988 @classmethod
2001 @classmethod
1989 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2002 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1990 if not lock_time:
2003 if not lock_time:
1991 lock_time = time.time()
2004 lock_time = time.time()
1992 if not lock_reason:
2005 if not lock_reason:
1993 lock_reason = cls.LOCK_AUTOMATIC
2006 lock_reason = cls.LOCK_AUTOMATIC
1994 repo.locked = [user_id, lock_time, lock_reason]
2007 repo.locked = [user_id, lock_time, lock_reason]
1995 Session().add(repo)
2008 Session().add(repo)
1996 Session().commit()
2009 Session().commit()
1997
2010
1998 @classmethod
2011 @classmethod
1999 def unlock(cls, repo):
2012 def unlock(cls, repo):
2000 repo.locked = None
2013 repo.locked = None
2001 Session().add(repo)
2014 Session().add(repo)
2002 Session().commit()
2015 Session().commit()
2003
2016
2004 @classmethod
2017 @classmethod
2005 def getlock(cls, repo):
2018 def getlock(cls, repo):
2006 return repo.locked
2019 return repo.locked
2007
2020
2008 def is_user_lock(self, user_id):
2021 def is_user_lock(self, user_id):
2009 if self.lock[0]:
2022 if self.lock[0]:
2010 lock_user_id = safe_int(self.lock[0])
2023 lock_user_id = safe_int(self.lock[0])
2011 user_id = safe_int(user_id)
2024 user_id = safe_int(user_id)
2012 # both are ints, and they are equal
2025 # both are ints, and they are equal
2013 return all([lock_user_id, user_id]) and lock_user_id == user_id
2026 return all([lock_user_id, user_id]) and lock_user_id == user_id
2014
2027
2015 return False
2028 return False
2016
2029
2017 def get_locking_state(self, action, user_id, only_when_enabled=True):
2030 def get_locking_state(self, action, user_id, only_when_enabled=True):
2018 """
2031 """
2019 Checks locking on this repository, if locking is enabled and lock is
2032 Checks locking on this repository, if locking is enabled and lock is
2020 present returns a tuple of make_lock, locked, locked_by.
2033 present returns a tuple of make_lock, locked, locked_by.
2021 make_lock can have 3 states None (do nothing) True, make lock
2034 make_lock can have 3 states None (do nothing) True, make lock
2022 False release lock, This value is later propagated to hooks, which
2035 False release lock, This value is later propagated to hooks, which
2023 do the locking. Think about this as signals passed to hooks what to do.
2036 do the locking. Think about this as signals passed to hooks what to do.
2024
2037
2025 """
2038 """
2026 # TODO: johbo: This is part of the business logic and should be moved
2039 # TODO: johbo: This is part of the business logic and should be moved
2027 # into the RepositoryModel.
2040 # into the RepositoryModel.
2028
2041
2029 if action not in ('push', 'pull'):
2042 if action not in ('push', 'pull'):
2030 raise ValueError("Invalid action value: %s" % repr(action))
2043 raise ValueError("Invalid action value: %s" % repr(action))
2031
2044
2032 # defines if locked error should be thrown to user
2045 # defines if locked error should be thrown to user
2033 currently_locked = False
2046 currently_locked = False
2034 # defines if new lock should be made, tri-state
2047 # defines if new lock should be made, tri-state
2035 make_lock = None
2048 make_lock = None
2036 repo = self
2049 repo = self
2037 user = User.get(user_id)
2050 user = User.get(user_id)
2038
2051
2039 lock_info = repo.locked
2052 lock_info = repo.locked
2040
2053
2041 if repo and (repo.enable_locking or not only_when_enabled):
2054 if repo and (repo.enable_locking or not only_when_enabled):
2042 if action == 'push':
2055 if action == 'push':
2043 # check if it's already locked !, if it is compare users
2056 # check if it's already locked !, if it is compare users
2044 locked_by_user_id = lock_info[0]
2057 locked_by_user_id = lock_info[0]
2045 if user.user_id == locked_by_user_id:
2058 if user.user_id == locked_by_user_id:
2046 log.debug(
2059 log.debug(
2047 'Got `push` action from user %s, now unlocking', user)
2060 'Got `push` action from user %s, now unlocking', user)
2048 # unlock if we have push from user who locked
2061 # unlock if we have push from user who locked
2049 make_lock = False
2062 make_lock = False
2050 else:
2063 else:
2051 # we're not the same user who locked, ban with
2064 # we're not the same user who locked, ban with
2052 # code defined in settings (default is 423 HTTP Locked) !
2065 # code defined in settings (default is 423 HTTP Locked) !
2053 log.debug('Repo %s is currently locked by %s', repo, user)
2066 log.debug('Repo %s is currently locked by %s', repo, user)
2054 currently_locked = True
2067 currently_locked = True
2055 elif action == 'pull':
2068 elif action == 'pull':
2056 # [0] user [1] date
2069 # [0] user [1] date
2057 if lock_info[0] and lock_info[1]:
2070 if lock_info[0] and lock_info[1]:
2058 log.debug('Repo %s is currently locked by %s', repo, user)
2071 log.debug('Repo %s is currently locked by %s', repo, user)
2059 currently_locked = True
2072 currently_locked = True
2060 else:
2073 else:
2061 log.debug('Setting lock on repo %s by %s', repo, user)
2074 log.debug('Setting lock on repo %s by %s', repo, user)
2062 make_lock = True
2075 make_lock = True
2063
2076
2064 else:
2077 else:
2065 log.debug('Repository %s do not have locking enabled', repo)
2078 log.debug('Repository %s do not have locking enabled', repo)
2066
2079
2067 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2080 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2068 make_lock, currently_locked, lock_info)
2081 make_lock, currently_locked, lock_info)
2069
2082
2070 from rhodecode.lib.auth import HasRepoPermissionAny
2083 from rhodecode.lib.auth import HasRepoPermissionAny
2071 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2084 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2072 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2085 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2073 # if we don't have at least write permission we cannot make a lock
2086 # if we don't have at least write permission we cannot make a lock
2074 log.debug('lock state reset back to FALSE due to lack '
2087 log.debug('lock state reset back to FALSE due to lack '
2075 'of at least read permission')
2088 'of at least read permission')
2076 make_lock = False
2089 make_lock = False
2077
2090
2078 return make_lock, currently_locked, lock_info
2091 return make_lock, currently_locked, lock_info
2079
2092
2080 @property
2093 @property
2081 def last_db_change(self):
2094 def last_db_change(self):
2082 return self.updated_on
2095 return self.updated_on
2083
2096
2084 @property
2097 @property
2085 def clone_uri_hidden(self):
2098 def clone_uri_hidden(self):
2086 clone_uri = self.clone_uri
2099 clone_uri = self.clone_uri
2087 if clone_uri:
2100 if clone_uri:
2088 import urlobject
2101 import urlobject
2089 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2102 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2090 if url_obj.password:
2103 if url_obj.password:
2091 clone_uri = url_obj.with_password('*****')
2104 clone_uri = url_obj.with_password('*****')
2092 return clone_uri
2105 return clone_uri
2093
2106
2094 @property
2107 @property
2095 def push_uri_hidden(self):
2108 def push_uri_hidden(self):
2096 push_uri = self.push_uri
2109 push_uri = self.push_uri
2097 if push_uri:
2110 if push_uri:
2098 import urlobject
2111 import urlobject
2099 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2112 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2100 if url_obj.password:
2113 if url_obj.password:
2101 push_uri = url_obj.with_password('*****')
2114 push_uri = url_obj.with_password('*****')
2102 return push_uri
2115 return push_uri
2103
2116
2104 def clone_url(self, **override):
2117 def clone_url(self, **override):
2105 from rhodecode.model.settings import SettingsModel
2118 from rhodecode.model.settings import SettingsModel
2106
2119
2107 uri_tmpl = None
2120 uri_tmpl = None
2108 if 'with_id' in override:
2121 if 'with_id' in override:
2109 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2122 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2110 del override['with_id']
2123 del override['with_id']
2111
2124
2112 if 'uri_tmpl' in override:
2125 if 'uri_tmpl' in override:
2113 uri_tmpl = override['uri_tmpl']
2126 uri_tmpl = override['uri_tmpl']
2114 del override['uri_tmpl']
2127 del override['uri_tmpl']
2115
2128
2116 ssh = False
2129 ssh = False
2117 if 'ssh' in override:
2130 if 'ssh' in override:
2118 ssh = True
2131 ssh = True
2119 del override['ssh']
2132 del override['ssh']
2120
2133
2121 # we didn't override our tmpl from **overrides
2134 # we didn't override our tmpl from **overrides
2122 if not uri_tmpl:
2135 if not uri_tmpl:
2123 rc_config = SettingsModel().get_all_settings(cache=True)
2136 rc_config = SettingsModel().get_all_settings(cache=True)
2124 if ssh:
2137 if ssh:
2125 uri_tmpl = rc_config.get(
2138 uri_tmpl = rc_config.get(
2126 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2139 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2127 else:
2140 else:
2128 uri_tmpl = rc_config.get(
2141 uri_tmpl = rc_config.get(
2129 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2142 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2130
2143
2131 request = get_current_request()
2144 request = get_current_request()
2132 return get_clone_url(request=request,
2145 return get_clone_url(request=request,
2133 uri_tmpl=uri_tmpl,
2146 uri_tmpl=uri_tmpl,
2134 repo_name=self.repo_name,
2147 repo_name=self.repo_name,
2135 repo_id=self.repo_id, **override)
2148 repo_id=self.repo_id, **override)
2136
2149
2137 def set_state(self, state):
2150 def set_state(self, state):
2138 self.repo_state = state
2151 self.repo_state = state
2139 Session().add(self)
2152 Session().add(self)
2140 #==========================================================================
2153 #==========================================================================
2141 # SCM PROPERTIES
2154 # SCM PROPERTIES
2142 #==========================================================================
2155 #==========================================================================
2143
2156
2144 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2157 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2145 return get_commit_safe(
2158 return get_commit_safe(
2146 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2159 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2147
2160
2148 def get_changeset(self, rev=None, pre_load=None):
2161 def get_changeset(self, rev=None, pre_load=None):
2149 warnings.warn("Use get_commit", DeprecationWarning)
2162 warnings.warn("Use get_commit", DeprecationWarning)
2150 commit_id = None
2163 commit_id = None
2151 commit_idx = None
2164 commit_idx = None
2152 if isinstance(rev, basestring):
2165 if isinstance(rev, basestring):
2153 commit_id = rev
2166 commit_id = rev
2154 else:
2167 else:
2155 commit_idx = rev
2168 commit_idx = rev
2156 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2169 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2157 pre_load=pre_load)
2170 pre_load=pre_load)
2158
2171
2159 def get_landing_commit(self):
2172 def get_landing_commit(self):
2160 """
2173 """
2161 Returns landing commit, or if that doesn't exist returns the tip
2174 Returns landing commit, or if that doesn't exist returns the tip
2162 """
2175 """
2163 _rev_type, _rev = self.landing_rev
2176 _rev_type, _rev = self.landing_rev
2164 commit = self.get_commit(_rev)
2177 commit = self.get_commit(_rev)
2165 if isinstance(commit, EmptyCommit):
2178 if isinstance(commit, EmptyCommit):
2166 return self.get_commit()
2179 return self.get_commit()
2167 return commit
2180 return commit
2168
2181
2169 def update_commit_cache(self, cs_cache=None, config=None):
2182 def update_commit_cache(self, cs_cache=None, config=None):
2170 """
2183 """
2171 Update cache of last changeset for repository, keys should be::
2184 Update cache of last changeset for repository, keys should be::
2172
2185
2173 short_id
2186 short_id
2174 raw_id
2187 raw_id
2175 revision
2188 revision
2176 parents
2189 parents
2177 message
2190 message
2178 date
2191 date
2179 author
2192 author
2180
2193
2181 :param cs_cache:
2194 :param cs_cache:
2182 """
2195 """
2183 from rhodecode.lib.vcs.backends.base import BaseChangeset
2196 from rhodecode.lib.vcs.backends.base import BaseChangeset
2184 if cs_cache is None:
2197 if cs_cache is None:
2185 # use no-cache version here
2198 # use no-cache version here
2186 scm_repo = self.scm_instance(cache=False, config=config)
2199 scm_repo = self.scm_instance(cache=False, config=config)
2187 if scm_repo:
2200 if scm_repo:
2188 cs_cache = scm_repo.get_commit(
2201 cs_cache = scm_repo.get_commit(
2189 pre_load=["author", "date", "message", "parents"])
2202 pre_load=["author", "date", "message", "parents"])
2190 else:
2203 else:
2191 cs_cache = EmptyCommit()
2204 cs_cache = EmptyCommit()
2192
2205
2193 if isinstance(cs_cache, BaseChangeset):
2206 if isinstance(cs_cache, BaseChangeset):
2194 cs_cache = cs_cache.__json__()
2207 cs_cache = cs_cache.__json__()
2195
2208
2196 def is_outdated(new_cs_cache):
2209 def is_outdated(new_cs_cache):
2197 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2210 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2198 new_cs_cache['revision'] != self.changeset_cache['revision']):
2211 new_cs_cache['revision'] != self.changeset_cache['revision']):
2199 return True
2212 return True
2200 return False
2213 return False
2201
2214
2202 # check if we have maybe already latest cached revision
2215 # check if we have maybe already latest cached revision
2203 if is_outdated(cs_cache) or not self.changeset_cache:
2216 if is_outdated(cs_cache) or not self.changeset_cache:
2204 _default = datetime.datetime.fromtimestamp(0)
2217 _default = datetime.datetime.fromtimestamp(0)
2205 last_change = cs_cache.get('date') or _default
2218 last_change = cs_cache.get('date') or _default
2206 log.debug('updated repo %s with new cs cache %s',
2219 log.debug('updated repo %s with new cs cache %s',
2207 self.repo_name, cs_cache)
2220 self.repo_name, cs_cache)
2208 self.updated_on = last_change
2221 self.updated_on = last_change
2209 self.changeset_cache = cs_cache
2222 self.changeset_cache = cs_cache
2210 Session().add(self)
2223 Session().add(self)
2211 Session().commit()
2224 Session().commit()
2212 else:
2225 else:
2213 log.debug('Skipping update_commit_cache for repo:`%s` '
2226 log.debug('Skipping update_commit_cache for repo:`%s` '
2214 'commit already with latest changes', self.repo_name)
2227 'commit already with latest changes', self.repo_name)
2215
2228
2216 @property
2229 @property
2217 def tip(self):
2230 def tip(self):
2218 return self.get_commit('tip')
2231 return self.get_commit('tip')
2219
2232
2220 @property
2233 @property
2221 def author(self):
2234 def author(self):
2222 return self.tip.author
2235 return self.tip.author
2223
2236
2224 @property
2237 @property
2225 def last_change(self):
2238 def last_change(self):
2226 return self.scm_instance().last_change
2239 return self.scm_instance().last_change
2227
2240
2228 def get_comments(self, revisions=None):
2241 def get_comments(self, revisions=None):
2229 """
2242 """
2230 Returns comments for this repository grouped by revisions
2243 Returns comments for this repository grouped by revisions
2231
2244
2232 :param revisions: filter query by revisions only
2245 :param revisions: filter query by revisions only
2233 """
2246 """
2234 cmts = ChangesetComment.query()\
2247 cmts = ChangesetComment.query()\
2235 .filter(ChangesetComment.repo == self)
2248 .filter(ChangesetComment.repo == self)
2236 if revisions:
2249 if revisions:
2237 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2250 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2238 grouped = collections.defaultdict(list)
2251 grouped = collections.defaultdict(list)
2239 for cmt in cmts.all():
2252 for cmt in cmts.all():
2240 grouped[cmt.revision].append(cmt)
2253 grouped[cmt.revision].append(cmt)
2241 return grouped
2254 return grouped
2242
2255
2243 def statuses(self, revisions=None):
2256 def statuses(self, revisions=None):
2244 """
2257 """
2245 Returns statuses for this repository
2258 Returns statuses for this repository
2246
2259
2247 :param revisions: list of revisions to get statuses for
2260 :param revisions: list of revisions to get statuses for
2248 """
2261 """
2249 statuses = ChangesetStatus.query()\
2262 statuses = ChangesetStatus.query()\
2250 .filter(ChangesetStatus.repo == self)\
2263 .filter(ChangesetStatus.repo == self)\
2251 .filter(ChangesetStatus.version == 0)
2264 .filter(ChangesetStatus.version == 0)
2252
2265
2253 if revisions:
2266 if revisions:
2254 # Try doing the filtering in chunks to avoid hitting limits
2267 # Try doing the filtering in chunks to avoid hitting limits
2255 size = 500
2268 size = 500
2256 status_results = []
2269 status_results = []
2257 for chunk in xrange(0, len(revisions), size):
2270 for chunk in xrange(0, len(revisions), size):
2258 status_results += statuses.filter(
2271 status_results += statuses.filter(
2259 ChangesetStatus.revision.in_(
2272 ChangesetStatus.revision.in_(
2260 revisions[chunk: chunk+size])
2273 revisions[chunk: chunk+size])
2261 ).all()
2274 ).all()
2262 else:
2275 else:
2263 status_results = statuses.all()
2276 status_results = statuses.all()
2264
2277
2265 grouped = {}
2278 grouped = {}
2266
2279
2267 # maybe we have open new pullrequest without a status?
2280 # maybe we have open new pullrequest without a status?
2268 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2281 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2269 status_lbl = ChangesetStatus.get_status_lbl(stat)
2282 status_lbl = ChangesetStatus.get_status_lbl(stat)
2270 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2283 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2271 for rev in pr.revisions:
2284 for rev in pr.revisions:
2272 pr_id = pr.pull_request_id
2285 pr_id = pr.pull_request_id
2273 pr_repo = pr.target_repo.repo_name
2286 pr_repo = pr.target_repo.repo_name
2274 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2287 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2275
2288
2276 for stat in status_results:
2289 for stat in status_results:
2277 pr_id = pr_repo = None
2290 pr_id = pr_repo = None
2278 if stat.pull_request:
2291 if stat.pull_request:
2279 pr_id = stat.pull_request.pull_request_id
2292 pr_id = stat.pull_request.pull_request_id
2280 pr_repo = stat.pull_request.target_repo.repo_name
2293 pr_repo = stat.pull_request.target_repo.repo_name
2281 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2294 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2282 pr_id, pr_repo]
2295 pr_id, pr_repo]
2283 return grouped
2296 return grouped
2284
2297
2285 # ==========================================================================
2298 # ==========================================================================
2286 # SCM CACHE INSTANCE
2299 # SCM CACHE INSTANCE
2287 # ==========================================================================
2300 # ==========================================================================
2288
2301
2289 def scm_instance(self, **kwargs):
2302 def scm_instance(self, **kwargs):
2290 import rhodecode
2303 import rhodecode
2291
2304
2292 # Passing a config will not hit the cache currently only used
2305 # Passing a config will not hit the cache currently only used
2293 # for repo2dbmapper
2306 # for repo2dbmapper
2294 config = kwargs.pop('config', None)
2307 config = kwargs.pop('config', None)
2295 cache = kwargs.pop('cache', None)
2308 cache = kwargs.pop('cache', None)
2296 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2309 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2297 # if cache is NOT defined use default global, else we have a full
2310 # if cache is NOT defined use default global, else we have a full
2298 # control over cache behaviour
2311 # control over cache behaviour
2299 if cache is None and full_cache and not config:
2312 if cache is None and full_cache and not config:
2300 return self._get_instance_cached()
2313 return self._get_instance_cached()
2301 return self._get_instance(cache=bool(cache), config=config)
2314 return self._get_instance(cache=bool(cache), config=config)
2302
2315
2303 def _get_instance_cached(self):
2316 def _get_instance_cached(self):
2304 @cache_region('long_term')
2317 @cache_region('long_term')
2305 def _get_repo(cache_key):
2318 def _get_repo(cache_key):
2306 return self._get_instance()
2319 return self._get_instance()
2307
2320
2308 invalidator_context = CacheKey.repo_context_cache(
2321 invalidator_context = CacheKey.repo_context_cache(
2309 _get_repo, self.repo_name, None, thread_scoped=True)
2322 _get_repo, self.repo_name, None, thread_scoped=True)
2310
2323
2311 with invalidator_context as context:
2324 with invalidator_context as context:
2312 context.invalidate()
2325 context.invalidate()
2313 repo = context.compute()
2326 repo = context.compute()
2314
2327
2315 return repo
2328 return repo
2316
2329
2317 def _get_instance(self, cache=True, config=None):
2330 def _get_instance(self, cache=True, config=None):
2318 config = config or self._config
2331 config = config or self._config
2319 custom_wire = {
2332 custom_wire = {
2320 'cache': cache # controls the vcs.remote cache
2333 'cache': cache # controls the vcs.remote cache
2321 }
2334 }
2322 repo = get_vcs_instance(
2335 repo = get_vcs_instance(
2323 repo_path=safe_str(self.repo_full_path),
2336 repo_path=safe_str(self.repo_full_path),
2324 config=config,
2337 config=config,
2325 with_wire=custom_wire,
2338 with_wire=custom_wire,
2326 create=False,
2339 create=False,
2327 _vcs_alias=self.repo_type)
2340 _vcs_alias=self.repo_type)
2328
2341
2329 return repo
2342 return repo
2330
2343
2331 def __json__(self):
2344 def __json__(self):
2332 return {'landing_rev': self.landing_rev}
2345 return {'landing_rev': self.landing_rev}
2333
2346
2334 def get_dict(self):
2347 def get_dict(self):
2335
2348
2336 # Since we transformed `repo_name` to a hybrid property, we need to
2349 # Since we transformed `repo_name` to a hybrid property, we need to
2337 # keep compatibility with the code which uses `repo_name` field.
2350 # keep compatibility with the code which uses `repo_name` field.
2338
2351
2339 result = super(Repository, self).get_dict()
2352 result = super(Repository, self).get_dict()
2340 result['repo_name'] = result.pop('_repo_name', None)
2353 result['repo_name'] = result.pop('_repo_name', None)
2341 return result
2354 return result
2342
2355
2343
2356
2344 class RepoGroup(Base, BaseModel):
2357 class RepoGroup(Base, BaseModel):
2345 __tablename__ = 'groups'
2358 __tablename__ = 'groups'
2346 __table_args__ = (
2359 __table_args__ = (
2347 UniqueConstraint('group_name', 'group_parent_id'),
2360 UniqueConstraint('group_name', 'group_parent_id'),
2348 CheckConstraint('group_id != group_parent_id'),
2361 CheckConstraint('group_id != group_parent_id'),
2349 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2362 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2350 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2363 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2351 )
2364 )
2352 __mapper_args__ = {'order_by': 'group_name'}
2365 __mapper_args__ = {'order_by': 'group_name'}
2353
2366
2354 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2367 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2355
2368
2356 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2369 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2357 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2370 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2358 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2371 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2359 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2372 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2360 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2373 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2361 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2374 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2362 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2375 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2363 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2376 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2364 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2377 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2365
2378
2366 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2379 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2367 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2380 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2368 parent_group = relationship('RepoGroup', remote_side=group_id)
2381 parent_group = relationship('RepoGroup', remote_side=group_id)
2369 user = relationship('User')
2382 user = relationship('User')
2370 integrations = relationship('Integration',
2383 integrations = relationship('Integration',
2371 cascade="all, delete, delete-orphan")
2384 cascade="all, delete, delete-orphan")
2372
2385
2373 def __init__(self, group_name='', parent_group=None):
2386 def __init__(self, group_name='', parent_group=None):
2374 self.group_name = group_name
2387 self.group_name = group_name
2375 self.parent_group = parent_group
2388 self.parent_group = parent_group
2376
2389
2377 def __unicode__(self):
2390 def __unicode__(self):
2378 return u"<%s('id:%s:%s')>" % (
2391 return u"<%s('id:%s:%s')>" % (
2379 self.__class__.__name__, self.group_id, self.group_name)
2392 self.__class__.__name__, self.group_id, self.group_name)
2380
2393
2381 @hybrid_property
2394 @hybrid_property
2382 def description_safe(self):
2395 def description_safe(self):
2383 from rhodecode.lib import helpers as h
2396 from rhodecode.lib import helpers as h
2384 return h.escape(self.group_description)
2397 return h.escape(self.group_description)
2385
2398
2386 @classmethod
2399 @classmethod
2387 def _generate_choice(cls, repo_group):
2400 def _generate_choice(cls, repo_group):
2388 from webhelpers.html import literal as _literal
2401 from webhelpers.html import literal as _literal
2389 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2402 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2390 return repo_group.group_id, _name(repo_group.full_path_splitted)
2403 return repo_group.group_id, _name(repo_group.full_path_splitted)
2391
2404
2392 @classmethod
2405 @classmethod
2393 def groups_choices(cls, groups=None, show_empty_group=True):
2406 def groups_choices(cls, groups=None, show_empty_group=True):
2394 if not groups:
2407 if not groups:
2395 groups = cls.query().all()
2408 groups = cls.query().all()
2396
2409
2397 repo_groups = []
2410 repo_groups = []
2398 if show_empty_group:
2411 if show_empty_group:
2399 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2412 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2400
2413
2401 repo_groups.extend([cls._generate_choice(x) for x in groups])
2414 repo_groups.extend([cls._generate_choice(x) for x in groups])
2402
2415
2403 repo_groups = sorted(
2416 repo_groups = sorted(
2404 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2417 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2405 return repo_groups
2418 return repo_groups
2406
2419
2407 @classmethod
2420 @classmethod
2408 def url_sep(cls):
2421 def url_sep(cls):
2409 return URL_SEP
2422 return URL_SEP
2410
2423
2411 @classmethod
2424 @classmethod
2412 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2425 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2413 if case_insensitive:
2426 if case_insensitive:
2414 gr = cls.query().filter(func.lower(cls.group_name)
2427 gr = cls.query().filter(func.lower(cls.group_name)
2415 == func.lower(group_name))
2428 == func.lower(group_name))
2416 else:
2429 else:
2417 gr = cls.query().filter(cls.group_name == group_name)
2430 gr = cls.query().filter(cls.group_name == group_name)
2418 if cache:
2431 if cache:
2419 name_key = _hash_key(group_name)
2432 name_key = _hash_key(group_name)
2420 gr = gr.options(
2433 gr = gr.options(
2421 FromCache("sql_cache_short", "get_group_%s" % name_key))
2434 FromCache("sql_cache_short", "get_group_%s" % name_key))
2422 return gr.scalar()
2435 return gr.scalar()
2423
2436
2424 @classmethod
2437 @classmethod
2425 def get_user_personal_repo_group(cls, user_id):
2438 def get_user_personal_repo_group(cls, user_id):
2426 user = User.get(user_id)
2439 user = User.get(user_id)
2427 if user.username == User.DEFAULT_USER:
2440 if user.username == User.DEFAULT_USER:
2428 return None
2441 return None
2429
2442
2430 return cls.query()\
2443 return cls.query()\
2431 .filter(cls.personal == true()) \
2444 .filter(cls.personal == true()) \
2432 .filter(cls.user == user).scalar()
2445 .filter(cls.user == user).scalar()
2433
2446
2434 @classmethod
2447 @classmethod
2435 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2448 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2436 case_insensitive=True):
2449 case_insensitive=True):
2437 q = RepoGroup.query()
2450 q = RepoGroup.query()
2438
2451
2439 if not isinstance(user_id, Optional):
2452 if not isinstance(user_id, Optional):
2440 q = q.filter(RepoGroup.user_id == user_id)
2453 q = q.filter(RepoGroup.user_id == user_id)
2441
2454
2442 if not isinstance(group_id, Optional):
2455 if not isinstance(group_id, Optional):
2443 q = q.filter(RepoGroup.group_parent_id == group_id)
2456 q = q.filter(RepoGroup.group_parent_id == group_id)
2444
2457
2445 if case_insensitive:
2458 if case_insensitive:
2446 q = q.order_by(func.lower(RepoGroup.group_name))
2459 q = q.order_by(func.lower(RepoGroup.group_name))
2447 else:
2460 else:
2448 q = q.order_by(RepoGroup.group_name)
2461 q = q.order_by(RepoGroup.group_name)
2449 return q.all()
2462 return q.all()
2450
2463
2451 @property
2464 @property
2452 def parents(self):
2465 def parents(self):
2453 parents_recursion_limit = 10
2466 parents_recursion_limit = 10
2454 groups = []
2467 groups = []
2455 if self.parent_group is None:
2468 if self.parent_group is None:
2456 return groups
2469 return groups
2457 cur_gr = self.parent_group
2470 cur_gr = self.parent_group
2458 groups.insert(0, cur_gr)
2471 groups.insert(0, cur_gr)
2459 cnt = 0
2472 cnt = 0
2460 while 1:
2473 while 1:
2461 cnt += 1
2474 cnt += 1
2462 gr = getattr(cur_gr, 'parent_group', None)
2475 gr = getattr(cur_gr, 'parent_group', None)
2463 cur_gr = cur_gr.parent_group
2476 cur_gr = cur_gr.parent_group
2464 if gr is None:
2477 if gr is None:
2465 break
2478 break
2466 if cnt == parents_recursion_limit:
2479 if cnt == parents_recursion_limit:
2467 # this will prevent accidental infinit loops
2480 # this will prevent accidental infinit loops
2468 log.error(('more than %s parents found for group %s, stopping '
2481 log.error(('more than %s parents found for group %s, stopping '
2469 'recursive parent fetching' % (parents_recursion_limit, self)))
2482 'recursive parent fetching' % (parents_recursion_limit, self)))
2470 break
2483 break
2471
2484
2472 groups.insert(0, gr)
2485 groups.insert(0, gr)
2473 return groups
2486 return groups
2474
2487
2475 @property
2488 @property
2476 def last_db_change(self):
2489 def last_db_change(self):
2477 return self.updated_on
2490 return self.updated_on
2478
2491
2479 @property
2492 @property
2480 def children(self):
2493 def children(self):
2481 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2494 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2482
2495
2483 @property
2496 @property
2484 def name(self):
2497 def name(self):
2485 return self.group_name.split(RepoGroup.url_sep())[-1]
2498 return self.group_name.split(RepoGroup.url_sep())[-1]
2486
2499
2487 @property
2500 @property
2488 def full_path(self):
2501 def full_path(self):
2489 return self.group_name
2502 return self.group_name
2490
2503
2491 @property
2504 @property
2492 def full_path_splitted(self):
2505 def full_path_splitted(self):
2493 return self.group_name.split(RepoGroup.url_sep())
2506 return self.group_name.split(RepoGroup.url_sep())
2494
2507
2495 @property
2508 @property
2496 def repositories(self):
2509 def repositories(self):
2497 return Repository.query()\
2510 return Repository.query()\
2498 .filter(Repository.group == self)\
2511 .filter(Repository.group == self)\
2499 .order_by(Repository.repo_name)
2512 .order_by(Repository.repo_name)
2500
2513
2501 @property
2514 @property
2502 def repositories_recursive_count(self):
2515 def repositories_recursive_count(self):
2503 cnt = self.repositories.count()
2516 cnt = self.repositories.count()
2504
2517
2505 def children_count(group):
2518 def children_count(group):
2506 cnt = 0
2519 cnt = 0
2507 for child in group.children:
2520 for child in group.children:
2508 cnt += child.repositories.count()
2521 cnt += child.repositories.count()
2509 cnt += children_count(child)
2522 cnt += children_count(child)
2510 return cnt
2523 return cnt
2511
2524
2512 return cnt + children_count(self)
2525 return cnt + children_count(self)
2513
2526
2514 def _recursive_objects(self, include_repos=True):
2527 def _recursive_objects(self, include_repos=True):
2515 all_ = []
2528 all_ = []
2516
2529
2517 def _get_members(root_gr):
2530 def _get_members(root_gr):
2518 if include_repos:
2531 if include_repos:
2519 for r in root_gr.repositories:
2532 for r in root_gr.repositories:
2520 all_.append(r)
2533 all_.append(r)
2521 childs = root_gr.children.all()
2534 childs = root_gr.children.all()
2522 if childs:
2535 if childs:
2523 for gr in childs:
2536 for gr in childs:
2524 all_.append(gr)
2537 all_.append(gr)
2525 _get_members(gr)
2538 _get_members(gr)
2526
2539
2527 _get_members(self)
2540 _get_members(self)
2528 return [self] + all_
2541 return [self] + all_
2529
2542
2530 def recursive_groups_and_repos(self):
2543 def recursive_groups_and_repos(self):
2531 """
2544 """
2532 Recursive return all groups, with repositories in those groups
2545 Recursive return all groups, with repositories in those groups
2533 """
2546 """
2534 return self._recursive_objects()
2547 return self._recursive_objects()
2535
2548
2536 def recursive_groups(self):
2549 def recursive_groups(self):
2537 """
2550 """
2538 Returns all children groups for this group including children of children
2551 Returns all children groups for this group including children of children
2539 """
2552 """
2540 return self._recursive_objects(include_repos=False)
2553 return self._recursive_objects(include_repos=False)
2541
2554
2542 def get_new_name(self, group_name):
2555 def get_new_name(self, group_name):
2543 """
2556 """
2544 returns new full group name based on parent and new name
2557 returns new full group name based on parent and new name
2545
2558
2546 :param group_name:
2559 :param group_name:
2547 """
2560 """
2548 path_prefix = (self.parent_group.full_path_splitted if
2561 path_prefix = (self.parent_group.full_path_splitted if
2549 self.parent_group else [])
2562 self.parent_group else [])
2550 return RepoGroup.url_sep().join(path_prefix + [group_name])
2563 return RepoGroup.url_sep().join(path_prefix + [group_name])
2551
2564
2552 def permissions(self, with_admins=True, with_owner=True):
2565 def permissions(self, with_admins=True, with_owner=True):
2553 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2566 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2554 q = q.options(joinedload(UserRepoGroupToPerm.group),
2567 q = q.options(joinedload(UserRepoGroupToPerm.group),
2555 joinedload(UserRepoGroupToPerm.user),
2568 joinedload(UserRepoGroupToPerm.user),
2556 joinedload(UserRepoGroupToPerm.permission),)
2569 joinedload(UserRepoGroupToPerm.permission),)
2557
2570
2558 # get owners and admins and permissions. We do a trick of re-writing
2571 # get owners and admins and permissions. We do a trick of re-writing
2559 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2572 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2560 # has a global reference and changing one object propagates to all
2573 # has a global reference and changing one object propagates to all
2561 # others. This means if admin is also an owner admin_row that change
2574 # others. This means if admin is also an owner admin_row that change
2562 # would propagate to both objects
2575 # would propagate to both objects
2563 perm_rows = []
2576 perm_rows = []
2564 for _usr in q.all():
2577 for _usr in q.all():
2565 usr = AttributeDict(_usr.user.get_dict())
2578 usr = AttributeDict(_usr.user.get_dict())
2566 usr.permission = _usr.permission.permission_name
2579 usr.permission = _usr.permission.permission_name
2567 perm_rows.append(usr)
2580 perm_rows.append(usr)
2568
2581
2569 # filter the perm rows by 'default' first and then sort them by
2582 # filter the perm rows by 'default' first and then sort them by
2570 # admin,write,read,none permissions sorted again alphabetically in
2583 # admin,write,read,none permissions sorted again alphabetically in
2571 # each group
2584 # each group
2572 perm_rows = sorted(perm_rows, key=display_user_sort)
2585 perm_rows = sorted(perm_rows, key=display_user_sort)
2573
2586
2574 _admin_perm = 'group.admin'
2587 _admin_perm = 'group.admin'
2575 owner_row = []
2588 owner_row = []
2576 if with_owner:
2589 if with_owner:
2577 usr = AttributeDict(self.user.get_dict())
2590 usr = AttributeDict(self.user.get_dict())
2578 usr.owner_row = True
2591 usr.owner_row = True
2579 usr.permission = _admin_perm
2592 usr.permission = _admin_perm
2580 owner_row.append(usr)
2593 owner_row.append(usr)
2581
2594
2582 super_admin_rows = []
2595 super_admin_rows = []
2583 if with_admins:
2596 if with_admins:
2584 for usr in User.get_all_super_admins():
2597 for usr in User.get_all_super_admins():
2585 # if this admin is also owner, don't double the record
2598 # if this admin is also owner, don't double the record
2586 if usr.user_id == owner_row[0].user_id:
2599 if usr.user_id == owner_row[0].user_id:
2587 owner_row[0].admin_row = True
2600 owner_row[0].admin_row = True
2588 else:
2601 else:
2589 usr = AttributeDict(usr.get_dict())
2602 usr = AttributeDict(usr.get_dict())
2590 usr.admin_row = True
2603 usr.admin_row = True
2591 usr.permission = _admin_perm
2604 usr.permission = _admin_perm
2592 super_admin_rows.append(usr)
2605 super_admin_rows.append(usr)
2593
2606
2594 return super_admin_rows + owner_row + perm_rows
2607 return super_admin_rows + owner_row + perm_rows
2595
2608
2596 def permission_user_groups(self):
2609 def permission_user_groups(self):
2597 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2610 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2598 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2611 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2599 joinedload(UserGroupRepoGroupToPerm.users_group),
2612 joinedload(UserGroupRepoGroupToPerm.users_group),
2600 joinedload(UserGroupRepoGroupToPerm.permission),)
2613 joinedload(UserGroupRepoGroupToPerm.permission),)
2601
2614
2602 perm_rows = []
2615 perm_rows = []
2603 for _user_group in q.all():
2616 for _user_group in q.all():
2604 usr = AttributeDict(_user_group.users_group.get_dict())
2617 usr = AttributeDict(_user_group.users_group.get_dict())
2605 usr.permission = _user_group.permission.permission_name
2618 usr.permission = _user_group.permission.permission_name
2606 perm_rows.append(usr)
2619 perm_rows.append(usr)
2607
2620
2608 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2621 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2609 return perm_rows
2622 return perm_rows
2610
2623
2611 def get_api_data(self):
2624 def get_api_data(self):
2612 """
2625 """
2613 Common function for generating api data
2626 Common function for generating api data
2614
2627
2615 """
2628 """
2616 group = self
2629 group = self
2617 data = {
2630 data = {
2618 'group_id': group.group_id,
2631 'group_id': group.group_id,
2619 'group_name': group.group_name,
2632 'group_name': group.group_name,
2620 'group_description': group.description_safe,
2633 'group_description': group.description_safe,
2621 'parent_group': group.parent_group.group_name if group.parent_group else None,
2634 'parent_group': group.parent_group.group_name if group.parent_group else None,
2622 'repositories': [x.repo_name for x in group.repositories],
2635 'repositories': [x.repo_name for x in group.repositories],
2623 'owner': group.user.username,
2636 'owner': group.user.username,
2624 }
2637 }
2625 return data
2638 return data
2626
2639
2627
2640
2628 class Permission(Base, BaseModel):
2641 class Permission(Base, BaseModel):
2629 __tablename__ = 'permissions'
2642 __tablename__ = 'permissions'
2630 __table_args__ = (
2643 __table_args__ = (
2631 Index('p_perm_name_idx', 'permission_name'),
2644 Index('p_perm_name_idx', 'permission_name'),
2632 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2645 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2633 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2646 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2634 )
2647 )
2635 PERMS = [
2648 PERMS = [
2636 ('hg.admin', _('RhodeCode Super Administrator')),
2649 ('hg.admin', _('RhodeCode Super Administrator')),
2637
2650
2638 ('repository.none', _('Repository no access')),
2651 ('repository.none', _('Repository no access')),
2639 ('repository.read', _('Repository read access')),
2652 ('repository.read', _('Repository read access')),
2640 ('repository.write', _('Repository write access')),
2653 ('repository.write', _('Repository write access')),
2641 ('repository.admin', _('Repository admin access')),
2654 ('repository.admin', _('Repository admin access')),
2642
2655
2643 ('group.none', _('Repository group no access')),
2656 ('group.none', _('Repository group no access')),
2644 ('group.read', _('Repository group read access')),
2657 ('group.read', _('Repository group read access')),
2645 ('group.write', _('Repository group write access')),
2658 ('group.write', _('Repository group write access')),
2646 ('group.admin', _('Repository group admin access')),
2659 ('group.admin', _('Repository group admin access')),
2647
2660
2648 ('usergroup.none', _('User group no access')),
2661 ('usergroup.none', _('User group no access')),
2649 ('usergroup.read', _('User group read access')),
2662 ('usergroup.read', _('User group read access')),
2650 ('usergroup.write', _('User group write access')),
2663 ('usergroup.write', _('User group write access')),
2651 ('usergroup.admin', _('User group admin access')),
2664 ('usergroup.admin', _('User group admin access')),
2652
2665
2653 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2666 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2654 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2667 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2655
2668
2656 ('hg.usergroup.create.false', _('User Group creation disabled')),
2669 ('hg.usergroup.create.false', _('User Group creation disabled')),
2657 ('hg.usergroup.create.true', _('User Group creation enabled')),
2670 ('hg.usergroup.create.true', _('User Group creation enabled')),
2658
2671
2659 ('hg.create.none', _('Repository creation disabled')),
2672 ('hg.create.none', _('Repository creation disabled')),
2660 ('hg.create.repository', _('Repository creation enabled')),
2673 ('hg.create.repository', _('Repository creation enabled')),
2661 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2674 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2662 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2675 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2663
2676
2664 ('hg.fork.none', _('Repository forking disabled')),
2677 ('hg.fork.none', _('Repository forking disabled')),
2665 ('hg.fork.repository', _('Repository forking enabled')),
2678 ('hg.fork.repository', _('Repository forking enabled')),
2666
2679
2667 ('hg.register.none', _('Registration disabled')),
2680 ('hg.register.none', _('Registration disabled')),
2668 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2681 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2669 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2682 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2670
2683
2671 ('hg.password_reset.enabled', _('Password reset enabled')),
2684 ('hg.password_reset.enabled', _('Password reset enabled')),
2672 ('hg.password_reset.hidden', _('Password reset hidden')),
2685 ('hg.password_reset.hidden', _('Password reset hidden')),
2673 ('hg.password_reset.disabled', _('Password reset disabled')),
2686 ('hg.password_reset.disabled', _('Password reset disabled')),
2674
2687
2675 ('hg.extern_activate.manual', _('Manual activation of external account')),
2688 ('hg.extern_activate.manual', _('Manual activation of external account')),
2676 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2689 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2677
2690
2678 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2691 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2679 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2692 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2680 ]
2693 ]
2681
2694
2682 # definition of system default permissions for DEFAULT user
2695 # definition of system default permissions for DEFAULT user
2683 DEFAULT_USER_PERMISSIONS = [
2696 DEFAULT_USER_PERMISSIONS = [
2684 'repository.read',
2697 'repository.read',
2685 'group.read',
2698 'group.read',
2686 'usergroup.read',
2699 'usergroup.read',
2687 'hg.create.repository',
2700 'hg.create.repository',
2688 'hg.repogroup.create.false',
2701 'hg.repogroup.create.false',
2689 'hg.usergroup.create.false',
2702 'hg.usergroup.create.false',
2690 'hg.create.write_on_repogroup.true',
2703 'hg.create.write_on_repogroup.true',
2691 'hg.fork.repository',
2704 'hg.fork.repository',
2692 'hg.register.manual_activate',
2705 'hg.register.manual_activate',
2693 'hg.password_reset.enabled',
2706 'hg.password_reset.enabled',
2694 'hg.extern_activate.auto',
2707 'hg.extern_activate.auto',
2695 'hg.inherit_default_perms.true',
2708 'hg.inherit_default_perms.true',
2696 ]
2709 ]
2697
2710
2698 # defines which permissions are more important higher the more important
2711 # defines which permissions are more important higher the more important
2699 # Weight defines which permissions are more important.
2712 # Weight defines which permissions are more important.
2700 # The higher number the more important.
2713 # The higher number the more important.
2701 PERM_WEIGHTS = {
2714 PERM_WEIGHTS = {
2702 'repository.none': 0,
2715 'repository.none': 0,
2703 'repository.read': 1,
2716 'repository.read': 1,
2704 'repository.write': 3,
2717 'repository.write': 3,
2705 'repository.admin': 4,
2718 'repository.admin': 4,
2706
2719
2707 'group.none': 0,
2720 'group.none': 0,
2708 'group.read': 1,
2721 'group.read': 1,
2709 'group.write': 3,
2722 'group.write': 3,
2710 'group.admin': 4,
2723 'group.admin': 4,
2711
2724
2712 'usergroup.none': 0,
2725 'usergroup.none': 0,
2713 'usergroup.read': 1,
2726 'usergroup.read': 1,
2714 'usergroup.write': 3,
2727 'usergroup.write': 3,
2715 'usergroup.admin': 4,
2728 'usergroup.admin': 4,
2716
2729
2717 'hg.repogroup.create.false': 0,
2730 'hg.repogroup.create.false': 0,
2718 'hg.repogroup.create.true': 1,
2731 'hg.repogroup.create.true': 1,
2719
2732
2720 'hg.usergroup.create.false': 0,
2733 'hg.usergroup.create.false': 0,
2721 'hg.usergroup.create.true': 1,
2734 'hg.usergroup.create.true': 1,
2722
2735
2723 'hg.fork.none': 0,
2736 'hg.fork.none': 0,
2724 'hg.fork.repository': 1,
2737 'hg.fork.repository': 1,
2725 'hg.create.none': 0,
2738 'hg.create.none': 0,
2726 'hg.create.repository': 1
2739 'hg.create.repository': 1
2727 }
2740 }
2728
2741
2729 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2742 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2730 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2743 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2731 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2744 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2732
2745
2733 def __unicode__(self):
2746 def __unicode__(self):
2734 return u"<%s('%s:%s')>" % (
2747 return u"<%s('%s:%s')>" % (
2735 self.__class__.__name__, self.permission_id, self.permission_name
2748 self.__class__.__name__, self.permission_id, self.permission_name
2736 )
2749 )
2737
2750
2738 @classmethod
2751 @classmethod
2739 def get_by_key(cls, key):
2752 def get_by_key(cls, key):
2740 return cls.query().filter(cls.permission_name == key).scalar()
2753 return cls.query().filter(cls.permission_name == key).scalar()
2741
2754
2742 @classmethod
2755 @classmethod
2743 def get_default_repo_perms(cls, user_id, repo_id=None):
2756 def get_default_repo_perms(cls, user_id, repo_id=None):
2744 q = Session().query(UserRepoToPerm, Repository, Permission)\
2757 q = Session().query(UserRepoToPerm, Repository, Permission)\
2745 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2758 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2746 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2759 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2747 .filter(UserRepoToPerm.user_id == user_id)
2760 .filter(UserRepoToPerm.user_id == user_id)
2748 if repo_id:
2761 if repo_id:
2749 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2762 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2750 return q.all()
2763 return q.all()
2751
2764
2752 @classmethod
2765 @classmethod
2753 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2766 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2754 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2767 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2755 .join(
2768 .join(
2756 Permission,
2769 Permission,
2757 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2770 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2758 .join(
2771 .join(
2759 Repository,
2772 Repository,
2760 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2773 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2761 .join(
2774 .join(
2762 UserGroup,
2775 UserGroup,
2763 UserGroupRepoToPerm.users_group_id ==
2776 UserGroupRepoToPerm.users_group_id ==
2764 UserGroup.users_group_id)\
2777 UserGroup.users_group_id)\
2765 .join(
2778 .join(
2766 UserGroupMember,
2779 UserGroupMember,
2767 UserGroupRepoToPerm.users_group_id ==
2780 UserGroupRepoToPerm.users_group_id ==
2768 UserGroupMember.users_group_id)\
2781 UserGroupMember.users_group_id)\
2769 .filter(
2782 .filter(
2770 UserGroupMember.user_id == user_id,
2783 UserGroupMember.user_id == user_id,
2771 UserGroup.users_group_active == true())
2784 UserGroup.users_group_active == true())
2772 if repo_id:
2785 if repo_id:
2773 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2786 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2774 return q.all()
2787 return q.all()
2775
2788
2776 @classmethod
2789 @classmethod
2777 def get_default_group_perms(cls, user_id, repo_group_id=None):
2790 def get_default_group_perms(cls, user_id, repo_group_id=None):
2778 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2791 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2779 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2792 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2780 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2793 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2781 .filter(UserRepoGroupToPerm.user_id == user_id)
2794 .filter(UserRepoGroupToPerm.user_id == user_id)
2782 if repo_group_id:
2795 if repo_group_id:
2783 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2796 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2784 return q.all()
2797 return q.all()
2785
2798
2786 @classmethod
2799 @classmethod
2787 def get_default_group_perms_from_user_group(
2800 def get_default_group_perms_from_user_group(
2788 cls, user_id, repo_group_id=None):
2801 cls, user_id, repo_group_id=None):
2789 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2802 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2790 .join(
2803 .join(
2791 Permission,
2804 Permission,
2792 UserGroupRepoGroupToPerm.permission_id ==
2805 UserGroupRepoGroupToPerm.permission_id ==
2793 Permission.permission_id)\
2806 Permission.permission_id)\
2794 .join(
2807 .join(
2795 RepoGroup,
2808 RepoGroup,
2796 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2809 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2797 .join(
2810 .join(
2798 UserGroup,
2811 UserGroup,
2799 UserGroupRepoGroupToPerm.users_group_id ==
2812 UserGroupRepoGroupToPerm.users_group_id ==
2800 UserGroup.users_group_id)\
2813 UserGroup.users_group_id)\
2801 .join(
2814 .join(
2802 UserGroupMember,
2815 UserGroupMember,
2803 UserGroupRepoGroupToPerm.users_group_id ==
2816 UserGroupRepoGroupToPerm.users_group_id ==
2804 UserGroupMember.users_group_id)\
2817 UserGroupMember.users_group_id)\
2805 .filter(
2818 .filter(
2806 UserGroupMember.user_id == user_id,
2819 UserGroupMember.user_id == user_id,
2807 UserGroup.users_group_active == true())
2820 UserGroup.users_group_active == true())
2808 if repo_group_id:
2821 if repo_group_id:
2809 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2822 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2810 return q.all()
2823 return q.all()
2811
2824
2812 @classmethod
2825 @classmethod
2813 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2826 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2814 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2827 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2815 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2828 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2816 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2829 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2817 .filter(UserUserGroupToPerm.user_id == user_id)
2830 .filter(UserUserGroupToPerm.user_id == user_id)
2818 if user_group_id:
2831 if user_group_id:
2819 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2832 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2820 return q.all()
2833 return q.all()
2821
2834
2822 @classmethod
2835 @classmethod
2823 def get_default_user_group_perms_from_user_group(
2836 def get_default_user_group_perms_from_user_group(
2824 cls, user_id, user_group_id=None):
2837 cls, user_id, user_group_id=None):
2825 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2838 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2826 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2839 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2827 .join(
2840 .join(
2828 Permission,
2841 Permission,
2829 UserGroupUserGroupToPerm.permission_id ==
2842 UserGroupUserGroupToPerm.permission_id ==
2830 Permission.permission_id)\
2843 Permission.permission_id)\
2831 .join(
2844 .join(
2832 TargetUserGroup,
2845 TargetUserGroup,
2833 UserGroupUserGroupToPerm.target_user_group_id ==
2846 UserGroupUserGroupToPerm.target_user_group_id ==
2834 TargetUserGroup.users_group_id)\
2847 TargetUserGroup.users_group_id)\
2835 .join(
2848 .join(
2836 UserGroup,
2849 UserGroup,
2837 UserGroupUserGroupToPerm.user_group_id ==
2850 UserGroupUserGroupToPerm.user_group_id ==
2838 UserGroup.users_group_id)\
2851 UserGroup.users_group_id)\
2839 .join(
2852 .join(
2840 UserGroupMember,
2853 UserGroupMember,
2841 UserGroupUserGroupToPerm.user_group_id ==
2854 UserGroupUserGroupToPerm.user_group_id ==
2842 UserGroupMember.users_group_id)\
2855 UserGroupMember.users_group_id)\
2843 .filter(
2856 .filter(
2844 UserGroupMember.user_id == user_id,
2857 UserGroupMember.user_id == user_id,
2845 UserGroup.users_group_active == true())
2858 UserGroup.users_group_active == true())
2846 if user_group_id:
2859 if user_group_id:
2847 q = q.filter(
2860 q = q.filter(
2848 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2861 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2849
2862
2850 return q.all()
2863 return q.all()
2851
2864
2852
2865
2853 class UserRepoToPerm(Base, BaseModel):
2866 class UserRepoToPerm(Base, BaseModel):
2854 __tablename__ = 'repo_to_perm'
2867 __tablename__ = 'repo_to_perm'
2855 __table_args__ = (
2868 __table_args__ = (
2856 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2869 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2857 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2870 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2858 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2871 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2859 )
2872 )
2860 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2873 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2861 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2874 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2862 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2875 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2863 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2876 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2864
2877
2865 user = relationship('User')
2878 user = relationship('User')
2866 repository = relationship('Repository')
2879 repository = relationship('Repository')
2867 permission = relationship('Permission')
2880 permission = relationship('Permission')
2868
2881
2869 @classmethod
2882 @classmethod
2870 def create(cls, user, repository, permission):
2883 def create(cls, user, repository, permission):
2871 n = cls()
2884 n = cls()
2872 n.user = user
2885 n.user = user
2873 n.repository = repository
2886 n.repository = repository
2874 n.permission = permission
2887 n.permission = permission
2875 Session().add(n)
2888 Session().add(n)
2876 return n
2889 return n
2877
2890
2878 def __unicode__(self):
2891 def __unicode__(self):
2879 return u'<%s => %s >' % (self.user, self.repository)
2892 return u'<%s => %s >' % (self.user, self.repository)
2880
2893
2881
2894
2882 class UserUserGroupToPerm(Base, BaseModel):
2895 class UserUserGroupToPerm(Base, BaseModel):
2883 __tablename__ = 'user_user_group_to_perm'
2896 __tablename__ = 'user_user_group_to_perm'
2884 __table_args__ = (
2897 __table_args__ = (
2885 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2898 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2899 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2887 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2900 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2888 )
2901 )
2889 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2902 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2890 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2903 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2891 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2904 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2892 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2905 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2893
2906
2894 user = relationship('User')
2907 user = relationship('User')
2895 user_group = relationship('UserGroup')
2908 user_group = relationship('UserGroup')
2896 permission = relationship('Permission')
2909 permission = relationship('Permission')
2897
2910
2898 @classmethod
2911 @classmethod
2899 def create(cls, user, user_group, permission):
2912 def create(cls, user, user_group, permission):
2900 n = cls()
2913 n = cls()
2901 n.user = user
2914 n.user = user
2902 n.user_group = user_group
2915 n.user_group = user_group
2903 n.permission = permission
2916 n.permission = permission
2904 Session().add(n)
2917 Session().add(n)
2905 return n
2918 return n
2906
2919
2907 def __unicode__(self):
2920 def __unicode__(self):
2908 return u'<%s => %s >' % (self.user, self.user_group)
2921 return u'<%s => %s >' % (self.user, self.user_group)
2909
2922
2910
2923
2911 class UserToPerm(Base, BaseModel):
2924 class UserToPerm(Base, BaseModel):
2912 __tablename__ = 'user_to_perm'
2925 __tablename__ = 'user_to_perm'
2913 __table_args__ = (
2926 __table_args__ = (
2914 UniqueConstraint('user_id', 'permission_id'),
2927 UniqueConstraint('user_id', 'permission_id'),
2915 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2928 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2916 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2929 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2917 )
2930 )
2918 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2931 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2919 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2932 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2920 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2933 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2921
2934
2922 user = relationship('User')
2935 user = relationship('User')
2923 permission = relationship('Permission', lazy='joined')
2936 permission = relationship('Permission', lazy='joined')
2924
2937
2925 def __unicode__(self):
2938 def __unicode__(self):
2926 return u'<%s => %s >' % (self.user, self.permission)
2939 return u'<%s => %s >' % (self.user, self.permission)
2927
2940
2928
2941
2929 class UserGroupRepoToPerm(Base, BaseModel):
2942 class UserGroupRepoToPerm(Base, BaseModel):
2930 __tablename__ = 'users_group_repo_to_perm'
2943 __tablename__ = 'users_group_repo_to_perm'
2931 __table_args__ = (
2944 __table_args__ = (
2932 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2945 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2933 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2946 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2934 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2947 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2935 )
2948 )
2936 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2949 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2937 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2950 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2938 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2951 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2939 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2952 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2940
2953
2941 users_group = relationship('UserGroup')
2954 users_group = relationship('UserGroup')
2942 permission = relationship('Permission')
2955 permission = relationship('Permission')
2943 repository = relationship('Repository')
2956 repository = relationship('Repository')
2944
2957
2945 @classmethod
2958 @classmethod
2946 def create(cls, users_group, repository, permission):
2959 def create(cls, users_group, repository, permission):
2947 n = cls()
2960 n = cls()
2948 n.users_group = users_group
2961 n.users_group = users_group
2949 n.repository = repository
2962 n.repository = repository
2950 n.permission = permission
2963 n.permission = permission
2951 Session().add(n)
2964 Session().add(n)
2952 return n
2965 return n
2953
2966
2954 def __unicode__(self):
2967 def __unicode__(self):
2955 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2968 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2956
2969
2957
2970
2958 class UserGroupUserGroupToPerm(Base, BaseModel):
2971 class UserGroupUserGroupToPerm(Base, BaseModel):
2959 __tablename__ = 'user_group_user_group_to_perm'
2972 __tablename__ = 'user_group_user_group_to_perm'
2960 __table_args__ = (
2973 __table_args__ = (
2961 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2974 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2962 CheckConstraint('target_user_group_id != user_group_id'),
2975 CheckConstraint('target_user_group_id != user_group_id'),
2963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2976 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2977 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2965 )
2978 )
2966 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)
2979 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)
2967 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2980 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2968 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2981 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2969 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2982 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2970
2983
2971 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2984 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2972 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2985 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2973 permission = relationship('Permission')
2986 permission = relationship('Permission')
2974
2987
2975 @classmethod
2988 @classmethod
2976 def create(cls, target_user_group, user_group, permission):
2989 def create(cls, target_user_group, user_group, permission):
2977 n = cls()
2990 n = cls()
2978 n.target_user_group = target_user_group
2991 n.target_user_group = target_user_group
2979 n.user_group = user_group
2992 n.user_group = user_group
2980 n.permission = permission
2993 n.permission = permission
2981 Session().add(n)
2994 Session().add(n)
2982 return n
2995 return n
2983
2996
2984 def __unicode__(self):
2997 def __unicode__(self):
2985 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2998 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2986
2999
2987
3000
2988 class UserGroupToPerm(Base, BaseModel):
3001 class UserGroupToPerm(Base, BaseModel):
2989 __tablename__ = 'users_group_to_perm'
3002 __tablename__ = 'users_group_to_perm'
2990 __table_args__ = (
3003 __table_args__ = (
2991 UniqueConstraint('users_group_id', 'permission_id',),
3004 UniqueConstraint('users_group_id', 'permission_id',),
2992 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3005 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2993 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3006 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2994 )
3007 )
2995 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3008 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2996 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3009 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2997 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3010 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2998
3011
2999 users_group = relationship('UserGroup')
3012 users_group = relationship('UserGroup')
3000 permission = relationship('Permission')
3013 permission = relationship('Permission')
3001
3014
3002
3015
3003 class UserRepoGroupToPerm(Base, BaseModel):
3016 class UserRepoGroupToPerm(Base, BaseModel):
3004 __tablename__ = 'user_repo_group_to_perm'
3017 __tablename__ = 'user_repo_group_to_perm'
3005 __table_args__ = (
3018 __table_args__ = (
3006 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3019 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3007 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3020 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3008 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3021 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3009 )
3022 )
3010
3023
3011 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3024 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3012 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3025 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3013 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3026 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3014 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3027 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3015
3028
3016 user = relationship('User')
3029 user = relationship('User')
3017 group = relationship('RepoGroup')
3030 group = relationship('RepoGroup')
3018 permission = relationship('Permission')
3031 permission = relationship('Permission')
3019
3032
3020 @classmethod
3033 @classmethod
3021 def create(cls, user, repository_group, permission):
3034 def create(cls, user, repository_group, permission):
3022 n = cls()
3035 n = cls()
3023 n.user = user
3036 n.user = user
3024 n.group = repository_group
3037 n.group = repository_group
3025 n.permission = permission
3038 n.permission = permission
3026 Session().add(n)
3039 Session().add(n)
3027 return n
3040 return n
3028
3041
3029
3042
3030 class UserGroupRepoGroupToPerm(Base, BaseModel):
3043 class UserGroupRepoGroupToPerm(Base, BaseModel):
3031 __tablename__ = 'users_group_repo_group_to_perm'
3044 __tablename__ = 'users_group_repo_group_to_perm'
3032 __table_args__ = (
3045 __table_args__ = (
3033 UniqueConstraint('users_group_id', 'group_id'),
3046 UniqueConstraint('users_group_id', 'group_id'),
3034 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3047 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3035 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3048 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3036 )
3049 )
3037
3050
3038 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)
3051 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)
3039 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3052 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3040 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3053 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3041 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3054 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3042
3055
3043 users_group = relationship('UserGroup')
3056 users_group = relationship('UserGroup')
3044 permission = relationship('Permission')
3057 permission = relationship('Permission')
3045 group = relationship('RepoGroup')
3058 group = relationship('RepoGroup')
3046
3059
3047 @classmethod
3060 @classmethod
3048 def create(cls, user_group, repository_group, permission):
3061 def create(cls, user_group, repository_group, permission):
3049 n = cls()
3062 n = cls()
3050 n.users_group = user_group
3063 n.users_group = user_group
3051 n.group = repository_group
3064 n.group = repository_group
3052 n.permission = permission
3065 n.permission = permission
3053 Session().add(n)
3066 Session().add(n)
3054 return n
3067 return n
3055
3068
3056 def __unicode__(self):
3069 def __unicode__(self):
3057 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3070 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3058
3071
3059
3072
3060 class Statistics(Base, BaseModel):
3073 class Statistics(Base, BaseModel):
3061 __tablename__ = 'statistics'
3074 __tablename__ = 'statistics'
3062 __table_args__ = (
3075 __table_args__ = (
3063 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3076 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3064 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3077 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3065 )
3078 )
3066 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3079 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3067 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3080 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3068 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3081 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3069 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3082 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3070 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3083 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3071 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3084 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3072
3085
3073 repository = relationship('Repository', single_parent=True)
3086 repository = relationship('Repository', single_parent=True)
3074
3087
3075
3088
3076 class UserFollowing(Base, BaseModel):
3089 class UserFollowing(Base, BaseModel):
3077 __tablename__ = 'user_followings'
3090 __tablename__ = 'user_followings'
3078 __table_args__ = (
3091 __table_args__ = (
3079 UniqueConstraint('user_id', 'follows_repository_id'),
3092 UniqueConstraint('user_id', 'follows_repository_id'),
3080 UniqueConstraint('user_id', 'follows_user_id'),
3093 UniqueConstraint('user_id', 'follows_user_id'),
3081 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3094 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3082 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3095 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3083 )
3096 )
3084
3097
3085 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3098 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3086 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3099 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3087 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3100 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3088 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3101 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3089 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3102 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3090
3103
3091 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3104 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3092
3105
3093 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3106 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3094 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3107 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3095
3108
3096 @classmethod
3109 @classmethod
3097 def get_repo_followers(cls, repo_id):
3110 def get_repo_followers(cls, repo_id):
3098 return cls.query().filter(cls.follows_repo_id == repo_id)
3111 return cls.query().filter(cls.follows_repo_id == repo_id)
3099
3112
3100
3113
3101 class CacheKey(Base, BaseModel):
3114 class CacheKey(Base, BaseModel):
3102 __tablename__ = 'cache_invalidation'
3115 __tablename__ = 'cache_invalidation'
3103 __table_args__ = (
3116 __table_args__ = (
3104 UniqueConstraint('cache_key'),
3117 UniqueConstraint('cache_key'),
3105 Index('key_idx', 'cache_key'),
3118 Index('key_idx', 'cache_key'),
3106 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3119 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3107 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3120 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3108 )
3121 )
3109 CACHE_TYPE_ATOM = 'ATOM'
3122 CACHE_TYPE_ATOM = 'ATOM'
3110 CACHE_TYPE_RSS = 'RSS'
3123 CACHE_TYPE_RSS = 'RSS'
3111 CACHE_TYPE_README = 'README'
3124 CACHE_TYPE_README = 'README'
3112
3125
3113 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3126 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3114 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3127 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3115 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3128 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3116 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3129 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3117
3130
3118 def __init__(self, cache_key, cache_args=''):
3131 def __init__(self, cache_key, cache_args=''):
3119 self.cache_key = cache_key
3132 self.cache_key = cache_key
3120 self.cache_args = cache_args
3133 self.cache_args = cache_args
3121 self.cache_active = False
3134 self.cache_active = False
3122
3135
3123 def __unicode__(self):
3136 def __unicode__(self):
3124 return u"<%s('%s:%s[%s]')>" % (
3137 return u"<%s('%s:%s[%s]')>" % (
3125 self.__class__.__name__,
3138 self.__class__.__name__,
3126 self.cache_id, self.cache_key, self.cache_active)
3139 self.cache_id, self.cache_key, self.cache_active)
3127
3140
3128 def _cache_key_partition(self):
3141 def _cache_key_partition(self):
3129 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3142 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3130 return prefix, repo_name, suffix
3143 return prefix, repo_name, suffix
3131
3144
3132 def get_prefix(self):
3145 def get_prefix(self):
3133 """
3146 """
3134 Try to extract prefix from existing cache key. The key could consist
3147 Try to extract prefix from existing cache key. The key could consist
3135 of prefix, repo_name, suffix
3148 of prefix, repo_name, suffix
3136 """
3149 """
3137 # this returns prefix, repo_name, suffix
3150 # this returns prefix, repo_name, suffix
3138 return self._cache_key_partition()[0]
3151 return self._cache_key_partition()[0]
3139
3152
3140 def get_suffix(self):
3153 def get_suffix(self):
3141 """
3154 """
3142 get suffix that might have been used in _get_cache_key to
3155 get suffix that might have been used in _get_cache_key to
3143 generate self.cache_key. Only used for informational purposes
3156 generate self.cache_key. Only used for informational purposes
3144 in repo_edit.mako.
3157 in repo_edit.mako.
3145 """
3158 """
3146 # prefix, repo_name, suffix
3159 # prefix, repo_name, suffix
3147 return self._cache_key_partition()[2]
3160 return self._cache_key_partition()[2]
3148
3161
3149 @classmethod
3162 @classmethod
3150 def delete_all_cache(cls):
3163 def delete_all_cache(cls):
3151 """
3164 """
3152 Delete all cache keys from database.
3165 Delete all cache keys from database.
3153 Should only be run when all instances are down and all entries
3166 Should only be run when all instances are down and all entries
3154 thus stale.
3167 thus stale.
3155 """
3168 """
3156 cls.query().delete()
3169 cls.query().delete()
3157 Session().commit()
3170 Session().commit()
3158
3171
3159 @classmethod
3172 @classmethod
3160 def get_cache_key(cls, repo_name, cache_type):
3173 def get_cache_key(cls, repo_name, cache_type):
3161 """
3174 """
3162
3175
3163 Generate a cache key for this process of RhodeCode instance.
3176 Generate a cache key for this process of RhodeCode instance.
3164 Prefix most likely will be process id or maybe explicitly set
3177 Prefix most likely will be process id or maybe explicitly set
3165 instance_id from .ini file.
3178 instance_id from .ini file.
3166 """
3179 """
3167 import rhodecode
3180 import rhodecode
3168 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3181 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3169
3182
3170 repo_as_unicode = safe_unicode(repo_name)
3183 repo_as_unicode = safe_unicode(repo_name)
3171 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3184 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3172 if cache_type else repo_as_unicode
3185 if cache_type else repo_as_unicode
3173
3186
3174 return u'{}{}'.format(prefix, key)
3187 return u'{}{}'.format(prefix, key)
3175
3188
3176 @classmethod
3189 @classmethod
3177 def set_invalidate(cls, repo_name, delete=False):
3190 def set_invalidate(cls, repo_name, delete=False):
3178 """
3191 """
3179 Mark all caches of a repo as invalid in the database.
3192 Mark all caches of a repo as invalid in the database.
3180 """
3193 """
3181
3194
3182 try:
3195 try:
3183 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3196 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3184 if delete:
3197 if delete:
3185 log.debug('cache objects deleted for repo %s',
3198 log.debug('cache objects deleted for repo %s',
3186 safe_str(repo_name))
3199 safe_str(repo_name))
3187 qry.delete()
3200 qry.delete()
3188 else:
3201 else:
3189 log.debug('cache objects marked as invalid for repo %s',
3202 log.debug('cache objects marked as invalid for repo %s',
3190 safe_str(repo_name))
3203 safe_str(repo_name))
3191 qry.update({"cache_active": False})
3204 qry.update({"cache_active": False})
3192
3205
3193 Session().commit()
3206 Session().commit()
3194 except Exception:
3207 except Exception:
3195 log.exception(
3208 log.exception(
3196 'Cache key invalidation failed for repository %s',
3209 'Cache key invalidation failed for repository %s',
3197 safe_str(repo_name))
3210 safe_str(repo_name))
3198 Session().rollback()
3211 Session().rollback()
3199
3212
3200 @classmethod
3213 @classmethod
3201 def get_active_cache(cls, cache_key):
3214 def get_active_cache(cls, cache_key):
3202 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3215 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3203 if inv_obj:
3216 if inv_obj:
3204 return inv_obj
3217 return inv_obj
3205 return None
3218 return None
3206
3219
3207 @classmethod
3220 @classmethod
3208 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3221 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3209 thread_scoped=False):
3222 thread_scoped=False):
3210 """
3223 """
3211 @cache_region('long_term')
3224 @cache_region('long_term')
3212 def _heavy_calculation(cache_key):
3225 def _heavy_calculation(cache_key):
3213 return 'result'
3226 return 'result'
3214
3227
3215 cache_context = CacheKey.repo_context_cache(
3228 cache_context = CacheKey.repo_context_cache(
3216 _heavy_calculation, repo_name, cache_type)
3229 _heavy_calculation, repo_name, cache_type)
3217
3230
3218 with cache_context as context:
3231 with cache_context as context:
3219 context.invalidate()
3232 context.invalidate()
3220 computed = context.compute()
3233 computed = context.compute()
3221
3234
3222 assert computed == 'result'
3235 assert computed == 'result'
3223 """
3236 """
3224 from rhodecode.lib import caches
3237 from rhodecode.lib import caches
3225 return caches.InvalidationContext(
3238 return caches.InvalidationContext(
3226 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3239 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3227
3240
3228
3241
3229 class ChangesetComment(Base, BaseModel):
3242 class ChangesetComment(Base, BaseModel):
3230 __tablename__ = 'changeset_comments'
3243 __tablename__ = 'changeset_comments'
3231 __table_args__ = (
3244 __table_args__ = (
3232 Index('cc_revision_idx', 'revision'),
3245 Index('cc_revision_idx', 'revision'),
3233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3246 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3234 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3247 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3235 )
3248 )
3236
3249
3237 COMMENT_OUTDATED = u'comment_outdated'
3250 COMMENT_OUTDATED = u'comment_outdated'
3238 COMMENT_TYPE_NOTE = u'note'
3251 COMMENT_TYPE_NOTE = u'note'
3239 COMMENT_TYPE_TODO = u'todo'
3252 COMMENT_TYPE_TODO = u'todo'
3240 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3253 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3241
3254
3242 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3255 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3243 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3256 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3244 revision = Column('revision', String(40), nullable=True)
3257 revision = Column('revision', String(40), nullable=True)
3245 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3258 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3246 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3259 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3247 line_no = Column('line_no', Unicode(10), nullable=True)
3260 line_no = Column('line_no', Unicode(10), nullable=True)
3248 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3261 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3249 f_path = Column('f_path', Unicode(1000), nullable=True)
3262 f_path = Column('f_path', Unicode(1000), nullable=True)
3250 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3263 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3251 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3264 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3252 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3265 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3253 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3266 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3254 renderer = Column('renderer', Unicode(64), nullable=True)
3267 renderer = Column('renderer', Unicode(64), nullable=True)
3255 display_state = Column('display_state', Unicode(128), nullable=True)
3268 display_state = Column('display_state', Unicode(128), nullable=True)
3256
3269
3257 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3270 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3258 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3271 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3259 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3272 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3260 author = relationship('User', lazy='joined')
3273 author = relationship('User', lazy='joined')
3261 repo = relationship('Repository')
3274 repo = relationship('Repository')
3262 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3275 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3263 pull_request = relationship('PullRequest', lazy='joined')
3276 pull_request = relationship('PullRequest', lazy='joined')
3264 pull_request_version = relationship('PullRequestVersion')
3277 pull_request_version = relationship('PullRequestVersion')
3265
3278
3266 @classmethod
3279 @classmethod
3267 def get_users(cls, revision=None, pull_request_id=None):
3280 def get_users(cls, revision=None, pull_request_id=None):
3268 """
3281 """
3269 Returns user associated with this ChangesetComment. ie those
3282 Returns user associated with this ChangesetComment. ie those
3270 who actually commented
3283 who actually commented
3271
3284
3272 :param cls:
3285 :param cls:
3273 :param revision:
3286 :param revision:
3274 """
3287 """
3275 q = Session().query(User)\
3288 q = Session().query(User)\
3276 .join(ChangesetComment.author)
3289 .join(ChangesetComment.author)
3277 if revision:
3290 if revision:
3278 q = q.filter(cls.revision == revision)
3291 q = q.filter(cls.revision == revision)
3279 elif pull_request_id:
3292 elif pull_request_id:
3280 q = q.filter(cls.pull_request_id == pull_request_id)
3293 q = q.filter(cls.pull_request_id == pull_request_id)
3281 return q.all()
3294 return q.all()
3282
3295
3283 @classmethod
3296 @classmethod
3284 def get_index_from_version(cls, pr_version, versions):
3297 def get_index_from_version(cls, pr_version, versions):
3285 num_versions = [x.pull_request_version_id for x in versions]
3298 num_versions = [x.pull_request_version_id for x in versions]
3286 try:
3299 try:
3287 return num_versions.index(pr_version) +1
3300 return num_versions.index(pr_version) +1
3288 except (IndexError, ValueError):
3301 except (IndexError, ValueError):
3289 return
3302 return
3290
3303
3291 @property
3304 @property
3292 def outdated(self):
3305 def outdated(self):
3293 return self.display_state == self.COMMENT_OUTDATED
3306 return self.display_state == self.COMMENT_OUTDATED
3294
3307
3295 def outdated_at_version(self, version):
3308 def outdated_at_version(self, version):
3296 """
3309 """
3297 Checks if comment is outdated for given pull request version
3310 Checks if comment is outdated for given pull request version
3298 """
3311 """
3299 return self.outdated and self.pull_request_version_id != version
3312 return self.outdated and self.pull_request_version_id != version
3300
3313
3301 def older_than_version(self, version):
3314 def older_than_version(self, version):
3302 """
3315 """
3303 Checks if comment is made from previous version than given
3316 Checks if comment is made from previous version than given
3304 """
3317 """
3305 if version is None:
3318 if version is None:
3306 return self.pull_request_version_id is not None
3319 return self.pull_request_version_id is not None
3307
3320
3308 return self.pull_request_version_id < version
3321 return self.pull_request_version_id < version
3309
3322
3310 @property
3323 @property
3311 def resolved(self):
3324 def resolved(self):
3312 return self.resolved_by[0] if self.resolved_by else None
3325 return self.resolved_by[0] if self.resolved_by else None
3313
3326
3314 @property
3327 @property
3315 def is_todo(self):
3328 def is_todo(self):
3316 return self.comment_type == self.COMMENT_TYPE_TODO
3329 return self.comment_type == self.COMMENT_TYPE_TODO
3317
3330
3318 @property
3331 @property
3319 def is_inline(self):
3332 def is_inline(self):
3320 return self.line_no and self.f_path
3333 return self.line_no and self.f_path
3321
3334
3322 def get_index_version(self, versions):
3335 def get_index_version(self, versions):
3323 return self.get_index_from_version(
3336 return self.get_index_from_version(
3324 self.pull_request_version_id, versions)
3337 self.pull_request_version_id, versions)
3325
3338
3326 def __repr__(self):
3339 def __repr__(self):
3327 if self.comment_id:
3340 if self.comment_id:
3328 return '<DB:Comment #%s>' % self.comment_id
3341 return '<DB:Comment #%s>' % self.comment_id
3329 else:
3342 else:
3330 return '<DB:Comment at %#x>' % id(self)
3343 return '<DB:Comment at %#x>' % id(self)
3331
3344
3332 def get_api_data(self):
3345 def get_api_data(self):
3333 comment = self
3346 comment = self
3334 data = {
3347 data = {
3335 'comment_id': comment.comment_id,
3348 'comment_id': comment.comment_id,
3336 'comment_type': comment.comment_type,
3349 'comment_type': comment.comment_type,
3337 'comment_text': comment.text,
3350 'comment_text': comment.text,
3338 'comment_status': comment.status_change,
3351 'comment_status': comment.status_change,
3339 'comment_f_path': comment.f_path,
3352 'comment_f_path': comment.f_path,
3340 'comment_lineno': comment.line_no,
3353 'comment_lineno': comment.line_no,
3341 'comment_author': comment.author,
3354 'comment_author': comment.author,
3342 'comment_created_on': comment.created_on
3355 'comment_created_on': comment.created_on
3343 }
3356 }
3344 return data
3357 return data
3345
3358
3346 def __json__(self):
3359 def __json__(self):
3347 data = dict()
3360 data = dict()
3348 data.update(self.get_api_data())
3361 data.update(self.get_api_data())
3349 return data
3362 return data
3350
3363
3351
3364
3352 class ChangesetStatus(Base, BaseModel):
3365 class ChangesetStatus(Base, BaseModel):
3353 __tablename__ = 'changeset_statuses'
3366 __tablename__ = 'changeset_statuses'
3354 __table_args__ = (
3367 __table_args__ = (
3355 Index('cs_revision_idx', 'revision'),
3368 Index('cs_revision_idx', 'revision'),
3356 Index('cs_version_idx', 'version'),
3369 Index('cs_version_idx', 'version'),
3357 UniqueConstraint('repo_id', 'revision', 'version'),
3370 UniqueConstraint('repo_id', 'revision', 'version'),
3358 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3371 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3359 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3372 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3360 )
3373 )
3361 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3374 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3362 STATUS_APPROVED = 'approved'
3375 STATUS_APPROVED = 'approved'
3363 STATUS_REJECTED = 'rejected'
3376 STATUS_REJECTED = 'rejected'
3364 STATUS_UNDER_REVIEW = 'under_review'
3377 STATUS_UNDER_REVIEW = 'under_review'
3365
3378
3366 STATUSES = [
3379 STATUSES = [
3367 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3380 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3368 (STATUS_APPROVED, _("Approved")),
3381 (STATUS_APPROVED, _("Approved")),
3369 (STATUS_REJECTED, _("Rejected")),
3382 (STATUS_REJECTED, _("Rejected")),
3370 (STATUS_UNDER_REVIEW, _("Under Review")),
3383 (STATUS_UNDER_REVIEW, _("Under Review")),
3371 ]
3384 ]
3372
3385
3373 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3386 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3374 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3387 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3375 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3388 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3376 revision = Column('revision', String(40), nullable=False)
3389 revision = Column('revision', String(40), nullable=False)
3377 status = Column('status', String(128), nullable=False, default=DEFAULT)
3390 status = Column('status', String(128), nullable=False, default=DEFAULT)
3378 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3391 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3379 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3392 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3380 version = Column('version', Integer(), nullable=False, default=0)
3393 version = Column('version', Integer(), nullable=False, default=0)
3381 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3394 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3382
3395
3383 author = relationship('User', lazy='joined')
3396 author = relationship('User', lazy='joined')
3384 repo = relationship('Repository')
3397 repo = relationship('Repository')
3385 comment = relationship('ChangesetComment', lazy='joined')
3398 comment = relationship('ChangesetComment', lazy='joined')
3386 pull_request = relationship('PullRequest', lazy='joined')
3399 pull_request = relationship('PullRequest', lazy='joined')
3387
3400
3388 def __unicode__(self):
3401 def __unicode__(self):
3389 return u"<%s('%s[v%s]:%s')>" % (
3402 return u"<%s('%s[v%s]:%s')>" % (
3390 self.__class__.__name__,
3403 self.__class__.__name__,
3391 self.status, self.version, self.author
3404 self.status, self.version, self.author
3392 )
3405 )
3393
3406
3394 @classmethod
3407 @classmethod
3395 def get_status_lbl(cls, value):
3408 def get_status_lbl(cls, value):
3396 return dict(cls.STATUSES).get(value)
3409 return dict(cls.STATUSES).get(value)
3397
3410
3398 @property
3411 @property
3399 def status_lbl(self):
3412 def status_lbl(self):
3400 return ChangesetStatus.get_status_lbl(self.status)
3413 return ChangesetStatus.get_status_lbl(self.status)
3401
3414
3402 def get_api_data(self):
3415 def get_api_data(self):
3403 status = self
3416 status = self
3404 data = {
3417 data = {
3405 'status_id': status.changeset_status_id,
3418 'status_id': status.changeset_status_id,
3406 'status': status.status,
3419 'status': status.status,
3407 }
3420 }
3408 return data
3421 return data
3409
3422
3410 def __json__(self):
3423 def __json__(self):
3411 data = dict()
3424 data = dict()
3412 data.update(self.get_api_data())
3425 data.update(self.get_api_data())
3413 return data
3426 return data
3414
3427
3415
3428
3416 class _PullRequestBase(BaseModel):
3429 class _PullRequestBase(BaseModel):
3417 """
3430 """
3418 Common attributes of pull request and version entries.
3431 Common attributes of pull request and version entries.
3419 """
3432 """
3420
3433
3421 # .status values
3434 # .status values
3422 STATUS_NEW = u'new'
3435 STATUS_NEW = u'new'
3423 STATUS_OPEN = u'open'
3436 STATUS_OPEN = u'open'
3424 STATUS_CLOSED = u'closed'
3437 STATUS_CLOSED = u'closed'
3425
3438
3426 title = Column('title', Unicode(255), nullable=True)
3439 title = Column('title', Unicode(255), nullable=True)
3427 description = Column(
3440 description = Column(
3428 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3441 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3429 nullable=True)
3442 nullable=True)
3430 # new/open/closed status of pull request (not approve/reject/etc)
3443 # new/open/closed status of pull request (not approve/reject/etc)
3431 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3444 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3432 created_on = Column(
3445 created_on = Column(
3433 'created_on', DateTime(timezone=False), nullable=False,
3446 'created_on', DateTime(timezone=False), nullable=False,
3434 default=datetime.datetime.now)
3447 default=datetime.datetime.now)
3435 updated_on = Column(
3448 updated_on = Column(
3436 'updated_on', DateTime(timezone=False), nullable=False,
3449 'updated_on', DateTime(timezone=False), nullable=False,
3437 default=datetime.datetime.now)
3450 default=datetime.datetime.now)
3438
3451
3439 @declared_attr
3452 @declared_attr
3440 def user_id(cls):
3453 def user_id(cls):
3441 return Column(
3454 return Column(
3442 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3455 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3443 unique=None)
3456 unique=None)
3444
3457
3445 # 500 revisions max
3458 # 500 revisions max
3446 _revisions = Column(
3459 _revisions = Column(
3447 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3460 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3448
3461
3449 @declared_attr
3462 @declared_attr
3450 def source_repo_id(cls):
3463 def source_repo_id(cls):
3451 # TODO: dan: rename column to source_repo_id
3464 # TODO: dan: rename column to source_repo_id
3452 return Column(
3465 return Column(
3453 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3466 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3454 nullable=False)
3467 nullable=False)
3455
3468
3456 source_ref = Column('org_ref', Unicode(255), nullable=False)
3469 source_ref = Column('org_ref', Unicode(255), nullable=False)
3457
3470
3458 @declared_attr
3471 @declared_attr
3459 def target_repo_id(cls):
3472 def target_repo_id(cls):
3460 # TODO: dan: rename column to target_repo_id
3473 # TODO: dan: rename column to target_repo_id
3461 return Column(
3474 return Column(
3462 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3475 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3463 nullable=False)
3476 nullable=False)
3464
3477
3465 target_ref = Column('other_ref', Unicode(255), nullable=False)
3478 target_ref = Column('other_ref', Unicode(255), nullable=False)
3466 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3479 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3467
3480
3468 # TODO: dan: rename column to last_merge_source_rev
3481 # TODO: dan: rename column to last_merge_source_rev
3469 _last_merge_source_rev = Column(
3482 _last_merge_source_rev = Column(
3470 'last_merge_org_rev', String(40), nullable=True)
3483 'last_merge_org_rev', String(40), nullable=True)
3471 # TODO: dan: rename column to last_merge_target_rev
3484 # TODO: dan: rename column to last_merge_target_rev
3472 _last_merge_target_rev = Column(
3485 _last_merge_target_rev = Column(
3473 'last_merge_other_rev', String(40), nullable=True)
3486 'last_merge_other_rev', String(40), nullable=True)
3474 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3487 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3475 merge_rev = Column('merge_rev', String(40), nullable=True)
3488 merge_rev = Column('merge_rev', String(40), nullable=True)
3476
3489
3477 reviewer_data = Column(
3490 reviewer_data = Column(
3478 'reviewer_data_json', MutationObj.as_mutable(
3491 'reviewer_data_json', MutationObj.as_mutable(
3479 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3492 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3480
3493
3481 @property
3494 @property
3482 def reviewer_data_json(self):
3495 def reviewer_data_json(self):
3483 return json.dumps(self.reviewer_data)
3496 return json.dumps(self.reviewer_data)
3484
3497
3485 @hybrid_property
3498 @hybrid_property
3486 def description_safe(self):
3499 def description_safe(self):
3487 from rhodecode.lib import helpers as h
3500 from rhodecode.lib import helpers as h
3488 return h.escape(self.description)
3501 return h.escape(self.description)
3489
3502
3490 @hybrid_property
3503 @hybrid_property
3491 def revisions(self):
3504 def revisions(self):
3492 return self._revisions.split(':') if self._revisions else []
3505 return self._revisions.split(':') if self._revisions else []
3493
3506
3494 @revisions.setter
3507 @revisions.setter
3495 def revisions(self, val):
3508 def revisions(self, val):
3496 self._revisions = ':'.join(val)
3509 self._revisions = ':'.join(val)
3497
3510
3498 @hybrid_property
3511 @hybrid_property
3499 def last_merge_status(self):
3512 def last_merge_status(self):
3500 return safe_int(self._last_merge_status)
3513 return safe_int(self._last_merge_status)
3501
3514
3502 @last_merge_status.setter
3515 @last_merge_status.setter
3503 def last_merge_status(self, val):
3516 def last_merge_status(self, val):
3504 self._last_merge_status = val
3517 self._last_merge_status = val
3505
3518
3506 @declared_attr
3519 @declared_attr
3507 def author(cls):
3520 def author(cls):
3508 return relationship('User', lazy='joined')
3521 return relationship('User', lazy='joined')
3509
3522
3510 @declared_attr
3523 @declared_attr
3511 def source_repo(cls):
3524 def source_repo(cls):
3512 return relationship(
3525 return relationship(
3513 'Repository',
3526 'Repository',
3514 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3527 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3515
3528
3516 @property
3529 @property
3517 def source_ref_parts(self):
3530 def source_ref_parts(self):
3518 return self.unicode_to_reference(self.source_ref)
3531 return self.unicode_to_reference(self.source_ref)
3519
3532
3520 @declared_attr
3533 @declared_attr
3521 def target_repo(cls):
3534 def target_repo(cls):
3522 return relationship(
3535 return relationship(
3523 'Repository',
3536 'Repository',
3524 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3537 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3525
3538
3526 @property
3539 @property
3527 def target_ref_parts(self):
3540 def target_ref_parts(self):
3528 return self.unicode_to_reference(self.target_ref)
3541 return self.unicode_to_reference(self.target_ref)
3529
3542
3530 @property
3543 @property
3531 def shadow_merge_ref(self):
3544 def shadow_merge_ref(self):
3532 return self.unicode_to_reference(self._shadow_merge_ref)
3545 return self.unicode_to_reference(self._shadow_merge_ref)
3533
3546
3534 @shadow_merge_ref.setter
3547 @shadow_merge_ref.setter
3535 def shadow_merge_ref(self, ref):
3548 def shadow_merge_ref(self, ref):
3536 self._shadow_merge_ref = self.reference_to_unicode(ref)
3549 self._shadow_merge_ref = self.reference_to_unicode(ref)
3537
3550
3538 def unicode_to_reference(self, raw):
3551 def unicode_to_reference(self, raw):
3539 """
3552 """
3540 Convert a unicode (or string) to a reference object.
3553 Convert a unicode (or string) to a reference object.
3541 If unicode evaluates to False it returns None.
3554 If unicode evaluates to False it returns None.
3542 """
3555 """
3543 if raw:
3556 if raw:
3544 refs = raw.split(':')
3557 refs = raw.split(':')
3545 return Reference(*refs)
3558 return Reference(*refs)
3546 else:
3559 else:
3547 return None
3560 return None
3548
3561
3549 def reference_to_unicode(self, ref):
3562 def reference_to_unicode(self, ref):
3550 """
3563 """
3551 Convert a reference object to unicode.
3564 Convert a reference object to unicode.
3552 If reference is None it returns None.
3565 If reference is None it returns None.
3553 """
3566 """
3554 if ref:
3567 if ref:
3555 return u':'.join(ref)
3568 return u':'.join(ref)
3556 else:
3569 else:
3557 return None
3570 return None
3558
3571
3559 def get_api_data(self, with_merge_state=True):
3572 def get_api_data(self, with_merge_state=True):
3560 from rhodecode.model.pull_request import PullRequestModel
3573 from rhodecode.model.pull_request import PullRequestModel
3561
3574
3562 pull_request = self
3575 pull_request = self
3563 if with_merge_state:
3576 if with_merge_state:
3564 merge_status = PullRequestModel().merge_status(pull_request)
3577 merge_status = PullRequestModel().merge_status(pull_request)
3565 merge_state = {
3578 merge_state = {
3566 'status': merge_status[0],
3579 'status': merge_status[0],
3567 'message': safe_unicode(merge_status[1]),
3580 'message': safe_unicode(merge_status[1]),
3568 }
3581 }
3569 else:
3582 else:
3570 merge_state = {'status': 'not_available',
3583 merge_state = {'status': 'not_available',
3571 'message': 'not_available'}
3584 'message': 'not_available'}
3572
3585
3573 merge_data = {
3586 merge_data = {
3574 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3587 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3575 'reference': (
3588 'reference': (
3576 pull_request.shadow_merge_ref._asdict()
3589 pull_request.shadow_merge_ref._asdict()
3577 if pull_request.shadow_merge_ref else None),
3590 if pull_request.shadow_merge_ref else None),
3578 }
3591 }
3579
3592
3580 data = {
3593 data = {
3581 'pull_request_id': pull_request.pull_request_id,
3594 'pull_request_id': pull_request.pull_request_id,
3582 'url': PullRequestModel().get_url(pull_request),
3595 'url': PullRequestModel().get_url(pull_request),
3583 'title': pull_request.title,
3596 'title': pull_request.title,
3584 'description': pull_request.description,
3597 'description': pull_request.description,
3585 'status': pull_request.status,
3598 'status': pull_request.status,
3586 'created_on': pull_request.created_on,
3599 'created_on': pull_request.created_on,
3587 'updated_on': pull_request.updated_on,
3600 'updated_on': pull_request.updated_on,
3588 'commit_ids': pull_request.revisions,
3601 'commit_ids': pull_request.revisions,
3589 'review_status': pull_request.calculated_review_status(),
3602 'review_status': pull_request.calculated_review_status(),
3590 'mergeable': merge_state,
3603 'mergeable': merge_state,
3591 'source': {
3604 'source': {
3592 'clone_url': pull_request.source_repo.clone_url(),
3605 'clone_url': pull_request.source_repo.clone_url(),
3593 'repository': pull_request.source_repo.repo_name,
3606 'repository': pull_request.source_repo.repo_name,
3594 'reference': {
3607 'reference': {
3595 'name': pull_request.source_ref_parts.name,
3608 'name': pull_request.source_ref_parts.name,
3596 'type': pull_request.source_ref_parts.type,
3609 'type': pull_request.source_ref_parts.type,
3597 'commit_id': pull_request.source_ref_parts.commit_id,
3610 'commit_id': pull_request.source_ref_parts.commit_id,
3598 },
3611 },
3599 },
3612 },
3600 'target': {
3613 'target': {
3601 'clone_url': pull_request.target_repo.clone_url(),
3614 'clone_url': pull_request.target_repo.clone_url(),
3602 'repository': pull_request.target_repo.repo_name,
3615 'repository': pull_request.target_repo.repo_name,
3603 'reference': {
3616 'reference': {
3604 'name': pull_request.target_ref_parts.name,
3617 'name': pull_request.target_ref_parts.name,
3605 'type': pull_request.target_ref_parts.type,
3618 'type': pull_request.target_ref_parts.type,
3606 'commit_id': pull_request.target_ref_parts.commit_id,
3619 'commit_id': pull_request.target_ref_parts.commit_id,
3607 },
3620 },
3608 },
3621 },
3609 'merge': merge_data,
3622 'merge': merge_data,
3610 'author': pull_request.author.get_api_data(include_secrets=False,
3623 'author': pull_request.author.get_api_data(include_secrets=False,
3611 details='basic'),
3624 details='basic'),
3612 'reviewers': [
3625 'reviewers': [
3613 {
3626 {
3614 'user': reviewer.get_api_data(include_secrets=False,
3627 'user': reviewer.get_api_data(include_secrets=False,
3615 details='basic'),
3628 details='basic'),
3616 'reasons': reasons,
3629 'reasons': reasons,
3617 'review_status': st[0][1].status if st else 'not_reviewed',
3630 'review_status': st[0][1].status if st else 'not_reviewed',
3618 }
3631 }
3619 for obj, reviewer, reasons, mandatory, st in
3632 for obj, reviewer, reasons, mandatory, st in
3620 pull_request.reviewers_statuses()
3633 pull_request.reviewers_statuses()
3621 ]
3634 ]
3622 }
3635 }
3623
3636
3624 return data
3637 return data
3625
3638
3626
3639
3627 class PullRequest(Base, _PullRequestBase):
3640 class PullRequest(Base, _PullRequestBase):
3628 __tablename__ = 'pull_requests'
3641 __tablename__ = 'pull_requests'
3629 __table_args__ = (
3642 __table_args__ = (
3630 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3643 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3631 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3644 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3632 )
3645 )
3633
3646
3634 pull_request_id = Column(
3647 pull_request_id = Column(
3635 'pull_request_id', Integer(), nullable=False, primary_key=True)
3648 'pull_request_id', Integer(), nullable=False, primary_key=True)
3636
3649
3637 def __repr__(self):
3650 def __repr__(self):
3638 if self.pull_request_id:
3651 if self.pull_request_id:
3639 return '<DB:PullRequest #%s>' % self.pull_request_id
3652 return '<DB:PullRequest #%s>' % self.pull_request_id
3640 else:
3653 else:
3641 return '<DB:PullRequest at %#x>' % id(self)
3654 return '<DB:PullRequest at %#x>' % id(self)
3642
3655
3643 reviewers = relationship('PullRequestReviewers',
3656 reviewers = relationship('PullRequestReviewers',
3644 cascade="all, delete, delete-orphan")
3657 cascade="all, delete, delete-orphan")
3645 statuses = relationship('ChangesetStatus',
3658 statuses = relationship('ChangesetStatus',
3646 cascade="all, delete, delete-orphan")
3659 cascade="all, delete, delete-orphan")
3647 comments = relationship('ChangesetComment',
3660 comments = relationship('ChangesetComment',
3648 cascade="all, delete, delete-orphan")
3661 cascade="all, delete, delete-orphan")
3649 versions = relationship('PullRequestVersion',
3662 versions = relationship('PullRequestVersion',
3650 cascade="all, delete, delete-orphan",
3663 cascade="all, delete, delete-orphan",
3651 lazy='dynamic')
3664 lazy='dynamic')
3652
3665
3653 @classmethod
3666 @classmethod
3654 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3667 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3655 internal_methods=None):
3668 internal_methods=None):
3656
3669
3657 class PullRequestDisplay(object):
3670 class PullRequestDisplay(object):
3658 """
3671 """
3659 Special object wrapper for showing PullRequest data via Versions
3672 Special object wrapper for showing PullRequest data via Versions
3660 It mimics PR object as close as possible. This is read only object
3673 It mimics PR object as close as possible. This is read only object
3661 just for display
3674 just for display
3662 """
3675 """
3663
3676
3664 def __init__(self, attrs, internal=None):
3677 def __init__(self, attrs, internal=None):
3665 self.attrs = attrs
3678 self.attrs = attrs
3666 # internal have priority over the given ones via attrs
3679 # internal have priority over the given ones via attrs
3667 self.internal = internal or ['versions']
3680 self.internal = internal or ['versions']
3668
3681
3669 def __getattr__(self, item):
3682 def __getattr__(self, item):
3670 if item in self.internal:
3683 if item in self.internal:
3671 return getattr(self, item)
3684 return getattr(self, item)
3672 try:
3685 try:
3673 return self.attrs[item]
3686 return self.attrs[item]
3674 except KeyError:
3687 except KeyError:
3675 raise AttributeError(
3688 raise AttributeError(
3676 '%s object has no attribute %s' % (self, item))
3689 '%s object has no attribute %s' % (self, item))
3677
3690
3678 def __repr__(self):
3691 def __repr__(self):
3679 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3692 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3680
3693
3681 def versions(self):
3694 def versions(self):
3682 return pull_request_obj.versions.order_by(
3695 return pull_request_obj.versions.order_by(
3683 PullRequestVersion.pull_request_version_id).all()
3696 PullRequestVersion.pull_request_version_id).all()
3684
3697
3685 def is_closed(self):
3698 def is_closed(self):
3686 return pull_request_obj.is_closed()
3699 return pull_request_obj.is_closed()
3687
3700
3688 @property
3701 @property
3689 def pull_request_version_id(self):
3702 def pull_request_version_id(self):
3690 return getattr(pull_request_obj, 'pull_request_version_id', None)
3703 return getattr(pull_request_obj, 'pull_request_version_id', None)
3691
3704
3692 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3705 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3693
3706
3694 attrs.author = StrictAttributeDict(
3707 attrs.author = StrictAttributeDict(
3695 pull_request_obj.author.get_api_data())
3708 pull_request_obj.author.get_api_data())
3696 if pull_request_obj.target_repo:
3709 if pull_request_obj.target_repo:
3697 attrs.target_repo = StrictAttributeDict(
3710 attrs.target_repo = StrictAttributeDict(
3698 pull_request_obj.target_repo.get_api_data())
3711 pull_request_obj.target_repo.get_api_data())
3699 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3712 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3700
3713
3701 if pull_request_obj.source_repo:
3714 if pull_request_obj.source_repo:
3702 attrs.source_repo = StrictAttributeDict(
3715 attrs.source_repo = StrictAttributeDict(
3703 pull_request_obj.source_repo.get_api_data())
3716 pull_request_obj.source_repo.get_api_data())
3704 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3717 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3705
3718
3706 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3719 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3707 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3720 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3708 attrs.revisions = pull_request_obj.revisions
3721 attrs.revisions = pull_request_obj.revisions
3709
3722
3710 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3723 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3711 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3724 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3712 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3725 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3713
3726
3714 return PullRequestDisplay(attrs, internal=internal_methods)
3727 return PullRequestDisplay(attrs, internal=internal_methods)
3715
3728
3716 def is_closed(self):
3729 def is_closed(self):
3717 return self.status == self.STATUS_CLOSED
3730 return self.status == self.STATUS_CLOSED
3718
3731
3719 def __json__(self):
3732 def __json__(self):
3720 return {
3733 return {
3721 'revisions': self.revisions,
3734 'revisions': self.revisions,
3722 }
3735 }
3723
3736
3724 def calculated_review_status(self):
3737 def calculated_review_status(self):
3725 from rhodecode.model.changeset_status import ChangesetStatusModel
3738 from rhodecode.model.changeset_status import ChangesetStatusModel
3726 return ChangesetStatusModel().calculated_review_status(self)
3739 return ChangesetStatusModel().calculated_review_status(self)
3727
3740
3728 def reviewers_statuses(self):
3741 def reviewers_statuses(self):
3729 from rhodecode.model.changeset_status import ChangesetStatusModel
3742 from rhodecode.model.changeset_status import ChangesetStatusModel
3730 return ChangesetStatusModel().reviewers_statuses(self)
3743 return ChangesetStatusModel().reviewers_statuses(self)
3731
3744
3732 @property
3745 @property
3733 def workspace_id(self):
3746 def workspace_id(self):
3734 from rhodecode.model.pull_request import PullRequestModel
3747 from rhodecode.model.pull_request import PullRequestModel
3735 return PullRequestModel()._workspace_id(self)
3748 return PullRequestModel()._workspace_id(self)
3736
3749
3737 def get_shadow_repo(self):
3750 def get_shadow_repo(self):
3738 workspace_id = self.workspace_id
3751 workspace_id = self.workspace_id
3739 vcs_obj = self.target_repo.scm_instance()
3752 vcs_obj = self.target_repo.scm_instance()
3740 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3753 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3741 workspace_id)
3754 workspace_id)
3742 return vcs_obj._get_shadow_instance(shadow_repository_path)
3755 return vcs_obj._get_shadow_instance(shadow_repository_path)
3743
3756
3744
3757
3745 class PullRequestVersion(Base, _PullRequestBase):
3758 class PullRequestVersion(Base, _PullRequestBase):
3746 __tablename__ = 'pull_request_versions'
3759 __tablename__ = 'pull_request_versions'
3747 __table_args__ = (
3760 __table_args__ = (
3748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3761 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3749 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3762 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3750 )
3763 )
3751
3764
3752 pull_request_version_id = Column(
3765 pull_request_version_id = Column(
3753 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3766 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3754 pull_request_id = Column(
3767 pull_request_id = Column(
3755 'pull_request_id', Integer(),
3768 'pull_request_id', Integer(),
3756 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3769 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3757 pull_request = relationship('PullRequest')
3770 pull_request = relationship('PullRequest')
3758
3771
3759 def __repr__(self):
3772 def __repr__(self):
3760 if self.pull_request_version_id:
3773 if self.pull_request_version_id:
3761 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3774 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3762 else:
3775 else:
3763 return '<DB:PullRequestVersion at %#x>' % id(self)
3776 return '<DB:PullRequestVersion at %#x>' % id(self)
3764
3777
3765 @property
3778 @property
3766 def reviewers(self):
3779 def reviewers(self):
3767 return self.pull_request.reviewers
3780 return self.pull_request.reviewers
3768
3781
3769 @property
3782 @property
3770 def versions(self):
3783 def versions(self):
3771 return self.pull_request.versions
3784 return self.pull_request.versions
3772
3785
3773 def is_closed(self):
3786 def is_closed(self):
3774 # calculate from original
3787 # calculate from original
3775 return self.pull_request.status == self.STATUS_CLOSED
3788 return self.pull_request.status == self.STATUS_CLOSED
3776
3789
3777 def calculated_review_status(self):
3790 def calculated_review_status(self):
3778 return self.pull_request.calculated_review_status()
3791 return self.pull_request.calculated_review_status()
3779
3792
3780 def reviewers_statuses(self):
3793 def reviewers_statuses(self):
3781 return self.pull_request.reviewers_statuses()
3794 return self.pull_request.reviewers_statuses()
3782
3795
3783
3796
3784 class PullRequestReviewers(Base, BaseModel):
3797 class PullRequestReviewers(Base, BaseModel):
3785 __tablename__ = 'pull_request_reviewers'
3798 __tablename__ = 'pull_request_reviewers'
3786 __table_args__ = (
3799 __table_args__ = (
3787 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3800 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3788 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3801 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3789 )
3802 )
3790
3803
3791 @hybrid_property
3804 @hybrid_property
3792 def reasons(self):
3805 def reasons(self):
3793 if not self._reasons:
3806 if not self._reasons:
3794 return []
3807 return []
3795 return self._reasons
3808 return self._reasons
3796
3809
3797 @reasons.setter
3810 @reasons.setter
3798 def reasons(self, val):
3811 def reasons(self, val):
3799 val = val or []
3812 val = val or []
3800 if any(not isinstance(x, basestring) for x in val):
3813 if any(not isinstance(x, basestring) for x in val):
3801 raise Exception('invalid reasons type, must be list of strings')
3814 raise Exception('invalid reasons type, must be list of strings')
3802 self._reasons = val
3815 self._reasons = val
3803
3816
3804 pull_requests_reviewers_id = Column(
3817 pull_requests_reviewers_id = Column(
3805 'pull_requests_reviewers_id', Integer(), nullable=False,
3818 'pull_requests_reviewers_id', Integer(), nullable=False,
3806 primary_key=True)
3819 primary_key=True)
3807 pull_request_id = Column(
3820 pull_request_id = Column(
3808 "pull_request_id", Integer(),
3821 "pull_request_id", Integer(),
3809 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3822 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3810 user_id = Column(
3823 user_id = Column(
3811 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3824 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3812 _reasons = Column(
3825 _reasons = Column(
3813 'reason', MutationList.as_mutable(
3826 'reason', MutationList.as_mutable(
3814 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3827 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3815
3828
3816 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3829 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3817 user = relationship('User')
3830 user = relationship('User')
3818 pull_request = relationship('PullRequest')
3831 pull_request = relationship('PullRequest')
3819
3832
3820 rule_data = Column(
3833 rule_data = Column(
3821 'rule_data_json',
3834 'rule_data_json',
3822 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3835 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3823
3836
3824 def rule_user_group_data(self):
3837 def rule_user_group_data(self):
3825 """
3838 """
3826 Returns the voting user group rule data for this reviewer
3839 Returns the voting user group rule data for this reviewer
3827 """
3840 """
3828
3841
3829 if self.rule_data and 'vote_rule' in self.rule_data:
3842 if self.rule_data and 'vote_rule' in self.rule_data:
3830 user_group_data = {}
3843 user_group_data = {}
3831 if 'rule_user_group_entry_id' in self.rule_data:
3844 if 'rule_user_group_entry_id' in self.rule_data:
3832 # means a group with voting rules !
3845 # means a group with voting rules !
3833 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3846 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3834 user_group_data['name'] = self.rule_data['rule_name']
3847 user_group_data['name'] = self.rule_data['rule_name']
3835 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3848 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3836
3849
3837 return user_group_data
3850 return user_group_data
3838
3851
3839 def __unicode__(self):
3852 def __unicode__(self):
3840 return u"<%s('id:%s')>" % (self.__class__.__name__,
3853 return u"<%s('id:%s')>" % (self.__class__.__name__,
3841 self.pull_requests_reviewers_id)
3854 self.pull_requests_reviewers_id)
3842
3855
3843
3856
3844 class Notification(Base, BaseModel):
3857 class Notification(Base, BaseModel):
3845 __tablename__ = 'notifications'
3858 __tablename__ = 'notifications'
3846 __table_args__ = (
3859 __table_args__ = (
3847 Index('notification_type_idx', 'type'),
3860 Index('notification_type_idx', 'type'),
3848 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3861 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3849 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3862 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3850 )
3863 )
3851
3864
3852 TYPE_CHANGESET_COMMENT = u'cs_comment'
3865 TYPE_CHANGESET_COMMENT = u'cs_comment'
3853 TYPE_MESSAGE = u'message'
3866 TYPE_MESSAGE = u'message'
3854 TYPE_MENTION = u'mention'
3867 TYPE_MENTION = u'mention'
3855 TYPE_REGISTRATION = u'registration'
3868 TYPE_REGISTRATION = u'registration'
3856 TYPE_PULL_REQUEST = u'pull_request'
3869 TYPE_PULL_REQUEST = u'pull_request'
3857 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3870 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3858
3871
3859 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3872 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3860 subject = Column('subject', Unicode(512), nullable=True)
3873 subject = Column('subject', Unicode(512), nullable=True)
3861 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3874 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3862 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3875 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3863 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3876 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3864 type_ = Column('type', Unicode(255))
3877 type_ = Column('type', Unicode(255))
3865
3878
3866 created_by_user = relationship('User')
3879 created_by_user = relationship('User')
3867 notifications_to_users = relationship('UserNotification', lazy='joined',
3880 notifications_to_users = relationship('UserNotification', lazy='joined',
3868 cascade="all, delete, delete-orphan")
3881 cascade="all, delete, delete-orphan")
3869
3882
3870 @property
3883 @property
3871 def recipients(self):
3884 def recipients(self):
3872 return [x.user for x in UserNotification.query()\
3885 return [x.user for x in UserNotification.query()\
3873 .filter(UserNotification.notification == self)\
3886 .filter(UserNotification.notification == self)\
3874 .order_by(UserNotification.user_id.asc()).all()]
3887 .order_by(UserNotification.user_id.asc()).all()]
3875
3888
3876 @classmethod
3889 @classmethod
3877 def create(cls, created_by, subject, body, recipients, type_=None):
3890 def create(cls, created_by, subject, body, recipients, type_=None):
3878 if type_ is None:
3891 if type_ is None:
3879 type_ = Notification.TYPE_MESSAGE
3892 type_ = Notification.TYPE_MESSAGE
3880
3893
3881 notification = cls()
3894 notification = cls()
3882 notification.created_by_user = created_by
3895 notification.created_by_user = created_by
3883 notification.subject = subject
3896 notification.subject = subject
3884 notification.body = body
3897 notification.body = body
3885 notification.type_ = type_
3898 notification.type_ = type_
3886 notification.created_on = datetime.datetime.now()
3899 notification.created_on = datetime.datetime.now()
3887
3900
3888 for u in recipients:
3901 for u in recipients:
3889 assoc = UserNotification()
3902 assoc = UserNotification()
3890 assoc.notification = notification
3903 assoc.notification = notification
3891
3904
3892 # if created_by is inside recipients mark his notification
3905 # if created_by is inside recipients mark his notification
3893 # as read
3906 # as read
3894 if u.user_id == created_by.user_id:
3907 if u.user_id == created_by.user_id:
3895 assoc.read = True
3908 assoc.read = True
3896
3909
3897 u.notifications.append(assoc)
3910 u.notifications.append(assoc)
3898 Session().add(notification)
3911 Session().add(notification)
3899
3912
3900 return notification
3913 return notification
3901
3914
3902
3915
3903 class UserNotification(Base, BaseModel):
3916 class UserNotification(Base, BaseModel):
3904 __tablename__ = 'user_to_notification'
3917 __tablename__ = 'user_to_notification'
3905 __table_args__ = (
3918 __table_args__ = (
3906 UniqueConstraint('user_id', 'notification_id'),
3919 UniqueConstraint('user_id', 'notification_id'),
3907 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3908 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3921 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3909 )
3922 )
3910 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3923 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3911 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3924 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3912 read = Column('read', Boolean, default=False)
3925 read = Column('read', Boolean, default=False)
3913 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3926 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3914
3927
3915 user = relationship('User', lazy="joined")
3928 user = relationship('User', lazy="joined")
3916 notification = relationship('Notification', lazy="joined",
3929 notification = relationship('Notification', lazy="joined",
3917 order_by=lambda: Notification.created_on.desc(),)
3930 order_by=lambda: Notification.created_on.desc(),)
3918
3931
3919 def mark_as_read(self):
3932 def mark_as_read(self):
3920 self.read = True
3933 self.read = True
3921 Session().add(self)
3934 Session().add(self)
3922
3935
3923
3936
3924 class Gist(Base, BaseModel):
3937 class Gist(Base, BaseModel):
3925 __tablename__ = 'gists'
3938 __tablename__ = 'gists'
3926 __table_args__ = (
3939 __table_args__ = (
3927 Index('g_gist_access_id_idx', 'gist_access_id'),
3940 Index('g_gist_access_id_idx', 'gist_access_id'),
3928 Index('g_created_on_idx', 'created_on'),
3941 Index('g_created_on_idx', 'created_on'),
3929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3930 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3943 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3931 )
3944 )
3932 GIST_PUBLIC = u'public'
3945 GIST_PUBLIC = u'public'
3933 GIST_PRIVATE = u'private'
3946 GIST_PRIVATE = u'private'
3934 DEFAULT_FILENAME = u'gistfile1.txt'
3947 DEFAULT_FILENAME = u'gistfile1.txt'
3935
3948
3936 ACL_LEVEL_PUBLIC = u'acl_public'
3949 ACL_LEVEL_PUBLIC = u'acl_public'
3937 ACL_LEVEL_PRIVATE = u'acl_private'
3950 ACL_LEVEL_PRIVATE = u'acl_private'
3938
3951
3939 gist_id = Column('gist_id', Integer(), primary_key=True)
3952 gist_id = Column('gist_id', Integer(), primary_key=True)
3940 gist_access_id = Column('gist_access_id', Unicode(250))
3953 gist_access_id = Column('gist_access_id', Unicode(250))
3941 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3954 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3942 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3955 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3943 gist_expires = Column('gist_expires', Float(53), nullable=False)
3956 gist_expires = Column('gist_expires', Float(53), nullable=False)
3944 gist_type = Column('gist_type', Unicode(128), nullable=False)
3957 gist_type = Column('gist_type', Unicode(128), nullable=False)
3945 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3958 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3946 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3959 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3947 acl_level = Column('acl_level', Unicode(128), nullable=True)
3960 acl_level = Column('acl_level', Unicode(128), nullable=True)
3948
3961
3949 owner = relationship('User')
3962 owner = relationship('User')
3950
3963
3951 def __repr__(self):
3964 def __repr__(self):
3952 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3965 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3953
3966
3954 @hybrid_property
3967 @hybrid_property
3955 def description_safe(self):
3968 def description_safe(self):
3956 from rhodecode.lib import helpers as h
3969 from rhodecode.lib import helpers as h
3957 return h.escape(self.gist_description)
3970 return h.escape(self.gist_description)
3958
3971
3959 @classmethod
3972 @classmethod
3960 def get_or_404(cls, id_):
3973 def get_or_404(cls, id_):
3961 from pyramid.httpexceptions import HTTPNotFound
3974 from pyramid.httpexceptions import HTTPNotFound
3962
3975
3963 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3976 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3964 if not res:
3977 if not res:
3965 raise HTTPNotFound()
3978 raise HTTPNotFound()
3966 return res
3979 return res
3967
3980
3968 @classmethod
3981 @classmethod
3969 def get_by_access_id(cls, gist_access_id):
3982 def get_by_access_id(cls, gist_access_id):
3970 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3983 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3971
3984
3972 def gist_url(self):
3985 def gist_url(self):
3973 from rhodecode.model.gist import GistModel
3986 from rhodecode.model.gist import GistModel
3974 return GistModel().get_url(self)
3987 return GistModel().get_url(self)
3975
3988
3976 @classmethod
3989 @classmethod
3977 def base_path(cls):
3990 def base_path(cls):
3978 """
3991 """
3979 Returns base path when all gists are stored
3992 Returns base path when all gists are stored
3980
3993
3981 :param cls:
3994 :param cls:
3982 """
3995 """
3983 from rhodecode.model.gist import GIST_STORE_LOC
3996 from rhodecode.model.gist import GIST_STORE_LOC
3984 q = Session().query(RhodeCodeUi)\
3997 q = Session().query(RhodeCodeUi)\
3985 .filter(RhodeCodeUi.ui_key == URL_SEP)
3998 .filter(RhodeCodeUi.ui_key == URL_SEP)
3986 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3999 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3987 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4000 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3988
4001
3989 def get_api_data(self):
4002 def get_api_data(self):
3990 """
4003 """
3991 Common function for generating gist related data for API
4004 Common function for generating gist related data for API
3992 """
4005 """
3993 gist = self
4006 gist = self
3994 data = {
4007 data = {
3995 'gist_id': gist.gist_id,
4008 'gist_id': gist.gist_id,
3996 'type': gist.gist_type,
4009 'type': gist.gist_type,
3997 'access_id': gist.gist_access_id,
4010 'access_id': gist.gist_access_id,
3998 'description': gist.gist_description,
4011 'description': gist.gist_description,
3999 'url': gist.gist_url(),
4012 'url': gist.gist_url(),
4000 'expires': gist.gist_expires,
4013 'expires': gist.gist_expires,
4001 'created_on': gist.created_on,
4014 'created_on': gist.created_on,
4002 'modified_at': gist.modified_at,
4015 'modified_at': gist.modified_at,
4003 'content': None,
4016 'content': None,
4004 'acl_level': gist.acl_level,
4017 'acl_level': gist.acl_level,
4005 }
4018 }
4006 return data
4019 return data
4007
4020
4008 def __json__(self):
4021 def __json__(self):
4009 data = dict(
4022 data = dict(
4010 )
4023 )
4011 data.update(self.get_api_data())
4024 data.update(self.get_api_data())
4012 return data
4025 return data
4013 # SCM functions
4026 # SCM functions
4014
4027
4015 def scm_instance(self, **kwargs):
4028 def scm_instance(self, **kwargs):
4016 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4029 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4017 return get_vcs_instance(
4030 return get_vcs_instance(
4018 repo_path=safe_str(full_repo_path), create=False)
4031 repo_path=safe_str(full_repo_path), create=False)
4019
4032
4020
4033
4021 class ExternalIdentity(Base, BaseModel):
4034 class ExternalIdentity(Base, BaseModel):
4022 __tablename__ = 'external_identities'
4035 __tablename__ = 'external_identities'
4023 __table_args__ = (
4036 __table_args__ = (
4024 Index('local_user_id_idx', 'local_user_id'),
4037 Index('local_user_id_idx', 'local_user_id'),
4025 Index('external_id_idx', 'external_id'),
4038 Index('external_id_idx', 'external_id'),
4026 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4039 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4027 'mysql_charset': 'utf8'})
4040 'mysql_charset': 'utf8'})
4028
4041
4029 external_id = Column('external_id', Unicode(255), default=u'',
4042 external_id = Column('external_id', Unicode(255), default=u'',
4030 primary_key=True)
4043 primary_key=True)
4031 external_username = Column('external_username', Unicode(1024), default=u'')
4044 external_username = Column('external_username', Unicode(1024), default=u'')
4032 local_user_id = Column('local_user_id', Integer(),
4045 local_user_id = Column('local_user_id', Integer(),
4033 ForeignKey('users.user_id'), primary_key=True)
4046 ForeignKey('users.user_id'), primary_key=True)
4034 provider_name = Column('provider_name', Unicode(255), default=u'',
4047 provider_name = Column('provider_name', Unicode(255), default=u'',
4035 primary_key=True)
4048 primary_key=True)
4036 access_token = Column('access_token', String(1024), default=u'')
4049 access_token = Column('access_token', String(1024), default=u'')
4037 alt_token = Column('alt_token', String(1024), default=u'')
4050 alt_token = Column('alt_token', String(1024), default=u'')
4038 token_secret = Column('token_secret', String(1024), default=u'')
4051 token_secret = Column('token_secret', String(1024), default=u'')
4039
4052
4040 @classmethod
4053 @classmethod
4041 def by_external_id_and_provider(cls, external_id, provider_name,
4054 def by_external_id_and_provider(cls, external_id, provider_name,
4042 local_user_id=None):
4055 local_user_id=None):
4043 """
4056 """
4044 Returns ExternalIdentity instance based on search params
4057 Returns ExternalIdentity instance based on search params
4045
4058
4046 :param external_id:
4059 :param external_id:
4047 :param provider_name:
4060 :param provider_name:
4048 :return: ExternalIdentity
4061 :return: ExternalIdentity
4049 """
4062 """
4050 query = cls.query()
4063 query = cls.query()
4051 query = query.filter(cls.external_id == external_id)
4064 query = query.filter(cls.external_id == external_id)
4052 query = query.filter(cls.provider_name == provider_name)
4065 query = query.filter(cls.provider_name == provider_name)
4053 if local_user_id:
4066 if local_user_id:
4054 query = query.filter(cls.local_user_id == local_user_id)
4067 query = query.filter(cls.local_user_id == local_user_id)
4055 return query.first()
4068 return query.first()
4056
4069
4057 @classmethod
4070 @classmethod
4058 def user_by_external_id_and_provider(cls, external_id, provider_name):
4071 def user_by_external_id_and_provider(cls, external_id, provider_name):
4059 """
4072 """
4060 Returns User instance based on search params
4073 Returns User instance based on search params
4061
4074
4062 :param external_id:
4075 :param external_id:
4063 :param provider_name:
4076 :param provider_name:
4064 :return: User
4077 :return: User
4065 """
4078 """
4066 query = User.query()
4079 query = User.query()
4067 query = query.filter(cls.external_id == external_id)
4080 query = query.filter(cls.external_id == external_id)
4068 query = query.filter(cls.provider_name == provider_name)
4081 query = query.filter(cls.provider_name == provider_name)
4069 query = query.filter(User.user_id == cls.local_user_id)
4082 query = query.filter(User.user_id == cls.local_user_id)
4070 return query.first()
4083 return query.first()
4071
4084
4072 @classmethod
4085 @classmethod
4073 def by_local_user_id(cls, local_user_id):
4086 def by_local_user_id(cls, local_user_id):
4074 """
4087 """
4075 Returns all tokens for user
4088 Returns all tokens for user
4076
4089
4077 :param local_user_id:
4090 :param local_user_id:
4078 :return: ExternalIdentity
4091 :return: ExternalIdentity
4079 """
4092 """
4080 query = cls.query()
4093 query = cls.query()
4081 query = query.filter(cls.local_user_id == local_user_id)
4094 query = query.filter(cls.local_user_id == local_user_id)
4082 return query
4095 return query
4083
4096
4084
4097
4085 class Integration(Base, BaseModel):
4098 class Integration(Base, BaseModel):
4086 __tablename__ = 'integrations'
4099 __tablename__ = 'integrations'
4087 __table_args__ = (
4100 __table_args__ = (
4088 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4101 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4089 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4102 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4090 )
4103 )
4091
4104
4092 integration_id = Column('integration_id', Integer(), primary_key=True)
4105 integration_id = Column('integration_id', Integer(), primary_key=True)
4093 integration_type = Column('integration_type', String(255))
4106 integration_type = Column('integration_type', String(255))
4094 enabled = Column('enabled', Boolean(), nullable=False)
4107 enabled = Column('enabled', Boolean(), nullable=False)
4095 name = Column('name', String(255), nullable=False)
4108 name = Column('name', String(255), nullable=False)
4096 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4109 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4097 default=False)
4110 default=False)
4098
4111
4099 settings = Column(
4112 settings = Column(
4100 'settings_json', MutationObj.as_mutable(
4113 'settings_json', MutationObj.as_mutable(
4101 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4114 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4102 repo_id = Column(
4115 repo_id = Column(
4103 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4116 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4104 nullable=True, unique=None, default=None)
4117 nullable=True, unique=None, default=None)
4105 repo = relationship('Repository', lazy='joined')
4118 repo = relationship('Repository', lazy='joined')
4106
4119
4107 repo_group_id = Column(
4120 repo_group_id = Column(
4108 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4121 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4109 nullable=True, unique=None, default=None)
4122 nullable=True, unique=None, default=None)
4110 repo_group = relationship('RepoGroup', lazy='joined')
4123 repo_group = relationship('RepoGroup', lazy='joined')
4111
4124
4112 @property
4125 @property
4113 def scope(self):
4126 def scope(self):
4114 if self.repo:
4127 if self.repo:
4115 return repr(self.repo)
4128 return repr(self.repo)
4116 if self.repo_group:
4129 if self.repo_group:
4117 if self.child_repos_only:
4130 if self.child_repos_only:
4118 return repr(self.repo_group) + ' (child repos only)'
4131 return repr(self.repo_group) + ' (child repos only)'
4119 else:
4132 else:
4120 return repr(self.repo_group) + ' (recursive)'
4133 return repr(self.repo_group) + ' (recursive)'
4121 if self.child_repos_only:
4134 if self.child_repos_only:
4122 return 'root_repos'
4135 return 'root_repos'
4123 return 'global'
4136 return 'global'
4124
4137
4125 def __repr__(self):
4138 def __repr__(self):
4126 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4139 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4127
4140
4128
4141
4129 class RepoReviewRuleUser(Base, BaseModel):
4142 class RepoReviewRuleUser(Base, BaseModel):
4130 __tablename__ = 'repo_review_rules_users'
4143 __tablename__ = 'repo_review_rules_users'
4131 __table_args__ = (
4144 __table_args__ = (
4132 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4145 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4133 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4146 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4134 )
4147 )
4135
4148
4136 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4149 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4137 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4150 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4138 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4151 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4139 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4152 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4140 user = relationship('User')
4153 user = relationship('User')
4141
4154
4142 def rule_data(self):
4155 def rule_data(self):
4143 return {
4156 return {
4144 'mandatory': self.mandatory
4157 'mandatory': self.mandatory
4145 }
4158 }
4146
4159
4147
4160
4148 class RepoReviewRuleUserGroup(Base, BaseModel):
4161 class RepoReviewRuleUserGroup(Base, BaseModel):
4149 __tablename__ = 'repo_review_rules_users_groups'
4162 __tablename__ = 'repo_review_rules_users_groups'
4150 __table_args__ = (
4163 __table_args__ = (
4151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4152 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4165 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4153 )
4166 )
4154 VOTE_RULE_ALL = -1
4167 VOTE_RULE_ALL = -1
4155
4168
4156 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4169 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4157 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4170 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4158 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4171 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4159 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4172 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4160 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4173 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4161 users_group = relationship('UserGroup')
4174 users_group = relationship('UserGroup')
4162
4175
4163 def rule_data(self):
4176 def rule_data(self):
4164 return {
4177 return {
4165 'mandatory': self.mandatory,
4178 'mandatory': self.mandatory,
4166 'vote_rule': self.vote_rule
4179 'vote_rule': self.vote_rule
4167 }
4180 }
4168
4181
4169 @property
4182 @property
4170 def vote_rule_label(self):
4183 def vote_rule_label(self):
4171 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4184 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4172 return 'all must vote'
4185 return 'all must vote'
4173 else:
4186 else:
4174 return 'min. vote {}'.format(self.vote_rule)
4187 return 'min. vote {}'.format(self.vote_rule)
4175
4188
4176
4189
4177 class RepoReviewRule(Base, BaseModel):
4190 class RepoReviewRule(Base, BaseModel):
4178 __tablename__ = 'repo_review_rules'
4191 __tablename__ = 'repo_review_rules'
4179 __table_args__ = (
4192 __table_args__ = (
4180 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4193 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4181 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4194 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4182 )
4195 )
4183
4196
4184 repo_review_rule_id = Column(
4197 repo_review_rule_id = Column(
4185 'repo_review_rule_id', Integer(), primary_key=True)
4198 'repo_review_rule_id', Integer(), primary_key=True)
4186 repo_id = Column(
4199 repo_id = Column(
4187 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4200 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4188 repo = relationship('Repository', backref='review_rules')
4201 repo = relationship('Repository', backref='review_rules')
4189
4202
4190 review_rule_name = Column('review_rule_name', String(255))
4203 review_rule_name = Column('review_rule_name', String(255))
4191 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4204 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4192 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4205 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4193 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4206 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4194
4207
4195 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4208 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4196 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4209 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4197 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4210 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4198 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4211 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4199
4212
4200 rule_users = relationship('RepoReviewRuleUser')
4213 rule_users = relationship('RepoReviewRuleUser')
4201 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4214 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4202
4215
4203 def _validate_glob(self, value):
4216 def _validate_glob(self, value):
4204 re.compile('^' + glob2re(value) + '$')
4217 re.compile('^' + glob2re(value) + '$')
4205
4218
4206 @hybrid_property
4219 @hybrid_property
4207 def source_branch_pattern(self):
4220 def source_branch_pattern(self):
4208 return self._branch_pattern or '*'
4221 return self._branch_pattern or '*'
4209
4222
4210 @source_branch_pattern.setter
4223 @source_branch_pattern.setter
4211 def source_branch_pattern(self, value):
4224 def source_branch_pattern(self, value):
4212 self._validate_glob(value)
4225 self._validate_glob(value)
4213 self._branch_pattern = value or '*'
4226 self._branch_pattern = value or '*'
4214
4227
4215 @hybrid_property
4228 @hybrid_property
4216 def target_branch_pattern(self):
4229 def target_branch_pattern(self):
4217 return self._target_branch_pattern or '*'
4230 return self._target_branch_pattern or '*'
4218
4231
4219 @target_branch_pattern.setter
4232 @target_branch_pattern.setter
4220 def target_branch_pattern(self, value):
4233 def target_branch_pattern(self, value):
4221 self._validate_glob(value)
4234 self._validate_glob(value)
4222 self._target_branch_pattern = value or '*'
4235 self._target_branch_pattern = value or '*'
4223
4236
4224 @hybrid_property
4237 @hybrid_property
4225 def file_pattern(self):
4238 def file_pattern(self):
4226 return self._file_pattern or '*'
4239 return self._file_pattern or '*'
4227
4240
4228 @file_pattern.setter
4241 @file_pattern.setter
4229 def file_pattern(self, value):
4242 def file_pattern(self, value):
4230 self._validate_glob(value)
4243 self._validate_glob(value)
4231 self._file_pattern = value or '*'
4244 self._file_pattern = value or '*'
4232
4245
4233 def matches(self, source_branch, target_branch, files_changed):
4246 def matches(self, source_branch, target_branch, files_changed):
4234 """
4247 """
4235 Check if this review rule matches a branch/files in a pull request
4248 Check if this review rule matches a branch/files in a pull request
4236
4249
4237 :param source_branch: source branch name for the commit
4250 :param source_branch: source branch name for the commit
4238 :param target_branch: target branch name for the commit
4251 :param target_branch: target branch name for the commit
4239 :param files_changed: list of file paths changed in the pull request
4252 :param files_changed: list of file paths changed in the pull request
4240 """
4253 """
4241
4254
4242 source_branch = source_branch or ''
4255 source_branch = source_branch or ''
4243 target_branch = target_branch or ''
4256 target_branch = target_branch or ''
4244 files_changed = files_changed or []
4257 files_changed = files_changed or []
4245
4258
4246 branch_matches = True
4259 branch_matches = True
4247 if source_branch or target_branch:
4260 if source_branch or target_branch:
4248 if self.source_branch_pattern == '*':
4261 if self.source_branch_pattern == '*':
4249 source_branch_match = True
4262 source_branch_match = True
4250 else:
4263 else:
4251 source_branch_regex = re.compile(
4264 source_branch_regex = re.compile(
4252 '^' + glob2re(self.source_branch_pattern) + '$')
4265 '^' + glob2re(self.source_branch_pattern) + '$')
4253 source_branch_match = bool(source_branch_regex.search(source_branch))
4266 source_branch_match = bool(source_branch_regex.search(source_branch))
4254 if self.target_branch_pattern == '*':
4267 if self.target_branch_pattern == '*':
4255 target_branch_match = True
4268 target_branch_match = True
4256 else:
4269 else:
4257 target_branch_regex = re.compile(
4270 target_branch_regex = re.compile(
4258 '^' + glob2re(self.target_branch_pattern) + '$')
4271 '^' + glob2re(self.target_branch_pattern) + '$')
4259 target_branch_match = bool(target_branch_regex.search(target_branch))
4272 target_branch_match = bool(target_branch_regex.search(target_branch))
4260
4273
4261 branch_matches = source_branch_match and target_branch_match
4274 branch_matches = source_branch_match and target_branch_match
4262
4275
4263 files_matches = True
4276 files_matches = True
4264 if self.file_pattern != '*':
4277 if self.file_pattern != '*':
4265 files_matches = False
4278 files_matches = False
4266 file_regex = re.compile(glob2re(self.file_pattern))
4279 file_regex = re.compile(glob2re(self.file_pattern))
4267 for filename in files_changed:
4280 for filename in files_changed:
4268 if file_regex.search(filename):
4281 if file_regex.search(filename):
4269 files_matches = True
4282 files_matches = True
4270 break
4283 break
4271
4284
4272 return branch_matches and files_matches
4285 return branch_matches and files_matches
4273
4286
4274 @property
4287 @property
4275 def review_users(self):
4288 def review_users(self):
4276 """ Returns the users which this rule applies to """
4289 """ Returns the users which this rule applies to """
4277
4290
4278 users = collections.OrderedDict()
4291 users = collections.OrderedDict()
4279
4292
4280 for rule_user in self.rule_users:
4293 for rule_user in self.rule_users:
4281 if rule_user.user.active:
4294 if rule_user.user.active:
4282 if rule_user.user not in users:
4295 if rule_user.user not in users:
4283 users[rule_user.user.username] = {
4296 users[rule_user.user.username] = {
4284 'user': rule_user.user,
4297 'user': rule_user.user,
4285 'source': 'user',
4298 'source': 'user',
4286 'source_data': {},
4299 'source_data': {},
4287 'data': rule_user.rule_data()
4300 'data': rule_user.rule_data()
4288 }
4301 }
4289
4302
4290 for rule_user_group in self.rule_user_groups:
4303 for rule_user_group in self.rule_user_groups:
4291 source_data = {
4304 source_data = {
4292 'user_group_id': rule_user_group.users_group.users_group_id,
4305 'user_group_id': rule_user_group.users_group.users_group_id,
4293 'name': rule_user_group.users_group.users_group_name,
4306 'name': rule_user_group.users_group.users_group_name,
4294 'members': len(rule_user_group.users_group.members)
4307 'members': len(rule_user_group.users_group.members)
4295 }
4308 }
4296 for member in rule_user_group.users_group.members:
4309 for member in rule_user_group.users_group.members:
4297 if member.user.active:
4310 if member.user.active:
4298 key = member.user.username
4311 key = member.user.username
4299 if key in users:
4312 if key in users:
4300 # skip this member as we have him already
4313 # skip this member as we have him already
4301 # this prevents from override the "first" matched
4314 # this prevents from override the "first" matched
4302 # users with duplicates in multiple groups
4315 # users with duplicates in multiple groups
4303 continue
4316 continue
4304
4317
4305 users[key] = {
4318 users[key] = {
4306 'user': member.user,
4319 'user': member.user,
4307 'source': 'user_group',
4320 'source': 'user_group',
4308 'source_data': source_data,
4321 'source_data': source_data,
4309 'data': rule_user_group.rule_data()
4322 'data': rule_user_group.rule_data()
4310 }
4323 }
4311
4324
4312 return users
4325 return users
4313
4326
4314 def user_group_vote_rule(self):
4327 def user_group_vote_rule(self):
4315 rules = []
4328 rules = []
4316 if self.rule_user_groups:
4329 if self.rule_user_groups:
4317 for user_group in self.rule_user_groups:
4330 for user_group in self.rule_user_groups:
4318 rules.append(user_group)
4331 rules.append(user_group)
4319 return rules
4332 return rules
4320
4333
4321 def __repr__(self):
4334 def __repr__(self):
4322 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4335 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4323 self.repo_review_rule_id, self.repo)
4336 self.repo_review_rule_id, self.repo)
4324
4337
4325
4338
4326 class ScheduleEntry(Base, BaseModel):
4339 class ScheduleEntry(Base, BaseModel):
4327 __tablename__ = 'schedule_entries'
4340 __tablename__ = 'schedule_entries'
4328 __table_args__ = (
4341 __table_args__ = (
4329 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4342 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4330 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4343 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4331 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4332 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4333 )
4346 )
4334 schedule_types = ['crontab', 'timedelta', 'integer']
4347 schedule_types = ['crontab', 'timedelta', 'integer']
4335 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4348 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4336
4349
4337 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4350 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4338 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4351 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4339 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4352 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4340
4353
4341 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4354 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4342 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4355 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4343
4356
4344 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4357 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4345 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4358 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4346
4359
4347 # task
4360 # task
4348 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4361 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4349 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4362 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4350 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4363 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4351 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4364 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4352
4365
4353 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4366 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4354 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4367 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4355
4368
4356 @hybrid_property
4369 @hybrid_property
4357 def schedule_type(self):
4370 def schedule_type(self):
4358 return self._schedule_type
4371 return self._schedule_type
4359
4372
4360 @schedule_type.setter
4373 @schedule_type.setter
4361 def schedule_type(self, val):
4374 def schedule_type(self, val):
4362 if val not in self.schedule_types:
4375 if val not in self.schedule_types:
4363 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4376 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4364 val, self.schedule_type))
4377 val, self.schedule_type))
4365
4378
4366 self._schedule_type = val
4379 self._schedule_type = val
4367
4380
4368 @classmethod
4381 @classmethod
4369 def get_uid(cls, obj):
4382 def get_uid(cls, obj):
4370 args = obj.task_args
4383 args = obj.task_args
4371 kwargs = obj.task_kwargs
4384 kwargs = obj.task_kwargs
4372 if isinstance(args, JsonRaw):
4385 if isinstance(args, JsonRaw):
4373 try:
4386 try:
4374 args = json.loads(args)
4387 args = json.loads(args)
4375 except ValueError:
4388 except ValueError:
4376 args = tuple()
4389 args = tuple()
4377
4390
4378 if isinstance(kwargs, JsonRaw):
4391 if isinstance(kwargs, JsonRaw):
4379 try:
4392 try:
4380 kwargs = json.loads(kwargs)
4393 kwargs = json.loads(kwargs)
4381 except ValueError:
4394 except ValueError:
4382 kwargs = dict()
4395 kwargs = dict()
4383
4396
4384 dot_notation = obj.task_dot_notation
4397 dot_notation = obj.task_dot_notation
4385 val = '.'.join(map(safe_str, [
4398 val = '.'.join(map(safe_str, [
4386 sorted(dot_notation), args, sorted(kwargs.items())]))
4399 sorted(dot_notation), args, sorted(kwargs.items())]))
4387 return hashlib.sha1(val).hexdigest()
4400 return hashlib.sha1(val).hexdigest()
4388
4401
4389 @classmethod
4402 @classmethod
4390 def get_by_schedule_name(cls, schedule_name):
4403 def get_by_schedule_name(cls, schedule_name):
4391 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4404 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4392
4405
4393 @classmethod
4406 @classmethod
4394 def get_by_schedule_id(cls, schedule_id):
4407 def get_by_schedule_id(cls, schedule_id):
4395 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4408 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4396
4409
4397 @property
4410 @property
4398 def task(self):
4411 def task(self):
4399 return self.task_dot_notation
4412 return self.task_dot_notation
4400
4413
4401 @property
4414 @property
4402 def schedule(self):
4415 def schedule(self):
4403 from rhodecode.lib.celerylib.utils import raw_2_schedule
4416 from rhodecode.lib.celerylib.utils import raw_2_schedule
4404 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4417 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4405 return schedule
4418 return schedule
4406
4419
4407 @property
4420 @property
4408 def args(self):
4421 def args(self):
4409 try:
4422 try:
4410 return list(self.task_args or [])
4423 return list(self.task_args or [])
4411 except ValueError:
4424 except ValueError:
4412 return list()
4425 return list()
4413
4426
4414 @property
4427 @property
4415 def kwargs(self):
4428 def kwargs(self):
4416 try:
4429 try:
4417 return dict(self.task_kwargs or {})
4430 return dict(self.task_kwargs or {})
4418 except ValueError:
4431 except ValueError:
4419 return dict()
4432 return dict()
4420
4433
4421 def _as_raw(self, val):
4434 def _as_raw(self, val):
4422 if hasattr(val, 'de_coerce'):
4435 if hasattr(val, 'de_coerce'):
4423 val = val.de_coerce()
4436 val = val.de_coerce()
4424 if val:
4437 if val:
4425 val = json.dumps(val)
4438 val = json.dumps(val)
4426
4439
4427 return val
4440 return val
4428
4441
4429 @property
4442 @property
4430 def schedule_definition_raw(self):
4443 def schedule_definition_raw(self):
4431 return self._as_raw(self.schedule_definition)
4444 return self._as_raw(self.schedule_definition)
4432
4445
4433 @property
4446 @property
4434 def args_raw(self):
4447 def args_raw(self):
4435 return self._as_raw(self.task_args)
4448 return self._as_raw(self.task_args)
4436
4449
4437 @property
4450 @property
4438 def kwargs_raw(self):
4451 def kwargs_raw(self):
4439 return self._as_raw(self.task_kwargs)
4452 return self._as_raw(self.task_kwargs)
4440
4453
4441 def __repr__(self):
4454 def __repr__(self):
4442 return '<DB:ScheduleEntry({}:{})>'.format(
4455 return '<DB:ScheduleEntry({}:{})>'.format(
4443 self.schedule_entry_id, self.schedule_name)
4456 self.schedule_entry_id, self.schedule_name)
4444
4457
4445
4458
4446 @event.listens_for(ScheduleEntry, 'before_update')
4459 @event.listens_for(ScheduleEntry, 'before_update')
4447 def update_task_uid(mapper, connection, target):
4460 def update_task_uid(mapper, connection, target):
4448 target.task_uid = ScheduleEntry.get_uid(target)
4461 target.task_uid = ScheduleEntry.get_uid(target)
4449
4462
4450
4463
4451 @event.listens_for(ScheduleEntry, 'before_insert')
4464 @event.listens_for(ScheduleEntry, 'before_insert')
4452 def set_task_uid(mapper, connection, target):
4465 def set_task_uid(mapper, connection, target):
4453 target.task_uid = ScheduleEntry.get_uid(target)
4466 target.task_uid = ScheduleEntry.get_uid(target)
4454
4467
4455
4468
4456 class DbMigrateVersion(Base, BaseModel):
4469 class DbMigrateVersion(Base, BaseModel):
4457 __tablename__ = 'db_migrate_version'
4470 __tablename__ = 'db_migrate_version'
4458 __table_args__ = (
4471 __table_args__ = (
4459 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4472 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4460 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4473 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4461 )
4474 )
4462 repository_id = Column('repository_id', String(250), primary_key=True)
4475 repository_id = Column('repository_id', String(250), primary_key=True)
4463 repository_path = Column('repository_path', Text)
4476 repository_path = Column('repository_path', Text)
4464 version = Column('version', Integer)
4477 version = Column('version', Integer)
4465
4478
4466
4479
4467 class DbSession(Base, BaseModel):
4480 class DbSession(Base, BaseModel):
4468 __tablename__ = 'db_session'
4481 __tablename__ = 'db_session'
4469 __table_args__ = (
4482 __table_args__ = (
4470 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4483 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4471 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4484 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4472 )
4485 )
4473
4486
4474 def __repr__(self):
4487 def __repr__(self):
4475 return '<DB:DbSession({})>'.format(self.id)
4488 return '<DB:DbSession({})>'.format(self.id)
4476
4489
4477 id = Column('id', Integer())
4490 id = Column('id', Integer())
4478 namespace = Column('namespace', String(255), primary_key=True)
4491 namespace = Column('namespace', String(255), primary_key=True)
4479 accessed = Column('accessed', DateTime, nullable=False)
4492 accessed = Column('accessed', DateTime, nullable=False)
4480 created = Column('created', DateTime, nullable=False)
4493 created = Column('created', DateTime, nullable=False)
4481 data = Column('data', PickleType, nullable=False)
4494 data = Column('data', PickleType, nullable=False)
@@ -1,53 +1,72 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Invalidate Cache for Repository')}</h3>
3 <h3 class="panel-title">${_('Invalidate Cache for Repository')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6
6
7 <h4>${_('Manually invalidate the repository cache. On the next access a repository cache will be recreated.')}</h4>
7 <h4>${_('Manually invalidate the repository cache. On the next access a repository cache will be recreated.')}</h4>
8
8
9 <p>
9 <p>
10 ${_('Cache purge can be automated by such api call. Can be called periodically in crontab etc.')}
10 ${_('Cache purge can be automated by such api call. Can be called periodically in crontab etc.')}
11 <br/>
11 <br/>
12 <code>
12 <code>
13 ${h.api_call_example(method='invalidate_cache', args={"repoid": c.rhodecode_db_repo.repo_name})}
13 ${h.api_call_example(method='invalidate_cache', args={"repoid": c.rhodecode_db_repo.repo_name})}
14 </code>
14 </code>
15 </p>
15 </p>
16
16
17 ${h.secure_form(h.route_path('edit_repo_caches', repo_name=c.repo_name), request=request)}
17 ${h.secure_form(h.route_path('edit_repo_caches', repo_name=c.repo_name), request=request)}
18 <div class="form">
18 <div class="form">
19 <div class="fields">
19 <div class="fields">
20 ${h.submit('reset_cache_%s' % c.rhodecode_db_repo.repo_name,_('Invalidate repository cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
20 ${h.submit('reset_cache_%s' % c.rhodecode_db_repo.repo_name,_('Invalidate repository cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
21 </div>
21 </div>
22 </div>
22 </div>
23 ${h.end_form()}
23 ${h.end_form()}
24
24
25 </div>
25 </div>
26 </div>
26 </div>
27
27
28
28
29 <div class="panel panel-default">
29 <div class="panel panel-default">
30 <div class="panel-heading">
30 <div class="panel-heading">
31 <h3 class="panel-title">
31 <h3 class="panel-title">
32 ${(_ungettext('List of repository caches (%(count)s entry)', 'List of repository caches (%(count)s entries)' ,len(c.rhodecode_db_repo.cache_keys)) % {'count': len(c.rhodecode_db_repo.cache_keys)})}
32 ${(_ungettext('List of repository caches (%(count)s entry)', 'List of repository caches (%(count)s entries)' ,len(c.rhodecode_db_repo.cache_keys)) % {'count': len(c.rhodecode_db_repo.cache_keys)})}
33 </h3>
33 </h3>
34 </div>
34 </div>
35 <div class="panel-body">
35 <div class="panel-body">
36 <div class="field" >
36 <div class="field" >
37 <table class="rctable edit_cache">
37 <table class="rctable edit_cache">
38 <tr>
38 <tr>
39 <th>${_('Prefix')}</th>
39 <th>${_('Prefix')}</th>
40 <th>${_('Key')}</th>
40 <th>${_('Key')}</th>
41 <th>${_('Active')}</th>
41 <th>${_('Active')}</th>
42 </tr>
42 </tr>
43 %for cache in c.rhodecode_db_repo.cache_keys:
43 %for cache in c.rhodecode_db_repo.cache_keys:
44 <tr>
44 <tr>
45 <td class="td-prefix">${cache.get_prefix() or '-'}</td>
45 <td class="td-prefix">${cache.get_prefix() or '-'}</td>
46 <td class="td-cachekey">${cache.cache_key}</td>
46 <td class="td-cachekey">${cache.cache_key}</td>
47 <td class="td-active">${h.bool2icon(cache.cache_active)}</td>
47 <td class="td-active">${h.bool2icon(cache.cache_active)}</td>
48 </tr>
48 </tr>
49 %endfor
49 %endfor
50 </table>
50 </table>
51 </div>
51 </div>
52 </div>
52 </div>
53 </div>
53 </div>
54
55
56 <div class="panel panel-default">
57 <div class="panel-heading">
58 <h3 class="panel-title">${_('Diff Caches')}</h3>
59 </div>
60 <div class="panel-body">
61 <table class="rctable edit_cache">
62 <tr>
63 <td>${_('Cached diff files')}:</td>
64 <td>${c.cached_diff_count}</td>
65 </tr>
66 <tr>
67 <td>${_('Cached diff size')}:</td>
68 <td>${h.format_byte_size(c.cached_diff_size)}</td>
69 </tr>
70 </table>
71 </div>
72 </div>
General Comments 0
You need to be logged in to leave comments. Login now