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