##// END OF EJS Templates
hg: Log original exception when doing the merge with rebase.
johbo -
r156:43455ea4 default
parent child Browse files
Show More
@@ -1,781 +1,785 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2016 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 HG repository module
22 HG repository module
23 """
23 """
24
24
25 import logging
25 import logging
26 import binascii
26 import binascii
27 import os
27 import os
28 import re
28 import re
29 import shutil
29 import shutil
30 import urllib
30 import urllib
31
31
32 from zope.cachedescriptors.property import Lazy as LazyProperty
32 from zope.cachedescriptors.property import Lazy as LazyProperty
33
33
34 from rhodecode.lib.compat import OrderedDict
34 from rhodecode.lib.compat import OrderedDict
35 from rhodecode.lib.datelib import (
35 from rhodecode.lib.datelib import (
36 utcdate_fromtimestamp, makedate, date_astimestamp)
36 utcdate_fromtimestamp, makedate, date_astimestamp)
37 from rhodecode.lib.utils import safe_unicode, safe_str
37 from rhodecode.lib.utils import safe_unicode, safe_str
38 from rhodecode.lib.vcs import connection
38 from rhodecode.lib.vcs import connection
39 from rhodecode.lib.vcs.backends.base import (
39 from rhodecode.lib.vcs.backends.base import (
40 BaseRepository, CollectionGenerator, Config, MergeResponse,
40 BaseRepository, CollectionGenerator, Config, MergeResponse,
41 MergeFailureReason)
41 MergeFailureReason)
42 from rhodecode.lib.vcs.backends.hg.commit import MercurialCommit
42 from rhodecode.lib.vcs.backends.hg.commit import MercurialCommit
43 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
43 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
44 from rhodecode.lib.vcs.backends.hg.inmemory import MercurialInMemoryCommit
44 from rhodecode.lib.vcs.backends.hg.inmemory import MercurialInMemoryCommit
45 from rhodecode.lib.vcs.conf import settings
45 from rhodecode.lib.vcs.conf import settings
46 from rhodecode.lib.vcs.exceptions import (
46 from rhodecode.lib.vcs.exceptions import (
47 EmptyRepositoryError, RepositoryError, TagAlreadyExistError,
47 EmptyRepositoryError, RepositoryError, TagAlreadyExistError,
48 TagDoesNotExistError, CommitDoesNotExistError)
48 TagDoesNotExistError, CommitDoesNotExistError)
49
49
50 hexlify = binascii.hexlify
50 hexlify = binascii.hexlify
51 nullid = "\0" * 20
51 nullid = "\0" * 20
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class MercurialRepository(BaseRepository):
56 class MercurialRepository(BaseRepository):
57 """
57 """
58 Mercurial repository backend
58 Mercurial repository backend
59 """
59 """
60 DEFAULT_BRANCH_NAME = 'default'
60 DEFAULT_BRANCH_NAME = 'default'
61
61
62 def __init__(self, repo_path, config=None, create=False, src_url=None,
62 def __init__(self, repo_path, config=None, create=False, src_url=None,
63 update_after_clone=False, with_wire=None):
63 update_after_clone=False, with_wire=None):
64 """
64 """
65 Raises RepositoryError if repository could not be find at the given
65 Raises RepositoryError if repository could not be find at the given
66 ``repo_path``.
66 ``repo_path``.
67
67
68 :param repo_path: local path of the repository
68 :param repo_path: local path of the repository
69 :param config: config object containing the repo configuration
69 :param config: config object containing the repo configuration
70 :param create=False: if set to True, would try to create repository if
70 :param create=False: if set to True, would try to create repository if
71 it does not exist rather than raising exception
71 it does not exist rather than raising exception
72 :param src_url=None: would try to clone repository from given location
72 :param src_url=None: would try to clone repository from given location
73 :param update_after_clone=False: sets update of working copy after
73 :param update_after_clone=False: sets update of working copy after
74 making a clone
74 making a clone
75 """
75 """
76 self.path = safe_str(os.path.abspath(repo_path))
76 self.path = safe_str(os.path.abspath(repo_path))
77 self.config = config if config else Config()
77 self.config = config if config else Config()
78 self._remote = connection.Hg(
78 self._remote = connection.Hg(
79 self.path, self.config, with_wire=with_wire)
79 self.path, self.config, with_wire=with_wire)
80
80
81 self._init_repo(create, src_url, update_after_clone)
81 self._init_repo(create, src_url, update_after_clone)
82
82
83 # caches
83 # caches
84 self._commit_ids = {}
84 self._commit_ids = {}
85
85
86 @LazyProperty
86 @LazyProperty
87 def commit_ids(self):
87 def commit_ids(self):
88 """
88 """
89 Returns list of commit ids, in ascending order. Being lazy
89 Returns list of commit ids, in ascending order. Being lazy
90 attribute allows external tools to inject shas from cache.
90 attribute allows external tools to inject shas from cache.
91 """
91 """
92 commit_ids = self._get_all_commit_ids()
92 commit_ids = self._get_all_commit_ids()
93 self._rebuild_cache(commit_ids)
93 self._rebuild_cache(commit_ids)
94 return commit_ids
94 return commit_ids
95
95
96 def _rebuild_cache(self, commit_ids):
96 def _rebuild_cache(self, commit_ids):
97 self._commit_ids = dict((commit_id, index)
97 self._commit_ids = dict((commit_id, index)
98 for index, commit_id in enumerate(commit_ids))
98 for index, commit_id in enumerate(commit_ids))
99
99
100 @LazyProperty
100 @LazyProperty
101 def branches(self):
101 def branches(self):
102 return self._get_branches()
102 return self._get_branches()
103
103
104 @LazyProperty
104 @LazyProperty
105 def branches_closed(self):
105 def branches_closed(self):
106 return self._get_branches(active=False, closed=True)
106 return self._get_branches(active=False, closed=True)
107
107
108 @LazyProperty
108 @LazyProperty
109 def branches_all(self):
109 def branches_all(self):
110 all_branches = {}
110 all_branches = {}
111 all_branches.update(self.branches)
111 all_branches.update(self.branches)
112 all_branches.update(self.branches_closed)
112 all_branches.update(self.branches_closed)
113 return all_branches
113 return all_branches
114
114
115 def _get_branches(self, active=True, closed=False):
115 def _get_branches(self, active=True, closed=False):
116 """
116 """
117 Gets branches for this repository
117 Gets branches for this repository
118 Returns only not closed active branches by default
118 Returns only not closed active branches by default
119
119
120 :param active: return also active branches
120 :param active: return also active branches
121 :param closed: return also closed branches
121 :param closed: return also closed branches
122
122
123 """
123 """
124 if self.is_empty():
124 if self.is_empty():
125 return {}
125 return {}
126
126
127 def get_name(ctx):
127 def get_name(ctx):
128 return ctx[0]
128 return ctx[0]
129
129
130 _branches = [(safe_unicode(n), hexlify(h),) for n, h in
130 _branches = [(safe_unicode(n), hexlify(h),) for n, h in
131 self._remote.branches(active, closed).items()]
131 self._remote.branches(active, closed).items()]
132
132
133 return OrderedDict(sorted(_branches, key=get_name, reverse=False))
133 return OrderedDict(sorted(_branches, key=get_name, reverse=False))
134
134
135 @LazyProperty
135 @LazyProperty
136 def tags(self):
136 def tags(self):
137 """
137 """
138 Gets tags for this repository
138 Gets tags for this repository
139 """
139 """
140 return self._get_tags()
140 return self._get_tags()
141
141
142 def _get_tags(self):
142 def _get_tags(self):
143 if self.is_empty():
143 if self.is_empty():
144 return {}
144 return {}
145
145
146 def get_name(ctx):
146 def get_name(ctx):
147 return ctx[0]
147 return ctx[0]
148
148
149 _tags = [(safe_unicode(n), hexlify(h),) for n, h in
149 _tags = [(safe_unicode(n), hexlify(h),) for n, h in
150 self._remote.tags().items()]
150 self._remote.tags().items()]
151
151
152 return OrderedDict(sorted(_tags, key=get_name, reverse=True))
152 return OrderedDict(sorted(_tags, key=get_name, reverse=True))
153
153
154 def tag(self, name, user, commit_id=None, message=None, date=None,
154 def tag(self, name, user, commit_id=None, message=None, date=None,
155 **kwargs):
155 **kwargs):
156 """
156 """
157 Creates and returns a tag for the given ``commit_id``.
157 Creates and returns a tag for the given ``commit_id``.
158
158
159 :param name: name for new tag
159 :param name: name for new tag
160 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
160 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
161 :param commit_id: commit id for which new tag would be created
161 :param commit_id: commit id for which new tag would be created
162 :param message: message of the tag's commit
162 :param message: message of the tag's commit
163 :param date: date of tag's commit
163 :param date: date of tag's commit
164
164
165 :raises TagAlreadyExistError: if tag with same name already exists
165 :raises TagAlreadyExistError: if tag with same name already exists
166 """
166 """
167 if name in self.tags:
167 if name in self.tags:
168 raise TagAlreadyExistError("Tag %s already exists" % name)
168 raise TagAlreadyExistError("Tag %s already exists" % name)
169 commit = self.get_commit(commit_id=commit_id)
169 commit = self.get_commit(commit_id=commit_id)
170 local = kwargs.setdefault('local', False)
170 local = kwargs.setdefault('local', False)
171
171
172 if message is None:
172 if message is None:
173 message = "Added tag %s for commit %s" % (name, commit.short_id)
173 message = "Added tag %s for commit %s" % (name, commit.short_id)
174
174
175 date, tz = date_to_timestamp_plus_offset(date)
175 date, tz = date_to_timestamp_plus_offset(date)
176
176
177 self._remote.tag(
177 self._remote.tag(
178 name, commit.raw_id, message, local, user, date, tz)
178 name, commit.raw_id, message, local, user, date, tz)
179
179
180 # Reinitialize tags
180 # Reinitialize tags
181 self.tags = self._get_tags()
181 self.tags = self._get_tags()
182 tag_id = self.tags[name]
182 tag_id = self.tags[name]
183
183
184 return self.get_commit(commit_id=tag_id)
184 return self.get_commit(commit_id=tag_id)
185
185
186 def remove_tag(self, name, user, message=None, date=None):
186 def remove_tag(self, name, user, message=None, date=None):
187 """
187 """
188 Removes tag with the given `name`.
188 Removes tag with the given `name`.
189
189
190 :param name: name of the tag to be removed
190 :param name: name of the tag to be removed
191 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
191 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
192 :param message: message of the tag's removal commit
192 :param message: message of the tag's removal commit
193 :param date: date of tag's removal commit
193 :param date: date of tag's removal commit
194
194
195 :raises TagDoesNotExistError: if tag with given name does not exists
195 :raises TagDoesNotExistError: if tag with given name does not exists
196 """
196 """
197 if name not in self.tags:
197 if name not in self.tags:
198 raise TagDoesNotExistError("Tag %s does not exist" % name)
198 raise TagDoesNotExistError("Tag %s does not exist" % name)
199 if message is None:
199 if message is None:
200 message = "Removed tag %s" % name
200 message = "Removed tag %s" % name
201 local = False
201 local = False
202
202
203 date, tz = date_to_timestamp_plus_offset(date)
203 date, tz = date_to_timestamp_plus_offset(date)
204
204
205 self._remote.tag(name, nullid, message, local, user, date, tz)
205 self._remote.tag(name, nullid, message, local, user, date, tz)
206 self.tags = self._get_tags()
206 self.tags = self._get_tags()
207
207
208 @LazyProperty
208 @LazyProperty
209 def bookmarks(self):
209 def bookmarks(self):
210 """
210 """
211 Gets bookmarks for this repository
211 Gets bookmarks for this repository
212 """
212 """
213 return self._get_bookmarks()
213 return self._get_bookmarks()
214
214
215 def _get_bookmarks(self):
215 def _get_bookmarks(self):
216 if self.is_empty():
216 if self.is_empty():
217 return {}
217 return {}
218
218
219 def get_name(ctx):
219 def get_name(ctx):
220 return ctx[0]
220 return ctx[0]
221
221
222 _bookmarks = [
222 _bookmarks = [
223 (safe_unicode(n), hexlify(h)) for n, h in
223 (safe_unicode(n), hexlify(h)) for n, h in
224 self._remote.bookmarks().items()]
224 self._remote.bookmarks().items()]
225
225
226 return OrderedDict(sorted(_bookmarks, key=get_name))
226 return OrderedDict(sorted(_bookmarks, key=get_name))
227
227
228 def _get_all_commit_ids(self):
228 def _get_all_commit_ids(self):
229 return self._remote.get_all_commit_ids('visible')
229 return self._remote.get_all_commit_ids('visible')
230
230
231 def get_diff(
231 def get_diff(
232 self, commit1, commit2, path='', ignore_whitespace=False,
232 self, commit1, commit2, path='', ignore_whitespace=False,
233 context=3, path1=None):
233 context=3, path1=None):
234 """
234 """
235 Returns (git like) *diff*, as plain text. Shows changes introduced by
235 Returns (git like) *diff*, as plain text. Shows changes introduced by
236 `commit2` since `commit1`.
236 `commit2` since `commit1`.
237
237
238 :param commit1: Entry point from which diff is shown. Can be
238 :param commit1: Entry point from which diff is shown. Can be
239 ``self.EMPTY_COMMIT`` - in this case, patch showing all
239 ``self.EMPTY_COMMIT`` - in this case, patch showing all
240 the changes since empty state of the repository until `commit2`
240 the changes since empty state of the repository until `commit2`
241 :param commit2: Until which commit changes should be shown.
241 :param commit2: Until which commit changes should be shown.
242 :param ignore_whitespace: If set to ``True``, would not show whitespace
242 :param ignore_whitespace: If set to ``True``, would not show whitespace
243 changes. Defaults to ``False``.
243 changes. Defaults to ``False``.
244 :param context: How many lines before/after changed lines should be
244 :param context: How many lines before/after changed lines should be
245 shown. Defaults to ``3``.
245 shown. Defaults to ``3``.
246 """
246 """
247 self._validate_diff_commits(commit1, commit2)
247 self._validate_diff_commits(commit1, commit2)
248 if path1 is not None and path1 != path:
248 if path1 is not None and path1 != path:
249 raise ValueError("Diff of two different paths not supported.")
249 raise ValueError("Diff of two different paths not supported.")
250
250
251 if path:
251 if path:
252 file_filter = [self.path, path]
252 file_filter = [self.path, path]
253 else:
253 else:
254 file_filter = None
254 file_filter = None
255
255
256 diff = self._remote.diff(
256 diff = self._remote.diff(
257 commit1.raw_id, commit2.raw_id, file_filter=file_filter,
257 commit1.raw_id, commit2.raw_id, file_filter=file_filter,
258 opt_git=True, opt_ignorews=ignore_whitespace,
258 opt_git=True, opt_ignorews=ignore_whitespace,
259 context=context)
259 context=context)
260 return MercurialDiff(diff)
260 return MercurialDiff(diff)
261
261
262 def strip(self, commit_id, branch=None):
262 def strip(self, commit_id, branch=None):
263 self._remote.strip(commit_id, update=False, backup="none")
263 self._remote.strip(commit_id, update=False, backup="none")
264
264
265 self.commit_ids = self._get_all_commit_ids()
265 self.commit_ids = self._get_all_commit_ids()
266 self._rebuild_cache(self.commit_ids)
266 self._rebuild_cache(self.commit_ids)
267
267
268 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
268 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
269 if commit_id1 == commit_id2:
269 if commit_id1 == commit_id2:
270 return commit_id1
270 return commit_id1
271
271
272 ancestors = self._remote.revs_from_revspec(
272 ancestors = self._remote.revs_from_revspec(
273 "ancestor(id(%s), id(%s))", commit_id1, commit_id2,
273 "ancestor(id(%s), id(%s))", commit_id1, commit_id2,
274 other_path=repo2.path)
274 other_path=repo2.path)
275 return repo2[ancestors[0]].raw_id if ancestors else None
275 return repo2[ancestors[0]].raw_id if ancestors else None
276
276
277 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
277 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
278 if commit_id1 == commit_id2:
278 if commit_id1 == commit_id2:
279 commits = []
279 commits = []
280 else:
280 else:
281 if merge:
281 if merge:
282 indexes = self._remote.revs_from_revspec(
282 indexes = self._remote.revs_from_revspec(
283 "ancestors(id(%s)) - ancestors(id(%s)) - id(%s)",
283 "ancestors(id(%s)) - ancestors(id(%s)) - id(%s)",
284 commit_id2, commit_id1, commit_id1, other_path=repo2.path)
284 commit_id2, commit_id1, commit_id1, other_path=repo2.path)
285 else:
285 else:
286 indexes = self._remote.revs_from_revspec(
286 indexes = self._remote.revs_from_revspec(
287 "id(%s)..id(%s) - id(%s)", commit_id1, commit_id2,
287 "id(%s)..id(%s) - id(%s)", commit_id1, commit_id2,
288 commit_id1, other_path=repo2.path)
288 commit_id1, other_path=repo2.path)
289
289
290 commits = [repo2.get_commit(commit_idx=idx, pre_load=pre_load)
290 commits = [repo2.get_commit(commit_idx=idx, pre_load=pre_load)
291 for idx in indexes]
291 for idx in indexes]
292
292
293 return commits
293 return commits
294
294
295 @staticmethod
295 @staticmethod
296 def check_url(url, config):
296 def check_url(url, config):
297 """
297 """
298 Function will check given url and try to verify if it's a valid
298 Function will check given url and try to verify if it's a valid
299 link. Sometimes it may happened that mercurial will issue basic
299 link. Sometimes it may happened that mercurial will issue basic
300 auth request that can cause whole API to hang when used from python
300 auth request that can cause whole API to hang when used from python
301 or other external calls.
301 or other external calls.
302
302
303 On failures it'll raise urllib2.HTTPError, exception is also thrown
303 On failures it'll raise urllib2.HTTPError, exception is also thrown
304 when the return code is non 200
304 when the return code is non 200
305 """
305 """
306 # check first if it's not an local url
306 # check first if it's not an local url
307 if os.path.isdir(url) or url.startswith('file:'):
307 if os.path.isdir(url) or url.startswith('file:'):
308 return True
308 return True
309
309
310 # Request the _remote to verify the url
310 # Request the _remote to verify the url
311 return connection.Hg.check_url(url, config.serialize())
311 return connection.Hg.check_url(url, config.serialize())
312
312
313 @staticmethod
313 @staticmethod
314 def is_valid_repository(path):
314 def is_valid_repository(path):
315 return os.path.isdir(os.path.join(path, '.hg'))
315 return os.path.isdir(os.path.join(path, '.hg'))
316
316
317 def _init_repo(self, create, src_url=None, update_after_clone=False):
317 def _init_repo(self, create, src_url=None, update_after_clone=False):
318 """
318 """
319 Function will check for mercurial repository in given path. If there
319 Function will check for mercurial repository in given path. If there
320 is no repository in that path it will raise an exception unless
320 is no repository in that path it will raise an exception unless
321 `create` parameter is set to True - in that case repository would
321 `create` parameter is set to True - in that case repository would
322 be created.
322 be created.
323
323
324 If `src_url` is given, would try to clone repository from the
324 If `src_url` is given, would try to clone repository from the
325 location at given clone_point. Additionally it'll make update to
325 location at given clone_point. Additionally it'll make update to
326 working copy accordingly to `update_after_clone` flag.
326 working copy accordingly to `update_after_clone` flag.
327 """
327 """
328 if create and os.path.exists(self.path):
328 if create and os.path.exists(self.path):
329 raise RepositoryError(
329 raise RepositoryError(
330 "Cannot create repository at %s, location already exist"
330 "Cannot create repository at %s, location already exist"
331 % self.path)
331 % self.path)
332
332
333 if src_url:
333 if src_url:
334 url = str(self._get_url(src_url))
334 url = str(self._get_url(src_url))
335 MercurialRepository.check_url(url, self.config)
335 MercurialRepository.check_url(url, self.config)
336
336
337 self._remote.clone(url, self.path, update_after_clone)
337 self._remote.clone(url, self.path, update_after_clone)
338
338
339 # Don't try to create if we've already cloned repo
339 # Don't try to create if we've already cloned repo
340 create = False
340 create = False
341
341
342 if create:
342 if create:
343 os.makedirs(self.path, mode=0755)
343 os.makedirs(self.path, mode=0755)
344
344
345 self._remote.localrepository(create)
345 self._remote.localrepository(create)
346
346
347 @LazyProperty
347 @LazyProperty
348 def in_memory_commit(self):
348 def in_memory_commit(self):
349 return MercurialInMemoryCommit(self)
349 return MercurialInMemoryCommit(self)
350
350
351 @LazyProperty
351 @LazyProperty
352 def description(self):
352 def description(self):
353 description = self._remote.get_config_value(
353 description = self._remote.get_config_value(
354 'web', 'description', untrusted=True)
354 'web', 'description', untrusted=True)
355 return safe_unicode(description or self.DEFAULT_DESCRIPTION)
355 return safe_unicode(description or self.DEFAULT_DESCRIPTION)
356
356
357 @LazyProperty
357 @LazyProperty
358 def contact(self):
358 def contact(self):
359 contact = (
359 contact = (
360 self._remote.get_config_value("web", "contact") or
360 self._remote.get_config_value("web", "contact") or
361 self._remote.get_config_value("ui", "username"))
361 self._remote.get_config_value("ui", "username"))
362 return safe_unicode(contact or self.DEFAULT_CONTACT)
362 return safe_unicode(contact or self.DEFAULT_CONTACT)
363
363
364 @LazyProperty
364 @LazyProperty
365 def last_change(self):
365 def last_change(self):
366 """
366 """
367 Returns last change made on this repository as
367 Returns last change made on this repository as
368 `datetime.datetime` object
368 `datetime.datetime` object
369 """
369 """
370 return utcdate_fromtimestamp(self._get_mtime(), makedate()[1])
370 return utcdate_fromtimestamp(self._get_mtime(), makedate()[1])
371
371
372 def _get_mtime(self):
372 def _get_mtime(self):
373 try:
373 try:
374 return date_astimestamp(self.get_commit().date)
374 return date_astimestamp(self.get_commit().date)
375 except RepositoryError:
375 except RepositoryError:
376 # fallback to filesystem
376 # fallback to filesystem
377 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
377 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
378 st_path = os.path.join(self.path, '.hg', "store")
378 st_path = os.path.join(self.path, '.hg', "store")
379 if os.path.exists(cl_path):
379 if os.path.exists(cl_path):
380 return os.stat(cl_path).st_mtime
380 return os.stat(cl_path).st_mtime
381 else:
381 else:
382 return os.stat(st_path).st_mtime
382 return os.stat(st_path).st_mtime
383
383
384 def _sanitize_commit_idx(self, idx):
384 def _sanitize_commit_idx(self, idx):
385 # Note: Mercurial has ``int(-1)`` reserved as not existing id_or_idx
385 # Note: Mercurial has ``int(-1)`` reserved as not existing id_or_idx
386 # number. A `long` is treated in the correct way though. So we convert
386 # number. A `long` is treated in the correct way though. So we convert
387 # `int` to `long` here to make sure it is handled correctly.
387 # `int` to `long` here to make sure it is handled correctly.
388 if isinstance(idx, int):
388 if isinstance(idx, int):
389 return long(idx)
389 return long(idx)
390 return idx
390 return idx
391
391
392 def _get_url(self, url):
392 def _get_url(self, url):
393 """
393 """
394 Returns normalized url. If schema is not given, would fall
394 Returns normalized url. If schema is not given, would fall
395 to filesystem
395 to filesystem
396 (``file:///``) schema.
396 (``file:///``) schema.
397 """
397 """
398 url = url.encode('utf8')
398 url = url.encode('utf8')
399 if url != 'default' and '://' not in url:
399 if url != 'default' and '://' not in url:
400 url = "file:" + urllib.pathname2url(url)
400 url = "file:" + urllib.pathname2url(url)
401 return url
401 return url
402
402
403 def get_hook_location(self):
403 def get_hook_location(self):
404 """
404 """
405 returns absolute path to location where hooks are stored
405 returns absolute path to location where hooks are stored
406 """
406 """
407 return os.path.join(self.path, '.hg', '.hgrc')
407 return os.path.join(self.path, '.hg', '.hgrc')
408
408
409 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
409 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
410 """
410 """
411 Returns ``MercurialCommit`` object representing repository's
411 Returns ``MercurialCommit`` object representing repository's
412 commit at the given `commit_id` or `commit_idx`.
412 commit at the given `commit_id` or `commit_idx`.
413 """
413 """
414 if self.is_empty():
414 if self.is_empty():
415 raise EmptyRepositoryError("There are no commits yet")
415 raise EmptyRepositoryError("There are no commits yet")
416
416
417 if commit_id is not None:
417 if commit_id is not None:
418 self._validate_commit_id(commit_id)
418 self._validate_commit_id(commit_id)
419 try:
419 try:
420 idx = self._commit_ids[commit_id]
420 idx = self._commit_ids[commit_id]
421 return MercurialCommit(self, commit_id, idx, pre_load=pre_load)
421 return MercurialCommit(self, commit_id, idx, pre_load=pre_load)
422 except KeyError:
422 except KeyError:
423 pass
423 pass
424 elif commit_idx is not None:
424 elif commit_idx is not None:
425 self._validate_commit_idx(commit_idx)
425 self._validate_commit_idx(commit_idx)
426 commit_idx = self._sanitize_commit_idx(commit_idx)
426 commit_idx = self._sanitize_commit_idx(commit_idx)
427 try:
427 try:
428 id_ = self.commit_ids[commit_idx]
428 id_ = self.commit_ids[commit_idx]
429 if commit_idx < 0:
429 if commit_idx < 0:
430 commit_idx += len(self.commit_ids)
430 commit_idx += len(self.commit_ids)
431 return MercurialCommit(
431 return MercurialCommit(
432 self, id_, commit_idx, pre_load=pre_load)
432 self, id_, commit_idx, pre_load=pre_load)
433 except IndexError:
433 except IndexError:
434 commit_id = commit_idx
434 commit_id = commit_idx
435 else:
435 else:
436 commit_id = "tip"
436 commit_id = "tip"
437
437
438 # TODO Paris: Ugly hack to "serialize" long for msgpack
438 # TODO Paris: Ugly hack to "serialize" long for msgpack
439 if isinstance(commit_id, long):
439 if isinstance(commit_id, long):
440 commit_id = float(commit_id)
440 commit_id = float(commit_id)
441
441
442 if isinstance(commit_id, unicode):
442 if isinstance(commit_id, unicode):
443 commit_id = safe_str(commit_id)
443 commit_id = safe_str(commit_id)
444
444
445 raw_id, idx = self._remote.lookup(commit_id, both=True)
445 raw_id, idx = self._remote.lookup(commit_id, both=True)
446
446
447 return MercurialCommit(self, raw_id, idx, pre_load=pre_load)
447 return MercurialCommit(self, raw_id, idx, pre_load=pre_load)
448
448
449 def get_commits(
449 def get_commits(
450 self, start_id=None, end_id=None, start_date=None, end_date=None,
450 self, start_id=None, end_id=None, start_date=None, end_date=None,
451 branch_name=None, pre_load=None):
451 branch_name=None, pre_load=None):
452 """
452 """
453 Returns generator of ``MercurialCommit`` objects from start to end
453 Returns generator of ``MercurialCommit`` objects from start to end
454 (both are inclusive)
454 (both are inclusive)
455
455
456 :param start_id: None, str(commit_id)
456 :param start_id: None, str(commit_id)
457 :param end_id: None, str(commit_id)
457 :param end_id: None, str(commit_id)
458 :param start_date: if specified, commits with commit date less than
458 :param start_date: if specified, commits with commit date less than
459 ``start_date`` would be filtered out from returned set
459 ``start_date`` would be filtered out from returned set
460 :param end_date: if specified, commits with commit date greater than
460 :param end_date: if specified, commits with commit date greater than
461 ``end_date`` would be filtered out from returned set
461 ``end_date`` would be filtered out from returned set
462 :param branch_name: if specified, commits not reachable from given
462 :param branch_name: if specified, commits not reachable from given
463 branch would be filtered out from returned set
463 branch would be filtered out from returned set
464
464
465 :raise BranchDoesNotExistError: If given ``branch_name`` does not
465 :raise BranchDoesNotExistError: If given ``branch_name`` does not
466 exist.
466 exist.
467 :raise CommitDoesNotExistError: If commit for given ``start`` or
467 :raise CommitDoesNotExistError: If commit for given ``start`` or
468 ``end`` could not be found.
468 ``end`` could not be found.
469 """
469 """
470 # actually we should check now if it's not an empty repo
470 # actually we should check now if it's not an empty repo
471 branch_ancestors = False
471 branch_ancestors = False
472 if self.is_empty():
472 if self.is_empty():
473 raise EmptyRepositoryError("There are no commits yet")
473 raise EmptyRepositoryError("There are no commits yet")
474 self._validate_branch_name(branch_name)
474 self._validate_branch_name(branch_name)
475
475
476 if start_id is not None:
476 if start_id is not None:
477 self._validate_commit_id(start_id)
477 self._validate_commit_id(start_id)
478 c_start = self.get_commit(commit_id=start_id)
478 c_start = self.get_commit(commit_id=start_id)
479 start_pos = self._commit_ids[c_start.raw_id]
479 start_pos = self._commit_ids[c_start.raw_id]
480 else:
480 else:
481 start_pos = None
481 start_pos = None
482
482
483 if end_id is not None:
483 if end_id is not None:
484 self._validate_commit_id(end_id)
484 self._validate_commit_id(end_id)
485 c_end = self.get_commit(commit_id=end_id)
485 c_end = self.get_commit(commit_id=end_id)
486 end_pos = max(0, self._commit_ids[c_end.raw_id])
486 end_pos = max(0, self._commit_ids[c_end.raw_id])
487 else:
487 else:
488 end_pos = None
488 end_pos = None
489
489
490 if None not in [start_id, end_id] and start_pos > end_pos:
490 if None not in [start_id, end_id] and start_pos > end_pos:
491 raise RepositoryError(
491 raise RepositoryError(
492 "Start commit '%s' cannot be after end commit '%s'" %
492 "Start commit '%s' cannot be after end commit '%s'" %
493 (start_id, end_id))
493 (start_id, end_id))
494
494
495 if end_pos is not None:
495 if end_pos is not None:
496 end_pos += 1
496 end_pos += 1
497
497
498 commit_filter = []
498 commit_filter = []
499 if branch_name and not branch_ancestors:
499 if branch_name and not branch_ancestors:
500 commit_filter.append('branch("%s")' % branch_name)
500 commit_filter.append('branch("%s")' % branch_name)
501 elif branch_name and branch_ancestors:
501 elif branch_name and branch_ancestors:
502 commit_filter.append('ancestors(branch("%s"))' % branch_name)
502 commit_filter.append('ancestors(branch("%s"))' % branch_name)
503 if start_date and not end_date:
503 if start_date and not end_date:
504 commit_filter.append('date(">%s")' % start_date)
504 commit_filter.append('date(">%s")' % start_date)
505 if end_date and not start_date:
505 if end_date and not start_date:
506 commit_filter.append('date("<%s")' % end_date)
506 commit_filter.append('date("<%s")' % end_date)
507 if start_date and end_date:
507 if start_date and end_date:
508 commit_filter.append(
508 commit_filter.append(
509 'date(">%s") and date("<%s")' % (start_date, end_date))
509 'date(">%s") and date("<%s")' % (start_date, end_date))
510
510
511 # TODO: johbo: Figure out a simpler way for this solution
511 # TODO: johbo: Figure out a simpler way for this solution
512 collection_generator = CollectionGenerator
512 collection_generator = CollectionGenerator
513 if commit_filter:
513 if commit_filter:
514 commit_filter = map(safe_str, commit_filter)
514 commit_filter = map(safe_str, commit_filter)
515 revisions = self._remote.rev_range(commit_filter)
515 revisions = self._remote.rev_range(commit_filter)
516 collection_generator = MercurialIndexBasedCollectionGenerator
516 collection_generator = MercurialIndexBasedCollectionGenerator
517 else:
517 else:
518 revisions = self.commit_ids
518 revisions = self.commit_ids
519
519
520 if start_pos or end_pos:
520 if start_pos or end_pos:
521 revisions = revisions[start_pos:end_pos]
521 revisions = revisions[start_pos:end_pos]
522
522
523 return collection_generator(self, revisions, pre_load=pre_load)
523 return collection_generator(self, revisions, pre_load=pre_load)
524
524
525 def pull(self, url, commit_ids=None):
525 def pull(self, url, commit_ids=None):
526 """
526 """
527 Tries to pull changes from external location.
527 Tries to pull changes from external location.
528
528
529 :param commit_ids: Optional. Can be set to a list of commit ids
529 :param commit_ids: Optional. Can be set to a list of commit ids
530 which shall be pulled from the other repository.
530 which shall be pulled from the other repository.
531 """
531 """
532 url = self._get_url(url)
532 url = self._get_url(url)
533 self._remote.pull(url, commit_ids=commit_ids)
533 self._remote.pull(url, commit_ids=commit_ids)
534
534
535 def _local_clone(self, clone_path):
535 def _local_clone(self, clone_path):
536 """
536 """
537 Create a local clone of the current repo.
537 Create a local clone of the current repo.
538 """
538 """
539 self._remote.clone(self.path, clone_path, update_after_clone=True,
539 self._remote.clone(self.path, clone_path, update_after_clone=True,
540 hooks=False)
540 hooks=False)
541
541
542 def _update(self, revision, clean=False):
542 def _update(self, revision, clean=False):
543 """
543 """
544 Update the working copty to the specified revision.
544 Update the working copty to the specified revision.
545 """
545 """
546 self._remote.update(revision, clean=clean)
546 self._remote.update(revision, clean=clean)
547
547
548 def _identify(self):
548 def _identify(self):
549 """
549 """
550 Return the current state of the working directory.
550 Return the current state of the working directory.
551 """
551 """
552 return self._remote.identify().strip().rstrip('+')
552 return self._remote.identify().strip().rstrip('+')
553
553
554 def _heads(self, branch=None):
554 def _heads(self, branch=None):
555 """
555 """
556 Return the commit ids of the repository heads.
556 Return the commit ids of the repository heads.
557 """
557 """
558 return self._remote.heads(branch=branch).strip().split(' ')
558 return self._remote.heads(branch=branch).strip().split(' ')
559
559
560 def _ancestor(self, revision1, revision2):
560 def _ancestor(self, revision1, revision2):
561 """
561 """
562 Return the common ancestor of the two revisions.
562 Return the common ancestor of the two revisions.
563 """
563 """
564 return self._remote.ancestor(
564 return self._remote.ancestor(
565 revision1, revision2).strip().split(':')[-1]
565 revision1, revision2).strip().split(':')[-1]
566
566
567 def _local_push(
567 def _local_push(
568 self, revision, repository_path, push_branches=False,
568 self, revision, repository_path, push_branches=False,
569 enable_hooks=False):
569 enable_hooks=False):
570 """
570 """
571 Push the given revision to the specified repository.
571 Push the given revision to the specified repository.
572
572
573 :param push_branches: allow to create branches in the target repo.
573 :param push_branches: allow to create branches in the target repo.
574 """
574 """
575 self._remote.push(
575 self._remote.push(
576 [revision], repository_path, hooks=enable_hooks,
576 [revision], repository_path, hooks=enable_hooks,
577 push_branches=push_branches)
577 push_branches=push_branches)
578
578
579 def _local_merge(self, target_ref, merge_message, user_name, user_email,
579 def _local_merge(self, target_ref, merge_message, user_name, user_email,
580 source_ref):
580 source_ref):
581 """
581 """
582 Merge the given source_revision into the checked out revision.
582 Merge the given source_revision into the checked out revision.
583
583
584 Returns the commit id of the merge and a boolean indicating if the
584 Returns the commit id of the merge and a boolean indicating if the
585 commit needs to be pushed.
585 commit needs to be pushed.
586 """
586 """
587 self._update(target_ref.commit_id)
587 self._update(target_ref.commit_id)
588
588
589 ancestor = self._ancestor(target_ref.commit_id, source_ref.commit_id)
589 ancestor = self._ancestor(target_ref.commit_id, source_ref.commit_id)
590 is_the_same_branch = self._is_the_same_branch(target_ref, source_ref)
590 is_the_same_branch = self._is_the_same_branch(target_ref, source_ref)
591
591
592 if ancestor == source_ref.commit_id:
592 if ancestor == source_ref.commit_id:
593 # Nothing to do, the changes were already integrated
593 # Nothing to do, the changes were already integrated
594 return target_ref.commit_id, False
594 return target_ref.commit_id, False
595
595
596 elif ancestor == target_ref.commit_id and is_the_same_branch:
596 elif ancestor == target_ref.commit_id and is_the_same_branch:
597 # In this case we should force a commit message
597 # In this case we should force a commit message
598 return source_ref.commit_id, True
598 return source_ref.commit_id, True
599
599
600 if settings.HG_USE_REBASE_FOR_MERGING:
600 if settings.HG_USE_REBASE_FOR_MERGING:
601 try:
601 try:
602 bookmark_name = 'rcbook%s%s' % (source_ref.commit_id,
602 bookmark_name = 'rcbook%s%s' % (source_ref.commit_id,
603 target_ref.commit_id)
603 target_ref.commit_id)
604 self.bookmark(bookmark_name, revision=source_ref.commit_id)
604 self.bookmark(bookmark_name, revision=source_ref.commit_id)
605 self._remote.rebase(
605 self._remote.rebase(
606 source=source_ref.commit_id, dest=target_ref.commit_id)
606 source=source_ref.commit_id, dest=target_ref.commit_id)
607 self._update(bookmark_name)
607 self._update(bookmark_name)
608 return self._identify(), True
608 return self._identify(), True
609 except RepositoryError:
609 except RepositoryError:
610 # The rebase-abort may raise another exception which 'hides'
611 # the original one, therefore we log it here.
612 log.exception('Error while rebasing shadow repo during merge.')
613
610 # Cleanup any rebase leftovers
614 # Cleanup any rebase leftovers
611 self._remote.rebase(abort=True)
615 self._remote.rebase(abort=True)
612 self._remote.update(clean=True)
616 self._remote.update(clean=True)
613 raise
617 raise
614 else:
618 else:
615 try:
619 try:
616 self._remote.merge(source_ref.commit_id)
620 self._remote.merge(source_ref.commit_id)
617 self._remote.commit(
621 self._remote.commit(
618 message=safe_str(merge_message),
622 message=safe_str(merge_message),
619 username=safe_str('%s <%s>' % (user_name, user_email)))
623 username=safe_str('%s <%s>' % (user_name, user_email)))
620 return self._identify(), True
624 return self._identify(), True
621 except RepositoryError:
625 except RepositoryError:
622 # Cleanup any merge leftovers
626 # Cleanup any merge leftovers
623 self._remote.update(clean=True)
627 self._remote.update(clean=True)
624 raise
628 raise
625
629
626 def _is_the_same_branch(self, target_ref, source_ref):
630 def _is_the_same_branch(self, target_ref, source_ref):
627 return (
631 return (
628 self._get_branch_name(target_ref) ==
632 self._get_branch_name(target_ref) ==
629 self._get_branch_name(source_ref))
633 self._get_branch_name(source_ref))
630
634
631 def _get_branch_name(self, ref):
635 def _get_branch_name(self, ref):
632 if ref.type == 'branch':
636 if ref.type == 'branch':
633 return ref.name
637 return ref.name
634 return self._remote.ctx_branch(ref.commit_id)
638 return self._remote.ctx_branch(ref.commit_id)
635
639
636 def _get_shadow_repository_path(self, workspace_id):
640 def _get_shadow_repository_path(self, workspace_id):
637 # The name of the shadow repository must start with '.', so it is
641 # The name of the shadow repository must start with '.', so it is
638 # skipped by 'rhodecode.lib.utils.get_filesystem_repos'.
642 # skipped by 'rhodecode.lib.utils.get_filesystem_repos'.
639 return os.path.join(
643 return os.path.join(
640 os.path.dirname(self.path),
644 os.path.dirname(self.path),
641 '.__shadow_%s_%s' % (os.path.basename(self.path), workspace_id))
645 '.__shadow_%s_%s' % (os.path.basename(self.path), workspace_id))
642
646
643 def _maybe_prepare_merge_workspace(self, workspace_id, unused_target_ref):
647 def _maybe_prepare_merge_workspace(self, workspace_id, unused_target_ref):
644 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
648 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
645 if not os.path.exists(shadow_repository_path):
649 if not os.path.exists(shadow_repository_path):
646 self._local_clone(shadow_repository_path)
650 self._local_clone(shadow_repository_path)
647 log.debug(
651 log.debug(
648 'Prepared shadow repository in %s', shadow_repository_path)
652 'Prepared shadow repository in %s', shadow_repository_path)
649
653
650 return shadow_repository_path
654 return shadow_repository_path
651
655
652 def cleanup_merge_workspace(self, workspace_id):
656 def cleanup_merge_workspace(self, workspace_id):
653 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
657 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
654 shutil.rmtree(shadow_repository_path, ignore_errors=True)
658 shutil.rmtree(shadow_repository_path, ignore_errors=True)
655
659
656 def _merge_repo(self, shadow_repository_path, target_ref,
660 def _merge_repo(self, shadow_repository_path, target_ref,
657 source_repo, source_ref, merge_message,
661 source_repo, source_ref, merge_message,
658 merger_name, merger_email, dry_run=False):
662 merger_name, merger_email, dry_run=False):
659 if target_ref.commit_id not in self._heads():
663 if target_ref.commit_id not in self._heads():
660 return MergeResponse(
664 return MergeResponse(
661 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
665 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
662
666
663 if (target_ref.type == 'branch' and
667 if (target_ref.type == 'branch' and
664 len(self._heads(target_ref.name)) != 1):
668 len(self._heads(target_ref.name)) != 1):
665 return MergeResponse(
669 return MergeResponse(
666 False, False, None,
670 False, False, None,
667 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS)
671 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS)
668
672
669 shadow_repo = self._get_shadow_instance(shadow_repository_path)
673 shadow_repo = self._get_shadow_instance(shadow_repository_path)
670
674
671 log.debug('Pulling in target reference %s', target_ref)
675 log.debug('Pulling in target reference %s', target_ref)
672 self._validate_pull_reference(target_ref)
676 self._validate_pull_reference(target_ref)
673 shadow_repo._local_pull(self.path, target_ref)
677 shadow_repo._local_pull(self.path, target_ref)
674 try:
678 try:
675 log.debug('Pulling in source reference %s', source_ref)
679 log.debug('Pulling in source reference %s', source_ref)
676 source_repo._validate_pull_reference(source_ref)
680 source_repo._validate_pull_reference(source_ref)
677 shadow_repo._local_pull(source_repo.path, source_ref)
681 shadow_repo._local_pull(source_repo.path, source_ref)
678 except CommitDoesNotExistError as e:
682 except CommitDoesNotExistError as e:
679 log.exception('Failure when doing local pull on hg shadow repo')
683 log.exception('Failure when doing local pull on hg shadow repo')
680 return MergeResponse(
684 return MergeResponse(
681 False, False, None, MergeFailureReason.MISSING_COMMIT)
685 False, False, None, MergeFailureReason.MISSING_COMMIT)
682
686
683 merge_commit_id = None
687 merge_commit_id = None
684 merge_failure_reason = MergeFailureReason.NONE
688 merge_failure_reason = MergeFailureReason.NONE
685
689
686 try:
690 try:
687 merge_commit_id, needs_push = shadow_repo._local_merge(
691 merge_commit_id, needs_push = shadow_repo._local_merge(
688 target_ref, merge_message, merger_name, merger_email,
692 target_ref, merge_message, merger_name, merger_email,
689 source_ref)
693 source_ref)
690 merge_possible = True
694 merge_possible = True
691 except RepositoryError as e:
695 except RepositoryError as e:
692 log.exception('Failure when doing local merge on hg shadow repo')
696 log.exception('Failure when doing local merge on hg shadow repo')
693 merge_possible = False
697 merge_possible = False
694 merge_failure_reason = MergeFailureReason.MERGE_FAILED
698 merge_failure_reason = MergeFailureReason.MERGE_FAILED
695
699
696 if merge_possible and not dry_run:
700 if merge_possible and not dry_run:
697 if needs_push:
701 if needs_push:
698 # In case the target is a bookmark, update it, so after pushing
702 # In case the target is a bookmark, update it, so after pushing
699 # the bookmarks is also updated in the target.
703 # the bookmarks is also updated in the target.
700 if target_ref.type == 'book':
704 if target_ref.type == 'book':
701 shadow_repo.bookmark(
705 shadow_repo.bookmark(
702 target_ref.name, revision=merge_commit_id)
706 target_ref.name, revision=merge_commit_id)
703
707
704 try:
708 try:
705 shadow_repo_with_hooks = self._get_shadow_instance(
709 shadow_repo_with_hooks = self._get_shadow_instance(
706 shadow_repository_path,
710 shadow_repository_path,
707 enable_hooks=True)
711 enable_hooks=True)
708 # Note: the push_branches option will push any new branch
712 # Note: the push_branches option will push any new branch
709 # defined in the source repository to the target. This may
713 # defined in the source repository to the target. This may
710 # be dangerous as branches are permanent in Mercurial.
714 # be dangerous as branches are permanent in Mercurial.
711 # This feature was requested in issue #441.
715 # This feature was requested in issue #441.
712 shadow_repo_with_hooks._local_push(
716 shadow_repo_with_hooks._local_push(
713 merge_commit_id, self.path, push_branches=True,
717 merge_commit_id, self.path, push_branches=True,
714 enable_hooks=True)
718 enable_hooks=True)
715 merge_succeeded = True
719 merge_succeeded = True
716 except RepositoryError:
720 except RepositoryError:
717 log.exception(
721 log.exception(
718 'Failure when doing local from the shadow repository '
722 'Failure when doing local push from the shadow '
719 'to the target repository.')
723 'repository to the target repository.')
720 merge_succeeded = False
724 merge_succeeded = False
721 merge_failure_reason = MergeFailureReason.PUSH_FAILED
725 merge_failure_reason = MergeFailureReason.PUSH_FAILED
722 else:
726 else:
723 merge_succeeded = True
727 merge_succeeded = True
724 else:
728 else:
725 merge_succeeded = False
729 merge_succeeded = False
726
730
727 if dry_run:
731 if dry_run:
728 merge_commit_id = None
732 merge_commit_id = None
729
733
730 return MergeResponse(
734 return MergeResponse(
731 merge_possible, merge_succeeded, merge_commit_id,
735 merge_possible, merge_succeeded, merge_commit_id,
732 merge_failure_reason)
736 merge_failure_reason)
733
737
734 def _get_shadow_instance(
738 def _get_shadow_instance(
735 self, shadow_repository_path, enable_hooks=False):
739 self, shadow_repository_path, enable_hooks=False):
736 config = self.config.copy()
740 config = self.config.copy()
737 if not enable_hooks:
741 if not enable_hooks:
738 config.clear_section('hooks')
742 config.clear_section('hooks')
739 return MercurialRepository(shadow_repository_path, config)
743 return MercurialRepository(shadow_repository_path, config)
740
744
741 def _validate_pull_reference(self, reference):
745 def _validate_pull_reference(self, reference):
742 if not (reference.name in self.bookmarks or
746 if not (reference.name in self.bookmarks or
743 reference.name in self.branches or
747 reference.name in self.branches or
744 self.get_commit(reference.commit_id)):
748 self.get_commit(reference.commit_id)):
745 raise CommitDoesNotExistError(
749 raise CommitDoesNotExistError(
746 'Unknown branch, bookmark or commit id')
750 'Unknown branch, bookmark or commit id')
747
751
748 def _local_pull(self, repository_path, reference):
752 def _local_pull(self, repository_path, reference):
749 """
753 """
750 Fetch a branch, bookmark or commit from a local repository.
754 Fetch a branch, bookmark or commit from a local repository.
751 """
755 """
752 repository_path = os.path.abspath(repository_path)
756 repository_path = os.path.abspath(repository_path)
753 if repository_path == self.path:
757 if repository_path == self.path:
754 raise ValueError('Cannot pull from the same repository')
758 raise ValueError('Cannot pull from the same repository')
755
759
756 reference_type_to_option_name = {
760 reference_type_to_option_name = {
757 'book': 'bookmark',
761 'book': 'bookmark',
758 'branch': 'branch',
762 'branch': 'branch',
759 }
763 }
760 option_name = reference_type_to_option_name.get(
764 option_name = reference_type_to_option_name.get(
761 reference.type, 'revision')
765 reference.type, 'revision')
762
766
763 if option_name == 'revision':
767 if option_name == 'revision':
764 ref = reference.commit_id
768 ref = reference.commit_id
765 else:
769 else:
766 ref = reference.name
770 ref = reference.name
767
771
768 options = {option_name: [ref]}
772 options = {option_name: [ref]}
769 self._remote.pull_cmd(repository_path, hooks=False, **options)
773 self._remote.pull_cmd(repository_path, hooks=False, **options)
770
774
771 def bookmark(self, bookmark, revision=None):
775 def bookmark(self, bookmark, revision=None):
772 if isinstance(bookmark, unicode):
776 if isinstance(bookmark, unicode):
773 bookmark = safe_str(bookmark)
777 bookmark = safe_str(bookmark)
774 self._remote.bookmark(bookmark, revision=revision)
778 self._remote.bookmark(bookmark, revision=revision)
775
779
776
780
777 class MercurialIndexBasedCollectionGenerator(CollectionGenerator):
781 class MercurialIndexBasedCollectionGenerator(CollectionGenerator):
778
782
779 def _commit_factory(self, commit_id):
783 def _commit_factory(self, commit_id):
780 return self.repo.get_commit(
784 return self.repo.get_commit(
781 commit_idx=commit_id, pre_load=self.pre_load)
785 commit_idx=commit_id, pre_load=self.pre_load)
@@ -1,3427 +1,3429 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 os
25 import os
26 import sys
26 import sys
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
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.exc import IntegrityError
39 from sqlalchemy.exc import IntegrityError
40 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.declarative import declared_attr
41 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.ext.hybrid import hybrid_property
42 from sqlalchemy.orm import (
42 from sqlalchemy.orm import (
43 relationship, joinedload, class_mapper, validates, aliased)
43 relationship, joinedload, class_mapper, validates, aliased)
44 from sqlalchemy.sql.expression import true
44 from sqlalchemy.sql.expression import true
45 from beaker.cache import cache_region, region_invalidate
45 from beaker.cache import cache_region, region_invalidate
46 from webob.exc import HTTPNotFound
46 from webob.exc import HTTPNotFound
47 from zope.cachedescriptors.property import Lazy as LazyProperty
47 from zope.cachedescriptors.property import Lazy as LazyProperty
48
48
49 from pylons import url
49 from pylons import url
50 from pylons.i18n.translation import lazy_ugettext as _
50 from pylons.i18n.translation import lazy_ugettext as _
51
51
52 from rhodecode.lib.vcs import get_backend
52 from rhodecode.lib.vcs import get_backend
53 from rhodecode.lib.vcs.utils.helpers import get_scm
53 from rhodecode.lib.vcs.utils.helpers import get_scm
54 from rhodecode.lib.vcs.exceptions import VCSError
54 from rhodecode.lib.vcs.exceptions import VCSError
55 from rhodecode.lib.vcs.backends.base import (
55 from rhodecode.lib.vcs.backends.base import (
56 EmptyCommit, Reference, MergeFailureReason)
56 EmptyCommit, Reference, MergeFailureReason)
57 from rhodecode.lib.utils2 import (
57 from rhodecode.lib.utils2 import (
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
60 from rhodecode.lib.ext_json import json
60 from rhodecode.lib.ext_json import json
61 from rhodecode.lib.caching_query import FromCache
61 from rhodecode.lib.caching_query import FromCache
62 from rhodecode.lib.encrypt import AESCipher
62 from rhodecode.lib.encrypt import AESCipher
63
63
64 from rhodecode.model.meta import Base, Session
64 from rhodecode.model.meta import Base, Session
65
65
66 URL_SEP = '/'
66 URL_SEP = '/'
67 log = logging.getLogger(__name__)
67 log = logging.getLogger(__name__)
68
68
69 # =============================================================================
69 # =============================================================================
70 # BASE CLASSES
70 # BASE CLASSES
71 # =============================================================================
71 # =============================================================================
72
72
73 # this is propagated from .ini file beaker.session.secret
73 # this is propagated from .ini file beaker.session.secret
74 # and initialized at environment.py
74 # and initialized at environment.py
75 ENCRYPTION_KEY = None
75 ENCRYPTION_KEY = None
76
76
77 # used to sort permissions by types, '#' used here is not allowed to be in
77 # used to sort permissions by types, '#' used here is not allowed to be in
78 # usernames, and it's very early in sorted string.printable table.
78 # usernames, and it's very early in sorted string.printable table.
79 PERMISSION_TYPE_SORT = {
79 PERMISSION_TYPE_SORT = {
80 'admin': '####',
80 'admin': '####',
81 'write': '###',
81 'write': '###',
82 'read': '##',
82 'read': '##',
83 'none': '#',
83 'none': '#',
84 }
84 }
85
85
86
86
87 def display_sort(obj):
87 def display_sort(obj):
88 """
88 """
89 Sort function used to sort permissions in .permissions() function of
89 Sort function used to sort permissions in .permissions() function of
90 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 Repository, RepoGroup, UserGroup. Also it put the default user in front
91 of all other resources
91 of all other resources
92 """
92 """
93
93
94 if obj.username == User.DEFAULT_USER:
94 if obj.username == User.DEFAULT_USER:
95 return '#####'
95 return '#####'
96 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
97 return prefix + obj.username
97 return prefix + obj.username
98
98
99
99
100 def _hash_key(k):
100 def _hash_key(k):
101 return md5_safe(k)
101 return md5_safe(k)
102
102
103
103
104 class EncryptedTextValue(TypeDecorator):
104 class EncryptedTextValue(TypeDecorator):
105 """
105 """
106 Special column for encrypted long text data, use like::
106 Special column for encrypted long text data, use like::
107
107
108 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108 value = Column("encrypted_value", EncryptedValue(), nullable=False)
109
109
110 This column is intelligent so if value is in unencrypted form it return
110 This column is intelligent so if value is in unencrypted form it return
111 unencrypted form, but on save it always encrypts
111 unencrypted form, but on save it always encrypts
112 """
112 """
113 impl = Text
113 impl = Text
114
114
115 def process_bind_param(self, value, dialect):
115 def process_bind_param(self, value, dialect):
116 if not value:
116 if not value:
117 return value
117 return value
118 if value.startswith('enc$aes$'):
118 if value.startswith('enc$aes$'):
119 # protect against double encrypting if someone manually starts
119 # protect against double encrypting if someone manually starts
120 # doing
120 # doing
121 raise ValueError('value needs to be in unencrypted format, ie. '
121 raise ValueError('value needs to be in unencrypted format, ie. '
122 'not starting with enc$aes$')
122 'not starting with enc$aes$')
123 return 'enc$aes$%s' % AESCipher(ENCRYPTION_KEY).encrypt(value)
123 return 'enc$aes$%s' % AESCipher(ENCRYPTION_KEY).encrypt(value)
124
124
125 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
126 if not value:
126 if not value:
127 return value
127 return value
128
128
129 parts = value.split('$', 3)
129 parts = value.split('$', 3)
130 if not len(parts) == 3:
130 if not len(parts) == 3:
131 # probably not encrypted values
131 # probably not encrypted values
132 return value
132 return value
133 else:
133 else:
134 if parts[0] != 'enc':
134 if parts[0] != 'enc':
135 # parts ok but without our header ?
135 # parts ok but without our header ?
136 return value
136 return value
137
137
138 # at that stage we know it's our encryption
138 # at that stage we know it's our encryption
139 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
139 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
140 return decrypted_data
140 return decrypted_data
141
141
142
142
143 class BaseModel(object):
143 class BaseModel(object):
144 """
144 """
145 Base Model for all classes
145 Base Model for all classes
146 """
146 """
147
147
148 @classmethod
148 @classmethod
149 def _get_keys(cls):
149 def _get_keys(cls):
150 """return column names for this model """
150 """return column names for this model """
151 return class_mapper(cls).c.keys()
151 return class_mapper(cls).c.keys()
152
152
153 def get_dict(self):
153 def get_dict(self):
154 """
154 """
155 return dict with keys and values corresponding
155 return dict with keys and values corresponding
156 to this model data """
156 to this model data """
157
157
158 d = {}
158 d = {}
159 for k in self._get_keys():
159 for k in self._get_keys():
160 d[k] = getattr(self, k)
160 d[k] = getattr(self, k)
161
161
162 # also use __json__() if present to get additional fields
162 # also use __json__() if present to get additional fields
163 _json_attr = getattr(self, '__json__', None)
163 _json_attr = getattr(self, '__json__', None)
164 if _json_attr:
164 if _json_attr:
165 # update with attributes from __json__
165 # update with attributes from __json__
166 if callable(_json_attr):
166 if callable(_json_attr):
167 _json_attr = _json_attr()
167 _json_attr = _json_attr()
168 for k, val in _json_attr.iteritems():
168 for k, val in _json_attr.iteritems():
169 d[k] = val
169 d[k] = val
170 return d
170 return d
171
171
172 def get_appstruct(self):
172 def get_appstruct(self):
173 """return list with keys and values tuples corresponding
173 """return list with keys and values tuples corresponding
174 to this model data """
174 to this model data """
175
175
176 l = []
176 l = []
177 for k in self._get_keys():
177 for k in self._get_keys():
178 l.append((k, getattr(self, k),))
178 l.append((k, getattr(self, k),))
179 return l
179 return l
180
180
181 def populate_obj(self, populate_dict):
181 def populate_obj(self, populate_dict):
182 """populate model with data from given populate_dict"""
182 """populate model with data from given populate_dict"""
183
183
184 for k in self._get_keys():
184 for k in self._get_keys():
185 if k in populate_dict:
185 if k in populate_dict:
186 setattr(self, k, populate_dict[k])
186 setattr(self, k, populate_dict[k])
187
187
188 @classmethod
188 @classmethod
189 def query(cls):
189 def query(cls):
190 return Session().query(cls)
190 return Session().query(cls)
191
191
192 @classmethod
192 @classmethod
193 def get(cls, id_):
193 def get(cls, id_):
194 if id_:
194 if id_:
195 return cls.query().get(id_)
195 return cls.query().get(id_)
196
196
197 @classmethod
197 @classmethod
198 def get_or_404(cls, id_):
198 def get_or_404(cls, id_):
199 try:
199 try:
200 id_ = int(id_)
200 id_ = int(id_)
201 except (TypeError, ValueError):
201 except (TypeError, ValueError):
202 raise HTTPNotFound
202 raise HTTPNotFound
203
203
204 res = cls.query().get(id_)
204 res = cls.query().get(id_)
205 if not res:
205 if not res:
206 raise HTTPNotFound
206 raise HTTPNotFound
207 return res
207 return res
208
208
209 @classmethod
209 @classmethod
210 def getAll(cls):
210 def getAll(cls):
211 # deprecated and left for backward compatibility
211 # deprecated and left for backward compatibility
212 return cls.get_all()
212 return cls.get_all()
213
213
214 @classmethod
214 @classmethod
215 def get_all(cls):
215 def get_all(cls):
216 return cls.query().all()
216 return cls.query().all()
217
217
218 @classmethod
218 @classmethod
219 def delete(cls, id_):
219 def delete(cls, id_):
220 obj = cls.query().get(id_)
220 obj = cls.query().get(id_)
221 Session().delete(obj)
221 Session().delete(obj)
222
222
223 def __repr__(self):
223 def __repr__(self):
224 if hasattr(self, '__unicode__'):
224 if hasattr(self, '__unicode__'):
225 # python repr needs to return str
225 # python repr needs to return str
226 try:
226 try:
227 return safe_str(self.__unicode__())
227 return safe_str(self.__unicode__())
228 except UnicodeDecodeError:
228 except UnicodeDecodeError:
229 pass
229 pass
230 return '<DB:%s>' % (self.__class__.__name__)
230 return '<DB:%s>' % (self.__class__.__name__)
231
231
232
232
233 class RhodeCodeSetting(Base, BaseModel):
233 class RhodeCodeSetting(Base, BaseModel):
234 __tablename__ = 'rhodecode_settings'
234 __tablename__ = 'rhodecode_settings'
235 __table_args__ = (
235 __table_args__ = (
236 UniqueConstraint('app_settings_name'),
236 UniqueConstraint('app_settings_name'),
237 {'extend_existing': True, 'mysql_engine': 'InnoDB',
237 {'extend_existing': True, 'mysql_engine': 'InnoDB',
238 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
238 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
239 )
239 )
240
240
241 SETTINGS_TYPES = {
241 SETTINGS_TYPES = {
242 'str': safe_str,
242 'str': safe_str,
243 'int': safe_int,
243 'int': safe_int,
244 'unicode': safe_unicode,
244 'unicode': safe_unicode,
245 'bool': str2bool,
245 'bool': str2bool,
246 'list': functools.partial(aslist, sep=',')
246 'list': functools.partial(aslist, sep=',')
247 }
247 }
248 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
248 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
249 GLOBAL_CONF_KEY = 'app_settings'
249 GLOBAL_CONF_KEY = 'app_settings'
250
250
251 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
251 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
252 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
252 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
253 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
253 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
254 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
254 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
255
255
256 def __init__(self, key='', val='', type='unicode'):
256 def __init__(self, key='', val='', type='unicode'):
257 self.app_settings_name = key
257 self.app_settings_name = key
258 self.app_settings_type = type
258 self.app_settings_type = type
259 self.app_settings_value = val
259 self.app_settings_value = val
260
260
261 @validates('_app_settings_value')
261 @validates('_app_settings_value')
262 def validate_settings_value(self, key, val):
262 def validate_settings_value(self, key, val):
263 assert type(val) == unicode
263 assert type(val) == unicode
264 return val
264 return val
265
265
266 @hybrid_property
266 @hybrid_property
267 def app_settings_value(self):
267 def app_settings_value(self):
268 v = self._app_settings_value
268 v = self._app_settings_value
269 _type = self.app_settings_type
269 _type = self.app_settings_type
270 if _type:
270 if _type:
271 _type = self.app_settings_type.split('.')[0]
271 _type = self.app_settings_type.split('.')[0]
272 # decode the encrypted value
272 # decode the encrypted value
273 if 'encrypted' in self.app_settings_type:
273 if 'encrypted' in self.app_settings_type:
274 cipher = EncryptedTextValue()
274 cipher = EncryptedTextValue()
275 v = safe_unicode(cipher.process_result_value(v, None))
275 v = safe_unicode(cipher.process_result_value(v, None))
276
276
277 converter = self.SETTINGS_TYPES.get(_type) or \
277 converter = self.SETTINGS_TYPES.get(_type) or \
278 self.SETTINGS_TYPES['unicode']
278 self.SETTINGS_TYPES['unicode']
279 return converter(v)
279 return converter(v)
280
280
281 @app_settings_value.setter
281 @app_settings_value.setter
282 def app_settings_value(self, val):
282 def app_settings_value(self, val):
283 """
283 """
284 Setter that will always make sure we use unicode in app_settings_value
284 Setter that will always make sure we use unicode in app_settings_value
285
285
286 :param val:
286 :param val:
287 """
287 """
288 val = safe_unicode(val)
288 val = safe_unicode(val)
289 # encode the encrypted value
289 # encode the encrypted value
290 if 'encrypted' in self.app_settings_type:
290 if 'encrypted' in self.app_settings_type:
291 cipher = EncryptedTextValue()
291 cipher = EncryptedTextValue()
292 val = safe_unicode(cipher.process_bind_param(val, None))
292 val = safe_unicode(cipher.process_bind_param(val, None))
293 self._app_settings_value = val
293 self._app_settings_value = val
294
294
295 @hybrid_property
295 @hybrid_property
296 def app_settings_type(self):
296 def app_settings_type(self):
297 return self._app_settings_type
297 return self._app_settings_type
298
298
299 @app_settings_type.setter
299 @app_settings_type.setter
300 def app_settings_type(self, val):
300 def app_settings_type(self, val):
301 if val.split('.')[0] not in self.SETTINGS_TYPES:
301 if val.split('.')[0] not in self.SETTINGS_TYPES:
302 raise Exception('type must be one of %s got %s'
302 raise Exception('type must be one of %s got %s'
303 % (self.SETTINGS_TYPES.keys(), val))
303 % (self.SETTINGS_TYPES.keys(), val))
304 self._app_settings_type = val
304 self._app_settings_type = val
305
305
306 def __unicode__(self):
306 def __unicode__(self):
307 return u"<%s('%s:%s[%s]')>" % (
307 return u"<%s('%s:%s[%s]')>" % (
308 self.__class__.__name__,
308 self.__class__.__name__,
309 self.app_settings_name, self.app_settings_value,
309 self.app_settings_name, self.app_settings_value,
310 self.app_settings_type
310 self.app_settings_type
311 )
311 )
312
312
313
313
314 class RhodeCodeUi(Base, BaseModel):
314 class RhodeCodeUi(Base, BaseModel):
315 __tablename__ = 'rhodecode_ui'
315 __tablename__ = 'rhodecode_ui'
316 __table_args__ = (
316 __table_args__ = (
317 UniqueConstraint('ui_key'),
317 UniqueConstraint('ui_key'),
318 {'extend_existing': True, 'mysql_engine': 'InnoDB',
318 {'extend_existing': True, 'mysql_engine': 'InnoDB',
319 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
319 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
320 )
320 )
321
321
322 HOOK_REPO_SIZE = 'changegroup.repo_size'
322 HOOK_REPO_SIZE = 'changegroup.repo_size'
323 # HG
323 # HG
324 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
324 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
325 HOOK_PULL = 'outgoing.pull_logger'
325 HOOK_PULL = 'outgoing.pull_logger'
326 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
326 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
327 HOOK_PUSH = 'changegroup.push_logger'
327 HOOK_PUSH = 'changegroup.push_logger'
328
328
329 # TODO: johbo: Unify way how hooks are configured for git and hg,
329 # TODO: johbo: Unify way how hooks are configured for git and hg,
330 # git part is currently hardcoded.
330 # git part is currently hardcoded.
331
331
332 # SVN PATTERNS
332 # SVN PATTERNS
333 SVN_BRANCH_ID = 'vcs_svn_branch'
333 SVN_BRANCH_ID = 'vcs_svn_branch'
334 SVN_TAG_ID = 'vcs_svn_tag'
334 SVN_TAG_ID = 'vcs_svn_tag'
335
335
336 ui_id = Column(
336 ui_id = Column(
337 "ui_id", Integer(), nullable=False, unique=True, default=None,
337 "ui_id", Integer(), nullable=False, unique=True, default=None,
338 primary_key=True)
338 primary_key=True)
339 ui_section = Column(
339 ui_section = Column(
340 "ui_section", String(255), nullable=True, unique=None, default=None)
340 "ui_section", String(255), nullable=True, unique=None, default=None)
341 ui_key = Column(
341 ui_key = Column(
342 "ui_key", String(255), nullable=True, unique=None, default=None)
342 "ui_key", String(255), nullable=True, unique=None, default=None)
343 ui_value = Column(
343 ui_value = Column(
344 "ui_value", String(255), nullable=True, unique=None, default=None)
344 "ui_value", String(255), nullable=True, unique=None, default=None)
345 ui_active = Column(
345 ui_active = Column(
346 "ui_active", Boolean(), nullable=True, unique=None, default=True)
346 "ui_active", Boolean(), nullable=True, unique=None, default=True)
347
347
348 def __repr__(self):
348 def __repr__(self):
349 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
349 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
350 self.ui_key, self.ui_value)
350 self.ui_key, self.ui_value)
351
351
352
352
353 class RepoRhodeCodeSetting(Base, BaseModel):
353 class RepoRhodeCodeSetting(Base, BaseModel):
354 __tablename__ = 'repo_rhodecode_settings'
354 __tablename__ = 'repo_rhodecode_settings'
355 __table_args__ = (
355 __table_args__ = (
356 UniqueConstraint(
356 UniqueConstraint(
357 'app_settings_name', 'repository_id',
357 'app_settings_name', 'repository_id',
358 name='uq_repo_rhodecode_setting_name_repo_id'),
358 name='uq_repo_rhodecode_setting_name_repo_id'),
359 {'extend_existing': True, 'mysql_engine': 'InnoDB',
359 {'extend_existing': True, 'mysql_engine': 'InnoDB',
360 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
360 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
361 )
361 )
362
362
363 repository_id = Column(
363 repository_id = Column(
364 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
364 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
365 nullable=False)
365 nullable=False)
366 app_settings_id = Column(
366 app_settings_id = Column(
367 "app_settings_id", Integer(), nullable=False, unique=True,
367 "app_settings_id", Integer(), nullable=False, unique=True,
368 default=None, primary_key=True)
368 default=None, primary_key=True)
369 app_settings_name = Column(
369 app_settings_name = Column(
370 "app_settings_name", String(255), nullable=True, unique=None,
370 "app_settings_name", String(255), nullable=True, unique=None,
371 default=None)
371 default=None)
372 _app_settings_value = Column(
372 _app_settings_value = Column(
373 "app_settings_value", String(4096), nullable=True, unique=None,
373 "app_settings_value", String(4096), nullable=True, unique=None,
374 default=None)
374 default=None)
375 _app_settings_type = Column(
375 _app_settings_type = Column(
376 "app_settings_type", String(255), nullable=True, unique=None,
376 "app_settings_type", String(255), nullable=True, unique=None,
377 default=None)
377 default=None)
378
378
379 repository = relationship('Repository')
379 repository = relationship('Repository')
380
380
381 def __init__(self, repository_id, key='', val='', type='unicode'):
381 def __init__(self, repository_id, key='', val='', type='unicode'):
382 self.repository_id = repository_id
382 self.repository_id = repository_id
383 self.app_settings_name = key
383 self.app_settings_name = key
384 self.app_settings_type = type
384 self.app_settings_type = type
385 self.app_settings_value = val
385 self.app_settings_value = val
386
386
387 @validates('_app_settings_value')
387 @validates('_app_settings_value')
388 def validate_settings_value(self, key, val):
388 def validate_settings_value(self, key, val):
389 assert type(val) == unicode
389 assert type(val) == unicode
390 return val
390 return val
391
391
392 @hybrid_property
392 @hybrid_property
393 def app_settings_value(self):
393 def app_settings_value(self):
394 v = self._app_settings_value
394 v = self._app_settings_value
395 type_ = self.app_settings_type
395 type_ = self.app_settings_type
396 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
396 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
397 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
397 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
398 return converter(v)
398 return converter(v)
399
399
400 @app_settings_value.setter
400 @app_settings_value.setter
401 def app_settings_value(self, val):
401 def app_settings_value(self, val):
402 """
402 """
403 Setter that will always make sure we use unicode in app_settings_value
403 Setter that will always make sure we use unicode in app_settings_value
404
404
405 :param val:
405 :param val:
406 """
406 """
407 self._app_settings_value = safe_unicode(val)
407 self._app_settings_value = safe_unicode(val)
408
408
409 @hybrid_property
409 @hybrid_property
410 def app_settings_type(self):
410 def app_settings_type(self):
411 return self._app_settings_type
411 return self._app_settings_type
412
412
413 @app_settings_type.setter
413 @app_settings_type.setter
414 def app_settings_type(self, val):
414 def app_settings_type(self, val):
415 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
415 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
416 if val not in SETTINGS_TYPES:
416 if val not in SETTINGS_TYPES:
417 raise Exception('type must be one of %s got %s'
417 raise Exception('type must be one of %s got %s'
418 % (SETTINGS_TYPES.keys(), val))
418 % (SETTINGS_TYPES.keys(), val))
419 self._app_settings_type = val
419 self._app_settings_type = val
420
420
421 def __unicode__(self):
421 def __unicode__(self):
422 return u"<%s('%s:%s:%s[%s]')>" % (
422 return u"<%s('%s:%s:%s[%s]')>" % (
423 self.__class__.__name__, self.repository.repo_name,
423 self.__class__.__name__, self.repository.repo_name,
424 self.app_settings_name, self.app_settings_value,
424 self.app_settings_name, self.app_settings_value,
425 self.app_settings_type
425 self.app_settings_type
426 )
426 )
427
427
428
428
429 class RepoRhodeCodeUi(Base, BaseModel):
429 class RepoRhodeCodeUi(Base, BaseModel):
430 __tablename__ = 'repo_rhodecode_ui'
430 __tablename__ = 'repo_rhodecode_ui'
431 __table_args__ = (
431 __table_args__ = (
432 UniqueConstraint(
432 UniqueConstraint(
433 'repository_id', 'ui_section', 'ui_key',
433 'repository_id', 'ui_section', 'ui_key',
434 name='uq_repo_rhodecode_ui_repository_id_section_key'),
434 name='uq_repo_rhodecode_ui_repository_id_section_key'),
435 {'extend_existing': True, 'mysql_engine': 'InnoDB',
435 {'extend_existing': True, 'mysql_engine': 'InnoDB',
436 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
436 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
437 )
437 )
438
438
439 repository_id = Column(
439 repository_id = Column(
440 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
440 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
441 nullable=False)
441 nullable=False)
442 ui_id = Column(
442 ui_id = Column(
443 "ui_id", Integer(), nullable=False, unique=True, default=None,
443 "ui_id", Integer(), nullable=False, unique=True, default=None,
444 primary_key=True)
444 primary_key=True)
445 ui_section = Column(
445 ui_section = Column(
446 "ui_section", String(255), nullable=True, unique=None, default=None)
446 "ui_section", String(255), nullable=True, unique=None, default=None)
447 ui_key = Column(
447 ui_key = Column(
448 "ui_key", String(255), nullable=True, unique=None, default=None)
448 "ui_key", String(255), nullable=True, unique=None, default=None)
449 ui_value = Column(
449 ui_value = Column(
450 "ui_value", String(255), nullable=True, unique=None, default=None)
450 "ui_value", String(255), nullable=True, unique=None, default=None)
451 ui_active = Column(
451 ui_active = Column(
452 "ui_active", Boolean(), nullable=True, unique=None, default=True)
452 "ui_active", Boolean(), nullable=True, unique=None, default=True)
453
453
454 repository = relationship('Repository')
454 repository = relationship('Repository')
455
455
456 def __repr__(self):
456 def __repr__(self):
457 return '<%s[%s:%s]%s=>%s]>' % (
457 return '<%s[%s:%s]%s=>%s]>' % (
458 self.__class__.__name__, self.repository.repo_name,
458 self.__class__.__name__, self.repository.repo_name,
459 self.ui_section, self.ui_key, self.ui_value)
459 self.ui_section, self.ui_key, self.ui_value)
460
460
461
461
462 class User(Base, BaseModel):
462 class User(Base, BaseModel):
463 __tablename__ = 'users'
463 __tablename__ = 'users'
464 __table_args__ = (
464 __table_args__ = (
465 UniqueConstraint('username'), UniqueConstraint('email'),
465 UniqueConstraint('username'), UniqueConstraint('email'),
466 Index('u_username_idx', 'username'),
466 Index('u_username_idx', 'username'),
467 Index('u_email_idx', 'email'),
467 Index('u_email_idx', 'email'),
468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
470 )
470 )
471 DEFAULT_USER = 'default'
471 DEFAULT_USER = 'default'
472 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
472 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
473 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
473 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
474
474
475 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
475 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
476 username = Column("username", String(255), nullable=True, unique=None, default=None)
476 username = Column("username", String(255), nullable=True, unique=None, default=None)
477 password = Column("password", String(255), nullable=True, unique=None, default=None)
477 password = Column("password", String(255), nullable=True, unique=None, default=None)
478 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
478 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
479 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
479 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
480 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
480 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
481 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
481 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
482 _email = Column("email", String(255), nullable=True, unique=None, default=None)
482 _email = Column("email", String(255), nullable=True, unique=None, default=None)
483 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
483 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
484 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
484 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
485 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
485 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
486 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
486 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
487 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
487 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
488 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
488 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
489 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
489 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
490
490
491 user_log = relationship('UserLog')
491 user_log = relationship('UserLog')
492 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
492 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
493
493
494 repositories = relationship('Repository')
494 repositories = relationship('Repository')
495 repository_groups = relationship('RepoGroup')
495 repository_groups = relationship('RepoGroup')
496 user_groups = relationship('UserGroup')
496 user_groups = relationship('UserGroup')
497
497
498 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
498 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
499 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
499 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
500
500
501 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
501 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
502 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
502 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
503 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
503 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
504
504
505 group_member = relationship('UserGroupMember', cascade='all')
505 group_member = relationship('UserGroupMember', cascade='all')
506
506
507 notifications = relationship('UserNotification', cascade='all')
507 notifications = relationship('UserNotification', cascade='all')
508 # notifications assigned to this user
508 # notifications assigned to this user
509 user_created_notifications = relationship('Notification', cascade='all')
509 user_created_notifications = relationship('Notification', cascade='all')
510 # comments created by this user
510 # comments created by this user
511 user_comments = relationship('ChangesetComment', cascade='all')
511 user_comments = relationship('ChangesetComment', cascade='all')
512 # user profile extra info
512 # user profile extra info
513 user_emails = relationship('UserEmailMap', cascade='all')
513 user_emails = relationship('UserEmailMap', cascade='all')
514 user_ip_map = relationship('UserIpMap', cascade='all')
514 user_ip_map = relationship('UserIpMap', cascade='all')
515 user_auth_tokens = relationship('UserApiKeys', cascade='all')
515 user_auth_tokens = relationship('UserApiKeys', cascade='all')
516 # gists
516 # gists
517 user_gists = relationship('Gist', cascade='all')
517 user_gists = relationship('Gist', cascade='all')
518 # user pull requests
518 # user pull requests
519 user_pull_requests = relationship('PullRequest', cascade='all')
519 user_pull_requests = relationship('PullRequest', cascade='all')
520 # external identities
520 # external identities
521 extenal_identities = relationship(
521 extenal_identities = relationship(
522 'ExternalIdentity',
522 'ExternalIdentity',
523 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
523 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
524 cascade='all')
524 cascade='all')
525
525
526 def __unicode__(self):
526 def __unicode__(self):
527 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
527 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
528 self.user_id, self.username)
528 self.user_id, self.username)
529
529
530 @hybrid_property
530 @hybrid_property
531 def email(self):
531 def email(self):
532 return self._email
532 return self._email
533
533
534 @email.setter
534 @email.setter
535 def email(self, val):
535 def email(self, val):
536 self._email = val.lower() if val else None
536 self._email = val.lower() if val else None
537
537
538 @property
538 @property
539 def firstname(self):
539 def firstname(self):
540 # alias for future
540 # alias for future
541 return self.name
541 return self.name
542
542
543 @property
543 @property
544 def emails(self):
544 def emails(self):
545 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
545 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
546 return [self.email] + [x.email for x in other]
546 return [self.email] + [x.email for x in other]
547
547
548 @property
548 @property
549 def auth_tokens(self):
549 def auth_tokens(self):
550 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
550 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
551
551
552 @property
552 @property
553 def extra_auth_tokens(self):
553 def extra_auth_tokens(self):
554 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
554 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
555
555
556 @property
556 @property
557 def feed_token(self):
557 def feed_token(self):
558 feed_tokens = UserApiKeys.query()\
558 feed_tokens = UserApiKeys.query()\
559 .filter(UserApiKeys.user == self)\
559 .filter(UserApiKeys.user == self)\
560 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
560 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
561 .all()
561 .all()
562 if feed_tokens:
562 if feed_tokens:
563 return feed_tokens[0].api_key
563 return feed_tokens[0].api_key
564 else:
564 else:
565 # use the main token so we don't end up with nothing...
565 # use the main token so we don't end up with nothing...
566 return self.api_key
566 return self.api_key
567
567
568 @classmethod
568 @classmethod
569 def extra_valid_auth_tokens(cls, user, role=None):
569 def extra_valid_auth_tokens(cls, user, role=None):
570 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
570 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
571 .filter(or_(UserApiKeys.expires == -1,
571 .filter(or_(UserApiKeys.expires == -1,
572 UserApiKeys.expires >= time.time()))
572 UserApiKeys.expires >= time.time()))
573 if role:
573 if role:
574 tokens = tokens.filter(or_(UserApiKeys.role == role,
574 tokens = tokens.filter(or_(UserApiKeys.role == role,
575 UserApiKeys.role == UserApiKeys.ROLE_ALL))
575 UserApiKeys.role == UserApiKeys.ROLE_ALL))
576 return tokens.all()
576 return tokens.all()
577
577
578 @property
578 @property
579 def ip_addresses(self):
579 def ip_addresses(self):
580 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
580 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
581 return [x.ip_addr for x in ret]
581 return [x.ip_addr for x in ret]
582
582
583 @property
583 @property
584 def username_and_name(self):
584 def username_and_name(self):
585 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
585 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
586
586
587 @property
587 @property
588 def username_or_name_or_email(self):
588 def username_or_name_or_email(self):
589 full_name = self.full_name if self.full_name is not ' ' else None
589 full_name = self.full_name if self.full_name is not ' ' else None
590 return self.username or full_name or self.email
590 return self.username or full_name or self.email
591
591
592 @property
592 @property
593 def full_name(self):
593 def full_name(self):
594 return '%s %s' % (self.firstname, self.lastname)
594 return '%s %s' % (self.firstname, self.lastname)
595
595
596 @property
596 @property
597 def full_name_or_username(self):
597 def full_name_or_username(self):
598 return ('%s %s' % (self.firstname, self.lastname)
598 return ('%s %s' % (self.firstname, self.lastname)
599 if (self.firstname and self.lastname) else self.username)
599 if (self.firstname and self.lastname) else self.username)
600
600
601 @property
601 @property
602 def full_contact(self):
602 def full_contact(self):
603 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
603 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
604
604
605 @property
605 @property
606 def short_contact(self):
606 def short_contact(self):
607 return '%s %s' % (self.firstname, self.lastname)
607 return '%s %s' % (self.firstname, self.lastname)
608
608
609 @property
609 @property
610 def is_admin(self):
610 def is_admin(self):
611 return self.admin
611 return self.admin
612
612
613 @property
613 @property
614 def AuthUser(self):
614 def AuthUser(self):
615 """
615 """
616 Returns instance of AuthUser for this user
616 Returns instance of AuthUser for this user
617 """
617 """
618 from rhodecode.lib.auth import AuthUser
618 from rhodecode.lib.auth import AuthUser
619 return AuthUser(user_id=self.user_id, api_key=self.api_key,
619 return AuthUser(user_id=self.user_id, api_key=self.api_key,
620 username=self.username)
620 username=self.username)
621
621
622 @hybrid_property
622 @hybrid_property
623 def user_data(self):
623 def user_data(self):
624 if not self._user_data:
624 if not self._user_data:
625 return {}
625 return {}
626
626
627 try:
627 try:
628 return json.loads(self._user_data)
628 return json.loads(self._user_data)
629 except TypeError:
629 except TypeError:
630 return {}
630 return {}
631
631
632 @user_data.setter
632 @user_data.setter
633 def user_data(self, val):
633 def user_data(self, val):
634 if not isinstance(val, dict):
634 if not isinstance(val, dict):
635 raise Exception('user_data must be dict, got %s' % type(val))
635 raise Exception('user_data must be dict, got %s' % type(val))
636 try:
636 try:
637 self._user_data = json.dumps(val)
637 self._user_data = json.dumps(val)
638 except Exception:
638 except Exception:
639 log.error(traceback.format_exc())
639 log.error(traceback.format_exc())
640
640
641 @classmethod
641 @classmethod
642 def get_by_username(cls, username, case_insensitive=False, cache=False):
642 def get_by_username(cls, username, case_insensitive=False, cache=False):
643 if case_insensitive:
643 if case_insensitive:
644 q = cls.query().filter(func.lower(cls.username) == func.lower(username))
644 q = cls.query().filter(func.lower(cls.username) == func.lower(username))
645 else:
645 else:
646 q = cls.query().filter(cls.username == username)
646 q = cls.query().filter(cls.username == username)
647
647
648 if cache:
648 if cache:
649 q = q.options(FromCache(
649 q = q.options(FromCache(
650 "sql_cache_short",
650 "sql_cache_short",
651 "get_user_%s" % _hash_key(username)))
651 "get_user_%s" % _hash_key(username)))
652 return q.scalar()
652 return q.scalar()
653
653
654 @classmethod
654 @classmethod
655 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
655 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
656 q = cls.query().filter(cls.api_key == auth_token)
656 q = cls.query().filter(cls.api_key == auth_token)
657
657
658 if cache:
658 if cache:
659 q = q.options(FromCache("sql_cache_short",
659 q = q.options(FromCache("sql_cache_short",
660 "get_auth_token_%s" % auth_token))
660 "get_auth_token_%s" % auth_token))
661 res = q.scalar()
661 res = q.scalar()
662
662
663 if fallback and not res:
663 if fallback and not res:
664 #fallback to additional keys
664 #fallback to additional keys
665 _res = UserApiKeys.query()\
665 _res = UserApiKeys.query()\
666 .filter(UserApiKeys.api_key == auth_token)\
666 .filter(UserApiKeys.api_key == auth_token)\
667 .filter(or_(UserApiKeys.expires == -1,
667 .filter(or_(UserApiKeys.expires == -1,
668 UserApiKeys.expires >= time.time()))\
668 UserApiKeys.expires >= time.time()))\
669 .first()
669 .first()
670 if _res:
670 if _res:
671 res = _res.user
671 res = _res.user
672 return res
672 return res
673
673
674 @classmethod
674 @classmethod
675 def get_by_email(cls, email, case_insensitive=False, cache=False):
675 def get_by_email(cls, email, case_insensitive=False, cache=False):
676
676
677 if case_insensitive:
677 if case_insensitive:
678 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
678 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
679
679
680 else:
680 else:
681 q = cls.query().filter(cls.email == email)
681 q = cls.query().filter(cls.email == email)
682
682
683 if cache:
683 if cache:
684 q = q.options(FromCache("sql_cache_short",
684 q = q.options(FromCache("sql_cache_short",
685 "get_email_key_%s" % email))
685 "get_email_key_%s" % email))
686
686
687 ret = q.scalar()
687 ret = q.scalar()
688 if ret is None:
688 if ret is None:
689 q = UserEmailMap.query()
689 q = UserEmailMap.query()
690 # try fetching in alternate email map
690 # try fetching in alternate email map
691 if case_insensitive:
691 if case_insensitive:
692 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
692 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
693 else:
693 else:
694 q = q.filter(UserEmailMap.email == email)
694 q = q.filter(UserEmailMap.email == email)
695 q = q.options(joinedload(UserEmailMap.user))
695 q = q.options(joinedload(UserEmailMap.user))
696 if cache:
696 if cache:
697 q = q.options(FromCache("sql_cache_short",
697 q = q.options(FromCache("sql_cache_short",
698 "get_email_map_key_%s" % email))
698 "get_email_map_key_%s" % email))
699 ret = getattr(q.scalar(), 'user', None)
699 ret = getattr(q.scalar(), 'user', None)
700
700
701 return ret
701 return ret
702
702
703 @classmethod
703 @classmethod
704 def get_from_cs_author(cls, author):
704 def get_from_cs_author(cls, author):
705 """
705 """
706 Tries to get User objects out of commit author string
706 Tries to get User objects out of commit author string
707
707
708 :param author:
708 :param author:
709 """
709 """
710 from rhodecode.lib.helpers import email, author_name
710 from rhodecode.lib.helpers import email, author_name
711 # Valid email in the attribute passed, see if they're in the system
711 # Valid email in the attribute passed, see if they're in the system
712 _email = email(author)
712 _email = email(author)
713 if _email:
713 if _email:
714 user = cls.get_by_email(_email, case_insensitive=True)
714 user = cls.get_by_email(_email, case_insensitive=True)
715 if user:
715 if user:
716 return user
716 return user
717 # Maybe we can match by username?
717 # Maybe we can match by username?
718 _author = author_name(author)
718 _author = author_name(author)
719 user = cls.get_by_username(_author, case_insensitive=True)
719 user = cls.get_by_username(_author, case_insensitive=True)
720 if user:
720 if user:
721 return user
721 return user
722
722
723 def update_userdata(self, **kwargs):
723 def update_userdata(self, **kwargs):
724 usr = self
724 usr = self
725 old = usr.user_data
725 old = usr.user_data
726 old.update(**kwargs)
726 old.update(**kwargs)
727 usr.user_data = old
727 usr.user_data = old
728 Session().add(usr)
728 Session().add(usr)
729 log.debug('updated userdata with ', kwargs)
729 log.debug('updated userdata with ', kwargs)
730
730
731 def update_lastlogin(self):
731 def update_lastlogin(self):
732 """Update user lastlogin"""
732 """Update user lastlogin"""
733 self.last_login = datetime.datetime.now()
733 self.last_login = datetime.datetime.now()
734 Session().add(self)
734 Session().add(self)
735 log.debug('updated user %s lastlogin', self.username)
735 log.debug('updated user %s lastlogin', self.username)
736
736
737 def update_lastactivity(self):
737 def update_lastactivity(self):
738 """Update user lastactivity"""
738 """Update user lastactivity"""
739 usr = self
739 usr = self
740 old = usr.user_data
740 old = usr.user_data
741 old.update({'last_activity': time.time()})
741 old.update({'last_activity': time.time()})
742 usr.user_data = old
742 usr.user_data = old
743 Session().add(usr)
743 Session().add(usr)
744 log.debug('updated user %s lastactivity', usr.username)
744 log.debug('updated user %s lastactivity', usr.username)
745
745
746 def update_password(self, new_password, change_api_key=False):
746 def update_password(self, new_password, change_api_key=False):
747 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
747 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
748
748
749 self.password = get_crypt_password(new_password)
749 self.password = get_crypt_password(new_password)
750 if change_api_key:
750 if change_api_key:
751 self.api_key = generate_auth_token(self.username)
751 self.api_key = generate_auth_token(self.username)
752 Session().add(self)
752 Session().add(self)
753
753
754 @classmethod
754 @classmethod
755 def get_first_admin(cls):
755 def get_first_admin(cls):
756 user = User.query().filter(User.admin == True).first()
756 user = User.query().filter(User.admin == True).first()
757 if user is None:
757 if user is None:
758 raise Exception('Missing administrative account!')
758 raise Exception('Missing administrative account!')
759 return user
759 return user
760
760
761 @classmethod
761 @classmethod
762 def get_all_super_admins(cls):
762 def get_all_super_admins(cls):
763 """
763 """
764 Returns all admin accounts sorted by username
764 Returns all admin accounts sorted by username
765 """
765 """
766 return User.query().filter(User.admin == true())\
766 return User.query().filter(User.admin == true())\
767 .order_by(User.username.asc()).all()
767 .order_by(User.username.asc()).all()
768
768
769 @classmethod
769 @classmethod
770 def get_default_user(cls, cache=False):
770 def get_default_user(cls, cache=False):
771 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
771 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
772 if user is None:
772 if user is None:
773 raise Exception('Missing default account!')
773 raise Exception('Missing default account!')
774 return user
774 return user
775
775
776 def _get_default_perms(self, user, suffix=''):
776 def _get_default_perms(self, user, suffix=''):
777 from rhodecode.model.permission import PermissionModel
777 from rhodecode.model.permission import PermissionModel
778 return PermissionModel().get_default_perms(user.user_perms, suffix)
778 return PermissionModel().get_default_perms(user.user_perms, suffix)
779
779
780 def get_default_perms(self, suffix=''):
780 def get_default_perms(self, suffix=''):
781 return self._get_default_perms(self, suffix)
781 return self._get_default_perms(self, suffix)
782
782
783 def get_api_data(self, include_secrets=False, details='full'):
783 def get_api_data(self, include_secrets=False, details='full'):
784 """
784 """
785 Common function for generating user related data for API
785 Common function for generating user related data for API
786
786
787 :param include_secrets: By default secrets in the API data will be replaced
787 :param include_secrets: By default secrets in the API data will be replaced
788 by a placeholder value to prevent exposing this data by accident. In case
788 by a placeholder value to prevent exposing this data by accident. In case
789 this data shall be exposed, set this flag to ``True``.
789 this data shall be exposed, set this flag to ``True``.
790
790
791 :param details: details can be 'basic|full' basic gives only a subset of
791 :param details: details can be 'basic|full' basic gives only a subset of
792 the available user information that includes user_id, name and emails.
792 the available user information that includes user_id, name and emails.
793 """
793 """
794 user = self
794 user = self
795 user_data = self.user_data
795 user_data = self.user_data
796 data = {
796 data = {
797 'user_id': user.user_id,
797 'user_id': user.user_id,
798 'username': user.username,
798 'username': user.username,
799 'firstname': user.name,
799 'firstname': user.name,
800 'lastname': user.lastname,
800 'lastname': user.lastname,
801 'email': user.email,
801 'email': user.email,
802 'emails': user.emails,
802 'emails': user.emails,
803 }
803 }
804 if details == 'basic':
804 if details == 'basic':
805 return data
805 return data
806
806
807 api_key_length = 40
807 api_key_length = 40
808 api_key_replacement = '*' * api_key_length
808 api_key_replacement = '*' * api_key_length
809
809
810 extras = {
810 extras = {
811 'api_key': api_key_replacement,
811 'api_key': api_key_replacement,
812 'api_keys': [api_key_replacement],
812 'api_keys': [api_key_replacement],
813 'active': user.active,
813 'active': user.active,
814 'admin': user.admin,
814 'admin': user.admin,
815 'extern_type': user.extern_type,
815 'extern_type': user.extern_type,
816 'extern_name': user.extern_name,
816 'extern_name': user.extern_name,
817 'last_login': user.last_login,
817 'last_login': user.last_login,
818 'ip_addresses': user.ip_addresses,
818 'ip_addresses': user.ip_addresses,
819 'language': user_data.get('language')
819 'language': user_data.get('language')
820 }
820 }
821 data.update(extras)
821 data.update(extras)
822
822
823 if include_secrets:
823 if include_secrets:
824 data['api_key'] = user.api_key
824 data['api_key'] = user.api_key
825 data['api_keys'] = user.auth_tokens
825 data['api_keys'] = user.auth_tokens
826 return data
826 return data
827
827
828 def __json__(self):
828 def __json__(self):
829 data = {
829 data = {
830 'full_name': self.full_name,
830 'full_name': self.full_name,
831 'full_name_or_username': self.full_name_or_username,
831 'full_name_or_username': self.full_name_or_username,
832 'short_contact': self.short_contact,
832 'short_contact': self.short_contact,
833 'full_contact': self.full_contact,
833 'full_contact': self.full_contact,
834 }
834 }
835 data.update(self.get_api_data())
835 data.update(self.get_api_data())
836 return data
836 return data
837
837
838
838
839 class UserApiKeys(Base, BaseModel):
839 class UserApiKeys(Base, BaseModel):
840 __tablename__ = 'user_api_keys'
840 __tablename__ = 'user_api_keys'
841 __table_args__ = (
841 __table_args__ = (
842 Index('uak_api_key_idx', 'api_key'),
842 Index('uak_api_key_idx', 'api_key'),
843 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
843 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
844 UniqueConstraint('api_key'),
844 UniqueConstraint('api_key'),
845 {'extend_existing': True, 'mysql_engine': 'InnoDB',
845 {'extend_existing': True, 'mysql_engine': 'InnoDB',
846 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
846 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
847 )
847 )
848 __mapper_args__ = {}
848 __mapper_args__ = {}
849
849
850 # ApiKey role
850 # ApiKey role
851 ROLE_ALL = 'token_role_all'
851 ROLE_ALL = 'token_role_all'
852 ROLE_HTTP = 'token_role_http'
852 ROLE_HTTP = 'token_role_http'
853 ROLE_VCS = 'token_role_vcs'
853 ROLE_VCS = 'token_role_vcs'
854 ROLE_API = 'token_role_api'
854 ROLE_API = 'token_role_api'
855 ROLE_FEED = 'token_role_feed'
855 ROLE_FEED = 'token_role_feed'
856 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
856 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
857
857
858 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
858 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
859 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
859 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
860 api_key = Column("api_key", String(255), nullable=False, unique=True)
860 api_key = Column("api_key", String(255), nullable=False, unique=True)
861 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
861 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
862 expires = Column('expires', Float(53), nullable=False)
862 expires = Column('expires', Float(53), nullable=False)
863 role = Column('role', String(255), nullable=True)
863 role = Column('role', String(255), nullable=True)
864 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
864 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
865
865
866 user = relationship('User', lazy='joined')
866 user = relationship('User', lazy='joined')
867
867
868 @classmethod
868 @classmethod
869 def _get_role_name(cls, role):
869 def _get_role_name(cls, role):
870 return {
870 return {
871 cls.ROLE_ALL: _('all'),
871 cls.ROLE_ALL: _('all'),
872 cls.ROLE_HTTP: _('http/web interface'),
872 cls.ROLE_HTTP: _('http/web interface'),
873 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
873 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
874 cls.ROLE_API: _('api calls'),
874 cls.ROLE_API: _('api calls'),
875 cls.ROLE_FEED: _('feed access'),
875 cls.ROLE_FEED: _('feed access'),
876 }.get(role, role)
876 }.get(role, role)
877
877
878 @property
878 @property
879 def expired(self):
879 def expired(self):
880 if self.expires == -1:
880 if self.expires == -1:
881 return False
881 return False
882 return time.time() > self.expires
882 return time.time() > self.expires
883
883
884 @property
884 @property
885 def role_humanized(self):
885 def role_humanized(self):
886 return self._get_role_name(self.role)
886 return self._get_role_name(self.role)
887
887
888
888
889 class UserEmailMap(Base, BaseModel):
889 class UserEmailMap(Base, BaseModel):
890 __tablename__ = 'user_email_map'
890 __tablename__ = 'user_email_map'
891 __table_args__ = (
891 __table_args__ = (
892 Index('uem_email_idx', 'email'),
892 Index('uem_email_idx', 'email'),
893 UniqueConstraint('email'),
893 UniqueConstraint('email'),
894 {'extend_existing': True, 'mysql_engine': 'InnoDB',
894 {'extend_existing': True, 'mysql_engine': 'InnoDB',
895 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
895 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
896 )
896 )
897 __mapper_args__ = {}
897 __mapper_args__ = {}
898
898
899 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
899 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
900 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
900 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
901 _email = Column("email", String(255), nullable=True, unique=False, default=None)
901 _email = Column("email", String(255), nullable=True, unique=False, default=None)
902 user = relationship('User', lazy='joined')
902 user = relationship('User', lazy='joined')
903
903
904 @validates('_email')
904 @validates('_email')
905 def validate_email(self, key, email):
905 def validate_email(self, key, email):
906 # check if this email is not main one
906 # check if this email is not main one
907 main_email = Session().query(User).filter(User.email == email).scalar()
907 main_email = Session().query(User).filter(User.email == email).scalar()
908 if main_email is not None:
908 if main_email is not None:
909 raise AttributeError('email %s is present is user table' % email)
909 raise AttributeError('email %s is present is user table' % email)
910 return email
910 return email
911
911
912 @hybrid_property
912 @hybrid_property
913 def email(self):
913 def email(self):
914 return self._email
914 return self._email
915
915
916 @email.setter
916 @email.setter
917 def email(self, val):
917 def email(self, val):
918 self._email = val.lower() if val else None
918 self._email = val.lower() if val else None
919
919
920
920
921 class UserIpMap(Base, BaseModel):
921 class UserIpMap(Base, BaseModel):
922 __tablename__ = 'user_ip_map'
922 __tablename__ = 'user_ip_map'
923 __table_args__ = (
923 __table_args__ = (
924 UniqueConstraint('user_id', 'ip_addr'),
924 UniqueConstraint('user_id', 'ip_addr'),
925 {'extend_existing': True, 'mysql_engine': 'InnoDB',
925 {'extend_existing': True, 'mysql_engine': 'InnoDB',
926 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
926 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
927 )
927 )
928 __mapper_args__ = {}
928 __mapper_args__ = {}
929
929
930 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
930 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
931 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
931 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
932 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
932 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
933 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
933 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
934 description = Column("description", String(10000), nullable=True, unique=None, default=None)
934 description = Column("description", String(10000), nullable=True, unique=None, default=None)
935 user = relationship('User', lazy='joined')
935 user = relationship('User', lazy='joined')
936
936
937 @classmethod
937 @classmethod
938 def _get_ip_range(cls, ip_addr):
938 def _get_ip_range(cls, ip_addr):
939 net = ipaddress.ip_network(ip_addr, strict=False)
939 net = ipaddress.ip_network(ip_addr, strict=False)
940 return [str(net.network_address), str(net.broadcast_address)]
940 return [str(net.network_address), str(net.broadcast_address)]
941
941
942 def __json__(self):
942 def __json__(self):
943 return {
943 return {
944 'ip_addr': self.ip_addr,
944 'ip_addr': self.ip_addr,
945 'ip_range': self._get_ip_range(self.ip_addr),
945 'ip_range': self._get_ip_range(self.ip_addr),
946 }
946 }
947
947
948 def __unicode__(self):
948 def __unicode__(self):
949 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
949 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
950 self.user_id, self.ip_addr)
950 self.user_id, self.ip_addr)
951
951
952 class UserLog(Base, BaseModel):
952 class UserLog(Base, BaseModel):
953 __tablename__ = 'user_logs'
953 __tablename__ = 'user_logs'
954 __table_args__ = (
954 __table_args__ = (
955 {'extend_existing': True, 'mysql_engine': 'InnoDB',
955 {'extend_existing': True, 'mysql_engine': 'InnoDB',
956 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
956 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
957 )
957 )
958 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
958 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
959 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
959 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
960 username = Column("username", String(255), nullable=True, unique=None, default=None)
960 username = Column("username", String(255), nullable=True, unique=None, default=None)
961 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
961 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
962 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
962 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
963 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
963 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
964 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
964 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
965 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
965 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
966
966
967 def __unicode__(self):
967 def __unicode__(self):
968 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
968 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
969 self.repository_name,
969 self.repository_name,
970 self.action)
970 self.action)
971
971
972 @property
972 @property
973 def action_as_day(self):
973 def action_as_day(self):
974 return datetime.date(*self.action_date.timetuple()[:3])
974 return datetime.date(*self.action_date.timetuple()[:3])
975
975
976 user = relationship('User')
976 user = relationship('User')
977 repository = relationship('Repository', cascade='')
977 repository = relationship('Repository', cascade='')
978
978
979
979
980 class UserGroup(Base, BaseModel):
980 class UserGroup(Base, BaseModel):
981 __tablename__ = 'users_groups'
981 __tablename__ = 'users_groups'
982 __table_args__ = (
982 __table_args__ = (
983 {'extend_existing': True, 'mysql_engine': 'InnoDB',
983 {'extend_existing': True, 'mysql_engine': 'InnoDB',
984 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
984 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
985 )
985 )
986
986
987 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
987 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
988 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
988 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
989 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
989 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
990 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
990 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
991 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
991 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
992 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
992 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
993 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
993 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
994 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
994 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
995
995
996 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
996 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
997 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
997 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
998 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
998 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
999 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
999 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1000 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1000 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1001 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1001 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1002
1002
1003 user = relationship('User')
1003 user = relationship('User')
1004
1004
1005 @hybrid_property
1005 @hybrid_property
1006 def group_data(self):
1006 def group_data(self):
1007 if not self._group_data:
1007 if not self._group_data:
1008 return {}
1008 return {}
1009
1009
1010 try:
1010 try:
1011 return json.loads(self._group_data)
1011 return json.loads(self._group_data)
1012 except TypeError:
1012 except TypeError:
1013 return {}
1013 return {}
1014
1014
1015 @group_data.setter
1015 @group_data.setter
1016 def group_data(self, val):
1016 def group_data(self, val):
1017 try:
1017 try:
1018 self._group_data = json.dumps(val)
1018 self._group_data = json.dumps(val)
1019 except Exception:
1019 except Exception:
1020 log.error(traceback.format_exc())
1020 log.error(traceback.format_exc())
1021
1021
1022 def __unicode__(self):
1022 def __unicode__(self):
1023 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1023 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1024 self.users_group_id,
1024 self.users_group_id,
1025 self.users_group_name)
1025 self.users_group_name)
1026
1026
1027 @classmethod
1027 @classmethod
1028 def get_by_group_name(cls, group_name, cache=False,
1028 def get_by_group_name(cls, group_name, cache=False,
1029 case_insensitive=False):
1029 case_insensitive=False):
1030 if case_insensitive:
1030 if case_insensitive:
1031 q = cls.query().filter(func.lower(cls.users_group_name) ==
1031 q = cls.query().filter(func.lower(cls.users_group_name) ==
1032 func.lower(group_name))
1032 func.lower(group_name))
1033
1033
1034 else:
1034 else:
1035 q = cls.query().filter(cls.users_group_name == group_name)
1035 q = cls.query().filter(cls.users_group_name == group_name)
1036 if cache:
1036 if cache:
1037 q = q.options(FromCache(
1037 q = q.options(FromCache(
1038 "sql_cache_short",
1038 "sql_cache_short",
1039 "get_group_%s" % _hash_key(group_name)))
1039 "get_group_%s" % _hash_key(group_name)))
1040 return q.scalar()
1040 return q.scalar()
1041
1041
1042 @classmethod
1042 @classmethod
1043 def get(cls, user_group_id, cache=False):
1043 def get(cls, user_group_id, cache=False):
1044 user_group = cls.query()
1044 user_group = cls.query()
1045 if cache:
1045 if cache:
1046 user_group = user_group.options(FromCache("sql_cache_short",
1046 user_group = user_group.options(FromCache("sql_cache_short",
1047 "get_users_group_%s" % user_group_id))
1047 "get_users_group_%s" % user_group_id))
1048 return user_group.get(user_group_id)
1048 return user_group.get(user_group_id)
1049
1049
1050 def permissions(self, with_admins=True, with_owner=True):
1050 def permissions(self, with_admins=True, with_owner=True):
1051 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1051 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1052 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1052 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1053 joinedload(UserUserGroupToPerm.user),
1053 joinedload(UserUserGroupToPerm.user),
1054 joinedload(UserUserGroupToPerm.permission),)
1054 joinedload(UserUserGroupToPerm.permission),)
1055
1055
1056 # get owners and admins and permissions. We do a trick of re-writing
1056 # get owners and admins and permissions. We do a trick of re-writing
1057 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1057 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1058 # has a global reference and changing one object propagates to all
1058 # has a global reference and changing one object propagates to all
1059 # others. This means if admin is also an owner admin_row that change
1059 # others. This means if admin is also an owner admin_row that change
1060 # would propagate to both objects
1060 # would propagate to both objects
1061 perm_rows = []
1061 perm_rows = []
1062 for _usr in q.all():
1062 for _usr in q.all():
1063 usr = AttributeDict(_usr.user.get_dict())
1063 usr = AttributeDict(_usr.user.get_dict())
1064 usr.permission = _usr.permission.permission_name
1064 usr.permission = _usr.permission.permission_name
1065 perm_rows.append(usr)
1065 perm_rows.append(usr)
1066
1066
1067 # filter the perm rows by 'default' first and then sort them by
1067 # filter the perm rows by 'default' first and then sort them by
1068 # admin,write,read,none permissions sorted again alphabetically in
1068 # admin,write,read,none permissions sorted again alphabetically in
1069 # each group
1069 # each group
1070 perm_rows = sorted(perm_rows, key=display_sort)
1070 perm_rows = sorted(perm_rows, key=display_sort)
1071
1071
1072 _admin_perm = 'usergroup.admin'
1072 _admin_perm = 'usergroup.admin'
1073 owner_row = []
1073 owner_row = []
1074 if with_owner:
1074 if with_owner:
1075 usr = AttributeDict(self.user.get_dict())
1075 usr = AttributeDict(self.user.get_dict())
1076 usr.owner_row = True
1076 usr.owner_row = True
1077 usr.permission = _admin_perm
1077 usr.permission = _admin_perm
1078 owner_row.append(usr)
1078 owner_row.append(usr)
1079
1079
1080 super_admin_rows = []
1080 super_admin_rows = []
1081 if with_admins:
1081 if with_admins:
1082 for usr in User.get_all_super_admins():
1082 for usr in User.get_all_super_admins():
1083 # if this admin is also owner, don't double the record
1083 # if this admin is also owner, don't double the record
1084 if usr.user_id == owner_row[0].user_id:
1084 if usr.user_id == owner_row[0].user_id:
1085 owner_row[0].admin_row = True
1085 owner_row[0].admin_row = True
1086 else:
1086 else:
1087 usr = AttributeDict(usr.get_dict())
1087 usr = AttributeDict(usr.get_dict())
1088 usr.admin_row = True
1088 usr.admin_row = True
1089 usr.permission = _admin_perm
1089 usr.permission = _admin_perm
1090 super_admin_rows.append(usr)
1090 super_admin_rows.append(usr)
1091
1091
1092 return super_admin_rows + owner_row + perm_rows
1092 return super_admin_rows + owner_row + perm_rows
1093
1093
1094 def permission_user_groups(self):
1094 def permission_user_groups(self):
1095 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1095 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1096 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1096 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1097 joinedload(UserGroupUserGroupToPerm.target_user_group),
1097 joinedload(UserGroupUserGroupToPerm.target_user_group),
1098 joinedload(UserGroupUserGroupToPerm.permission),)
1098 joinedload(UserGroupUserGroupToPerm.permission),)
1099
1099
1100 perm_rows = []
1100 perm_rows = []
1101 for _user_group in q.all():
1101 for _user_group in q.all():
1102 usr = AttributeDict(_user_group.user_group.get_dict())
1102 usr = AttributeDict(_user_group.user_group.get_dict())
1103 usr.permission = _user_group.permission.permission_name
1103 usr.permission = _user_group.permission.permission_name
1104 perm_rows.append(usr)
1104 perm_rows.append(usr)
1105
1105
1106 return perm_rows
1106 return perm_rows
1107
1107
1108 def _get_default_perms(self, user_group, suffix=''):
1108 def _get_default_perms(self, user_group, suffix=''):
1109 from rhodecode.model.permission import PermissionModel
1109 from rhodecode.model.permission import PermissionModel
1110 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1110 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1111
1111
1112 def get_default_perms(self, suffix=''):
1112 def get_default_perms(self, suffix=''):
1113 return self._get_default_perms(self, suffix)
1113 return self._get_default_perms(self, suffix)
1114
1114
1115 def get_api_data(self, with_group_members=True, include_secrets=False):
1115 def get_api_data(self, with_group_members=True, include_secrets=False):
1116 """
1116 """
1117 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1117 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1118 basically forwarded.
1118 basically forwarded.
1119
1119
1120 """
1120 """
1121 user_group = self
1121 user_group = self
1122
1122
1123 data = {
1123 data = {
1124 'users_group_id': user_group.users_group_id,
1124 'users_group_id': user_group.users_group_id,
1125 'group_name': user_group.users_group_name,
1125 'group_name': user_group.users_group_name,
1126 'group_description': user_group.user_group_description,
1126 'group_description': user_group.user_group_description,
1127 'active': user_group.users_group_active,
1127 'active': user_group.users_group_active,
1128 'owner': user_group.user.username,
1128 'owner': user_group.user.username,
1129 }
1129 }
1130 if with_group_members:
1130 if with_group_members:
1131 users = []
1131 users = []
1132 for user in user_group.members:
1132 for user in user_group.members:
1133 user = user.user
1133 user = user.user
1134 users.append(user.get_api_data(include_secrets=include_secrets))
1134 users.append(user.get_api_data(include_secrets=include_secrets))
1135 data['users'] = users
1135 data['users'] = users
1136
1136
1137 return data
1137 return data
1138
1138
1139
1139
1140 class UserGroupMember(Base, BaseModel):
1140 class UserGroupMember(Base, BaseModel):
1141 __tablename__ = 'users_groups_members'
1141 __tablename__ = 'users_groups_members'
1142 __table_args__ = (
1142 __table_args__ = (
1143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1145 )
1145 )
1146
1146
1147 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1147 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1148 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1148 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1149 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1149 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1150
1150
1151 user = relationship('User', lazy='joined')
1151 user = relationship('User', lazy='joined')
1152 users_group = relationship('UserGroup')
1152 users_group = relationship('UserGroup')
1153
1153
1154 def __init__(self, gr_id='', u_id=''):
1154 def __init__(self, gr_id='', u_id=''):
1155 self.users_group_id = gr_id
1155 self.users_group_id = gr_id
1156 self.user_id = u_id
1156 self.user_id = u_id
1157
1157
1158
1158
1159 class RepositoryField(Base, BaseModel):
1159 class RepositoryField(Base, BaseModel):
1160 __tablename__ = 'repositories_fields'
1160 __tablename__ = 'repositories_fields'
1161 __table_args__ = (
1161 __table_args__ = (
1162 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1162 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1164 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1164 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1165 )
1165 )
1166 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1166 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1167
1167
1168 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1168 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1169 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1169 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1170 field_key = Column("field_key", String(250))
1170 field_key = Column("field_key", String(250))
1171 field_label = Column("field_label", String(1024), nullable=False)
1171 field_label = Column("field_label", String(1024), nullable=False)
1172 field_value = Column("field_value", String(10000), nullable=False)
1172 field_value = Column("field_value", String(10000), nullable=False)
1173 field_desc = Column("field_desc", String(1024), nullable=False)
1173 field_desc = Column("field_desc", String(1024), nullable=False)
1174 field_type = Column("field_type", String(255), nullable=False, unique=None)
1174 field_type = Column("field_type", String(255), nullable=False, unique=None)
1175 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1175 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1176
1176
1177 repository = relationship('Repository')
1177 repository = relationship('Repository')
1178
1178
1179 @property
1179 @property
1180 def field_key_prefixed(self):
1180 def field_key_prefixed(self):
1181 return 'ex_%s' % self.field_key
1181 return 'ex_%s' % self.field_key
1182
1182
1183 @classmethod
1183 @classmethod
1184 def un_prefix_key(cls, key):
1184 def un_prefix_key(cls, key):
1185 if key.startswith(cls.PREFIX):
1185 if key.startswith(cls.PREFIX):
1186 return key[len(cls.PREFIX):]
1186 return key[len(cls.PREFIX):]
1187 return key
1187 return key
1188
1188
1189 @classmethod
1189 @classmethod
1190 def get_by_key_name(cls, key, repo):
1190 def get_by_key_name(cls, key, repo):
1191 row = cls.query()\
1191 row = cls.query()\
1192 .filter(cls.repository == repo)\
1192 .filter(cls.repository == repo)\
1193 .filter(cls.field_key == key).scalar()
1193 .filter(cls.field_key == key).scalar()
1194 return row
1194 return row
1195
1195
1196
1196
1197 class Repository(Base, BaseModel):
1197 class Repository(Base, BaseModel):
1198 __tablename__ = 'repositories'
1198 __tablename__ = 'repositories'
1199 __table_args__ = (
1199 __table_args__ = (
1200 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1200 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1202 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1202 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1203 )
1203 )
1204 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1204 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1205 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1205 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1206
1206
1207 STATE_CREATED = 'repo_state_created'
1207 STATE_CREATED = 'repo_state_created'
1208 STATE_PENDING = 'repo_state_pending'
1208 STATE_PENDING = 'repo_state_pending'
1209 STATE_ERROR = 'repo_state_error'
1209 STATE_ERROR = 'repo_state_error'
1210
1210
1211 LOCK_AUTOMATIC = 'lock_auto'
1211 LOCK_AUTOMATIC = 'lock_auto'
1212 LOCK_API = 'lock_api'
1212 LOCK_API = 'lock_api'
1213 LOCK_WEB = 'lock_web'
1213 LOCK_WEB = 'lock_web'
1214 LOCK_PULL = 'lock_pull'
1214 LOCK_PULL = 'lock_pull'
1215
1215
1216 NAME_SEP = URL_SEP
1216 NAME_SEP = URL_SEP
1217
1217
1218 repo_id = Column(
1218 repo_id = Column(
1219 "repo_id", Integer(), nullable=False, unique=True, default=None,
1219 "repo_id", Integer(), nullable=False, unique=True, default=None,
1220 primary_key=True)
1220 primary_key=True)
1221 _repo_name = Column(
1221 _repo_name = Column(
1222 "repo_name", Text(), nullable=False, default=None)
1222 "repo_name", Text(), nullable=False, default=None)
1223 _repo_name_hash = Column(
1223 _repo_name_hash = Column(
1224 "repo_name_hash", String(255), nullable=False, unique=True)
1224 "repo_name_hash", String(255), nullable=False, unique=True)
1225 repo_state = Column("repo_state", String(255), nullable=True)
1225 repo_state = Column("repo_state", String(255), nullable=True)
1226
1226
1227 clone_uri = Column(
1227 clone_uri = Column(
1228 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1228 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1229 default=None)
1229 default=None)
1230 repo_type = Column(
1230 repo_type = Column(
1231 "repo_type", String(255), nullable=False, unique=False, default=None)
1231 "repo_type", String(255), nullable=False, unique=False, default=None)
1232 user_id = Column(
1232 user_id = Column(
1233 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1233 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1234 unique=False, default=None)
1234 unique=False, default=None)
1235 private = Column(
1235 private = Column(
1236 "private", Boolean(), nullable=True, unique=None, default=None)
1236 "private", Boolean(), nullable=True, unique=None, default=None)
1237 enable_statistics = Column(
1237 enable_statistics = Column(
1238 "statistics", Boolean(), nullable=True, unique=None, default=True)
1238 "statistics", Boolean(), nullable=True, unique=None, default=True)
1239 enable_downloads = Column(
1239 enable_downloads = Column(
1240 "downloads", Boolean(), nullable=True, unique=None, default=True)
1240 "downloads", Boolean(), nullable=True, unique=None, default=True)
1241 description = Column(
1241 description = Column(
1242 "description", String(10000), nullable=True, unique=None, default=None)
1242 "description", String(10000), nullable=True, unique=None, default=None)
1243 created_on = Column(
1243 created_on = Column(
1244 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1244 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1245 default=datetime.datetime.now)
1245 default=datetime.datetime.now)
1246 updated_on = Column(
1246 updated_on = Column(
1247 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1247 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1248 default=datetime.datetime.now)
1248 default=datetime.datetime.now)
1249 _landing_revision = Column(
1249 _landing_revision = Column(
1250 "landing_revision", String(255), nullable=False, unique=False,
1250 "landing_revision", String(255), nullable=False, unique=False,
1251 default=None)
1251 default=None)
1252 enable_locking = Column(
1252 enable_locking = Column(
1253 "enable_locking", Boolean(), nullable=False, unique=None,
1253 "enable_locking", Boolean(), nullable=False, unique=None,
1254 default=False)
1254 default=False)
1255 _locked = Column(
1255 _locked = Column(
1256 "locked", String(255), nullable=True, unique=False, default=None)
1256 "locked", String(255), nullable=True, unique=False, default=None)
1257 _changeset_cache = Column(
1257 _changeset_cache = Column(
1258 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1258 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1259
1259
1260 fork_id = Column(
1260 fork_id = Column(
1261 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1261 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1262 nullable=True, unique=False, default=None)
1262 nullable=True, unique=False, default=None)
1263 group_id = Column(
1263 group_id = Column(
1264 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1264 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1265 unique=False, default=None)
1265 unique=False, default=None)
1266
1266
1267 user = relationship('User')
1267 user = relationship('User')
1268 fork = relationship('Repository', remote_side=repo_id)
1268 fork = relationship('Repository', remote_side=repo_id)
1269 group = relationship('RepoGroup')
1269 group = relationship('RepoGroup')
1270 repo_to_perm = relationship(
1270 repo_to_perm = relationship(
1271 'UserRepoToPerm', cascade='all',
1271 'UserRepoToPerm', cascade='all',
1272 order_by='UserRepoToPerm.repo_to_perm_id')
1272 order_by='UserRepoToPerm.repo_to_perm_id')
1273 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1273 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1274 stats = relationship('Statistics', cascade='all', uselist=False)
1274 stats = relationship('Statistics', cascade='all', uselist=False)
1275
1275
1276 followers = relationship(
1276 followers = relationship(
1277 'UserFollowing',
1277 'UserFollowing',
1278 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1278 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1279 cascade='all')
1279 cascade='all')
1280 extra_fields = relationship(
1280 extra_fields = relationship(
1281 'RepositoryField', cascade="all, delete, delete-orphan")
1281 'RepositoryField', cascade="all, delete, delete-orphan")
1282 logs = relationship('UserLog')
1282 logs = relationship('UserLog')
1283 comments = relationship(
1283 comments = relationship(
1284 'ChangesetComment', cascade="all, delete, delete-orphan")
1284 'ChangesetComment', cascade="all, delete, delete-orphan")
1285 pull_requests_source = relationship(
1285 pull_requests_source = relationship(
1286 'PullRequest',
1286 'PullRequest',
1287 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1287 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1288 cascade="all, delete, delete-orphan")
1288 cascade="all, delete, delete-orphan")
1289 pull_requests_target = relationship(
1289 pull_requests_target = relationship(
1290 'PullRequest',
1290 'PullRequest',
1291 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1291 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1292 cascade="all, delete, delete-orphan")
1292 cascade="all, delete, delete-orphan")
1293 ui = relationship('RepoRhodeCodeUi', cascade="all")
1293 ui = relationship('RepoRhodeCodeUi', cascade="all")
1294 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1294 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1295
1295
1296 def __unicode__(self):
1296 def __unicode__(self):
1297 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1297 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1298 safe_unicode(self.repo_name))
1298 safe_unicode(self.repo_name))
1299
1299
1300 @hybrid_property
1300 @hybrid_property
1301 def landing_rev(self):
1301 def landing_rev(self):
1302 # always should return [rev_type, rev]
1302 # always should return [rev_type, rev]
1303 if self._landing_revision:
1303 if self._landing_revision:
1304 _rev_info = self._landing_revision.split(':')
1304 _rev_info = self._landing_revision.split(':')
1305 if len(_rev_info) < 2:
1305 if len(_rev_info) < 2:
1306 _rev_info.insert(0, 'rev')
1306 _rev_info.insert(0, 'rev')
1307 return [_rev_info[0], _rev_info[1]]
1307 return [_rev_info[0], _rev_info[1]]
1308 return [None, None]
1308 return [None, None]
1309
1309
1310 @landing_rev.setter
1310 @landing_rev.setter
1311 def landing_rev(self, val):
1311 def landing_rev(self, val):
1312 if ':' not in val:
1312 if ':' not in val:
1313 raise ValueError('value must be delimited with `:` and consist '
1313 raise ValueError('value must be delimited with `:` and consist '
1314 'of <rev_type>:<rev>, got %s instead' % val)
1314 'of <rev_type>:<rev>, got %s instead' % val)
1315 self._landing_revision = val
1315 self._landing_revision = val
1316
1316
1317 @hybrid_property
1317 @hybrid_property
1318 def locked(self):
1318 def locked(self):
1319 if self._locked:
1319 if self._locked:
1320 user_id, timelocked, reason = self._locked.split(':')
1320 user_id, timelocked, reason = self._locked.split(':')
1321 lock_values = int(user_id), timelocked, reason
1321 lock_values = int(user_id), timelocked, reason
1322 else:
1322 else:
1323 lock_values = [None, None, None]
1323 lock_values = [None, None, None]
1324 return lock_values
1324 return lock_values
1325
1325
1326 @locked.setter
1326 @locked.setter
1327 def locked(self, val):
1327 def locked(self, val):
1328 if val and isinstance(val, (list, tuple)):
1328 if val and isinstance(val, (list, tuple)):
1329 self._locked = ':'.join(map(str, val))
1329 self._locked = ':'.join(map(str, val))
1330 else:
1330 else:
1331 self._locked = None
1331 self._locked = None
1332
1332
1333 @hybrid_property
1333 @hybrid_property
1334 def changeset_cache(self):
1334 def changeset_cache(self):
1335 from rhodecode.lib.vcs.backends.base import EmptyCommit
1335 from rhodecode.lib.vcs.backends.base import EmptyCommit
1336 dummy = EmptyCommit().__json__()
1336 dummy = EmptyCommit().__json__()
1337 if not self._changeset_cache:
1337 if not self._changeset_cache:
1338 return dummy
1338 return dummy
1339 try:
1339 try:
1340 return json.loads(self._changeset_cache)
1340 return json.loads(self._changeset_cache)
1341 except TypeError:
1341 except TypeError:
1342 return dummy
1342 return dummy
1343 except Exception:
1343 except Exception:
1344 log.error(traceback.format_exc())
1344 log.error(traceback.format_exc())
1345 return dummy
1345 return dummy
1346
1346
1347 @changeset_cache.setter
1347 @changeset_cache.setter
1348 def changeset_cache(self, val):
1348 def changeset_cache(self, val):
1349 try:
1349 try:
1350 self._changeset_cache = json.dumps(val)
1350 self._changeset_cache = json.dumps(val)
1351 except Exception:
1351 except Exception:
1352 log.error(traceback.format_exc())
1352 log.error(traceback.format_exc())
1353
1353
1354 @hybrid_property
1354 @hybrid_property
1355 def repo_name(self):
1355 def repo_name(self):
1356 return self._repo_name
1356 return self._repo_name
1357
1357
1358 @repo_name.setter
1358 @repo_name.setter
1359 def repo_name(self, value):
1359 def repo_name(self, value):
1360 self._repo_name = value
1360 self._repo_name = value
1361 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1361 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1362
1362
1363 @classmethod
1363 @classmethod
1364 def normalize_repo_name(cls, repo_name):
1364 def normalize_repo_name(cls, repo_name):
1365 """
1365 """
1366 Normalizes os specific repo_name to the format internally stored inside
1366 Normalizes os specific repo_name to the format internally stored inside
1367 dabatabase using URL_SEP
1367 dabatabase using URL_SEP
1368
1368
1369 :param cls:
1369 :param cls:
1370 :param repo_name:
1370 :param repo_name:
1371 """
1371 """
1372 return cls.NAME_SEP.join(repo_name.split(os.sep))
1372 return cls.NAME_SEP.join(repo_name.split(os.sep))
1373
1373
1374 @classmethod
1374 @classmethod
1375 def get_by_repo_name(cls, repo_name):
1375 def get_by_repo_name(cls, repo_name):
1376 q = Session().query(cls).filter(cls.repo_name == repo_name)
1376 q = Session().query(cls).filter(cls.repo_name == repo_name)
1377 q = q.options(joinedload(Repository.fork))\
1377 q = q.options(joinedload(Repository.fork))\
1378 .options(joinedload(Repository.user))\
1378 .options(joinedload(Repository.user))\
1379 .options(joinedload(Repository.group))
1379 .options(joinedload(Repository.group))
1380 return q.scalar()
1380 return q.scalar()
1381
1381
1382 @classmethod
1382 @classmethod
1383 def get_by_full_path(cls, repo_full_path):
1383 def get_by_full_path(cls, repo_full_path):
1384 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1384 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1385 repo_name = cls.normalize_repo_name(repo_name)
1385 repo_name = cls.normalize_repo_name(repo_name)
1386 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1386 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1387
1387
1388 @classmethod
1388 @classmethod
1389 def get_repo_forks(cls, repo_id):
1389 def get_repo_forks(cls, repo_id):
1390 return cls.query().filter(Repository.fork_id == repo_id)
1390 return cls.query().filter(Repository.fork_id == repo_id)
1391
1391
1392 @classmethod
1392 @classmethod
1393 def base_path(cls):
1393 def base_path(cls):
1394 """
1394 """
1395 Returns base path when all repos are stored
1395 Returns base path when all repos are stored
1396
1396
1397 :param cls:
1397 :param cls:
1398 """
1398 """
1399 q = Session().query(RhodeCodeUi)\
1399 q = Session().query(RhodeCodeUi)\
1400 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1400 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1401 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1401 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1402 return q.one().ui_value
1402 return q.one().ui_value
1403
1403
1404 @classmethod
1404 @classmethod
1405 def is_valid(cls, repo_name):
1405 def is_valid(cls, repo_name):
1406 """
1406 """
1407 returns True if given repo name is a valid filesystem repository
1407 returns True if given repo name is a valid filesystem repository
1408
1408
1409 :param cls:
1409 :param cls:
1410 :param repo_name:
1410 :param repo_name:
1411 """
1411 """
1412 from rhodecode.lib.utils import is_valid_repo
1412 from rhodecode.lib.utils import is_valid_repo
1413
1413
1414 return is_valid_repo(repo_name, cls.base_path())
1414 return is_valid_repo(repo_name, cls.base_path())
1415
1415
1416 @classmethod
1416 @classmethod
1417 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1417 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1418 case_insensitive=True):
1418 case_insensitive=True):
1419 q = Repository.query()
1419 q = Repository.query()
1420
1420
1421 if not isinstance(user_id, Optional):
1421 if not isinstance(user_id, Optional):
1422 q = q.filter(Repository.user_id == user_id)
1422 q = q.filter(Repository.user_id == user_id)
1423
1423
1424 if not isinstance(group_id, Optional):
1424 if not isinstance(group_id, Optional):
1425 q = q.filter(Repository.group_id == group_id)
1425 q = q.filter(Repository.group_id == group_id)
1426
1426
1427 if case_insensitive:
1427 if case_insensitive:
1428 q = q.order_by(func.lower(Repository.repo_name))
1428 q = q.order_by(func.lower(Repository.repo_name))
1429 else:
1429 else:
1430 q = q.order_by(Repository.repo_name)
1430 q = q.order_by(Repository.repo_name)
1431 return q.all()
1431 return q.all()
1432
1432
1433 @property
1433 @property
1434 def forks(self):
1434 def forks(self):
1435 """
1435 """
1436 Return forks of this repo
1436 Return forks of this repo
1437 """
1437 """
1438 return Repository.get_repo_forks(self.repo_id)
1438 return Repository.get_repo_forks(self.repo_id)
1439
1439
1440 @property
1440 @property
1441 def parent(self):
1441 def parent(self):
1442 """
1442 """
1443 Returns fork parent
1443 Returns fork parent
1444 """
1444 """
1445 return self.fork
1445 return self.fork
1446
1446
1447 @property
1447 @property
1448 def just_name(self):
1448 def just_name(self):
1449 return self.repo_name.split(self.NAME_SEP)[-1]
1449 return self.repo_name.split(self.NAME_SEP)[-1]
1450
1450
1451 @property
1451 @property
1452 def groups_with_parents(self):
1452 def groups_with_parents(self):
1453 groups = []
1453 groups = []
1454 if self.group is None:
1454 if self.group is None:
1455 return groups
1455 return groups
1456
1456
1457 cur_gr = self.group
1457 cur_gr = self.group
1458 groups.insert(0, cur_gr)
1458 groups.insert(0, cur_gr)
1459 while 1:
1459 while 1:
1460 gr = getattr(cur_gr, 'parent_group', None)
1460 gr = getattr(cur_gr, 'parent_group', None)
1461 cur_gr = cur_gr.parent_group
1461 cur_gr = cur_gr.parent_group
1462 if gr is None:
1462 if gr is None:
1463 break
1463 break
1464 groups.insert(0, gr)
1464 groups.insert(0, gr)
1465
1465
1466 return groups
1466 return groups
1467
1467
1468 @property
1468 @property
1469 def groups_and_repo(self):
1469 def groups_and_repo(self):
1470 return self.groups_with_parents, self
1470 return self.groups_with_parents, self
1471
1471
1472 @LazyProperty
1472 @LazyProperty
1473 def repo_path(self):
1473 def repo_path(self):
1474 """
1474 """
1475 Returns base full path for that repository means where it actually
1475 Returns base full path for that repository means where it actually
1476 exists on a filesystem
1476 exists on a filesystem
1477 """
1477 """
1478 q = Session().query(RhodeCodeUi).filter(
1478 q = Session().query(RhodeCodeUi).filter(
1479 RhodeCodeUi.ui_key == self.NAME_SEP)
1479 RhodeCodeUi.ui_key == self.NAME_SEP)
1480 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1480 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1481 return q.one().ui_value
1481 return q.one().ui_value
1482
1482
1483 @property
1483 @property
1484 def repo_full_path(self):
1484 def repo_full_path(self):
1485 p = [self.repo_path]
1485 p = [self.repo_path]
1486 # we need to split the name by / since this is how we store the
1486 # we need to split the name by / since this is how we store the
1487 # names in the database, but that eventually needs to be converted
1487 # names in the database, but that eventually needs to be converted
1488 # into a valid system path
1488 # into a valid system path
1489 p += self.repo_name.split(self.NAME_SEP)
1489 p += self.repo_name.split(self.NAME_SEP)
1490 return os.path.join(*map(safe_unicode, p))
1490 return os.path.join(*map(safe_unicode, p))
1491
1491
1492 @property
1492 @property
1493 def cache_keys(self):
1493 def cache_keys(self):
1494 """
1494 """
1495 Returns associated cache keys for that repo
1495 Returns associated cache keys for that repo
1496 """
1496 """
1497 return CacheKey.query()\
1497 return CacheKey.query()\
1498 .filter(CacheKey.cache_args == self.repo_name)\
1498 .filter(CacheKey.cache_args == self.repo_name)\
1499 .order_by(CacheKey.cache_key)\
1499 .order_by(CacheKey.cache_key)\
1500 .all()
1500 .all()
1501
1501
1502 def get_new_name(self, repo_name):
1502 def get_new_name(self, repo_name):
1503 """
1503 """
1504 returns new full repository name based on assigned group and new new
1504 returns new full repository name based on assigned group and new new
1505
1505
1506 :param group_name:
1506 :param group_name:
1507 """
1507 """
1508 path_prefix = self.group.full_path_splitted if self.group else []
1508 path_prefix = self.group.full_path_splitted if self.group else []
1509 return self.NAME_SEP.join(path_prefix + [repo_name])
1509 return self.NAME_SEP.join(path_prefix + [repo_name])
1510
1510
1511 @property
1511 @property
1512 def _config(self):
1512 def _config(self):
1513 """
1513 """
1514 Returns db based config object.
1514 Returns db based config object.
1515 """
1515 """
1516 from rhodecode.lib.utils import make_db_config
1516 from rhodecode.lib.utils import make_db_config
1517 return make_db_config(clear_session=False, repo=self)
1517 return make_db_config(clear_session=False, repo=self)
1518
1518
1519 def permissions(self, with_admins=True, with_owner=True):
1519 def permissions(self, with_admins=True, with_owner=True):
1520 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1520 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1521 q = q.options(joinedload(UserRepoToPerm.repository),
1521 q = q.options(joinedload(UserRepoToPerm.repository),
1522 joinedload(UserRepoToPerm.user),
1522 joinedload(UserRepoToPerm.user),
1523 joinedload(UserRepoToPerm.permission),)
1523 joinedload(UserRepoToPerm.permission),)
1524
1524
1525 # get owners and admins and permissions. We do a trick of re-writing
1525 # get owners and admins and permissions. We do a trick of re-writing
1526 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1526 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1527 # has a global reference and changing one object propagates to all
1527 # has a global reference and changing one object propagates to all
1528 # others. This means if admin is also an owner admin_row that change
1528 # others. This means if admin is also an owner admin_row that change
1529 # would propagate to both objects
1529 # would propagate to both objects
1530 perm_rows = []
1530 perm_rows = []
1531 for _usr in q.all():
1531 for _usr in q.all():
1532 usr = AttributeDict(_usr.user.get_dict())
1532 usr = AttributeDict(_usr.user.get_dict())
1533 usr.permission = _usr.permission.permission_name
1533 usr.permission = _usr.permission.permission_name
1534 perm_rows.append(usr)
1534 perm_rows.append(usr)
1535
1535
1536 # filter the perm rows by 'default' first and then sort them by
1536 # filter the perm rows by 'default' first and then sort them by
1537 # admin,write,read,none permissions sorted again alphabetically in
1537 # admin,write,read,none permissions sorted again alphabetically in
1538 # each group
1538 # each group
1539 perm_rows = sorted(perm_rows, key=display_sort)
1539 perm_rows = sorted(perm_rows, key=display_sort)
1540
1540
1541 _admin_perm = 'repository.admin'
1541 _admin_perm = 'repository.admin'
1542 owner_row = []
1542 owner_row = []
1543 if with_owner:
1543 if with_owner:
1544 usr = AttributeDict(self.user.get_dict())
1544 usr = AttributeDict(self.user.get_dict())
1545 usr.owner_row = True
1545 usr.owner_row = True
1546 usr.permission = _admin_perm
1546 usr.permission = _admin_perm
1547 owner_row.append(usr)
1547 owner_row.append(usr)
1548
1548
1549 super_admin_rows = []
1549 super_admin_rows = []
1550 if with_admins:
1550 if with_admins:
1551 for usr in User.get_all_super_admins():
1551 for usr in User.get_all_super_admins():
1552 # if this admin is also owner, don't double the record
1552 # if this admin is also owner, don't double the record
1553 if usr.user_id == owner_row[0].user_id:
1553 if usr.user_id == owner_row[0].user_id:
1554 owner_row[0].admin_row = True
1554 owner_row[0].admin_row = True
1555 else:
1555 else:
1556 usr = AttributeDict(usr.get_dict())
1556 usr = AttributeDict(usr.get_dict())
1557 usr.admin_row = True
1557 usr.admin_row = True
1558 usr.permission = _admin_perm
1558 usr.permission = _admin_perm
1559 super_admin_rows.append(usr)
1559 super_admin_rows.append(usr)
1560
1560
1561 return super_admin_rows + owner_row + perm_rows
1561 return super_admin_rows + owner_row + perm_rows
1562
1562
1563 def permission_user_groups(self):
1563 def permission_user_groups(self):
1564 q = UserGroupRepoToPerm.query().filter(
1564 q = UserGroupRepoToPerm.query().filter(
1565 UserGroupRepoToPerm.repository == self)
1565 UserGroupRepoToPerm.repository == self)
1566 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1566 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1567 joinedload(UserGroupRepoToPerm.users_group),
1567 joinedload(UserGroupRepoToPerm.users_group),
1568 joinedload(UserGroupRepoToPerm.permission),)
1568 joinedload(UserGroupRepoToPerm.permission),)
1569
1569
1570 perm_rows = []
1570 perm_rows = []
1571 for _user_group in q.all():
1571 for _user_group in q.all():
1572 usr = AttributeDict(_user_group.users_group.get_dict())
1572 usr = AttributeDict(_user_group.users_group.get_dict())
1573 usr.permission = _user_group.permission.permission_name
1573 usr.permission = _user_group.permission.permission_name
1574 perm_rows.append(usr)
1574 perm_rows.append(usr)
1575
1575
1576 return perm_rows
1576 return perm_rows
1577
1577
1578 def get_api_data(self, include_secrets=False):
1578 def get_api_data(self, include_secrets=False):
1579 """
1579 """
1580 Common function for generating repo api data
1580 Common function for generating repo api data
1581
1581
1582 :param include_secrets: See :meth:`User.get_api_data`.
1582 :param include_secrets: See :meth:`User.get_api_data`.
1583
1583
1584 """
1584 """
1585 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1585 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1586 # move this methods on models level.
1586 # move this methods on models level.
1587 from rhodecode.model.settings import SettingsModel
1587 from rhodecode.model.settings import SettingsModel
1588
1588
1589 repo = self
1589 repo = self
1590 _user_id, _time, _reason = self.locked
1590 _user_id, _time, _reason = self.locked
1591
1591
1592 data = {
1592 data = {
1593 'repo_id': repo.repo_id,
1593 'repo_id': repo.repo_id,
1594 'repo_name': repo.repo_name,
1594 'repo_name': repo.repo_name,
1595 'repo_type': repo.repo_type,
1595 'repo_type': repo.repo_type,
1596 'clone_uri': repo.clone_uri or '',
1596 'clone_uri': repo.clone_uri or '',
1597 'private': repo.private,
1597 'private': repo.private,
1598 'created_on': repo.created_on,
1598 'created_on': repo.created_on,
1599 'description': repo.description,
1599 'description': repo.description,
1600 'landing_rev': repo.landing_rev,
1600 'landing_rev': repo.landing_rev,
1601 'owner': repo.user.username,
1601 'owner': repo.user.username,
1602 'fork_of': repo.fork.repo_name if repo.fork else None,
1602 'fork_of': repo.fork.repo_name if repo.fork else None,
1603 'enable_statistics': repo.enable_statistics,
1603 'enable_statistics': repo.enable_statistics,
1604 'enable_locking': repo.enable_locking,
1604 'enable_locking': repo.enable_locking,
1605 'enable_downloads': repo.enable_downloads,
1605 'enable_downloads': repo.enable_downloads,
1606 'last_changeset': repo.changeset_cache,
1606 'last_changeset': repo.changeset_cache,
1607 'locked_by': User.get(_user_id).get_api_data(
1607 'locked_by': User.get(_user_id).get_api_data(
1608 include_secrets=include_secrets) if _user_id else None,
1608 include_secrets=include_secrets) if _user_id else None,
1609 'locked_date': time_to_datetime(_time) if _time else None,
1609 'locked_date': time_to_datetime(_time) if _time else None,
1610 'lock_reason': _reason if _reason else None,
1610 'lock_reason': _reason if _reason else None,
1611 }
1611 }
1612
1612
1613 # TODO: mikhail: should be per-repo settings here
1613 # TODO: mikhail: should be per-repo settings here
1614 rc_config = SettingsModel().get_all_settings()
1614 rc_config = SettingsModel().get_all_settings()
1615 repository_fields = str2bool(
1615 repository_fields = str2bool(
1616 rc_config.get('rhodecode_repository_fields'))
1616 rc_config.get('rhodecode_repository_fields'))
1617 if repository_fields:
1617 if repository_fields:
1618 for f in self.extra_fields:
1618 for f in self.extra_fields:
1619 data[f.field_key_prefixed] = f.field_value
1619 data[f.field_key_prefixed] = f.field_value
1620
1620
1621 return data
1621 return data
1622
1622
1623 @classmethod
1623 @classmethod
1624 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1624 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1625 if not lock_time:
1625 if not lock_time:
1626 lock_time = time.time()
1626 lock_time = time.time()
1627 if not lock_reason:
1627 if not lock_reason:
1628 lock_reason = cls.LOCK_AUTOMATIC
1628 lock_reason = cls.LOCK_AUTOMATIC
1629 repo.locked = [user_id, lock_time, lock_reason]
1629 repo.locked = [user_id, lock_time, lock_reason]
1630 Session().add(repo)
1630 Session().add(repo)
1631 Session().commit()
1631 Session().commit()
1632
1632
1633 @classmethod
1633 @classmethod
1634 def unlock(cls, repo):
1634 def unlock(cls, repo):
1635 repo.locked = None
1635 repo.locked = None
1636 Session().add(repo)
1636 Session().add(repo)
1637 Session().commit()
1637 Session().commit()
1638
1638
1639 @classmethod
1639 @classmethod
1640 def getlock(cls, repo):
1640 def getlock(cls, repo):
1641 return repo.locked
1641 return repo.locked
1642
1642
1643 def is_user_lock(self, user_id):
1643 def is_user_lock(self, user_id):
1644 if self.lock[0]:
1644 if self.lock[0]:
1645 lock_user_id = safe_int(self.lock[0])
1645 lock_user_id = safe_int(self.lock[0])
1646 user_id = safe_int(user_id)
1646 user_id = safe_int(user_id)
1647 # both are ints, and they are equal
1647 # both are ints, and they are equal
1648 return all([lock_user_id, user_id]) and lock_user_id == user_id
1648 return all([lock_user_id, user_id]) and lock_user_id == user_id
1649
1649
1650 return False
1650 return False
1651
1651
1652 def get_locking_state(self, action, user_id, only_when_enabled=True):
1652 def get_locking_state(self, action, user_id, only_when_enabled=True):
1653 """
1653 """
1654 Checks locking on this repository, if locking is enabled and lock is
1654 Checks locking on this repository, if locking is enabled and lock is
1655 present returns a tuple of make_lock, locked, locked_by.
1655 present returns a tuple of make_lock, locked, locked_by.
1656 make_lock can have 3 states None (do nothing) True, make lock
1656 make_lock can have 3 states None (do nothing) True, make lock
1657 False release lock, This value is later propagated to hooks, which
1657 False release lock, This value is later propagated to hooks, which
1658 do the locking. Think about this as signals passed to hooks what to do.
1658 do the locking. Think about this as signals passed to hooks what to do.
1659
1659
1660 """
1660 """
1661 # TODO: johbo: This is part of the business logic and should be moved
1661 # TODO: johbo: This is part of the business logic and should be moved
1662 # into the RepositoryModel.
1662 # into the RepositoryModel.
1663
1663
1664 if action not in ('push', 'pull'):
1664 if action not in ('push', 'pull'):
1665 raise ValueError("Invalid action value: %s" % repr(action))
1665 raise ValueError("Invalid action value: %s" % repr(action))
1666
1666
1667 # defines if locked error should be thrown to user
1667 # defines if locked error should be thrown to user
1668 currently_locked = False
1668 currently_locked = False
1669 # defines if new lock should be made, tri-state
1669 # defines if new lock should be made, tri-state
1670 make_lock = None
1670 make_lock = None
1671 repo = self
1671 repo = self
1672 user = User.get(user_id)
1672 user = User.get(user_id)
1673
1673
1674 lock_info = repo.locked
1674 lock_info = repo.locked
1675
1675
1676 if repo and (repo.enable_locking or not only_when_enabled):
1676 if repo and (repo.enable_locking or not only_when_enabled):
1677 if action == 'push':
1677 if action == 'push':
1678 # check if it's already locked !, if it is compare users
1678 # check if it's already locked !, if it is compare users
1679 locked_by_user_id = lock_info[0]
1679 locked_by_user_id = lock_info[0]
1680 if user.user_id == locked_by_user_id:
1680 if user.user_id == locked_by_user_id:
1681 log.debug(
1681 log.debug(
1682 'Got `push` action from user %s, now unlocking', user)
1682 'Got `push` action from user %s, now unlocking', user)
1683 # unlock if we have push from user who locked
1683 # unlock if we have push from user who locked
1684 make_lock = False
1684 make_lock = False
1685 else:
1685 else:
1686 # we're not the same user who locked, ban with
1686 # we're not the same user who locked, ban with
1687 # code defined in settings (default is 423 HTTP Locked) !
1687 # code defined in settings (default is 423 HTTP Locked) !
1688 log.debug('Repo %s is currently locked by %s', repo, user)
1688 log.debug('Repo %s is currently locked by %s', repo, user)
1689 currently_locked = True
1689 currently_locked = True
1690 elif action == 'pull':
1690 elif action == 'pull':
1691 # [0] user [1] date
1691 # [0] user [1] date
1692 if lock_info[0] and lock_info[1]:
1692 if lock_info[0] and lock_info[1]:
1693 log.debug('Repo %s is currently locked by %s', repo, user)
1693 log.debug('Repo %s is currently locked by %s', repo, user)
1694 currently_locked = True
1694 currently_locked = True
1695 else:
1695 else:
1696 log.debug('Setting lock on repo %s by %s', repo, user)
1696 log.debug('Setting lock on repo %s by %s', repo, user)
1697 make_lock = True
1697 make_lock = True
1698
1698
1699 else:
1699 else:
1700 log.debug('Repository %s do not have locking enabled', repo)
1700 log.debug('Repository %s do not have locking enabled', repo)
1701
1701
1702 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1702 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1703 make_lock, currently_locked, lock_info)
1703 make_lock, currently_locked, lock_info)
1704
1704
1705 from rhodecode.lib.auth import HasRepoPermissionAny
1705 from rhodecode.lib.auth import HasRepoPermissionAny
1706 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1706 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1707 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1707 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1708 # if we don't have at least write permission we cannot make a lock
1708 # if we don't have at least write permission we cannot make a lock
1709 log.debug('lock state reset back to FALSE due to lack '
1709 log.debug('lock state reset back to FALSE due to lack '
1710 'of at least read permission')
1710 'of at least read permission')
1711 make_lock = False
1711 make_lock = False
1712
1712
1713 return make_lock, currently_locked, lock_info
1713 return make_lock, currently_locked, lock_info
1714
1714
1715 @property
1715 @property
1716 def last_db_change(self):
1716 def last_db_change(self):
1717 return self.updated_on
1717 return self.updated_on
1718
1718
1719 @property
1719 @property
1720 def clone_uri_hidden(self):
1720 def clone_uri_hidden(self):
1721 clone_uri = self.clone_uri
1721 clone_uri = self.clone_uri
1722 if clone_uri:
1722 if clone_uri:
1723 import urlobject
1723 import urlobject
1724 url_obj = urlobject.URLObject(self.clone_uri)
1724 url_obj = urlobject.URLObject(self.clone_uri)
1725 if url_obj.password:
1725 if url_obj.password:
1726 clone_uri = url_obj.with_password('*****')
1726 clone_uri = url_obj.with_password('*****')
1727 return clone_uri
1727 return clone_uri
1728
1728
1729 def clone_url(self, **override):
1729 def clone_url(self, **override):
1730 qualified_home_url = url('home', qualified=True)
1730 qualified_home_url = url('home', qualified=True)
1731
1731
1732 uri_tmpl = None
1732 uri_tmpl = None
1733 if 'with_id' in override:
1733 if 'with_id' in override:
1734 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1734 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1735 del override['with_id']
1735 del override['with_id']
1736
1736
1737 if 'uri_tmpl' in override:
1737 if 'uri_tmpl' in override:
1738 uri_tmpl = override['uri_tmpl']
1738 uri_tmpl = override['uri_tmpl']
1739 del override['uri_tmpl']
1739 del override['uri_tmpl']
1740
1740
1741 # we didn't override our tmpl from **overrides
1741 # we didn't override our tmpl from **overrides
1742 if not uri_tmpl:
1742 if not uri_tmpl:
1743 uri_tmpl = self.DEFAULT_CLONE_URI
1743 uri_tmpl = self.DEFAULT_CLONE_URI
1744 try:
1744 try:
1745 from pylons import tmpl_context as c
1745 from pylons import tmpl_context as c
1746 uri_tmpl = c.clone_uri_tmpl
1746 uri_tmpl = c.clone_uri_tmpl
1747 except Exception:
1747 except Exception:
1748 # in any case if we call this outside of request context,
1748 # in any case if we call this outside of request context,
1749 # ie, not having tmpl_context set up
1749 # ie, not having tmpl_context set up
1750 pass
1750 pass
1751
1751
1752 return get_clone_url(uri_tmpl=uri_tmpl,
1752 return get_clone_url(uri_tmpl=uri_tmpl,
1753 qualifed_home_url=qualified_home_url,
1753 qualifed_home_url=qualified_home_url,
1754 repo_name=self.repo_name,
1754 repo_name=self.repo_name,
1755 repo_id=self.repo_id, **override)
1755 repo_id=self.repo_id, **override)
1756
1756
1757 def set_state(self, state):
1757 def set_state(self, state):
1758 self.repo_state = state
1758 self.repo_state = state
1759 Session().add(self)
1759 Session().add(self)
1760 #==========================================================================
1760 #==========================================================================
1761 # SCM PROPERTIES
1761 # SCM PROPERTIES
1762 #==========================================================================
1762 #==========================================================================
1763
1763
1764 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1764 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1765 return get_commit_safe(
1765 return get_commit_safe(
1766 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1766 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1767
1767
1768 def get_changeset(self, rev=None, pre_load=None):
1768 def get_changeset(self, rev=None, pre_load=None):
1769 warnings.warn("Use get_commit", DeprecationWarning)
1769 warnings.warn("Use get_commit", DeprecationWarning)
1770 commit_id = None
1770 commit_id = None
1771 commit_idx = None
1771 commit_idx = None
1772 if isinstance(rev, basestring):
1772 if isinstance(rev, basestring):
1773 commit_id = rev
1773 commit_id = rev
1774 else:
1774 else:
1775 commit_idx = rev
1775 commit_idx = rev
1776 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1776 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1777 pre_load=pre_load)
1777 pre_load=pre_load)
1778
1778
1779 def get_landing_commit(self):
1779 def get_landing_commit(self):
1780 """
1780 """
1781 Returns landing commit, or if that doesn't exist returns the tip
1781 Returns landing commit, or if that doesn't exist returns the tip
1782 """
1782 """
1783 _rev_type, _rev = self.landing_rev
1783 _rev_type, _rev = self.landing_rev
1784 commit = self.get_commit(_rev)
1784 commit = self.get_commit(_rev)
1785 if isinstance(commit, EmptyCommit):
1785 if isinstance(commit, EmptyCommit):
1786 return self.get_commit()
1786 return self.get_commit()
1787 return commit
1787 return commit
1788
1788
1789 def update_commit_cache(self, cs_cache=None, config=None):
1789 def update_commit_cache(self, cs_cache=None, config=None):
1790 """
1790 """
1791 Update cache of last changeset for repository, keys should be::
1791 Update cache of last changeset for repository, keys should be::
1792
1792
1793 short_id
1793 short_id
1794 raw_id
1794 raw_id
1795 revision
1795 revision
1796 parents
1796 parents
1797 message
1797 message
1798 date
1798 date
1799 author
1799 author
1800
1800
1801 :param cs_cache:
1801 :param cs_cache:
1802 """
1802 """
1803 from rhodecode.lib.vcs.backends.base import BaseChangeset
1803 from rhodecode.lib.vcs.backends.base import BaseChangeset
1804 if cs_cache is None:
1804 if cs_cache is None:
1805 # use no-cache version here
1805 # use no-cache version here
1806 scm_repo = self.scm_instance(cache=False, config=config)
1806 scm_repo = self.scm_instance(cache=False, config=config)
1807 if scm_repo:
1807 if scm_repo:
1808 cs_cache = scm_repo.get_commit(
1808 cs_cache = scm_repo.get_commit(
1809 pre_load=["author", "date", "message", "parents"])
1809 pre_load=["author", "date", "message", "parents"])
1810 else:
1810 else:
1811 cs_cache = EmptyCommit()
1811 cs_cache = EmptyCommit()
1812
1812
1813 if isinstance(cs_cache, BaseChangeset):
1813 if isinstance(cs_cache, BaseChangeset):
1814 cs_cache = cs_cache.__json__()
1814 cs_cache = cs_cache.__json__()
1815
1815
1816 def is_outdated(new_cs_cache):
1816 def is_outdated(new_cs_cache):
1817 if new_cs_cache['raw_id'] != self.changeset_cache['raw_id']:
1817 if new_cs_cache['raw_id'] != self.changeset_cache['raw_id']:
1818 return True
1818 return True
1819 return False
1819 return False
1820
1820
1821 # check if we have maybe already latest cached revision
1821 # check if we have maybe already latest cached revision
1822 if is_outdated(cs_cache) or not self.changeset_cache:
1822 if is_outdated(cs_cache) or not self.changeset_cache:
1823 _default = datetime.datetime.fromtimestamp(0)
1823 _default = datetime.datetime.fromtimestamp(0)
1824 last_change = cs_cache.get('date') or _default
1824 last_change = cs_cache.get('date') or _default
1825 log.debug('updated repo %s with new cs cache %s',
1825 log.debug('updated repo %s with new cs cache %s',
1826 self.repo_name, cs_cache)
1826 self.repo_name, cs_cache)
1827 self.updated_on = last_change
1827 self.updated_on = last_change
1828 self.changeset_cache = cs_cache
1828 self.changeset_cache = cs_cache
1829 Session().add(self)
1829 Session().add(self)
1830 Session().commit()
1830 Session().commit()
1831 else:
1831 else:
1832 log.debug('Skipping update_commit_cache for repo:`%s` '
1832 log.debug('Skipping update_commit_cache for repo:`%s` '
1833 'commit already with latest changes', self.repo_name)
1833 'commit already with latest changes', self.repo_name)
1834
1834
1835 @property
1835 @property
1836 def tip(self):
1836 def tip(self):
1837 return self.get_commit('tip')
1837 return self.get_commit('tip')
1838
1838
1839 @property
1839 @property
1840 def author(self):
1840 def author(self):
1841 return self.tip.author
1841 return self.tip.author
1842
1842
1843 @property
1843 @property
1844 def last_change(self):
1844 def last_change(self):
1845 return self.scm_instance().last_change
1845 return self.scm_instance().last_change
1846
1846
1847 def get_comments(self, revisions=None):
1847 def get_comments(self, revisions=None):
1848 """
1848 """
1849 Returns comments for this repository grouped by revisions
1849 Returns comments for this repository grouped by revisions
1850
1850
1851 :param revisions: filter query by revisions only
1851 :param revisions: filter query by revisions only
1852 """
1852 """
1853 cmts = ChangesetComment.query()\
1853 cmts = ChangesetComment.query()\
1854 .filter(ChangesetComment.repo == self)
1854 .filter(ChangesetComment.repo == self)
1855 if revisions:
1855 if revisions:
1856 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1856 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1857 grouped = collections.defaultdict(list)
1857 grouped = collections.defaultdict(list)
1858 for cmt in cmts.all():
1858 for cmt in cmts.all():
1859 grouped[cmt.revision].append(cmt)
1859 grouped[cmt.revision].append(cmt)
1860 return grouped
1860 return grouped
1861
1861
1862 def statuses(self, revisions=None):
1862 def statuses(self, revisions=None):
1863 """
1863 """
1864 Returns statuses for this repository
1864 Returns statuses for this repository
1865
1865
1866 :param revisions: list of revisions to get statuses for
1866 :param revisions: list of revisions to get statuses for
1867 """
1867 """
1868 statuses = ChangesetStatus.query()\
1868 statuses = ChangesetStatus.query()\
1869 .filter(ChangesetStatus.repo == self)\
1869 .filter(ChangesetStatus.repo == self)\
1870 .filter(ChangesetStatus.version == 0)
1870 .filter(ChangesetStatus.version == 0)
1871
1871
1872 if revisions:
1872 if revisions:
1873 # Try doing the filtering in chunks to avoid hitting limits
1873 # Try doing the filtering in chunks to avoid hitting limits
1874 size = 500
1874 size = 500
1875 status_results = []
1875 status_results = []
1876 for chunk in xrange(0, len(revisions), size):
1876 for chunk in xrange(0, len(revisions), size):
1877 status_results += statuses.filter(
1877 status_results += statuses.filter(
1878 ChangesetStatus.revision.in_(
1878 ChangesetStatus.revision.in_(
1879 revisions[chunk: chunk+size])
1879 revisions[chunk: chunk+size])
1880 ).all()
1880 ).all()
1881 else:
1881 else:
1882 status_results = statuses.all()
1882 status_results = statuses.all()
1883
1883
1884 grouped = {}
1884 grouped = {}
1885
1885
1886 # maybe we have open new pullrequest without a status?
1886 # maybe we have open new pullrequest without a status?
1887 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1887 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1888 status_lbl = ChangesetStatus.get_status_lbl(stat)
1888 status_lbl = ChangesetStatus.get_status_lbl(stat)
1889 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1889 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1890 for rev in pr.revisions:
1890 for rev in pr.revisions:
1891 pr_id = pr.pull_request_id
1891 pr_id = pr.pull_request_id
1892 pr_repo = pr.target_repo.repo_name
1892 pr_repo = pr.target_repo.repo_name
1893 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1893 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1894
1894
1895 for stat in status_results:
1895 for stat in status_results:
1896 pr_id = pr_repo = None
1896 pr_id = pr_repo = None
1897 if stat.pull_request:
1897 if stat.pull_request:
1898 pr_id = stat.pull_request.pull_request_id
1898 pr_id = stat.pull_request.pull_request_id
1899 pr_repo = stat.pull_request.target_repo.repo_name
1899 pr_repo = stat.pull_request.target_repo.repo_name
1900 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1900 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1901 pr_id, pr_repo]
1901 pr_id, pr_repo]
1902 return grouped
1902 return grouped
1903
1903
1904 # ==========================================================================
1904 # ==========================================================================
1905 # SCM CACHE INSTANCE
1905 # SCM CACHE INSTANCE
1906 # ==========================================================================
1906 # ==========================================================================
1907
1907
1908 def scm_instance(self, **kwargs):
1908 def scm_instance(self, **kwargs):
1909 import rhodecode
1909 import rhodecode
1910
1910
1911 # Passing a config will not hit the cache currently only used
1911 # Passing a config will not hit the cache currently only used
1912 # for repo2dbmapper
1912 # for repo2dbmapper
1913 config = kwargs.pop('config', None)
1913 config = kwargs.pop('config', None)
1914 cache = kwargs.pop('cache', None)
1914 cache = kwargs.pop('cache', None)
1915 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1915 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1916 # if cache is NOT defined use default global, else we have a full
1916 # if cache is NOT defined use default global, else we have a full
1917 # control over cache behaviour
1917 # control over cache behaviour
1918 if cache is None and full_cache and not config:
1918 if cache is None and full_cache and not config:
1919 return self._get_instance_cached()
1919 return self._get_instance_cached()
1920 return self._get_instance(cache=bool(cache), config=config)
1920 return self._get_instance(cache=bool(cache), config=config)
1921
1921
1922 def _get_instance_cached(self):
1922 def _get_instance_cached(self):
1923 @cache_region('long_term')
1923 @cache_region('long_term')
1924 def _get_repo(cache_key):
1924 def _get_repo(cache_key):
1925 return self._get_instance()
1925 return self._get_instance()
1926
1926
1927 invalidator_context = CacheKey.repo_context_cache(
1927 invalidator_context = CacheKey.repo_context_cache(
1928 _get_repo, self.repo_name, None)
1928 _get_repo, self.repo_name, None)
1929
1929
1930 with invalidator_context as context:
1930 with invalidator_context as context:
1931 context.invalidate()
1931 context.invalidate()
1932 repo = context.compute()
1932 repo = context.compute()
1933
1933
1934 return repo
1934 return repo
1935
1935
1936 def _get_instance(self, cache=True, config=None):
1936 def _get_instance(self, cache=True, config=None):
1937 repo_full_path = self.repo_full_path
1937 repo_full_path = self.repo_full_path
1938 try:
1938 try:
1939 vcs_alias = get_scm(repo_full_path)[0]
1939 vcs_alias = get_scm(repo_full_path)[0]
1940 log.debug(
1940 log.debug(
1941 'Creating instance of %s repository from %s',
1941 'Creating instance of %s repository from %s',
1942 vcs_alias, repo_full_path)
1942 vcs_alias, repo_full_path)
1943 backend = get_backend(vcs_alias)
1943 backend = get_backend(vcs_alias)
1944 except VCSError:
1944 except VCSError:
1945 log.exception(
1945 log.exception(
1946 'Perhaps this repository is in db and not in '
1946 'Perhaps this repository is in db and not in '
1947 'filesystem run rescan repositories with '
1947 'filesystem run rescan repositories with '
1948 '"destroy old data" option from admin panel')
1948 '"destroy old data" option from admin panel')
1949 return
1949 return
1950
1950
1951 config = config or self._config
1951 config = config or self._config
1952 custom_wire = {
1952 custom_wire = {
1953 'cache': cache # controls the vcs.remote cache
1953 'cache': cache # controls the vcs.remote cache
1954 }
1954 }
1955 repo = backend(
1955 repo = backend(
1956 safe_str(repo_full_path), config=config, create=False,
1956 safe_str(repo_full_path), config=config, create=False,
1957 with_wire=custom_wire)
1957 with_wire=custom_wire)
1958
1958
1959 return repo
1959 return repo
1960
1960
1961 def __json__(self):
1961 def __json__(self):
1962 return {'landing_rev': self.landing_rev}
1962 return {'landing_rev': self.landing_rev}
1963
1963
1964 def get_dict(self):
1964 def get_dict(self):
1965
1965
1966 # Since we transformed `repo_name` to a hybrid property, we need to
1966 # Since we transformed `repo_name` to a hybrid property, we need to
1967 # keep compatibility with the code which uses `repo_name` field.
1967 # keep compatibility with the code which uses `repo_name` field.
1968
1968
1969 result = super(Repository, self).get_dict()
1969 result = super(Repository, self).get_dict()
1970 result['repo_name'] = result.pop('_repo_name', None)
1970 result['repo_name'] = result.pop('_repo_name', None)
1971 return result
1971 return result
1972
1972
1973
1973
1974 class RepoGroup(Base, BaseModel):
1974 class RepoGroup(Base, BaseModel):
1975 __tablename__ = 'groups'
1975 __tablename__ = 'groups'
1976 __table_args__ = (
1976 __table_args__ = (
1977 UniqueConstraint('group_name', 'group_parent_id'),
1977 UniqueConstraint('group_name', 'group_parent_id'),
1978 CheckConstraint('group_id != group_parent_id'),
1978 CheckConstraint('group_id != group_parent_id'),
1979 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1979 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1980 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1980 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1981 )
1981 )
1982 __mapper_args__ = {'order_by': 'group_name'}
1982 __mapper_args__ = {'order_by': 'group_name'}
1983
1983
1984 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
1984 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
1985
1985
1986 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1986 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1987 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
1987 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
1988 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1988 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1989 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
1989 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
1990 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1990 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1991 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1991 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1992 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1992 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1993
1993
1994 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1994 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1995 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1995 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1996 parent_group = relationship('RepoGroup', remote_side=group_id)
1996 parent_group = relationship('RepoGroup', remote_side=group_id)
1997 user = relationship('User')
1997 user = relationship('User')
1998
1998
1999 def __init__(self, group_name='', parent_group=None):
1999 def __init__(self, group_name='', parent_group=None):
2000 self.group_name = group_name
2000 self.group_name = group_name
2001 self.parent_group = parent_group
2001 self.parent_group = parent_group
2002
2002
2003 def __unicode__(self):
2003 def __unicode__(self):
2004 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2004 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2005 self.group_name)
2005 self.group_name)
2006
2006
2007 @classmethod
2007 @classmethod
2008 def _generate_choice(cls, repo_group):
2008 def _generate_choice(cls, repo_group):
2009 from webhelpers.html import literal as _literal
2009 from webhelpers.html import literal as _literal
2010 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2010 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2011 return repo_group.group_id, _name(repo_group.full_path_splitted)
2011 return repo_group.group_id, _name(repo_group.full_path_splitted)
2012
2012
2013 @classmethod
2013 @classmethod
2014 def groups_choices(cls, groups=None, show_empty_group=True):
2014 def groups_choices(cls, groups=None, show_empty_group=True):
2015 if not groups:
2015 if not groups:
2016 groups = cls.query().all()
2016 groups = cls.query().all()
2017
2017
2018 repo_groups = []
2018 repo_groups = []
2019 if show_empty_group:
2019 if show_empty_group:
2020 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2020 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2021
2021
2022 repo_groups.extend([cls._generate_choice(x) for x in groups])
2022 repo_groups.extend([cls._generate_choice(x) for x in groups])
2023
2023
2024 repo_groups = sorted(
2024 repo_groups = sorted(
2025 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2025 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2026 return repo_groups
2026 return repo_groups
2027
2027
2028 @classmethod
2028 @classmethod
2029 def url_sep(cls):
2029 def url_sep(cls):
2030 return URL_SEP
2030 return URL_SEP
2031
2031
2032 @classmethod
2032 @classmethod
2033 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2033 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2034 if case_insensitive:
2034 if case_insensitive:
2035 gr = cls.query().filter(func.lower(cls.group_name)
2035 gr = cls.query().filter(func.lower(cls.group_name)
2036 == func.lower(group_name))
2036 == func.lower(group_name))
2037 else:
2037 else:
2038 gr = cls.query().filter(cls.group_name == group_name)
2038 gr = cls.query().filter(cls.group_name == group_name)
2039 if cache:
2039 if cache:
2040 gr = gr.options(FromCache(
2040 gr = gr.options(FromCache(
2041 "sql_cache_short",
2041 "sql_cache_short",
2042 "get_group_%s" % _hash_key(group_name)))
2042 "get_group_%s" % _hash_key(group_name)))
2043 return gr.scalar()
2043 return gr.scalar()
2044
2044
2045 @classmethod
2045 @classmethod
2046 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2046 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2047 case_insensitive=True):
2047 case_insensitive=True):
2048 q = RepoGroup.query()
2048 q = RepoGroup.query()
2049
2049
2050 if not isinstance(user_id, Optional):
2050 if not isinstance(user_id, Optional):
2051 q = q.filter(RepoGroup.user_id == user_id)
2051 q = q.filter(RepoGroup.user_id == user_id)
2052
2052
2053 if not isinstance(group_id, Optional):
2053 if not isinstance(group_id, Optional):
2054 q = q.filter(RepoGroup.group_parent_id == group_id)
2054 q = q.filter(RepoGroup.group_parent_id == group_id)
2055
2055
2056 if case_insensitive:
2056 if case_insensitive:
2057 q = q.order_by(func.lower(RepoGroup.group_name))
2057 q = q.order_by(func.lower(RepoGroup.group_name))
2058 else:
2058 else:
2059 q = q.order_by(RepoGroup.group_name)
2059 q = q.order_by(RepoGroup.group_name)
2060 return q.all()
2060 return q.all()
2061
2061
2062 @property
2062 @property
2063 def parents(self):
2063 def parents(self):
2064 parents_recursion_limit = 10
2064 parents_recursion_limit = 10
2065 groups = []
2065 groups = []
2066 if self.parent_group is None:
2066 if self.parent_group is None:
2067 return groups
2067 return groups
2068 cur_gr = self.parent_group
2068 cur_gr = self.parent_group
2069 groups.insert(0, cur_gr)
2069 groups.insert(0, cur_gr)
2070 cnt = 0
2070 cnt = 0
2071 while 1:
2071 while 1:
2072 cnt += 1
2072 cnt += 1
2073 gr = getattr(cur_gr, 'parent_group', None)
2073 gr = getattr(cur_gr, 'parent_group', None)
2074 cur_gr = cur_gr.parent_group
2074 cur_gr = cur_gr.parent_group
2075 if gr is None:
2075 if gr is None:
2076 break
2076 break
2077 if cnt == parents_recursion_limit:
2077 if cnt == parents_recursion_limit:
2078 # this will prevent accidental infinit loops
2078 # this will prevent accidental infinit loops
2079 log.error(('more than %s parents found for group %s, stopping '
2079 log.error(('more than %s parents found for group %s, stopping '
2080 'recursive parent fetching' % (parents_recursion_limit, self)))
2080 'recursive parent fetching' % (parents_recursion_limit, self)))
2081 break
2081 break
2082
2082
2083 groups.insert(0, gr)
2083 groups.insert(0, gr)
2084 return groups
2084 return groups
2085
2085
2086 @property
2086 @property
2087 def children(self):
2087 def children(self):
2088 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2088 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2089
2089
2090 @property
2090 @property
2091 def name(self):
2091 def name(self):
2092 return self.group_name.split(RepoGroup.url_sep())[-1]
2092 return self.group_name.split(RepoGroup.url_sep())[-1]
2093
2093
2094 @property
2094 @property
2095 def full_path(self):
2095 def full_path(self):
2096 return self.group_name
2096 return self.group_name
2097
2097
2098 @property
2098 @property
2099 def full_path_splitted(self):
2099 def full_path_splitted(self):
2100 return self.group_name.split(RepoGroup.url_sep())
2100 return self.group_name.split(RepoGroup.url_sep())
2101
2101
2102 @property
2102 @property
2103 def repositories(self):
2103 def repositories(self):
2104 return Repository.query()\
2104 return Repository.query()\
2105 .filter(Repository.group == self)\
2105 .filter(Repository.group == self)\
2106 .order_by(Repository.repo_name)
2106 .order_by(Repository.repo_name)
2107
2107
2108 @property
2108 @property
2109 def repositories_recursive_count(self):
2109 def repositories_recursive_count(self):
2110 cnt = self.repositories.count()
2110 cnt = self.repositories.count()
2111
2111
2112 def children_count(group):
2112 def children_count(group):
2113 cnt = 0
2113 cnt = 0
2114 for child in group.children:
2114 for child in group.children:
2115 cnt += child.repositories.count()
2115 cnt += child.repositories.count()
2116 cnt += children_count(child)
2116 cnt += children_count(child)
2117 return cnt
2117 return cnt
2118
2118
2119 return cnt + children_count(self)
2119 return cnt + children_count(self)
2120
2120
2121 def _recursive_objects(self, include_repos=True):
2121 def _recursive_objects(self, include_repos=True):
2122 all_ = []
2122 all_ = []
2123
2123
2124 def _get_members(root_gr):
2124 def _get_members(root_gr):
2125 if include_repos:
2125 if include_repos:
2126 for r in root_gr.repositories:
2126 for r in root_gr.repositories:
2127 all_.append(r)
2127 all_.append(r)
2128 childs = root_gr.children.all()
2128 childs = root_gr.children.all()
2129 if childs:
2129 if childs:
2130 for gr in childs:
2130 for gr in childs:
2131 all_.append(gr)
2131 all_.append(gr)
2132 _get_members(gr)
2132 _get_members(gr)
2133
2133
2134 _get_members(self)
2134 _get_members(self)
2135 return [self] + all_
2135 return [self] + all_
2136
2136
2137 def recursive_groups_and_repos(self):
2137 def recursive_groups_and_repos(self):
2138 """
2138 """
2139 Recursive return all groups, with repositories in those groups
2139 Recursive return all groups, with repositories in those groups
2140 """
2140 """
2141 return self._recursive_objects()
2141 return self._recursive_objects()
2142
2142
2143 def recursive_groups(self):
2143 def recursive_groups(self):
2144 """
2144 """
2145 Returns all children groups for this group including children of children
2145 Returns all children groups for this group including children of children
2146 """
2146 """
2147 return self._recursive_objects(include_repos=False)
2147 return self._recursive_objects(include_repos=False)
2148
2148
2149 def get_new_name(self, group_name):
2149 def get_new_name(self, group_name):
2150 """
2150 """
2151 returns new full group name based on parent and new name
2151 returns new full group name based on parent and new name
2152
2152
2153 :param group_name:
2153 :param group_name:
2154 """
2154 """
2155 path_prefix = (self.parent_group.full_path_splitted if
2155 path_prefix = (self.parent_group.full_path_splitted if
2156 self.parent_group else [])
2156 self.parent_group else [])
2157 return RepoGroup.url_sep().join(path_prefix + [group_name])
2157 return RepoGroup.url_sep().join(path_prefix + [group_name])
2158
2158
2159 def permissions(self, with_admins=True, with_owner=True):
2159 def permissions(self, with_admins=True, with_owner=True):
2160 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2160 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2161 q = q.options(joinedload(UserRepoGroupToPerm.group),
2161 q = q.options(joinedload(UserRepoGroupToPerm.group),
2162 joinedload(UserRepoGroupToPerm.user),
2162 joinedload(UserRepoGroupToPerm.user),
2163 joinedload(UserRepoGroupToPerm.permission),)
2163 joinedload(UserRepoGroupToPerm.permission),)
2164
2164
2165 # get owners and admins and permissions. We do a trick of re-writing
2165 # get owners and admins and permissions. We do a trick of re-writing
2166 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2166 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2167 # has a global reference and changing one object propagates to all
2167 # has a global reference and changing one object propagates to all
2168 # others. This means if admin is also an owner admin_row that change
2168 # others. This means if admin is also an owner admin_row that change
2169 # would propagate to both objects
2169 # would propagate to both objects
2170 perm_rows = []
2170 perm_rows = []
2171 for _usr in q.all():
2171 for _usr in q.all():
2172 usr = AttributeDict(_usr.user.get_dict())
2172 usr = AttributeDict(_usr.user.get_dict())
2173 usr.permission = _usr.permission.permission_name
2173 usr.permission = _usr.permission.permission_name
2174 perm_rows.append(usr)
2174 perm_rows.append(usr)
2175
2175
2176 # filter the perm rows by 'default' first and then sort them by
2176 # filter the perm rows by 'default' first and then sort them by
2177 # admin,write,read,none permissions sorted again alphabetically in
2177 # admin,write,read,none permissions sorted again alphabetically in
2178 # each group
2178 # each group
2179 perm_rows = sorted(perm_rows, key=display_sort)
2179 perm_rows = sorted(perm_rows, key=display_sort)
2180
2180
2181 _admin_perm = 'group.admin'
2181 _admin_perm = 'group.admin'
2182 owner_row = []
2182 owner_row = []
2183 if with_owner:
2183 if with_owner:
2184 usr = AttributeDict(self.user.get_dict())
2184 usr = AttributeDict(self.user.get_dict())
2185 usr.owner_row = True
2185 usr.owner_row = True
2186 usr.permission = _admin_perm
2186 usr.permission = _admin_perm
2187 owner_row.append(usr)
2187 owner_row.append(usr)
2188
2188
2189 super_admin_rows = []
2189 super_admin_rows = []
2190 if with_admins:
2190 if with_admins:
2191 for usr in User.get_all_super_admins():
2191 for usr in User.get_all_super_admins():
2192 # if this admin is also owner, don't double the record
2192 # if this admin is also owner, don't double the record
2193 if usr.user_id == owner_row[0].user_id:
2193 if usr.user_id == owner_row[0].user_id:
2194 owner_row[0].admin_row = True
2194 owner_row[0].admin_row = True
2195 else:
2195 else:
2196 usr = AttributeDict(usr.get_dict())
2196 usr = AttributeDict(usr.get_dict())
2197 usr.admin_row = True
2197 usr.admin_row = True
2198 usr.permission = _admin_perm
2198 usr.permission = _admin_perm
2199 super_admin_rows.append(usr)
2199 super_admin_rows.append(usr)
2200
2200
2201 return super_admin_rows + owner_row + perm_rows
2201 return super_admin_rows + owner_row + perm_rows
2202
2202
2203 def permission_user_groups(self):
2203 def permission_user_groups(self):
2204 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2204 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2205 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2205 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2206 joinedload(UserGroupRepoGroupToPerm.users_group),
2206 joinedload(UserGroupRepoGroupToPerm.users_group),
2207 joinedload(UserGroupRepoGroupToPerm.permission),)
2207 joinedload(UserGroupRepoGroupToPerm.permission),)
2208
2208
2209 perm_rows = []
2209 perm_rows = []
2210 for _user_group in q.all():
2210 for _user_group in q.all():
2211 usr = AttributeDict(_user_group.users_group.get_dict())
2211 usr = AttributeDict(_user_group.users_group.get_dict())
2212 usr.permission = _user_group.permission.permission_name
2212 usr.permission = _user_group.permission.permission_name
2213 perm_rows.append(usr)
2213 perm_rows.append(usr)
2214
2214
2215 return perm_rows
2215 return perm_rows
2216
2216
2217 def get_api_data(self):
2217 def get_api_data(self):
2218 """
2218 """
2219 Common function for generating api data
2219 Common function for generating api data
2220
2220
2221 """
2221 """
2222 group = self
2222 group = self
2223 data = {
2223 data = {
2224 'group_id': group.group_id,
2224 'group_id': group.group_id,
2225 'group_name': group.group_name,
2225 'group_name': group.group_name,
2226 'group_description': group.group_description,
2226 'group_description': group.group_description,
2227 'parent_group': group.parent_group.group_name if group.parent_group else None,
2227 'parent_group': group.parent_group.group_name if group.parent_group else None,
2228 'repositories': [x.repo_name for x in group.repositories],
2228 'repositories': [x.repo_name for x in group.repositories],
2229 'owner': group.user.username,
2229 'owner': group.user.username,
2230 }
2230 }
2231 return data
2231 return data
2232
2232
2233
2233
2234 class Permission(Base, BaseModel):
2234 class Permission(Base, BaseModel):
2235 __tablename__ = 'permissions'
2235 __tablename__ = 'permissions'
2236 __table_args__ = (
2236 __table_args__ = (
2237 Index('p_perm_name_idx', 'permission_name'),
2237 Index('p_perm_name_idx', 'permission_name'),
2238 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2238 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2239 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2239 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2240 )
2240 )
2241 PERMS = [
2241 PERMS = [
2242 ('hg.admin', _('RhodeCode Super Administrator')),
2242 ('hg.admin', _('RhodeCode Super Administrator')),
2243
2243
2244 ('repository.none', _('Repository no access')),
2244 ('repository.none', _('Repository no access')),
2245 ('repository.read', _('Repository read access')),
2245 ('repository.read', _('Repository read access')),
2246 ('repository.write', _('Repository write access')),
2246 ('repository.write', _('Repository write access')),
2247 ('repository.admin', _('Repository admin access')),
2247 ('repository.admin', _('Repository admin access')),
2248
2248
2249 ('group.none', _('Repository group no access')),
2249 ('group.none', _('Repository group no access')),
2250 ('group.read', _('Repository group read access')),
2250 ('group.read', _('Repository group read access')),
2251 ('group.write', _('Repository group write access')),
2251 ('group.write', _('Repository group write access')),
2252 ('group.admin', _('Repository group admin access')),
2252 ('group.admin', _('Repository group admin access')),
2253
2253
2254 ('usergroup.none', _('User group no access')),
2254 ('usergroup.none', _('User group no access')),
2255 ('usergroup.read', _('User group read access')),
2255 ('usergroup.read', _('User group read access')),
2256 ('usergroup.write', _('User group write access')),
2256 ('usergroup.write', _('User group write access')),
2257 ('usergroup.admin', _('User group admin access')),
2257 ('usergroup.admin', _('User group admin access')),
2258
2258
2259 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2259 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2260 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2260 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2261
2261
2262 ('hg.usergroup.create.false', _('User Group creation disabled')),
2262 ('hg.usergroup.create.false', _('User Group creation disabled')),
2263 ('hg.usergroup.create.true', _('User Group creation enabled')),
2263 ('hg.usergroup.create.true', _('User Group creation enabled')),
2264
2264
2265 ('hg.create.none', _('Repository creation disabled')),
2265 ('hg.create.none', _('Repository creation disabled')),
2266 ('hg.create.repository', _('Repository creation enabled')),
2266 ('hg.create.repository', _('Repository creation enabled')),
2267 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2267 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2268 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2268 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2269
2269
2270 ('hg.fork.none', _('Repository forking disabled')),
2270 ('hg.fork.none', _('Repository forking disabled')),
2271 ('hg.fork.repository', _('Repository forking enabled')),
2271 ('hg.fork.repository', _('Repository forking enabled')),
2272
2272
2273 ('hg.register.none', _('Registration disabled')),
2273 ('hg.register.none', _('Registration disabled')),
2274 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2274 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2275 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2275 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2276
2276
2277 ('hg.extern_activate.manual', _('Manual activation of external account')),
2277 ('hg.extern_activate.manual', _('Manual activation of external account')),
2278 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2278 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2279
2279
2280 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2280 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2281 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2281 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2282 ]
2282 ]
2283
2283
2284 # definition of system default permissions for DEFAULT user
2284 # definition of system default permissions for DEFAULT user
2285 DEFAULT_USER_PERMISSIONS = [
2285 DEFAULT_USER_PERMISSIONS = [
2286 'repository.read',
2286 'repository.read',
2287 'group.read',
2287 'group.read',
2288 'usergroup.read',
2288 'usergroup.read',
2289 'hg.create.repository',
2289 'hg.create.repository',
2290 'hg.repogroup.create.false',
2290 'hg.repogroup.create.false',
2291 'hg.usergroup.create.false',
2291 'hg.usergroup.create.false',
2292 'hg.create.write_on_repogroup.true',
2292 'hg.create.write_on_repogroup.true',
2293 'hg.fork.repository',
2293 'hg.fork.repository',
2294 'hg.register.manual_activate',
2294 'hg.register.manual_activate',
2295 'hg.extern_activate.auto',
2295 'hg.extern_activate.auto',
2296 'hg.inherit_default_perms.true',
2296 'hg.inherit_default_perms.true',
2297 ]
2297 ]
2298
2298
2299 # defines which permissions are more important higher the more important
2299 # defines which permissions are more important higher the more important
2300 # Weight defines which permissions are more important.
2300 # Weight defines which permissions are more important.
2301 # The higher number the more important.
2301 # The higher number the more important.
2302 PERM_WEIGHTS = {
2302 PERM_WEIGHTS = {
2303 'repository.none': 0,
2303 'repository.none': 0,
2304 'repository.read': 1,
2304 'repository.read': 1,
2305 'repository.write': 3,
2305 'repository.write': 3,
2306 'repository.admin': 4,
2306 'repository.admin': 4,
2307
2307
2308 'group.none': 0,
2308 'group.none': 0,
2309 'group.read': 1,
2309 'group.read': 1,
2310 'group.write': 3,
2310 'group.write': 3,
2311 'group.admin': 4,
2311 'group.admin': 4,
2312
2312
2313 'usergroup.none': 0,
2313 'usergroup.none': 0,
2314 'usergroup.read': 1,
2314 'usergroup.read': 1,
2315 'usergroup.write': 3,
2315 'usergroup.write': 3,
2316 'usergroup.admin': 4,
2316 'usergroup.admin': 4,
2317
2317
2318 'hg.repogroup.create.false': 0,
2318 'hg.repogroup.create.false': 0,
2319 'hg.repogroup.create.true': 1,
2319 'hg.repogroup.create.true': 1,
2320
2320
2321 'hg.usergroup.create.false': 0,
2321 'hg.usergroup.create.false': 0,
2322 'hg.usergroup.create.true': 1,
2322 'hg.usergroup.create.true': 1,
2323
2323
2324 'hg.fork.none': 0,
2324 'hg.fork.none': 0,
2325 'hg.fork.repository': 1,
2325 'hg.fork.repository': 1,
2326 'hg.create.none': 0,
2326 'hg.create.none': 0,
2327 'hg.create.repository': 1
2327 'hg.create.repository': 1
2328 }
2328 }
2329
2329
2330 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2330 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2331 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2331 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2332 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2332 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2333
2333
2334 def __unicode__(self):
2334 def __unicode__(self):
2335 return u"<%s('%s:%s')>" % (
2335 return u"<%s('%s:%s')>" % (
2336 self.__class__.__name__, self.permission_id, self.permission_name
2336 self.__class__.__name__, self.permission_id, self.permission_name
2337 )
2337 )
2338
2338
2339 @classmethod
2339 @classmethod
2340 def get_by_key(cls, key):
2340 def get_by_key(cls, key):
2341 return cls.query().filter(cls.permission_name == key).scalar()
2341 return cls.query().filter(cls.permission_name == key).scalar()
2342
2342
2343 @classmethod
2343 @classmethod
2344 def get_default_repo_perms(cls, user_id, repo_id=None):
2344 def get_default_repo_perms(cls, user_id, repo_id=None):
2345 q = Session().query(UserRepoToPerm, Repository, Permission)\
2345 q = Session().query(UserRepoToPerm, Repository, Permission)\
2346 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2346 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2347 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2347 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2348 .filter(UserRepoToPerm.user_id == user_id)
2348 .filter(UserRepoToPerm.user_id == user_id)
2349 if repo_id:
2349 if repo_id:
2350 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2350 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2351 return q.all()
2351 return q.all()
2352
2352
2353 @classmethod
2353 @classmethod
2354 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2354 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2355 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2355 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2356 .join(
2356 .join(
2357 Permission,
2357 Permission,
2358 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2358 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2359 .join(
2359 .join(
2360 Repository,
2360 Repository,
2361 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2361 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2362 .join(
2362 .join(
2363 UserGroup,
2363 UserGroup,
2364 UserGroupRepoToPerm.users_group_id ==
2364 UserGroupRepoToPerm.users_group_id ==
2365 UserGroup.users_group_id)\
2365 UserGroup.users_group_id)\
2366 .join(
2366 .join(
2367 UserGroupMember,
2367 UserGroupMember,
2368 UserGroupRepoToPerm.users_group_id ==
2368 UserGroupRepoToPerm.users_group_id ==
2369 UserGroupMember.users_group_id)\
2369 UserGroupMember.users_group_id)\
2370 .filter(
2370 .filter(
2371 UserGroupMember.user_id == user_id,
2371 UserGroupMember.user_id == user_id,
2372 UserGroup.users_group_active == true())
2372 UserGroup.users_group_active == true())
2373 if repo_id:
2373 if repo_id:
2374 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2374 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2375 return q.all()
2375 return q.all()
2376
2376
2377 @classmethod
2377 @classmethod
2378 def get_default_group_perms(cls, user_id, repo_group_id=None):
2378 def get_default_group_perms(cls, user_id, repo_group_id=None):
2379 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2379 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2380 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2380 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2381 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2381 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2382 .filter(UserRepoGroupToPerm.user_id == user_id)
2382 .filter(UserRepoGroupToPerm.user_id == user_id)
2383 if repo_group_id:
2383 if repo_group_id:
2384 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2384 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2385 return q.all()
2385 return q.all()
2386
2386
2387 @classmethod
2387 @classmethod
2388 def get_default_group_perms_from_user_group(
2388 def get_default_group_perms_from_user_group(
2389 cls, user_id, repo_group_id=None):
2389 cls, user_id, repo_group_id=None):
2390 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2390 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2391 .join(
2391 .join(
2392 Permission,
2392 Permission,
2393 UserGroupRepoGroupToPerm.permission_id ==
2393 UserGroupRepoGroupToPerm.permission_id ==
2394 Permission.permission_id)\
2394 Permission.permission_id)\
2395 .join(
2395 .join(
2396 RepoGroup,
2396 RepoGroup,
2397 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2397 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2398 .join(
2398 .join(
2399 UserGroup,
2399 UserGroup,
2400 UserGroupRepoGroupToPerm.users_group_id ==
2400 UserGroupRepoGroupToPerm.users_group_id ==
2401 UserGroup.users_group_id)\
2401 UserGroup.users_group_id)\
2402 .join(
2402 .join(
2403 UserGroupMember,
2403 UserGroupMember,
2404 UserGroupRepoGroupToPerm.users_group_id ==
2404 UserGroupRepoGroupToPerm.users_group_id ==
2405 UserGroupMember.users_group_id)\
2405 UserGroupMember.users_group_id)\
2406 .filter(
2406 .filter(
2407 UserGroupMember.user_id == user_id,
2407 UserGroupMember.user_id == user_id,
2408 UserGroup.users_group_active == true())
2408 UserGroup.users_group_active == true())
2409 if repo_group_id:
2409 if repo_group_id:
2410 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2410 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2411 return q.all()
2411 return q.all()
2412
2412
2413 @classmethod
2413 @classmethod
2414 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2414 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2415 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2415 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2416 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2416 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2417 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2417 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2418 .filter(UserUserGroupToPerm.user_id == user_id)
2418 .filter(UserUserGroupToPerm.user_id == user_id)
2419 if user_group_id:
2419 if user_group_id:
2420 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2420 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2421 return q.all()
2421 return q.all()
2422
2422
2423 @classmethod
2423 @classmethod
2424 def get_default_user_group_perms_from_user_group(
2424 def get_default_user_group_perms_from_user_group(
2425 cls, user_id, user_group_id=None):
2425 cls, user_id, user_group_id=None):
2426 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2426 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2427 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2427 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2428 .join(
2428 .join(
2429 Permission,
2429 Permission,
2430 UserGroupUserGroupToPerm.permission_id ==
2430 UserGroupUserGroupToPerm.permission_id ==
2431 Permission.permission_id)\
2431 Permission.permission_id)\
2432 .join(
2432 .join(
2433 TargetUserGroup,
2433 TargetUserGroup,
2434 UserGroupUserGroupToPerm.target_user_group_id ==
2434 UserGroupUserGroupToPerm.target_user_group_id ==
2435 TargetUserGroup.users_group_id)\
2435 TargetUserGroup.users_group_id)\
2436 .join(
2436 .join(
2437 UserGroup,
2437 UserGroup,
2438 UserGroupUserGroupToPerm.user_group_id ==
2438 UserGroupUserGroupToPerm.user_group_id ==
2439 UserGroup.users_group_id)\
2439 UserGroup.users_group_id)\
2440 .join(
2440 .join(
2441 UserGroupMember,
2441 UserGroupMember,
2442 UserGroupUserGroupToPerm.user_group_id ==
2442 UserGroupUserGroupToPerm.user_group_id ==
2443 UserGroupMember.users_group_id)\
2443 UserGroupMember.users_group_id)\
2444 .filter(
2444 .filter(
2445 UserGroupMember.user_id == user_id,
2445 UserGroupMember.user_id == user_id,
2446 UserGroup.users_group_active == true())
2446 UserGroup.users_group_active == true())
2447 if user_group_id:
2447 if user_group_id:
2448 q = q.filter(
2448 q = q.filter(
2449 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2449 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2450
2450
2451 return q.all()
2451 return q.all()
2452
2452
2453
2453
2454 class UserRepoToPerm(Base, BaseModel):
2454 class UserRepoToPerm(Base, BaseModel):
2455 __tablename__ = 'repo_to_perm'
2455 __tablename__ = 'repo_to_perm'
2456 __table_args__ = (
2456 __table_args__ = (
2457 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2457 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2458 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2458 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2459 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2459 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2460 )
2460 )
2461 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2461 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2462 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2462 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2463 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2463 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2464 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2464 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2465
2465
2466 user = relationship('User')
2466 user = relationship('User')
2467 repository = relationship('Repository')
2467 repository = relationship('Repository')
2468 permission = relationship('Permission')
2468 permission = relationship('Permission')
2469
2469
2470 @classmethod
2470 @classmethod
2471 def create(cls, user, repository, permission):
2471 def create(cls, user, repository, permission):
2472 n = cls()
2472 n = cls()
2473 n.user = user
2473 n.user = user
2474 n.repository = repository
2474 n.repository = repository
2475 n.permission = permission
2475 n.permission = permission
2476 Session().add(n)
2476 Session().add(n)
2477 return n
2477 return n
2478
2478
2479 def __unicode__(self):
2479 def __unicode__(self):
2480 return u'<%s => %s >' % (self.user, self.repository)
2480 return u'<%s => %s >' % (self.user, self.repository)
2481
2481
2482
2482
2483 class UserUserGroupToPerm(Base, BaseModel):
2483 class UserUserGroupToPerm(Base, BaseModel):
2484 __tablename__ = 'user_user_group_to_perm'
2484 __tablename__ = 'user_user_group_to_perm'
2485 __table_args__ = (
2485 __table_args__ = (
2486 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2486 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2487 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2487 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2488 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2488 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2489 )
2489 )
2490 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2490 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2491 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2491 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2492 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2492 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2493 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2493 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2494
2494
2495 user = relationship('User')
2495 user = relationship('User')
2496 user_group = relationship('UserGroup')
2496 user_group = relationship('UserGroup')
2497 permission = relationship('Permission')
2497 permission = relationship('Permission')
2498
2498
2499 @classmethod
2499 @classmethod
2500 def create(cls, user, user_group, permission):
2500 def create(cls, user, user_group, permission):
2501 n = cls()
2501 n = cls()
2502 n.user = user
2502 n.user = user
2503 n.user_group = user_group
2503 n.user_group = user_group
2504 n.permission = permission
2504 n.permission = permission
2505 Session().add(n)
2505 Session().add(n)
2506 return n
2506 return n
2507
2507
2508 def __unicode__(self):
2508 def __unicode__(self):
2509 return u'<%s => %s >' % (self.user, self.user_group)
2509 return u'<%s => %s >' % (self.user, self.user_group)
2510
2510
2511
2511
2512 class UserToPerm(Base, BaseModel):
2512 class UserToPerm(Base, BaseModel):
2513 __tablename__ = 'user_to_perm'
2513 __tablename__ = 'user_to_perm'
2514 __table_args__ = (
2514 __table_args__ = (
2515 UniqueConstraint('user_id', 'permission_id'),
2515 UniqueConstraint('user_id', 'permission_id'),
2516 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2516 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2517 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2517 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2518 )
2518 )
2519 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2519 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2520 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2520 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2521 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2521 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2522
2522
2523 user = relationship('User')
2523 user = relationship('User')
2524 permission = relationship('Permission', lazy='joined')
2524 permission = relationship('Permission', lazy='joined')
2525
2525
2526 def __unicode__(self):
2526 def __unicode__(self):
2527 return u'<%s => %s >' % (self.user, self.permission)
2527 return u'<%s => %s >' % (self.user, self.permission)
2528
2528
2529
2529
2530 class UserGroupRepoToPerm(Base, BaseModel):
2530 class UserGroupRepoToPerm(Base, BaseModel):
2531 __tablename__ = 'users_group_repo_to_perm'
2531 __tablename__ = 'users_group_repo_to_perm'
2532 __table_args__ = (
2532 __table_args__ = (
2533 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2533 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2534 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2534 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2535 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2535 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2536 )
2536 )
2537 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2537 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2538 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2538 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2539 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2539 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2540 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2540 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2541
2541
2542 users_group = relationship('UserGroup')
2542 users_group = relationship('UserGroup')
2543 permission = relationship('Permission')
2543 permission = relationship('Permission')
2544 repository = relationship('Repository')
2544 repository = relationship('Repository')
2545
2545
2546 @classmethod
2546 @classmethod
2547 def create(cls, users_group, repository, permission):
2547 def create(cls, users_group, repository, permission):
2548 n = cls()
2548 n = cls()
2549 n.users_group = users_group
2549 n.users_group = users_group
2550 n.repository = repository
2550 n.repository = repository
2551 n.permission = permission
2551 n.permission = permission
2552 Session().add(n)
2552 Session().add(n)
2553 return n
2553 return n
2554
2554
2555 def __unicode__(self):
2555 def __unicode__(self):
2556 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2556 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2557
2557
2558
2558
2559 class UserGroupUserGroupToPerm(Base, BaseModel):
2559 class UserGroupUserGroupToPerm(Base, BaseModel):
2560 __tablename__ = 'user_group_user_group_to_perm'
2560 __tablename__ = 'user_group_user_group_to_perm'
2561 __table_args__ = (
2561 __table_args__ = (
2562 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2562 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2563 CheckConstraint('target_user_group_id != user_group_id'),
2563 CheckConstraint('target_user_group_id != user_group_id'),
2564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2565 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2565 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2566 )
2566 )
2567 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)
2567 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)
2568 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2568 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2569 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2569 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2570 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2570 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2571
2571
2572 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2572 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2573 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2573 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2574 permission = relationship('Permission')
2574 permission = relationship('Permission')
2575
2575
2576 @classmethod
2576 @classmethod
2577 def create(cls, target_user_group, user_group, permission):
2577 def create(cls, target_user_group, user_group, permission):
2578 n = cls()
2578 n = cls()
2579 n.target_user_group = target_user_group
2579 n.target_user_group = target_user_group
2580 n.user_group = user_group
2580 n.user_group = user_group
2581 n.permission = permission
2581 n.permission = permission
2582 Session().add(n)
2582 Session().add(n)
2583 return n
2583 return n
2584
2584
2585 def __unicode__(self):
2585 def __unicode__(self):
2586 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2586 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2587
2587
2588
2588
2589 class UserGroupToPerm(Base, BaseModel):
2589 class UserGroupToPerm(Base, BaseModel):
2590 __tablename__ = 'users_group_to_perm'
2590 __tablename__ = 'users_group_to_perm'
2591 __table_args__ = (
2591 __table_args__ = (
2592 UniqueConstraint('users_group_id', 'permission_id',),
2592 UniqueConstraint('users_group_id', 'permission_id',),
2593 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2593 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2594 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2594 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2595 )
2595 )
2596 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2596 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2597 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2597 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2598 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2598 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2599
2599
2600 users_group = relationship('UserGroup')
2600 users_group = relationship('UserGroup')
2601 permission = relationship('Permission')
2601 permission = relationship('Permission')
2602
2602
2603
2603
2604 class UserRepoGroupToPerm(Base, BaseModel):
2604 class UserRepoGroupToPerm(Base, BaseModel):
2605 __tablename__ = 'user_repo_group_to_perm'
2605 __tablename__ = 'user_repo_group_to_perm'
2606 __table_args__ = (
2606 __table_args__ = (
2607 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2607 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2608 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2608 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2609 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2609 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2610 )
2610 )
2611
2611
2612 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2612 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2613 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2613 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2614 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2614 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2615 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2615 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2616
2616
2617 user = relationship('User')
2617 user = relationship('User')
2618 group = relationship('RepoGroup')
2618 group = relationship('RepoGroup')
2619 permission = relationship('Permission')
2619 permission = relationship('Permission')
2620
2620
2621 @classmethod
2621 @classmethod
2622 def create(cls, user, repository_group, permission):
2622 def create(cls, user, repository_group, permission):
2623 n = cls()
2623 n = cls()
2624 n.user = user
2624 n.user = user
2625 n.group = repository_group
2625 n.group = repository_group
2626 n.permission = permission
2626 n.permission = permission
2627 Session().add(n)
2627 Session().add(n)
2628 return n
2628 return n
2629
2629
2630
2630
2631 class UserGroupRepoGroupToPerm(Base, BaseModel):
2631 class UserGroupRepoGroupToPerm(Base, BaseModel):
2632 __tablename__ = 'users_group_repo_group_to_perm'
2632 __tablename__ = 'users_group_repo_group_to_perm'
2633 __table_args__ = (
2633 __table_args__ = (
2634 UniqueConstraint('users_group_id', 'group_id'),
2634 UniqueConstraint('users_group_id', 'group_id'),
2635 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2635 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2636 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2636 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2637 )
2637 )
2638
2638
2639 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)
2639 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)
2640 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2640 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2641 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2641 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2642 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2642 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2643
2643
2644 users_group = relationship('UserGroup')
2644 users_group = relationship('UserGroup')
2645 permission = relationship('Permission')
2645 permission = relationship('Permission')
2646 group = relationship('RepoGroup')
2646 group = relationship('RepoGroup')
2647
2647
2648 @classmethod
2648 @classmethod
2649 def create(cls, user_group, repository_group, permission):
2649 def create(cls, user_group, repository_group, permission):
2650 n = cls()
2650 n = cls()
2651 n.users_group = user_group
2651 n.users_group = user_group
2652 n.group = repository_group
2652 n.group = repository_group
2653 n.permission = permission
2653 n.permission = permission
2654 Session().add(n)
2654 Session().add(n)
2655 return n
2655 return n
2656
2656
2657 def __unicode__(self):
2657 def __unicode__(self):
2658 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2658 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2659
2659
2660
2660
2661 class Statistics(Base, BaseModel):
2661 class Statistics(Base, BaseModel):
2662 __tablename__ = 'statistics'
2662 __tablename__ = 'statistics'
2663 __table_args__ = (
2663 __table_args__ = (
2664 UniqueConstraint('repository_id'),
2664 UniqueConstraint('repository_id'),
2665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2666 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2666 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2667 )
2667 )
2668 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2668 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2669 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2669 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2670 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2670 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2671 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2671 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2672 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2672 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2673 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2673 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2674
2674
2675 repository = relationship('Repository', single_parent=True)
2675 repository = relationship('Repository', single_parent=True)
2676
2676
2677
2677
2678 class UserFollowing(Base, BaseModel):
2678 class UserFollowing(Base, BaseModel):
2679 __tablename__ = 'user_followings'
2679 __tablename__ = 'user_followings'
2680 __table_args__ = (
2680 __table_args__ = (
2681 UniqueConstraint('user_id', 'follows_repository_id'),
2681 UniqueConstraint('user_id', 'follows_repository_id'),
2682 UniqueConstraint('user_id', 'follows_user_id'),
2682 UniqueConstraint('user_id', 'follows_user_id'),
2683 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2683 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2684 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2684 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2685 )
2685 )
2686
2686
2687 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2687 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2688 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2688 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2689 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2689 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2690 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2690 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2691 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2691 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2692
2692
2693 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2693 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2694
2694
2695 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2695 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2696 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2696 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2697
2697
2698 @classmethod
2698 @classmethod
2699 def get_repo_followers(cls, repo_id):
2699 def get_repo_followers(cls, repo_id):
2700 return cls.query().filter(cls.follows_repo_id == repo_id)
2700 return cls.query().filter(cls.follows_repo_id == repo_id)
2701
2701
2702
2702
2703 class CacheKey(Base, BaseModel):
2703 class CacheKey(Base, BaseModel):
2704 __tablename__ = 'cache_invalidation'
2704 __tablename__ = 'cache_invalidation'
2705 __table_args__ = (
2705 __table_args__ = (
2706 UniqueConstraint('cache_key'),
2706 UniqueConstraint('cache_key'),
2707 Index('key_idx', 'cache_key'),
2707 Index('key_idx', 'cache_key'),
2708 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2708 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2709 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2709 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2710 )
2710 )
2711 CACHE_TYPE_ATOM = 'ATOM'
2711 CACHE_TYPE_ATOM = 'ATOM'
2712 CACHE_TYPE_RSS = 'RSS'
2712 CACHE_TYPE_RSS = 'RSS'
2713 CACHE_TYPE_README = 'README'
2713 CACHE_TYPE_README = 'README'
2714
2714
2715 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2715 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2716 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2716 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2717 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2717 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2718 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2718 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2719
2719
2720 def __init__(self, cache_key, cache_args=''):
2720 def __init__(self, cache_key, cache_args=''):
2721 self.cache_key = cache_key
2721 self.cache_key = cache_key
2722 self.cache_args = cache_args
2722 self.cache_args = cache_args
2723 self.cache_active = False
2723 self.cache_active = False
2724
2724
2725 def __unicode__(self):
2725 def __unicode__(self):
2726 return u"<%s('%s:%s[%s]')>" % (
2726 return u"<%s('%s:%s[%s]')>" % (
2727 self.__class__.__name__,
2727 self.__class__.__name__,
2728 self.cache_id, self.cache_key, self.cache_active)
2728 self.cache_id, self.cache_key, self.cache_active)
2729
2729
2730 def _cache_key_partition(self):
2730 def _cache_key_partition(self):
2731 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2731 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2732 return prefix, repo_name, suffix
2732 return prefix, repo_name, suffix
2733
2733
2734 def get_prefix(self):
2734 def get_prefix(self):
2735 """
2735 """
2736 Try to extract prefix from existing cache key. The key could consist
2736 Try to extract prefix from existing cache key. The key could consist
2737 of prefix, repo_name, suffix
2737 of prefix, repo_name, suffix
2738 """
2738 """
2739 # this returns prefix, repo_name, suffix
2739 # this returns prefix, repo_name, suffix
2740 return self._cache_key_partition()[0]
2740 return self._cache_key_partition()[0]
2741
2741
2742 def get_suffix(self):
2742 def get_suffix(self):
2743 """
2743 """
2744 get suffix that might have been used in _get_cache_key to
2744 get suffix that might have been used in _get_cache_key to
2745 generate self.cache_key. Only used for informational purposes
2745 generate self.cache_key. Only used for informational purposes
2746 in repo_edit.html.
2746 in repo_edit.html.
2747 """
2747 """
2748 # prefix, repo_name, suffix
2748 # prefix, repo_name, suffix
2749 return self._cache_key_partition()[2]
2749 return self._cache_key_partition()[2]
2750
2750
2751 @classmethod
2751 @classmethod
2752 def delete_all_cache(cls):
2752 def delete_all_cache(cls):
2753 """
2753 """
2754 Delete all cache keys from database.
2754 Delete all cache keys from database.
2755 Should only be run when all instances are down and all entries
2755 Should only be run when all instances are down and all entries
2756 thus stale.
2756 thus stale.
2757 """
2757 """
2758 cls.query().delete()
2758 cls.query().delete()
2759 Session().commit()
2759 Session().commit()
2760
2760
2761 @classmethod
2761 @classmethod
2762 def get_cache_key(cls, repo_name, cache_type):
2762 def get_cache_key(cls, repo_name, cache_type):
2763 """
2763 """
2764
2764
2765 Generate a cache key for this process of RhodeCode instance.
2765 Generate a cache key for this process of RhodeCode instance.
2766 Prefix most likely will be process id or maybe explicitly set
2766 Prefix most likely will be process id or maybe explicitly set
2767 instance_id from .ini file.
2767 instance_id from .ini file.
2768 """
2768 """
2769 import rhodecode
2769 import rhodecode
2770 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2770 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2771
2771
2772 repo_as_unicode = safe_unicode(repo_name)
2772 repo_as_unicode = safe_unicode(repo_name)
2773 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2773 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2774 if cache_type else repo_as_unicode
2774 if cache_type else repo_as_unicode
2775
2775
2776 return u'{}{}'.format(prefix, key)
2776 return u'{}{}'.format(prefix, key)
2777
2777
2778 @classmethod
2778 @classmethod
2779 def set_invalidate(cls, repo_name, delete=False):
2779 def set_invalidate(cls, repo_name, delete=False):
2780 """
2780 """
2781 Mark all caches of a repo as invalid in the database.
2781 Mark all caches of a repo as invalid in the database.
2782 """
2782 """
2783
2783
2784 try:
2784 try:
2785 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2785 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2786 if delete:
2786 if delete:
2787 log.debug('cache objects deleted for repo %s',
2787 log.debug('cache objects deleted for repo %s',
2788 safe_str(repo_name))
2788 safe_str(repo_name))
2789 qry.delete()
2789 qry.delete()
2790 else:
2790 else:
2791 log.debug('cache objects marked as invalid for repo %s',
2791 log.debug('cache objects marked as invalid for repo %s',
2792 safe_str(repo_name))
2792 safe_str(repo_name))
2793 qry.update({"cache_active": False})
2793 qry.update({"cache_active": False})
2794
2794
2795 Session().commit()
2795 Session().commit()
2796 except Exception:
2796 except Exception:
2797 log.error(traceback.format_exc())
2797 log.exception(
2798 'Cache key invalidation failed for repository %s',
2799 safe_str(repo_name))
2798 Session().rollback()
2800 Session().rollback()
2799
2801
2800 @classmethod
2802 @classmethod
2801 def get_active_cache(cls, cache_key):
2803 def get_active_cache(cls, cache_key):
2802 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2804 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2803 if inv_obj:
2805 if inv_obj:
2804 return inv_obj
2806 return inv_obj
2805 return None
2807 return None
2806
2808
2807 @classmethod
2809 @classmethod
2808 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2810 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2809 """
2811 """
2810 @cache_region('long_term')
2812 @cache_region('long_term')
2811 def _heavy_calculation(cache_key):
2813 def _heavy_calculation(cache_key):
2812 return 'result'
2814 return 'result'
2813
2815
2814 cache_context = CacheKey.repo_context_cache(
2816 cache_context = CacheKey.repo_context_cache(
2815 _heavy_calculation, repo_name, cache_type)
2817 _heavy_calculation, repo_name, cache_type)
2816
2818
2817 with cache_context as context:
2819 with cache_context as context:
2818 context.invalidate()
2820 context.invalidate()
2819 computed = context.compute()
2821 computed = context.compute()
2820
2822
2821 assert computed == 'result'
2823 assert computed == 'result'
2822 """
2824 """
2823 from rhodecode.lib import caches
2825 from rhodecode.lib import caches
2824 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2826 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2825
2827
2826
2828
2827 class ChangesetComment(Base, BaseModel):
2829 class ChangesetComment(Base, BaseModel):
2828 __tablename__ = 'changeset_comments'
2830 __tablename__ = 'changeset_comments'
2829 __table_args__ = (
2831 __table_args__ = (
2830 Index('cc_revision_idx', 'revision'),
2832 Index('cc_revision_idx', 'revision'),
2831 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2833 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2832 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2834 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2833 )
2835 )
2834
2836
2835 COMMENT_OUTDATED = u'comment_outdated'
2837 COMMENT_OUTDATED = u'comment_outdated'
2836
2838
2837 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2839 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2838 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2840 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2839 revision = Column('revision', String(40), nullable=True)
2841 revision = Column('revision', String(40), nullable=True)
2840 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2842 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2841 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2843 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2842 line_no = Column('line_no', Unicode(10), nullable=True)
2844 line_no = Column('line_no', Unicode(10), nullable=True)
2843 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2845 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2844 f_path = Column('f_path', Unicode(1000), nullable=True)
2846 f_path = Column('f_path', Unicode(1000), nullable=True)
2845 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2847 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2846 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2848 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2847 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2849 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2848 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2850 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2849 renderer = Column('renderer', Unicode(64), nullable=True)
2851 renderer = Column('renderer', Unicode(64), nullable=True)
2850 display_state = Column('display_state', Unicode(128), nullable=True)
2852 display_state = Column('display_state', Unicode(128), nullable=True)
2851
2853
2852 author = relationship('User', lazy='joined')
2854 author = relationship('User', lazy='joined')
2853 repo = relationship('Repository')
2855 repo = relationship('Repository')
2854 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2856 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2855 pull_request = relationship('PullRequest', lazy='joined')
2857 pull_request = relationship('PullRequest', lazy='joined')
2856 pull_request_version = relationship('PullRequestVersion')
2858 pull_request_version = relationship('PullRequestVersion')
2857
2859
2858 @classmethod
2860 @classmethod
2859 def get_users(cls, revision=None, pull_request_id=None):
2861 def get_users(cls, revision=None, pull_request_id=None):
2860 """
2862 """
2861 Returns user associated with this ChangesetComment. ie those
2863 Returns user associated with this ChangesetComment. ie those
2862 who actually commented
2864 who actually commented
2863
2865
2864 :param cls:
2866 :param cls:
2865 :param revision:
2867 :param revision:
2866 """
2868 """
2867 q = Session().query(User)\
2869 q = Session().query(User)\
2868 .join(ChangesetComment.author)
2870 .join(ChangesetComment.author)
2869 if revision:
2871 if revision:
2870 q = q.filter(cls.revision == revision)
2872 q = q.filter(cls.revision == revision)
2871 elif pull_request_id:
2873 elif pull_request_id:
2872 q = q.filter(cls.pull_request_id == pull_request_id)
2874 q = q.filter(cls.pull_request_id == pull_request_id)
2873 return q.all()
2875 return q.all()
2874
2876
2875 def render(self, mentions=False):
2877 def render(self, mentions=False):
2876 from rhodecode.lib import helpers as h
2878 from rhodecode.lib import helpers as h
2877 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2879 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2878
2880
2879 def __repr__(self):
2881 def __repr__(self):
2880 if self.comment_id:
2882 if self.comment_id:
2881 return '<DB:ChangesetComment #%s>' % self.comment_id
2883 return '<DB:ChangesetComment #%s>' % self.comment_id
2882 else:
2884 else:
2883 return '<DB:ChangesetComment at %#x>' % id(self)
2885 return '<DB:ChangesetComment at %#x>' % id(self)
2884
2886
2885
2887
2886 class ChangesetStatus(Base, BaseModel):
2888 class ChangesetStatus(Base, BaseModel):
2887 __tablename__ = 'changeset_statuses'
2889 __tablename__ = 'changeset_statuses'
2888 __table_args__ = (
2890 __table_args__ = (
2889 Index('cs_revision_idx', 'revision'),
2891 Index('cs_revision_idx', 'revision'),
2890 Index('cs_version_idx', 'version'),
2892 Index('cs_version_idx', 'version'),
2891 UniqueConstraint('repo_id', 'revision', 'version'),
2893 UniqueConstraint('repo_id', 'revision', 'version'),
2892 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2894 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2893 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2895 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2894 )
2896 )
2895 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2897 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2896 STATUS_APPROVED = 'approved'
2898 STATUS_APPROVED = 'approved'
2897 STATUS_REJECTED = 'rejected'
2899 STATUS_REJECTED = 'rejected'
2898 STATUS_UNDER_REVIEW = 'under_review'
2900 STATUS_UNDER_REVIEW = 'under_review'
2899
2901
2900 STATUSES = [
2902 STATUSES = [
2901 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2903 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2902 (STATUS_APPROVED, _("Approved")),
2904 (STATUS_APPROVED, _("Approved")),
2903 (STATUS_REJECTED, _("Rejected")),
2905 (STATUS_REJECTED, _("Rejected")),
2904 (STATUS_UNDER_REVIEW, _("Under Review")),
2906 (STATUS_UNDER_REVIEW, _("Under Review")),
2905 ]
2907 ]
2906
2908
2907 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2909 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2908 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2910 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2909 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2911 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2910 revision = Column('revision', String(40), nullable=False)
2912 revision = Column('revision', String(40), nullable=False)
2911 status = Column('status', String(128), nullable=False, default=DEFAULT)
2913 status = Column('status', String(128), nullable=False, default=DEFAULT)
2912 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2914 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2913 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2915 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2914 version = Column('version', Integer(), nullable=False, default=0)
2916 version = Column('version', Integer(), nullable=False, default=0)
2915 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2917 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2916
2918
2917 author = relationship('User', lazy='joined')
2919 author = relationship('User', lazy='joined')
2918 repo = relationship('Repository')
2920 repo = relationship('Repository')
2919 comment = relationship('ChangesetComment', lazy='joined')
2921 comment = relationship('ChangesetComment', lazy='joined')
2920 pull_request = relationship('PullRequest', lazy='joined')
2922 pull_request = relationship('PullRequest', lazy='joined')
2921
2923
2922 def __unicode__(self):
2924 def __unicode__(self):
2923 return u"<%s('%s[%s]:%s')>" % (
2925 return u"<%s('%s[%s]:%s')>" % (
2924 self.__class__.__name__,
2926 self.__class__.__name__,
2925 self.status, self.version, self.author
2927 self.status, self.version, self.author
2926 )
2928 )
2927
2929
2928 @classmethod
2930 @classmethod
2929 def get_status_lbl(cls, value):
2931 def get_status_lbl(cls, value):
2930 return dict(cls.STATUSES).get(value)
2932 return dict(cls.STATUSES).get(value)
2931
2933
2932 @property
2934 @property
2933 def status_lbl(self):
2935 def status_lbl(self):
2934 return ChangesetStatus.get_status_lbl(self.status)
2936 return ChangesetStatus.get_status_lbl(self.status)
2935
2937
2936
2938
2937 class _PullRequestBase(BaseModel):
2939 class _PullRequestBase(BaseModel):
2938 """
2940 """
2939 Common attributes of pull request and version entries.
2941 Common attributes of pull request and version entries.
2940 """
2942 """
2941
2943
2942 # .status values
2944 # .status values
2943 STATUS_NEW = u'new'
2945 STATUS_NEW = u'new'
2944 STATUS_OPEN = u'open'
2946 STATUS_OPEN = u'open'
2945 STATUS_CLOSED = u'closed'
2947 STATUS_CLOSED = u'closed'
2946
2948
2947 title = Column('title', Unicode(255), nullable=True)
2949 title = Column('title', Unicode(255), nullable=True)
2948 description = Column(
2950 description = Column(
2949 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2951 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2950 nullable=True)
2952 nullable=True)
2951 # new/open/closed status of pull request (not approve/reject/etc)
2953 # new/open/closed status of pull request (not approve/reject/etc)
2952 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
2954 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
2953 created_on = Column(
2955 created_on = Column(
2954 'created_on', DateTime(timezone=False), nullable=False,
2956 'created_on', DateTime(timezone=False), nullable=False,
2955 default=datetime.datetime.now)
2957 default=datetime.datetime.now)
2956 updated_on = Column(
2958 updated_on = Column(
2957 'updated_on', DateTime(timezone=False), nullable=False,
2959 'updated_on', DateTime(timezone=False), nullable=False,
2958 default=datetime.datetime.now)
2960 default=datetime.datetime.now)
2959
2961
2960 @declared_attr
2962 @declared_attr
2961 def user_id(cls):
2963 def user_id(cls):
2962 return Column(
2964 return Column(
2963 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
2965 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
2964 unique=None)
2966 unique=None)
2965
2967
2966 # 500 revisions max
2968 # 500 revisions max
2967 _revisions = Column(
2969 _revisions = Column(
2968 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
2970 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
2969
2971
2970 @declared_attr
2972 @declared_attr
2971 def source_repo_id(cls):
2973 def source_repo_id(cls):
2972 # TODO: dan: rename column to source_repo_id
2974 # TODO: dan: rename column to source_repo_id
2973 return Column(
2975 return Column(
2974 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
2976 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
2975 nullable=False)
2977 nullable=False)
2976
2978
2977 source_ref = Column('org_ref', Unicode(255), nullable=False)
2979 source_ref = Column('org_ref', Unicode(255), nullable=False)
2978
2980
2979 @declared_attr
2981 @declared_attr
2980 def target_repo_id(cls):
2982 def target_repo_id(cls):
2981 # TODO: dan: rename column to target_repo_id
2983 # TODO: dan: rename column to target_repo_id
2982 return Column(
2984 return Column(
2983 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
2985 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
2984 nullable=False)
2986 nullable=False)
2985
2987
2986 target_ref = Column('other_ref', Unicode(255), nullable=False)
2988 target_ref = Column('other_ref', Unicode(255), nullable=False)
2987
2989
2988 # TODO: dan: rename column to last_merge_source_rev
2990 # TODO: dan: rename column to last_merge_source_rev
2989 _last_merge_source_rev = Column(
2991 _last_merge_source_rev = Column(
2990 'last_merge_org_rev', String(40), nullable=True)
2992 'last_merge_org_rev', String(40), nullable=True)
2991 # TODO: dan: rename column to last_merge_target_rev
2993 # TODO: dan: rename column to last_merge_target_rev
2992 _last_merge_target_rev = Column(
2994 _last_merge_target_rev = Column(
2993 'last_merge_other_rev', String(40), nullable=True)
2995 'last_merge_other_rev', String(40), nullable=True)
2994 _last_merge_status = Column('merge_status', Integer(), nullable=True)
2996 _last_merge_status = Column('merge_status', Integer(), nullable=True)
2995 merge_rev = Column('merge_rev', String(40), nullable=True)
2997 merge_rev = Column('merge_rev', String(40), nullable=True)
2996
2998
2997 @hybrid_property
2999 @hybrid_property
2998 def revisions(self):
3000 def revisions(self):
2999 return self._revisions.split(':') if self._revisions else []
3001 return self._revisions.split(':') if self._revisions else []
3000
3002
3001 @revisions.setter
3003 @revisions.setter
3002 def revisions(self, val):
3004 def revisions(self, val):
3003 self._revisions = ':'.join(val)
3005 self._revisions = ':'.join(val)
3004
3006
3005 @declared_attr
3007 @declared_attr
3006 def author(cls):
3008 def author(cls):
3007 return relationship('User', lazy='joined')
3009 return relationship('User', lazy='joined')
3008
3010
3009 @declared_attr
3011 @declared_attr
3010 def source_repo(cls):
3012 def source_repo(cls):
3011 return relationship(
3013 return relationship(
3012 'Repository',
3014 'Repository',
3013 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3015 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3014
3016
3015 @property
3017 @property
3016 def source_ref_parts(self):
3018 def source_ref_parts(self):
3017 refs = self.source_ref.split(':')
3019 refs = self.source_ref.split(':')
3018 return Reference(refs[0], refs[1], refs[2])
3020 return Reference(refs[0], refs[1], refs[2])
3019
3021
3020 @declared_attr
3022 @declared_attr
3021 def target_repo(cls):
3023 def target_repo(cls):
3022 return relationship(
3024 return relationship(
3023 'Repository',
3025 'Repository',
3024 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3026 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3025
3027
3026 @property
3028 @property
3027 def target_ref_parts(self):
3029 def target_ref_parts(self):
3028 refs = self.target_ref.split(':')
3030 refs = self.target_ref.split(':')
3029 return Reference(refs[0], refs[1], refs[2])
3031 return Reference(refs[0], refs[1], refs[2])
3030
3032
3031
3033
3032 class PullRequest(Base, _PullRequestBase):
3034 class PullRequest(Base, _PullRequestBase):
3033 __tablename__ = 'pull_requests'
3035 __tablename__ = 'pull_requests'
3034 __table_args__ = (
3036 __table_args__ = (
3035 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3036 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3037 )
3039 )
3038
3040
3039 pull_request_id = Column(
3041 pull_request_id = Column(
3040 'pull_request_id', Integer(), nullable=False, primary_key=True)
3042 'pull_request_id', Integer(), nullable=False, primary_key=True)
3041
3043
3042 def __repr__(self):
3044 def __repr__(self):
3043 if self.pull_request_id:
3045 if self.pull_request_id:
3044 return '<DB:PullRequest #%s>' % self.pull_request_id
3046 return '<DB:PullRequest #%s>' % self.pull_request_id
3045 else:
3047 else:
3046 return '<DB:PullRequest at %#x>' % id(self)
3048 return '<DB:PullRequest at %#x>' % id(self)
3047
3049
3048 reviewers = relationship('PullRequestReviewers',
3050 reviewers = relationship('PullRequestReviewers',
3049 cascade="all, delete, delete-orphan")
3051 cascade="all, delete, delete-orphan")
3050 statuses = relationship('ChangesetStatus')
3052 statuses = relationship('ChangesetStatus')
3051 comments = relationship('ChangesetComment',
3053 comments = relationship('ChangesetComment',
3052 cascade="all, delete, delete-orphan")
3054 cascade="all, delete, delete-orphan")
3053 versions = relationship('PullRequestVersion',
3055 versions = relationship('PullRequestVersion',
3054 cascade="all, delete, delete-orphan")
3056 cascade="all, delete, delete-orphan")
3055
3057
3056 def is_closed(self):
3058 def is_closed(self):
3057 return self.status == self.STATUS_CLOSED
3059 return self.status == self.STATUS_CLOSED
3058
3060
3059 def get_api_data(self):
3061 def get_api_data(self):
3060 from rhodecode.model.pull_request import PullRequestModel
3062 from rhodecode.model.pull_request import PullRequestModel
3061 pull_request = self
3063 pull_request = self
3062 merge_status = PullRequestModel().merge_status(pull_request)
3064 merge_status = PullRequestModel().merge_status(pull_request)
3063 data = {
3065 data = {
3064 'pull_request_id': pull_request.pull_request_id,
3066 'pull_request_id': pull_request.pull_request_id,
3065 'url': url('pullrequest_show',
3067 'url': url('pullrequest_show',
3066 repo_name=pull_request.target_repo.repo_name,
3068 repo_name=pull_request.target_repo.repo_name,
3067 pull_request_id=pull_request.pull_request_id,
3069 pull_request_id=pull_request.pull_request_id,
3068 qualified=True),
3070 qualified=True),
3069 'title': pull_request.title,
3071 'title': pull_request.title,
3070 'description': pull_request.description,
3072 'description': pull_request.description,
3071 'status': pull_request.status,
3073 'status': pull_request.status,
3072 'created_on': pull_request.created_on,
3074 'created_on': pull_request.created_on,
3073 'updated_on': pull_request.updated_on,
3075 'updated_on': pull_request.updated_on,
3074 'commit_ids': pull_request.revisions,
3076 'commit_ids': pull_request.revisions,
3075 'review_status': pull_request.calculated_review_status(),
3077 'review_status': pull_request.calculated_review_status(),
3076 'mergeable': {
3078 'mergeable': {
3077 'status': merge_status[0],
3079 'status': merge_status[0],
3078 'message': unicode(merge_status[1]),
3080 'message': unicode(merge_status[1]),
3079 },
3081 },
3080 'source': {
3082 'source': {
3081 'clone_url': pull_request.source_repo.clone_url(),
3083 'clone_url': pull_request.source_repo.clone_url(),
3082 'repository': pull_request.source_repo.repo_name,
3084 'repository': pull_request.source_repo.repo_name,
3083 'reference': {
3085 'reference': {
3084 'name': pull_request.source_ref_parts.name,
3086 'name': pull_request.source_ref_parts.name,
3085 'type': pull_request.source_ref_parts.type,
3087 'type': pull_request.source_ref_parts.type,
3086 'commit_id': pull_request.source_ref_parts.commit_id,
3088 'commit_id': pull_request.source_ref_parts.commit_id,
3087 },
3089 },
3088 },
3090 },
3089 'target': {
3091 'target': {
3090 'clone_url': pull_request.target_repo.clone_url(),
3092 'clone_url': pull_request.target_repo.clone_url(),
3091 'repository': pull_request.target_repo.repo_name,
3093 'repository': pull_request.target_repo.repo_name,
3092 'reference': {
3094 'reference': {
3093 'name': pull_request.target_ref_parts.name,
3095 'name': pull_request.target_ref_parts.name,
3094 'type': pull_request.target_ref_parts.type,
3096 'type': pull_request.target_ref_parts.type,
3095 'commit_id': pull_request.target_ref_parts.commit_id,
3097 'commit_id': pull_request.target_ref_parts.commit_id,
3096 },
3098 },
3097 },
3099 },
3098 'author': pull_request.author.get_api_data(include_secrets=False,
3100 'author': pull_request.author.get_api_data(include_secrets=False,
3099 details='basic'),
3101 details='basic'),
3100 'reviewers': [
3102 'reviewers': [
3101 {
3103 {
3102 'user': reviewer.get_api_data(include_secrets=False,
3104 'user': reviewer.get_api_data(include_secrets=False,
3103 details='basic'),
3105 details='basic'),
3104 'review_status': st[0][1].status if st else 'not_reviewed',
3106 'review_status': st[0][1].status if st else 'not_reviewed',
3105 }
3107 }
3106 for reviewer, st in pull_request.reviewers_statuses()
3108 for reviewer, st in pull_request.reviewers_statuses()
3107 ]
3109 ]
3108 }
3110 }
3109
3111
3110 return data
3112 return data
3111
3113
3112 def __json__(self):
3114 def __json__(self):
3113 return {
3115 return {
3114 'revisions': self.revisions,
3116 'revisions': self.revisions,
3115 }
3117 }
3116
3118
3117 def calculated_review_status(self):
3119 def calculated_review_status(self):
3118 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3120 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3119 # because it's tricky on how to use ChangesetStatusModel from there
3121 # because it's tricky on how to use ChangesetStatusModel from there
3120 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3122 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3121 from rhodecode.model.changeset_status import ChangesetStatusModel
3123 from rhodecode.model.changeset_status import ChangesetStatusModel
3122 return ChangesetStatusModel().calculated_review_status(self)
3124 return ChangesetStatusModel().calculated_review_status(self)
3123
3125
3124 def reviewers_statuses(self):
3126 def reviewers_statuses(self):
3125 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3127 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3126 from rhodecode.model.changeset_status import ChangesetStatusModel
3128 from rhodecode.model.changeset_status import ChangesetStatusModel
3127 return ChangesetStatusModel().reviewers_statuses(self)
3129 return ChangesetStatusModel().reviewers_statuses(self)
3128
3130
3129
3131
3130 class PullRequestVersion(Base, _PullRequestBase):
3132 class PullRequestVersion(Base, _PullRequestBase):
3131 __tablename__ = 'pull_request_versions'
3133 __tablename__ = 'pull_request_versions'
3132 __table_args__ = (
3134 __table_args__ = (
3133 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3135 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3134 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3136 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3135 )
3137 )
3136
3138
3137 pull_request_version_id = Column(
3139 pull_request_version_id = Column(
3138 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3140 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3139 pull_request_id = Column(
3141 pull_request_id = Column(
3140 'pull_request_id', Integer(),
3142 'pull_request_id', Integer(),
3141 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3143 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3142 pull_request = relationship('PullRequest')
3144 pull_request = relationship('PullRequest')
3143
3145
3144 def __repr__(self):
3146 def __repr__(self):
3145 if self.pull_request_version_id:
3147 if self.pull_request_version_id:
3146 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3148 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3147 else:
3149 else:
3148 return '<DB:PullRequestVersion at %#x>' % id(self)
3150 return '<DB:PullRequestVersion at %#x>' % id(self)
3149
3151
3150
3152
3151 class PullRequestReviewers(Base, BaseModel):
3153 class PullRequestReviewers(Base, BaseModel):
3152 __tablename__ = 'pull_request_reviewers'
3154 __tablename__ = 'pull_request_reviewers'
3153 __table_args__ = (
3155 __table_args__ = (
3154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3156 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3155 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3157 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3156 )
3158 )
3157
3159
3158 def __init__(self, user=None, pull_request=None):
3160 def __init__(self, user=None, pull_request=None):
3159 self.user = user
3161 self.user = user
3160 self.pull_request = pull_request
3162 self.pull_request = pull_request
3161
3163
3162 pull_requests_reviewers_id = Column(
3164 pull_requests_reviewers_id = Column(
3163 'pull_requests_reviewers_id', Integer(), nullable=False,
3165 'pull_requests_reviewers_id', Integer(), nullable=False,
3164 primary_key=True)
3166 primary_key=True)
3165 pull_request_id = Column(
3167 pull_request_id = Column(
3166 "pull_request_id", Integer(),
3168 "pull_request_id", Integer(),
3167 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3169 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3168 user_id = Column(
3170 user_id = Column(
3169 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3171 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3170
3172
3171 user = relationship('User')
3173 user = relationship('User')
3172 pull_request = relationship('PullRequest')
3174 pull_request = relationship('PullRequest')
3173
3175
3174
3176
3175 class Notification(Base, BaseModel):
3177 class Notification(Base, BaseModel):
3176 __tablename__ = 'notifications'
3178 __tablename__ = 'notifications'
3177 __table_args__ = (
3179 __table_args__ = (
3178 Index('notification_type_idx', 'type'),
3180 Index('notification_type_idx', 'type'),
3179 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3181 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3180 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3182 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3181 )
3183 )
3182
3184
3183 TYPE_CHANGESET_COMMENT = u'cs_comment'
3185 TYPE_CHANGESET_COMMENT = u'cs_comment'
3184 TYPE_MESSAGE = u'message'
3186 TYPE_MESSAGE = u'message'
3185 TYPE_MENTION = u'mention'
3187 TYPE_MENTION = u'mention'
3186 TYPE_REGISTRATION = u'registration'
3188 TYPE_REGISTRATION = u'registration'
3187 TYPE_PULL_REQUEST = u'pull_request'
3189 TYPE_PULL_REQUEST = u'pull_request'
3188 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3190 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3189
3191
3190 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3192 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3191 subject = Column('subject', Unicode(512), nullable=True)
3193 subject = Column('subject', Unicode(512), nullable=True)
3192 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3194 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3193 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3195 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3194 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3196 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3195 type_ = Column('type', Unicode(255))
3197 type_ = Column('type', Unicode(255))
3196
3198
3197 created_by_user = relationship('User')
3199 created_by_user = relationship('User')
3198 notifications_to_users = relationship('UserNotification', lazy='joined',
3200 notifications_to_users = relationship('UserNotification', lazy='joined',
3199 cascade="all, delete, delete-orphan")
3201 cascade="all, delete, delete-orphan")
3200
3202
3201 @property
3203 @property
3202 def recipients(self):
3204 def recipients(self):
3203 return [x.user for x in UserNotification.query()\
3205 return [x.user for x in UserNotification.query()\
3204 .filter(UserNotification.notification == self)\
3206 .filter(UserNotification.notification == self)\
3205 .order_by(UserNotification.user_id.asc()).all()]
3207 .order_by(UserNotification.user_id.asc()).all()]
3206
3208
3207 @classmethod
3209 @classmethod
3208 def create(cls, created_by, subject, body, recipients, type_=None):
3210 def create(cls, created_by, subject, body, recipients, type_=None):
3209 if type_ is None:
3211 if type_ is None:
3210 type_ = Notification.TYPE_MESSAGE
3212 type_ = Notification.TYPE_MESSAGE
3211
3213
3212 notification = cls()
3214 notification = cls()
3213 notification.created_by_user = created_by
3215 notification.created_by_user = created_by
3214 notification.subject = subject
3216 notification.subject = subject
3215 notification.body = body
3217 notification.body = body
3216 notification.type_ = type_
3218 notification.type_ = type_
3217 notification.created_on = datetime.datetime.now()
3219 notification.created_on = datetime.datetime.now()
3218
3220
3219 for u in recipients:
3221 for u in recipients:
3220 assoc = UserNotification()
3222 assoc = UserNotification()
3221 assoc.notification = notification
3223 assoc.notification = notification
3222
3224
3223 # if created_by is inside recipients mark his notification
3225 # if created_by is inside recipients mark his notification
3224 # as read
3226 # as read
3225 if u.user_id == created_by.user_id:
3227 if u.user_id == created_by.user_id:
3226 assoc.read = True
3228 assoc.read = True
3227
3229
3228 u.notifications.append(assoc)
3230 u.notifications.append(assoc)
3229 Session().add(notification)
3231 Session().add(notification)
3230
3232
3231 return notification
3233 return notification
3232
3234
3233 @property
3235 @property
3234 def description(self):
3236 def description(self):
3235 from rhodecode.model.notification import NotificationModel
3237 from rhodecode.model.notification import NotificationModel
3236 return NotificationModel().make_description(self)
3238 return NotificationModel().make_description(self)
3237
3239
3238
3240
3239 class UserNotification(Base, BaseModel):
3241 class UserNotification(Base, BaseModel):
3240 __tablename__ = 'user_to_notification'
3242 __tablename__ = 'user_to_notification'
3241 __table_args__ = (
3243 __table_args__ = (
3242 UniqueConstraint('user_id', 'notification_id'),
3244 UniqueConstraint('user_id', 'notification_id'),
3243 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3245 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3244 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3246 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3245 )
3247 )
3246 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3248 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3247 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3249 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3248 read = Column('read', Boolean, default=False)
3250 read = Column('read', Boolean, default=False)
3249 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3251 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3250
3252
3251 user = relationship('User', lazy="joined")
3253 user = relationship('User', lazy="joined")
3252 notification = relationship('Notification', lazy="joined",
3254 notification = relationship('Notification', lazy="joined",
3253 order_by=lambda: Notification.created_on.desc(),)
3255 order_by=lambda: Notification.created_on.desc(),)
3254
3256
3255 def mark_as_read(self):
3257 def mark_as_read(self):
3256 self.read = True
3258 self.read = True
3257 Session().add(self)
3259 Session().add(self)
3258
3260
3259
3261
3260 class Gist(Base, BaseModel):
3262 class Gist(Base, BaseModel):
3261 __tablename__ = 'gists'
3263 __tablename__ = 'gists'
3262 __table_args__ = (
3264 __table_args__ = (
3263 Index('g_gist_access_id_idx', 'gist_access_id'),
3265 Index('g_gist_access_id_idx', 'gist_access_id'),
3264 Index('g_created_on_idx', 'created_on'),
3266 Index('g_created_on_idx', 'created_on'),
3265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3267 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3268 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3267 )
3269 )
3268 GIST_PUBLIC = u'public'
3270 GIST_PUBLIC = u'public'
3269 GIST_PRIVATE = u'private'
3271 GIST_PRIVATE = u'private'
3270 DEFAULT_FILENAME = u'gistfile1.txt'
3272 DEFAULT_FILENAME = u'gistfile1.txt'
3271
3273
3272 ACL_LEVEL_PUBLIC = u'acl_public'
3274 ACL_LEVEL_PUBLIC = u'acl_public'
3273 ACL_LEVEL_PRIVATE = u'acl_private'
3275 ACL_LEVEL_PRIVATE = u'acl_private'
3274
3276
3275 gist_id = Column('gist_id', Integer(), primary_key=True)
3277 gist_id = Column('gist_id', Integer(), primary_key=True)
3276 gist_access_id = Column('gist_access_id', Unicode(250))
3278 gist_access_id = Column('gist_access_id', Unicode(250))
3277 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3279 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3278 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3280 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3279 gist_expires = Column('gist_expires', Float(53), nullable=False)
3281 gist_expires = Column('gist_expires', Float(53), nullable=False)
3280 gist_type = Column('gist_type', Unicode(128), nullable=False)
3282 gist_type = Column('gist_type', Unicode(128), nullable=False)
3281 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3283 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3282 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3284 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3283 acl_level = Column('acl_level', Unicode(128), nullable=True)
3285 acl_level = Column('acl_level', Unicode(128), nullable=True)
3284
3286
3285 owner = relationship('User')
3287 owner = relationship('User')
3286
3288
3287 def __repr__(self):
3289 def __repr__(self):
3288 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3290 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3289
3291
3290 @classmethod
3292 @classmethod
3291 def get_or_404(cls, id_):
3293 def get_or_404(cls, id_):
3292 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3294 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3293 if not res:
3295 if not res:
3294 raise HTTPNotFound
3296 raise HTTPNotFound
3295 return res
3297 return res
3296
3298
3297 @classmethod
3299 @classmethod
3298 def get_by_access_id(cls, gist_access_id):
3300 def get_by_access_id(cls, gist_access_id):
3299 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3301 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3300
3302
3301 def gist_url(self):
3303 def gist_url(self):
3302 import rhodecode
3304 import rhodecode
3303 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3305 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3304 if alias_url:
3306 if alias_url:
3305 return alias_url.replace('{gistid}', self.gist_access_id)
3307 return alias_url.replace('{gistid}', self.gist_access_id)
3306
3308
3307 return url('gist', gist_id=self.gist_access_id, qualified=True)
3309 return url('gist', gist_id=self.gist_access_id, qualified=True)
3308
3310
3309 @classmethod
3311 @classmethod
3310 def base_path(cls):
3312 def base_path(cls):
3311 """
3313 """
3312 Returns base path when all gists are stored
3314 Returns base path when all gists are stored
3313
3315
3314 :param cls:
3316 :param cls:
3315 """
3317 """
3316 from rhodecode.model.gist import GIST_STORE_LOC
3318 from rhodecode.model.gist import GIST_STORE_LOC
3317 q = Session().query(RhodeCodeUi)\
3319 q = Session().query(RhodeCodeUi)\
3318 .filter(RhodeCodeUi.ui_key == URL_SEP)
3320 .filter(RhodeCodeUi.ui_key == URL_SEP)
3319 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3321 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3320 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3322 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3321
3323
3322 def get_api_data(self):
3324 def get_api_data(self):
3323 """
3325 """
3324 Common function for generating gist related data for API
3326 Common function for generating gist related data for API
3325 """
3327 """
3326 gist = self
3328 gist = self
3327 data = {
3329 data = {
3328 'gist_id': gist.gist_id,
3330 'gist_id': gist.gist_id,
3329 'type': gist.gist_type,
3331 'type': gist.gist_type,
3330 'access_id': gist.gist_access_id,
3332 'access_id': gist.gist_access_id,
3331 'description': gist.gist_description,
3333 'description': gist.gist_description,
3332 'url': gist.gist_url(),
3334 'url': gist.gist_url(),
3333 'expires': gist.gist_expires,
3335 'expires': gist.gist_expires,
3334 'created_on': gist.created_on,
3336 'created_on': gist.created_on,
3335 'modified_at': gist.modified_at,
3337 'modified_at': gist.modified_at,
3336 'content': None,
3338 'content': None,
3337 'acl_level': gist.acl_level,
3339 'acl_level': gist.acl_level,
3338 }
3340 }
3339 return data
3341 return data
3340
3342
3341 def __json__(self):
3343 def __json__(self):
3342 data = dict(
3344 data = dict(
3343 )
3345 )
3344 data.update(self.get_api_data())
3346 data.update(self.get_api_data())
3345 return data
3347 return data
3346 # SCM functions
3348 # SCM functions
3347
3349
3348 def scm_instance(self, **kwargs):
3350 def scm_instance(self, **kwargs):
3349 from rhodecode.lib.vcs import get_repo
3351 from rhodecode.lib.vcs import get_repo
3350 base_path = self.base_path()
3352 base_path = self.base_path()
3351 return get_repo(os.path.join(*map(safe_str,
3353 return get_repo(os.path.join(*map(safe_str,
3352 [base_path, self.gist_access_id])))
3354 [base_path, self.gist_access_id])))
3353
3355
3354
3356
3355 class DbMigrateVersion(Base, BaseModel):
3357 class DbMigrateVersion(Base, BaseModel):
3356 __tablename__ = 'db_migrate_version'
3358 __tablename__ = 'db_migrate_version'
3357 __table_args__ = (
3359 __table_args__ = (
3358 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3360 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3359 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3361 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3360 )
3362 )
3361 repository_id = Column('repository_id', String(250), primary_key=True)
3363 repository_id = Column('repository_id', String(250), primary_key=True)
3362 repository_path = Column('repository_path', Text)
3364 repository_path = Column('repository_path', Text)
3363 version = Column('version', Integer)
3365 version = Column('version', Integer)
3364
3366
3365
3367
3366 class ExternalIdentity(Base, BaseModel):
3368 class ExternalIdentity(Base, BaseModel):
3367 __tablename__ = 'external_identities'
3369 __tablename__ = 'external_identities'
3368 __table_args__ = (
3370 __table_args__ = (
3369 Index('local_user_id_idx', 'local_user_id'),
3371 Index('local_user_id_idx', 'local_user_id'),
3370 Index('external_id_idx', 'external_id'),
3372 Index('external_id_idx', 'external_id'),
3371 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3373 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3372 'mysql_charset': 'utf8'})
3374 'mysql_charset': 'utf8'})
3373
3375
3374 external_id = Column('external_id', Unicode(255), default=u'',
3376 external_id = Column('external_id', Unicode(255), default=u'',
3375 primary_key=True)
3377 primary_key=True)
3376 external_username = Column('external_username', Unicode(1024), default=u'')
3378 external_username = Column('external_username', Unicode(1024), default=u'')
3377 local_user_id = Column('local_user_id', Integer(),
3379 local_user_id = Column('local_user_id', Integer(),
3378 ForeignKey('users.user_id'), primary_key=True)
3380 ForeignKey('users.user_id'), primary_key=True)
3379 provider_name = Column('provider_name', Unicode(255), default=u'',
3381 provider_name = Column('provider_name', Unicode(255), default=u'',
3380 primary_key=True)
3382 primary_key=True)
3381 access_token = Column('access_token', String(1024), default=u'')
3383 access_token = Column('access_token', String(1024), default=u'')
3382 alt_token = Column('alt_token', String(1024), default=u'')
3384 alt_token = Column('alt_token', String(1024), default=u'')
3383 token_secret = Column('token_secret', String(1024), default=u'')
3385 token_secret = Column('token_secret', String(1024), default=u'')
3384
3386
3385 @classmethod
3387 @classmethod
3386 def by_external_id_and_provider(cls, external_id, provider_name,
3388 def by_external_id_and_provider(cls, external_id, provider_name,
3387 local_user_id=None):
3389 local_user_id=None):
3388 """
3390 """
3389 Returns ExternalIdentity instance based on search params
3391 Returns ExternalIdentity instance based on search params
3390
3392
3391 :param external_id:
3393 :param external_id:
3392 :param provider_name:
3394 :param provider_name:
3393 :return: ExternalIdentity
3395 :return: ExternalIdentity
3394 """
3396 """
3395 query = cls.query()
3397 query = cls.query()
3396 query = query.filter(cls.external_id == external_id)
3398 query = query.filter(cls.external_id == external_id)
3397 query = query.filter(cls.provider_name == provider_name)
3399 query = query.filter(cls.provider_name == provider_name)
3398 if local_user_id:
3400 if local_user_id:
3399 query = query.filter(cls.local_user_id == local_user_id)
3401 query = query.filter(cls.local_user_id == local_user_id)
3400 return query.first()
3402 return query.first()
3401
3403
3402 @classmethod
3404 @classmethod
3403 def user_by_external_id_and_provider(cls, external_id, provider_name):
3405 def user_by_external_id_and_provider(cls, external_id, provider_name):
3404 """
3406 """
3405 Returns User instance based on search params
3407 Returns User instance based on search params
3406
3408
3407 :param external_id:
3409 :param external_id:
3408 :param provider_name:
3410 :param provider_name:
3409 :return: User
3411 :return: User
3410 """
3412 """
3411 query = User.query()
3413 query = User.query()
3412 query = query.filter(cls.external_id == external_id)
3414 query = query.filter(cls.external_id == external_id)
3413 query = query.filter(cls.provider_name == provider_name)
3415 query = query.filter(cls.provider_name == provider_name)
3414 query = query.filter(User.user_id == cls.local_user_id)
3416 query = query.filter(User.user_id == cls.local_user_id)
3415 return query.first()
3417 return query.first()
3416
3418
3417 @classmethod
3419 @classmethod
3418 def by_local_user_id(cls, local_user_id):
3420 def by_local_user_id(cls, local_user_id):
3419 """
3421 """
3420 Returns all tokens for user
3422 Returns all tokens for user
3421
3423
3422 :param local_user_id:
3424 :param local_user_id:
3423 :return: ExternalIdentity
3425 :return: ExternalIdentity
3424 """
3426 """
3425 query = cls.query()
3427 query = cls.query()
3426 query = query.filter(cls.local_user_id == local_user_id)
3428 query = query.filter(cls.local_user_id == local_user_id)
3427 return query
3429 return query
General Comments 0
You need to be logged in to leave comments. Login now