##// END OF EJS Templates
whitespace and formatting
marcink -
r3057:79c5967a beta
parent child Browse files
Show More
@@ -1,1815 +1,1814 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db_1_4_0
3 rhodecode.model.db_1_4_0
4 ~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode <=1.4.X
6 Database Models for RhodeCode <=1.4.X
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 import time
32 from collections import defaultdict
32 from collections import defaultdict
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47
47
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 safe_unicode, remove_suffix
49 safe_unicode, remove_suffix
50 from rhodecode.lib.compat import json
50 from rhodecode.lib.compat import json
51 from rhodecode.lib.caching_query import FromCache
51 from rhodecode.lib.caching_query import FromCache
52
52
53 from rhodecode.model.meta import Base, Session
53 from rhodecode.model.meta import Base, Session
54
54
55 URL_SEP = '/'
55 URL_SEP = '/'
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58 #==============================================================================
58 #==============================================================================
59 # BASE CLASSES
59 # BASE CLASSES
60 #==============================================================================
60 #==============================================================================
61
61
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63
63
64
64
65 class BaseModel(object):
65 class BaseModel(object):
66 """
66 """
67 Base Model for all classess
67 Base Model for all classess
68 """
68 """
69
69
70 @classmethod
70 @classmethod
71 def _get_keys(cls):
71 def _get_keys(cls):
72 """return column names for this model """
72 """return column names for this model """
73 return class_mapper(cls).c.keys()
73 return class_mapper(cls).c.keys()
74
74
75 def get_dict(self):
75 def get_dict(self):
76 """
76 """
77 return dict with keys and values corresponding
77 return dict with keys and values corresponding
78 to this model data """
78 to this model data """
79
79
80 d = {}
80 d = {}
81 for k in self._get_keys():
81 for k in self._get_keys():
82 d[k] = getattr(self, k)
82 d[k] = getattr(self, k)
83
83
84 # also use __json__() if present to get additional fields
84 # also use __json__() if present to get additional fields
85 _json_attr = getattr(self, '__json__', None)
85 _json_attr = getattr(self, '__json__', None)
86 if _json_attr:
86 if _json_attr:
87 # update with attributes from __json__
87 # update with attributes from __json__
88 if callable(_json_attr):
88 if callable(_json_attr):
89 _json_attr = _json_attr()
89 _json_attr = _json_attr()
90 for k, val in _json_attr.iteritems():
90 for k, val in _json_attr.iteritems():
91 d[k] = val
91 d[k] = val
92 return d
92 return d
93
93
94 def get_appstruct(self):
94 def get_appstruct(self):
95 """return list with keys and values tupples corresponding
95 """return list with keys and values tupples corresponding
96 to this model data """
96 to this model data """
97
97
98 l = []
98 l = []
99 for k in self._get_keys():
99 for k in self._get_keys():
100 l.append((k, getattr(self, k),))
100 l.append((k, getattr(self, k),))
101 return l
101 return l
102
102
103 def populate_obj(self, populate_dict):
103 def populate_obj(self, populate_dict):
104 """populate model with data from given populate_dict"""
104 """populate model with data from given populate_dict"""
105
105
106 for k in self._get_keys():
106 for k in self._get_keys():
107 if k in populate_dict:
107 if k in populate_dict:
108 setattr(self, k, populate_dict[k])
108 setattr(self, k, populate_dict[k])
109
109
110 @classmethod
110 @classmethod
111 def query(cls):
111 def query(cls):
112 return Session().query(cls)
112 return Session().query(cls)
113
113
114 @classmethod
114 @classmethod
115 def get(cls, id_):
115 def get(cls, id_):
116 if id_:
116 if id_:
117 return cls.query().get(id_)
117 return cls.query().get(id_)
118
118
119 @classmethod
119 @classmethod
120 def get_or_404(cls, id_):
120 def get_or_404(cls, id_):
121 try:
121 try:
122 id_ = int(id_)
122 id_ = int(id_)
123 except (TypeError, ValueError):
123 except (TypeError, ValueError):
124 raise HTTPNotFound
124 raise HTTPNotFound
125
125
126 res = cls.query().get(id_)
126 res = cls.query().get(id_)
127 if not res:
127 if not res:
128 raise HTTPNotFound
128 raise HTTPNotFound
129 return res
129 return res
130
130
131 @classmethod
131 @classmethod
132 def getAll(cls):
132 def getAll(cls):
133 return cls.query().all()
133 return cls.query().all()
134
134
135 @classmethod
135 @classmethod
136 def delete(cls, id_):
136 def delete(cls, id_):
137 obj = cls.query().get(id_)
137 obj = cls.query().get(id_)
138 Session().delete(obj)
138 Session().delete(obj)
139
139
140 def __repr__(self):
140 def __repr__(self):
141 if hasattr(self, '__unicode__'):
141 if hasattr(self, '__unicode__'):
142 # python repr needs to return str
142 # python repr needs to return str
143 return safe_str(self.__unicode__())
143 return safe_str(self.__unicode__())
144 return '<DB:%s>' % (self.__class__.__name__)
144 return '<DB:%s>' % (self.__class__.__name__)
145
145
146
146
147 class RhodeCodeSetting(Base, BaseModel):
147 class RhodeCodeSetting(Base, BaseModel):
148 __tablename__ = 'rhodecode_settings'
148 __tablename__ = 'rhodecode_settings'
149 __table_args__ = (
149 __table_args__ = (
150 UniqueConstraint('app_settings_name'),
150 UniqueConstraint('app_settings_name'),
151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 'mysql_charset': 'utf8'}
152 'mysql_charset': 'utf8'}
153 )
153 )
154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
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 def __init__(self, k='', v=''):
158 def __init__(self, k='', v=''):
159 self.app_settings_name = k
159 self.app_settings_name = k
160 self.app_settings_value = v
160 self.app_settings_value = v
161
161
162 @validates('_app_settings_value')
162 @validates('_app_settings_value')
163 def validate_settings_value(self, key, val):
163 def validate_settings_value(self, key, val):
164 assert type(val) == unicode
164 assert type(val) == unicode
165 return val
165 return val
166
166
167 @hybrid_property
167 @hybrid_property
168 def app_settings_value(self):
168 def app_settings_value(self):
169 v = self._app_settings_value
169 v = self._app_settings_value
170 if self.app_settings_name == 'ldap_active':
170 if self.app_settings_name == 'ldap_active':
171 v = str2bool(v)
171 v = str2bool(v)
172 return v
172 return v
173
173
174 @app_settings_value.setter
174 @app_settings_value.setter
175 def app_settings_value(self, val):
175 def app_settings_value(self, val):
176 """
176 """
177 Setter that will always make sure we use unicode in app_settings_value
177 Setter that will always make sure we use unicode in app_settings_value
178
178
179 :param val:
179 :param val:
180 """
180 """
181 self._app_settings_value = safe_unicode(val)
181 self._app_settings_value = safe_unicode(val)
182
182
183 def __unicode__(self):
183 def __unicode__(self):
184 return u"<%s('%s:%s')>" % (
184 return u"<%s('%s:%s')>" % (
185 self.__class__.__name__,
185 self.__class__.__name__,
186 self.app_settings_name, self.app_settings_value
186 self.app_settings_name, self.app_settings_value
187 )
187 )
188
188
189 @classmethod
189 @classmethod
190 def get_by_name(cls, key):
190 def get_by_name(cls, key):
191 return cls.query()\
191 return cls.query()\
192 .filter(cls.app_settings_name == key).scalar()
192 .filter(cls.app_settings_name == key).scalar()
193
193
194 @classmethod
194 @classmethod
195 def get_by_name_or_create(cls, key):
195 def get_by_name_or_create(cls, key):
196 res = cls.get_by_name(key)
196 res = cls.get_by_name(key)
197 if not res:
197 if not res:
198 res = cls(key)
198 res = cls(key)
199 return res
199 return res
200
200
201 @classmethod
201 @classmethod
202 def get_app_settings(cls, cache=False):
202 def get_app_settings(cls, cache=False):
203
203
204 ret = cls.query()
204 ret = cls.query()
205
205
206 if cache:
206 if cache:
207 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
207 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
208
208
209 if not ret:
209 if not ret:
210 raise Exception('Could not get application settings !')
210 raise Exception('Could not get application settings !')
211 settings = {}
211 settings = {}
212 for each in ret:
212 for each in ret:
213 settings['rhodecode_' + each.app_settings_name] = \
213 settings['rhodecode_' + each.app_settings_name] = \
214 each.app_settings_value
214 each.app_settings_value
215
215
216 return settings
216 return settings
217
217
218 @classmethod
218 @classmethod
219 def get_ldap_settings(cls, cache=False):
219 def get_ldap_settings(cls, cache=False):
220 ret = cls.query()\
220 ret = cls.query()\
221 .filter(cls.app_settings_name.startswith('ldap_')).all()
221 .filter(cls.app_settings_name.startswith('ldap_')).all()
222 fd = {}
222 fd = {}
223 for row in ret:
223 for row in ret:
224 fd.update({row.app_settings_name: row.app_settings_value})
224 fd.update({row.app_settings_name: row.app_settings_value})
225
225
226 return fd
226 return fd
227
227
228
228
229 class RhodeCodeUi(Base, BaseModel):
229 class RhodeCodeUi(Base, BaseModel):
230 __tablename__ = 'rhodecode_ui'
230 __tablename__ = 'rhodecode_ui'
231 __table_args__ = (
231 __table_args__ = (
232 UniqueConstraint('ui_key'),
232 UniqueConstraint('ui_key'),
233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
234 'mysql_charset': 'utf8'}
234 'mysql_charset': 'utf8'}
235 )
235 )
236
236
237 HOOK_UPDATE = 'changegroup.update'
237 HOOK_UPDATE = 'changegroup.update'
238 HOOK_REPO_SIZE = 'changegroup.repo_size'
238 HOOK_REPO_SIZE = 'changegroup.repo_size'
239 HOOK_PUSH = 'changegroup.push_logger'
239 HOOK_PUSH = 'changegroup.push_logger'
240 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
240 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
241 HOOK_PULL = 'outgoing.pull_logger'
241 HOOK_PULL = 'outgoing.pull_logger'
242 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
242 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
243
243
244 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
244 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
245 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
245 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
248 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
249
249
250 @classmethod
250 @classmethod
251 def get_by_key(cls, key):
251 def get_by_key(cls, key):
252 return cls.query().filter(cls.ui_key == key).scalar()
252 return cls.query().filter(cls.ui_key == key).scalar()
253
253
254 @classmethod
254 @classmethod
255 def get_builtin_hooks(cls):
255 def get_builtin_hooks(cls):
256 q = cls.query()
256 q = cls.query()
257 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
257 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
258 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
258 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
259 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
259 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
260 return q.all()
260 return q.all()
261
261
262 @classmethod
262 @classmethod
263 def get_custom_hooks(cls):
263 def get_custom_hooks(cls):
264 q = cls.query()
264 q = cls.query()
265 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
265 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
266 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
266 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
267 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
267 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
268 q = q.filter(cls.ui_section == 'hooks')
268 q = q.filter(cls.ui_section == 'hooks')
269 return q.all()
269 return q.all()
270
270
271 @classmethod
271 @classmethod
272 def get_repos_location(cls):
272 def get_repos_location(cls):
273 return cls.get_by_key('/').ui_value
273 return cls.get_by_key('/').ui_value
274
274
275 @classmethod
275 @classmethod
276 def create_or_update_hook(cls, key, val):
276 def create_or_update_hook(cls, key, val):
277 new_ui = cls.get_by_key(key) or cls()
277 new_ui = cls.get_by_key(key) or cls()
278 new_ui.ui_section = 'hooks'
278 new_ui.ui_section = 'hooks'
279 new_ui.ui_active = True
279 new_ui.ui_active = True
280 new_ui.ui_key = key
280 new_ui.ui_key = key
281 new_ui.ui_value = val
281 new_ui.ui_value = val
282
282
283 Session().add(new_ui)
283 Session().add(new_ui)
284
284
285 def __repr__(self):
285 def __repr__(self):
286 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
286 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
287 self.ui_value)
287 self.ui_value)
288
288
289
289
290 class User(Base, BaseModel):
290 class User(Base, BaseModel):
291 __tablename__ = 'users'
291 __tablename__ = 'users'
292 __table_args__ = (
292 __table_args__ = (
293 UniqueConstraint('username'), UniqueConstraint('email'),
293 UniqueConstraint('username'), UniqueConstraint('email'),
294 Index('u_username_idx', 'username'),
294 Index('u_username_idx', 'username'),
295 Index('u_email_idx', 'email'),
295 Index('u_email_idx', 'email'),
296 {'extend_existing': True, 'mysql_engine': 'InnoDB',
296 {'extend_existing': True, 'mysql_engine': 'InnoDB',
297 'mysql_charset': 'utf8'}
297 'mysql_charset': 'utf8'}
298 )
298 )
299 DEFAULT_USER = 'default'
299 DEFAULT_USER = 'default'
300 DEFAULT_PERMISSIONS = [
300 DEFAULT_PERMISSIONS = [
301 'hg.register.manual_activate', 'hg.create.repository',
301 'hg.register.manual_activate', 'hg.create.repository',
302 'hg.fork.repository', 'repository.read', 'group.read'
302 'hg.fork.repository', 'repository.read', 'group.read'
303 ]
303 ]
304 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
304 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
305 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
305 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
307 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
308 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
308 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
309 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
309 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
310 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
310 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
311 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
311 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
312 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
312 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
313 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
313 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
314 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
314 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
315 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
315 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
316
316
317 user_log = relationship('UserLog', cascade='all')
317 user_log = relationship('UserLog', cascade='all')
318 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
318 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
319
319
320 repositories = relationship('Repository')
320 repositories = relationship('Repository')
321 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
321 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
322 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
322 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
323 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
323 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
324
324
325 group_member = relationship('UsersGroupMember', cascade='all')
325 group_member = relationship('UsersGroupMember', cascade='all')
326
326
327 notifications = relationship('UserNotification', cascade='all')
327 notifications = relationship('UserNotification', cascade='all')
328 # notifications assigned to this user
328 # notifications assigned to this user
329 user_created_notifications = relationship('Notification', cascade='all')
329 user_created_notifications = relationship('Notification', cascade='all')
330 # comments created by this user
330 # comments created by this user
331 user_comments = relationship('ChangesetComment', cascade='all')
331 user_comments = relationship('ChangesetComment', cascade='all')
332 #extra emails for this user
332 #extra emails for this user
333 user_emails = relationship('UserEmailMap', cascade='all')
333 user_emails = relationship('UserEmailMap', cascade='all')
334
334
335 @hybrid_property
335 @hybrid_property
336 def email(self):
336 def email(self):
337 return self._email
337 return self._email
338
338
339 @email.setter
339 @email.setter
340 def email(self, val):
340 def email(self, val):
341 self._email = val.lower() if val else None
341 self._email = val.lower() if val else None
342
342
343 @property
343 @property
344 def firstname(self):
344 def firstname(self):
345 # alias for future
345 # alias for future
346 return self.name
346 return self.name
347
347
348 @property
348 @property
349 def emails(self):
349 def emails(self):
350 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
350 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
351 return [self.email] + [x.email for x in other]
351 return [self.email] + [x.email for x in other]
352
352
353 @property
353 @property
354 def username_and_name(self):
354 def username_and_name(self):
355 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
355 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
356
356
357 @property
357 @property
358 def full_name(self):
358 def full_name(self):
359 return '%s %s' % (self.firstname, self.lastname)
359 return '%s %s' % (self.firstname, self.lastname)
360
360
361 @property
361 @property
362 def full_name_or_username(self):
362 def full_name_or_username(self):
363 return ('%s %s' % (self.firstname, self.lastname)
363 return ('%s %s' % (self.firstname, self.lastname)
364 if (self.firstname and self.lastname) else self.username)
364 if (self.firstname and self.lastname) else self.username)
365
365
366 @property
366 @property
367 def full_contact(self):
367 def full_contact(self):
368 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
368 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
369
369
370 @property
370 @property
371 def short_contact(self):
371 def short_contact(self):
372 return '%s %s' % (self.firstname, self.lastname)
372 return '%s %s' % (self.firstname, self.lastname)
373
373
374 @property
374 @property
375 def is_admin(self):
375 def is_admin(self):
376 return self.admin
376 return self.admin
377
377
378 def __unicode__(self):
378 def __unicode__(self):
379 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
379 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
380 self.user_id, self.username)
380 self.user_id, self.username)
381
381
382 @classmethod
382 @classmethod
383 def get_by_username(cls, username, case_insensitive=False, cache=False):
383 def get_by_username(cls, username, case_insensitive=False, cache=False):
384 if case_insensitive:
384 if case_insensitive:
385 q = cls.query().filter(cls.username.ilike(username))
385 q = cls.query().filter(cls.username.ilike(username))
386 else:
386 else:
387 q = cls.query().filter(cls.username == username)
387 q = cls.query().filter(cls.username == username)
388
388
389 if cache:
389 if cache:
390 q = q.options(FromCache(
390 q = q.options(FromCache(
391 "sql_cache_short",
391 "sql_cache_short",
392 "get_user_%s" % _hash_key(username)
392 "get_user_%s" % _hash_key(username)
393 )
393 )
394 )
394 )
395 return q.scalar()
395 return q.scalar()
396
396
397 @classmethod
397 @classmethod
398 def get_by_api_key(cls, api_key, cache=False):
398 def get_by_api_key(cls, api_key, cache=False):
399 q = cls.query().filter(cls.api_key == api_key)
399 q = cls.query().filter(cls.api_key == api_key)
400
400
401 if cache:
401 if cache:
402 q = q.options(FromCache("sql_cache_short",
402 q = q.options(FromCache("sql_cache_short",
403 "get_api_key_%s" % api_key))
403 "get_api_key_%s" % api_key))
404 return q.scalar()
404 return q.scalar()
405
405
406 @classmethod
406 @classmethod
407 def get_by_email(cls, email, case_insensitive=False, cache=False):
407 def get_by_email(cls, email, case_insensitive=False, cache=False):
408 if case_insensitive:
408 if case_insensitive:
409 q = cls.query().filter(cls.email.ilike(email))
409 q = cls.query().filter(cls.email.ilike(email))
410 else:
410 else:
411 q = cls.query().filter(cls.email == email)
411 q = cls.query().filter(cls.email == email)
412
412
413 if cache:
413 if cache:
414 q = q.options(FromCache("sql_cache_short",
414 q = q.options(FromCache("sql_cache_short",
415 "get_email_key_%s" % email))
415 "get_email_key_%s" % email))
416
416
417 ret = q.scalar()
417 ret = q.scalar()
418 if ret is None:
418 if ret is None:
419 q = UserEmailMap.query()
419 q = UserEmailMap.query()
420 # try fetching in alternate email map
420 # try fetching in alternate email map
421 if case_insensitive:
421 if case_insensitive:
422 q = q.filter(UserEmailMap.email.ilike(email))
422 q = q.filter(UserEmailMap.email.ilike(email))
423 else:
423 else:
424 q = q.filter(UserEmailMap.email == email)
424 q = q.filter(UserEmailMap.email == email)
425 q = q.options(joinedload(UserEmailMap.user))
425 q = q.options(joinedload(UserEmailMap.user))
426 if cache:
426 if cache:
427 q = q.options(FromCache("sql_cache_short",
427 q = q.options(FromCache("sql_cache_short",
428 "get_email_map_key_%s" % email))
428 "get_email_map_key_%s" % email))
429 ret = getattr(q.scalar(), 'user', None)
429 ret = getattr(q.scalar(), 'user', None)
430
430
431 return ret
431 return ret
432
432
433 def update_lastlogin(self):
433 def update_lastlogin(self):
434 """Update user lastlogin"""
434 """Update user lastlogin"""
435 self.last_login = datetime.datetime.now()
435 self.last_login = datetime.datetime.now()
436 Session().add(self)
436 Session().add(self)
437 log.debug('updated user %s lastlogin' % self.username)
437 log.debug('updated user %s lastlogin' % self.username)
438
438
439 def get_api_data(self):
439 def get_api_data(self):
440 """
440 """
441 Common function for generating user related data for API
441 Common function for generating user related data for API
442 """
442 """
443 user = self
443 user = self
444 data = dict(
444 data = dict(
445 user_id=user.user_id,
445 user_id=user.user_id,
446 username=user.username,
446 username=user.username,
447 firstname=user.name,
447 firstname=user.name,
448 lastname=user.lastname,
448 lastname=user.lastname,
449 email=user.email,
449 email=user.email,
450 emails=user.emails,
450 emails=user.emails,
451 api_key=user.api_key,
451 api_key=user.api_key,
452 active=user.active,
452 active=user.active,
453 admin=user.admin,
453 admin=user.admin,
454 ldap_dn=user.ldap_dn,
454 ldap_dn=user.ldap_dn,
455 last_login=user.last_login,
455 last_login=user.last_login,
456 )
456 )
457 return data
457 return data
458
458
459 def __json__(self):
459 def __json__(self):
460 data = dict(
460 data = dict(
461 full_name=self.full_name,
461 full_name=self.full_name,
462 full_name_or_username=self.full_name_or_username,
462 full_name_or_username=self.full_name_or_username,
463 short_contact=self.short_contact,
463 short_contact=self.short_contact,
464 full_contact=self.full_contact
464 full_contact=self.full_contact
465 )
465 )
466 data.update(self.get_api_data())
466 data.update(self.get_api_data())
467 return data
467 return data
468
468
469
469
470 class UserEmailMap(Base, BaseModel):
470 class UserEmailMap(Base, BaseModel):
471 __tablename__ = 'user_email_map'
471 __tablename__ = 'user_email_map'
472 __table_args__ = (
472 __table_args__ = (
473 Index('uem_email_idx', 'email'),
473 Index('uem_email_idx', 'email'),
474 UniqueConstraint('email'),
474 UniqueConstraint('email'),
475 {'extend_existing': True, 'mysql_engine': 'InnoDB',
475 {'extend_existing': True, 'mysql_engine': 'InnoDB',
476 'mysql_charset': 'utf8'}
476 'mysql_charset': 'utf8'}
477 )
477 )
478 __mapper_args__ = {}
478 __mapper_args__ = {}
479
479
480 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
480 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
481 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
481 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
482 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
482 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
483 user = relationship('User', lazy='joined')
483 user = relationship('User', lazy='joined')
484
484
485 @validates('_email')
485 @validates('_email')
486 def validate_email(self, key, email):
486 def validate_email(self, key, email):
487 # check if this email is not main one
487 # check if this email is not main one
488 main_email = Session().query(User).filter(User.email == email).scalar()
488 main_email = Session().query(User).filter(User.email == email).scalar()
489 if main_email is not None:
489 if main_email is not None:
490 raise AttributeError('email %s is present is user table' % email)
490 raise AttributeError('email %s is present is user table' % email)
491 return email
491 return email
492
492
493 @hybrid_property
493 @hybrid_property
494 def email(self):
494 def email(self):
495 return self._email
495 return self._email
496
496
497 @email.setter
497 @email.setter
498 def email(self, val):
498 def email(self, val):
499 self._email = val.lower() if val else None
499 self._email = val.lower() if val else None
500
500
501
501
502 class UserLog(Base, BaseModel):
502 class UserLog(Base, BaseModel):
503 __tablename__ = 'user_logs'
503 __tablename__ = 'user_logs'
504 __table_args__ = (
504 __table_args__ = (
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 'mysql_charset': 'utf8'},
506 'mysql_charset': 'utf8'},
507 )
507 )
508 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
508 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
510 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
510 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
511 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
511 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
512 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
512 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
513 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
513 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
514 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
514 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
515
515
516 @property
516 @property
517 def action_as_day(self):
517 def action_as_day(self):
518 return datetime.date(*self.action_date.timetuple()[:3])
518 return datetime.date(*self.action_date.timetuple()[:3])
519
519
520 user = relationship('User')
520 user = relationship('User')
521 repository = relationship('Repository', cascade='')
521 repository = relationship('Repository', cascade='')
522
522
523
523
524 class UsersGroup(Base, BaseModel):
524 class UsersGroup(Base, BaseModel):
525 __tablename__ = 'users_groups'
525 __tablename__ = 'users_groups'
526 __table_args__ = (
526 __table_args__ = (
527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
528 'mysql_charset': 'utf8'},
528 'mysql_charset': 'utf8'},
529 )
529 )
530
530
531 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
531 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
532 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
532 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
533 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
533 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
534 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
534 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
535
535
536 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
536 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
537 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
537 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
538 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
538 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
539
539
540 def __unicode__(self):
540 def __unicode__(self):
541 return u'<userGroup(%s)>' % (self.users_group_name)
541 return u'<userGroup(%s)>' % (self.users_group_name)
542
542
543 @classmethod
543 @classmethod
544 def get_by_group_name(cls, group_name, cache=False,
544 def get_by_group_name(cls, group_name, cache=False,
545 case_insensitive=False):
545 case_insensitive=False):
546 if case_insensitive:
546 if case_insensitive:
547 q = cls.query().filter(cls.users_group_name.ilike(group_name))
547 q = cls.query().filter(cls.users_group_name.ilike(group_name))
548 else:
548 else:
549 q = cls.query().filter(cls.users_group_name == group_name)
549 q = cls.query().filter(cls.users_group_name == group_name)
550 if cache:
550 if cache:
551 q = q.options(FromCache(
551 q = q.options(FromCache(
552 "sql_cache_short",
552 "sql_cache_short",
553 "get_user_%s" % _hash_key(group_name)
553 "get_user_%s" % _hash_key(group_name)
554 )
554 )
555 )
555 )
556 return q.scalar()
556 return q.scalar()
557
557
558 @classmethod
558 @classmethod
559 def get(cls, users_group_id, cache=False):
559 def get(cls, users_group_id, cache=False):
560 users_group = cls.query()
560 users_group = cls.query()
561 if cache:
561 if cache:
562 users_group = users_group.options(FromCache("sql_cache_short",
562 users_group = users_group.options(FromCache("sql_cache_short",
563 "get_users_group_%s" % users_group_id))
563 "get_users_group_%s" % users_group_id))
564 return users_group.get(users_group_id)
564 return users_group.get(users_group_id)
565
565
566 def get_api_data(self):
566 def get_api_data(self):
567 users_group = self
567 users_group = self
568
568
569 data = dict(
569 data = dict(
570 users_group_id=users_group.users_group_id,
570 users_group_id=users_group.users_group_id,
571 group_name=users_group.users_group_name,
571 group_name=users_group.users_group_name,
572 active=users_group.users_group_active,
572 active=users_group.users_group_active,
573 )
573 )
574
574
575 return data
575 return data
576
576
577
577
578 class UsersGroupMember(Base, BaseModel):
578 class UsersGroupMember(Base, BaseModel):
579 __tablename__ = 'users_groups_members'
579 __tablename__ = 'users_groups_members'
580 __table_args__ = (
580 __table_args__ = (
581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
582 'mysql_charset': 'utf8'},
582 'mysql_charset': 'utf8'},
583 )
583 )
584
584
585 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
585 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
586 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
586 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
588
588
589 user = relationship('User', lazy='joined')
589 user = relationship('User', lazy='joined')
590 users_group = relationship('UsersGroup')
590 users_group = relationship('UsersGroup')
591
591
592 def __init__(self, gr_id='', u_id=''):
592 def __init__(self, gr_id='', u_id=''):
593 self.users_group_id = gr_id
593 self.users_group_id = gr_id
594 self.user_id = u_id
594 self.user_id = u_id
595
595
596
596
597 class Repository(Base, BaseModel):
597 class Repository(Base, BaseModel):
598 __tablename__ = 'repositories'
598 __tablename__ = 'repositories'
599 __table_args__ = (
599 __table_args__ = (
600 UniqueConstraint('repo_name'),
600 UniqueConstraint('repo_name'),
601 Index('r_repo_name_idx', 'repo_name'),
601 Index('r_repo_name_idx', 'repo_name'),
602 {'extend_existing': True, 'mysql_engine': 'InnoDB',
602 {'extend_existing': True, 'mysql_engine': 'InnoDB',
603 'mysql_charset': 'utf8'},
603 'mysql_charset': 'utf8'},
604 )
604 )
605
605
606 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
606 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
607 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
607 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
608 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
608 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
609 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
609 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
611 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
611 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
612 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
612 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
613 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
613 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
614 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
614 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
615 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
615 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
616 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
616 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
617 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
617 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
618 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
618 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
619 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
619 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
620
620
621 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
621 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
622 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
622 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
623
623
624 user = relationship('User')
624 user = relationship('User')
625 fork = relationship('Repository', remote_side=repo_id)
625 fork = relationship('Repository', remote_side=repo_id)
626 group = relationship('RepoGroup')
626 group = relationship('RepoGroup')
627 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
627 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
628 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
628 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
629 stats = relationship('Statistics', cascade='all', uselist=False)
629 stats = relationship('Statistics', cascade='all', uselist=False)
630
630
631 followers = relationship('UserFollowing',
631 followers = relationship('UserFollowing',
632 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
632 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
633 cascade='all')
633 cascade='all')
634
634
635 logs = relationship('UserLog')
635 logs = relationship('UserLog')
636 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
636 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
637
637
638 pull_requests_org = relationship('PullRequest',
638 pull_requests_org = relationship('PullRequest',
639 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
639 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
640 cascade="all, delete, delete-orphan")
640 cascade="all, delete, delete-orphan")
641
641
642 pull_requests_other = relationship('PullRequest',
642 pull_requests_other = relationship('PullRequest',
643 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
643 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
644 cascade="all, delete, delete-orphan")
644 cascade="all, delete, delete-orphan")
645
645
646 def __unicode__(self):
646 def __unicode__(self):
647 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
647 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
648 self.repo_name)
648 self.repo_name)
649
649
650 @hybrid_property
650 @hybrid_property
651 def locked(self):
651 def locked(self):
652 # always should return [user_id, timelocked]
652 # always should return [user_id, timelocked]
653 if self._locked:
653 if self._locked:
654 _lock_info = self._locked.split(':')
654 _lock_info = self._locked.split(':')
655 return int(_lock_info[0]), _lock_info[1]
655 return int(_lock_info[0]), _lock_info[1]
656 return [None, None]
656 return [None, None]
657
657
658 @locked.setter
658 @locked.setter
659 def locked(self, val):
659 def locked(self, val):
660 if val and isinstance(val, (list, tuple)):
660 if val and isinstance(val, (list, tuple)):
661 self._locked = ':'.join(map(str, val))
661 self._locked = ':'.join(map(str, val))
662 else:
662 else:
663 self._locked = None
663 self._locked = None
664
664
665 @classmethod
665 @classmethod
666 def url_sep(cls):
666 def url_sep(cls):
667 return URL_SEP
667 return URL_SEP
668
668
669 @classmethod
669 @classmethod
670 def get_by_repo_name(cls, repo_name):
670 def get_by_repo_name(cls, repo_name):
671 q = Session().query(cls).filter(cls.repo_name == repo_name)
671 q = Session().query(cls).filter(cls.repo_name == repo_name)
672 q = q.options(joinedload(Repository.fork))\
672 q = q.options(joinedload(Repository.fork))\
673 .options(joinedload(Repository.user))\
673 .options(joinedload(Repository.user))\
674 .options(joinedload(Repository.group))
674 .options(joinedload(Repository.group))
675 return q.scalar()
675 return q.scalar()
676
676
677 @classmethod
677 @classmethod
678 def get_by_full_path(cls, repo_full_path):
678 def get_by_full_path(cls, repo_full_path):
679 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
679 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
680 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
680 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
681
681
682 @classmethod
682 @classmethod
683 def get_repo_forks(cls, repo_id):
683 def get_repo_forks(cls, repo_id):
684 return cls.query().filter(Repository.fork_id == repo_id)
684 return cls.query().filter(Repository.fork_id == repo_id)
685
685
686 @classmethod
686 @classmethod
687 def base_path(cls):
687 def base_path(cls):
688 """
688 """
689 Returns base path when all repos are stored
689 Returns base path when all repos are stored
690
690
691 :param cls:
691 :param cls:
692 """
692 """
693 q = Session().query(RhodeCodeUi)\
693 q = Session().query(RhodeCodeUi)\
694 .filter(RhodeCodeUi.ui_key == cls.url_sep())
694 .filter(RhodeCodeUi.ui_key == cls.url_sep())
695 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
695 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
696 return q.one().ui_value
696 return q.one().ui_value
697
697
698 @property
698 @property
699 def forks(self):
699 def forks(self):
700 """
700 """
701 Return forks of this repo
701 Return forks of this repo
702 """
702 """
703 return Repository.get_repo_forks(self.repo_id)
703 return Repository.get_repo_forks(self.repo_id)
704
704
705 @property
705 @property
706 def parent(self):
706 def parent(self):
707 """
707 """
708 Returns fork parent
708 Returns fork parent
709 """
709 """
710 return self.fork
710 return self.fork
711
711
712 @property
712 @property
713 def just_name(self):
713 def just_name(self):
714 return self.repo_name.split(Repository.url_sep())[-1]
714 return self.repo_name.split(Repository.url_sep())[-1]
715
715
716 @property
716 @property
717 def groups_with_parents(self):
717 def groups_with_parents(self):
718 groups = []
718 groups = []
719 if self.group is None:
719 if self.group is None:
720 return groups
720 return groups
721
721
722 cur_gr = self.group
722 cur_gr = self.group
723 groups.insert(0, cur_gr)
723 groups.insert(0, cur_gr)
724 while 1:
724 while 1:
725 gr = getattr(cur_gr, 'parent_group', None)
725 gr = getattr(cur_gr, 'parent_group', None)
726 cur_gr = cur_gr.parent_group
726 cur_gr = cur_gr.parent_group
727 if gr is None:
727 if gr is None:
728 break
728 break
729 groups.insert(0, gr)
729 groups.insert(0, gr)
730
730
731 return groups
731 return groups
732
732
733 @property
733 @property
734 def groups_and_repo(self):
734 def groups_and_repo(self):
735 return self.groups_with_parents, self.just_name
735 return self.groups_with_parents, self.just_name
736
736
737 @LazyProperty
737 @LazyProperty
738 def repo_path(self):
738 def repo_path(self):
739 """
739 """
740 Returns base full path for that repository means where it actually
740 Returns base full path for that repository means where it actually
741 exists on a filesystem
741 exists on a filesystem
742 """
742 """
743 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
743 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
744 Repository.url_sep())
744 Repository.url_sep())
745 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
745 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
746 return q.one().ui_value
746 return q.one().ui_value
747
747
748 @property
748 @property
749 def repo_full_path(self):
749 def repo_full_path(self):
750 p = [self.repo_path]
750 p = [self.repo_path]
751 # we need to split the name by / since this is how we store the
751 # we need to split the name by / since this is how we store the
752 # names in the database, but that eventually needs to be converted
752 # names in the database, but that eventually needs to be converted
753 # into a valid system path
753 # into a valid system path
754 p += self.repo_name.split(Repository.url_sep())
754 p += self.repo_name.split(Repository.url_sep())
755 return os.path.join(*p)
755 return os.path.join(*p)
756
756
757 @property
757 @property
758 def cache_keys(self):
758 def cache_keys(self):
759 """
759 """
760 Returns associated cache keys for that repo
760 Returns associated cache keys for that repo
761 """
761 """
762 return CacheInvalidation.query()\
762 return CacheInvalidation.query()\
763 .filter(CacheInvalidation.cache_args == self.repo_name)\
763 .filter(CacheInvalidation.cache_args == self.repo_name)\
764 .order_by(CacheInvalidation.cache_key)\
764 .order_by(CacheInvalidation.cache_key)\
765 .all()
765 .all()
766
766
767 def get_new_name(self, repo_name):
767 def get_new_name(self, repo_name):
768 """
768 """
769 returns new full repository name based on assigned group and new new
769 returns new full repository name based on assigned group and new new
770
770
771 :param group_name:
771 :param group_name:
772 """
772 """
773 path_prefix = self.group.full_path_splitted if self.group else []
773 path_prefix = self.group.full_path_splitted if self.group else []
774 return Repository.url_sep().join(path_prefix + [repo_name])
774 return Repository.url_sep().join(path_prefix + [repo_name])
775
775
776 @property
776 @property
777 def _ui(self):
777 def _ui(self):
778 """
778 """
779 Creates an db based ui object for this repository
779 Creates an db based ui object for this repository
780 """
780 """
781 from rhodecode.lib.utils import make_ui
781 from rhodecode.lib.utils import make_ui
782 return make_ui('db', clear_session=False)
782 return make_ui('db', clear_session=False)
783
783
784 @classmethod
784 @classmethod
785 def inject_ui(cls, repo, extras={}):
785 def inject_ui(cls, repo, extras={}):
786 from rhodecode.lib.vcs.backends.hg import MercurialRepository
786 from rhodecode.lib.vcs.backends.hg import MercurialRepository
787 from rhodecode.lib.vcs.backends.git import GitRepository
787 from rhodecode.lib.vcs.backends.git import GitRepository
788 required = (MercurialRepository, GitRepository)
788 required = (MercurialRepository, GitRepository)
789 if not isinstance(repo, required):
789 if not isinstance(repo, required):
790 raise Exception('repo must be instance of %s' % required)
790 raise Exception('repo must be instance of %s' % required)
791
791
792 # inject ui extra param to log this action via push logger
792 # inject ui extra param to log this action via push logger
793 for k, v in extras.items():
793 for k, v in extras.items():
794 repo._repo.ui.setconfig('rhodecode_extras', k, v)
794 repo._repo.ui.setconfig('rhodecode_extras', k, v)
795
795
796 @classmethod
796 @classmethod
797 def is_valid(cls, repo_name):
797 def is_valid(cls, repo_name):
798 """
798 """
799 returns True if given repo name is a valid filesystem repository
799 returns True if given repo name is a valid filesystem repository
800
800
801 :param cls:
801 :param cls:
802 :param repo_name:
802 :param repo_name:
803 """
803 """
804 from rhodecode.lib.utils import is_valid_repo
804 from rhodecode.lib.utils import is_valid_repo
805
805
806 return is_valid_repo(repo_name, cls.base_path())
806 return is_valid_repo(repo_name, cls.base_path())
807
807
808 def get_api_data(self):
808 def get_api_data(self):
809 """
809 """
810 Common function for generating repo api data
810 Common function for generating repo api data
811
811
812 """
812 """
813 repo = self
813 repo = self
814 data = dict(
814 data = dict(
815 repo_id=repo.repo_id,
815 repo_id=repo.repo_id,
816 repo_name=repo.repo_name,
816 repo_name=repo.repo_name,
817 repo_type=repo.repo_type,
817 repo_type=repo.repo_type,
818 clone_uri=repo.clone_uri,
818 clone_uri=repo.clone_uri,
819 private=repo.private,
819 private=repo.private,
820 created_on=repo.created_on,
820 created_on=repo.created_on,
821 description=repo.description,
821 description=repo.description,
822 landing_rev=repo.landing_rev,
822 landing_rev=repo.landing_rev,
823 owner=repo.user.username,
823 owner=repo.user.username,
824 fork_of=repo.fork.repo_name if repo.fork else None
824 fork_of=repo.fork.repo_name if repo.fork else None
825 )
825 )
826
826
827 return data
827 return data
828
828
829 @classmethod
829 @classmethod
830 def lock(cls, repo, user_id):
830 def lock(cls, repo, user_id):
831 repo.locked = [user_id, time.time()]
831 repo.locked = [user_id, time.time()]
832 Session().add(repo)
832 Session().add(repo)
833 Session().commit()
833 Session().commit()
834
834
835 @classmethod
835 @classmethod
836 def unlock(cls, repo):
836 def unlock(cls, repo):
837 repo.locked = None
837 repo.locked = None
838 Session().add(repo)
838 Session().add(repo)
839 Session().commit()
839 Session().commit()
840
840
841 @property
841 @property
842 def last_db_change(self):
842 def last_db_change(self):
843 return self.updated_on
843 return self.updated_on
844
844
845 #==========================================================================
845 #==========================================================================
846 # SCM PROPERTIES
846 # SCM PROPERTIES
847 #==========================================================================
847 #==========================================================================
848
848
849 def get_changeset(self, rev=None):
849 def get_changeset(self, rev=None):
850 return get_changeset_safe(self.scm_instance, rev)
850 return get_changeset_safe(self.scm_instance, rev)
851
851
852 def get_landing_changeset(self):
852 def get_landing_changeset(self):
853 """
853 """
854 Returns landing changeset, or if that doesn't exist returns the tip
854 Returns landing changeset, or if that doesn't exist returns the tip
855 """
855 """
856 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
856 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
857 return cs
857 return cs
858
858
859 def update_last_change(self, last_change=None):
859 def update_last_change(self, last_change=None):
860 if last_change is None:
860 if last_change is None:
861 last_change = datetime.datetime.now()
861 last_change = datetime.datetime.now()
862 if self.updated_on is None or self.updated_on != last_change:
862 if self.updated_on is None or self.updated_on != last_change:
863 log.debug('updated repo %s with new date %s' % (self, last_change))
863 log.debug('updated repo %s with new date %s' % (self, last_change))
864 self.updated_on = last_change
864 self.updated_on = last_change
865 Session().add(self)
865 Session().add(self)
866 Session().commit()
866 Session().commit()
867
867
868 @property
868 @property
869 def tip(self):
869 def tip(self):
870 return self.get_changeset('tip')
870 return self.get_changeset('tip')
871
871
872 @property
872 @property
873 def author(self):
873 def author(self):
874 return self.tip.author
874 return self.tip.author
875
875
876 @property
876 @property
877 def last_change(self):
877 def last_change(self):
878 return self.scm_instance.last_change
878 return self.scm_instance.last_change
879
879
880 def get_comments(self, revisions=None):
880 def get_comments(self, revisions=None):
881 """
881 """
882 Returns comments for this repository grouped by revisions
882 Returns comments for this repository grouped by revisions
883
883
884 :param revisions: filter query by revisions only
884 :param revisions: filter query by revisions only
885 """
885 """
886 cmts = ChangesetComment.query()\
886 cmts = ChangesetComment.query()\
887 .filter(ChangesetComment.repo == self)
887 .filter(ChangesetComment.repo == self)
888 if revisions:
888 if revisions:
889 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
889 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
890 grouped = defaultdict(list)
890 grouped = defaultdict(list)
891 for cmt in cmts.all():
891 for cmt in cmts.all():
892 grouped[cmt.revision].append(cmt)
892 grouped[cmt.revision].append(cmt)
893 return grouped
893 return grouped
894
894
895 def statuses(self, revisions=None):
895 def statuses(self, revisions=None):
896 """
896 """
897 Returns statuses for this repository
897 Returns statuses for this repository
898
898
899 :param revisions: list of revisions to get statuses for
899 :param revisions: list of revisions to get statuses for
900 :type revisions: list
900 :type revisions: list
901 """
901 """
902
902
903 statuses = ChangesetStatus.query()\
903 statuses = ChangesetStatus.query()\
904 .filter(ChangesetStatus.repo == self)\
904 .filter(ChangesetStatus.repo == self)\
905 .filter(ChangesetStatus.version == 0)
905 .filter(ChangesetStatus.version == 0)
906 if revisions:
906 if revisions:
907 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
907 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
908 grouped = {}
908 grouped = {}
909
909
910 #maybe we have open new pullrequest without a status ?
910 #maybe we have open new pullrequest without a status ?
911 stat = ChangesetStatus.STATUS_UNDER_REVIEW
911 stat = ChangesetStatus.STATUS_UNDER_REVIEW
912 status_lbl = ChangesetStatus.get_status_lbl(stat)
912 status_lbl = ChangesetStatus.get_status_lbl(stat)
913 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
913 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
914 for rev in pr.revisions:
914 for rev in pr.revisions:
915 pr_id = pr.pull_request_id
915 pr_id = pr.pull_request_id
916 pr_repo = pr.other_repo.repo_name
916 pr_repo = pr.other_repo.repo_name
917 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
917 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
918
918
919 for stat in statuses.all():
919 for stat in statuses.all():
920 pr_id = pr_repo = None
920 pr_id = pr_repo = None
921 if stat.pull_request:
921 if stat.pull_request:
922 pr_id = stat.pull_request.pull_request_id
922 pr_id = stat.pull_request.pull_request_id
923 pr_repo = stat.pull_request.other_repo.repo_name
923 pr_repo = stat.pull_request.other_repo.repo_name
924 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
924 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
925 pr_id, pr_repo]
925 pr_id, pr_repo]
926 return grouped
926 return grouped
927
927
928 #==========================================================================
928 #==========================================================================
929 # SCM CACHE INSTANCE
929 # SCM CACHE INSTANCE
930 #==========================================================================
930 #==========================================================================
931
931
932 @property
932 @property
933 def invalidate(self):
933 def invalidate(self):
934 return CacheInvalidation.invalidate(self.repo_name)
934 return CacheInvalidation.invalidate(self.repo_name)
935
935
936 def set_invalidate(self):
936 def set_invalidate(self):
937 """
937 """
938 set a cache for invalidation for this instance
938 set a cache for invalidation for this instance
939 """
939 """
940 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
940 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
941
941
942 @LazyProperty
942 @LazyProperty
943 def scm_instance(self):
943 def scm_instance(self):
944 import rhodecode
944 import rhodecode
945 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
945 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
946 if full_cache:
946 if full_cache:
947 return self.scm_instance_cached()
947 return self.scm_instance_cached()
948 return self.__get_instance()
948 return self.__get_instance()
949
949
950 def scm_instance_cached(self, cache_map=None):
950 def scm_instance_cached(self, cache_map=None):
951 @cache_region('long_term')
951 @cache_region('long_term')
952 def _c(repo_name):
952 def _c(repo_name):
953 return self.__get_instance()
953 return self.__get_instance()
954 rn = self.repo_name
954 rn = self.repo_name
955 log.debug('Getting cached instance of repo')
955 log.debug('Getting cached instance of repo')
956
956
957 if cache_map:
957 if cache_map:
958 # get using prefilled cache_map
958 # get using prefilled cache_map
959 invalidate_repo = cache_map[self.repo_name]
959 invalidate_repo = cache_map[self.repo_name]
960 if invalidate_repo:
960 if invalidate_repo:
961 invalidate_repo = (None if invalidate_repo.cache_active
961 invalidate_repo = (None if invalidate_repo.cache_active
962 else invalidate_repo)
962 else invalidate_repo)
963 else:
963 else:
964 # get from invalidate
964 # get from invalidate
965 invalidate_repo = self.invalidate
965 invalidate_repo = self.invalidate
966
966
967 if invalidate_repo is not None:
967 if invalidate_repo is not None:
968 region_invalidate(_c, None, rn)
968 region_invalidate(_c, None, rn)
969 # update our cache
969 # update our cache
970 CacheInvalidation.set_valid(invalidate_repo.cache_key)
970 CacheInvalidation.set_valid(invalidate_repo.cache_key)
971 return _c(rn)
971 return _c(rn)
972
972
973 def __get_instance(self):
973 def __get_instance(self):
974 repo_full_path = self.repo_full_path
974 repo_full_path = self.repo_full_path
975 try:
975 try:
976 alias = get_scm(repo_full_path)[0]
976 alias = get_scm(repo_full_path)[0]
977 log.debug('Creating instance of %s repository' % alias)
977 log.debug('Creating instance of %s repository' % alias)
978 backend = get_backend(alias)
978 backend = get_backend(alias)
979 except VCSError:
979 except VCSError:
980 log.error(traceback.format_exc())
980 log.error(traceback.format_exc())
981 log.error('Perhaps this repository is in db and not in '
981 log.error('Perhaps this repository is in db and not in '
982 'filesystem run rescan repositories with '
982 'filesystem run rescan repositories with '
983 '"destroy old data " option from admin panel')
983 '"destroy old data " option from admin panel')
984 return
984 return
985
985
986 if alias == 'hg':
986 if alias == 'hg':
987
987
988 repo = backend(safe_str(repo_full_path), create=False,
988 repo = backend(safe_str(repo_full_path), create=False,
989 baseui=self._ui)
989 baseui=self._ui)
990 # skip hidden web repository
990 # skip hidden web repository
991 if repo._get_hidden():
991 if repo._get_hidden():
992 return
992 return
993 else:
993 else:
994 repo = backend(repo_full_path, create=False)
994 repo = backend(repo_full_path, create=False)
995
995
996 return repo
996 return repo
997
997
998
998
999 class RepoGroup(Base, BaseModel):
999 class RepoGroup(Base, BaseModel):
1000 __tablename__ = 'groups'
1000 __tablename__ = 'groups'
1001 __table_args__ = (
1001 __table_args__ = (
1002 UniqueConstraint('group_name', 'group_parent_id'),
1002 UniqueConstraint('group_name', 'group_parent_id'),
1003 CheckConstraint('group_id != group_parent_id'),
1003 CheckConstraint('group_id != group_parent_id'),
1004 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1004 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1005 'mysql_charset': 'utf8'},
1005 'mysql_charset': 'utf8'},
1006 )
1006 )
1007 __mapper_args__ = {'order_by': 'group_name'}
1007 __mapper_args__ = {'order_by': 'group_name'}
1008
1008
1009 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1009 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1010 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1010 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1011 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1011 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1012 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1012 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1013 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1013 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1014
1014
1015 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1015 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1016 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1016 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1017
1017
1018 parent_group = relationship('RepoGroup', remote_side=group_id)
1018 parent_group = relationship('RepoGroup', remote_side=group_id)
1019
1019
1020 def __init__(self, group_name='', parent_group=None):
1020 def __init__(self, group_name='', parent_group=None):
1021 self.group_name = group_name
1021 self.group_name = group_name
1022 self.parent_group = parent_group
1022 self.parent_group = parent_group
1023
1023
1024 def __unicode__(self):
1024 def __unicode__(self):
1025 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1025 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1026 self.group_name)
1026 self.group_name)
1027
1027
1028 @classmethod
1028 @classmethod
1029 def groups_choices(cls, check_perms=False):
1029 def groups_choices(cls, check_perms=False):
1030 from webhelpers.html import literal as _literal
1030 from webhelpers.html import literal as _literal
1031 from rhodecode.model.scm import ScmModel
1031 from rhodecode.model.scm import ScmModel
1032 groups = cls.query().all()
1032 groups = cls.query().all()
1033 if check_perms:
1033 if check_perms:
1034 #filter group user have access to, it's done
1034 #filter group user have access to, it's done
1035 #magically inside ScmModel based on current user
1035 #magically inside ScmModel based on current user
1036 groups = ScmModel().get_repos_groups(groups)
1036 groups = ScmModel().get_repos_groups(groups)
1037 repo_groups = [('', '')]
1037 repo_groups = [('', '')]
1038 sep = ' &raquo; '
1038 sep = ' &raquo; '
1039 _name = lambda k: _literal(sep.join(k))
1039 _name = lambda k: _literal(sep.join(k))
1040
1040
1041 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1041 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1042 for x in groups])
1042 for x in groups])
1043
1043
1044 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1044 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1045 return repo_groups
1045 return repo_groups
1046
1046
1047 @classmethod
1047 @classmethod
1048 def url_sep(cls):
1048 def url_sep(cls):
1049 return URL_SEP
1049 return URL_SEP
1050
1050
1051 @classmethod
1051 @classmethod
1052 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1052 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1053 if case_insensitive:
1053 if case_insensitive:
1054 gr = cls.query()\
1054 gr = cls.query()\
1055 .filter(cls.group_name.ilike(group_name))
1055 .filter(cls.group_name.ilike(group_name))
1056 else:
1056 else:
1057 gr = cls.query()\
1057 gr = cls.query()\
1058 .filter(cls.group_name == group_name)
1058 .filter(cls.group_name == group_name)
1059 if cache:
1059 if cache:
1060 gr = gr.options(FromCache(
1060 gr = gr.options(FromCache(
1061 "sql_cache_short",
1061 "sql_cache_short",
1062 "get_group_%s" % _hash_key(group_name)
1062 "get_group_%s" % _hash_key(group_name)
1063 )
1063 )
1064 )
1064 )
1065 return gr.scalar()
1065 return gr.scalar()
1066
1066
1067 @property
1067 @property
1068 def parents(self):
1068 def parents(self):
1069 parents_recursion_limit = 5
1069 parents_recursion_limit = 5
1070 groups = []
1070 groups = []
1071 if self.parent_group is None:
1071 if self.parent_group is None:
1072 return groups
1072 return groups
1073 cur_gr = self.parent_group
1073 cur_gr = self.parent_group
1074 groups.insert(0, cur_gr)
1074 groups.insert(0, cur_gr)
1075 cnt = 0
1075 cnt = 0
1076 while 1:
1076 while 1:
1077 cnt += 1
1077 cnt += 1
1078 gr = getattr(cur_gr, 'parent_group', None)
1078 gr = getattr(cur_gr, 'parent_group', None)
1079 cur_gr = cur_gr.parent_group
1079 cur_gr = cur_gr.parent_group
1080 if gr is None:
1080 if gr is None:
1081 break
1081 break
1082 if cnt == parents_recursion_limit:
1082 if cnt == parents_recursion_limit:
1083 # this will prevent accidental infinit loops
1083 # this will prevent accidental infinit loops
1084 log.error('group nested more than %s' %
1084 log.error('group nested more than %s' %
1085 parents_recursion_limit)
1085 parents_recursion_limit)
1086 break
1086 break
1087
1087
1088 groups.insert(0, gr)
1088 groups.insert(0, gr)
1089 return groups
1089 return groups
1090
1090
1091 @property
1091 @property
1092 def children(self):
1092 def children(self):
1093 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1093 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1094
1094
1095 @property
1095 @property
1096 def name(self):
1096 def name(self):
1097 return self.group_name.split(RepoGroup.url_sep())[-1]
1097 return self.group_name.split(RepoGroup.url_sep())[-1]
1098
1098
1099 @property
1099 @property
1100 def full_path(self):
1100 def full_path(self):
1101 return self.group_name
1101 return self.group_name
1102
1102
1103 @property
1103 @property
1104 def full_path_splitted(self):
1104 def full_path_splitted(self):
1105 return self.group_name.split(RepoGroup.url_sep())
1105 return self.group_name.split(RepoGroup.url_sep())
1106
1106
1107 @property
1107 @property
1108 def repositories(self):
1108 def repositories(self):
1109 return Repository.query()\
1109 return Repository.query()\
1110 .filter(Repository.group == self)\
1110 .filter(Repository.group == self)\
1111 .order_by(Repository.repo_name)
1111 .order_by(Repository.repo_name)
1112
1112
1113 @property
1113 @property
1114 def repositories_recursive_count(self):
1114 def repositories_recursive_count(self):
1115 cnt = self.repositories.count()
1115 cnt = self.repositories.count()
1116
1116
1117 def children_count(group):
1117 def children_count(group):
1118 cnt = 0
1118 cnt = 0
1119 for child in group.children:
1119 for child in group.children:
1120 cnt += child.repositories.count()
1120 cnt += child.repositories.count()
1121 cnt += children_count(child)
1121 cnt += children_count(child)
1122 return cnt
1122 return cnt
1123
1123
1124 return cnt + children_count(self)
1124 return cnt + children_count(self)
1125
1125
1126 def recursive_groups_and_repos(self):
1126 def recursive_groups_and_repos(self):
1127 """
1127 """
1128 Recursive return all groups, with repositories in those groups
1128 Recursive return all groups, with repositories in those groups
1129 """
1129 """
1130 all_ = []
1130 all_ = []
1131
1131
1132 def _get_members(root_gr):
1132 def _get_members(root_gr):
1133 for r in root_gr.repositories:
1133 for r in root_gr.repositories:
1134 all_.append(r)
1134 all_.append(r)
1135 childs = root_gr.children.all()
1135 childs = root_gr.children.all()
1136 if childs:
1136 if childs:
1137 for gr in childs:
1137 for gr in childs:
1138 all_.append(gr)
1138 all_.append(gr)
1139 _get_members(gr)
1139 _get_members(gr)
1140
1140
1141 _get_members(self)
1141 _get_members(self)
1142 return [self] + all_
1142 return [self] + all_
1143
1143
1144 def get_new_name(self, group_name):
1144 def get_new_name(self, group_name):
1145 """
1145 """
1146 returns new full group name based on parent and new name
1146 returns new full group name based on parent and new name
1147
1147
1148 :param group_name:
1148 :param group_name:
1149 """
1149 """
1150 path_prefix = (self.parent_group.full_path_splitted if
1150 path_prefix = (self.parent_group.full_path_splitted if
1151 self.parent_group else [])
1151 self.parent_group else [])
1152 return RepoGroup.url_sep().join(path_prefix + [group_name])
1152 return RepoGroup.url_sep().join(path_prefix + [group_name])
1153
1153
1154
1154
1155 class Permission(Base, BaseModel):
1155 class Permission(Base, BaseModel):
1156 __tablename__ = 'permissions'
1156 __tablename__ = 'permissions'
1157 __table_args__ = (
1157 __table_args__ = (
1158 Index('p_perm_name_idx', 'permission_name'),
1158 Index('p_perm_name_idx', 'permission_name'),
1159 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1159 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1160 'mysql_charset': 'utf8'},
1160 'mysql_charset': 'utf8'},
1161 )
1161 )
1162 PERMS = [
1162 PERMS = [
1163 ('repository.none', _('Repository no access')),
1163 ('repository.none', _('Repository no access')),
1164 ('repository.read', _('Repository read access')),
1164 ('repository.read', _('Repository read access')),
1165 ('repository.write', _('Repository write access')),
1165 ('repository.write', _('Repository write access')),
1166 ('repository.admin', _('Repository admin access')),
1166 ('repository.admin', _('Repository admin access')),
1167
1167
1168 ('group.none', _('Repositories Group no access')),
1168 ('group.none', _('Repositories Group no access')),
1169 ('group.read', _('Repositories Group read access')),
1169 ('group.read', _('Repositories Group read access')),
1170 ('group.write', _('Repositories Group write access')),
1170 ('group.write', _('Repositories Group write access')),
1171 ('group.admin', _('Repositories Group admin access')),
1171 ('group.admin', _('Repositories Group admin access')),
1172
1172
1173 ('hg.admin', _('RhodeCode Administrator')),
1173 ('hg.admin', _('RhodeCode Administrator')),
1174 ('hg.create.none', _('Repository creation disabled')),
1174 ('hg.create.none', _('Repository creation disabled')),
1175 ('hg.create.repository', _('Repository creation enabled')),
1175 ('hg.create.repository', _('Repository creation enabled')),
1176 ('hg.fork.none', _('Repository forking disabled')),
1176 ('hg.fork.none', _('Repository forking disabled')),
1177 ('hg.fork.repository', _('Repository forking enabled')),
1177 ('hg.fork.repository', _('Repository forking enabled')),
1178 ('hg.register.none', _('Register disabled')),
1178 ('hg.register.none', _('Register disabled')),
1179 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1179 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1180 'with manual activation')),
1180 'with manual activation')),
1181
1181
1182 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1182 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1183 'with auto activation')),
1183 'with auto activation')),
1184 ]
1184 ]
1185
1185
1186 # defines which permissions are more important higher the more important
1186 # defines which permissions are more important higher the more important
1187 PERM_WEIGHTS = {
1187 PERM_WEIGHTS = {
1188 'repository.none': 0,
1188 'repository.none': 0,
1189 'repository.read': 1,
1189 'repository.read': 1,
1190 'repository.write': 3,
1190 'repository.write': 3,
1191 'repository.admin': 4,
1191 'repository.admin': 4,
1192
1192
1193 'group.none': 0,
1193 'group.none': 0,
1194 'group.read': 1,
1194 'group.read': 1,
1195 'group.write': 3,
1195 'group.write': 3,
1196 'group.admin': 4,
1196 'group.admin': 4,
1197
1197
1198 'hg.fork.none': 0,
1198 'hg.fork.none': 0,
1199 'hg.fork.repository': 1,
1199 'hg.fork.repository': 1,
1200 'hg.create.none': 0,
1200 'hg.create.none': 0,
1201 'hg.create.repository':1
1201 'hg.create.repository':1
1202 }
1202 }
1203
1203
1204 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1204 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1205 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1205 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1206 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1206 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1207
1207
1208 def __unicode__(self):
1208 def __unicode__(self):
1209 return u"<%s('%s:%s')>" % (
1209 return u"<%s('%s:%s')>" % (
1210 self.__class__.__name__, self.permission_id, self.permission_name
1210 self.__class__.__name__, self.permission_id, self.permission_name
1211 )
1211 )
1212
1212
1213 @classmethod
1213 @classmethod
1214 def get_by_key(cls, key):
1214 def get_by_key(cls, key):
1215 return cls.query().filter(cls.permission_name == key).scalar()
1215 return cls.query().filter(cls.permission_name == key).scalar()
1216
1216
1217 @classmethod
1217 @classmethod
1218 def get_default_perms(cls, default_user_id):
1218 def get_default_perms(cls, default_user_id):
1219 q = Session().query(UserRepoToPerm, Repository, cls)\
1219 q = Session().query(UserRepoToPerm, Repository, cls)\
1220 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1220 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1221 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1221 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1222 .filter(UserRepoToPerm.user_id == default_user_id)
1222 .filter(UserRepoToPerm.user_id == default_user_id)
1223
1223
1224 return q.all()
1224 return q.all()
1225
1225
1226 @classmethod
1226 @classmethod
1227 def get_default_group_perms(cls, default_user_id):
1227 def get_default_group_perms(cls, default_user_id):
1228 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1228 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1229 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1229 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1230 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1230 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1231 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1231 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1232
1232
1233 return q.all()
1233 return q.all()
1234
1234
1235
1235
1236 class UserRepoToPerm(Base, BaseModel):
1236 class UserRepoToPerm(Base, BaseModel):
1237 __tablename__ = 'repo_to_perm'
1237 __tablename__ = 'repo_to_perm'
1238 __table_args__ = (
1238 __table_args__ = (
1239 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1239 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1240 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1240 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1241 'mysql_charset': 'utf8'}
1241 'mysql_charset': 'utf8'}
1242 )
1242 )
1243 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1243 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1244 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1244 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1245 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1245 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1246 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1246 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1247
1247
1248 user = relationship('User')
1248 user = relationship('User')
1249 repository = relationship('Repository')
1249 repository = relationship('Repository')
1250 permission = relationship('Permission')
1250 permission = relationship('Permission')
1251
1251
1252 @classmethod
1252 @classmethod
1253 def create(cls, user, repository, permission):
1253 def create(cls, user, repository, permission):
1254 n = cls()
1254 n = cls()
1255 n.user = user
1255 n.user = user
1256 n.repository = repository
1256 n.repository = repository
1257 n.permission = permission
1257 n.permission = permission
1258 Session().add(n)
1258 Session().add(n)
1259 return n
1259 return n
1260
1260
1261 def __unicode__(self):
1261 def __unicode__(self):
1262 return u'<user:%s => %s >' % (self.user, self.repository)
1262 return u'<user:%s => %s >' % (self.user, self.repository)
1263
1263
1264
1264
1265 class UserToPerm(Base, BaseModel):
1265 class UserToPerm(Base, BaseModel):
1266 __tablename__ = 'user_to_perm'
1266 __tablename__ = 'user_to_perm'
1267 __table_args__ = (
1267 __table_args__ = (
1268 UniqueConstraint('user_id', 'permission_id'),
1268 UniqueConstraint('user_id', 'permission_id'),
1269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1270 'mysql_charset': 'utf8'}
1270 'mysql_charset': 'utf8'}
1271 )
1271 )
1272 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1272 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1273 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1273 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1274 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1274 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1275
1275
1276 user = relationship('User')
1276 user = relationship('User')
1277 permission = relationship('Permission', lazy='joined')
1277 permission = relationship('Permission', lazy='joined')
1278
1278
1279
1279
1280 class UsersGroupRepoToPerm(Base, BaseModel):
1280 class UsersGroupRepoToPerm(Base, BaseModel):
1281 __tablename__ = 'users_group_repo_to_perm'
1281 __tablename__ = 'users_group_repo_to_perm'
1282 __table_args__ = (
1282 __table_args__ = (
1283 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1283 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1284 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1284 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1285 'mysql_charset': 'utf8'}
1285 'mysql_charset': 'utf8'}
1286 )
1286 )
1287 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1287 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1288 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1288 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1289 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1289 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1290 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1290 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1291
1291
1292 users_group = relationship('UsersGroup')
1292 users_group = relationship('UsersGroup')
1293 permission = relationship('Permission')
1293 permission = relationship('Permission')
1294 repository = relationship('Repository')
1294 repository = relationship('Repository')
1295
1295
1296 @classmethod
1296 @classmethod
1297 def create(cls, users_group, repository, permission):
1297 def create(cls, users_group, repository, permission):
1298 n = cls()
1298 n = cls()
1299 n.users_group = users_group
1299 n.users_group = users_group
1300 n.repository = repository
1300 n.repository = repository
1301 n.permission = permission
1301 n.permission = permission
1302 Session().add(n)
1302 Session().add(n)
1303 return n
1303 return n
1304
1304
1305 def __unicode__(self):
1305 def __unicode__(self):
1306 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1306 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1307
1307
1308
1308
1309 class UsersGroupToPerm(Base, BaseModel):
1309 class UsersGroupToPerm(Base, BaseModel):
1310 __tablename__ = 'users_group_to_perm'
1310 __tablename__ = 'users_group_to_perm'
1311 __table_args__ = (
1311 __table_args__ = (
1312 UniqueConstraint('users_group_id', 'permission_id',),
1312 UniqueConstraint('users_group_id', 'permission_id',),
1313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1314 'mysql_charset': 'utf8'}
1314 'mysql_charset': 'utf8'}
1315 )
1315 )
1316 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1316 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1317 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1317 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1318 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1318 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1319
1319
1320 users_group = relationship('UsersGroup')
1320 users_group = relationship('UsersGroup')
1321 permission = relationship('Permission')
1321 permission = relationship('Permission')
1322
1322
1323
1323
1324 class UserRepoGroupToPerm(Base, BaseModel):
1324 class UserRepoGroupToPerm(Base, BaseModel):
1325 __tablename__ = 'user_repo_group_to_perm'
1325 __tablename__ = 'user_repo_group_to_perm'
1326 __table_args__ = (
1326 __table_args__ = (
1327 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1327 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1328 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1328 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1329 'mysql_charset': 'utf8'}
1329 'mysql_charset': 'utf8'}
1330 )
1330 )
1331
1331
1332 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1332 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1333 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1333 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1334 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1334 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1335 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1335 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1336
1336
1337 user = relationship('User')
1337 user = relationship('User')
1338 group = relationship('RepoGroup')
1338 group = relationship('RepoGroup')
1339 permission = relationship('Permission')
1339 permission = relationship('Permission')
1340
1340
1341
1341
1342 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1342 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1343 __tablename__ = 'users_group_repo_group_to_perm'
1343 __tablename__ = 'users_group_repo_group_to_perm'
1344 __table_args__ = (
1344 __table_args__ = (
1345 UniqueConstraint('users_group_id', 'group_id'),
1345 UniqueConstraint('users_group_id', 'group_id'),
1346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1347 'mysql_charset': 'utf8'}
1347 'mysql_charset': 'utf8'}
1348 )
1348 )
1349
1349
1350 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)
1350 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)
1351 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1351 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1352 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1352 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1353 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1353 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1354
1354
1355 users_group = relationship('UsersGroup')
1355 users_group = relationship('UsersGroup')
1356 permission = relationship('Permission')
1356 permission = relationship('Permission')
1357 group = relationship('RepoGroup')
1357 group = relationship('RepoGroup')
1358
1358
1359
1359
1360 class Statistics(Base, BaseModel):
1360 class Statistics(Base, BaseModel):
1361 __tablename__ = 'statistics'
1361 __tablename__ = 'statistics'
1362 __table_args__ = (
1362 __table_args__ = (
1363 UniqueConstraint('repository_id'),
1363 UniqueConstraint('repository_id'),
1364 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1364 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1365 'mysql_charset': 'utf8'}
1365 'mysql_charset': 'utf8'}
1366 )
1366 )
1367 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1367 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1368 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1368 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1369 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1369 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1370 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1370 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1371 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1371 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1372 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1372 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1373
1373
1374 repository = relationship('Repository', single_parent=True)
1374 repository = relationship('Repository', single_parent=True)
1375
1375
1376
1376
1377 class UserFollowing(Base, BaseModel):
1377 class UserFollowing(Base, BaseModel):
1378 __tablename__ = 'user_followings'
1378 __tablename__ = 'user_followings'
1379 __table_args__ = (
1379 __table_args__ = (
1380 UniqueConstraint('user_id', 'follows_repository_id'),
1380 UniqueConstraint('user_id', 'follows_repository_id'),
1381 UniqueConstraint('user_id', 'follows_user_id'),
1381 UniqueConstraint('user_id', 'follows_user_id'),
1382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1383 'mysql_charset': 'utf8'}
1383 'mysql_charset': 'utf8'}
1384 )
1384 )
1385
1385
1386 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1386 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1387 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1387 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1388 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1388 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1389 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1389 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1390 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1390 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1391
1391
1392 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1392 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1393
1393
1394 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1394 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1395 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1395 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1396
1396
1397 @classmethod
1397 @classmethod
1398 def get_repo_followers(cls, repo_id):
1398 def get_repo_followers(cls, repo_id):
1399 return cls.query().filter(cls.follows_repo_id == repo_id)
1399 return cls.query().filter(cls.follows_repo_id == repo_id)
1400
1400
1401
1401
1402 class CacheInvalidation(Base, BaseModel):
1402 class CacheInvalidation(Base, BaseModel):
1403 __tablename__ = 'cache_invalidation'
1403 __tablename__ = 'cache_invalidation'
1404 __table_args__ = (
1404 __table_args__ = (
1405 UniqueConstraint('cache_key'),
1405 UniqueConstraint('cache_key'),
1406 Index('key_idx', 'cache_key'),
1406 Index('key_idx', 'cache_key'),
1407 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1407 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1408 'mysql_charset': 'utf8'},
1408 'mysql_charset': 'utf8'},
1409 )
1409 )
1410 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1410 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1411 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1411 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1412 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1412 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1413 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1413 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1414
1414
1415 def __init__(self, cache_key, cache_args=''):
1415 def __init__(self, cache_key, cache_args=''):
1416 self.cache_key = cache_key
1416 self.cache_key = cache_key
1417 self.cache_args = cache_args
1417 self.cache_args = cache_args
1418 self.cache_active = False
1418 self.cache_active = False
1419
1419
1420 def __unicode__(self):
1420 def __unicode__(self):
1421 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1421 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1422 self.cache_id, self.cache_key)
1422 self.cache_id, self.cache_key)
1423
1423
1424 @property
1424 @property
1425 def prefix(self):
1425 def prefix(self):
1426 _split = self.cache_key.split(self.cache_args, 1)
1426 _split = self.cache_key.split(self.cache_args, 1)
1427 if _split and len(_split) == 2:
1427 if _split and len(_split) == 2:
1428 return _split[0]
1428 return _split[0]
1429 return ''
1429 return ''
1430
1430
1431 @classmethod
1431 @classmethod
1432 def clear_cache(cls):
1432 def clear_cache(cls):
1433 cls.query().delete()
1433 cls.query().delete()
1434
1434
1435 @classmethod
1435 @classmethod
1436 def _get_key(cls, key):
1436 def _get_key(cls, key):
1437 """
1437 """
1438 Wrapper for generating a key, together with a prefix
1438 Wrapper for generating a key, together with a prefix
1439
1439
1440 :param key:
1440 :param key:
1441 """
1441 """
1442 import rhodecode
1442 import rhodecode
1443 prefix = ''
1443 prefix = ''
1444 org_key = key
1444 org_key = key
1445 iid = rhodecode.CONFIG.get('instance_id')
1445 iid = rhodecode.CONFIG.get('instance_id')
1446 if iid:
1446 if iid:
1447 prefix = iid
1447 prefix = iid
1448
1448
1449 return "%s%s" % (prefix, key), prefix, org_key
1449 return "%s%s" % (prefix, key), prefix, org_key
1450
1450
1451 @classmethod
1451 @classmethod
1452 def get_by_key(cls, key):
1452 def get_by_key(cls, key):
1453 return cls.query().filter(cls.cache_key == key).scalar()
1453 return cls.query().filter(cls.cache_key == key).scalar()
1454
1454
1455 @classmethod
1455 @classmethod
1456 def get_by_repo_name(cls, repo_name):
1456 def get_by_repo_name(cls, repo_name):
1457 return cls.query().filter(cls.cache_args == repo_name).all()
1457 return cls.query().filter(cls.cache_args == repo_name).all()
1458
1458
1459 @classmethod
1459 @classmethod
1460 def _get_or_create_key(cls, key, repo_name, commit=True):
1460 def _get_or_create_key(cls, key, repo_name, commit=True):
1461 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1461 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1462 if not inv_obj:
1462 if not inv_obj:
1463 try:
1463 try:
1464 inv_obj = CacheInvalidation(key, repo_name)
1464 inv_obj = CacheInvalidation(key, repo_name)
1465 Session().add(inv_obj)
1465 Session().add(inv_obj)
1466 if commit:
1466 if commit:
1467 Session().commit()
1467 Session().commit()
1468 except Exception:
1468 except Exception:
1469 log.error(traceback.format_exc())
1469 log.error(traceback.format_exc())
1470 Session().rollback()
1470 Session().rollback()
1471 return inv_obj
1471 return inv_obj
1472
1472
1473 @classmethod
1473 @classmethod
1474 def invalidate(cls, key):
1474 def invalidate(cls, key):
1475 """
1475 """
1476 Returns Invalidation object if this given key should be invalidated
1476 Returns Invalidation object if this given key should be invalidated
1477 None otherwise. `cache_active = False` means that this cache
1477 None otherwise. `cache_active = False` means that this cache
1478 state is not valid and needs to be invalidated
1478 state is not valid and needs to be invalidated
1479
1479
1480 :param key:
1480 :param key:
1481 """
1481 """
1482 repo_name = key
1482 repo_name = key
1483 repo_name = remove_suffix(repo_name, '_README')
1483 repo_name = remove_suffix(repo_name, '_README')
1484 repo_name = remove_suffix(repo_name, '_RSS')
1484 repo_name = remove_suffix(repo_name, '_RSS')
1485 repo_name = remove_suffix(repo_name, '_ATOM')
1485 repo_name = remove_suffix(repo_name, '_ATOM')
1486
1486
1487 # adds instance prefix
1487 # adds instance prefix
1488 key, _prefix, _org_key = cls._get_key(key)
1488 key, _prefix, _org_key = cls._get_key(key)
1489 inv = cls._get_or_create_key(key, repo_name)
1489 inv = cls._get_or_create_key(key, repo_name)
1490
1490
1491 if inv and inv.cache_active is False:
1491 if inv and inv.cache_active is False:
1492 return inv
1492 return inv
1493
1493
1494 @classmethod
1494 @classmethod
1495 def set_invalidate(cls, key=None, repo_name=None):
1495 def set_invalidate(cls, key=None, repo_name=None):
1496 """
1496 """
1497 Mark this Cache key for invalidation, either by key or whole
1497 Mark this Cache key for invalidation, either by key or whole
1498 cache sets based on repo_name
1498 cache sets based on repo_name
1499
1499
1500 :param key:
1500 :param key:
1501 """
1501 """
1502 if key:
1502 if key:
1503 key, _prefix, _org_key = cls._get_key(key)
1503 key, _prefix, _org_key = cls._get_key(key)
1504 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1504 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1505 elif repo_name:
1505 elif repo_name:
1506 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1506 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1507
1507
1508 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1508 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1509 % (len(inv_objs), key, repo_name))
1509 % (len(inv_objs), key, repo_name))
1510 try:
1510 try:
1511 for inv_obj in inv_objs:
1511 for inv_obj in inv_objs:
1512 inv_obj.cache_active = False
1512 inv_obj.cache_active = False
1513 Session().add(inv_obj)
1513 Session().add(inv_obj)
1514 Session().commit()
1514 Session().commit()
1515 except Exception:
1515 except Exception:
1516 log.error(traceback.format_exc())
1516 log.error(traceback.format_exc())
1517 Session().rollback()
1517 Session().rollback()
1518
1518
1519 @classmethod
1519 @classmethod
1520 def set_valid(cls, key):
1520 def set_valid(cls, key):
1521 """
1521 """
1522 Mark this cache key as active and currently cached
1522 Mark this cache key as active and currently cached
1523
1523
1524 :param key:
1524 :param key:
1525 """
1525 """
1526 inv_obj = cls.get_by_key(key)
1526 inv_obj = cls.get_by_key(key)
1527 inv_obj.cache_active = True
1527 inv_obj.cache_active = True
1528 Session().add(inv_obj)
1528 Session().add(inv_obj)
1529 Session().commit()
1529 Session().commit()
1530
1530
1531 @classmethod
1531 @classmethod
1532 def get_cache_map(cls):
1532 def get_cache_map(cls):
1533
1533
1534 class cachemapdict(dict):
1534 class cachemapdict(dict):
1535
1535
1536 def __init__(self, *args, **kwargs):
1536 def __init__(self, *args, **kwargs):
1537 fixkey = kwargs.get('fixkey')
1537 fixkey = kwargs.get('fixkey')
1538 if fixkey:
1538 if fixkey:
1539 del kwargs['fixkey']
1539 del kwargs['fixkey']
1540 self.fixkey = fixkey
1540 self.fixkey = fixkey
1541 super(cachemapdict, self).__init__(*args, **kwargs)
1541 super(cachemapdict, self).__init__(*args, **kwargs)
1542
1542
1543 def __getattr__(self, name):
1543 def __getattr__(self, name):
1544 key = name
1544 key = name
1545 if self.fixkey:
1545 if self.fixkey:
1546 key, _prefix, _org_key = cls._get_key(key)
1546 key, _prefix, _org_key = cls._get_key(key)
1547 if key in self.__dict__:
1547 if key in self.__dict__:
1548 return self.__dict__[key]
1548 return self.__dict__[key]
1549 else:
1549 else:
1550 return self[key]
1550 return self[key]
1551
1551
1552 def __getitem__(self, key):
1552 def __getitem__(self, key):
1553 if self.fixkey:
1553 if self.fixkey:
1554 key, _prefix, _org_key = cls._get_key(key)
1554 key, _prefix, _org_key = cls._get_key(key)
1555 try:
1555 try:
1556 return super(cachemapdict, self).__getitem__(key)
1556 return super(cachemapdict, self).__getitem__(key)
1557 except KeyError:
1557 except KeyError:
1558 return
1558 return
1559
1559
1560 cache_map = cachemapdict(fixkey=True)
1560 cache_map = cachemapdict(fixkey=True)
1561 for obj in cls.query().all():
1561 for obj in cls.query().all():
1562 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1562 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1563 return cache_map
1563 return cache_map
1564
1564
1565
1565
1566 class ChangesetComment(Base, BaseModel):
1566 class ChangesetComment(Base, BaseModel):
1567 __tablename__ = 'changeset_comments'
1567 __tablename__ = 'changeset_comments'
1568 __table_args__ = (
1568 __table_args__ = (
1569 Index('cc_revision_idx', 'revision'),
1569 Index('cc_revision_idx', 'revision'),
1570 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1570 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1571 'mysql_charset': 'utf8'},
1571 'mysql_charset': 'utf8'},
1572 )
1572 )
1573 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1573 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1574 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1574 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1575 revision = Column('revision', String(40), nullable=True)
1575 revision = Column('revision', String(40), nullable=True)
1576 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1576 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1577 line_no = Column('line_no', Unicode(10), nullable=True)
1577 line_no = Column('line_no', Unicode(10), nullable=True)
1578 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1578 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1579 f_path = Column('f_path', Unicode(1000), nullable=True)
1579 f_path = Column('f_path', Unicode(1000), nullable=True)
1580 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1580 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1581 text = Column('text', UnicodeText(25000), nullable=False)
1581 text = Column('text', UnicodeText(25000), nullable=False)
1582 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1582 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1583 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1583 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1584
1584
1585 author = relationship('User', lazy='joined')
1585 author = relationship('User', lazy='joined')
1586 repo = relationship('Repository')
1586 repo = relationship('Repository')
1587 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1587 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1588 pull_request = relationship('PullRequest', lazy='joined')
1588 pull_request = relationship('PullRequest', lazy='joined')
1589
1589
1590 @classmethod
1590 @classmethod
1591 def get_users(cls, revision=None, pull_request_id=None):
1591 def get_users(cls, revision=None, pull_request_id=None):
1592 """
1592 """
1593 Returns user associated with this ChangesetComment. ie those
1593 Returns user associated with this ChangesetComment. ie those
1594 who actually commented
1594 who actually commented
1595
1595
1596 :param cls:
1596 :param cls:
1597 :param revision:
1597 :param revision:
1598 """
1598 """
1599 q = Session().query(User)\
1599 q = Session().query(User)\
1600 .join(ChangesetComment.author)
1600 .join(ChangesetComment.author)
1601 if revision:
1601 if revision:
1602 q = q.filter(cls.revision == revision)
1602 q = q.filter(cls.revision == revision)
1603 elif pull_request_id:
1603 elif pull_request_id:
1604 q = q.filter(cls.pull_request_id == pull_request_id)
1604 q = q.filter(cls.pull_request_id == pull_request_id)
1605 return q.all()
1605 return q.all()
1606
1606
1607
1607
1608 class ChangesetStatus(Base, BaseModel):
1608 class ChangesetStatus(Base, BaseModel):
1609 __tablename__ = 'changeset_statuses'
1609 __tablename__ = 'changeset_statuses'
1610 __table_args__ = (
1610 __table_args__ = (
1611 Index('cs_revision_idx', 'revision'),
1611 Index('cs_revision_idx', 'revision'),
1612 Index('cs_version_idx', 'version'),
1612 Index('cs_version_idx', 'version'),
1613 UniqueConstraint('repo_id', 'revision', 'version'),
1613 UniqueConstraint('repo_id', 'revision', 'version'),
1614 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1614 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1615 'mysql_charset': 'utf8'}
1615 'mysql_charset': 'utf8'}
1616 )
1616 )
1617 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1617 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1618 STATUS_APPROVED = 'approved'
1618 STATUS_APPROVED = 'approved'
1619 STATUS_REJECTED = 'rejected'
1619 STATUS_REJECTED = 'rejected'
1620 STATUS_UNDER_REVIEW = 'under_review'
1620 STATUS_UNDER_REVIEW = 'under_review'
1621
1621
1622 STATUSES = [
1622 STATUSES = [
1623 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1623 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1624 (STATUS_APPROVED, _("Approved")),
1624 (STATUS_APPROVED, _("Approved")),
1625 (STATUS_REJECTED, _("Rejected")),
1625 (STATUS_REJECTED, _("Rejected")),
1626 (STATUS_UNDER_REVIEW, _("Under Review")),
1626 (STATUS_UNDER_REVIEW, _("Under Review")),
1627 ]
1627 ]
1628
1628
1629 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1629 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1630 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1630 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1631 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1631 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1632 revision = Column('revision', String(40), nullable=False)
1632 revision = Column('revision', String(40), nullable=False)
1633 status = Column('status', String(128), nullable=False, default=DEFAULT)
1633 status = Column('status', String(128), nullable=False, default=DEFAULT)
1634 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1634 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1635 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1635 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1636 version = Column('version', Integer(), nullable=False, default=0)
1636 version = Column('version', Integer(), nullable=False, default=0)
1637 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1637 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1638
1638
1639 author = relationship('User', lazy='joined')
1639 author = relationship('User', lazy='joined')
1640 repo = relationship('Repository')
1640 repo = relationship('Repository')
1641 comment = relationship('ChangesetComment', lazy='joined')
1641 comment = relationship('ChangesetComment', lazy='joined')
1642 pull_request = relationship('PullRequest', lazy='joined')
1642 pull_request = relationship('PullRequest', lazy='joined')
1643
1643
1644 def __unicode__(self):
1644 def __unicode__(self):
1645 return u"<%s('%s:%s')>" % (
1645 return u"<%s('%s:%s')>" % (
1646 self.__class__.__name__,
1646 self.__class__.__name__,
1647 self.status, self.author
1647 self.status, self.author
1648 )
1648 )
1649
1649
1650 @classmethod
1650 @classmethod
1651 def get_status_lbl(cls, value):
1651 def get_status_lbl(cls, value):
1652 return dict(cls.STATUSES).get(value)
1652 return dict(cls.STATUSES).get(value)
1653
1653
1654 @property
1654 @property
1655 def status_lbl(self):
1655 def status_lbl(self):
1656 return ChangesetStatus.get_status_lbl(self.status)
1656 return ChangesetStatus.get_status_lbl(self.status)
1657
1657
1658
1658
1659 class PullRequest(Base, BaseModel):
1659 class PullRequest(Base, BaseModel):
1660 __tablename__ = 'pull_requests'
1660 __tablename__ = 'pull_requests'
1661 __table_args__ = (
1661 __table_args__ = (
1662 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1662 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1663 'mysql_charset': 'utf8'},
1663 'mysql_charset': 'utf8'},
1664 )
1664 )
1665
1665
1666 STATUS_NEW = u'new'
1666 STATUS_NEW = u'new'
1667 STATUS_OPEN = u'open'
1667 STATUS_OPEN = u'open'
1668 STATUS_CLOSED = u'closed'
1668 STATUS_CLOSED = u'closed'
1669
1669
1670 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1670 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1671 title = Column('title', Unicode(256), nullable=True)
1671 title = Column('title', Unicode(256), nullable=True)
1672 description = Column('description', UnicodeText(10240), nullable=True)
1672 description = Column('description', UnicodeText(10240), nullable=True)
1673 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1673 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1674 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1674 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1675 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1675 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1676 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1676 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1677 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1677 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1678 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1678 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1679 org_ref = Column('org_ref', Unicode(256), nullable=False)
1679 org_ref = Column('org_ref', Unicode(256), nullable=False)
1680 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1680 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1681 other_ref = Column('other_ref', Unicode(256), nullable=False)
1681 other_ref = Column('other_ref', Unicode(256), nullable=False)
1682
1682
1683 @hybrid_property
1683 @hybrid_property
1684 def revisions(self):
1684 def revisions(self):
1685 return self._revisions.split(':')
1685 return self._revisions.split(':')
1686
1686
1687 @revisions.setter
1687 @revisions.setter
1688 def revisions(self, val):
1688 def revisions(self, val):
1689 self._revisions = ':'.join(val)
1689 self._revisions = ':'.join(val)
1690
1690
1691 author = relationship('User', lazy='joined')
1691 author = relationship('User', lazy='joined')
1692 reviewers = relationship('PullRequestReviewers',
1692 reviewers = relationship('PullRequestReviewers',
1693 cascade="all, delete, delete-orphan")
1693 cascade="all, delete, delete-orphan")
1694 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1694 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1695 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1695 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1696 statuses = relationship('ChangesetStatus')
1696 statuses = relationship('ChangesetStatus')
1697 comments = relationship('ChangesetComment',
1697 comments = relationship('ChangesetComment',
1698 cascade="all, delete, delete-orphan")
1698 cascade="all, delete, delete-orphan")
1699
1699
1700 def is_closed(self):
1700 def is_closed(self):
1701 return self.status == self.STATUS_CLOSED
1701 return self.status == self.STATUS_CLOSED
1702
1702
1703 def __json__(self):
1703 def __json__(self):
1704 return dict(
1704 return dict(
1705 revisions=self.revisions
1705 revisions=self.revisions
1706 )
1706 )
1707
1707
1708
1708
1709 class PullRequestReviewers(Base, BaseModel):
1709 class PullRequestReviewers(Base, BaseModel):
1710 __tablename__ = 'pull_request_reviewers'
1710 __tablename__ = 'pull_request_reviewers'
1711 __table_args__ = (
1711 __table_args__ = (
1712 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1712 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1713 'mysql_charset': 'utf8'},
1713 'mysql_charset': 'utf8'},
1714 )
1714 )
1715
1715
1716 def __init__(self, user=None, pull_request=None):
1716 def __init__(self, user=None, pull_request=None):
1717 self.user = user
1717 self.user = user
1718 self.pull_request = pull_request
1718 self.pull_request = pull_request
1719
1719
1720 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1720 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1721 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1721 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1722 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1722 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1723
1723
1724 user = relationship('User')
1724 user = relationship('User')
1725 pull_request = relationship('PullRequest')
1725 pull_request = relationship('PullRequest')
1726
1726
1727
1727
1728 class Notification(Base, BaseModel):
1728 class Notification(Base, BaseModel):
1729 __tablename__ = 'notifications'
1729 __tablename__ = 'notifications'
1730 __table_args__ = (
1730 __table_args__ = (
1731 Index('notification_type_idx', 'type'),
1731 Index('notification_type_idx', 'type'),
1732 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1732 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1733 'mysql_charset': 'utf8'},
1733 'mysql_charset': 'utf8'},
1734 )
1734 )
1735
1735
1736 TYPE_CHANGESET_COMMENT = u'cs_comment'
1736 TYPE_CHANGESET_COMMENT = u'cs_comment'
1737 TYPE_MESSAGE = u'message'
1737 TYPE_MESSAGE = u'message'
1738 TYPE_MENTION = u'mention'
1738 TYPE_MENTION = u'mention'
1739 TYPE_REGISTRATION = u'registration'
1739 TYPE_REGISTRATION = u'registration'
1740 TYPE_PULL_REQUEST = u'pull_request'
1740 TYPE_PULL_REQUEST = u'pull_request'
1741 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1741 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1742
1742
1743 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1743 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1744 subject = Column('subject', Unicode(512), nullable=True)
1744 subject = Column('subject', Unicode(512), nullable=True)
1745 body = Column('body', UnicodeText(50000), nullable=True)
1745 body = Column('body', UnicodeText(50000), nullable=True)
1746 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1746 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1747 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1747 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1748 type_ = Column('type', Unicode(256))
1748 type_ = Column('type', Unicode(256))
1749
1749
1750 created_by_user = relationship('User')
1750 created_by_user = relationship('User')
1751 notifications_to_users = relationship('UserNotification', lazy='joined',
1751 notifications_to_users = relationship('UserNotification', lazy='joined',
1752 cascade="all, delete, delete-orphan")
1752 cascade="all, delete, delete-orphan")
1753
1753
1754 @property
1754 @property
1755 def recipients(self):
1755 def recipients(self):
1756 return [x.user for x in UserNotification.query()\
1756 return [x.user for x in UserNotification.query()\
1757 .filter(UserNotification.notification == self)\
1757 .filter(UserNotification.notification == self)\
1758 .order_by(UserNotification.user_id.asc()).all()]
1758 .order_by(UserNotification.user_id.asc()).all()]
1759
1759
1760 @classmethod
1760 @classmethod
1761 def create(cls, created_by, subject, body, recipients, type_=None):
1761 def create(cls, created_by, subject, body, recipients, type_=None):
1762 if type_ is None:
1762 if type_ is None:
1763 type_ = Notification.TYPE_MESSAGE
1763 type_ = Notification.TYPE_MESSAGE
1764
1764
1765 notification = cls()
1765 notification = cls()
1766 notification.created_by_user = created_by
1766 notification.created_by_user = created_by
1767 notification.subject = subject
1767 notification.subject = subject
1768 notification.body = body
1768 notification.body = body
1769 notification.type_ = type_
1769 notification.type_ = type_
1770 notification.created_on = datetime.datetime.now()
1770 notification.created_on = datetime.datetime.now()
1771
1771
1772 for u in recipients:
1772 for u in recipients:
1773 assoc = UserNotification()
1773 assoc = UserNotification()
1774 assoc.notification = notification
1774 assoc.notification = notification
1775 u.notifications.append(assoc)
1775 u.notifications.append(assoc)
1776 Session().add(notification)
1776 Session().add(notification)
1777 return notification
1777 return notification
1778
1778
1779 @property
1779 @property
1780 def description(self):
1780 def description(self):
1781 from rhodecode.model.notification import NotificationModel
1781 from rhodecode.model.notification import NotificationModel
1782 return NotificationModel().make_description(self)
1782 return NotificationModel().make_description(self)
1783
1783
1784
1784
1785 class UserNotification(Base, BaseModel):
1785 class UserNotification(Base, BaseModel):
1786 __tablename__ = 'user_to_notification'
1786 __tablename__ = 'user_to_notification'
1787 __table_args__ = (
1787 __table_args__ = (
1788 UniqueConstraint('user_id', 'notification_id'),
1788 UniqueConstraint('user_id', 'notification_id'),
1789 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1789 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1790 'mysql_charset': 'utf8'}
1790 'mysql_charset': 'utf8'}
1791 )
1791 )
1792 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1792 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1793 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1793 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1794 read = Column('read', Boolean, default=False)
1794 read = Column('read', Boolean, default=False)
1795 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1795 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1796
1796
1797 user = relationship('User', lazy="joined")
1797 user = relationship('User', lazy="joined")
1798 notification = relationship('Notification', lazy="joined",
1798 notification = relationship('Notification', lazy="joined",
1799 order_by=lambda: Notification.created_on.desc(),)
1799 order_by=lambda: Notification.created_on.desc(),)
1800
1800
1801 def mark_as_read(self):
1801 def mark_as_read(self):
1802 self.read = True
1802 self.read = True
1803 Session().add(self)
1803 Session().add(self)
1804
1804
1805
1805
1806 class DbMigrateVersion(Base, BaseModel):
1806 class DbMigrateVersion(Base, BaseModel):
1807 __tablename__ = 'db_migrate_version'
1807 __tablename__ = 'db_migrate_version'
1808 __table_args__ = (
1808 __table_args__ = (
1809 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1809 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1810 'mysql_charset': 'utf8'},
1810 'mysql_charset': 'utf8'},
1811 )
1811 )
1812 repository_id = Column('repository_id', String(250), primary_key=True)
1812 repository_id = Column('repository_id', String(250), primary_key=True)
1813 repository_path = Column('repository_path', Text)
1813 repository_path = Column('repository_path', Text)
1814 version = Column('version', Integer)
1814 version = Column('version', Integer)
1815
@@ -1,527 +1,527 b''
1 import re
1 import re
2 from itertools import chain
2 from itertools import chain
3 from dulwich import objects
3 from dulwich import objects
4 from subprocess import Popen, PIPE
4 from subprocess import Popen, PIPE
5 from rhodecode.lib.vcs.conf import settings
5 from rhodecode.lib.vcs.conf import settings
6 from rhodecode.lib.vcs.exceptions import RepositoryError
6 from rhodecode.lib.vcs.exceptions import RepositoryError
7 from rhodecode.lib.vcs.exceptions import ChangesetError
7 from rhodecode.lib.vcs.exceptions import ChangesetError
8 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
8 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
9 from rhodecode.lib.vcs.exceptions import VCSError
9 from rhodecode.lib.vcs.exceptions import VCSError
10 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
10 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
11 from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError
11 from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError
12 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
12 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
13 from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, \
13 from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, \
14 RemovedFileNode, SubModuleNode, ChangedFileNodesGenerator,\
14 RemovedFileNode, SubModuleNode, ChangedFileNodesGenerator,\
15 AddedFileNodesGenerator, RemovedFileNodesGenerator
15 AddedFileNodesGenerator, RemovedFileNodesGenerator
16 from rhodecode.lib.vcs.utils import safe_unicode
16 from rhodecode.lib.vcs.utils import safe_unicode
17 from rhodecode.lib.vcs.utils import date_fromtimestamp
17 from rhodecode.lib.vcs.utils import date_fromtimestamp
18 from rhodecode.lib.vcs.utils.lazy import LazyProperty
18 from rhodecode.lib.vcs.utils.lazy import LazyProperty
19
19
20
20
21 class GitChangeset(BaseChangeset):
21 class GitChangeset(BaseChangeset):
22 """
22 """
23 Represents state of the repository at single revision.
23 Represents state of the repository at single revision.
24 """
24 """
25
25
26 def __init__(self, repository, revision):
26 def __init__(self, repository, revision):
27 self._stat_modes = {}
27 self._stat_modes = {}
28 self.repository = repository
28 self.repository = repository
29
29
30 try:
30 try:
31 commit = self.repository._repo.get_object(revision)
31 commit = self.repository._repo.get_object(revision)
32 if isinstance(commit, objects.Tag):
32 if isinstance(commit, objects.Tag):
33 revision = commit.object[1]
33 revision = commit.object[1]
34 commit = self.repository._repo.get_object(commit.object[1])
34 commit = self.repository._repo.get_object(commit.object[1])
35 except KeyError:
35 except KeyError:
36 raise RepositoryError("Cannot get object with id %s" % revision)
36 raise RepositoryError("Cannot get object with id %s" % revision)
37 self.raw_id = revision
37 self.raw_id = revision
38 self.id = self.raw_id
38 self.id = self.raw_id
39 self.short_id = self.raw_id[:12]
39 self.short_id = self.raw_id[:12]
40 self._commit = commit
40 self._commit = commit
41
41
42 self._tree_id = commit.tree
42 self._tree_id = commit.tree
43 self._commiter_property = 'committer'
43 self._commiter_property = 'committer'
44 self._author_property = 'author'
44 self._author_property = 'author'
45 self._date_property = 'commit_time'
45 self._date_property = 'commit_time'
46 self._date_tz_property = 'commit_timezone'
46 self._date_tz_property = 'commit_timezone'
47 self.revision = repository.revisions.index(revision)
47 self.revision = repository.revisions.index(revision)
48
48
49 self.message = safe_unicode(commit.message)
49 self.message = safe_unicode(commit.message)
50
50
51 self.nodes = {}
51 self.nodes = {}
52 self._paths = {}
52 self._paths = {}
53
53
54 @LazyProperty
54 @LazyProperty
55 def commiter(self):
55 def commiter(self):
56 return safe_unicode(getattr(self._commit, self._commiter_property))
56 return safe_unicode(getattr(self._commit, self._commiter_property))
57
57
58 @LazyProperty
58 @LazyProperty
59 def author(self):
59 def author(self):
60 return safe_unicode(getattr(self._commit, self._author_property))
60 return safe_unicode(getattr(self._commit, self._author_property))
61
61
62 @LazyProperty
62 @LazyProperty
63 def date(self):
63 def date(self):
64 return date_fromtimestamp(getattr(self._commit, self._date_property),
64 return date_fromtimestamp(getattr(self._commit, self._date_property),
65 getattr(self._commit, self._date_tz_property))
65 getattr(self._commit, self._date_tz_property))
66
66
67 @LazyProperty
67 @LazyProperty
68 def _timestamp(self):
68 def _timestamp(self):
69 return getattr(self._commit, self._date_property)
69 return getattr(self._commit, self._date_property)
70
70
71 @LazyProperty
71 @LazyProperty
72 def status(self):
72 def status(self):
73 """
73 """
74 Returns modified, added, removed, deleted files for current changeset
74 Returns modified, added, removed, deleted files for current changeset
75 """
75 """
76 return self.changed, self.added, self.removed
76 return self.changed, self.added, self.removed
77
77
78 @LazyProperty
78 @LazyProperty
79 def tags(self):
79 def tags(self):
80 _tags = []
80 _tags = []
81 for tname, tsha in self.repository.tags.iteritems():
81 for tname, tsha in self.repository.tags.iteritems():
82 if tsha == self.raw_id:
82 if tsha == self.raw_id:
83 _tags.append(tname)
83 _tags.append(tname)
84 return _tags
84 return _tags
85
85
86 @LazyProperty
86 @LazyProperty
87 def branch(self):
87 def branch(self):
88
88
89 heads = self.repository._heads(reverse=False)
89 heads = self.repository._heads(reverse=False)
90
90
91 ref = heads.get(self.raw_id)
91 ref = heads.get(self.raw_id)
92 if ref:
92 if ref:
93 return safe_unicode(ref)
93 return safe_unicode(ref)
94
94
95 def _fix_path(self, path):
95 def _fix_path(self, path):
96 """
96 """
97 Paths are stored without trailing slash so we need to get rid off it if
97 Paths are stored without trailing slash so we need to get rid off it if
98 needed.
98 needed.
99 """
99 """
100 if path.endswith('/'):
100 if path.endswith('/'):
101 path = path.rstrip('/')
101 path = path.rstrip('/')
102 return path
102 return path
103
103
104 def _get_id_for_path(self, path):
104 def _get_id_for_path(self, path):
105
105
106 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
106 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
107 if not path in self._paths:
107 if not path in self._paths:
108 path = path.strip('/')
108 path = path.strip('/')
109 # set root tree
109 # set root tree
110 tree = self.repository._repo[self._tree_id]
110 tree = self.repository._repo[self._tree_id]
111 if path == '':
111 if path == '':
112 self._paths[''] = tree.id
112 self._paths[''] = tree.id
113 return tree.id
113 return tree.id
114 splitted = path.split('/')
114 splitted = path.split('/')
115 dirs, name = splitted[:-1], splitted[-1]
115 dirs, name = splitted[:-1], splitted[-1]
116 curdir = ''
116 curdir = ''
117
117
118 # initially extract things from root dir
118 # initially extract things from root dir
119 for item, stat, id in tree.iteritems():
119 for item, stat, id in tree.iteritems():
120 if curdir:
120 if curdir:
121 name = '/'.join((curdir, item))
121 name = '/'.join((curdir, item))
122 else:
122 else:
123 name = item
123 name = item
124 self._paths[name] = id
124 self._paths[name] = id
125 self._stat_modes[name] = stat
125 self._stat_modes[name] = stat
126
126
127 for dir in dirs:
127 for dir in dirs:
128 if curdir:
128 if curdir:
129 curdir = '/'.join((curdir, dir))
129 curdir = '/'.join((curdir, dir))
130 else:
130 else:
131 curdir = dir
131 curdir = dir
132 dir_id = None
132 dir_id = None
133 for item, stat, id in tree.iteritems():
133 for item, stat, id in tree.iteritems():
134 if dir == item:
134 if dir == item:
135 dir_id = id
135 dir_id = id
136 if dir_id:
136 if dir_id:
137 # Update tree
137 # Update tree
138 tree = self.repository._repo[dir_id]
138 tree = self.repository._repo[dir_id]
139 if not isinstance(tree, objects.Tree):
139 if not isinstance(tree, objects.Tree):
140 raise ChangesetError('%s is not a directory' % curdir)
140 raise ChangesetError('%s is not a directory' % curdir)
141 else:
141 else:
142 raise ChangesetError('%s have not been found' % curdir)
142 raise ChangesetError('%s have not been found' % curdir)
143
143
144 # cache all items from the given traversed tree
144 # cache all items from the given traversed tree
145 for item, stat, id in tree.iteritems():
145 for item, stat, id in tree.iteritems():
146 if curdir:
146 if curdir:
147 name = '/'.join((curdir, item))
147 name = '/'.join((curdir, item))
148 else:
148 else:
149 name = item
149 name = item
150 self._paths[name] = id
150 self._paths[name] = id
151 self._stat_modes[name] = stat
151 self._stat_modes[name] = stat
152 if not path in self._paths:
152 if not path in self._paths:
153 raise NodeDoesNotExistError("There is no file nor directory "
153 raise NodeDoesNotExistError("There is no file nor directory "
154 "at the given path %r at revision %r"
154 "at the given path %r at revision %r"
155 % (path, self.short_id))
155 % (path, self.short_id))
156 return self._paths[path]
156 return self._paths[path]
157
157
158 def _get_kind(self, path):
158 def _get_kind(self, path):
159 obj = self.repository._repo[self._get_id_for_path(path)]
159 obj = self.repository._repo[self._get_id_for_path(path)]
160 if isinstance(obj, objects.Blob):
160 if isinstance(obj, objects.Blob):
161 return NodeKind.FILE
161 return NodeKind.FILE
162 elif isinstance(obj, objects.Tree):
162 elif isinstance(obj, objects.Tree):
163 return NodeKind.DIR
163 return NodeKind.DIR
164
164
165 def _get_filectx(self, path):
165 def _get_filectx(self, path):
166 path = self._fix_path(path)
166 path = self._fix_path(path)
167 if self._get_kind(path) != NodeKind.FILE:
167 if self._get_kind(path) != NodeKind.FILE:
168 raise ChangesetError("File does not exist for revision %r at "
168 raise ChangesetError("File does not exist for revision %r at "
169 " %r" % (self.raw_id, path))
169 " %r" % (self.raw_id, path))
170 return path
170 return path
171
171
172 def _get_file_nodes(self):
172 def _get_file_nodes(self):
173 return chain(*(t[2] for t in self.walk()))
173 return chain(*(t[2] for t in self.walk()))
174
174
175 @LazyProperty
175 @LazyProperty
176 def parents(self):
176 def parents(self):
177 """
177 """
178 Returns list of parents changesets.
178 Returns list of parents changesets.
179 """
179 """
180 return [self.repository.get_changeset(parent)
180 return [self.repository.get_changeset(parent)
181 for parent in self._commit.parents]
181 for parent in self._commit.parents]
182
182
183 def next(self, branch=None):
183 def next(self, branch=None):
184
184
185 if branch and self.branch != branch:
185 if branch and self.branch != branch:
186 raise VCSError('Branch option used on changeset not belonging '
186 raise VCSError('Branch option used on changeset not belonging '
187 'to that branch')
187 'to that branch')
188
188
189 def _next(changeset, branch):
189 def _next(changeset, branch):
190 try:
190 try:
191 next_ = changeset.revision + 1
191 next_ = changeset.revision + 1
192 next_rev = changeset.repository.revisions[next_]
192 next_rev = changeset.repository.revisions[next_]
193 except IndexError:
193 except IndexError:
194 raise ChangesetDoesNotExistError
194 raise ChangesetDoesNotExistError
195 cs = changeset.repository.get_changeset(next_rev)
195 cs = changeset.repository.get_changeset(next_rev)
196
196
197 if branch and branch != cs.branch:
197 if branch and branch != cs.branch:
198 return _next(cs, branch)
198 return _next(cs, branch)
199
199
200 return cs
200 return cs
201
201
202 return _next(self, branch)
202 return _next(self, branch)
203
203
204 def prev(self, branch=None):
204 def prev(self, branch=None):
205 if branch and self.branch != branch:
205 if branch and self.branch != branch:
206 raise VCSError('Branch option used on changeset not belonging '
206 raise VCSError('Branch option used on changeset not belonging '
207 'to that branch')
207 'to that branch')
208
208
209 def _prev(changeset, branch):
209 def _prev(changeset, branch):
210 try:
210 try:
211 prev_ = changeset.revision - 1
211 prev_ = changeset.revision - 1
212 if prev_ < 0:
212 if prev_ < 0:
213 raise IndexError
213 raise IndexError
214 prev_rev = changeset.repository.revisions[prev_]
214 prev_rev = changeset.repository.revisions[prev_]
215 except IndexError:
215 except IndexError:
216 raise ChangesetDoesNotExistError
216 raise ChangesetDoesNotExistError
217
217
218 cs = changeset.repository.get_changeset(prev_rev)
218 cs = changeset.repository.get_changeset(prev_rev)
219
219
220 if branch and branch != cs.branch:
220 if branch and branch != cs.branch:
221 return _prev(cs, branch)
221 return _prev(cs, branch)
222
222
223 return cs
223 return cs
224
224
225 return _prev(self, branch)
225 return _prev(self, branch)
226
226
227 def diff(self, ignore_whitespace=True, context=3):
227 def diff(self, ignore_whitespace=True, context=3):
228 rev1 = self.parents[0] if self.parents else self.repository.EMPTY_CHANGESET
228 rev1 = self.parents[0] if self.parents else self.repository.EMPTY_CHANGESET
229 rev2 = self
229 rev2 = self
230 return ''.join(self.repository.get_diff(rev1, rev2,
230 return ''.join(self.repository.get_diff(rev1, rev2,
231 ignore_whitespace=ignore_whitespace,
231 ignore_whitespace=ignore_whitespace,
232 context=context))
232 context=context))
233
233
234 def get_file_mode(self, path):
234 def get_file_mode(self, path):
235 """
235 """
236 Returns stat mode of the file at the given ``path``.
236 Returns stat mode of the file at the given ``path``.
237 """
237 """
238 # ensure path is traversed
238 # ensure path is traversed
239 self._get_id_for_path(path)
239 self._get_id_for_path(path)
240 return self._stat_modes[path]
240 return self._stat_modes[path]
241
241
242 def get_file_content(self, path):
242 def get_file_content(self, path):
243 """
243 """
244 Returns content of the file at given ``path``.
244 Returns content of the file at given ``path``.
245 """
245 """
246 id = self._get_id_for_path(path)
246 id = self._get_id_for_path(path)
247 blob = self.repository._repo[id]
247 blob = self.repository._repo[id]
248 return blob.as_pretty_string()
248 return blob.as_pretty_string()
249
249
250 def get_file_size(self, path):
250 def get_file_size(self, path):
251 """
251 """
252 Returns size of the file at given ``path``.
252 Returns size of the file at given ``path``.
253 """
253 """
254 id = self._get_id_for_path(path)
254 id = self._get_id_for_path(path)
255 blob = self.repository._repo[id]
255 blob = self.repository._repo[id]
256 return blob.raw_length()
256 return blob.raw_length()
257
257
258 def get_file_changeset(self, path):
258 def get_file_changeset(self, path):
259 """
259 """
260 Returns last commit of the file at the given ``path``.
260 Returns last commit of the file at the given ``path``.
261 """
261 """
262 node = self.get_node(path)
262 node = self.get_node(path)
263 return node.history[0]
263 return node.history[0]
264
264
265 def get_file_history(self, path):
265 def get_file_history(self, path):
266 """
266 """
267 Returns history of file as reversed list of ``Changeset`` objects for
267 Returns history of file as reversed list of ``Changeset`` objects for
268 which file at given ``path`` has been modified.
268 which file at given ``path`` has been modified.
269
269
270 TODO: This function now uses os underlying 'git' and 'grep' commands
270 TODO: This function now uses os underlying 'git' and 'grep' commands
271 which is generally not good. Should be replaced with algorithm
271 which is generally not good. Should be replaced with algorithm
272 iterating commits.
272 iterating commits.
273 """
273 """
274 self._get_filectx(path)
274 self._get_filectx(path)
275
275
276 cmd = 'log --pretty="format: %%H" -s -p %s -- "%s"' % (
276 cmd = 'log --pretty="format: %%H" -s -p %s -- "%s"' % (
277 self.id, path
277 self.id, path
278 )
278 )
279 so, se = self.repository.run_git_command(cmd)
279 so, se = self.repository.run_git_command(cmd)
280 ids = re.findall(r'[0-9a-fA-F]{40}', so)
280 ids = re.findall(r'[0-9a-fA-F]{40}', so)
281 return [self.repository.get_changeset(id) for id in ids]
281 return [self.repository.get_changeset(id) for id in ids]
282
282
283 def get_file_history_2(self, path):
283 def get_file_history_2(self, path):
284 """
284 """
285 Returns history of file as reversed list of ``Changeset`` objects for
285 Returns history of file as reversed list of ``Changeset`` objects for
286 which file at given ``path`` has been modified.
286 which file at given ``path`` has been modified.
287
287
288 """
288 """
289 self._get_filectx(path)
289 self._get_filectx(path)
290 from dulwich.walk import Walker
290 from dulwich.walk import Walker
291 include = [self.id]
291 include = [self.id]
292 walker = Walker(self.repository._repo.object_store, include,
292 walker = Walker(self.repository._repo.object_store, include,
293 paths=[path], max_entries=1)
293 paths=[path], max_entries=1)
294 return [self.repository.get_changeset(sha)
294 return [self.repository.get_changeset(sha)
295 for sha in (x.commit.id for x in walker)]
295 for sha in (x.commit.id for x in walker)]
296
296
297 def get_file_annotate(self, path):
297 def get_file_annotate(self, path):
298 """
298 """
299 Returns a generator of four element tuples with
299 Returns a generator of four element tuples with
300 lineno, sha, changeset lazy loader and line
300 lineno, sha, changeset lazy loader and line
301
301
302 TODO: This function now uses os underlying 'git' command which is
302 TODO: This function now uses os underlying 'git' command which is
303 generally not good. Should be replaced with algorithm iterating
303 generally not good. Should be replaced with algorithm iterating
304 commits.
304 commits.
305 """
305 """
306 cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
306 cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
307 # -l ==> outputs long shas (and we need all 40 characters)
307 # -l ==> outputs long shas (and we need all 40 characters)
308 # --root ==> doesn't put '^' character for bounderies
308 # --root ==> doesn't put '^' character for bounderies
309 # -r sha ==> blames for the given revision
309 # -r sha ==> blames for the given revision
310 so, se = self.repository.run_git_command(cmd)
310 so, se = self.repository.run_git_command(cmd)
311
311
312 for i, blame_line in enumerate(so.split('\n')[:-1]):
312 for i, blame_line in enumerate(so.split('\n')[:-1]):
313 ln_no = i + 1
313 ln_no = i + 1
314 sha, line = re.split(r' ', blame_line, 1)
314 sha, line = re.split(r' ', blame_line, 1)
315 yield (ln_no, sha, lambda: self.repository.get_changeset(sha), line)
315 yield (ln_no, sha, lambda: self.repository.get_changeset(sha), line)
316
316
317 def fill_archive(self, stream=None, kind='tgz', prefix=None,
317 def fill_archive(self, stream=None, kind='tgz', prefix=None,
318 subrepos=False):
318 subrepos=False):
319 """
319 """
320 Fills up given stream.
320 Fills up given stream.
321
321
322 :param stream: file like object.
322 :param stream: file like object.
323 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
323 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
324 Default: ``tgz``.
324 Default: ``tgz``.
325 :param prefix: name of root directory in archive.
325 :param prefix: name of root directory in archive.
326 Default is repository name and changeset's raw_id joined with dash
326 Default is repository name and changeset's raw_id joined with dash
327 (``repo-tip.<KIND>``).
327 (``repo-tip.<KIND>``).
328 :param subrepos: include subrepos in this archive.
328 :param subrepos: include subrepos in this archive.
329
329
330 :raise ImproperArchiveTypeError: If given kind is wrong.
330 :raise ImproperArchiveTypeError: If given kind is wrong.
331 :raise VcsError: If given stream is None
331 :raise VcsError: If given stream is None
332
332
333 """
333 """
334 allowed_kinds = settings.ARCHIVE_SPECS.keys()
334 allowed_kinds = settings.ARCHIVE_SPECS.keys()
335 if kind not in allowed_kinds:
335 if kind not in allowed_kinds:
336 raise ImproperArchiveTypeError('Archive kind not supported use one'
336 raise ImproperArchiveTypeError('Archive kind not supported use one'
337 'of %s', allowed_kinds)
337 'of %s', allowed_kinds)
338
338
339 if prefix is None:
339 if prefix is None:
340 prefix = '%s-%s' % (self.repository.name, self.short_id)
340 prefix = '%s-%s' % (self.repository.name, self.short_id)
341 elif prefix.startswith('/'):
341 elif prefix.startswith('/'):
342 raise VCSError("Prefix cannot start with leading slash")
342 raise VCSError("Prefix cannot start with leading slash")
343 elif prefix.strip() == '':
343 elif prefix.strip() == '':
344 raise VCSError("Prefix cannot be empty")
344 raise VCSError("Prefix cannot be empty")
345
345
346 if kind == 'zip':
346 if kind == 'zip':
347 frmt = 'zip'
347 frmt = 'zip'
348 else:
348 else:
349 frmt = 'tar'
349 frmt = 'tar'
350 cmd = 'git archive --format=%s --prefix=%s/ %s' % (frmt, prefix,
350 cmd = 'git archive --format=%s --prefix=%s/ %s' % (frmt, prefix,
351 self.raw_id)
351 self.raw_id)
352 if kind == 'tgz':
352 if kind == 'tgz':
353 cmd += ' | gzip -9'
353 cmd += ' | gzip -9'
354 elif kind == 'tbz2':
354 elif kind == 'tbz2':
355 cmd += ' | bzip2 -9'
355 cmd += ' | bzip2 -9'
356
356
357 if stream is None:
357 if stream is None:
358 raise VCSError('You need to pass in a valid stream for filling'
358 raise VCSError('You need to pass in a valid stream for filling'
359 ' with archival data')
359 ' with archival data')
360 popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
360 popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
361 cwd=self.repository.path)
361 cwd=self.repository.path)
362
362
363 buffer_size = 1024 * 8
363 buffer_size = 1024 * 8
364 chunk = popen.stdout.read(buffer_size)
364 chunk = popen.stdout.read(buffer_size)
365 while chunk:
365 while chunk:
366 stream.write(chunk)
366 stream.write(chunk)
367 chunk = popen.stdout.read(buffer_size)
367 chunk = popen.stdout.read(buffer_size)
368 # Make sure all descriptors would be read
368 # Make sure all descriptors would be read
369 popen.communicate()
369 popen.communicate()
370
370
371 def get_nodes(self, path):
371 def get_nodes(self, path):
372 if self._get_kind(path) != NodeKind.DIR:
372 if self._get_kind(path) != NodeKind.DIR:
373 raise ChangesetError("Directory does not exist for revision %r at "
373 raise ChangesetError("Directory does not exist for revision %r at "
374 " %r" % (self.revision, path))
374 " %r" % (self.revision, path))
375 path = self._fix_path(path)
375 path = self._fix_path(path)
376 id = self._get_id_for_path(path)
376 id = self._get_id_for_path(path)
377 tree = self.repository._repo[id]
377 tree = self.repository._repo[id]
378 dirnodes = []
378 dirnodes = []
379 filenodes = []
379 filenodes = []
380 als = self.repository.alias
380 als = self.repository.alias
381 for name, stat, id in tree.iteritems():
381 for name, stat, id in tree.iteritems():
382 if objects.S_ISGITLINK(stat):
382 if objects.S_ISGITLINK(stat):
383 dirnodes.append(SubModuleNode(name, url=None, changeset=id,
383 dirnodes.append(SubModuleNode(name, url=None, changeset=id,
384 alias=als))
384 alias=als))
385 continue
385 continue
386
386
387 obj = self.repository._repo.get_object(id)
387 obj = self.repository._repo.get_object(id)
388 if path != '':
388 if path != '':
389 obj_path = '/'.join((path, name))
389 obj_path = '/'.join((path, name))
390 else:
390 else:
391 obj_path = name
391 obj_path = name
392 if obj_path not in self._stat_modes:
392 if obj_path not in self._stat_modes:
393 self._stat_modes[obj_path] = stat
393 self._stat_modes[obj_path] = stat
394 if isinstance(obj, objects.Tree):
394 if isinstance(obj, objects.Tree):
395 dirnodes.append(DirNode(obj_path, changeset=self))
395 dirnodes.append(DirNode(obj_path, changeset=self))
396 elif isinstance(obj, objects.Blob):
396 elif isinstance(obj, objects.Blob):
397 filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
397 filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
398 else:
398 else:
399 raise ChangesetError("Requested object should be Tree "
399 raise ChangesetError("Requested object should be Tree "
400 "or Blob, is %r" % type(obj))
400 "or Blob, is %r" % type(obj))
401 nodes = dirnodes + filenodes
401 nodes = dirnodes + filenodes
402 for node in nodes:
402 for node in nodes:
403 if not node.path in self.nodes:
403 if not node.path in self.nodes:
404 self.nodes[node.path] = node
404 self.nodes[node.path] = node
405 nodes.sort()
405 nodes.sort()
406 return nodes
406 return nodes
407
407
408 def get_node(self, path):
408 def get_node(self, path):
409 if isinstance(path, unicode):
409 if isinstance(path, unicode):
410 path = path.encode('utf-8')
410 path = path.encode('utf-8')
411 path = self._fix_path(path)
411 path = self._fix_path(path)
412 if not path in self.nodes:
412 if not path in self.nodes:
413 try:
413 try:
414 id_ = self._get_id_for_path(path)
414 id_ = self._get_id_for_path(path)
415 except ChangesetError:
415 except ChangesetError:
416 raise NodeDoesNotExistError("Cannot find one of parents' "
416 raise NodeDoesNotExistError("Cannot find one of parents' "
417 "directories for a given path: %s" % path)
417 "directories for a given path: %s" % path)
418
418
419 _GL = lambda m: m and objects.S_ISGITLINK(m)
419 _GL = lambda m: m and objects.S_ISGITLINK(m)
420 if _GL(self._stat_modes.get(path)):
420 if _GL(self._stat_modes.get(path)):
421 node = SubModuleNode(path, url=None, changeset=id_,
421 node = SubModuleNode(path, url=None, changeset=id_,
422 alias=self.repository.alias)
422 alias=self.repository.alias)
423 else:
423 else:
424 obj = self.repository._repo.get_object(id_)
424 obj = self.repository._repo.get_object(id_)
425
425
426 if isinstance(obj, objects.Tree):
426 if isinstance(obj, objects.Tree):
427 if path == '':
427 if path == '':
428 node = RootNode(changeset=self)
428 node = RootNode(changeset=self)
429 else:
429 else:
430 node = DirNode(path, changeset=self)
430 node = DirNode(path, changeset=self)
431 node._tree = obj
431 node._tree = obj
432 elif isinstance(obj, objects.Blob):
432 elif isinstance(obj, objects.Blob):
433 node = FileNode(path, changeset=self)
433 node = FileNode(path, changeset=self)
434 node._blob = obj
434 node._blob = obj
435 else:
435 else:
436 raise NodeDoesNotExistError("There is no file nor directory "
436 raise NodeDoesNotExistError("There is no file nor directory "
437 "at the given path %r at revision %r"
437 "at the given path %r at revision %r"
438 % (path, self.short_id))
438 % (path, self.short_id))
439 # cache node
439 # cache node
440 self.nodes[path] = node
440 self.nodes[path] = node
441 return self.nodes[path]
441 return self.nodes[path]
442
442
443 @LazyProperty
443 @LazyProperty
444 def affected_files(self):
444 def affected_files(self):
445 """
445 """
446 Get's a fast accessible file changes for given changeset
446 Get's a fast accessible file changes for given changeset
447 """
447 """
448 a, m, d = self._changes_cache
448 a, m, d = self._changes_cache
449 return list(a.union(m).union(d))
449 return list(a.union(m).union(d))
450
450
451 @LazyProperty
451 @LazyProperty
452 def _diff_name_status(self):
452 def _diff_name_status(self):
453 output = []
453 output = []
454 for parent in self.parents:
454 for parent in self.parents:
455 cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id,
455 cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id,
456 self.raw_id)
456 self.raw_id)
457 so, se = self.repository.run_git_command(cmd)
457 so, se = self.repository.run_git_command(cmd)
458 output.append(so.strip())
458 output.append(so.strip())
459 return '\n'.join(output)
459 return '\n'.join(output)
460
460
461 @LazyProperty
461 @LazyProperty
462 def _changes_cache(self):
462 def _changes_cache(self):
463 added = set()
463 added = set()
464 modified = set()
464 modified = set()
465 deleted = set()
465 deleted = set()
466 _r = self.repository._repo
466 _r = self.repository._repo
467
467
468 parents = self.parents
468 parents = self.parents
469 if not self.parents:
469 if not self.parents:
470 parents = [EmptyChangeset()]
470 parents = [EmptyChangeset()]
471 for parent in parents:
471 for parent in parents:
472 if isinstance(parent, EmptyChangeset):
472 if isinstance(parent, EmptyChangeset):
473 oid = None
473 oid = None
474 else:
474 else:
475 oid = _r[parent.raw_id].tree
475 oid = _r[parent.raw_id].tree
476 changes = _r.object_store.tree_changes(oid, _r[self.raw_id].tree)
476 changes = _r.object_store.tree_changes(oid, _r[self.raw_id].tree)
477 for (oldpath, newpath), (_, _), (_, _) in changes:
477 for (oldpath, newpath), (_, _), (_, _) in changes:
478 if newpath and oldpath:
478 if newpath and oldpath:
479 modified.add(newpath)
479 modified.add(newpath)
480 elif newpath and not oldpath:
480 elif newpath and not oldpath:
481 added.add(newpath)
481 added.add(newpath)
482 elif not newpath and oldpath:
482 elif not newpath and oldpath:
483 deleted.add(oldpath)
483 deleted.add(oldpath)
484 return added, modified, deleted
484 return added, modified, deleted
485
485
486 def _get_paths_for_status(self, status):
486 def _get_paths_for_status(self, status):
487 """
487 """
488 Returns sorted list of paths for given ``status``.
488 Returns sorted list of paths for given ``status``.
489
489
490 :param status: one of: *added*, *modified* or *deleted*
490 :param status: one of: *added*, *modified* or *deleted*
491 """
491 """
492 a, m, d = self._changes_cache
492 a, m, d = self._changes_cache
493 return sorted({
493 return sorted({
494 'added': list(a),
494 'added': list(a),
495 'modified': list(m),
495 'modified': list(m),
496 'deleted': list(d)}[status]
496 'deleted': list(d)}[status]
497 )
497 )
498
498
499 @LazyProperty
499 @LazyProperty
500 def added(self):
500 def added(self):
501 """
501 """
502 Returns list of added ``FileNode`` objects.
502 Returns list of added ``FileNode`` objects.
503 """
503 """
504 if not self.parents:
504 if not self.parents:
505 return list(self._get_file_nodes())
505 return list(self._get_file_nodes())
506 return AddedFileNodesGenerator([n for n in
506 return AddedFileNodesGenerator([n for n in
507 self._get_paths_for_status('added')], self)
507 self._get_paths_for_status('added')], self)
508
508
509 @LazyProperty
509 @LazyProperty
510 def changed(self):
510 def changed(self):
511 """
511 """
512 Returns list of modified ``FileNode`` objects.
512 Returns list of modified ``FileNode`` objects.
513 """
513 """
514 if not self.parents:
514 if not self.parents:
515 return []
515 return []
516 return ChangedFileNodesGenerator([n for n in
516 return ChangedFileNodesGenerator([n for n in
517 self._get_paths_for_status('modified')], self)
517 self._get_paths_for_status('modified')], self)
518
518
519 @LazyProperty
519 @LazyProperty
520 def removed(self):
520 def removed(self):
521 """
521 """
522 Returns list of removed ``FileNode`` objects.
522 Returns list of removed ``FileNode`` objects.
523 """
523 """
524 if not self.parents:
524 if not self.parents:
525 return []
525 return []
526 return RemovedFileNodesGenerator([n for n in
526 return RemovedFileNodesGenerator([n for n in
527 self._get_paths_for_status('deleted')], self)
527 self._get_paths_for_status('deleted')], self)
@@ -1,46 +1,45 b''
1 class LazyProperty(object):
1 class LazyProperty(object):
2 """
2 """
3 Decorator for easier creation of ``property`` from potentially expensive to
3 Decorator for easier creation of ``property`` from potentially expensive to
4 calculate attribute of the class.
4 calculate attribute of the class.
5
5
6 Usage::
6 Usage::
7
7
8 class Foo(object):
8 class Foo(object):
9 @LazyProperty
9 @LazyProperty
10 def bar(self):
10 def bar(self):
11 print 'Calculating self._bar'
11 print 'Calculating self._bar'
12 return 42
12 return 42
13
13
14 Taken from http://blog.pythonisito.com/2008/08/lazy-descriptors.html and
14 Taken from http://blog.pythonisito.com/2008/08/lazy-descriptors.html and
15 used widely.
15 used widely.
16 """
16 """
17
17
18 def __init__(self, func):
18 def __init__(self, func):
19 self._func = func
19 self._func = func
20 self.__module__ = func.__module__
20 self.__module__ = func.__module__
21 self.__name__ = func.__name__
21 self.__name__ = func.__name__
22 self.__doc__ = func.__doc__
22 self.__doc__ = func.__doc__
23
23
24 def __get__(self, obj, klass=None):
24 def __get__(self, obj, klass=None):
25 if obj is None:
25 if obj is None:
26 return self
26 return self
27 result = obj.__dict__[self.__name__] = self._func(obj)
27 result = obj.__dict__[self.__name__] = self._func(obj)
28 return result
28 return result
29
29
30 import threading
30 import threading
31
31
32
32
33 class ThreadLocalLazyProperty(LazyProperty):
33 class ThreadLocalLazyProperty(LazyProperty):
34 """
34 """
35 Same as above but uses thread local dict for cache storage.
35 Same as above but uses thread local dict for cache storage.
36 """
36 """
37
37
38 def __get__(self, obj, klass=None):
38 def __get__(self, obj, klass=None):
39 if obj is None:
39 if obj is None:
40 return self
40 return self
41 if not hasattr(obj, '__tl_dict__'):
41 if not hasattr(obj, '__tl_dict__'):
42 obj.__tl_dict__ = threading.local().__dict__
42 obj.__tl_dict__ = threading.local().__dict__
43
43
44 result = obj.__tl_dict__[self.__name__] = self._func(obj)
44 result = obj.__tl_dict__[self.__name__] = self._func(obj)
45 return result
45 return result
46
@@ -1,93 +1,93 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repositories defaults')} - ${c.rhodecode_name}
5 ${_('Repositories defaults')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${_('Defaults')}
11 ${_('Defaults')}
12 </%def>
12 </%def>
13
13
14 <%def name="page_nav()">
14 <%def name="page_nav()">
15 ${self.menu('admin')}
15 ${self.menu('admin')}
16 </%def>
16 </%def>
17
17
18 <%def name="main()">
18 <%def name="main()">
19 <div class="box">
19 <div class="box">
20 <!-- box / title -->
20 <!-- box / title -->
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 </div>
23 </div>
24
24
25 <h3>${_('Repositories defaults')}</h3>
25 <h3>${_('Repositories defaults')}</h3>
26
26
27 ${h.form(url('default', id='defaults'),method='put')}
27 ${h.form(url('default', id='defaults'),method='put')}
28 <div class="form">
28 <div class="form">
29 <!-- fields -->
29 <!-- fields -->
30
30
31 <div class="fields">
31 <div class="fields">
32
32
33 <div class="field">
33 <div class="field">
34 <div class="label">
34 <div class="label">
35 <label for="default_repo_type">${_('Type')}:</label>
35 <label for="default_repo_type">${_('Type')}:</label>
36 </div>
36 </div>
37 <div class="input">
37 <div class="input">
38 ${h.select('default_repo_type','hg',c.backends,class_="medium")}
38 ${h.select('default_repo_type','hg',c.backends,class_="medium")}
39 </div>
39 </div>
40 </div>
40 </div>
41
41
42 <div class="field">
42 <div class="field">
43 <div class="label label-checkbox">
43 <div class="label label-checkbox">
44 <label for="default_repo_private">${_('Private repository')}:</label>
44 <label for="default_repo_private">${_('Private repository')}:</label>
45 </div>
45 </div>
46 <div class="checkboxes">
46 <div class="checkboxes">
47 ${h.checkbox('default_repo_private',value="True")}
47 ${h.checkbox('default_repo_private',value="True")}
48 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
48 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
49 </div>
49 </div>
50 </div>
50 </div>
51
51
52
52
53 <div class="field">
53 <div class="field">
54 <div class="label label-checkbox">
54 <div class="label label-checkbox">
55 <label for="default_repo_enable_statistics">${_('Enable statistics')}:</label>
55 <label for="default_repo_enable_statistics">${_('Enable statistics')}:</label>
56 </div>
56 </div>
57 <div class="checkboxes">
57 <div class="checkboxes">
58 ${h.checkbox('default_repo_enable_statistics',value="True")}
58 ${h.checkbox('default_repo_enable_statistics',value="True")}
59 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
59 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
60 </div>
60 </div>
61 </div>
61 </div>
62
62
63 <div class="field">
63 <div class="field">
64 <div class="label label-checkbox">
64 <div class="label label-checkbox">
65 <label for="default_repo_enable_downloads">${_('Enable downloads')}:</label>
65 <label for="default_repo_enable_downloads">${_('Enable downloads')}:</label>
66 </div>
66 </div>
67 <div class="checkboxes">
67 <div class="checkboxes">
68 ${h.checkbox('default_repo_enable_downloads',value="True")}
68 ${h.checkbox('default_repo_enable_downloads',value="True")}
69 <span class="help-block">${_('Enable download menu on summary page.')}</span>
69 <span class="help-block">${_('Enable download menu on summary page.')}</span>
70 </div>
70 </div>
71 </div>
71 </div>
72
72
73 <div class="field">
73 <div class="field">
74 <div class="label label-checkbox">
74 <div class="label label-checkbox">
75 <label for="default_repo_enable_locking">${_('Enable locking')}:</label>
75 <label for="default_repo_enable_locking">${_('Enable locking')}:</label>
76 </div>
76 </div>
77 <div class="checkboxes">
77 <div class="checkboxes">
78 ${h.checkbox('default_repo_enable_locking',value="True")}
78 ${h.checkbox('default_repo_enable_locking',value="True")}
79 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
79 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
80 </div>
80 </div>
81 </div>
81 </div>
82
82
83 <div class="buttons">
83 <div class="buttons">
84 ${h.submit('save',_('Save'),class_="ui-btn large")}
84 ${h.submit('save',_('Save'),class_="ui-btn large")}
85 </div>
85 </div>
86 </div>
86 </div>
87 </div>
87 </div>
88 ${h.end_form()}
88 ${h.end_form()}
89
89
90 ##<h3>${_('Groups defaults')}</h3>
90 ##<h3>${_('Groups defaults')}</h3>
91
91
92 </div>
92 </div>
93 </%def>
93 </%def>
@@ -1,98 +1,98 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Permissions administration')} - ${c.rhodecode_name}
5 ${_('Permissions administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${_('Permissions')}
11 ${_('Permissions')}
12 </%def>
12 </%def>
13
13
14 <%def name="page_nav()">
14 <%def name="page_nav()">
15 ${self.menu('admin')}
15 ${self.menu('admin')}
16 </%def>
16 </%def>
17
17
18 <%def name="main()">
18 <%def name="main()">
19 <div class="box">
19 <div class="box">
20 <!-- box / title -->
20 <!-- box / title -->
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 </div>
23 </div>
24 <h3>${_('Default permissions')}</h3>
24 <h3>${_('Default permissions')}</h3>
25 ${h.form(url('permission', id='default'),method='put')}
25 ${h.form(url('permission', id='default'),method='put')}
26 <div class="form">
26 <div class="form">
27 <!-- fields -->
27 <!-- fields -->
28 <div class="fields">
28 <div class="fields">
29 <div class="field">
29 <div class="field">
30 <div class="label label-checkbox">
30 <div class="label label-checkbox">
31 <label for="anonymous">${_('Anonymous access')}:</label>
31 <label for="anonymous">${_('Anonymous access')}:</label>
32 </div>
32 </div>
33 <div class="checkboxes">
33 <div class="checkboxes">
34 <div class="checkbox">
34 <div class="checkbox">
35 ${h.checkbox('anonymous',True)}
35 ${h.checkbox('anonymous',True)}
36 </div>
36 </div>
37 </div>
37 </div>
38 </div>
38 </div>
39 <div class="field">
39 <div class="field">
40 <div class="label">
40 <div class="label">
41 <label for="default_repo_perm">${_('Repository')}:</label>
41 <label for="default_repo_perm">${_('Repository')}:</label>
42 </div>
42 </div>
43 <div class="select">
43 <div class="select">
44 ${h.select('default_repo_perm','',c.repo_perms_choices)}
44 ${h.select('default_repo_perm','',c.repo_perms_choices)}
45
45
46 ${h.checkbox('overwrite_default_repo','true')}
46 ${h.checkbox('overwrite_default_repo','true')}
47 <label for="overwrite_default_repo">
47 <label for="overwrite_default_repo">
48 <span class="tooltip"
48 <span class="tooltip"
49 title="${h.tooltip(_('All default permissions on each repository will be reset to choosen permission, note that all custom default permission on repositories will be lost'))}">
49 title="${h.tooltip(_('All default permissions on each repository will be reset to choosen permission, note that all custom default permission on repositories will be lost'))}">
50 ${_('overwrite existing settings')}</span> </label>
50 ${_('overwrite existing settings')}</span> </label>
51 </div>
51 </div>
52 </div>
52 </div>
53 <div class="field">
53 <div class="field">
54 <div class="label">
54 <div class="label">
55 <label for="default_group_perm">${_('Repository group')}:</label>
55 <label for="default_group_perm">${_('Repository group')}:</label>
56 </div>
56 </div>
57 <div class="select">
57 <div class="select">
58 ${h.select('default_group_perm','',c.group_perms_choices)}
58 ${h.select('default_group_perm','',c.group_perms_choices)}
59 ${h.checkbox('overwrite_default_group','true')}
59 ${h.checkbox('overwrite_default_group','true')}
60 <label for="overwrite_default_group">
60 <label for="overwrite_default_group">
61 <span class="tooltip"
61 <span class="tooltip"
62 title="${h.tooltip(_('All default permissions on each repository group will be reset to choosen permission, note that all custom default permission on repositories group will be lost'))}">
62 title="${h.tooltip(_('All default permissions on each repository group will be reset to choosen permission, note that all custom default permission on repositories group will be lost'))}">
63 ${_('overwrite existing settings')}</span> </label>
63 ${_('overwrite existing settings')}</span> </label>
64
64
65 </div>
65 </div>
66 </div>
66 </div>
67 <div class="field">
67 <div class="field">
68 <div class="label">
68 <div class="label">
69 <label for="default_register">${_('Registration')}:</label>
69 <label for="default_register">${_('Registration')}:</label>
70 </div>
70 </div>
71 <div class="select">
71 <div class="select">
72 ${h.select('default_register','',c.register_choices)}
72 ${h.select('default_register','',c.register_choices)}
73 </div>
73 </div>
74 </div>
74 </div>
75 <div class="field">
75 <div class="field">
76 <div class="label">
76 <div class="label">
77 <label for="default_create">${_('Repository creation')}:</label>
77 <label for="default_create">${_('Repository creation')}:</label>
78 </div>
78 </div>
79 <div class="select">
79 <div class="select">
80 ${h.select('default_create','',c.create_choices)}
80 ${h.select('default_create','',c.create_choices)}
81 </div>
81 </div>
82 </div>
82 </div>
83 <div class="field">
83 <div class="field">
84 <div class="label">
84 <div class="label">
85 <label for="default_fork">${_('Repository forking')}:</label>
85 <label for="default_fork">${_('Repository forking')}:</label>
86 </div>
86 </div>
87 <div class="select">
87 <div class="select">
88 ${h.select('default_fork','',c.fork_choices)}
88 ${h.select('default_fork','',c.fork_choices)}
89 </div>
89 </div>
90 </div>
90 </div>
91 <div class="buttons">
91 <div class="buttons">
92 ${h.submit('set',_('set'),class_="ui-btn large")}
92 ${h.submit('set',_('set'),class_="ui-btn large")}
93 </div>
93 </div>
94 </div>
94 </div>
95 </div>
95 </div>
96 ${h.end_form()}
96 ${h.end_form()}
97 </div>
97 </div>
98 </%def>
98 </%def>
@@ -1,77 +1,77 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 ${h.form(url('repos'))}
3 ${h.form(url('repos'))}
4 <div class="form">
4 <div class="form">
5 <!-- fields -->
5 <!-- fields -->
6 <div class="fields">
6 <div class="fields">
7 <div class="field">
7 <div class="field">
8 <div class="label">
8 <div class="label">
9 <label for="repo_name">${_('Name')}:</label>
9 <label for="repo_name">${_('Name')}:</label>
10 </div>
10 </div>
11 <div class="input">
11 <div class="input">
12 ${h.text('repo_name',c.new_repo,class_="small")}
12 ${h.text('repo_name',c.new_repo,class_="small")}
13 %if not h.HasPermissionAll('hg.admin')('repo create form'):
13 %if not h.HasPermissionAll('hg.admin')('repo create form'):
14 ${h.hidden('user_created',True)}
14 ${h.hidden('user_created',True)}
15 %endif
15 %endif
16 </div>
16 </div>
17 </div>
17 </div>
18 <div class="field">
18 <div class="field">
19 <div class="label">
19 <div class="label">
20 <label for="clone_uri">${_('Clone from')}:</label>
20 <label for="clone_uri">${_('Clone from')}:</label>
21 </div>
21 </div>
22 <div class="input">
22 <div class="input">
23 ${h.text('clone_uri',class_="small")}
23 ${h.text('clone_uri',class_="small")}
24 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
24 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
25 </div>
25 </div>
26 </div>
26 </div>
27 <div class="field">
27 <div class="field">
28 <div class="label">
28 <div class="label">
29 <label for="repo_group">${_('Repository group')}:</label>
29 <label for="repo_group">${_('Repository group')}:</label>
30 </div>
30 </div>
31 <div class="input">
31 <div class="input">
32 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
32 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
33 <span class="help-block">${_('Optionaly select a group to put this repository into.')}</span>
33 <span class="help-block">${_('Optionaly select a group to put this repository into.')}</span>
34 </div>
34 </div>
35 </div>
35 </div>
36 <div class="field">
36 <div class="field">
37 <div class="label">
37 <div class="label">
38 <label for="repo_type">${_('Type')}:</label>
38 <label for="repo_type">${_('Type')}:</label>
39 </div>
39 </div>
40 <div class="input">
40 <div class="input">
41 ${h.select('repo_type','hg',c.backends,class_="small")}
41 ${h.select('repo_type','hg',c.backends,class_="small")}
42 <span class="help-block">${_('Type of repository to create.')}</span>
42 <span class="help-block">${_('Type of repository to create.')}</span>
43 </div>
43 </div>
44 </div>
44 </div>
45 <div class="field">
45 <div class="field">
46 <div class="label">
46 <div class="label">
47 <label for="repo_landing_rev">${_('Landing revision')}:</label>
47 <label for="repo_landing_rev">${_('Landing revision')}:</label>
48 </div>
48 </div>
49 <div class="input">
49 <div class="input">
50 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
50 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
51 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
51 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
52 </div>
52 </div>
53 </div>
53 </div>
54 <div class="field">
54 <div class="field">
55 <div class="label label-textarea">
55 <div class="label label-textarea">
56 <label for="repo_description">${_('Description')}:</label>
56 <label for="repo_description">${_('Description')}:</label>
57 </div>
57 </div>
58 <div class="textarea text-area editor">
58 <div class="textarea text-area editor">
59 ${h.textarea('repo_description')}
59 ${h.textarea('repo_description')}
60 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
60 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
61 </div>
61 </div>
62 </div>
62 </div>
63 <div class="field">
63 <div class="field">
64 <div class="label label-checkbox">
64 <div class="label label-checkbox">
65 <label for="repo_private">${_('Private repository')}:</label>
65 <label for="repo_private">${_('Private repository')}:</label>
66 </div>
66 </div>
67 <div class="checkboxes">
67 <div class="checkboxes">
68 ${h.checkbox('repo_private',value="True")}
68 ${h.checkbox('repo_private',value="True")}
69 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
69 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
70 </div>
70 </div>
71 </div>
71 </div>
72 <div class="buttons">
72 <div class="buttons">
73 ${h.submit('add',_('add'),class_="ui-btn large")}
73 ${h.submit('add',_('add'),class_="ui-btn large")}
74 </div>
74 </div>
75 </div>
75 </div>
76 </div>
76 </div>
77 ${h.end_form()}
77 ${h.end_form()}
@@ -1,186 +1,184 b''
1 """Pylons application test package
1 """Pylons application test package
2
2
3 This package assumes the Pylons environment is already loaded, such as
3 This package assumes the Pylons environment is already loaded, such as
4 when this script is imported from the `nosetests --with-pylons=test.ini`
4 when this script is imported from the `nosetests --with-pylons=test.ini`
5 command.
5 command.
6
6
7 This module initializes the application via ``websetup`` (`paster
7 This module initializes the application via ``websetup`` (`paster
8 setup-app`) and provides the base testing objects.
8 setup-app`) and provides the base testing objects.
9 """
9 """
10 import os
10 import os
11 import time
11 import time
12 import logging
12 import logging
13 import datetime
13 import datetime
14 import hashlib
14 import hashlib
15 import tempfile
15 import tempfile
16 from os.path import join as jn
16 from os.path import join as jn
17
17
18 from unittest import TestCase
18 from unittest import TestCase
19 from tempfile import _RandomNameSequence
19 from tempfile import _RandomNameSequence
20
20
21 from paste.deploy import loadapp
21 from paste.deploy import loadapp
22 from paste.script.appinstall import SetupCommand
22 from paste.script.appinstall import SetupCommand
23 from pylons import config, url
23 from pylons import config, url
24 from routes.util import URLGenerator
24 from routes.util import URLGenerator
25 from webtest import TestApp
25 from webtest import TestApp
26
26
27 from rhodecode import is_windows
27 from rhodecode import is_windows
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.db import User
29 from rhodecode.model.db import User
30 from rhodecode.tests.nose_parametrized import parameterized
30 from rhodecode.tests.nose_parametrized import parameterized
31
31
32 import pylons.test
32 import pylons.test
33
33
34
34
35 os.environ['TZ'] = 'UTC'
35 os.environ['TZ'] = 'UTC'
36 if not is_windows:
36 if not is_windows:
37 time.tzset()
37 time.tzset()
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41 __all__ = [
41 __all__ = [
42 'parameterized', 'environ', 'url', 'get_new_dir', 'TestController',
42 'parameterized', 'environ', 'url', 'get_new_dir', 'TestController',
43 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO',
43 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO',
44 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
44 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
45 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
45 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
46 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
46 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
47 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
47 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
48 'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
48 'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
49 'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'HG_REMOTE_REPO',
49 'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'HG_REMOTE_REPO',
50 'GIT_REMOTE_REPO', 'SCM_TESTS', '_get_repo_create_params'
50 'GIT_REMOTE_REPO', 'SCM_TESTS', '_get_repo_create_params'
51 ]
51 ]
52
52
53 # Invoke websetup with the current config file
53 # Invoke websetup with the current config file
54 # SetupCommand('setup-app').run([config_file])
54 # SetupCommand('setup-app').run([config_file])
55
55
56 ##RUNNING DESIRED TESTS
56 ##RUNNING DESIRED TESTS
57 # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
57 # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
58 # nosetests --pdb --pdb-failures
58 # nosetests --pdb --pdb-failures
59 # nosetests --with-coverage --cover-package=rhodecode.model.validators rhodecode.tests.test_validators
59 # nosetests --with-coverage --cover-package=rhodecode.model.validators rhodecode.tests.test_validators
60 environ = {}
60 environ = {}
61
61
62 #SOME GLOBALS FOR TESTS
62 #SOME GLOBALS FOR TESTS
63
63
64 TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
64 TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
65 TEST_USER_ADMIN_LOGIN = 'test_admin'
65 TEST_USER_ADMIN_LOGIN = 'test_admin'
66 TEST_USER_ADMIN_PASS = 'test12'
66 TEST_USER_ADMIN_PASS = 'test12'
67 TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
67 TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
68
68
69 TEST_USER_REGULAR_LOGIN = 'test_regular'
69 TEST_USER_REGULAR_LOGIN = 'test_regular'
70 TEST_USER_REGULAR_PASS = 'test12'
70 TEST_USER_REGULAR_PASS = 'test12'
71 TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
71 TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
72
72
73 TEST_USER_REGULAR2_LOGIN = 'test_regular2'
73 TEST_USER_REGULAR2_LOGIN = 'test_regular2'
74 TEST_USER_REGULAR2_PASS = 'test12'
74 TEST_USER_REGULAR2_PASS = 'test12'
75 TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
75 TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
76
76
77 HG_REPO = 'vcs_test_hg'
77 HG_REPO = 'vcs_test_hg'
78 GIT_REPO = 'vcs_test_git'
78 GIT_REPO = 'vcs_test_git'
79
79
80 NEW_HG_REPO = 'vcs_test_hg_new'
80 NEW_HG_REPO = 'vcs_test_hg_new'
81 NEW_GIT_REPO = 'vcs_test_git_new'
81 NEW_GIT_REPO = 'vcs_test_git_new'
82
82
83 HG_FORK = 'vcs_test_hg_fork'
83 HG_FORK = 'vcs_test_hg_fork'
84 GIT_FORK = 'vcs_test_git_fork'
84 GIT_FORK = 'vcs_test_git_fork'
85
85
86 ## VCS
86 ## VCS
87 SCM_TESTS = ['hg', 'git']
87 SCM_TESTS = ['hg', 'git']
88 uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
88 uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
89
89
90 GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git'
90 GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git'
91
91
92 TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
92 TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
93 TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcsgitclone%s' % uniq_suffix)
93 TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcsgitclone%s' % uniq_suffix)
94 TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, 'vcsgitpull%s' % uniq_suffix)
94 TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, 'vcsgitpull%s' % uniq_suffix)
95
95
96
96
97 HG_REMOTE_REPO = 'http://bitbucket.org/marcinkuzminski/vcs'
97 HG_REMOTE_REPO = 'http://bitbucket.org/marcinkuzminski/vcs'
98
98
99 TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO)
99 TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO)
100 TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcshgclone%s' % uniq_suffix)
100 TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcshgclone%s' % uniq_suffix)
101 TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, 'vcshgpull%s' % uniq_suffix)
101 TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, 'vcshgpull%s' % uniq_suffix)
102
102
103 TEST_DIR = tempfile.gettempdir()
103 TEST_DIR = tempfile.gettempdir()
104 TEST_REPO_PREFIX = 'vcs-test'
104 TEST_REPO_PREFIX = 'vcs-test'
105
105
106 # cached repos if any !
106 # cached repos if any !
107 # comment out to get some other repos from bb or github
107 # comment out to get some other repos from bb or github
108 GIT_REMOTE_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
108 GIT_REMOTE_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
109 HG_REMOTE_REPO = jn(TESTS_TMP_PATH, HG_REPO)
109 HG_REMOTE_REPO = jn(TESTS_TMP_PATH, HG_REPO)
110
110
111
111
112 def get_new_dir(title):
112 def get_new_dir(title):
113 """
113 """
114 Returns always new directory path.
114 Returns always new directory path.
115 """
115 """
116 from rhodecode.tests.vcs.utils import get_normalized_path
116 from rhodecode.tests.vcs.utils import get_normalized_path
117 name = TEST_REPO_PREFIX
117 name = TEST_REPO_PREFIX
118 if title:
118 if title:
119 name = '-'.join((name, title))
119 name = '-'.join((name, title))
120 hex = hashlib.sha1(str(time.time())).hexdigest()
120 hex = hashlib.sha1(str(time.time())).hexdigest()
121 name = '-'.join((name, hex))
121 name = '-'.join((name, hex))
122 path = os.path.join(TEST_DIR, name)
122 path = os.path.join(TEST_DIR, name)
123 return get_normalized_path(path)
123 return get_normalized_path(path)
124
124
125
125
126 class TestController(TestCase):
126 class TestController(TestCase):
127
127
128 def __init__(self, *args, **kwargs):
128 def __init__(self, *args, **kwargs):
129 wsgiapp = pylons.test.pylonsapp
129 wsgiapp = pylons.test.pylonsapp
130 config = wsgiapp.config
130 config = wsgiapp.config
131
131
132 self.app = TestApp(wsgiapp)
132 self.app = TestApp(wsgiapp)
133 url._push_object(URLGenerator(config['routes.map'], environ))
133 url._push_object(URLGenerator(config['routes.map'], environ))
134 self.Session = Session
134 self.Session = Session
135 self.index_location = config['app_conf']['index_dir']
135 self.index_location = config['app_conf']['index_dir']
136 TestCase.__init__(self, *args, **kwargs)
136 TestCase.__init__(self, *args, **kwargs)
137
137
138 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
138 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
139 password=TEST_USER_ADMIN_PASS):
139 password=TEST_USER_ADMIN_PASS):
140 self._logged_username = username
140 self._logged_username = username
141 response = self.app.post(url(controller='login', action='index'),
141 response = self.app.post(url(controller='login', action='index'),
142 {'username': username,
142 {'username': username,
143 'password': password})
143 'password': password})
144
144
145 if 'invalid user name' in response.body:
145 if 'invalid user name' in response.body:
146 self.fail('could not login using %s %s' % (username, password))
146 self.fail('could not login using %s %s' % (username, password))
147
147
148 self.assertEqual(response.status, '302 Found')
148 self.assertEqual(response.status, '302 Found')
149 ses = response.session['rhodecode_user']
149 ses = response.session['rhodecode_user']
150 self.assertEqual(ses.get('username'), username)
150 self.assertEqual(ses.get('username'), username)
151 response = response.follow()
151 response = response.follow()
152 self.assertEqual(ses.get('is_authenticated'), True)
152 self.assertEqual(ses.get('is_authenticated'), True)
153
153
154 return response.session['rhodecode_user']
154 return response.session['rhodecode_user']
155
155
156 def _get_logged_user(self):
156 def _get_logged_user(self):
157 return User.get_by_username(self._logged_username)
157 return User.get_by_username(self._logged_username)
158
158
159 def checkSessionFlash(self, response, msg):
159 def checkSessionFlash(self, response, msg):
160 self.assertTrue('flash' in response.session)
160 self.assertTrue('flash' in response.session)
161 if not msg in response.session['flash'][0][1]:
161 if not msg in response.session['flash'][0][1]:
162 self.fail(
162 self.fail(
163 'msg `%s` not found in session flash: got `%s` instead' % (
163 'msg `%s` not found in session flash: got `%s` instead' % (
164 msg, response.session['flash'])
164 msg, response.session['flash'])
165 )
165 )
166
166
167
167
168 ## HELPERS ##
168 ## HELPERS ##
169
169
170 def _get_repo_create_params(**custom):
170 def _get_repo_create_params(**custom):
171 defs = {
171 defs = {
172 'repo_name': None,
172 'repo_name': None,
173 'repo_type': 'hg',
173 'repo_type': 'hg',
174 'clone_uri': '',
174 'clone_uri': '',
175 'repo_group': '',
175 'repo_group': '',
176 'repo_description': 'DESC',
176 'repo_description': 'DESC',
177 'repo_private': False,
177 'repo_private': False,
178 'repo_landing_rev': 'tip'
178 'repo_landing_rev': 'tip'
179 }
179 }
180 defs.update(custom)
180 defs.update(custom)
181 if 'repo_name_full' not in custom:
181 if 'repo_name_full' not in custom:
182 defs.update({'repo_name_full': defs['repo_name']})
182 defs.update({'repo_name_full': defs['repo_name']})
183
183
184 return defs
184 return defs
185
186
@@ -1,326 +1,326 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 import os
3 import os
4 from rhodecode.lib import vcs
4 from rhodecode.lib import vcs
5
5
6 from rhodecode.model.db import Repository, RepoGroup
6 from rhodecode.model.db import Repository, RepoGroup
7 from rhodecode.tests import *
7 from rhodecode.tests import *
8 from rhodecode.model.repos_group import ReposGroupModel
8 from rhodecode.model.repos_group import ReposGroupModel
9 from rhodecode.model.repo import RepoModel
9 from rhodecode.model.repo import RepoModel
10
10
11
11
12 class TestAdminReposController(TestController):
12 class TestAdminReposController(TestController):
13
13
14 def __make_repo(self):
14 def __make_repo(self):
15 pass
15 pass
16
16
17 def test_index(self):
17 def test_index(self):
18 self.log_user()
18 self.log_user()
19 response = self.app.get(url('repos'))
19 response = self.app.get(url('repos'))
20 # Test response...
20 # Test response...
21
21
22 def test_index_as_xml(self):
22 def test_index_as_xml(self):
23 response = self.app.get(url('formatted_repos', format='xml'))
23 response = self.app.get(url('formatted_repos', format='xml'))
24
24
25 def test_create_hg(self):
25 def test_create_hg(self):
26 self.log_user()
26 self.log_user()
27 repo_name = NEW_HG_REPO
27 repo_name = NEW_HG_REPO
28 description = 'description for newly created repo'
28 description = 'description for newly created repo'
29 response = self.app.post(url('repos'),
29 response = self.app.post(url('repos'),
30 _get_repo_create_params(repo_private=False,
30 _get_repo_create_params(repo_private=False,
31 repo_name=repo_name,
31 repo_name=repo_name,
32 repo_description=description))
32 repo_description=description))
33 self.checkSessionFlash(response,
33 self.checkSessionFlash(response,
34 'created repository %s' % (repo_name))
34 'created repository %s' % (repo_name))
35
35
36 #test if the repo was created in the database
36 #test if the repo was created in the database
37 new_repo = self.Session().query(Repository)\
37 new_repo = self.Session().query(Repository)\
38 .filter(Repository.repo_name == repo_name).one()
38 .filter(Repository.repo_name == repo_name).one()
39
39
40 self.assertEqual(new_repo.repo_name, repo_name)
40 self.assertEqual(new_repo.repo_name, repo_name)
41 self.assertEqual(new_repo.description, description)
41 self.assertEqual(new_repo.description, description)
42
42
43 #test if repository is visible in the list ?
43 #test if repository is visible in the list ?
44 response = response.follow()
44 response = response.follow()
45
45
46 response.mustcontain(repo_name)
46 response.mustcontain(repo_name)
47
47
48 #test if repository was created on filesystem
48 #test if repository was created on filesystem
49 try:
49 try:
50 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
50 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
51 except:
51 except:
52 self.fail('no repo %s in filesystem' % repo_name)
52 self.fail('no repo %s in filesystem' % repo_name)
53
53
54 def test_create_hg_non_ascii(self):
54 def test_create_hg_non_ascii(self):
55 self.log_user()
55 self.log_user()
56 non_ascii = "Δ…Δ™Ε‚"
56 non_ascii = "Δ…Δ™Ε‚"
57 repo_name = "%s%s" % (NEW_HG_REPO, non_ascii)
57 repo_name = "%s%s" % (NEW_HG_REPO, non_ascii)
58 repo_name_unicode = repo_name.decode('utf8')
58 repo_name_unicode = repo_name.decode('utf8')
59 description = 'description for newly created repo' + non_ascii
59 description = 'description for newly created repo' + non_ascii
60 description_unicode = description.decode('utf8')
60 description_unicode = description.decode('utf8')
61 private = False
61 private = False
62 response = self.app.post(url('repos'),
62 response = self.app.post(url('repos'),
63 _get_repo_create_params(repo_private=False,
63 _get_repo_create_params(repo_private=False,
64 repo_name=repo_name,
64 repo_name=repo_name,
65 repo_description=description))
65 repo_description=description))
66 self.checkSessionFlash(response,
66 self.checkSessionFlash(response,
67 'created repository %s' % (repo_name_unicode))
67 'created repository %s' % (repo_name_unicode))
68
68
69 #test if the repo was created in the database
69 #test if the repo was created in the database
70 new_repo = self.Session().query(Repository)\
70 new_repo = self.Session().query(Repository)\
71 .filter(Repository.repo_name == repo_name_unicode).one()
71 .filter(Repository.repo_name == repo_name_unicode).one()
72
72
73 self.assertEqual(new_repo.repo_name, repo_name_unicode)
73 self.assertEqual(new_repo.repo_name, repo_name_unicode)
74 self.assertEqual(new_repo.description, description_unicode)
74 self.assertEqual(new_repo.description, description_unicode)
75
75
76 #test if repository is visible in the list ?
76 #test if repository is visible in the list ?
77 response = response.follow()
77 response = response.follow()
78
78
79 response.mustcontain(repo_name)
79 response.mustcontain(repo_name)
80
80
81 #test if repository was created on filesystem
81 #test if repository was created on filesystem
82 try:
82 try:
83 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
83 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
84 except:
84 except:
85 self.fail('no repo %s in filesystem' % repo_name)
85 self.fail('no repo %s in filesystem' % repo_name)
86
86
87 def test_create_hg_in_group(self):
87 def test_create_hg_in_group(self):
88 self.log_user()
88 self.log_user()
89
89
90 ## create GROUP
90 ## create GROUP
91 group_name = 'sometest'
91 group_name = 'sometest'
92 gr = ReposGroupModel().create(group_name=group_name,
92 gr = ReposGroupModel().create(group_name=group_name,
93 group_description='test',)
93 group_description='test',)
94 self.Session().commit()
94 self.Session().commit()
95
95
96 repo_name = 'ingroup'
96 repo_name = 'ingroup'
97 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
97 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
98 description = 'description for newly created repo'
98 description = 'description for newly created repo'
99 response = self.app.post(url('repos'),
99 response = self.app.post(url('repos'),
100 _get_repo_create_params(repo_private=False,
100 _get_repo_create_params(repo_private=False,
101 repo_name=repo_name,
101 repo_name=repo_name,
102 repo_description=description,
102 repo_description=description,
103 repo_group=gr.group_id,))
103 repo_group=gr.group_id,))
104
104
105 self.checkSessionFlash(response,
105 self.checkSessionFlash(response,
106 'created repository %s' % (repo_name))
106 'created repository %s' % (repo_name))
107
107
108 #test if the repo was created in the database
108 #test if the repo was created in the database
109 new_repo = self.Session().query(Repository)\
109 new_repo = self.Session().query(Repository)\
110 .filter(Repository.repo_name == repo_name_full).one()
110 .filter(Repository.repo_name == repo_name_full).one()
111
111
112 self.assertEqual(new_repo.repo_name, repo_name_full)
112 self.assertEqual(new_repo.repo_name, repo_name_full)
113 self.assertEqual(new_repo.description, description)
113 self.assertEqual(new_repo.description, description)
114
114
115 #test if repository is visible in the list ?
115 #test if repository is visible in the list ?
116 response = response.follow()
116 response = response.follow()
117
117
118 response.mustcontain(repo_name_full)
118 response.mustcontain(repo_name_full)
119
119
120 #test if repository was created on filesystem
120 #test if repository was created on filesystem
121 try:
121 try:
122 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
122 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
123 except:
123 except:
124 ReposGroupModel().delete(group_name)
124 ReposGroupModel().delete(group_name)
125 self.Session().commit()
125 self.Session().commit()
126 self.fail('no repo %s in filesystem' % repo_name)
126 self.fail('no repo %s in filesystem' % repo_name)
127
127
128 RepoModel().delete(repo_name_full)
128 RepoModel().delete(repo_name_full)
129 ReposGroupModel().delete(group_name)
129 ReposGroupModel().delete(group_name)
130 self.Session().commit()
130 self.Session().commit()
131
131
132 def test_create_git(self):
132 def test_create_git(self):
133 self.log_user()
133 self.log_user()
134 repo_name = NEW_GIT_REPO
134 repo_name = NEW_GIT_REPO
135 description = 'description for newly created repo'
135 description = 'description for newly created repo'
136
136
137 response = self.app.post(url('repos'),
137 response = self.app.post(url('repos'),
138 _get_repo_create_params(repo_private=False,
138 _get_repo_create_params(repo_private=False,
139 repo_type='git',
139 repo_type='git',
140 repo_name=repo_name,
140 repo_name=repo_name,
141 repo_description=description))
141 repo_description=description))
142 self.checkSessionFlash(response,
142 self.checkSessionFlash(response,
143 'created repository %s' % (repo_name))
143 'created repository %s' % (repo_name))
144
144
145 #test if the repo was created in the database
145 #test if the repo was created in the database
146 new_repo = self.Session().query(Repository)\
146 new_repo = self.Session().query(Repository)\
147 .filter(Repository.repo_name == repo_name).one()
147 .filter(Repository.repo_name == repo_name).one()
148
148
149 self.assertEqual(new_repo.repo_name, repo_name)
149 self.assertEqual(new_repo.repo_name, repo_name)
150 self.assertEqual(new_repo.description, description)
150 self.assertEqual(new_repo.description, description)
151
151
152 #test if repository is visible in the list ?
152 #test if repository is visible in the list ?
153 response = response.follow()
153 response = response.follow()
154
154
155 response.mustcontain(repo_name)
155 response.mustcontain(repo_name)
156
156
157 #test if repository was created on filesystem
157 #test if repository was created on filesystem
158 try:
158 try:
159 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
159 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
160 except:
160 except:
161 self.fail('no repo %s in filesystem' % repo_name)
161 self.fail('no repo %s in filesystem' % repo_name)
162
162
163 def test_create_git_non_ascii(self):
163 def test_create_git_non_ascii(self):
164 self.log_user()
164 self.log_user()
165 non_ascii = "Δ…Δ™Ε‚"
165 non_ascii = "Δ…Δ™Ε‚"
166 repo_name = "%s%s" % (NEW_GIT_REPO, non_ascii)
166 repo_name = "%s%s" % (NEW_GIT_REPO, non_ascii)
167 repo_name_unicode = repo_name.decode('utf8')
167 repo_name_unicode = repo_name.decode('utf8')
168 description = 'description for newly created repo' + non_ascii
168 description = 'description for newly created repo' + non_ascii
169 description_unicode = description.decode('utf8')
169 description_unicode = description.decode('utf8')
170 private = False
170 private = False
171 response = self.app.post(url('repos'),
171 response = self.app.post(url('repos'),
172 _get_repo_create_params(repo_private=False,
172 _get_repo_create_params(repo_private=False,
173 repo_type='git',
173 repo_type='git',
174 repo_name=repo_name,
174 repo_name=repo_name,
175 repo_description=description))
175 repo_description=description))
176
176
177 self.checkSessionFlash(response,
177 self.checkSessionFlash(response,
178 'created repository %s' % (repo_name_unicode))
178 'created repository %s' % (repo_name_unicode))
179
179
180 #test if the repo was created in the database
180 #test if the repo was created in the database
181 new_repo = self.Session().query(Repository)\
181 new_repo = self.Session().query(Repository)\
182 .filter(Repository.repo_name == repo_name_unicode).one()
182 .filter(Repository.repo_name == repo_name_unicode).one()
183
183
184 self.assertEqual(new_repo.repo_name, repo_name_unicode)
184 self.assertEqual(new_repo.repo_name, repo_name_unicode)
185 self.assertEqual(new_repo.description, description_unicode)
185 self.assertEqual(new_repo.description, description_unicode)
186
186
187 #test if repository is visible in the list ?
187 #test if repository is visible in the list ?
188 response = response.follow()
188 response = response.follow()
189
189
190 response.mustcontain(repo_name)
190 response.mustcontain(repo_name)
191
191
192 #test if repository was created on filesystem
192 #test if repository was created on filesystem
193 try:
193 try:
194 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
194 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
195 except:
195 except:
196 self.fail('no repo %s in filesystem' % repo_name)
196 self.fail('no repo %s in filesystem' % repo_name)
197
197
198 def test_new(self):
198 def test_new(self):
199 self.log_user()
199 self.log_user()
200 response = self.app.get(url('new_repo'))
200 response = self.app.get(url('new_repo'))
201
201
202 def test_new_as_xml(self):
202 def test_new_as_xml(self):
203 response = self.app.get(url('formatted_new_repo', format='xml'))
203 response = self.app.get(url('formatted_new_repo', format='xml'))
204
204
205 def test_update(self):
205 def test_update(self):
206 response = self.app.put(url('repo', repo_name=HG_REPO))
206 response = self.app.put(url('repo', repo_name=HG_REPO))
207
207
208 def test_update_browser_fakeout(self):
208 def test_update_browser_fakeout(self):
209 response = self.app.post(url('repo', repo_name=HG_REPO),
209 response = self.app.post(url('repo', repo_name=HG_REPO),
210 params=dict(_method='put'))
210 params=dict(_method='put'))
211
211
212 def test_delete_hg(self):
212 def test_delete_hg(self):
213 self.log_user()
213 self.log_user()
214 repo_name = 'vcs_test_new_to_delete'
214 repo_name = 'vcs_test_new_to_delete'
215 description = 'description for newly created repo'
215 description = 'description for newly created repo'
216 private = False
216 private = False
217 response = self.app.post(url('repos'),
217 response = self.app.post(url('repos'),
218 _get_repo_create_params(repo_private=False,
218 _get_repo_create_params(repo_private=False,
219 repo_type='hg',
219 repo_type='hg',
220 repo_name=repo_name,
220 repo_name=repo_name,
221 repo_description=description))
221 repo_description=description))
222
222
223 self.checkSessionFlash(response,
223 self.checkSessionFlash(response,
224 'created repository %s' % (repo_name))
224 'created repository %s' % (repo_name))
225
225
226 #test if the repo was created in the database
226 #test if the repo was created in the database
227 new_repo = self.Session().query(Repository)\
227 new_repo = self.Session().query(Repository)\
228 .filter(Repository.repo_name == repo_name).one()
228 .filter(Repository.repo_name == repo_name).one()
229
229
230 self.assertEqual(new_repo.repo_name, repo_name)
230 self.assertEqual(new_repo.repo_name, repo_name)
231 self.assertEqual(new_repo.description, description)
231 self.assertEqual(new_repo.description, description)
232
232
233 #test if repository is visible in the list ?
233 #test if repository is visible in the list ?
234 response = response.follow()
234 response = response.follow()
235
235
236 response.mustcontain(repo_name)
236 response.mustcontain(repo_name)
237
237
238 #test if repository was created on filesystem
238 #test if repository was created on filesystem
239 try:
239 try:
240 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
240 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
241 except:
241 except:
242 self.fail('no repo %s in filesystem' % repo_name)
242 self.fail('no repo %s in filesystem' % repo_name)
243
243
244 response = self.app.delete(url('repo', repo_name=repo_name))
244 response = self.app.delete(url('repo', repo_name=repo_name))
245
245
246 self.assertTrue('''deleted repository %s''' % (repo_name) in
246 self.assertTrue('''deleted repository %s''' % (repo_name) in
247 response.session['flash'][0])
247 response.session['flash'][0])
248
248
249 response.follow()
249 response.follow()
250
250
251 #check if repo was deleted from db
251 #check if repo was deleted from db
252 deleted_repo = self.Session().query(Repository)\
252 deleted_repo = self.Session().query(Repository)\
253 .filter(Repository.repo_name == repo_name).scalar()
253 .filter(Repository.repo_name == repo_name).scalar()
254
254
255 self.assertEqual(deleted_repo, None)
255 self.assertEqual(deleted_repo, None)
256
256
257 self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
257 self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
258 False)
258 False)
259
259
260 def test_delete_git(self):
260 def test_delete_git(self):
261 self.log_user()
261 self.log_user()
262 repo_name = 'vcs_test_new_to_delete'
262 repo_name = 'vcs_test_new_to_delete'
263 description = 'description for newly created repo'
263 description = 'description for newly created repo'
264 private = False
264 private = False
265 response = self.app.post(url('repos'),
265 response = self.app.post(url('repos'),
266 _get_repo_create_params(repo_private=False,
266 _get_repo_create_params(repo_private=False,
267 repo_type='git',
267 repo_type='git',
268 repo_name=repo_name,
268 repo_name=repo_name,
269 repo_description=description))
269 repo_description=description))
270
270
271 self.checkSessionFlash(response,
271 self.checkSessionFlash(response,
272 'created repository %s' % (repo_name))
272 'created repository %s' % (repo_name))
273
273
274 #test if the repo was created in the database
274 #test if the repo was created in the database
275 new_repo = self.Session().query(Repository)\
275 new_repo = self.Session().query(Repository)\
276 .filter(Repository.repo_name == repo_name).one()
276 .filter(Repository.repo_name == repo_name).one()
277
277
278 self.assertEqual(new_repo.repo_name, repo_name)
278 self.assertEqual(new_repo.repo_name, repo_name)
279 self.assertEqual(new_repo.description, description)
279 self.assertEqual(new_repo.description, description)
280
280
281 #test if repository is visible in the list ?
281 #test if repository is visible in the list ?
282 response = response.follow()
282 response = response.follow()
283
283
284 response.mustcontain(repo_name)
284 response.mustcontain(repo_name)
285
285
286 #test if repository was created on filesystem
286 #test if repository was created on filesystem
287 try:
287 try:
288 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
288 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
289 except:
289 except:
290 self.fail('no repo %s in filesystem' % repo_name)
290 self.fail('no repo %s in filesystem' % repo_name)
291
291
292 response = self.app.delete(url('repo', repo_name=repo_name))
292 response = self.app.delete(url('repo', repo_name=repo_name))
293
293
294 self.assertTrue('''deleted repository %s''' % (repo_name) in
294 self.assertTrue('''deleted repository %s''' % (repo_name) in
295 response.session['flash'][0])
295 response.session['flash'][0])
296
296
297 response.follow()
297 response.follow()
298
298
299 #check if repo was deleted from db
299 #check if repo was deleted from db
300 deleted_repo = self.Session().query(Repository)\
300 deleted_repo = self.Session().query(Repository)\
301 .filter(Repository.repo_name == repo_name).scalar()
301 .filter(Repository.repo_name == repo_name).scalar()
302
302
303 self.assertEqual(deleted_repo, None)
303 self.assertEqual(deleted_repo, None)
304
304
305 self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
305 self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
306 False)
306 False)
307
307
308 def test_delete_repo_with_group(self):
308 def test_delete_repo_with_group(self):
309 #TODO:
309 #TODO:
310 pass
310 pass
311
311
312 def test_delete_browser_fakeout(self):
312 def test_delete_browser_fakeout(self):
313 response = self.app.post(url('repo', repo_name=HG_REPO),
313 response = self.app.post(url('repo', repo_name=HG_REPO),
314 params=dict(_method='delete'))
314 params=dict(_method='delete'))
315
315
316 def test_show_hg(self):
316 def test_show_hg(self):
317 self.log_user()
317 self.log_user()
318 response = self.app.get(url('repo', repo_name=HG_REPO))
318 response = self.app.get(url('repo', repo_name=HG_REPO))
319
319
320 def test_show_git(self):
320 def test_show_git(self):
321 self.log_user()
321 self.log_user()
322 response = self.app.get(url('repo', repo_name=GIT_REPO))
322 response = self.app.get(url('repo', repo_name=GIT_REPO))
323
323
324
324
325 def test_edit(self):
325 def test_edit(self):
326 response = self.app.get(url('edit_repo', repo_name=HG_REPO))
326 response = self.app.get(url('edit_repo', repo_name=HG_REPO))
General Comments 0
You need to be logged in to leave comments. Login now