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