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