##// END OF EJS Templates
Moved inject ui into base vcs classe
marcink -
r3477:951aa274 beta
parent child Browse files
Show More
@@ -1,997 +1,1018 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 vcs.backends.base
3 vcs.backends.base
4 ~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~
5
5
6 Base for all available scm backends
6 Base for all available scm backends
7
7
8 :created_on: Apr 8, 2010
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
10 """
11
11
12
12
13 from itertools import chain
13 from itertools import chain
14 from rhodecode.lib.vcs.utils import author_name, author_email
14 from rhodecode.lib.vcs.utils import author_name, author_email
15 from rhodecode.lib.vcs.utils.lazy import LazyProperty
15 from rhodecode.lib.vcs.utils.lazy import LazyProperty
16 from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs
16 from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs
17 from rhodecode.lib.vcs.conf import settings
17 from rhodecode.lib.vcs.conf import settings
18
18
19 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
19 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
20 NodeAlreadyAddedError, NodeAlreadyChangedError, NodeAlreadyExistsError, \
20 NodeAlreadyAddedError, NodeAlreadyChangedError, NodeAlreadyExistsError, \
21 NodeAlreadyRemovedError, NodeDoesNotExistError, NodeNotChangedError, \
21 NodeAlreadyRemovedError, NodeDoesNotExistError, NodeNotChangedError, \
22 RepositoryError
22 RepositoryError
23
23
24
24
25 class BaseRepository(object):
25 class BaseRepository(object):
26 """
26 """
27 Base Repository for final backends
27 Base Repository for final backends
28
28
29 **Attributes**
29 **Attributes**
30
30
31 ``DEFAULT_BRANCH_NAME``
31 ``DEFAULT_BRANCH_NAME``
32 name of default branch (i.e. "trunk" for svn, "master" for git etc.
32 name of default branch (i.e. "trunk" for svn, "master" for git etc.
33
33
34 ``scm``
34 ``scm``
35 alias of scm, i.e. *git* or *hg*
35 alias of scm, i.e. *git* or *hg*
36
36
37 ``repo``
37 ``repo``
38 object from external api
38 object from external api
39
39
40 ``revisions``
40 ``revisions``
41 list of all available revisions' ids, in ascending order
41 list of all available revisions' ids, in ascending order
42
42
43 ``changesets``
43 ``changesets``
44 storage dict caching returned changesets
44 storage dict caching returned changesets
45
45
46 ``path``
46 ``path``
47 absolute path to the repository
47 absolute path to the repository
48
48
49 ``branches``
49 ``branches``
50 branches as list of changesets
50 branches as list of changesets
51
51
52 ``tags``
52 ``tags``
53 tags as list of changesets
53 tags as list of changesets
54 """
54 """
55 scm = None
55 scm = None
56 DEFAULT_BRANCH_NAME = None
56 DEFAULT_BRANCH_NAME = None
57 EMPTY_CHANGESET = '0' * 40
57 EMPTY_CHANGESET = '0' * 40
58
58
59 def __init__(self, repo_path, create=False, **kwargs):
59 def __init__(self, repo_path, create=False, **kwargs):
60 """
60 """
61 Initializes repository. Raises RepositoryError if repository could
61 Initializes repository. Raises RepositoryError if repository could
62 not be find at the given ``repo_path`` or directory at ``repo_path``
62 not be find at the given ``repo_path`` or directory at ``repo_path``
63 exists and ``create`` is set to True.
63 exists and ``create`` is set to True.
64
64
65 :param repo_path: local path of the repository
65 :param repo_path: local path of the repository
66 :param create=False: if set to True, would try to craete repository.
66 :param create=False: if set to True, would try to craete repository.
67 :param src_url=None: if set, should be proper url from which repository
67 :param src_url=None: if set, should be proper url from which repository
68 would be cloned; requires ``create`` parameter to be set to True -
68 would be cloned; requires ``create`` parameter to be set to True -
69 raises RepositoryError if src_url is set and create evaluates to
69 raises RepositoryError if src_url is set and create evaluates to
70 False
70 False
71 """
71 """
72 raise NotImplementedError
72 raise NotImplementedError
73
73
74 def __str__(self):
74 def __str__(self):
75 return '<%s at %s>' % (self.__class__.__name__, self.path)
75 return '<%s at %s>' % (self.__class__.__name__, self.path)
76
76
77 def __repr__(self):
77 def __repr__(self):
78 return self.__str__()
78 return self.__str__()
79
79
80 def __len__(self):
80 def __len__(self):
81 return self.count()
81 return self.count()
82
82
83 @LazyProperty
83 @LazyProperty
84 def alias(self):
84 def alias(self):
85 for k, v in settings.BACKENDS.items():
85 for k, v in settings.BACKENDS.items():
86 if v.split('.')[-1] == str(self.__class__.__name__):
86 if v.split('.')[-1] == str(self.__class__.__name__):
87 return k
87 return k
88
88
89 @LazyProperty
89 @LazyProperty
90 def name(self):
90 def name(self):
91 raise NotImplementedError
91 raise NotImplementedError
92
92
93 @LazyProperty
93 @LazyProperty
94 def owner(self):
94 def owner(self):
95 raise NotImplementedError
95 raise NotImplementedError
96
96
97 @LazyProperty
97 @LazyProperty
98 def description(self):
98 def description(self):
99 raise NotImplementedError
99 raise NotImplementedError
100
100
101 @LazyProperty
101 @LazyProperty
102 def size(self):
102 def size(self):
103 """
103 """
104 Returns combined size in bytes for all repository files
104 Returns combined size in bytes for all repository files
105 """
105 """
106
106
107 size = 0
107 size = 0
108 try:
108 try:
109 tip = self.get_changeset()
109 tip = self.get_changeset()
110 for topnode, dirs, files in tip.walk('/'):
110 for topnode, dirs, files in tip.walk('/'):
111 for f in files:
111 for f in files:
112 size += tip.get_file_size(f.path)
112 size += tip.get_file_size(f.path)
113 for dir in dirs:
113 for dir in dirs:
114 for f in files:
114 for f in files:
115 size += tip.get_file_size(f.path)
115 size += tip.get_file_size(f.path)
116
116
117 except RepositoryError, e:
117 except RepositoryError, e:
118 pass
118 pass
119 return size
119 return size
120
120
121 def is_valid(self):
121 def is_valid(self):
122 """
122 """
123 Validates repository.
123 Validates repository.
124 """
124 """
125 raise NotImplementedError
125 raise NotImplementedError
126
126
127 def get_last_change(self):
127 def get_last_change(self):
128 self.get_changesets()
128 self.get_changesets()
129
129
130 #==========================================================================
130 #==========================================================================
131 # CHANGESETS
131 # CHANGESETS
132 #==========================================================================
132 #==========================================================================
133
133
134 def get_changeset(self, revision=None):
134 def get_changeset(self, revision=None):
135 """
135 """
136 Returns instance of ``Changeset`` class. If ``revision`` is None, most
136 Returns instance of ``Changeset`` class. If ``revision`` is None, most
137 recent changeset is returned.
137 recent changeset is returned.
138
138
139 :raises ``EmptyRepositoryError``: if there are no revisions
139 :raises ``EmptyRepositoryError``: if there are no revisions
140 """
140 """
141 raise NotImplementedError
141 raise NotImplementedError
142
142
143 def __iter__(self):
143 def __iter__(self):
144 """
144 """
145 Allows Repository objects to be iterated.
145 Allows Repository objects to be iterated.
146
146
147 *Requires* implementation of ``__getitem__`` method.
147 *Requires* implementation of ``__getitem__`` method.
148 """
148 """
149 for revision in self.revisions:
149 for revision in self.revisions:
150 yield self.get_changeset(revision)
150 yield self.get_changeset(revision)
151
151
152 def get_changesets(self, start=None, end=None, start_date=None,
152 def get_changesets(self, start=None, end=None, start_date=None,
153 end_date=None, branch_name=None, reverse=False):
153 end_date=None, branch_name=None, reverse=False):
154 """
154 """
155 Returns iterator of ``MercurialChangeset`` objects from start to end
155 Returns iterator of ``MercurialChangeset`` objects from start to end
156 not inclusive This should behave just like a list, ie. end is not
156 not inclusive This should behave just like a list, ie. end is not
157 inclusive
157 inclusive
158
158
159 :param start: None or str
159 :param start: None or str
160 :param end: None or str
160 :param end: None or str
161 :param start_date:
161 :param start_date:
162 :param end_date:
162 :param end_date:
163 :param branch_name:
163 :param branch_name:
164 :param reversed:
164 :param reversed:
165 """
165 """
166 raise NotImplementedError
166 raise NotImplementedError
167
167
168 def __getslice__(self, i, j):
168 def __getslice__(self, i, j):
169 """
169 """
170 Returns a iterator of sliced repository
170 Returns a iterator of sliced repository
171 """
171 """
172 for rev in self.revisions[i:j]:
172 for rev in self.revisions[i:j]:
173 yield self.get_changeset(rev)
173 yield self.get_changeset(rev)
174
174
175 def __getitem__(self, key):
175 def __getitem__(self, key):
176 return self.get_changeset(key)
176 return self.get_changeset(key)
177
177
178 def count(self):
178 def count(self):
179 return len(self.revisions)
179 return len(self.revisions)
180
180
181 def tag(self, name, user, revision=None, message=None, date=None, **opts):
181 def tag(self, name, user, revision=None, message=None, date=None, **opts):
182 """
182 """
183 Creates and returns a tag for the given ``revision``.
183 Creates and returns a tag for the given ``revision``.
184
184
185 :param name: name for new tag
185 :param name: name for new tag
186 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
186 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
187 :param revision: changeset id for which new tag would be created
187 :param revision: changeset id for which new tag would be created
188 :param message: message of the tag's commit
188 :param message: message of the tag's commit
189 :param date: date of tag's commit
189 :param date: date of tag's commit
190
190
191 :raises TagAlreadyExistError: if tag with same name already exists
191 :raises TagAlreadyExistError: if tag with same name already exists
192 """
192 """
193 raise NotImplementedError
193 raise NotImplementedError
194
194
195 def remove_tag(self, name, user, message=None, date=None):
195 def remove_tag(self, name, user, message=None, date=None):
196 """
196 """
197 Removes tag with the given ``name``.
197 Removes tag with the given ``name``.
198
198
199 :param name: name of the tag to be removed
199 :param name: name of the tag to be removed
200 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
200 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
201 :param message: message of the tag's removal commit
201 :param message: message of the tag's removal commit
202 :param date: date of tag's removal commit
202 :param date: date of tag's removal commit
203
203
204 :raises TagDoesNotExistError: if tag with given name does not exists
204 :raises TagDoesNotExistError: if tag with given name does not exists
205 """
205 """
206 raise NotImplementedError
206 raise NotImplementedError
207
207
208 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
208 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
209 context=3):
209 context=3):
210 """
210 """
211 Returns (git like) *diff*, as plain text. Shows changes introduced by
211 Returns (git like) *diff*, as plain text. Shows changes introduced by
212 ``rev2`` since ``rev1``.
212 ``rev2`` since ``rev1``.
213
213
214 :param rev1: Entry point from which diff is shown. Can be
214 :param rev1: Entry point from which diff is shown. Can be
215 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
215 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
216 the changes since empty state of the repository until ``rev2``
216 the changes since empty state of the repository until ``rev2``
217 :param rev2: Until which revision changes should be shown.
217 :param rev2: Until which revision changes should be shown.
218 :param ignore_whitespace: If set to ``True``, would not show whitespace
218 :param ignore_whitespace: If set to ``True``, would not show whitespace
219 changes. Defaults to ``False``.
219 changes. Defaults to ``False``.
220 :param context: How many lines before/after changed lines should be
220 :param context: How many lines before/after changed lines should be
221 shown. Defaults to ``3``.
221 shown. Defaults to ``3``.
222 """
222 """
223 raise NotImplementedError
223 raise NotImplementedError
224
224
225 # ========== #
225 # ========== #
226 # COMMIT API #
226 # COMMIT API #
227 # ========== #
227 # ========== #
228
228
229 @LazyProperty
229 @LazyProperty
230 def in_memory_changeset(self):
230 def in_memory_changeset(self):
231 """
231 """
232 Returns ``InMemoryChangeset`` object for this repository.
232 Returns ``InMemoryChangeset`` object for this repository.
233 """
233 """
234 raise NotImplementedError
234 raise NotImplementedError
235
235
236 def add(self, filenode, **kwargs):
236 def add(self, filenode, **kwargs):
237 """
237 """
238 Commit api function that will add given ``FileNode`` into this
238 Commit api function that will add given ``FileNode`` into this
239 repository.
239 repository.
240
240
241 :raises ``NodeAlreadyExistsError``: if there is a file with same path
241 :raises ``NodeAlreadyExistsError``: if there is a file with same path
242 already in repository
242 already in repository
243 :raises ``NodeAlreadyAddedError``: if given node is already marked as
243 :raises ``NodeAlreadyAddedError``: if given node is already marked as
244 *added*
244 *added*
245 """
245 """
246 raise NotImplementedError
246 raise NotImplementedError
247
247
248 def remove(self, filenode, **kwargs):
248 def remove(self, filenode, **kwargs):
249 """
249 """
250 Commit api function that will remove given ``FileNode`` into this
250 Commit api function that will remove given ``FileNode`` into this
251 repository.
251 repository.
252
252
253 :raises ``EmptyRepositoryError``: if there are no changesets yet
253 :raises ``EmptyRepositoryError``: if there are no changesets yet
254 :raises ``NodeDoesNotExistError``: if there is no file with given path
254 :raises ``NodeDoesNotExistError``: if there is no file with given path
255 """
255 """
256 raise NotImplementedError
256 raise NotImplementedError
257
257
258 def commit(self, message, **kwargs):
258 def commit(self, message, **kwargs):
259 """
259 """
260 Persists current changes made on this repository and returns newly
260 Persists current changes made on this repository and returns newly
261 created changeset.
261 created changeset.
262
262
263 :raises ``NothingChangedError``: if no changes has been made
263 :raises ``NothingChangedError``: if no changes has been made
264 """
264 """
265 raise NotImplementedError
265 raise NotImplementedError
266
266
267 def get_state(self):
267 def get_state(self):
268 """
268 """
269 Returns dictionary with ``added``, ``changed`` and ``removed`` lists
269 Returns dictionary with ``added``, ``changed`` and ``removed`` lists
270 containing ``FileNode`` objects.
270 containing ``FileNode`` objects.
271 """
271 """
272 raise NotImplementedError
272 raise NotImplementedError
273
273
274 def get_config_value(self, section, name, config_file=None):
274 def get_config_value(self, section, name, config_file=None):
275 """
275 """
276 Returns configuration value for a given [``section``] and ``name``.
276 Returns configuration value for a given [``section``] and ``name``.
277
277
278 :param section: Section we want to retrieve value from
278 :param section: Section we want to retrieve value from
279 :param name: Name of configuration we want to retrieve
279 :param name: Name of configuration we want to retrieve
280 :param config_file: A path to file which should be used to retrieve
280 :param config_file: A path to file which should be used to retrieve
281 configuration from (might also be a list of file paths)
281 configuration from (might also be a list of file paths)
282 """
282 """
283 raise NotImplementedError
283 raise NotImplementedError
284
284
285 def get_user_name(self, config_file=None):
285 def get_user_name(self, config_file=None):
286 """
286 """
287 Returns user's name from global configuration file.
287 Returns user's name from global configuration file.
288
288
289 :param config_file: A path to file which should be used to retrieve
289 :param config_file: A path to file which should be used to retrieve
290 configuration from (might also be a list of file paths)
290 configuration from (might also be a list of file paths)
291 """
291 """
292 raise NotImplementedError
292 raise NotImplementedError
293
293
294 def get_user_email(self, config_file=None):
294 def get_user_email(self, config_file=None):
295 """
295 """
296 Returns user's email from global configuration file.
296 Returns user's email from global configuration file.
297
297
298 :param config_file: A path to file which should be used to retrieve
298 :param config_file: A path to file which should be used to retrieve
299 configuration from (might also be a list of file paths)
299 configuration from (might also be a list of file paths)
300 """
300 """
301 raise NotImplementedError
301 raise NotImplementedError
302
302
303 # =========== #
303 # =========== #
304 # WORKDIR API #
304 # WORKDIR API #
305 # =========== #
305 # =========== #
306
306
307 @LazyProperty
307 @LazyProperty
308 def workdir(self):
308 def workdir(self):
309 """
309 """
310 Returns ``Workdir`` instance for this repository.
310 Returns ``Workdir`` instance for this repository.
311 """
311 """
312 raise NotImplementedError
312 raise NotImplementedError
313
313
314 def inject_ui(self, **extras):
315 """
316 Injects extra parameters into UI object of this repo
317 """
318 required_extras = [
319 'ip',
320 'username',
321 'action',
322 'repository',
323 'scm',
324 'config',
325 'server_url',
326 'make_lock',
327 'locked_by',
328 ]
329 for req in required_extras:
330 if req not in extras:
331 raise AttributeError('Missing attribute %s in extras' % (req))
332 for k, v in extras.items():
333 self._repo.ui.setconfig('rhodecode_extras', k, v)
334
314
335
315 class BaseChangeset(object):
336 class BaseChangeset(object):
316 """
337 """
317 Each backend should implement it's changeset representation.
338 Each backend should implement it's changeset representation.
318
339
319 **Attributes**
340 **Attributes**
320
341
321 ``repository``
342 ``repository``
322 repository object within which changeset exists
343 repository object within which changeset exists
323
344
324 ``id``
345 ``id``
325 may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
346 may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
326
347
327 ``raw_id``
348 ``raw_id``
328 raw changeset representation (i.e. full 40 length sha for git
349 raw changeset representation (i.e. full 40 length sha for git
329 backend)
350 backend)
330
351
331 ``short_id``
352 ``short_id``
332 shortened (if apply) version of ``raw_id``; it would be simple
353 shortened (if apply) version of ``raw_id``; it would be simple
333 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
354 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
334 as ``raw_id`` for subversion
355 as ``raw_id`` for subversion
335
356
336 ``revision``
357 ``revision``
337 revision number as integer
358 revision number as integer
338
359
339 ``files``
360 ``files``
340 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
361 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
341
362
342 ``dirs``
363 ``dirs``
343 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
364 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
344
365
345 ``nodes``
366 ``nodes``
346 combined list of ``Node`` objects
367 combined list of ``Node`` objects
347
368
348 ``author``
369 ``author``
349 author of the changeset, as unicode
370 author of the changeset, as unicode
350
371
351 ``message``
372 ``message``
352 message of the changeset, as unicode
373 message of the changeset, as unicode
353
374
354 ``parents``
375 ``parents``
355 list of parent changesets
376 list of parent changesets
356
377
357 ``last``
378 ``last``
358 ``True`` if this is last changeset in repository, ``False``
379 ``True`` if this is last changeset in repository, ``False``
359 otherwise; trying to access this attribute while there is no
380 otherwise; trying to access this attribute while there is no
360 changesets would raise ``EmptyRepositoryError``
381 changesets would raise ``EmptyRepositoryError``
361 """
382 """
362 def __str__(self):
383 def __str__(self):
363 return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
384 return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
364 self.short_id)
385 self.short_id)
365
386
366 def __repr__(self):
387 def __repr__(self):
367 return self.__str__()
388 return self.__str__()
368
389
369 def __unicode__(self):
390 def __unicode__(self):
370 return u'%s:%s' % (self.revision, self.short_id)
391 return u'%s:%s' % (self.revision, self.short_id)
371
392
372 def __eq__(self, other):
393 def __eq__(self, other):
373 return self.raw_id == other.raw_id
394 return self.raw_id == other.raw_id
374
395
375 def __json__(self):
396 def __json__(self):
376 return dict(
397 return dict(
377 short_id=self.short_id,
398 short_id=self.short_id,
378 raw_id=self.raw_id,
399 raw_id=self.raw_id,
379 revision=self.revision,
400 revision=self.revision,
380 message=self.message,
401 message=self.message,
381 date=self.date,
402 date=self.date,
382 author=self.author,
403 author=self.author,
383 )
404 )
384
405
385 @LazyProperty
406 @LazyProperty
386 def last(self):
407 def last(self):
387 if self.repository is None:
408 if self.repository is None:
388 raise ChangesetError("Cannot check if it's most recent revision")
409 raise ChangesetError("Cannot check if it's most recent revision")
389 return self.raw_id == self.repository.revisions[-1]
410 return self.raw_id == self.repository.revisions[-1]
390
411
391 @LazyProperty
412 @LazyProperty
392 def parents(self):
413 def parents(self):
393 """
414 """
394 Returns list of parents changesets.
415 Returns list of parents changesets.
395 """
416 """
396 raise NotImplementedError
417 raise NotImplementedError
397
418
398 @LazyProperty
419 @LazyProperty
399 def children(self):
420 def children(self):
400 """
421 """
401 Returns list of children changesets.
422 Returns list of children changesets.
402 """
423 """
403 raise NotImplementedError
424 raise NotImplementedError
404
425
405 @LazyProperty
426 @LazyProperty
406 def id(self):
427 def id(self):
407 """
428 """
408 Returns string identifying this changeset.
429 Returns string identifying this changeset.
409 """
430 """
410 raise NotImplementedError
431 raise NotImplementedError
411
432
412 @LazyProperty
433 @LazyProperty
413 def raw_id(self):
434 def raw_id(self):
414 """
435 """
415 Returns raw string identifying this changeset.
436 Returns raw string identifying this changeset.
416 """
437 """
417 raise NotImplementedError
438 raise NotImplementedError
418
439
419 @LazyProperty
440 @LazyProperty
420 def short_id(self):
441 def short_id(self):
421 """
442 """
422 Returns shortened version of ``raw_id`` attribute, as string,
443 Returns shortened version of ``raw_id`` attribute, as string,
423 identifying this changeset, useful for web representation.
444 identifying this changeset, useful for web representation.
424 """
445 """
425 raise NotImplementedError
446 raise NotImplementedError
426
447
427 @LazyProperty
448 @LazyProperty
428 def revision(self):
449 def revision(self):
429 """
450 """
430 Returns integer identifying this changeset.
451 Returns integer identifying this changeset.
431
452
432 """
453 """
433 raise NotImplementedError
454 raise NotImplementedError
434
455
435 @LazyProperty
456 @LazyProperty
436 def commiter(self):
457 def commiter(self):
437 """
458 """
438 Returns Commiter for given commit
459 Returns Commiter for given commit
439 """
460 """
440
461
441 raise NotImplementedError
462 raise NotImplementedError
442
463
443 @LazyProperty
464 @LazyProperty
444 def commiter_name(self):
465 def commiter_name(self):
445 """
466 """
446 Returns Author name for given commit
467 Returns Author name for given commit
447 """
468 """
448
469
449 return author_name(self.commiter)
470 return author_name(self.commiter)
450
471
451 @LazyProperty
472 @LazyProperty
452 def commiter_email(self):
473 def commiter_email(self):
453 """
474 """
454 Returns Author email address for given commit
475 Returns Author email address for given commit
455 """
476 """
456
477
457 return author_email(self.commiter)
478 return author_email(self.commiter)
458
479
459 @LazyProperty
480 @LazyProperty
460 def author(self):
481 def author(self):
461 """
482 """
462 Returns Author for given commit
483 Returns Author for given commit
463 """
484 """
464
485
465 raise NotImplementedError
486 raise NotImplementedError
466
487
467 @LazyProperty
488 @LazyProperty
468 def author_name(self):
489 def author_name(self):
469 """
490 """
470 Returns Author name for given commit
491 Returns Author name for given commit
471 """
492 """
472
493
473 return author_name(self.author)
494 return author_name(self.author)
474
495
475 @LazyProperty
496 @LazyProperty
476 def author_email(self):
497 def author_email(self):
477 """
498 """
478 Returns Author email address for given commit
499 Returns Author email address for given commit
479 """
500 """
480
501
481 return author_email(self.author)
502 return author_email(self.author)
482
503
483 def get_file_mode(self, path):
504 def get_file_mode(self, path):
484 """
505 """
485 Returns stat mode of the file at the given ``path``.
506 Returns stat mode of the file at the given ``path``.
486 """
507 """
487 raise NotImplementedError
508 raise NotImplementedError
488
509
489 def get_file_content(self, path):
510 def get_file_content(self, path):
490 """
511 """
491 Returns content of the file at the given ``path``.
512 Returns content of the file at the given ``path``.
492 """
513 """
493 raise NotImplementedError
514 raise NotImplementedError
494
515
495 def get_file_size(self, path):
516 def get_file_size(self, path):
496 """
517 """
497 Returns size of the file at the given ``path``.
518 Returns size of the file at the given ``path``.
498 """
519 """
499 raise NotImplementedError
520 raise NotImplementedError
500
521
501 def get_file_changeset(self, path):
522 def get_file_changeset(self, path):
502 """
523 """
503 Returns last commit of the file at the given ``path``.
524 Returns last commit of the file at the given ``path``.
504 """
525 """
505 raise NotImplementedError
526 raise NotImplementedError
506
527
507 def get_file_history(self, path):
528 def get_file_history(self, path):
508 """
529 """
509 Returns history of file as reversed list of ``Changeset`` objects for
530 Returns history of file as reversed list of ``Changeset`` objects for
510 which file at given ``path`` has been modified.
531 which file at given ``path`` has been modified.
511 """
532 """
512 raise NotImplementedError
533 raise NotImplementedError
513
534
514 def get_nodes(self, path):
535 def get_nodes(self, path):
515 """
536 """
516 Returns combined ``DirNode`` and ``FileNode`` objects list representing
537 Returns combined ``DirNode`` and ``FileNode`` objects list representing
517 state of changeset at the given ``path``.
538 state of changeset at the given ``path``.
518
539
519 :raises ``ChangesetError``: if node at the given ``path`` is not
540 :raises ``ChangesetError``: if node at the given ``path`` is not
520 instance of ``DirNode``
541 instance of ``DirNode``
521 """
542 """
522 raise NotImplementedError
543 raise NotImplementedError
523
544
524 def get_node(self, path):
545 def get_node(self, path):
525 """
546 """
526 Returns ``Node`` object from the given ``path``.
547 Returns ``Node`` object from the given ``path``.
527
548
528 :raises ``NodeDoesNotExistError``: if there is no node at the given
549 :raises ``NodeDoesNotExistError``: if there is no node at the given
529 ``path``
550 ``path``
530 """
551 """
531 raise NotImplementedError
552 raise NotImplementedError
532
553
533 def fill_archive(self, stream=None, kind='tgz', prefix=None):
554 def fill_archive(self, stream=None, kind='tgz', prefix=None):
534 """
555 """
535 Fills up given stream.
556 Fills up given stream.
536
557
537 :param stream: file like object.
558 :param stream: file like object.
538 :param kind: one of following: ``zip``, ``tar``, ``tgz``
559 :param kind: one of following: ``zip``, ``tar``, ``tgz``
539 or ``tbz2``. Default: ``tgz``.
560 or ``tbz2``. Default: ``tgz``.
540 :param prefix: name of root directory in archive.
561 :param prefix: name of root directory in archive.
541 Default is repository name and changeset's raw_id joined with dash.
562 Default is repository name and changeset's raw_id joined with dash.
542
563
543 repo-tip.<kind>
564 repo-tip.<kind>
544 """
565 """
545
566
546 raise NotImplementedError
567 raise NotImplementedError
547
568
548 def get_chunked_archive(self, **kwargs):
569 def get_chunked_archive(self, **kwargs):
549 """
570 """
550 Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
571 Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
551
572
552 :param chunk_size: extra parameter which controls size of returned
573 :param chunk_size: extra parameter which controls size of returned
553 chunks. Default:8k.
574 chunks. Default:8k.
554 """
575 """
555
576
556 chunk_size = kwargs.pop('chunk_size', 8192)
577 chunk_size = kwargs.pop('chunk_size', 8192)
557 stream = kwargs.get('stream')
578 stream = kwargs.get('stream')
558 self.fill_archive(**kwargs)
579 self.fill_archive(**kwargs)
559 while True:
580 while True:
560 data = stream.read(chunk_size)
581 data = stream.read(chunk_size)
561 if not data:
582 if not data:
562 break
583 break
563 yield data
584 yield data
564
585
565 @LazyProperty
586 @LazyProperty
566 def root(self):
587 def root(self):
567 """
588 """
568 Returns ``RootNode`` object for this changeset.
589 Returns ``RootNode`` object for this changeset.
569 """
590 """
570 return self.get_node('')
591 return self.get_node('')
571
592
572 def next(self, branch=None):
593 def next(self, branch=None):
573 """
594 """
574 Returns next changeset from current, if branch is gives it will return
595 Returns next changeset from current, if branch is gives it will return
575 next changeset belonging to this branch
596 next changeset belonging to this branch
576
597
577 :param branch: show changesets within the given named branch
598 :param branch: show changesets within the given named branch
578 """
599 """
579 raise NotImplementedError
600 raise NotImplementedError
580
601
581 def prev(self, branch=None):
602 def prev(self, branch=None):
582 """
603 """
583 Returns previous changeset from current, if branch is gives it will
604 Returns previous changeset from current, if branch is gives it will
584 return previous changeset belonging to this branch
605 return previous changeset belonging to this branch
585
606
586 :param branch: show changesets within the given named branch
607 :param branch: show changesets within the given named branch
587 """
608 """
588 raise NotImplementedError
609 raise NotImplementedError
589
610
590 @LazyProperty
611 @LazyProperty
591 def added(self):
612 def added(self):
592 """
613 """
593 Returns list of added ``FileNode`` objects.
614 Returns list of added ``FileNode`` objects.
594 """
615 """
595 raise NotImplementedError
616 raise NotImplementedError
596
617
597 @LazyProperty
618 @LazyProperty
598 def changed(self):
619 def changed(self):
599 """
620 """
600 Returns list of modified ``FileNode`` objects.
621 Returns list of modified ``FileNode`` objects.
601 """
622 """
602 raise NotImplementedError
623 raise NotImplementedError
603
624
604 @LazyProperty
625 @LazyProperty
605 def removed(self):
626 def removed(self):
606 """
627 """
607 Returns list of removed ``FileNode`` objects.
628 Returns list of removed ``FileNode`` objects.
608 """
629 """
609 raise NotImplementedError
630 raise NotImplementedError
610
631
611 @LazyProperty
632 @LazyProperty
612 def size(self):
633 def size(self):
613 """
634 """
614 Returns total number of bytes from contents of all filenodes.
635 Returns total number of bytes from contents of all filenodes.
615 """
636 """
616 return sum((node.size for node in self.get_filenodes_generator()))
637 return sum((node.size for node in self.get_filenodes_generator()))
617
638
618 def walk(self, topurl=''):
639 def walk(self, topurl=''):
619 """
640 """
620 Similar to os.walk method. Insted of filesystem it walks through
641 Similar to os.walk method. Insted of filesystem it walks through
621 changeset starting at given ``topurl``. Returns generator of tuples
642 changeset starting at given ``topurl``. Returns generator of tuples
622 (topnode, dirnodes, filenodes).
643 (topnode, dirnodes, filenodes).
623 """
644 """
624 topnode = self.get_node(topurl)
645 topnode = self.get_node(topurl)
625 yield (topnode, topnode.dirs, topnode.files)
646 yield (topnode, topnode.dirs, topnode.files)
626 for dirnode in topnode.dirs:
647 for dirnode in topnode.dirs:
627 for tup in self.walk(dirnode.path):
648 for tup in self.walk(dirnode.path):
628 yield tup
649 yield tup
629
650
630 def get_filenodes_generator(self):
651 def get_filenodes_generator(self):
631 """
652 """
632 Returns generator that yields *all* file nodes.
653 Returns generator that yields *all* file nodes.
633 """
654 """
634 for topnode, dirs, files in self.walk():
655 for topnode, dirs, files in self.walk():
635 for node in files:
656 for node in files:
636 yield node
657 yield node
637
658
638 def as_dict(self):
659 def as_dict(self):
639 """
660 """
640 Returns dictionary with changeset's attributes and their values.
661 Returns dictionary with changeset's attributes and their values.
641 """
662 """
642 data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
663 data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
643 'revision', 'date', 'message'])
664 'revision', 'date', 'message'])
644 data['author'] = {'name': self.author_name, 'email': self.author_email}
665 data['author'] = {'name': self.author_name, 'email': self.author_email}
645 data['added'] = [node.path for node in self.added]
666 data['added'] = [node.path for node in self.added]
646 data['changed'] = [node.path for node in self.changed]
667 data['changed'] = [node.path for node in self.changed]
647 data['removed'] = [node.path for node in self.removed]
668 data['removed'] = [node.path for node in self.removed]
648 return data
669 return data
649
670
650
671
651 class BaseWorkdir(object):
672 class BaseWorkdir(object):
652 """
673 """
653 Working directory representation of single repository.
674 Working directory representation of single repository.
654
675
655 :attribute: repository: repository object of working directory
676 :attribute: repository: repository object of working directory
656 """
677 """
657
678
658 def __init__(self, repository):
679 def __init__(self, repository):
659 self.repository = repository
680 self.repository = repository
660
681
661 def get_branch(self):
682 def get_branch(self):
662 """
683 """
663 Returns name of current branch.
684 Returns name of current branch.
664 """
685 """
665 raise NotImplementedError
686 raise NotImplementedError
666
687
667 def get_changeset(self):
688 def get_changeset(self):
668 """
689 """
669 Returns current changeset.
690 Returns current changeset.
670 """
691 """
671 raise NotImplementedError
692 raise NotImplementedError
672
693
673 def get_added(self):
694 def get_added(self):
674 """
695 """
675 Returns list of ``FileNode`` objects marked as *new* in working
696 Returns list of ``FileNode`` objects marked as *new* in working
676 directory.
697 directory.
677 """
698 """
678 raise NotImplementedError
699 raise NotImplementedError
679
700
680 def get_changed(self):
701 def get_changed(self):
681 """
702 """
682 Returns list of ``FileNode`` objects *changed* in working directory.
703 Returns list of ``FileNode`` objects *changed* in working directory.
683 """
704 """
684 raise NotImplementedError
705 raise NotImplementedError
685
706
686 def get_removed(self):
707 def get_removed(self):
687 """
708 """
688 Returns list of ``RemovedFileNode`` objects marked as *removed* in
709 Returns list of ``RemovedFileNode`` objects marked as *removed* in
689 working directory.
710 working directory.
690 """
711 """
691 raise NotImplementedError
712 raise NotImplementedError
692
713
693 def get_untracked(self):
714 def get_untracked(self):
694 """
715 """
695 Returns list of ``FileNode`` objects which are present within working
716 Returns list of ``FileNode`` objects which are present within working
696 directory however are not tracked by repository.
717 directory however are not tracked by repository.
697 """
718 """
698 raise NotImplementedError
719 raise NotImplementedError
699
720
700 def get_status(self):
721 def get_status(self):
701 """
722 """
702 Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
723 Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
703 lists.
724 lists.
704 """
725 """
705 raise NotImplementedError
726 raise NotImplementedError
706
727
707 def commit(self, message, **kwargs):
728 def commit(self, message, **kwargs):
708 """
729 """
709 Commits local (from working directory) changes and returns newly
730 Commits local (from working directory) changes and returns newly
710 created
731 created
711 ``Changeset``. Updates repository's ``revisions`` list.
732 ``Changeset``. Updates repository's ``revisions`` list.
712
733
713 :raises ``CommitError``: if any error occurs while committing
734 :raises ``CommitError``: if any error occurs while committing
714 """
735 """
715 raise NotImplementedError
736 raise NotImplementedError
716
737
717 def update(self, revision=None):
738 def update(self, revision=None):
718 """
739 """
719 Fetches content of the given revision and populates it within working
740 Fetches content of the given revision and populates it within working
720 directory.
741 directory.
721 """
742 """
722 raise NotImplementedError
743 raise NotImplementedError
723
744
724 def checkout_branch(self, branch=None):
745 def checkout_branch(self, branch=None):
725 """
746 """
726 Checks out ``branch`` or the backend's default branch.
747 Checks out ``branch`` or the backend's default branch.
727
748
728 Raises ``BranchDoesNotExistError`` if the branch does not exist.
749 Raises ``BranchDoesNotExistError`` if the branch does not exist.
729 """
750 """
730 raise NotImplementedError
751 raise NotImplementedError
731
752
732
753
733 class BaseInMemoryChangeset(object):
754 class BaseInMemoryChangeset(object):
734 """
755 """
735 Represents differences between repository's state (most recent head) and
756 Represents differences between repository's state (most recent head) and
736 changes made *in place*.
757 changes made *in place*.
737
758
738 **Attributes**
759 **Attributes**
739
760
740 ``repository``
761 ``repository``
741 repository object for this in-memory-changeset
762 repository object for this in-memory-changeset
742
763
743 ``added``
764 ``added``
744 list of ``FileNode`` objects marked as *added*
765 list of ``FileNode`` objects marked as *added*
745
766
746 ``changed``
767 ``changed``
747 list of ``FileNode`` objects marked as *changed*
768 list of ``FileNode`` objects marked as *changed*
748
769
749 ``removed``
770 ``removed``
750 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
771 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
751 *removed*
772 *removed*
752
773
753 ``parents``
774 ``parents``
754 list of ``Changeset`` representing parents of in-memory changeset.
775 list of ``Changeset`` representing parents of in-memory changeset.
755 Should always be 2-element sequence.
776 Should always be 2-element sequence.
756
777
757 """
778 """
758
779
759 def __init__(self, repository):
780 def __init__(self, repository):
760 self.repository = repository
781 self.repository = repository
761 self.added = []
782 self.added = []
762 self.changed = []
783 self.changed = []
763 self.removed = []
784 self.removed = []
764 self.parents = []
785 self.parents = []
765
786
766 def add(self, *filenodes):
787 def add(self, *filenodes):
767 """
788 """
768 Marks given ``FileNode`` objects as *to be committed*.
789 Marks given ``FileNode`` objects as *to be committed*.
769
790
770 :raises ``NodeAlreadyExistsError``: if node with same path exists at
791 :raises ``NodeAlreadyExistsError``: if node with same path exists at
771 latest changeset
792 latest changeset
772 :raises ``NodeAlreadyAddedError``: if node with same path is already
793 :raises ``NodeAlreadyAddedError``: if node with same path is already
773 marked as *added*
794 marked as *added*
774 """
795 """
775 # Check if not already marked as *added* first
796 # Check if not already marked as *added* first
776 for node in filenodes:
797 for node in filenodes:
777 if node.path in (n.path for n in self.added):
798 if node.path in (n.path for n in self.added):
778 raise NodeAlreadyAddedError("Such FileNode %s is already "
799 raise NodeAlreadyAddedError("Such FileNode %s is already "
779 "marked for addition" % node.path)
800 "marked for addition" % node.path)
780 for node in filenodes:
801 for node in filenodes:
781 self.added.append(node)
802 self.added.append(node)
782
803
783 def change(self, *filenodes):
804 def change(self, *filenodes):
784 """
805 """
785 Marks given ``FileNode`` objects to be *changed* in next commit.
806 Marks given ``FileNode`` objects to be *changed* in next commit.
786
807
787 :raises ``EmptyRepositoryError``: if there are no changesets yet
808 :raises ``EmptyRepositoryError``: if there are no changesets yet
788 :raises ``NodeAlreadyExistsError``: if node with same path is already
809 :raises ``NodeAlreadyExistsError``: if node with same path is already
789 marked to be *changed*
810 marked to be *changed*
790 :raises ``NodeAlreadyRemovedError``: if node with same path is already
811 :raises ``NodeAlreadyRemovedError``: if node with same path is already
791 marked to be *removed*
812 marked to be *removed*
792 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
813 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
793 changeset
814 changeset
794 :raises ``NodeNotChangedError``: if node hasn't really be changed
815 :raises ``NodeNotChangedError``: if node hasn't really be changed
795 """
816 """
796 for node in filenodes:
817 for node in filenodes:
797 if node.path in (n.path for n in self.removed):
818 if node.path in (n.path for n in self.removed):
798 raise NodeAlreadyRemovedError("Node at %s is already marked "
819 raise NodeAlreadyRemovedError("Node at %s is already marked "
799 "as removed" % node.path)
820 "as removed" % node.path)
800 try:
821 try:
801 self.repository.get_changeset()
822 self.repository.get_changeset()
802 except EmptyRepositoryError:
823 except EmptyRepositoryError:
803 raise EmptyRepositoryError("Nothing to change - try to *add* new "
824 raise EmptyRepositoryError("Nothing to change - try to *add* new "
804 "nodes rather than changing them")
825 "nodes rather than changing them")
805 for node in filenodes:
826 for node in filenodes:
806 if node.path in (n.path for n in self.changed):
827 if node.path in (n.path for n in self.changed):
807 raise NodeAlreadyChangedError("Node at '%s' is already "
828 raise NodeAlreadyChangedError("Node at '%s' is already "
808 "marked as changed" % node.path)
829 "marked as changed" % node.path)
809 self.changed.append(node)
830 self.changed.append(node)
810
831
811 def remove(self, *filenodes):
832 def remove(self, *filenodes):
812 """
833 """
813 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
834 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
814 *removed* in next commit.
835 *removed* in next commit.
815
836
816 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
837 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
817 be *removed*
838 be *removed*
818 :raises ``NodeAlreadyChangedError``: if node has been already marked to
839 :raises ``NodeAlreadyChangedError``: if node has been already marked to
819 be *changed*
840 be *changed*
820 """
841 """
821 for node in filenodes:
842 for node in filenodes:
822 if node.path in (n.path for n in self.removed):
843 if node.path in (n.path for n in self.removed):
823 raise NodeAlreadyRemovedError("Node is already marked to "
844 raise NodeAlreadyRemovedError("Node is already marked to "
824 "for removal at %s" % node.path)
845 "for removal at %s" % node.path)
825 if node.path in (n.path for n in self.changed):
846 if node.path in (n.path for n in self.changed):
826 raise NodeAlreadyChangedError("Node is already marked to "
847 raise NodeAlreadyChangedError("Node is already marked to "
827 "be changed at %s" % node.path)
848 "be changed at %s" % node.path)
828 # We only mark node as *removed* - real removal is done by
849 # We only mark node as *removed* - real removal is done by
829 # commit method
850 # commit method
830 self.removed.append(node)
851 self.removed.append(node)
831
852
832 def reset(self):
853 def reset(self):
833 """
854 """
834 Resets this instance to initial state (cleans ``added``, ``changed``
855 Resets this instance to initial state (cleans ``added``, ``changed``
835 and ``removed`` lists).
856 and ``removed`` lists).
836 """
857 """
837 self.added = []
858 self.added = []
838 self.changed = []
859 self.changed = []
839 self.removed = []
860 self.removed = []
840 self.parents = []
861 self.parents = []
841
862
842 def get_ipaths(self):
863 def get_ipaths(self):
843 """
864 """
844 Returns generator of paths from nodes marked as added, changed or
865 Returns generator of paths from nodes marked as added, changed or
845 removed.
866 removed.
846 """
867 """
847 for node in chain(self.added, self.changed, self.removed):
868 for node in chain(self.added, self.changed, self.removed):
848 yield node.path
869 yield node.path
849
870
850 def get_paths(self):
871 def get_paths(self):
851 """
872 """
852 Returns list of paths from nodes marked as added, changed or removed.
873 Returns list of paths from nodes marked as added, changed or removed.
853 """
874 """
854 return list(self.get_ipaths())
875 return list(self.get_ipaths())
855
876
856 def check_integrity(self, parents=None):
877 def check_integrity(self, parents=None):
857 """
878 """
858 Checks in-memory changeset's integrity. Also, sets parents if not
879 Checks in-memory changeset's integrity. Also, sets parents if not
859 already set.
880 already set.
860
881
861 :raises CommitError: if any error occurs (i.e.
882 :raises CommitError: if any error occurs (i.e.
862 ``NodeDoesNotExistError``).
883 ``NodeDoesNotExistError``).
863 """
884 """
864 if not self.parents:
885 if not self.parents:
865 parents = parents or []
886 parents = parents or []
866 if len(parents) == 0:
887 if len(parents) == 0:
867 try:
888 try:
868 parents = [self.repository.get_changeset(), None]
889 parents = [self.repository.get_changeset(), None]
869 except EmptyRepositoryError:
890 except EmptyRepositoryError:
870 parents = [None, None]
891 parents = [None, None]
871 elif len(parents) == 1:
892 elif len(parents) == 1:
872 parents += [None]
893 parents += [None]
873 self.parents = parents
894 self.parents = parents
874
895
875 # Local parents, only if not None
896 # Local parents, only if not None
876 parents = [p for p in self.parents if p]
897 parents = [p for p in self.parents if p]
877
898
878 # Check nodes marked as added
899 # Check nodes marked as added
879 for p in parents:
900 for p in parents:
880 for node in self.added:
901 for node in self.added:
881 try:
902 try:
882 p.get_node(node.path)
903 p.get_node(node.path)
883 except NodeDoesNotExistError:
904 except NodeDoesNotExistError:
884 pass
905 pass
885 else:
906 else:
886 raise NodeAlreadyExistsError("Node at %s already exists "
907 raise NodeAlreadyExistsError("Node at %s already exists "
887 "at %s" % (node.path, p))
908 "at %s" % (node.path, p))
888
909
889 # Check nodes marked as changed
910 # Check nodes marked as changed
890 missing = set(self.changed)
911 missing = set(self.changed)
891 not_changed = set(self.changed)
912 not_changed = set(self.changed)
892 if self.changed and not parents:
913 if self.changed and not parents:
893 raise NodeDoesNotExistError(str(self.changed[0].path))
914 raise NodeDoesNotExistError(str(self.changed[0].path))
894 for p in parents:
915 for p in parents:
895 for node in self.changed:
916 for node in self.changed:
896 try:
917 try:
897 old = p.get_node(node.path)
918 old = p.get_node(node.path)
898 missing.remove(node)
919 missing.remove(node)
899 if old.content != node.content:
920 if old.content != node.content:
900 not_changed.remove(node)
921 not_changed.remove(node)
901 except NodeDoesNotExistError:
922 except NodeDoesNotExistError:
902 pass
923 pass
903 if self.changed and missing:
924 if self.changed and missing:
904 raise NodeDoesNotExistError("Node at %s is missing "
925 raise NodeDoesNotExistError("Node at %s is missing "
905 "(parents: %s)" % (node.path, parents))
926 "(parents: %s)" % (node.path, parents))
906
927
907 if self.changed and not_changed:
928 if self.changed and not_changed:
908 raise NodeNotChangedError("Node at %s wasn't actually changed "
929 raise NodeNotChangedError("Node at %s wasn't actually changed "
909 "since parents' changesets: %s" % (not_changed.pop().path,
930 "since parents' changesets: %s" % (not_changed.pop().path,
910 parents)
931 parents)
911 )
932 )
912
933
913 # Check nodes marked as removed
934 # Check nodes marked as removed
914 if self.removed and not parents:
935 if self.removed and not parents:
915 raise NodeDoesNotExistError("Cannot remove node at %s as there "
936 raise NodeDoesNotExistError("Cannot remove node at %s as there "
916 "were no parents specified" % self.removed[0].path)
937 "were no parents specified" % self.removed[0].path)
917 really_removed = set()
938 really_removed = set()
918 for p in parents:
939 for p in parents:
919 for node in self.removed:
940 for node in self.removed:
920 try:
941 try:
921 p.get_node(node.path)
942 p.get_node(node.path)
922 really_removed.add(node)
943 really_removed.add(node)
923 except ChangesetError:
944 except ChangesetError:
924 pass
945 pass
925 not_removed = set(self.removed) - really_removed
946 not_removed = set(self.removed) - really_removed
926 if not_removed:
947 if not_removed:
927 raise NodeDoesNotExistError("Cannot remove node at %s from "
948 raise NodeDoesNotExistError("Cannot remove node at %s from "
928 "following parents: %s" % (not_removed[0], parents))
949 "following parents: %s" % (not_removed[0], parents))
929
950
930 def commit(self, message, author, parents=None, branch=None, date=None,
951 def commit(self, message, author, parents=None, branch=None, date=None,
931 **kwargs):
952 **kwargs):
932 """
953 """
933 Performs in-memory commit (doesn't check workdir in any way) and
954 Performs in-memory commit (doesn't check workdir in any way) and
934 returns newly created ``Changeset``. Updates repository's
955 returns newly created ``Changeset``. Updates repository's
935 ``revisions``.
956 ``revisions``.
936
957
937 .. note::
958 .. note::
938 While overriding this method each backend's should call
959 While overriding this method each backend's should call
939 ``self.check_integrity(parents)`` in the first place.
960 ``self.check_integrity(parents)`` in the first place.
940
961
941 :param message: message of the commit
962 :param message: message of the commit
942 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
963 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
943 :param parents: single parent or sequence of parents from which commit
964 :param parents: single parent or sequence of parents from which commit
944 would be derieved
965 would be derieved
945 :param date: ``datetime.datetime`` instance. Defaults to
966 :param date: ``datetime.datetime`` instance. Defaults to
946 ``datetime.datetime.now()``.
967 ``datetime.datetime.now()``.
947 :param branch: branch name, as string. If none given, default backend's
968 :param branch: branch name, as string. If none given, default backend's
948 branch would be used.
969 branch would be used.
949
970
950 :raises ``CommitError``: if any error occurs while committing
971 :raises ``CommitError``: if any error occurs while committing
951 """
972 """
952 raise NotImplementedError
973 raise NotImplementedError
953
974
954
975
955 class EmptyChangeset(BaseChangeset):
976 class EmptyChangeset(BaseChangeset):
956 """
977 """
957 An dummy empty changeset. It's possible to pass hash when creating
978 An dummy empty changeset. It's possible to pass hash when creating
958 an EmptyChangeset
979 an EmptyChangeset
959 """
980 """
960
981
961 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
982 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
962 alias=None, revision=-1, message='', author='', date=''):
983 alias=None, revision=-1, message='', author='', date=''):
963 self._empty_cs = cs
984 self._empty_cs = cs
964 self.revision = revision
985 self.revision = revision
965 self.message = message
986 self.message = message
966 self.author = author
987 self.author = author
967 self.date = date
988 self.date = date
968 self.repository = repo
989 self.repository = repo
969 self.requested_revision = requested_revision
990 self.requested_revision = requested_revision
970 self.alias = alias
991 self.alias = alias
971
992
972 @LazyProperty
993 @LazyProperty
973 def raw_id(self):
994 def raw_id(self):
974 """
995 """
975 Returns raw string identifying this changeset, useful for web
996 Returns raw string identifying this changeset, useful for web
976 representation.
997 representation.
977 """
998 """
978
999
979 return self._empty_cs
1000 return self._empty_cs
980
1001
981 @LazyProperty
1002 @LazyProperty
982 def branch(self):
1003 def branch(self):
983 from rhodecode.lib.vcs.backends import get_backend
1004 from rhodecode.lib.vcs.backends import get_backend
984 return get_backend(self.alias).DEFAULT_BRANCH_NAME
1005 return get_backend(self.alias).DEFAULT_BRANCH_NAME
985
1006
986 @LazyProperty
1007 @LazyProperty
987 def short_id(self):
1008 def short_id(self):
988 return self.raw_id[:12]
1009 return self.raw_id[:12]
989
1010
990 def get_file_changeset(self, path):
1011 def get_file_changeset(self, path):
991 return self
1012 return self
992
1013
993 def get_file_content(self, path):
1014 def get_file_content(self, path):
994 return u''
1015 return u''
995
1016
996 def get_file_size(self, path):
1017 def get_file_size(self, path):
997 return 0
1018 return 0
@@ -1,2061 +1,2053 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 import time
32 from collections import defaultdict
32 from collections import defaultdict
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48
48
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 safe_unicode, remove_suffix, remove_prefix
50 safe_unicode, remove_suffix, remove_prefix
51 from rhodecode.lib.compat import json
51 from rhodecode.lib.compat import json
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model.meta import Base, Session
54 from rhodecode.model.meta import Base, Session
55
55
56 URL_SEP = '/'
56 URL_SEP = '/'
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59 #==============================================================================
59 #==============================================================================
60 # BASE CLASSES
60 # BASE CLASSES
61 #==============================================================================
61 #==============================================================================
62
62
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64
64
65
65
66 class BaseModel(object):
66 class BaseModel(object):
67 """
67 """
68 Base Model for all classess
68 Base Model for all classess
69 """
69 """
70
70
71 @classmethod
71 @classmethod
72 def _get_keys(cls):
72 def _get_keys(cls):
73 """return column names for this model """
73 """return column names for this model """
74 return class_mapper(cls).c.keys()
74 return class_mapper(cls).c.keys()
75
75
76 def get_dict(self):
76 def get_dict(self):
77 """
77 """
78 return dict with keys and values corresponding
78 return dict with keys and values corresponding
79 to this model data """
79 to this model data """
80
80
81 d = {}
81 d = {}
82 for k in self._get_keys():
82 for k in self._get_keys():
83 d[k] = getattr(self, k)
83 d[k] = getattr(self, k)
84
84
85 # also use __json__() if present to get additional fields
85 # also use __json__() if present to get additional fields
86 _json_attr = getattr(self, '__json__', None)
86 _json_attr = getattr(self, '__json__', None)
87 if _json_attr:
87 if _json_attr:
88 # update with attributes from __json__
88 # update with attributes from __json__
89 if callable(_json_attr):
89 if callable(_json_attr):
90 _json_attr = _json_attr()
90 _json_attr = _json_attr()
91 for k, val in _json_attr.iteritems():
91 for k, val in _json_attr.iteritems():
92 d[k] = val
92 d[k] = val
93 return d
93 return d
94
94
95 def get_appstruct(self):
95 def get_appstruct(self):
96 """return list with keys and values tupples corresponding
96 """return list with keys and values tupples corresponding
97 to this model data """
97 to this model data """
98
98
99 l = []
99 l = []
100 for k in self._get_keys():
100 for k in self._get_keys():
101 l.append((k, getattr(self, k),))
101 l.append((k, getattr(self, k),))
102 return l
102 return l
103
103
104 def populate_obj(self, populate_dict):
104 def populate_obj(self, populate_dict):
105 """populate model with data from given populate_dict"""
105 """populate model with data from given populate_dict"""
106
106
107 for k in self._get_keys():
107 for k in self._get_keys():
108 if k in populate_dict:
108 if k in populate_dict:
109 setattr(self, k, populate_dict[k])
109 setattr(self, k, populate_dict[k])
110
110
111 @classmethod
111 @classmethod
112 def query(cls):
112 def query(cls):
113 return Session().query(cls)
113 return Session().query(cls)
114
114
115 @classmethod
115 @classmethod
116 def get(cls, id_):
116 def get(cls, id_):
117 if id_:
117 if id_:
118 return cls.query().get(id_)
118 return cls.query().get(id_)
119
119
120 @classmethod
120 @classmethod
121 def get_or_404(cls, id_):
121 def get_or_404(cls, id_):
122 try:
122 try:
123 id_ = int(id_)
123 id_ = int(id_)
124 except (TypeError, ValueError):
124 except (TypeError, ValueError):
125 raise HTTPNotFound
125 raise HTTPNotFound
126
126
127 res = cls.query().get(id_)
127 res = cls.query().get(id_)
128 if not res:
128 if not res:
129 raise HTTPNotFound
129 raise HTTPNotFound
130 return res
130 return res
131
131
132 @classmethod
132 @classmethod
133 def getAll(cls):
133 def getAll(cls):
134 return cls.query().all()
134 return cls.query().all()
135
135
136 @classmethod
136 @classmethod
137 def delete(cls, id_):
137 def delete(cls, id_):
138 obj = cls.query().get(id_)
138 obj = cls.query().get(id_)
139 Session().delete(obj)
139 Session().delete(obj)
140
140
141 def __repr__(self):
141 def __repr__(self):
142 if hasattr(self, '__unicode__'):
142 if hasattr(self, '__unicode__'):
143 # python repr needs to return str
143 # python repr needs to return str
144 return safe_str(self.__unicode__())
144 return safe_str(self.__unicode__())
145 return '<DB:%s>' % (self.__class__.__name__)
145 return '<DB:%s>' % (self.__class__.__name__)
146
146
147
147
148 class RhodeCodeSetting(Base, BaseModel):
148 class RhodeCodeSetting(Base, BaseModel):
149 __tablename__ = 'rhodecode_settings'
149 __tablename__ = 'rhodecode_settings'
150 __table_args__ = (
150 __table_args__ = (
151 UniqueConstraint('app_settings_name'),
151 UniqueConstraint('app_settings_name'),
152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 'mysql_charset': 'utf8'}
153 'mysql_charset': 'utf8'}
154 )
154 )
155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
158
158
159 def __init__(self, k='', v=''):
159 def __init__(self, k='', v=''):
160 self.app_settings_name = k
160 self.app_settings_name = k
161 self.app_settings_value = v
161 self.app_settings_value = v
162
162
163 @validates('_app_settings_value')
163 @validates('_app_settings_value')
164 def validate_settings_value(self, key, val):
164 def validate_settings_value(self, key, val):
165 assert type(val) == unicode
165 assert type(val) == unicode
166 return val
166 return val
167
167
168 @hybrid_property
168 @hybrid_property
169 def app_settings_value(self):
169 def app_settings_value(self):
170 v = self._app_settings_value
170 v = self._app_settings_value
171 if self.app_settings_name in ["ldap_active",
171 if self.app_settings_name in ["ldap_active",
172 "default_repo_enable_statistics",
172 "default_repo_enable_statistics",
173 "default_repo_enable_locking",
173 "default_repo_enable_locking",
174 "default_repo_private",
174 "default_repo_private",
175 "default_repo_enable_downloads"]:
175 "default_repo_enable_downloads"]:
176 v = str2bool(v)
176 v = str2bool(v)
177 return v
177 return v
178
178
179 @app_settings_value.setter
179 @app_settings_value.setter
180 def app_settings_value(self, val):
180 def app_settings_value(self, val):
181 """
181 """
182 Setter that will always make sure we use unicode in app_settings_value
182 Setter that will always make sure we use unicode in app_settings_value
183
183
184 :param val:
184 :param val:
185 """
185 """
186 self._app_settings_value = safe_unicode(val)
186 self._app_settings_value = safe_unicode(val)
187
187
188 def __unicode__(self):
188 def __unicode__(self):
189 return u"<%s('%s:%s')>" % (
189 return u"<%s('%s:%s')>" % (
190 self.__class__.__name__,
190 self.__class__.__name__,
191 self.app_settings_name, self.app_settings_value
191 self.app_settings_name, self.app_settings_value
192 )
192 )
193
193
194 @classmethod
194 @classmethod
195 def get_by_name(cls, key):
195 def get_by_name(cls, key):
196 return cls.query()\
196 return cls.query()\
197 .filter(cls.app_settings_name == key).scalar()
197 .filter(cls.app_settings_name == key).scalar()
198
198
199 @classmethod
199 @classmethod
200 def get_by_name_or_create(cls, key):
200 def get_by_name_or_create(cls, key):
201 res = cls.get_by_name(key)
201 res = cls.get_by_name(key)
202 if not res:
202 if not res:
203 res = cls(key)
203 res = cls(key)
204 return res
204 return res
205
205
206 @classmethod
206 @classmethod
207 def get_app_settings(cls, cache=False):
207 def get_app_settings(cls, cache=False):
208
208
209 ret = cls.query()
209 ret = cls.query()
210
210
211 if cache:
211 if cache:
212 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
213
213
214 if not ret:
214 if not ret:
215 raise Exception('Could not get application settings !')
215 raise Exception('Could not get application settings !')
216 settings = {}
216 settings = {}
217 for each in ret:
217 for each in ret:
218 settings['rhodecode_' + each.app_settings_name] = \
218 settings['rhodecode_' + each.app_settings_name] = \
219 each.app_settings_value
219 each.app_settings_value
220
220
221 return settings
221 return settings
222
222
223 @classmethod
223 @classmethod
224 def get_ldap_settings(cls, cache=False):
224 def get_ldap_settings(cls, cache=False):
225 ret = cls.query()\
225 ret = cls.query()\
226 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 .filter(cls.app_settings_name.startswith('ldap_')).all()
227 fd = {}
227 fd = {}
228 for row in ret:
228 for row in ret:
229 fd.update({row.app_settings_name: row.app_settings_value})
229 fd.update({row.app_settings_name: row.app_settings_value})
230
230
231 return fd
231 return fd
232
232
233 @classmethod
233 @classmethod
234 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
235 ret = cls.query()\
235 ret = cls.query()\
236 .filter(cls.app_settings_name.startswith('default_')).all()
236 .filter(cls.app_settings_name.startswith('default_')).all()
237 fd = {}
237 fd = {}
238 for row in ret:
238 for row in ret:
239 key = row.app_settings_name
239 key = row.app_settings_name
240 if strip_prefix:
240 if strip_prefix:
241 key = remove_prefix(key, prefix='default_')
241 key = remove_prefix(key, prefix='default_')
242 fd.update({key: row.app_settings_value})
242 fd.update({key: row.app_settings_value})
243
243
244 return fd
244 return fd
245
245
246
246
247 class RhodeCodeUi(Base, BaseModel):
247 class RhodeCodeUi(Base, BaseModel):
248 __tablename__ = 'rhodecode_ui'
248 __tablename__ = 'rhodecode_ui'
249 __table_args__ = (
249 __table_args__ = (
250 UniqueConstraint('ui_key'),
250 UniqueConstraint('ui_key'),
251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
252 'mysql_charset': 'utf8'}
252 'mysql_charset': 'utf8'}
253 )
253 )
254
254
255 HOOK_UPDATE = 'changegroup.update'
255 HOOK_UPDATE = 'changegroup.update'
256 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 HOOK_REPO_SIZE = 'changegroup.repo_size'
257 HOOK_PUSH = 'changegroup.push_logger'
257 HOOK_PUSH = 'changegroup.push_logger'
258 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
259 HOOK_PULL = 'outgoing.pull_logger'
259 HOOK_PULL = 'outgoing.pull_logger'
260 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
261
261
262 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
263 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
266 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
267
267
268 @classmethod
268 @classmethod
269 def get_by_key(cls, key):
269 def get_by_key(cls, key):
270 return cls.query().filter(cls.ui_key == key).scalar()
270 return cls.query().filter(cls.ui_key == key).scalar()
271
271
272 @classmethod
272 @classmethod
273 def get_builtin_hooks(cls):
273 def get_builtin_hooks(cls):
274 q = cls.query()
274 q = cls.query()
275 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
276 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
277 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
278 return q.all()
278 return q.all()
279
279
280 @classmethod
280 @classmethod
281 def get_custom_hooks(cls):
281 def get_custom_hooks(cls):
282 q = cls.query()
282 q = cls.query()
283 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
284 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
285 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
286 q = q.filter(cls.ui_section == 'hooks')
286 q = q.filter(cls.ui_section == 'hooks')
287 return q.all()
287 return q.all()
288
288
289 @classmethod
289 @classmethod
290 def get_repos_location(cls):
290 def get_repos_location(cls):
291 return cls.get_by_key('/').ui_value
291 return cls.get_by_key('/').ui_value
292
292
293 @classmethod
293 @classmethod
294 def create_or_update_hook(cls, key, val):
294 def create_or_update_hook(cls, key, val):
295 new_ui = cls.get_by_key(key) or cls()
295 new_ui = cls.get_by_key(key) or cls()
296 new_ui.ui_section = 'hooks'
296 new_ui.ui_section = 'hooks'
297 new_ui.ui_active = True
297 new_ui.ui_active = True
298 new_ui.ui_key = key
298 new_ui.ui_key = key
299 new_ui.ui_value = val
299 new_ui.ui_value = val
300
300
301 Session().add(new_ui)
301 Session().add(new_ui)
302
302
303 def __repr__(self):
303 def __repr__(self):
304 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
305 self.ui_value)
305 self.ui_value)
306
306
307
307
308 class User(Base, BaseModel):
308 class User(Base, BaseModel):
309 __tablename__ = 'users'
309 __tablename__ = 'users'
310 __table_args__ = (
310 __table_args__ = (
311 UniqueConstraint('username'), UniqueConstraint('email'),
311 UniqueConstraint('username'), UniqueConstraint('email'),
312 Index('u_username_idx', 'username'),
312 Index('u_username_idx', 'username'),
313 Index('u_email_idx', 'email'),
313 Index('u_email_idx', 'email'),
314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
315 'mysql_charset': 'utf8'}
315 'mysql_charset': 'utf8'}
316 )
316 )
317 DEFAULT_USER = 'default'
317 DEFAULT_USER = 'default'
318 DEFAULT_PERMISSIONS = [
318 DEFAULT_PERMISSIONS = [
319 'hg.register.manual_activate', 'hg.create.repository',
319 'hg.register.manual_activate', 'hg.create.repository',
320 'hg.fork.repository', 'repository.read', 'group.read'
320 'hg.fork.repository', 'repository.read', 'group.read'
321 ]
321 ]
322 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
323 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
325 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
326 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
327 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
331 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
334
334
335 user_log = relationship('UserLog')
335 user_log = relationship('UserLog')
336 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
337
337
338 repositories = relationship('Repository')
338 repositories = relationship('Repository')
339 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
340 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
341
341
342 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
343 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
344
344
345 group_member = relationship('UserGroupMember', cascade='all')
345 group_member = relationship('UserGroupMember', cascade='all')
346
346
347 notifications = relationship('UserNotification', cascade='all')
347 notifications = relationship('UserNotification', cascade='all')
348 # notifications assigned to this user
348 # notifications assigned to this user
349 user_created_notifications = relationship('Notification', cascade='all')
349 user_created_notifications = relationship('Notification', cascade='all')
350 # comments created by this user
350 # comments created by this user
351 user_comments = relationship('ChangesetComment', cascade='all')
351 user_comments = relationship('ChangesetComment', cascade='all')
352 #extra emails for this user
352 #extra emails for this user
353 user_emails = relationship('UserEmailMap', cascade='all')
353 user_emails = relationship('UserEmailMap', cascade='all')
354
354
355 @hybrid_property
355 @hybrid_property
356 def email(self):
356 def email(self):
357 return self._email
357 return self._email
358
358
359 @email.setter
359 @email.setter
360 def email(self, val):
360 def email(self, val):
361 self._email = val.lower() if val else None
361 self._email = val.lower() if val else None
362
362
363 @property
363 @property
364 def firstname(self):
364 def firstname(self):
365 # alias for future
365 # alias for future
366 return self.name
366 return self.name
367
367
368 @property
368 @property
369 def emails(self):
369 def emails(self):
370 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
371 return [self.email] + [x.email for x in other]
371 return [self.email] + [x.email for x in other]
372
372
373 @property
373 @property
374 def ip_addresses(self):
374 def ip_addresses(self):
375 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
376 return [x.ip_addr for x in ret]
376 return [x.ip_addr for x in ret]
377
377
378 @property
378 @property
379 def username_and_name(self):
379 def username_and_name(self):
380 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
381
381
382 @property
382 @property
383 def full_name(self):
383 def full_name(self):
384 return '%s %s' % (self.firstname, self.lastname)
384 return '%s %s' % (self.firstname, self.lastname)
385
385
386 @property
386 @property
387 def full_name_or_username(self):
387 def full_name_or_username(self):
388 return ('%s %s' % (self.firstname, self.lastname)
388 return ('%s %s' % (self.firstname, self.lastname)
389 if (self.firstname and self.lastname) else self.username)
389 if (self.firstname and self.lastname) else self.username)
390
390
391 @property
391 @property
392 def full_contact(self):
392 def full_contact(self):
393 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
394
394
395 @property
395 @property
396 def short_contact(self):
396 def short_contact(self):
397 return '%s %s' % (self.firstname, self.lastname)
397 return '%s %s' % (self.firstname, self.lastname)
398
398
399 @property
399 @property
400 def is_admin(self):
400 def is_admin(self):
401 return self.admin
401 return self.admin
402
402
403 @property
403 @property
404 def AuthUser(self):
404 def AuthUser(self):
405 """
405 """
406 Returns instance of AuthUser for this user
406 Returns instance of AuthUser for this user
407 """
407 """
408 from rhodecode.lib.auth import AuthUser
408 from rhodecode.lib.auth import AuthUser
409 return AuthUser(user_id=self.user_id, api_key=self.api_key,
409 return AuthUser(user_id=self.user_id, api_key=self.api_key,
410 username=self.username)
410 username=self.username)
411
411
412 def __unicode__(self):
412 def __unicode__(self):
413 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
413 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
414 self.user_id, self.username)
414 self.user_id, self.username)
415
415
416 @classmethod
416 @classmethod
417 def get_by_username(cls, username, case_insensitive=False, cache=False):
417 def get_by_username(cls, username, case_insensitive=False, cache=False):
418 if case_insensitive:
418 if case_insensitive:
419 q = cls.query().filter(cls.username.ilike(username))
419 q = cls.query().filter(cls.username.ilike(username))
420 else:
420 else:
421 q = cls.query().filter(cls.username == username)
421 q = cls.query().filter(cls.username == username)
422
422
423 if cache:
423 if cache:
424 q = q.options(FromCache(
424 q = q.options(FromCache(
425 "sql_cache_short",
425 "sql_cache_short",
426 "get_user_%s" % _hash_key(username)
426 "get_user_%s" % _hash_key(username)
427 )
427 )
428 )
428 )
429 return q.scalar()
429 return q.scalar()
430
430
431 @classmethod
431 @classmethod
432 def get_by_api_key(cls, api_key, cache=False):
432 def get_by_api_key(cls, api_key, cache=False):
433 q = cls.query().filter(cls.api_key == api_key)
433 q = cls.query().filter(cls.api_key == api_key)
434
434
435 if cache:
435 if cache:
436 q = q.options(FromCache("sql_cache_short",
436 q = q.options(FromCache("sql_cache_short",
437 "get_api_key_%s" % api_key))
437 "get_api_key_%s" % api_key))
438 return q.scalar()
438 return q.scalar()
439
439
440 @classmethod
440 @classmethod
441 def get_by_email(cls, email, case_insensitive=False, cache=False):
441 def get_by_email(cls, email, case_insensitive=False, cache=False):
442 if case_insensitive:
442 if case_insensitive:
443 q = cls.query().filter(cls.email.ilike(email))
443 q = cls.query().filter(cls.email.ilike(email))
444 else:
444 else:
445 q = cls.query().filter(cls.email == email)
445 q = cls.query().filter(cls.email == email)
446
446
447 if cache:
447 if cache:
448 q = q.options(FromCache("sql_cache_short",
448 q = q.options(FromCache("sql_cache_short",
449 "get_email_key_%s" % email))
449 "get_email_key_%s" % email))
450
450
451 ret = q.scalar()
451 ret = q.scalar()
452 if ret is None:
452 if ret is None:
453 q = UserEmailMap.query()
453 q = UserEmailMap.query()
454 # try fetching in alternate email map
454 # try fetching in alternate email map
455 if case_insensitive:
455 if case_insensitive:
456 q = q.filter(UserEmailMap.email.ilike(email))
456 q = q.filter(UserEmailMap.email.ilike(email))
457 else:
457 else:
458 q = q.filter(UserEmailMap.email == email)
458 q = q.filter(UserEmailMap.email == email)
459 q = q.options(joinedload(UserEmailMap.user))
459 q = q.options(joinedload(UserEmailMap.user))
460 if cache:
460 if cache:
461 q = q.options(FromCache("sql_cache_short",
461 q = q.options(FromCache("sql_cache_short",
462 "get_email_map_key_%s" % email))
462 "get_email_map_key_%s" % email))
463 ret = getattr(q.scalar(), 'user', None)
463 ret = getattr(q.scalar(), 'user', None)
464
464
465 return ret
465 return ret
466
466
467 @classmethod
467 @classmethod
468 def get_from_cs_author(cls, author):
468 def get_from_cs_author(cls, author):
469 """
469 """
470 Tries to get User objects out of commit author string
470 Tries to get User objects out of commit author string
471
471
472 :param author:
472 :param author:
473 """
473 """
474 from rhodecode.lib.helpers import email, author_name
474 from rhodecode.lib.helpers import email, author_name
475 # Valid email in the attribute passed, see if they're in the system
475 # Valid email in the attribute passed, see if they're in the system
476 _email = email(author)
476 _email = email(author)
477 if _email:
477 if _email:
478 user = cls.get_by_email(_email, case_insensitive=True)
478 user = cls.get_by_email(_email, case_insensitive=True)
479 if user:
479 if user:
480 return user
480 return user
481 # Maybe we can match by username?
481 # Maybe we can match by username?
482 _author = author_name(author)
482 _author = author_name(author)
483 user = cls.get_by_username(_author, case_insensitive=True)
483 user = cls.get_by_username(_author, case_insensitive=True)
484 if user:
484 if user:
485 return user
485 return user
486
486
487 def update_lastlogin(self):
487 def update_lastlogin(self):
488 """Update user lastlogin"""
488 """Update user lastlogin"""
489 self.last_login = datetime.datetime.now()
489 self.last_login = datetime.datetime.now()
490 Session().add(self)
490 Session().add(self)
491 log.debug('updated user %s lastlogin' % self.username)
491 log.debug('updated user %s lastlogin' % self.username)
492
492
493 def get_api_data(self):
493 def get_api_data(self):
494 """
494 """
495 Common function for generating user related data for API
495 Common function for generating user related data for API
496 """
496 """
497 user = self
497 user = self
498 data = dict(
498 data = dict(
499 user_id=user.user_id,
499 user_id=user.user_id,
500 username=user.username,
500 username=user.username,
501 firstname=user.name,
501 firstname=user.name,
502 lastname=user.lastname,
502 lastname=user.lastname,
503 email=user.email,
503 email=user.email,
504 emails=user.emails,
504 emails=user.emails,
505 api_key=user.api_key,
505 api_key=user.api_key,
506 active=user.active,
506 active=user.active,
507 admin=user.admin,
507 admin=user.admin,
508 ldap_dn=user.ldap_dn,
508 ldap_dn=user.ldap_dn,
509 last_login=user.last_login,
509 last_login=user.last_login,
510 ip_addresses=user.ip_addresses
510 ip_addresses=user.ip_addresses
511 )
511 )
512 return data
512 return data
513
513
514 def __json__(self):
514 def __json__(self):
515 data = dict(
515 data = dict(
516 full_name=self.full_name,
516 full_name=self.full_name,
517 full_name_or_username=self.full_name_or_username,
517 full_name_or_username=self.full_name_or_username,
518 short_contact=self.short_contact,
518 short_contact=self.short_contact,
519 full_contact=self.full_contact
519 full_contact=self.full_contact
520 )
520 )
521 data.update(self.get_api_data())
521 data.update(self.get_api_data())
522 return data
522 return data
523
523
524
524
525 class UserEmailMap(Base, BaseModel):
525 class UserEmailMap(Base, BaseModel):
526 __tablename__ = 'user_email_map'
526 __tablename__ = 'user_email_map'
527 __table_args__ = (
527 __table_args__ = (
528 Index('uem_email_idx', 'email'),
528 Index('uem_email_idx', 'email'),
529 UniqueConstraint('email'),
529 UniqueConstraint('email'),
530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
531 'mysql_charset': 'utf8'}
531 'mysql_charset': 'utf8'}
532 )
532 )
533 __mapper_args__ = {}
533 __mapper_args__ = {}
534
534
535 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
535 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
537 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
537 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
538 user = relationship('User', lazy='joined')
538 user = relationship('User', lazy='joined')
539
539
540 @validates('_email')
540 @validates('_email')
541 def validate_email(self, key, email):
541 def validate_email(self, key, email):
542 # check if this email is not main one
542 # check if this email is not main one
543 main_email = Session().query(User).filter(User.email == email).scalar()
543 main_email = Session().query(User).filter(User.email == email).scalar()
544 if main_email is not None:
544 if main_email is not None:
545 raise AttributeError('email %s is present is user table' % email)
545 raise AttributeError('email %s is present is user table' % email)
546 return email
546 return email
547
547
548 @hybrid_property
548 @hybrid_property
549 def email(self):
549 def email(self):
550 return self._email
550 return self._email
551
551
552 @email.setter
552 @email.setter
553 def email(self, val):
553 def email(self, val):
554 self._email = val.lower() if val else None
554 self._email = val.lower() if val else None
555
555
556
556
557 class UserIpMap(Base, BaseModel):
557 class UserIpMap(Base, BaseModel):
558 __tablename__ = 'user_ip_map'
558 __tablename__ = 'user_ip_map'
559 __table_args__ = (
559 __table_args__ = (
560 UniqueConstraint('user_id', 'ip_addr'),
560 UniqueConstraint('user_id', 'ip_addr'),
561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
562 'mysql_charset': 'utf8'}
562 'mysql_charset': 'utf8'}
563 )
563 )
564 __mapper_args__ = {}
564 __mapper_args__ = {}
565
565
566 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
566 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
568 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
568 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
569 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
569 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
570 user = relationship('User', lazy='joined')
570 user = relationship('User', lazy='joined')
571
571
572 @classmethod
572 @classmethod
573 def _get_ip_range(cls, ip_addr):
573 def _get_ip_range(cls, ip_addr):
574 from rhodecode.lib import ipaddr
574 from rhodecode.lib import ipaddr
575 net = ipaddr.IPNetwork(address=ip_addr)
575 net = ipaddr.IPNetwork(address=ip_addr)
576 return [str(net.network), str(net.broadcast)]
576 return [str(net.network), str(net.broadcast)]
577
577
578 def __json__(self):
578 def __json__(self):
579 return dict(
579 return dict(
580 ip_addr=self.ip_addr,
580 ip_addr=self.ip_addr,
581 ip_range=self._get_ip_range(self.ip_addr)
581 ip_range=self._get_ip_range(self.ip_addr)
582 )
582 )
583
583
584
584
585 class UserLog(Base, BaseModel):
585 class UserLog(Base, BaseModel):
586 __tablename__ = 'user_logs'
586 __tablename__ = 'user_logs'
587 __table_args__ = (
587 __table_args__ = (
588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
589 'mysql_charset': 'utf8'},
589 'mysql_charset': 'utf8'},
590 )
590 )
591 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
591 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
593 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
593 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
594 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
594 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
595 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
595 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
598 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
598 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
599
599
600 @property
600 @property
601 def action_as_day(self):
601 def action_as_day(self):
602 return datetime.date(*self.action_date.timetuple()[:3])
602 return datetime.date(*self.action_date.timetuple()[:3])
603
603
604 user = relationship('User')
604 user = relationship('User')
605 repository = relationship('Repository', cascade='')
605 repository = relationship('Repository', cascade='')
606
606
607
607
608 class UserGroup(Base, BaseModel):
608 class UserGroup(Base, BaseModel):
609 __tablename__ = 'users_groups'
609 __tablename__ = 'users_groups'
610 __table_args__ = (
610 __table_args__ = (
611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
612 'mysql_charset': 'utf8'},
612 'mysql_charset': 'utf8'},
613 )
613 )
614
614
615 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
615 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
616 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
616 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
617 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
617 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
618 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
618 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
619
619
620 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
620 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
621 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
621 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
622 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
622 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
623
623
624 def __unicode__(self):
624 def __unicode__(self):
625 return u'<userGroup(%s)>' % (self.users_group_name)
625 return u'<userGroup(%s)>' % (self.users_group_name)
626
626
627 @classmethod
627 @classmethod
628 def get_by_group_name(cls, group_name, cache=False,
628 def get_by_group_name(cls, group_name, cache=False,
629 case_insensitive=False):
629 case_insensitive=False):
630 if case_insensitive:
630 if case_insensitive:
631 q = cls.query().filter(cls.users_group_name.ilike(group_name))
631 q = cls.query().filter(cls.users_group_name.ilike(group_name))
632 else:
632 else:
633 q = cls.query().filter(cls.users_group_name == group_name)
633 q = cls.query().filter(cls.users_group_name == group_name)
634 if cache:
634 if cache:
635 q = q.options(FromCache(
635 q = q.options(FromCache(
636 "sql_cache_short",
636 "sql_cache_short",
637 "get_user_%s" % _hash_key(group_name)
637 "get_user_%s" % _hash_key(group_name)
638 )
638 )
639 )
639 )
640 return q.scalar()
640 return q.scalar()
641
641
642 @classmethod
642 @classmethod
643 def get(cls, users_group_id, cache=False):
643 def get(cls, users_group_id, cache=False):
644 users_group = cls.query()
644 users_group = cls.query()
645 if cache:
645 if cache:
646 users_group = users_group.options(FromCache("sql_cache_short",
646 users_group = users_group.options(FromCache("sql_cache_short",
647 "get_users_group_%s" % users_group_id))
647 "get_users_group_%s" % users_group_id))
648 return users_group.get(users_group_id)
648 return users_group.get(users_group_id)
649
649
650 def get_api_data(self):
650 def get_api_data(self):
651 users_group = self
651 users_group = self
652
652
653 data = dict(
653 data = dict(
654 users_group_id=users_group.users_group_id,
654 users_group_id=users_group.users_group_id,
655 group_name=users_group.users_group_name,
655 group_name=users_group.users_group_name,
656 active=users_group.users_group_active,
656 active=users_group.users_group_active,
657 )
657 )
658
658
659 return data
659 return data
660
660
661
661
662 class UserGroupMember(Base, BaseModel):
662 class UserGroupMember(Base, BaseModel):
663 __tablename__ = 'users_groups_members'
663 __tablename__ = 'users_groups_members'
664 __table_args__ = (
664 __table_args__ = (
665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
666 'mysql_charset': 'utf8'},
666 'mysql_charset': 'utf8'},
667 )
667 )
668
668
669 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
669 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
670 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
670 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
671 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
671 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
672
672
673 user = relationship('User', lazy='joined')
673 user = relationship('User', lazy='joined')
674 users_group = relationship('UserGroup')
674 users_group = relationship('UserGroup')
675
675
676 def __init__(self, gr_id='', u_id=''):
676 def __init__(self, gr_id='', u_id=''):
677 self.users_group_id = gr_id
677 self.users_group_id = gr_id
678 self.user_id = u_id
678 self.user_id = u_id
679
679
680
680
681 class RepositoryField(Base, BaseModel):
681 class RepositoryField(Base, BaseModel):
682 __tablename__ = 'repositories_fields'
682 __tablename__ = 'repositories_fields'
683 __table_args__ = (
683 __table_args__ = (
684 UniqueConstraint('repository_id', 'field_key'), # no-multi field
684 UniqueConstraint('repository_id', 'field_key'), # no-multi field
685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
686 'mysql_charset': 'utf8'},
686 'mysql_charset': 'utf8'},
687 )
687 )
688 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
688 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
689
689
690 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
690 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
692 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
692 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
693 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
693 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
694 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
694 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
695 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
695 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
696 field_type = Column("field_type", String(256), nullable=False, unique=None)
696 field_type = Column("field_type", String(256), nullable=False, unique=None)
697 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
697 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
698
698
699 repository = relationship('Repository')
699 repository = relationship('Repository')
700
700
701 @property
701 @property
702 def field_key_prefixed(self):
702 def field_key_prefixed(self):
703 return 'ex_%s' % self.field_key
703 return 'ex_%s' % self.field_key
704
704
705 @classmethod
705 @classmethod
706 def un_prefix_key(cls, key):
706 def un_prefix_key(cls, key):
707 if key.startswith(cls.PREFIX):
707 if key.startswith(cls.PREFIX):
708 return key[len(cls.PREFIX):]
708 return key[len(cls.PREFIX):]
709 return key
709 return key
710
710
711 @classmethod
711 @classmethod
712 def get_by_key_name(cls, key, repo):
712 def get_by_key_name(cls, key, repo):
713 row = cls.query()\
713 row = cls.query()\
714 .filter(cls.repository == repo)\
714 .filter(cls.repository == repo)\
715 .filter(cls.field_key == key).scalar()
715 .filter(cls.field_key == key).scalar()
716 return row
716 return row
717
717
718
718
719 class Repository(Base, BaseModel):
719 class Repository(Base, BaseModel):
720 __tablename__ = 'repositories'
720 __tablename__ = 'repositories'
721 __table_args__ = (
721 __table_args__ = (
722 UniqueConstraint('repo_name'),
722 UniqueConstraint('repo_name'),
723 Index('r_repo_name_idx', 'repo_name'),
723 Index('r_repo_name_idx', 'repo_name'),
724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
725 'mysql_charset': 'utf8'},
725 'mysql_charset': 'utf8'},
726 )
726 )
727
727
728 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
728 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
729 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
729 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
730 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
730 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
731 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
731 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
732 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
732 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
733 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
733 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
734 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
734 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
735 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
735 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
736 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
736 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
737 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
737 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
738 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
738 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
739 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
739 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
740 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
740 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
741 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
741 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
742 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
742 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
743
743
744 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
744 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
745 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
745 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
746
746
747 user = relationship('User')
747 user = relationship('User')
748 fork = relationship('Repository', remote_side=repo_id)
748 fork = relationship('Repository', remote_side=repo_id)
749 group = relationship('RepoGroup')
749 group = relationship('RepoGroup')
750 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
750 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
751 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
751 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
752 stats = relationship('Statistics', cascade='all', uselist=False)
752 stats = relationship('Statistics', cascade='all', uselist=False)
753
753
754 followers = relationship('UserFollowing',
754 followers = relationship('UserFollowing',
755 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
755 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
756 cascade='all')
756 cascade='all')
757 extra_fields = relationship('RepositoryField',
757 extra_fields = relationship('RepositoryField',
758 cascade="all, delete, delete-orphan")
758 cascade="all, delete, delete-orphan")
759
759
760 logs = relationship('UserLog')
760 logs = relationship('UserLog')
761 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
761 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
762
762
763 pull_requests_org = relationship('PullRequest',
763 pull_requests_org = relationship('PullRequest',
764 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
764 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
765 cascade="all, delete, delete-orphan")
765 cascade="all, delete, delete-orphan")
766
766
767 pull_requests_other = relationship('PullRequest',
767 pull_requests_other = relationship('PullRequest',
768 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
768 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
769 cascade="all, delete, delete-orphan")
769 cascade="all, delete, delete-orphan")
770
770
771 def __unicode__(self):
771 def __unicode__(self):
772 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
772 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
773 self.repo_name)
773 self.repo_name)
774
774
775 @hybrid_property
775 @hybrid_property
776 def locked(self):
776 def locked(self):
777 # always should return [user_id, timelocked]
777 # always should return [user_id, timelocked]
778 if self._locked:
778 if self._locked:
779 _lock_info = self._locked.split(':')
779 _lock_info = self._locked.split(':')
780 return int(_lock_info[0]), _lock_info[1]
780 return int(_lock_info[0]), _lock_info[1]
781 return [None, None]
781 return [None, None]
782
782
783 @locked.setter
783 @locked.setter
784 def locked(self, val):
784 def locked(self, val):
785 if val and isinstance(val, (list, tuple)):
785 if val and isinstance(val, (list, tuple)):
786 self._locked = ':'.join(map(str, val))
786 self._locked = ':'.join(map(str, val))
787 else:
787 else:
788 self._locked = None
788 self._locked = None
789
789
790 @hybrid_property
790 @hybrid_property
791 def changeset_cache(self):
791 def changeset_cache(self):
792 from rhodecode.lib.vcs.backends.base import EmptyChangeset
792 from rhodecode.lib.vcs.backends.base import EmptyChangeset
793 dummy = EmptyChangeset().__json__()
793 dummy = EmptyChangeset().__json__()
794 if not self._changeset_cache:
794 if not self._changeset_cache:
795 return dummy
795 return dummy
796 try:
796 try:
797 return json.loads(self._changeset_cache)
797 return json.loads(self._changeset_cache)
798 except TypeError:
798 except TypeError:
799 return dummy
799 return dummy
800
800
801 @changeset_cache.setter
801 @changeset_cache.setter
802 def changeset_cache(self, val):
802 def changeset_cache(self, val):
803 try:
803 try:
804 self._changeset_cache = json.dumps(val)
804 self._changeset_cache = json.dumps(val)
805 except:
805 except:
806 log.error(traceback.format_exc())
806 log.error(traceback.format_exc())
807
807
808 @classmethod
808 @classmethod
809 def url_sep(cls):
809 def url_sep(cls):
810 return URL_SEP
810 return URL_SEP
811
811
812 @classmethod
812 @classmethod
813 def normalize_repo_name(cls, repo_name):
813 def normalize_repo_name(cls, repo_name):
814 """
814 """
815 Normalizes os specific repo_name to the format internally stored inside
815 Normalizes os specific repo_name to the format internally stored inside
816 dabatabase using URL_SEP
816 dabatabase using URL_SEP
817
817
818 :param cls:
818 :param cls:
819 :param repo_name:
819 :param repo_name:
820 """
820 """
821 return cls.url_sep().join(repo_name.split(os.sep))
821 return cls.url_sep().join(repo_name.split(os.sep))
822
822
823 @classmethod
823 @classmethod
824 def get_by_repo_name(cls, repo_name):
824 def get_by_repo_name(cls, repo_name):
825 q = Session().query(cls).filter(cls.repo_name == repo_name)
825 q = Session().query(cls).filter(cls.repo_name == repo_name)
826 q = q.options(joinedload(Repository.fork))\
826 q = q.options(joinedload(Repository.fork))\
827 .options(joinedload(Repository.user))\
827 .options(joinedload(Repository.user))\
828 .options(joinedload(Repository.group))
828 .options(joinedload(Repository.group))
829 return q.scalar()
829 return q.scalar()
830
830
831 @classmethod
831 @classmethod
832 def get_by_full_path(cls, repo_full_path):
832 def get_by_full_path(cls, repo_full_path):
833 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
833 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
834 repo_name = cls.normalize_repo_name(repo_name)
834 repo_name = cls.normalize_repo_name(repo_name)
835 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
835 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
836
836
837 @classmethod
837 @classmethod
838 def get_repo_forks(cls, repo_id):
838 def get_repo_forks(cls, repo_id):
839 return cls.query().filter(Repository.fork_id == repo_id)
839 return cls.query().filter(Repository.fork_id == repo_id)
840
840
841 @classmethod
841 @classmethod
842 def base_path(cls):
842 def base_path(cls):
843 """
843 """
844 Returns base path when all repos are stored
844 Returns base path when all repos are stored
845
845
846 :param cls:
846 :param cls:
847 """
847 """
848 q = Session().query(RhodeCodeUi)\
848 q = Session().query(RhodeCodeUi)\
849 .filter(RhodeCodeUi.ui_key == cls.url_sep())
849 .filter(RhodeCodeUi.ui_key == cls.url_sep())
850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
851 return q.one().ui_value
851 return q.one().ui_value
852
852
853 @property
853 @property
854 def forks(self):
854 def forks(self):
855 """
855 """
856 Return forks of this repo
856 Return forks of this repo
857 """
857 """
858 return Repository.get_repo_forks(self.repo_id)
858 return Repository.get_repo_forks(self.repo_id)
859
859
860 @property
860 @property
861 def parent(self):
861 def parent(self):
862 """
862 """
863 Returns fork parent
863 Returns fork parent
864 """
864 """
865 return self.fork
865 return self.fork
866
866
867 @property
867 @property
868 def just_name(self):
868 def just_name(self):
869 return self.repo_name.split(Repository.url_sep())[-1]
869 return self.repo_name.split(Repository.url_sep())[-1]
870
870
871 @property
871 @property
872 def groups_with_parents(self):
872 def groups_with_parents(self):
873 groups = []
873 groups = []
874 if self.group is None:
874 if self.group is None:
875 return groups
875 return groups
876
876
877 cur_gr = self.group
877 cur_gr = self.group
878 groups.insert(0, cur_gr)
878 groups.insert(0, cur_gr)
879 while 1:
879 while 1:
880 gr = getattr(cur_gr, 'parent_group', None)
880 gr = getattr(cur_gr, 'parent_group', None)
881 cur_gr = cur_gr.parent_group
881 cur_gr = cur_gr.parent_group
882 if gr is None:
882 if gr is None:
883 break
883 break
884 groups.insert(0, gr)
884 groups.insert(0, gr)
885
885
886 return groups
886 return groups
887
887
888 @property
888 @property
889 def groups_and_repo(self):
889 def groups_and_repo(self):
890 return self.groups_with_parents, self.just_name
890 return self.groups_with_parents, self.just_name
891
891
892 @LazyProperty
892 @LazyProperty
893 def repo_path(self):
893 def repo_path(self):
894 """
894 """
895 Returns base full path for that repository means where it actually
895 Returns base full path for that repository means where it actually
896 exists on a filesystem
896 exists on a filesystem
897 """
897 """
898 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
898 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
899 Repository.url_sep())
899 Repository.url_sep())
900 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
900 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
901 return q.one().ui_value
901 return q.one().ui_value
902
902
903 @property
903 @property
904 def repo_full_path(self):
904 def repo_full_path(self):
905 p = [self.repo_path]
905 p = [self.repo_path]
906 # we need to split the name by / since this is how we store the
906 # we need to split the name by / since this is how we store the
907 # names in the database, but that eventually needs to be converted
907 # names in the database, but that eventually needs to be converted
908 # into a valid system path
908 # into a valid system path
909 p += self.repo_name.split(Repository.url_sep())
909 p += self.repo_name.split(Repository.url_sep())
910 return os.path.join(*p)
910 return os.path.join(*p)
911
911
912 @property
912 @property
913 def cache_keys(self):
913 def cache_keys(self):
914 """
914 """
915 Returns associated cache keys for that repo
915 Returns associated cache keys for that repo
916 """
916 """
917 return CacheInvalidation.query()\
917 return CacheInvalidation.query()\
918 .filter(CacheInvalidation.cache_args == self.repo_name)\
918 .filter(CacheInvalidation.cache_args == self.repo_name)\
919 .order_by(CacheInvalidation.cache_key)\
919 .order_by(CacheInvalidation.cache_key)\
920 .all()
920 .all()
921
921
922 def get_new_name(self, repo_name):
922 def get_new_name(self, repo_name):
923 """
923 """
924 returns new full repository name based on assigned group and new new
924 returns new full repository name based on assigned group and new new
925
925
926 :param group_name:
926 :param group_name:
927 """
927 """
928 path_prefix = self.group.full_path_splitted if self.group else []
928 path_prefix = self.group.full_path_splitted if self.group else []
929 return Repository.url_sep().join(path_prefix + [repo_name])
929 return Repository.url_sep().join(path_prefix + [repo_name])
930
930
931 @property
931 @property
932 def _ui(self):
932 def _ui(self):
933 """
933 """
934 Creates an db based ui object for this repository
934 Creates an db based ui object for this repository
935 """
935 """
936 from rhodecode.lib.utils import make_ui
936 from rhodecode.lib.utils import make_ui
937 return make_ui('db', clear_session=False)
937 return make_ui('db', clear_session=False)
938
938
939 @classmethod
939 @classmethod
940 def inject_ui(cls, repo, extras={}):
940 def inject_ui(cls, repo, extras={}):
941 from rhodecode.lib.vcs.backends.hg import MercurialRepository
941 repo.inject_ui(extras)
942 from rhodecode.lib.vcs.backends.git import GitRepository
943 required = (MercurialRepository, GitRepository)
944 if not isinstance(repo, required):
945 raise Exception('repo must be instance of %s' % required)
946
947 # inject ui extra param to log this action via push logger
948 for k, v in extras.items():
949 repo._repo.ui.setconfig('rhodecode_extras', k, v)
950
942
951 @classmethod
943 @classmethod
952 def is_valid(cls, repo_name):
944 def is_valid(cls, repo_name):
953 """
945 """
954 returns True if given repo name is a valid filesystem repository
946 returns True if given repo name is a valid filesystem repository
955
947
956 :param cls:
948 :param cls:
957 :param repo_name:
949 :param repo_name:
958 """
950 """
959 from rhodecode.lib.utils import is_valid_repo
951 from rhodecode.lib.utils import is_valid_repo
960
952
961 return is_valid_repo(repo_name, cls.base_path())
953 return is_valid_repo(repo_name, cls.base_path())
962
954
963 def get_api_data(self):
955 def get_api_data(self):
964 """
956 """
965 Common function for generating repo api data
957 Common function for generating repo api data
966
958
967 """
959 """
968 repo = self
960 repo = self
969 data = dict(
961 data = dict(
970 repo_id=repo.repo_id,
962 repo_id=repo.repo_id,
971 repo_name=repo.repo_name,
963 repo_name=repo.repo_name,
972 repo_type=repo.repo_type,
964 repo_type=repo.repo_type,
973 clone_uri=repo.clone_uri,
965 clone_uri=repo.clone_uri,
974 private=repo.private,
966 private=repo.private,
975 created_on=repo.created_on,
967 created_on=repo.created_on,
976 description=repo.description,
968 description=repo.description,
977 landing_rev=repo.landing_rev,
969 landing_rev=repo.landing_rev,
978 owner=repo.user.username,
970 owner=repo.user.username,
979 fork_of=repo.fork.repo_name if repo.fork else None,
971 fork_of=repo.fork.repo_name if repo.fork else None,
980 enable_statistics=repo.enable_statistics,
972 enable_statistics=repo.enable_statistics,
981 enable_locking=repo.enable_locking,
973 enable_locking=repo.enable_locking,
982 enable_downloads=repo.enable_downloads,
974 enable_downloads=repo.enable_downloads,
983 last_changeset=repo.changeset_cache
975 last_changeset=repo.changeset_cache
984 )
976 )
985 rc_config = RhodeCodeSetting.get_app_settings()
977 rc_config = RhodeCodeSetting.get_app_settings()
986 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
978 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
987 if repository_fields:
979 if repository_fields:
988 for f in self.extra_fields:
980 for f in self.extra_fields:
989 data[f.field_key_prefixed] = f.field_value
981 data[f.field_key_prefixed] = f.field_value
990
982
991 return data
983 return data
992
984
993 @classmethod
985 @classmethod
994 def lock(cls, repo, user_id):
986 def lock(cls, repo, user_id):
995 repo.locked = [user_id, time.time()]
987 repo.locked = [user_id, time.time()]
996 Session().add(repo)
988 Session().add(repo)
997 Session().commit()
989 Session().commit()
998
990
999 @classmethod
991 @classmethod
1000 def unlock(cls, repo):
992 def unlock(cls, repo):
1001 repo.locked = None
993 repo.locked = None
1002 Session().add(repo)
994 Session().add(repo)
1003 Session().commit()
995 Session().commit()
1004
996
1005 @classmethod
997 @classmethod
1006 def getlock(cls, repo):
998 def getlock(cls, repo):
1007 return repo.locked
999 return repo.locked
1008
1000
1009 @property
1001 @property
1010 def last_db_change(self):
1002 def last_db_change(self):
1011 return self.updated_on
1003 return self.updated_on
1012
1004
1013 def clone_url(self, **override):
1005 def clone_url(self, **override):
1014 from pylons import url
1006 from pylons import url
1015 from urlparse import urlparse
1007 from urlparse import urlparse
1016 import urllib
1008 import urllib
1017 parsed_url = urlparse(url('home', qualified=True))
1009 parsed_url = urlparse(url('home', qualified=True))
1018 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1010 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1019 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1011 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1020 args = {
1012 args = {
1021 'user': '',
1013 'user': '',
1022 'pass': '',
1014 'pass': '',
1023 'scheme': parsed_url.scheme,
1015 'scheme': parsed_url.scheme,
1024 'netloc': parsed_url.netloc,
1016 'netloc': parsed_url.netloc,
1025 'prefix': decoded_path,
1017 'prefix': decoded_path,
1026 'path': self.repo_name
1018 'path': self.repo_name
1027 }
1019 }
1028
1020
1029 args.update(override)
1021 args.update(override)
1030 return default_clone_uri % args
1022 return default_clone_uri % args
1031
1023
1032 #==========================================================================
1024 #==========================================================================
1033 # SCM PROPERTIES
1025 # SCM PROPERTIES
1034 #==========================================================================
1026 #==========================================================================
1035
1027
1036 def get_changeset(self, rev=None):
1028 def get_changeset(self, rev=None):
1037 return get_changeset_safe(self.scm_instance, rev)
1029 return get_changeset_safe(self.scm_instance, rev)
1038
1030
1039 def get_landing_changeset(self):
1031 def get_landing_changeset(self):
1040 """
1032 """
1041 Returns landing changeset, or if that doesn't exist returns the tip
1033 Returns landing changeset, or if that doesn't exist returns the tip
1042 """
1034 """
1043 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1035 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1044 return cs
1036 return cs
1045
1037
1046 def update_changeset_cache(self, cs_cache=None):
1038 def update_changeset_cache(self, cs_cache=None):
1047 """
1039 """
1048 Update cache of last changeset for repository, keys should be::
1040 Update cache of last changeset for repository, keys should be::
1049
1041
1050 short_id
1042 short_id
1051 raw_id
1043 raw_id
1052 revision
1044 revision
1053 message
1045 message
1054 date
1046 date
1055 author
1047 author
1056
1048
1057 :param cs_cache:
1049 :param cs_cache:
1058 """
1050 """
1059 from rhodecode.lib.vcs.backends.base import BaseChangeset
1051 from rhodecode.lib.vcs.backends.base import BaseChangeset
1060 if cs_cache is None:
1052 if cs_cache is None:
1061 cs_cache = EmptyChangeset()
1053 cs_cache = EmptyChangeset()
1062 # use no-cache version here
1054 # use no-cache version here
1063 scm_repo = self.scm_instance_no_cache
1055 scm_repo = self.scm_instance_no_cache
1064 if scm_repo:
1056 if scm_repo:
1065 cs_cache = scm_repo.get_changeset()
1057 cs_cache = scm_repo.get_changeset()
1066
1058
1067 if isinstance(cs_cache, BaseChangeset):
1059 if isinstance(cs_cache, BaseChangeset):
1068 cs_cache = cs_cache.__json__()
1060 cs_cache = cs_cache.__json__()
1069
1061
1070 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1062 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1071 _default = datetime.datetime.fromtimestamp(0)
1063 _default = datetime.datetime.fromtimestamp(0)
1072 last_change = cs_cache.get('date') or _default
1064 last_change = cs_cache.get('date') or _default
1073 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1065 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1074 self.updated_on = last_change
1066 self.updated_on = last_change
1075 self.changeset_cache = cs_cache
1067 self.changeset_cache = cs_cache
1076 Session().add(self)
1068 Session().add(self)
1077 Session().commit()
1069 Session().commit()
1078 else:
1070 else:
1079 log.debug('Skipping repo:%s already with latest changes' % self)
1071 log.debug('Skipping repo:%s already with latest changes' % self)
1080
1072
1081 @property
1073 @property
1082 def tip(self):
1074 def tip(self):
1083 return self.get_changeset('tip')
1075 return self.get_changeset('tip')
1084
1076
1085 @property
1077 @property
1086 def author(self):
1078 def author(self):
1087 return self.tip.author
1079 return self.tip.author
1088
1080
1089 @property
1081 @property
1090 def last_change(self):
1082 def last_change(self):
1091 return self.scm_instance.last_change
1083 return self.scm_instance.last_change
1092
1084
1093 def get_comments(self, revisions=None):
1085 def get_comments(self, revisions=None):
1094 """
1086 """
1095 Returns comments for this repository grouped by revisions
1087 Returns comments for this repository grouped by revisions
1096
1088
1097 :param revisions: filter query by revisions only
1089 :param revisions: filter query by revisions only
1098 """
1090 """
1099 cmts = ChangesetComment.query()\
1091 cmts = ChangesetComment.query()\
1100 .filter(ChangesetComment.repo == self)
1092 .filter(ChangesetComment.repo == self)
1101 if revisions:
1093 if revisions:
1102 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1094 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1103 grouped = defaultdict(list)
1095 grouped = defaultdict(list)
1104 for cmt in cmts.all():
1096 for cmt in cmts.all():
1105 grouped[cmt.revision].append(cmt)
1097 grouped[cmt.revision].append(cmt)
1106 return grouped
1098 return grouped
1107
1099
1108 def statuses(self, revisions=None):
1100 def statuses(self, revisions=None):
1109 """
1101 """
1110 Returns statuses for this repository
1102 Returns statuses for this repository
1111
1103
1112 :param revisions: list of revisions to get statuses for
1104 :param revisions: list of revisions to get statuses for
1113 :type revisions: list
1105 :type revisions: list
1114 """
1106 """
1115
1107
1116 statuses = ChangesetStatus.query()\
1108 statuses = ChangesetStatus.query()\
1117 .filter(ChangesetStatus.repo == self)\
1109 .filter(ChangesetStatus.repo == self)\
1118 .filter(ChangesetStatus.version == 0)
1110 .filter(ChangesetStatus.version == 0)
1119 if revisions:
1111 if revisions:
1120 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1112 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1121 grouped = {}
1113 grouped = {}
1122
1114
1123 #maybe we have open new pullrequest without a status ?
1115 #maybe we have open new pullrequest without a status ?
1124 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1116 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1125 status_lbl = ChangesetStatus.get_status_lbl(stat)
1117 status_lbl = ChangesetStatus.get_status_lbl(stat)
1126 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1118 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1127 for rev in pr.revisions:
1119 for rev in pr.revisions:
1128 pr_id = pr.pull_request_id
1120 pr_id = pr.pull_request_id
1129 pr_repo = pr.other_repo.repo_name
1121 pr_repo = pr.other_repo.repo_name
1130 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1122 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1131
1123
1132 for stat in statuses.all():
1124 for stat in statuses.all():
1133 pr_id = pr_repo = None
1125 pr_id = pr_repo = None
1134 if stat.pull_request:
1126 if stat.pull_request:
1135 pr_id = stat.pull_request.pull_request_id
1127 pr_id = stat.pull_request.pull_request_id
1136 pr_repo = stat.pull_request.other_repo.repo_name
1128 pr_repo = stat.pull_request.other_repo.repo_name
1137 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1129 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1138 pr_id, pr_repo]
1130 pr_id, pr_repo]
1139 return grouped
1131 return grouped
1140
1132
1141 def _repo_size(self):
1133 def _repo_size(self):
1142 from rhodecode.lib import helpers as h
1134 from rhodecode.lib import helpers as h
1143 log.debug('calculating repository size...')
1135 log.debug('calculating repository size...')
1144 return h.format_byte_size(self.scm_instance.size)
1136 return h.format_byte_size(self.scm_instance.size)
1145
1137
1146 #==========================================================================
1138 #==========================================================================
1147 # SCM CACHE INSTANCE
1139 # SCM CACHE INSTANCE
1148 #==========================================================================
1140 #==========================================================================
1149
1141
1150 @property
1142 @property
1151 def invalidate(self):
1143 def invalidate(self):
1152 return CacheInvalidation.invalidate(self.repo_name)
1144 return CacheInvalidation.invalidate(self.repo_name)
1153
1145
1154 def set_invalidate(self):
1146 def set_invalidate(self):
1155 """
1147 """
1156 set a cache for invalidation for this instance
1148 set a cache for invalidation for this instance
1157 """
1149 """
1158 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1150 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1159
1151
1160 @LazyProperty
1152 @LazyProperty
1161 def scm_instance_no_cache(self):
1153 def scm_instance_no_cache(self):
1162 return self.__get_instance()
1154 return self.__get_instance()
1163
1155
1164 @LazyProperty
1156 @LazyProperty
1165 def scm_instance(self):
1157 def scm_instance(self):
1166 import rhodecode
1158 import rhodecode
1167 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1159 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1168 if full_cache:
1160 if full_cache:
1169 return self.scm_instance_cached()
1161 return self.scm_instance_cached()
1170 return self.__get_instance()
1162 return self.__get_instance()
1171
1163
1172 def scm_instance_cached(self, cache_map=None):
1164 def scm_instance_cached(self, cache_map=None):
1173 @cache_region('long_term')
1165 @cache_region('long_term')
1174 def _c(repo_name):
1166 def _c(repo_name):
1175 return self.__get_instance()
1167 return self.__get_instance()
1176 rn = self.repo_name
1168 rn = self.repo_name
1177 log.debug('Getting cached instance of repo')
1169 log.debug('Getting cached instance of repo')
1178
1170
1179 if cache_map:
1171 if cache_map:
1180 # get using prefilled cache_map
1172 # get using prefilled cache_map
1181 invalidate_repo = cache_map[self.repo_name]
1173 invalidate_repo = cache_map[self.repo_name]
1182 if invalidate_repo:
1174 if invalidate_repo:
1183 invalidate_repo = (None if invalidate_repo.cache_active
1175 invalidate_repo = (None if invalidate_repo.cache_active
1184 else invalidate_repo)
1176 else invalidate_repo)
1185 else:
1177 else:
1186 # get from invalidate
1178 # get from invalidate
1187 invalidate_repo = self.invalidate
1179 invalidate_repo = self.invalidate
1188
1180
1189 if invalidate_repo is not None:
1181 if invalidate_repo is not None:
1190 region_invalidate(_c, None, rn)
1182 region_invalidate(_c, None, rn)
1191 # update our cache
1183 # update our cache
1192 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1184 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1193 return _c(rn)
1185 return _c(rn)
1194
1186
1195 def __get_instance(self):
1187 def __get_instance(self):
1196 repo_full_path = self.repo_full_path
1188 repo_full_path = self.repo_full_path
1197 try:
1189 try:
1198 alias = get_scm(repo_full_path)[0]
1190 alias = get_scm(repo_full_path)[0]
1199 log.debug('Creating instance of %s repository from %s'
1191 log.debug('Creating instance of %s repository from %s'
1200 % (alias, repo_full_path))
1192 % (alias, repo_full_path))
1201 backend = get_backend(alias)
1193 backend = get_backend(alias)
1202 except VCSError:
1194 except VCSError:
1203 log.error(traceback.format_exc())
1195 log.error(traceback.format_exc())
1204 log.error('Perhaps this repository is in db and not in '
1196 log.error('Perhaps this repository is in db and not in '
1205 'filesystem run rescan repositories with '
1197 'filesystem run rescan repositories with '
1206 '"destroy old data " option from admin panel')
1198 '"destroy old data " option from admin panel')
1207 return
1199 return
1208
1200
1209 if alias == 'hg':
1201 if alias == 'hg':
1210
1202
1211 repo = backend(safe_str(repo_full_path), create=False,
1203 repo = backend(safe_str(repo_full_path), create=False,
1212 baseui=self._ui)
1204 baseui=self._ui)
1213 # skip hidden web repository
1205 # skip hidden web repository
1214 if repo._get_hidden():
1206 if repo._get_hidden():
1215 return
1207 return
1216 else:
1208 else:
1217 repo = backend(repo_full_path, create=False)
1209 repo = backend(repo_full_path, create=False)
1218
1210
1219 return repo
1211 return repo
1220
1212
1221
1213
1222 class RepoGroup(Base, BaseModel):
1214 class RepoGroup(Base, BaseModel):
1223 __tablename__ = 'groups'
1215 __tablename__ = 'groups'
1224 __table_args__ = (
1216 __table_args__ = (
1225 UniqueConstraint('group_name', 'group_parent_id'),
1217 UniqueConstraint('group_name', 'group_parent_id'),
1226 CheckConstraint('group_id != group_parent_id'),
1218 CheckConstraint('group_id != group_parent_id'),
1227 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1219 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1228 'mysql_charset': 'utf8'},
1220 'mysql_charset': 'utf8'},
1229 )
1221 )
1230 __mapper_args__ = {'order_by': 'group_name'}
1222 __mapper_args__ = {'order_by': 'group_name'}
1231
1223
1232 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1224 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1233 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1225 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1234 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1226 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1235 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1227 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1236 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1228 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1237
1229
1238 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1230 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1239 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1231 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1240
1232
1241 parent_group = relationship('RepoGroup', remote_side=group_id)
1233 parent_group = relationship('RepoGroup', remote_side=group_id)
1242
1234
1243 def __init__(self, group_name='', parent_group=None):
1235 def __init__(self, group_name='', parent_group=None):
1244 self.group_name = group_name
1236 self.group_name = group_name
1245 self.parent_group = parent_group
1237 self.parent_group = parent_group
1246
1238
1247 def __unicode__(self):
1239 def __unicode__(self):
1248 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1240 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1249 self.group_name)
1241 self.group_name)
1250
1242
1251 @classmethod
1243 @classmethod
1252 def groups_choices(cls, groups=None, show_empty_group=True):
1244 def groups_choices(cls, groups=None, show_empty_group=True):
1253 from webhelpers.html import literal as _literal
1245 from webhelpers.html import literal as _literal
1254 if not groups:
1246 if not groups:
1255 groups = cls.query().all()
1247 groups = cls.query().all()
1256
1248
1257 repo_groups = []
1249 repo_groups = []
1258 if show_empty_group:
1250 if show_empty_group:
1259 repo_groups = [('-1', '-- no parent --')]
1251 repo_groups = [('-1', '-- no parent --')]
1260 sep = ' &raquo; '
1252 sep = ' &raquo; '
1261 _name = lambda k: _literal(sep.join(k))
1253 _name = lambda k: _literal(sep.join(k))
1262
1254
1263 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1255 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1264 for x in groups])
1256 for x in groups])
1265
1257
1266 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1258 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1267 return repo_groups
1259 return repo_groups
1268
1260
1269 @classmethod
1261 @classmethod
1270 def url_sep(cls):
1262 def url_sep(cls):
1271 return URL_SEP
1263 return URL_SEP
1272
1264
1273 @classmethod
1265 @classmethod
1274 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1266 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1275 if case_insensitive:
1267 if case_insensitive:
1276 gr = cls.query()\
1268 gr = cls.query()\
1277 .filter(cls.group_name.ilike(group_name))
1269 .filter(cls.group_name.ilike(group_name))
1278 else:
1270 else:
1279 gr = cls.query()\
1271 gr = cls.query()\
1280 .filter(cls.group_name == group_name)
1272 .filter(cls.group_name == group_name)
1281 if cache:
1273 if cache:
1282 gr = gr.options(FromCache(
1274 gr = gr.options(FromCache(
1283 "sql_cache_short",
1275 "sql_cache_short",
1284 "get_group_%s" % _hash_key(group_name)
1276 "get_group_%s" % _hash_key(group_name)
1285 )
1277 )
1286 )
1278 )
1287 return gr.scalar()
1279 return gr.scalar()
1288
1280
1289 @property
1281 @property
1290 def parents(self):
1282 def parents(self):
1291 parents_recursion_limit = 5
1283 parents_recursion_limit = 5
1292 groups = []
1284 groups = []
1293 if self.parent_group is None:
1285 if self.parent_group is None:
1294 return groups
1286 return groups
1295 cur_gr = self.parent_group
1287 cur_gr = self.parent_group
1296 groups.insert(0, cur_gr)
1288 groups.insert(0, cur_gr)
1297 cnt = 0
1289 cnt = 0
1298 while 1:
1290 while 1:
1299 cnt += 1
1291 cnt += 1
1300 gr = getattr(cur_gr, 'parent_group', None)
1292 gr = getattr(cur_gr, 'parent_group', None)
1301 cur_gr = cur_gr.parent_group
1293 cur_gr = cur_gr.parent_group
1302 if gr is None:
1294 if gr is None:
1303 break
1295 break
1304 if cnt == parents_recursion_limit:
1296 if cnt == parents_recursion_limit:
1305 # this will prevent accidental infinit loops
1297 # this will prevent accidental infinit loops
1306 log.error('group nested more than %s' %
1298 log.error('group nested more than %s' %
1307 parents_recursion_limit)
1299 parents_recursion_limit)
1308 break
1300 break
1309
1301
1310 groups.insert(0, gr)
1302 groups.insert(0, gr)
1311 return groups
1303 return groups
1312
1304
1313 @property
1305 @property
1314 def children(self):
1306 def children(self):
1315 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1307 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1316
1308
1317 @property
1309 @property
1318 def name(self):
1310 def name(self):
1319 return self.group_name.split(RepoGroup.url_sep())[-1]
1311 return self.group_name.split(RepoGroup.url_sep())[-1]
1320
1312
1321 @property
1313 @property
1322 def full_path(self):
1314 def full_path(self):
1323 return self.group_name
1315 return self.group_name
1324
1316
1325 @property
1317 @property
1326 def full_path_splitted(self):
1318 def full_path_splitted(self):
1327 return self.group_name.split(RepoGroup.url_sep())
1319 return self.group_name.split(RepoGroup.url_sep())
1328
1320
1329 @property
1321 @property
1330 def repositories(self):
1322 def repositories(self):
1331 return Repository.query()\
1323 return Repository.query()\
1332 .filter(Repository.group == self)\
1324 .filter(Repository.group == self)\
1333 .order_by(Repository.repo_name)
1325 .order_by(Repository.repo_name)
1334
1326
1335 @property
1327 @property
1336 def repositories_recursive_count(self):
1328 def repositories_recursive_count(self):
1337 cnt = self.repositories.count()
1329 cnt = self.repositories.count()
1338
1330
1339 def children_count(group):
1331 def children_count(group):
1340 cnt = 0
1332 cnt = 0
1341 for child in group.children:
1333 for child in group.children:
1342 cnt += child.repositories.count()
1334 cnt += child.repositories.count()
1343 cnt += children_count(child)
1335 cnt += children_count(child)
1344 return cnt
1336 return cnt
1345
1337
1346 return cnt + children_count(self)
1338 return cnt + children_count(self)
1347
1339
1348 def _recursive_objects(self, include_repos=True):
1340 def _recursive_objects(self, include_repos=True):
1349 all_ = []
1341 all_ = []
1350
1342
1351 def _get_members(root_gr):
1343 def _get_members(root_gr):
1352 if include_repos:
1344 if include_repos:
1353 for r in root_gr.repositories:
1345 for r in root_gr.repositories:
1354 all_.append(r)
1346 all_.append(r)
1355 childs = root_gr.children.all()
1347 childs = root_gr.children.all()
1356 if childs:
1348 if childs:
1357 for gr in childs:
1349 for gr in childs:
1358 all_.append(gr)
1350 all_.append(gr)
1359 _get_members(gr)
1351 _get_members(gr)
1360
1352
1361 _get_members(self)
1353 _get_members(self)
1362 return [self] + all_
1354 return [self] + all_
1363
1355
1364 def recursive_groups_and_repos(self):
1356 def recursive_groups_and_repos(self):
1365 """
1357 """
1366 Recursive return all groups, with repositories in those groups
1358 Recursive return all groups, with repositories in those groups
1367 """
1359 """
1368 return self._recursive_objects()
1360 return self._recursive_objects()
1369
1361
1370 def recursive_groups(self):
1362 def recursive_groups(self):
1371 """
1363 """
1372 Returns all children groups for this group including children of children
1364 Returns all children groups for this group including children of children
1373 """
1365 """
1374 return self._recursive_objects(include_repos=False)
1366 return self._recursive_objects(include_repos=False)
1375
1367
1376 def get_new_name(self, group_name):
1368 def get_new_name(self, group_name):
1377 """
1369 """
1378 returns new full group name based on parent and new name
1370 returns new full group name based on parent and new name
1379
1371
1380 :param group_name:
1372 :param group_name:
1381 """
1373 """
1382 path_prefix = (self.parent_group.full_path_splitted if
1374 path_prefix = (self.parent_group.full_path_splitted if
1383 self.parent_group else [])
1375 self.parent_group else [])
1384 return RepoGroup.url_sep().join(path_prefix + [group_name])
1376 return RepoGroup.url_sep().join(path_prefix + [group_name])
1385
1377
1386
1378
1387 class Permission(Base, BaseModel):
1379 class Permission(Base, BaseModel):
1388 __tablename__ = 'permissions'
1380 __tablename__ = 'permissions'
1389 __table_args__ = (
1381 __table_args__ = (
1390 Index('p_perm_name_idx', 'permission_name'),
1382 Index('p_perm_name_idx', 'permission_name'),
1391 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1383 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1392 'mysql_charset': 'utf8'},
1384 'mysql_charset': 'utf8'},
1393 )
1385 )
1394 PERMS = [
1386 PERMS = [
1395 ('repository.none', _('Repository no access')),
1387 ('repository.none', _('Repository no access')),
1396 ('repository.read', _('Repository read access')),
1388 ('repository.read', _('Repository read access')),
1397 ('repository.write', _('Repository write access')),
1389 ('repository.write', _('Repository write access')),
1398 ('repository.admin', _('Repository admin access')),
1390 ('repository.admin', _('Repository admin access')),
1399
1391
1400 ('group.none', _('Repository group no access')),
1392 ('group.none', _('Repository group no access')),
1401 ('group.read', _('Repository group read access')),
1393 ('group.read', _('Repository group read access')),
1402 ('group.write', _('Repository group write access')),
1394 ('group.write', _('Repository group write access')),
1403 ('group.admin', _('Repository group admin access')),
1395 ('group.admin', _('Repository group admin access')),
1404
1396
1405 ('hg.admin', _('RhodeCode Administrator')),
1397 ('hg.admin', _('RhodeCode Administrator')),
1406 ('hg.create.none', _('Repository creation disabled')),
1398 ('hg.create.none', _('Repository creation disabled')),
1407 ('hg.create.repository', _('Repository creation enabled')),
1399 ('hg.create.repository', _('Repository creation enabled')),
1408 ('hg.fork.none', _('Repository forking disabled')),
1400 ('hg.fork.none', _('Repository forking disabled')),
1409 ('hg.fork.repository', _('Repository forking enabled')),
1401 ('hg.fork.repository', _('Repository forking enabled')),
1410 ('hg.register.none', _('Register disabled')),
1402 ('hg.register.none', _('Register disabled')),
1411 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1403 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1412 'with manual activation')),
1404 'with manual activation')),
1413
1405
1414 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1406 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1415 'with auto activation')),
1407 'with auto activation')),
1416 ]
1408 ]
1417
1409
1418 # defines which permissions are more important higher the more important
1410 # defines which permissions are more important higher the more important
1419 PERM_WEIGHTS = {
1411 PERM_WEIGHTS = {
1420 'repository.none': 0,
1412 'repository.none': 0,
1421 'repository.read': 1,
1413 'repository.read': 1,
1422 'repository.write': 3,
1414 'repository.write': 3,
1423 'repository.admin': 4,
1415 'repository.admin': 4,
1424
1416
1425 'group.none': 0,
1417 'group.none': 0,
1426 'group.read': 1,
1418 'group.read': 1,
1427 'group.write': 3,
1419 'group.write': 3,
1428 'group.admin': 4,
1420 'group.admin': 4,
1429
1421
1430 'hg.fork.none': 0,
1422 'hg.fork.none': 0,
1431 'hg.fork.repository': 1,
1423 'hg.fork.repository': 1,
1432 'hg.create.none': 0,
1424 'hg.create.none': 0,
1433 'hg.create.repository':1
1425 'hg.create.repository':1
1434 }
1426 }
1435
1427
1436 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1428 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1437 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1429 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1438 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1430 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1439
1431
1440 def __unicode__(self):
1432 def __unicode__(self):
1441 return u"<%s('%s:%s')>" % (
1433 return u"<%s('%s:%s')>" % (
1442 self.__class__.__name__, self.permission_id, self.permission_name
1434 self.__class__.__name__, self.permission_id, self.permission_name
1443 )
1435 )
1444
1436
1445 @classmethod
1437 @classmethod
1446 def get_by_key(cls, key):
1438 def get_by_key(cls, key):
1447 return cls.query().filter(cls.permission_name == key).scalar()
1439 return cls.query().filter(cls.permission_name == key).scalar()
1448
1440
1449 @classmethod
1441 @classmethod
1450 def get_default_perms(cls, default_user_id):
1442 def get_default_perms(cls, default_user_id):
1451 q = Session().query(UserRepoToPerm, Repository, cls)\
1443 q = Session().query(UserRepoToPerm, Repository, cls)\
1452 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1444 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1453 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1445 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1454 .filter(UserRepoToPerm.user_id == default_user_id)
1446 .filter(UserRepoToPerm.user_id == default_user_id)
1455
1447
1456 return q.all()
1448 return q.all()
1457
1449
1458 @classmethod
1450 @classmethod
1459 def get_default_group_perms(cls, default_user_id):
1451 def get_default_group_perms(cls, default_user_id):
1460 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1452 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1461 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1453 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1462 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1454 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1463 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1455 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1464
1456
1465 return q.all()
1457 return q.all()
1466
1458
1467
1459
1468 class UserRepoToPerm(Base, BaseModel):
1460 class UserRepoToPerm(Base, BaseModel):
1469 __tablename__ = 'repo_to_perm'
1461 __tablename__ = 'repo_to_perm'
1470 __table_args__ = (
1462 __table_args__ = (
1471 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1463 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1472 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1473 'mysql_charset': 'utf8'}
1465 'mysql_charset': 'utf8'}
1474 )
1466 )
1475 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1467 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1476 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1468 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1477 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1469 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1478 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1470 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1479
1471
1480 user = relationship('User')
1472 user = relationship('User')
1481 repository = relationship('Repository')
1473 repository = relationship('Repository')
1482 permission = relationship('Permission')
1474 permission = relationship('Permission')
1483
1475
1484 @classmethod
1476 @classmethod
1485 def create(cls, user, repository, permission):
1477 def create(cls, user, repository, permission):
1486 n = cls()
1478 n = cls()
1487 n.user = user
1479 n.user = user
1488 n.repository = repository
1480 n.repository = repository
1489 n.permission = permission
1481 n.permission = permission
1490 Session().add(n)
1482 Session().add(n)
1491 return n
1483 return n
1492
1484
1493 def __unicode__(self):
1485 def __unicode__(self):
1494 return u'<user:%s => %s >' % (self.user, self.repository)
1486 return u'<user:%s => %s >' % (self.user, self.repository)
1495
1487
1496
1488
1497 class UserToPerm(Base, BaseModel):
1489 class UserToPerm(Base, BaseModel):
1498 __tablename__ = 'user_to_perm'
1490 __tablename__ = 'user_to_perm'
1499 __table_args__ = (
1491 __table_args__ = (
1500 UniqueConstraint('user_id', 'permission_id'),
1492 UniqueConstraint('user_id', 'permission_id'),
1501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1502 'mysql_charset': 'utf8'}
1494 'mysql_charset': 'utf8'}
1503 )
1495 )
1504 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1496 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1505 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1497 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1506 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1498 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1507
1499
1508 user = relationship('User')
1500 user = relationship('User')
1509 permission = relationship('Permission', lazy='joined')
1501 permission = relationship('Permission', lazy='joined')
1510
1502
1511
1503
1512 class UserGroupRepoToPerm(Base, BaseModel):
1504 class UserGroupRepoToPerm(Base, BaseModel):
1513 __tablename__ = 'users_group_repo_to_perm'
1505 __tablename__ = 'users_group_repo_to_perm'
1514 __table_args__ = (
1506 __table_args__ = (
1515 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1507 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1516 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1508 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1517 'mysql_charset': 'utf8'}
1509 'mysql_charset': 'utf8'}
1518 )
1510 )
1519 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1511 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1520 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1512 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1521 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1513 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1522 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1514 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1523
1515
1524 users_group = relationship('UserGroup')
1516 users_group = relationship('UserGroup')
1525 permission = relationship('Permission')
1517 permission = relationship('Permission')
1526 repository = relationship('Repository')
1518 repository = relationship('Repository')
1527
1519
1528 @classmethod
1520 @classmethod
1529 def create(cls, users_group, repository, permission):
1521 def create(cls, users_group, repository, permission):
1530 n = cls()
1522 n = cls()
1531 n.users_group = users_group
1523 n.users_group = users_group
1532 n.repository = repository
1524 n.repository = repository
1533 n.permission = permission
1525 n.permission = permission
1534 Session().add(n)
1526 Session().add(n)
1535 return n
1527 return n
1536
1528
1537 def __unicode__(self):
1529 def __unicode__(self):
1538 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1530 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1539
1531
1540
1532
1541 class UserGroupToPerm(Base, BaseModel):
1533 class UserGroupToPerm(Base, BaseModel):
1542 __tablename__ = 'users_group_to_perm'
1534 __tablename__ = 'users_group_to_perm'
1543 __table_args__ = (
1535 __table_args__ = (
1544 UniqueConstraint('users_group_id', 'permission_id',),
1536 UniqueConstraint('users_group_id', 'permission_id',),
1545 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1537 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1546 'mysql_charset': 'utf8'}
1538 'mysql_charset': 'utf8'}
1547 )
1539 )
1548 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1540 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1549 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1541 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1550 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1542 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1551
1543
1552 users_group = relationship('UserGroup')
1544 users_group = relationship('UserGroup')
1553 permission = relationship('Permission')
1545 permission = relationship('Permission')
1554
1546
1555
1547
1556 class UserRepoGroupToPerm(Base, BaseModel):
1548 class UserRepoGroupToPerm(Base, BaseModel):
1557 __tablename__ = 'user_repo_group_to_perm'
1549 __tablename__ = 'user_repo_group_to_perm'
1558 __table_args__ = (
1550 __table_args__ = (
1559 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1551 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1560 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1552 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1561 'mysql_charset': 'utf8'}
1553 'mysql_charset': 'utf8'}
1562 )
1554 )
1563
1555
1564 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1556 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1557 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1566 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1558 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1567 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1559 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1568
1560
1569 user = relationship('User')
1561 user = relationship('User')
1570 group = relationship('RepoGroup')
1562 group = relationship('RepoGroup')
1571 permission = relationship('Permission')
1563 permission = relationship('Permission')
1572
1564
1573
1565
1574 class UserGroupRepoGroupToPerm(Base, BaseModel):
1566 class UserGroupRepoGroupToPerm(Base, BaseModel):
1575 __tablename__ = 'users_group_repo_group_to_perm'
1567 __tablename__ = 'users_group_repo_group_to_perm'
1576 __table_args__ = (
1568 __table_args__ = (
1577 UniqueConstraint('users_group_id', 'group_id'),
1569 UniqueConstraint('users_group_id', 'group_id'),
1578 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1570 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1579 'mysql_charset': 'utf8'}
1571 'mysql_charset': 'utf8'}
1580 )
1572 )
1581
1573
1582 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)
1574 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)
1583 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1575 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1584 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1576 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1585 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1577 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1586
1578
1587 users_group = relationship('UserGroup')
1579 users_group = relationship('UserGroup')
1588 permission = relationship('Permission')
1580 permission = relationship('Permission')
1589 group = relationship('RepoGroup')
1581 group = relationship('RepoGroup')
1590
1582
1591
1583
1592 class Statistics(Base, BaseModel):
1584 class Statistics(Base, BaseModel):
1593 __tablename__ = 'statistics'
1585 __tablename__ = 'statistics'
1594 __table_args__ = (
1586 __table_args__ = (
1595 UniqueConstraint('repository_id'),
1587 UniqueConstraint('repository_id'),
1596 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1597 'mysql_charset': 'utf8'}
1589 'mysql_charset': 'utf8'}
1598 )
1590 )
1599 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1600 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1592 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1601 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1593 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1602 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1594 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1603 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1595 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1604 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1596 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1605
1597
1606 repository = relationship('Repository', single_parent=True)
1598 repository = relationship('Repository', single_parent=True)
1607
1599
1608
1600
1609 class UserFollowing(Base, BaseModel):
1601 class UserFollowing(Base, BaseModel):
1610 __tablename__ = 'user_followings'
1602 __tablename__ = 'user_followings'
1611 __table_args__ = (
1603 __table_args__ = (
1612 UniqueConstraint('user_id', 'follows_repository_id'),
1604 UniqueConstraint('user_id', 'follows_repository_id'),
1613 UniqueConstraint('user_id', 'follows_user_id'),
1605 UniqueConstraint('user_id', 'follows_user_id'),
1614 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1606 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1615 'mysql_charset': 'utf8'}
1607 'mysql_charset': 'utf8'}
1616 )
1608 )
1617
1609
1618 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1610 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1619 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1611 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1620 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1612 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1621 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1613 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1622 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1614 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1623
1615
1624 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1616 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1625
1617
1626 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1618 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1627 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1619 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1628
1620
1629 @classmethod
1621 @classmethod
1630 def get_repo_followers(cls, repo_id):
1622 def get_repo_followers(cls, repo_id):
1631 return cls.query().filter(cls.follows_repo_id == repo_id)
1623 return cls.query().filter(cls.follows_repo_id == repo_id)
1632
1624
1633
1625
1634 class CacheInvalidation(Base, BaseModel):
1626 class CacheInvalidation(Base, BaseModel):
1635 __tablename__ = 'cache_invalidation'
1627 __tablename__ = 'cache_invalidation'
1636 __table_args__ = (
1628 __table_args__ = (
1637 UniqueConstraint('cache_key'),
1629 UniqueConstraint('cache_key'),
1638 Index('key_idx', 'cache_key'),
1630 Index('key_idx', 'cache_key'),
1639 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1631 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1640 'mysql_charset': 'utf8'},
1632 'mysql_charset': 'utf8'},
1641 )
1633 )
1642 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1634 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1643 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1635 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1644 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1636 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1645 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1637 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1646
1638
1647 def __init__(self, cache_key, cache_args=''):
1639 def __init__(self, cache_key, cache_args=''):
1648 self.cache_key = cache_key
1640 self.cache_key = cache_key
1649 self.cache_args = cache_args
1641 self.cache_args = cache_args
1650 self.cache_active = False
1642 self.cache_active = False
1651
1643
1652 def __unicode__(self):
1644 def __unicode__(self):
1653 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1645 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1654 self.cache_id, self.cache_key)
1646 self.cache_id, self.cache_key)
1655
1647
1656 @property
1648 @property
1657 def prefix(self):
1649 def prefix(self):
1658 _split = self.cache_key.split(self.cache_args, 1)
1650 _split = self.cache_key.split(self.cache_args, 1)
1659 if _split and len(_split) == 2:
1651 if _split and len(_split) == 2:
1660 return _split[0]
1652 return _split[0]
1661 return ''
1653 return ''
1662
1654
1663 @classmethod
1655 @classmethod
1664 def clear_cache(cls):
1656 def clear_cache(cls):
1665 cls.query().delete()
1657 cls.query().delete()
1666
1658
1667 @classmethod
1659 @classmethod
1668 def _get_key(cls, key):
1660 def _get_key(cls, key):
1669 """
1661 """
1670 Wrapper for generating a key, together with a prefix
1662 Wrapper for generating a key, together with a prefix
1671
1663
1672 :param key:
1664 :param key:
1673 """
1665 """
1674 import rhodecode
1666 import rhodecode
1675 prefix = ''
1667 prefix = ''
1676 org_key = key
1668 org_key = key
1677 iid = rhodecode.CONFIG.get('instance_id')
1669 iid = rhodecode.CONFIG.get('instance_id')
1678 if iid:
1670 if iid:
1679 prefix = iid
1671 prefix = iid
1680
1672
1681 return "%s%s" % (prefix, key), prefix, org_key
1673 return "%s%s" % (prefix, key), prefix, org_key
1682
1674
1683 @classmethod
1675 @classmethod
1684 def get_by_key(cls, key):
1676 def get_by_key(cls, key):
1685 return cls.query().filter(cls.cache_key == key).scalar()
1677 return cls.query().filter(cls.cache_key == key).scalar()
1686
1678
1687 @classmethod
1679 @classmethod
1688 def get_by_repo_name(cls, repo_name):
1680 def get_by_repo_name(cls, repo_name):
1689 return cls.query().filter(cls.cache_args == repo_name).all()
1681 return cls.query().filter(cls.cache_args == repo_name).all()
1690
1682
1691 @classmethod
1683 @classmethod
1692 def _get_or_create_key(cls, key, repo_name, commit=True):
1684 def _get_or_create_key(cls, key, repo_name, commit=True):
1693 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1685 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1694 if not inv_obj:
1686 if not inv_obj:
1695 try:
1687 try:
1696 inv_obj = CacheInvalidation(key, repo_name)
1688 inv_obj = CacheInvalidation(key, repo_name)
1697 Session().add(inv_obj)
1689 Session().add(inv_obj)
1698 if commit:
1690 if commit:
1699 Session().commit()
1691 Session().commit()
1700 except Exception:
1692 except Exception:
1701 log.error(traceback.format_exc())
1693 log.error(traceback.format_exc())
1702 Session().rollback()
1694 Session().rollback()
1703 return inv_obj
1695 return inv_obj
1704
1696
1705 @classmethod
1697 @classmethod
1706 def invalidate(cls, key):
1698 def invalidate(cls, key):
1707 """
1699 """
1708 Returns Invalidation object if this given key should be invalidated
1700 Returns Invalidation object if this given key should be invalidated
1709 None otherwise. `cache_active = False` means that this cache
1701 None otherwise. `cache_active = False` means that this cache
1710 state is not valid and needs to be invalidated
1702 state is not valid and needs to be invalidated
1711
1703
1712 :param key:
1704 :param key:
1713 """
1705 """
1714 repo_name = key
1706 repo_name = key
1715 repo_name = remove_suffix(repo_name, '_README')
1707 repo_name = remove_suffix(repo_name, '_README')
1716 repo_name = remove_suffix(repo_name, '_RSS')
1708 repo_name = remove_suffix(repo_name, '_RSS')
1717 repo_name = remove_suffix(repo_name, '_ATOM')
1709 repo_name = remove_suffix(repo_name, '_ATOM')
1718
1710
1719 # adds instance prefix
1711 # adds instance prefix
1720 key, _prefix, _org_key = cls._get_key(key)
1712 key, _prefix, _org_key = cls._get_key(key)
1721 inv = cls._get_or_create_key(key, repo_name)
1713 inv = cls._get_or_create_key(key, repo_name)
1722
1714
1723 if inv and inv.cache_active is False:
1715 if inv and inv.cache_active is False:
1724 return inv
1716 return inv
1725
1717
1726 @classmethod
1718 @classmethod
1727 def set_invalidate(cls, key=None, repo_name=None):
1719 def set_invalidate(cls, key=None, repo_name=None):
1728 """
1720 """
1729 Mark this Cache key for invalidation, either by key or whole
1721 Mark this Cache key for invalidation, either by key or whole
1730 cache sets based on repo_name
1722 cache sets based on repo_name
1731
1723
1732 :param key:
1724 :param key:
1733 """
1725 """
1734 invalidated_keys = []
1726 invalidated_keys = []
1735 if key:
1727 if key:
1736 key, _prefix, _org_key = cls._get_key(key)
1728 key, _prefix, _org_key = cls._get_key(key)
1737 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1729 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1738 elif repo_name:
1730 elif repo_name:
1739 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1731 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1740
1732
1741 try:
1733 try:
1742 for inv_obj in inv_objs:
1734 for inv_obj in inv_objs:
1743 inv_obj.cache_active = False
1735 inv_obj.cache_active = False
1744 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1736 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1745 % (inv_obj, key, repo_name))
1737 % (inv_obj, key, repo_name))
1746 invalidated_keys.append(inv_obj.cache_key)
1738 invalidated_keys.append(inv_obj.cache_key)
1747 Session().add(inv_obj)
1739 Session().add(inv_obj)
1748 Session().commit()
1740 Session().commit()
1749 except Exception:
1741 except Exception:
1750 log.error(traceback.format_exc())
1742 log.error(traceback.format_exc())
1751 Session().rollback()
1743 Session().rollback()
1752 return invalidated_keys
1744 return invalidated_keys
1753
1745
1754 @classmethod
1746 @classmethod
1755 def set_valid(cls, key):
1747 def set_valid(cls, key):
1756 """
1748 """
1757 Mark this cache key as active and currently cached
1749 Mark this cache key as active and currently cached
1758
1750
1759 :param key:
1751 :param key:
1760 """
1752 """
1761 inv_obj = cls.get_by_key(key)
1753 inv_obj = cls.get_by_key(key)
1762 inv_obj.cache_active = True
1754 inv_obj.cache_active = True
1763 Session().add(inv_obj)
1755 Session().add(inv_obj)
1764 Session().commit()
1756 Session().commit()
1765
1757
1766 @classmethod
1758 @classmethod
1767 def get_cache_map(cls):
1759 def get_cache_map(cls):
1768
1760
1769 class cachemapdict(dict):
1761 class cachemapdict(dict):
1770
1762
1771 def __init__(self, *args, **kwargs):
1763 def __init__(self, *args, **kwargs):
1772 fixkey = kwargs.get('fixkey')
1764 fixkey = kwargs.get('fixkey')
1773 if fixkey:
1765 if fixkey:
1774 del kwargs['fixkey']
1766 del kwargs['fixkey']
1775 self.fixkey = fixkey
1767 self.fixkey = fixkey
1776 super(cachemapdict, self).__init__(*args, **kwargs)
1768 super(cachemapdict, self).__init__(*args, **kwargs)
1777
1769
1778 def __getattr__(self, name):
1770 def __getattr__(self, name):
1779 key = name
1771 key = name
1780 if self.fixkey:
1772 if self.fixkey:
1781 key, _prefix, _org_key = cls._get_key(key)
1773 key, _prefix, _org_key = cls._get_key(key)
1782 if key in self.__dict__:
1774 if key in self.__dict__:
1783 return self.__dict__[key]
1775 return self.__dict__[key]
1784 else:
1776 else:
1785 return self[key]
1777 return self[key]
1786
1778
1787 def __getitem__(self, key):
1779 def __getitem__(self, key):
1788 if self.fixkey:
1780 if self.fixkey:
1789 key, _prefix, _org_key = cls._get_key(key)
1781 key, _prefix, _org_key = cls._get_key(key)
1790 try:
1782 try:
1791 return super(cachemapdict, self).__getitem__(key)
1783 return super(cachemapdict, self).__getitem__(key)
1792 except KeyError:
1784 except KeyError:
1793 return
1785 return
1794
1786
1795 cache_map = cachemapdict(fixkey=True)
1787 cache_map = cachemapdict(fixkey=True)
1796 for obj in cls.query().all():
1788 for obj in cls.query().all():
1797 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1789 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1798 return cache_map
1790 return cache_map
1799
1791
1800
1792
1801 class ChangesetComment(Base, BaseModel):
1793 class ChangesetComment(Base, BaseModel):
1802 __tablename__ = 'changeset_comments'
1794 __tablename__ = 'changeset_comments'
1803 __table_args__ = (
1795 __table_args__ = (
1804 Index('cc_revision_idx', 'revision'),
1796 Index('cc_revision_idx', 'revision'),
1805 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1797 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1806 'mysql_charset': 'utf8'},
1798 'mysql_charset': 'utf8'},
1807 )
1799 )
1808 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1800 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1809 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1801 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1810 revision = Column('revision', String(40), nullable=True)
1802 revision = Column('revision', String(40), nullable=True)
1811 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1803 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1812 line_no = Column('line_no', Unicode(10), nullable=True)
1804 line_no = Column('line_no', Unicode(10), nullable=True)
1813 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1805 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1814 f_path = Column('f_path', Unicode(1000), nullable=True)
1806 f_path = Column('f_path', Unicode(1000), nullable=True)
1815 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1807 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1816 text = Column('text', UnicodeText(25000), nullable=False)
1808 text = Column('text', UnicodeText(25000), nullable=False)
1817 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1809 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1818 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1810 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1819
1811
1820 author = relationship('User', lazy='joined')
1812 author = relationship('User', lazy='joined')
1821 repo = relationship('Repository')
1813 repo = relationship('Repository')
1822 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1814 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1823 pull_request = relationship('PullRequest', lazy='joined')
1815 pull_request = relationship('PullRequest', lazy='joined')
1824
1816
1825 @classmethod
1817 @classmethod
1826 def get_users(cls, revision=None, pull_request_id=None):
1818 def get_users(cls, revision=None, pull_request_id=None):
1827 """
1819 """
1828 Returns user associated with this ChangesetComment. ie those
1820 Returns user associated with this ChangesetComment. ie those
1829 who actually commented
1821 who actually commented
1830
1822
1831 :param cls:
1823 :param cls:
1832 :param revision:
1824 :param revision:
1833 """
1825 """
1834 q = Session().query(User)\
1826 q = Session().query(User)\
1835 .join(ChangesetComment.author)
1827 .join(ChangesetComment.author)
1836 if revision:
1828 if revision:
1837 q = q.filter(cls.revision == revision)
1829 q = q.filter(cls.revision == revision)
1838 elif pull_request_id:
1830 elif pull_request_id:
1839 q = q.filter(cls.pull_request_id == pull_request_id)
1831 q = q.filter(cls.pull_request_id == pull_request_id)
1840 return q.all()
1832 return q.all()
1841
1833
1842
1834
1843 class ChangesetStatus(Base, BaseModel):
1835 class ChangesetStatus(Base, BaseModel):
1844 __tablename__ = 'changeset_statuses'
1836 __tablename__ = 'changeset_statuses'
1845 __table_args__ = (
1837 __table_args__ = (
1846 Index('cs_revision_idx', 'revision'),
1838 Index('cs_revision_idx', 'revision'),
1847 Index('cs_version_idx', 'version'),
1839 Index('cs_version_idx', 'version'),
1848 UniqueConstraint('repo_id', 'revision', 'version'),
1840 UniqueConstraint('repo_id', 'revision', 'version'),
1849 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1841 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1850 'mysql_charset': 'utf8'}
1842 'mysql_charset': 'utf8'}
1851 )
1843 )
1852 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1844 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1853 STATUS_APPROVED = 'approved'
1845 STATUS_APPROVED = 'approved'
1854 STATUS_REJECTED = 'rejected'
1846 STATUS_REJECTED = 'rejected'
1855 STATUS_UNDER_REVIEW = 'under_review'
1847 STATUS_UNDER_REVIEW = 'under_review'
1856
1848
1857 STATUSES = [
1849 STATUSES = [
1858 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1850 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1859 (STATUS_APPROVED, _("Approved")),
1851 (STATUS_APPROVED, _("Approved")),
1860 (STATUS_REJECTED, _("Rejected")),
1852 (STATUS_REJECTED, _("Rejected")),
1861 (STATUS_UNDER_REVIEW, _("Under Review")),
1853 (STATUS_UNDER_REVIEW, _("Under Review")),
1862 ]
1854 ]
1863
1855
1864 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1856 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1865 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1857 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1866 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1858 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1867 revision = Column('revision', String(40), nullable=False)
1859 revision = Column('revision', String(40), nullable=False)
1868 status = Column('status', String(128), nullable=False, default=DEFAULT)
1860 status = Column('status', String(128), nullable=False, default=DEFAULT)
1869 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1861 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1870 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1862 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1871 version = Column('version', Integer(), nullable=False, default=0)
1863 version = Column('version', Integer(), nullable=False, default=0)
1872 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1864 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1873
1865
1874 author = relationship('User', lazy='joined')
1866 author = relationship('User', lazy='joined')
1875 repo = relationship('Repository')
1867 repo = relationship('Repository')
1876 comment = relationship('ChangesetComment', lazy='joined')
1868 comment = relationship('ChangesetComment', lazy='joined')
1877 pull_request = relationship('PullRequest', lazy='joined')
1869 pull_request = relationship('PullRequest', lazy='joined')
1878
1870
1879 def __unicode__(self):
1871 def __unicode__(self):
1880 return u"<%s('%s:%s')>" % (
1872 return u"<%s('%s:%s')>" % (
1881 self.__class__.__name__,
1873 self.__class__.__name__,
1882 self.status, self.author
1874 self.status, self.author
1883 )
1875 )
1884
1876
1885 @classmethod
1877 @classmethod
1886 def get_status_lbl(cls, value):
1878 def get_status_lbl(cls, value):
1887 return dict(cls.STATUSES).get(value)
1879 return dict(cls.STATUSES).get(value)
1888
1880
1889 @property
1881 @property
1890 def status_lbl(self):
1882 def status_lbl(self):
1891 return ChangesetStatus.get_status_lbl(self.status)
1883 return ChangesetStatus.get_status_lbl(self.status)
1892
1884
1893
1885
1894 class PullRequest(Base, BaseModel):
1886 class PullRequest(Base, BaseModel):
1895 __tablename__ = 'pull_requests'
1887 __tablename__ = 'pull_requests'
1896 __table_args__ = (
1888 __table_args__ = (
1897 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1889 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1898 'mysql_charset': 'utf8'},
1890 'mysql_charset': 'utf8'},
1899 )
1891 )
1900
1892
1901 STATUS_NEW = u'new'
1893 STATUS_NEW = u'new'
1902 STATUS_OPEN = u'open'
1894 STATUS_OPEN = u'open'
1903 STATUS_CLOSED = u'closed'
1895 STATUS_CLOSED = u'closed'
1904
1896
1905 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1897 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1906 title = Column('title', Unicode(256), nullable=True)
1898 title = Column('title', Unicode(256), nullable=True)
1907 description = Column('description', UnicodeText(10240), nullable=True)
1899 description = Column('description', UnicodeText(10240), nullable=True)
1908 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1900 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1909 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1901 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1910 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1902 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1911 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1903 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1912 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1904 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1913 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1905 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1914 org_ref = Column('org_ref', Unicode(256), nullable=False)
1906 org_ref = Column('org_ref', Unicode(256), nullable=False)
1915 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1907 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1916 other_ref = Column('other_ref', Unicode(256), nullable=False)
1908 other_ref = Column('other_ref', Unicode(256), nullable=False)
1917
1909
1918 @hybrid_property
1910 @hybrid_property
1919 def revisions(self):
1911 def revisions(self):
1920 return self._revisions.split(':')
1912 return self._revisions.split(':')
1921
1913
1922 @revisions.setter
1914 @revisions.setter
1923 def revisions(self, val):
1915 def revisions(self, val):
1924 self._revisions = ':'.join(val)
1916 self._revisions = ':'.join(val)
1925
1917
1926 @property
1918 @property
1927 def org_ref_parts(self):
1919 def org_ref_parts(self):
1928 return self.org_ref.split(':')
1920 return self.org_ref.split(':')
1929
1921
1930 @property
1922 @property
1931 def other_ref_parts(self):
1923 def other_ref_parts(self):
1932 return self.other_ref.split(':')
1924 return self.other_ref.split(':')
1933
1925
1934 author = relationship('User', lazy='joined')
1926 author = relationship('User', lazy='joined')
1935 reviewers = relationship('PullRequestReviewers',
1927 reviewers = relationship('PullRequestReviewers',
1936 cascade="all, delete, delete-orphan")
1928 cascade="all, delete, delete-orphan")
1937 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1929 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1938 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1930 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1939 statuses = relationship('ChangesetStatus')
1931 statuses = relationship('ChangesetStatus')
1940 comments = relationship('ChangesetComment',
1932 comments = relationship('ChangesetComment',
1941 cascade="all, delete, delete-orphan")
1933 cascade="all, delete, delete-orphan")
1942
1934
1943 def is_closed(self):
1935 def is_closed(self):
1944 return self.status == self.STATUS_CLOSED
1936 return self.status == self.STATUS_CLOSED
1945
1937
1946 @property
1938 @property
1947 def last_review_status(self):
1939 def last_review_status(self):
1948 return self.statuses[-1].status if self.statuses else ''
1940 return self.statuses[-1].status if self.statuses else ''
1949
1941
1950 def __json__(self):
1942 def __json__(self):
1951 return dict(
1943 return dict(
1952 revisions=self.revisions
1944 revisions=self.revisions
1953 )
1945 )
1954
1946
1955
1947
1956 class PullRequestReviewers(Base, BaseModel):
1948 class PullRequestReviewers(Base, BaseModel):
1957 __tablename__ = 'pull_request_reviewers'
1949 __tablename__ = 'pull_request_reviewers'
1958 __table_args__ = (
1950 __table_args__ = (
1959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1951 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1960 'mysql_charset': 'utf8'},
1952 'mysql_charset': 'utf8'},
1961 )
1953 )
1962
1954
1963 def __init__(self, user=None, pull_request=None):
1955 def __init__(self, user=None, pull_request=None):
1964 self.user = user
1956 self.user = user
1965 self.pull_request = pull_request
1957 self.pull_request = pull_request
1966
1958
1967 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1959 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1968 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1960 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1961 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1970
1962
1971 user = relationship('User')
1963 user = relationship('User')
1972 pull_request = relationship('PullRequest')
1964 pull_request = relationship('PullRequest')
1973
1965
1974
1966
1975 class Notification(Base, BaseModel):
1967 class Notification(Base, BaseModel):
1976 __tablename__ = 'notifications'
1968 __tablename__ = 'notifications'
1977 __table_args__ = (
1969 __table_args__ = (
1978 Index('notification_type_idx', 'type'),
1970 Index('notification_type_idx', 'type'),
1979 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1971 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1980 'mysql_charset': 'utf8'},
1972 'mysql_charset': 'utf8'},
1981 )
1973 )
1982
1974
1983 TYPE_CHANGESET_COMMENT = u'cs_comment'
1975 TYPE_CHANGESET_COMMENT = u'cs_comment'
1984 TYPE_MESSAGE = u'message'
1976 TYPE_MESSAGE = u'message'
1985 TYPE_MENTION = u'mention'
1977 TYPE_MENTION = u'mention'
1986 TYPE_REGISTRATION = u'registration'
1978 TYPE_REGISTRATION = u'registration'
1987 TYPE_PULL_REQUEST = u'pull_request'
1979 TYPE_PULL_REQUEST = u'pull_request'
1988 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1980 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1989
1981
1990 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1982 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1991 subject = Column('subject', Unicode(512), nullable=True)
1983 subject = Column('subject', Unicode(512), nullable=True)
1992 body = Column('body', UnicodeText(50000), nullable=True)
1984 body = Column('body', UnicodeText(50000), nullable=True)
1993 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1985 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1994 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1986 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1995 type_ = Column('type', Unicode(256))
1987 type_ = Column('type', Unicode(256))
1996
1988
1997 created_by_user = relationship('User')
1989 created_by_user = relationship('User')
1998 notifications_to_users = relationship('UserNotification', lazy='joined',
1990 notifications_to_users = relationship('UserNotification', lazy='joined',
1999 cascade="all, delete, delete-orphan")
1991 cascade="all, delete, delete-orphan")
2000
1992
2001 @property
1993 @property
2002 def recipients(self):
1994 def recipients(self):
2003 return [x.user for x in UserNotification.query()\
1995 return [x.user for x in UserNotification.query()\
2004 .filter(UserNotification.notification == self)\
1996 .filter(UserNotification.notification == self)\
2005 .order_by(UserNotification.user_id.asc()).all()]
1997 .order_by(UserNotification.user_id.asc()).all()]
2006
1998
2007 @classmethod
1999 @classmethod
2008 def create(cls, created_by, subject, body, recipients, type_=None):
2000 def create(cls, created_by, subject, body, recipients, type_=None):
2009 if type_ is None:
2001 if type_ is None:
2010 type_ = Notification.TYPE_MESSAGE
2002 type_ = Notification.TYPE_MESSAGE
2011
2003
2012 notification = cls()
2004 notification = cls()
2013 notification.created_by_user = created_by
2005 notification.created_by_user = created_by
2014 notification.subject = subject
2006 notification.subject = subject
2015 notification.body = body
2007 notification.body = body
2016 notification.type_ = type_
2008 notification.type_ = type_
2017 notification.created_on = datetime.datetime.now()
2009 notification.created_on = datetime.datetime.now()
2018
2010
2019 for u in recipients:
2011 for u in recipients:
2020 assoc = UserNotification()
2012 assoc = UserNotification()
2021 assoc.notification = notification
2013 assoc.notification = notification
2022 u.notifications.append(assoc)
2014 u.notifications.append(assoc)
2023 Session().add(notification)
2015 Session().add(notification)
2024 return notification
2016 return notification
2025
2017
2026 @property
2018 @property
2027 def description(self):
2019 def description(self):
2028 from rhodecode.model.notification import NotificationModel
2020 from rhodecode.model.notification import NotificationModel
2029 return NotificationModel().make_description(self)
2021 return NotificationModel().make_description(self)
2030
2022
2031
2023
2032 class UserNotification(Base, BaseModel):
2024 class UserNotification(Base, BaseModel):
2033 __tablename__ = 'user_to_notification'
2025 __tablename__ = 'user_to_notification'
2034 __table_args__ = (
2026 __table_args__ = (
2035 UniqueConstraint('user_id', 'notification_id'),
2027 UniqueConstraint('user_id', 'notification_id'),
2036 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2028 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2037 'mysql_charset': 'utf8'}
2029 'mysql_charset': 'utf8'}
2038 )
2030 )
2039 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2031 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2040 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2032 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2041 read = Column('read', Boolean, default=False)
2033 read = Column('read', Boolean, default=False)
2042 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2034 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2043
2035
2044 user = relationship('User', lazy="joined")
2036 user = relationship('User', lazy="joined")
2045 notification = relationship('Notification', lazy="joined",
2037 notification = relationship('Notification', lazy="joined",
2046 order_by=lambda: Notification.created_on.desc(),)
2038 order_by=lambda: Notification.created_on.desc(),)
2047
2039
2048 def mark_as_read(self):
2040 def mark_as_read(self):
2049 self.read = True
2041 self.read = True
2050 Session().add(self)
2042 Session().add(self)
2051
2043
2052
2044
2053 class DbMigrateVersion(Base, BaseModel):
2045 class DbMigrateVersion(Base, BaseModel):
2054 __tablename__ = 'db_migrate_version'
2046 __tablename__ = 'db_migrate_version'
2055 __table_args__ = (
2047 __table_args__ = (
2056 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2048 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2057 'mysql_charset': 'utf8'},
2049 'mysql_charset': 'utf8'},
2058 )
2050 )
2059 repository_id = Column('repository_id', String(250), primary_key=True)
2051 repository_id = Column('repository_id', String(250), primary_key=True)
2060 repository_path = Column('repository_path', Text)
2052 repository_path = Column('repository_path', Text)
2061 version = Column('version', Integer)
2053 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now