##// END OF EJS Templates
Minor changes
Mads Kiilerich -
r3717:6ff98871 beta
parent child Browse files
Show More
@@ -1,2136 +1,2136 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 import time
32 from collections import defaultdict
32 from collections import defaultdict
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48
48
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
51 from rhodecode.lib.compat import json
51 from rhodecode.lib.compat import json
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model.meta import Base, Session
54 from rhodecode.model.meta import Base, Session
55
55
56 URL_SEP = '/'
56 URL_SEP = '/'
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59 #==============================================================================
59 #==============================================================================
60 # BASE CLASSES
60 # BASE CLASSES
61 #==============================================================================
61 #==============================================================================
62
62
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64
64
65
65
66 class BaseModel(object):
66 class BaseModel(object):
67 """
67 """
68 Base Model for all classess
68 Base Model for all classess
69 """
69 """
70
70
71 @classmethod
71 @classmethod
72 def _get_keys(cls):
72 def _get_keys(cls):
73 """return column names for this model """
73 """return column names for this model """
74 return class_mapper(cls).c.keys()
74 return class_mapper(cls).c.keys()
75
75
76 def get_dict(self):
76 def get_dict(self):
77 """
77 """
78 return dict with keys and values corresponding
78 return dict with keys and values corresponding
79 to this model data """
79 to this model data """
80
80
81 d = {}
81 d = {}
82 for k in self._get_keys():
82 for k in self._get_keys():
83 d[k] = getattr(self, k)
83 d[k] = getattr(self, k)
84
84
85 # also use __json__() if present to get additional fields
85 # also use __json__() if present to get additional fields
86 _json_attr = getattr(self, '__json__', None)
86 _json_attr = getattr(self, '__json__', None)
87 if _json_attr:
87 if _json_attr:
88 # update with attributes from __json__
88 # update with attributes from __json__
89 if callable(_json_attr):
89 if callable(_json_attr):
90 _json_attr = _json_attr()
90 _json_attr = _json_attr()
91 for k, val in _json_attr.iteritems():
91 for k, val in _json_attr.iteritems():
92 d[k] = val
92 d[k] = val
93 return d
93 return d
94
94
95 def get_appstruct(self):
95 def get_appstruct(self):
96 """return list with keys and values tupples corresponding
96 """return list with keys and values tupples corresponding
97 to this model data """
97 to this model data """
98
98
99 l = []
99 l = []
100 for k in self._get_keys():
100 for k in self._get_keys():
101 l.append((k, getattr(self, k),))
101 l.append((k, getattr(self, k),))
102 return l
102 return l
103
103
104 def populate_obj(self, populate_dict):
104 def populate_obj(self, populate_dict):
105 """populate model with data from given populate_dict"""
105 """populate model with data from given populate_dict"""
106
106
107 for k in self._get_keys():
107 for k in self._get_keys():
108 if k in populate_dict:
108 if k in populate_dict:
109 setattr(self, k, populate_dict[k])
109 setattr(self, k, populate_dict[k])
110
110
111 @classmethod
111 @classmethod
112 def query(cls):
112 def query(cls):
113 return Session().query(cls)
113 return Session().query(cls)
114
114
115 @classmethod
115 @classmethod
116 def get(cls, id_):
116 def get(cls, id_):
117 if id_:
117 if id_:
118 return cls.query().get(id_)
118 return cls.query().get(id_)
119
119
120 @classmethod
120 @classmethod
121 def get_or_404(cls, id_):
121 def get_or_404(cls, id_):
122 try:
122 try:
123 id_ = int(id_)
123 id_ = int(id_)
124 except (TypeError, ValueError):
124 except (TypeError, ValueError):
125 raise HTTPNotFound
125 raise HTTPNotFound
126
126
127 res = cls.query().get(id_)
127 res = cls.query().get(id_)
128 if not res:
128 if not res:
129 raise HTTPNotFound
129 raise HTTPNotFound
130 return res
130 return res
131
131
132 @classmethod
132 @classmethod
133 def getAll(cls):
133 def getAll(cls):
134 # deprecated and left for backward compatibility
134 # deprecated and left for backward compatibility
135 return cls.get_all()
135 return cls.get_all()
136
136
137 @classmethod
137 @classmethod
138 def get_all(cls):
138 def get_all(cls):
139 return cls.query().all()
139 return cls.query().all()
140
140
141 @classmethod
141 @classmethod
142 def delete(cls, id_):
142 def delete(cls, id_):
143 obj = cls.query().get(id_)
143 obj = cls.query().get(id_)
144 Session().delete(obj)
144 Session().delete(obj)
145
145
146 def __repr__(self):
146 def __repr__(self):
147 if hasattr(self, '__unicode__'):
147 if hasattr(self, '__unicode__'):
148 # python repr needs to return str
148 # python repr needs to return str
149 return safe_str(self.__unicode__())
149 return safe_str(self.__unicode__())
150 return '<DB:%s>' % (self.__class__.__name__)
150 return '<DB:%s>' % (self.__class__.__name__)
151
151
152
152
153 class RhodeCodeSetting(Base, BaseModel):
153 class RhodeCodeSetting(Base, BaseModel):
154 __tablename__ = 'rhodecode_settings'
154 __tablename__ = 'rhodecode_settings'
155 __table_args__ = (
155 __table_args__ = (
156 UniqueConstraint('app_settings_name'),
156 UniqueConstraint('app_settings_name'),
157 {'extend_existing': True, 'mysql_engine': 'InnoDB',
157 {'extend_existing': True, 'mysql_engine': 'InnoDB',
158 'mysql_charset': 'utf8'}
158 'mysql_charset': 'utf8'}
159 )
159 )
160 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
160 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
161 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
161 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
162 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
162 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
163
163
164 def __init__(self, k='', v=''):
164 def __init__(self, k='', v=''):
165 self.app_settings_name = k
165 self.app_settings_name = k
166 self.app_settings_value = v
166 self.app_settings_value = v
167
167
168 @validates('_app_settings_value')
168 @validates('_app_settings_value')
169 def validate_settings_value(self, key, val):
169 def validate_settings_value(self, key, val):
170 assert type(val) == unicode
170 assert type(val) == unicode
171 return val
171 return val
172
172
173 @hybrid_property
173 @hybrid_property
174 def app_settings_value(self):
174 def app_settings_value(self):
175 v = self._app_settings_value
175 v = self._app_settings_value
176 if self.app_settings_name in ["ldap_active",
176 if self.app_settings_name in ["ldap_active",
177 "default_repo_enable_statistics",
177 "default_repo_enable_statistics",
178 "default_repo_enable_locking",
178 "default_repo_enable_locking",
179 "default_repo_private",
179 "default_repo_private",
180 "default_repo_enable_downloads"]:
180 "default_repo_enable_downloads"]:
181 v = str2bool(v)
181 v = str2bool(v)
182 return v
182 return v
183
183
184 @app_settings_value.setter
184 @app_settings_value.setter
185 def app_settings_value(self, val):
185 def app_settings_value(self, val):
186 """
186 """
187 Setter that will always make sure we use unicode in app_settings_value
187 Setter that will always make sure we use unicode in app_settings_value
188
188
189 :param val:
189 :param val:
190 """
190 """
191 self._app_settings_value = safe_unicode(val)
191 self._app_settings_value = safe_unicode(val)
192
192
193 def __unicode__(self):
193 def __unicode__(self):
194 return u"<%s('%s:%s')>" % (
194 return u"<%s('%s:%s')>" % (
195 self.__class__.__name__,
195 self.__class__.__name__,
196 self.app_settings_name, self.app_settings_value
196 self.app_settings_name, self.app_settings_value
197 )
197 )
198
198
199 @classmethod
199 @classmethod
200 def get_by_name(cls, key):
200 def get_by_name(cls, key):
201 return cls.query()\
201 return cls.query()\
202 .filter(cls.app_settings_name == key).scalar()
202 .filter(cls.app_settings_name == key).scalar()
203
203
204 @classmethod
204 @classmethod
205 def get_by_name_or_create(cls, key):
205 def get_by_name_or_create(cls, key):
206 res = cls.get_by_name(key)
206 res = cls.get_by_name(key)
207 if not res:
207 if not res:
208 res = cls(key)
208 res = cls(key)
209 return res
209 return res
210
210
211 @classmethod
211 @classmethod
212 def get_app_settings(cls, cache=False):
212 def get_app_settings(cls, cache=False):
213
213
214 ret = cls.query()
214 ret = cls.query()
215
215
216 if cache:
216 if cache:
217 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
217 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
218
218
219 if not ret:
219 if not ret:
220 raise Exception('Could not get application settings !')
220 raise Exception('Could not get application settings !')
221 settings = {}
221 settings = {}
222 for each in ret:
222 for each in ret:
223 settings['rhodecode_' + each.app_settings_name] = \
223 settings['rhodecode_' + each.app_settings_name] = \
224 each.app_settings_value
224 each.app_settings_value
225
225
226 return settings
226 return settings
227
227
228 @classmethod
228 @classmethod
229 def get_ldap_settings(cls, cache=False):
229 def get_ldap_settings(cls, cache=False):
230 ret = cls.query()\
230 ret = cls.query()\
231 .filter(cls.app_settings_name.startswith('ldap_')).all()
231 .filter(cls.app_settings_name.startswith('ldap_')).all()
232 fd = {}
232 fd = {}
233 for row in ret:
233 for row in ret:
234 fd.update({row.app_settings_name: row.app_settings_value})
234 fd.update({row.app_settings_name: row.app_settings_value})
235
235
236 return fd
236 return fd
237
237
238 @classmethod
238 @classmethod
239 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
239 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
240 ret = cls.query()\
240 ret = cls.query()\
241 .filter(cls.app_settings_name.startswith('default_')).all()
241 .filter(cls.app_settings_name.startswith('default_')).all()
242 fd = {}
242 fd = {}
243 for row in ret:
243 for row in ret:
244 key = row.app_settings_name
244 key = row.app_settings_name
245 if strip_prefix:
245 if strip_prefix:
246 key = remove_prefix(key, prefix='default_')
246 key = remove_prefix(key, prefix='default_')
247 fd.update({key: row.app_settings_value})
247 fd.update({key: row.app_settings_value})
248
248
249 return fd
249 return fd
250
250
251
251
252 class RhodeCodeUi(Base, BaseModel):
252 class RhodeCodeUi(Base, BaseModel):
253 __tablename__ = 'rhodecode_ui'
253 __tablename__ = 'rhodecode_ui'
254 __table_args__ = (
254 __table_args__ = (
255 UniqueConstraint('ui_key'),
255 UniqueConstraint('ui_key'),
256 {'extend_existing': True, 'mysql_engine': 'InnoDB',
256 {'extend_existing': True, 'mysql_engine': 'InnoDB',
257 'mysql_charset': 'utf8'}
257 'mysql_charset': 'utf8'}
258 )
258 )
259
259
260 HOOK_UPDATE = 'changegroup.update'
260 HOOK_UPDATE = 'changegroup.update'
261 HOOK_REPO_SIZE = 'changegroup.repo_size'
261 HOOK_REPO_SIZE = 'changegroup.repo_size'
262 HOOK_PUSH = 'changegroup.push_logger'
262 HOOK_PUSH = 'changegroup.push_logger'
263 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
263 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
264 HOOK_PULL = 'outgoing.pull_logger'
264 HOOK_PULL = 'outgoing.pull_logger'
265 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
265 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
266
266
267 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
267 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
268 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
271 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
272
272
273 @classmethod
273 @classmethod
274 def get_by_key(cls, key):
274 def get_by_key(cls, key):
275 return cls.query().filter(cls.ui_key == key).scalar()
275 return cls.query().filter(cls.ui_key == key).scalar()
276
276
277 @classmethod
277 @classmethod
278 def get_builtin_hooks(cls):
278 def get_builtin_hooks(cls):
279 q = cls.query()
279 q = cls.query()
280 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
280 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
281 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
281 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
282 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
282 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
283 return q.all()
283 return q.all()
284
284
285 @classmethod
285 @classmethod
286 def get_custom_hooks(cls):
286 def get_custom_hooks(cls):
287 q = cls.query()
287 q = cls.query()
288 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
288 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
289 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
289 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
290 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
290 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
291 q = q.filter(cls.ui_section == 'hooks')
291 q = q.filter(cls.ui_section == 'hooks')
292 return q.all()
292 return q.all()
293
293
294 @classmethod
294 @classmethod
295 def get_repos_location(cls):
295 def get_repos_location(cls):
296 return cls.get_by_key('/').ui_value
296 return cls.get_by_key('/').ui_value
297
297
298 @classmethod
298 @classmethod
299 def create_or_update_hook(cls, key, val):
299 def create_or_update_hook(cls, key, val):
300 new_ui = cls.get_by_key(key) or cls()
300 new_ui = cls.get_by_key(key) or cls()
301 new_ui.ui_section = 'hooks'
301 new_ui.ui_section = 'hooks'
302 new_ui.ui_active = True
302 new_ui.ui_active = True
303 new_ui.ui_key = key
303 new_ui.ui_key = key
304 new_ui.ui_value = val
304 new_ui.ui_value = val
305
305
306 Session().add(new_ui)
306 Session().add(new_ui)
307
307
308 def __repr__(self):
308 def __repr__(self):
309 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
309 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
310 self.ui_value)
310 self.ui_value)
311
311
312
312
313 class User(Base, BaseModel):
313 class User(Base, BaseModel):
314 __tablename__ = 'users'
314 __tablename__ = 'users'
315 __table_args__ = (
315 __table_args__ = (
316 UniqueConstraint('username'), UniqueConstraint('email'),
316 UniqueConstraint('username'), UniqueConstraint('email'),
317 Index('u_username_idx', 'username'),
317 Index('u_username_idx', 'username'),
318 Index('u_email_idx', 'email'),
318 Index('u_email_idx', 'email'),
319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
320 'mysql_charset': 'utf8'}
320 'mysql_charset': 'utf8'}
321 )
321 )
322 DEFAULT_USER = 'default'
322 DEFAULT_USER = 'default'
323 DEFAULT_PERMISSIONS = [
323 DEFAULT_PERMISSIONS = [
324 'hg.register.manual_activate', 'hg.create.repository',
324 'hg.register.manual_activate', 'hg.create.repository',
325 'hg.fork.repository', 'repository.read', 'group.read'
325 'hg.fork.repository', 'repository.read', 'group.read'
326 ]
326 ]
327 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
327 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
328 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
330 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
331 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
331 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
332 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
334 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
334 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
335 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
335 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
336 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
336 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
337 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
337 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
338 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
338 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
339
339
340 user_log = relationship('UserLog')
340 user_log = relationship('UserLog')
341 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
341 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
342
342
343 repositories = relationship('Repository')
343 repositories = relationship('Repository')
344 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
344 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
345 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
345 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
346
346
347 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
347 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
348 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
348 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
349
349
350 group_member = relationship('UserGroupMember', cascade='all')
350 group_member = relationship('UserGroupMember', cascade='all')
351
351
352 notifications = relationship('UserNotification', cascade='all')
352 notifications = relationship('UserNotification', cascade='all')
353 # notifications assigned to this user
353 # notifications assigned to this user
354 user_created_notifications = relationship('Notification', cascade='all')
354 user_created_notifications = relationship('Notification', cascade='all')
355 # comments created by this user
355 # comments created by this user
356 user_comments = relationship('ChangesetComment', cascade='all')
356 user_comments = relationship('ChangesetComment', cascade='all')
357 #extra emails for this user
357 #extra emails for this user
358 user_emails = relationship('UserEmailMap', cascade='all')
358 user_emails = relationship('UserEmailMap', cascade='all')
359
359
360 @hybrid_property
360 @hybrid_property
361 def email(self):
361 def email(self):
362 return self._email
362 return self._email
363
363
364 @email.setter
364 @email.setter
365 def email(self, val):
365 def email(self, val):
366 self._email = val.lower() if val else None
366 self._email = val.lower() if val else None
367
367
368 @property
368 @property
369 def firstname(self):
369 def firstname(self):
370 # alias for future
370 # alias for future
371 return self.name
371 return self.name
372
372
373 @property
373 @property
374 def emails(self):
374 def emails(self):
375 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
375 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
376 return [self.email] + [x.email for x in other]
376 return [self.email] + [x.email for x in other]
377
377
378 @property
378 @property
379 def ip_addresses(self):
379 def ip_addresses(self):
380 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
380 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
381 return [x.ip_addr for x in ret]
381 return [x.ip_addr for x in ret]
382
382
383 @property
383 @property
384 def username_and_name(self):
384 def username_and_name(self):
385 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
385 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
386
386
387 @property
387 @property
388 def full_name(self):
388 def full_name(self):
389 return '%s %s' % (self.firstname, self.lastname)
389 return '%s %s' % (self.firstname, self.lastname)
390
390
391 @property
391 @property
392 def full_name_or_username(self):
392 def full_name_or_username(self):
393 return ('%s %s' % (self.firstname, self.lastname)
393 return ('%s %s' % (self.firstname, self.lastname)
394 if (self.firstname and self.lastname) else self.username)
394 if (self.firstname and self.lastname) else self.username)
395
395
396 @property
396 @property
397 def full_contact(self):
397 def full_contact(self):
398 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
398 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
399
399
400 @property
400 @property
401 def short_contact(self):
401 def short_contact(self):
402 return '%s %s' % (self.firstname, self.lastname)
402 return '%s %s' % (self.firstname, self.lastname)
403
403
404 @property
404 @property
405 def is_admin(self):
405 def is_admin(self):
406 return self.admin
406 return self.admin
407
407
408 @property
408 @property
409 def AuthUser(self):
409 def AuthUser(self):
410 """
410 """
411 Returns instance of AuthUser for this user
411 Returns instance of AuthUser for this user
412 """
412 """
413 from rhodecode.lib.auth import AuthUser
413 from rhodecode.lib.auth import AuthUser
414 return AuthUser(user_id=self.user_id, api_key=self.api_key,
414 return AuthUser(user_id=self.user_id, api_key=self.api_key,
415 username=self.username)
415 username=self.username)
416
416
417 def __unicode__(self):
417 def __unicode__(self):
418 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
418 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
419 self.user_id, self.username)
419 self.user_id, self.username)
420
420
421 @classmethod
421 @classmethod
422 def get_by_username(cls, username, case_insensitive=False, cache=False):
422 def get_by_username(cls, username, case_insensitive=False, cache=False):
423 if case_insensitive:
423 if case_insensitive:
424 q = cls.query().filter(cls.username.ilike(username))
424 q = cls.query().filter(cls.username.ilike(username))
425 else:
425 else:
426 q = cls.query().filter(cls.username == username)
426 q = cls.query().filter(cls.username == username)
427
427
428 if cache:
428 if cache:
429 q = q.options(FromCache(
429 q = q.options(FromCache(
430 "sql_cache_short",
430 "sql_cache_short",
431 "get_user_%s" % _hash_key(username)
431 "get_user_%s" % _hash_key(username)
432 )
432 )
433 )
433 )
434 return q.scalar()
434 return q.scalar()
435
435
436 @classmethod
436 @classmethod
437 def get_by_api_key(cls, api_key, cache=False):
437 def get_by_api_key(cls, api_key, cache=False):
438 q = cls.query().filter(cls.api_key == api_key)
438 q = cls.query().filter(cls.api_key == api_key)
439
439
440 if cache:
440 if cache:
441 q = q.options(FromCache("sql_cache_short",
441 q = q.options(FromCache("sql_cache_short",
442 "get_api_key_%s" % api_key))
442 "get_api_key_%s" % api_key))
443 return q.scalar()
443 return q.scalar()
444
444
445 @classmethod
445 @classmethod
446 def get_by_email(cls, email, case_insensitive=False, cache=False):
446 def get_by_email(cls, email, case_insensitive=False, cache=False):
447 if case_insensitive:
447 if case_insensitive:
448 q = cls.query().filter(cls.email.ilike(email))
448 q = cls.query().filter(cls.email.ilike(email))
449 else:
449 else:
450 q = cls.query().filter(cls.email == email)
450 q = cls.query().filter(cls.email == email)
451
451
452 if cache:
452 if cache:
453 q = q.options(FromCache("sql_cache_short",
453 q = q.options(FromCache("sql_cache_short",
454 "get_email_key_%s" % email))
454 "get_email_key_%s" % email))
455
455
456 ret = q.scalar()
456 ret = q.scalar()
457 if ret is None:
457 if ret is None:
458 q = UserEmailMap.query()
458 q = UserEmailMap.query()
459 # try fetching in alternate email map
459 # try fetching in alternate email map
460 if case_insensitive:
460 if case_insensitive:
461 q = q.filter(UserEmailMap.email.ilike(email))
461 q = q.filter(UserEmailMap.email.ilike(email))
462 else:
462 else:
463 q = q.filter(UserEmailMap.email == email)
463 q = q.filter(UserEmailMap.email == email)
464 q = q.options(joinedload(UserEmailMap.user))
464 q = q.options(joinedload(UserEmailMap.user))
465 if cache:
465 if cache:
466 q = q.options(FromCache("sql_cache_short",
466 q = q.options(FromCache("sql_cache_short",
467 "get_email_map_key_%s" % email))
467 "get_email_map_key_%s" % email))
468 ret = getattr(q.scalar(), 'user', None)
468 ret = getattr(q.scalar(), 'user', None)
469
469
470 return ret
470 return ret
471
471
472 @classmethod
472 @classmethod
473 def get_from_cs_author(cls, author):
473 def get_from_cs_author(cls, author):
474 """
474 """
475 Tries to get User objects out of commit author string
475 Tries to get User objects out of commit author string
476
476
477 :param author:
477 :param author:
478 """
478 """
479 from rhodecode.lib.helpers import email, author_name
479 from rhodecode.lib.helpers import email, author_name
480 # Valid email in the attribute passed, see if they're in the system
480 # Valid email in the attribute passed, see if they're in the system
481 _email = email(author)
481 _email = email(author)
482 if _email:
482 if _email:
483 user = cls.get_by_email(_email, case_insensitive=True)
483 user = cls.get_by_email(_email, case_insensitive=True)
484 if user:
484 if user:
485 return user
485 return user
486 # Maybe we can match by username?
486 # Maybe we can match by username?
487 _author = author_name(author)
487 _author = author_name(author)
488 user = cls.get_by_username(_author, case_insensitive=True)
488 user = cls.get_by_username(_author, case_insensitive=True)
489 if user:
489 if user:
490 return user
490 return user
491
491
492 def update_lastlogin(self):
492 def update_lastlogin(self):
493 """Update user lastlogin"""
493 """Update user lastlogin"""
494 self.last_login = datetime.datetime.now()
494 self.last_login = datetime.datetime.now()
495 Session().add(self)
495 Session().add(self)
496 log.debug('updated user %s lastlogin' % self.username)
496 log.debug('updated user %s lastlogin' % self.username)
497
497
498 @classmethod
498 @classmethod
499 def get_first_admin(cls):
499 def get_first_admin(cls):
500 user = User.query().filter(User.admin == True).first()
500 user = User.query().filter(User.admin == True).first()
501 if user is None:
501 if user is None:
502 raise Exception('Missing administrative account!')
502 raise Exception('Missing administrative account!')
503 return user
503 return user
504
504
505 def get_api_data(self):
505 def get_api_data(self):
506 """
506 """
507 Common function for generating user related data for API
507 Common function for generating user related data for API
508 """
508 """
509 user = self
509 user = self
510 data = dict(
510 data = dict(
511 user_id=user.user_id,
511 user_id=user.user_id,
512 username=user.username,
512 username=user.username,
513 firstname=user.name,
513 firstname=user.name,
514 lastname=user.lastname,
514 lastname=user.lastname,
515 email=user.email,
515 email=user.email,
516 emails=user.emails,
516 emails=user.emails,
517 api_key=user.api_key,
517 api_key=user.api_key,
518 active=user.active,
518 active=user.active,
519 admin=user.admin,
519 admin=user.admin,
520 ldap_dn=user.ldap_dn,
520 ldap_dn=user.ldap_dn,
521 last_login=user.last_login,
521 last_login=user.last_login,
522 ip_addresses=user.ip_addresses
522 ip_addresses=user.ip_addresses
523 )
523 )
524 return data
524 return data
525
525
526 def __json__(self):
526 def __json__(self):
527 data = dict(
527 data = dict(
528 full_name=self.full_name,
528 full_name=self.full_name,
529 full_name_or_username=self.full_name_or_username,
529 full_name_or_username=self.full_name_or_username,
530 short_contact=self.short_contact,
530 short_contact=self.short_contact,
531 full_contact=self.full_contact
531 full_contact=self.full_contact
532 )
532 )
533 data.update(self.get_api_data())
533 data.update(self.get_api_data())
534 return data
534 return data
535
535
536
536
537 class UserEmailMap(Base, BaseModel):
537 class UserEmailMap(Base, BaseModel):
538 __tablename__ = 'user_email_map'
538 __tablename__ = 'user_email_map'
539 __table_args__ = (
539 __table_args__ = (
540 Index('uem_email_idx', 'email'),
540 Index('uem_email_idx', 'email'),
541 UniqueConstraint('email'),
541 UniqueConstraint('email'),
542 {'extend_existing': True, 'mysql_engine': 'InnoDB',
542 {'extend_existing': True, 'mysql_engine': 'InnoDB',
543 'mysql_charset': 'utf8'}
543 'mysql_charset': 'utf8'}
544 )
544 )
545 __mapper_args__ = {}
545 __mapper_args__ = {}
546
546
547 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
547 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
548 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
548 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
549 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
549 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
550 user = relationship('User', lazy='joined')
550 user = relationship('User', lazy='joined')
551
551
552 @validates('_email')
552 @validates('_email')
553 def validate_email(self, key, email):
553 def validate_email(self, key, email):
554 # check if this email is not main one
554 # check if this email is not main one
555 main_email = Session().query(User).filter(User.email == email).scalar()
555 main_email = Session().query(User).filter(User.email == email).scalar()
556 if main_email is not None:
556 if main_email is not None:
557 raise AttributeError('email %s is present is user table' % email)
557 raise AttributeError('email %s is present is user table' % email)
558 return email
558 return email
559
559
560 @hybrid_property
560 @hybrid_property
561 def email(self):
561 def email(self):
562 return self._email
562 return self._email
563
563
564 @email.setter
564 @email.setter
565 def email(self, val):
565 def email(self, val):
566 self._email = val.lower() if val else None
566 self._email = val.lower() if val else None
567
567
568
568
569 class UserIpMap(Base, BaseModel):
569 class UserIpMap(Base, BaseModel):
570 __tablename__ = 'user_ip_map'
570 __tablename__ = 'user_ip_map'
571 __table_args__ = (
571 __table_args__ = (
572 UniqueConstraint('user_id', 'ip_addr'),
572 UniqueConstraint('user_id', 'ip_addr'),
573 {'extend_existing': True, 'mysql_engine': 'InnoDB',
573 {'extend_existing': True, 'mysql_engine': 'InnoDB',
574 'mysql_charset': 'utf8'}
574 'mysql_charset': 'utf8'}
575 )
575 )
576 __mapper_args__ = {}
576 __mapper_args__ = {}
577
577
578 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
578 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
579 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
579 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
580 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
580 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
581 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
581 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
582 user = relationship('User', lazy='joined')
582 user = relationship('User', lazy='joined')
583
583
584 @classmethod
584 @classmethod
585 def _get_ip_range(cls, ip_addr):
585 def _get_ip_range(cls, ip_addr):
586 from rhodecode.lib import ipaddr
586 from rhodecode.lib import ipaddr
587 net = ipaddr.IPNetwork(address=ip_addr)
587 net = ipaddr.IPNetwork(address=ip_addr)
588 return [str(net.network), str(net.broadcast)]
588 return [str(net.network), str(net.broadcast)]
589
589
590 def __json__(self):
590 def __json__(self):
591 return dict(
591 return dict(
592 ip_addr=self.ip_addr,
592 ip_addr=self.ip_addr,
593 ip_range=self._get_ip_range(self.ip_addr)
593 ip_range=self._get_ip_range(self.ip_addr)
594 )
594 )
595
595
596
596
597 class UserLog(Base, BaseModel):
597 class UserLog(Base, BaseModel):
598 __tablename__ = 'user_logs'
598 __tablename__ = 'user_logs'
599 __table_args__ = (
599 __table_args__ = (
600 {'extend_existing': True, 'mysql_engine': 'InnoDB',
600 {'extend_existing': True, 'mysql_engine': 'InnoDB',
601 'mysql_charset': 'utf8'},
601 'mysql_charset': 'utf8'},
602 )
602 )
603 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
603 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
604 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
604 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
605 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
605 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
606 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
606 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
607 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
607 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
608 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
608 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
609 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
609 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
610 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
610 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
611
611
612 @property
612 @property
613 def action_as_day(self):
613 def action_as_day(self):
614 return datetime.date(*self.action_date.timetuple()[:3])
614 return datetime.date(*self.action_date.timetuple()[:3])
615
615
616 user = relationship('User')
616 user = relationship('User')
617 repository = relationship('Repository', cascade='')
617 repository = relationship('Repository', cascade='')
618
618
619
619
620 class UserGroup(Base, BaseModel):
620 class UserGroup(Base, BaseModel):
621 __tablename__ = 'users_groups'
621 __tablename__ = 'users_groups'
622 __table_args__ = (
622 __table_args__ = (
623 {'extend_existing': True, 'mysql_engine': 'InnoDB',
623 {'extend_existing': True, 'mysql_engine': 'InnoDB',
624 'mysql_charset': 'utf8'},
624 'mysql_charset': 'utf8'},
625 )
625 )
626
626
627 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
627 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
628 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
628 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
629 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
629 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
630 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
630 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
631 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
631 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
632
632
633 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
633 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
634 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
634 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
635 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
635 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
636 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
636 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
637 user = relationship('User')
637 user = relationship('User')
638
638
639 def __unicode__(self):
639 def __unicode__(self):
640 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
640 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
641 self.users_group_id,
641 self.users_group_id,
642 self.users_group_name)
642 self.users_group_name)
643
643
644 @classmethod
644 @classmethod
645 def get_by_group_name(cls, group_name, cache=False,
645 def get_by_group_name(cls, group_name, cache=False,
646 case_insensitive=False):
646 case_insensitive=False):
647 if case_insensitive:
647 if case_insensitive:
648 q = cls.query().filter(cls.users_group_name.ilike(group_name))
648 q = cls.query().filter(cls.users_group_name.ilike(group_name))
649 else:
649 else:
650 q = cls.query().filter(cls.users_group_name == group_name)
650 q = cls.query().filter(cls.users_group_name == group_name)
651 if cache:
651 if cache:
652 q = q.options(FromCache(
652 q = q.options(FromCache(
653 "sql_cache_short",
653 "sql_cache_short",
654 "get_user_%s" % _hash_key(group_name)
654 "get_user_%s" % _hash_key(group_name)
655 )
655 )
656 )
656 )
657 return q.scalar()
657 return q.scalar()
658
658
659 @classmethod
659 @classmethod
660 def get(cls, users_group_id, cache=False):
660 def get(cls, users_group_id, cache=False):
661 users_group = cls.query()
661 users_group = cls.query()
662 if cache:
662 if cache:
663 users_group = users_group.options(FromCache("sql_cache_short",
663 users_group = users_group.options(FromCache("sql_cache_short",
664 "get_users_group_%s" % users_group_id))
664 "get_users_group_%s" % users_group_id))
665 return users_group.get(users_group_id)
665 return users_group.get(users_group_id)
666
666
667 def get_api_data(self):
667 def get_api_data(self):
668 users_group = self
668 users_group = self
669
669
670 data = dict(
670 data = dict(
671 users_group_id=users_group.users_group_id,
671 users_group_id=users_group.users_group_id,
672 group_name=users_group.users_group_name,
672 group_name=users_group.users_group_name,
673 active=users_group.users_group_active,
673 active=users_group.users_group_active,
674 )
674 )
675
675
676 return data
676 return data
677
677
678
678
679 class UserGroupMember(Base, BaseModel):
679 class UserGroupMember(Base, BaseModel):
680 __tablename__ = 'users_groups_members'
680 __tablename__ = 'users_groups_members'
681 __table_args__ = (
681 __table_args__ = (
682 {'extend_existing': True, 'mysql_engine': 'InnoDB',
682 {'extend_existing': True, 'mysql_engine': 'InnoDB',
683 'mysql_charset': 'utf8'},
683 'mysql_charset': 'utf8'},
684 )
684 )
685
685
686 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
686 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
687 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
687 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
688 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
688 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
689
689
690 user = relationship('User', lazy='joined')
690 user = relationship('User', lazy='joined')
691 users_group = relationship('UserGroup')
691 users_group = relationship('UserGroup')
692
692
693 def __init__(self, gr_id='', u_id=''):
693 def __init__(self, gr_id='', u_id=''):
694 self.users_group_id = gr_id
694 self.users_group_id = gr_id
695 self.user_id = u_id
695 self.user_id = u_id
696
696
697
697
698 class RepositoryField(Base, BaseModel):
698 class RepositoryField(Base, BaseModel):
699 __tablename__ = 'repositories_fields'
699 __tablename__ = 'repositories_fields'
700 __table_args__ = (
700 __table_args__ = (
701 UniqueConstraint('repository_id', 'field_key'), # no-multi field
701 UniqueConstraint('repository_id', 'field_key'), # no-multi field
702 {'extend_existing': True, 'mysql_engine': 'InnoDB',
702 {'extend_existing': True, 'mysql_engine': 'InnoDB',
703 'mysql_charset': 'utf8'},
703 'mysql_charset': 'utf8'},
704 )
704 )
705 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
705 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
706
706
707 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
707 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
708 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
708 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
709 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
709 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
710 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
710 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
711 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
711 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
712 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
712 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
713 field_type = Column("field_type", String(256), nullable=False, unique=None)
713 field_type = Column("field_type", String(256), nullable=False, unique=None)
714 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
714 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
715
715
716 repository = relationship('Repository')
716 repository = relationship('Repository')
717
717
718 @property
718 @property
719 def field_key_prefixed(self):
719 def field_key_prefixed(self):
720 return 'ex_%s' % self.field_key
720 return 'ex_%s' % self.field_key
721
721
722 @classmethod
722 @classmethod
723 def un_prefix_key(cls, key):
723 def un_prefix_key(cls, key):
724 if key.startswith(cls.PREFIX):
724 if key.startswith(cls.PREFIX):
725 return key[len(cls.PREFIX):]
725 return key[len(cls.PREFIX):]
726 return key
726 return key
727
727
728 @classmethod
728 @classmethod
729 def get_by_key_name(cls, key, repo):
729 def get_by_key_name(cls, key, repo):
730 row = cls.query()\
730 row = cls.query()\
731 .filter(cls.repository == repo)\
731 .filter(cls.repository == repo)\
732 .filter(cls.field_key == key).scalar()
732 .filter(cls.field_key == key).scalar()
733 return row
733 return row
734
734
735
735
736 class Repository(Base, BaseModel):
736 class Repository(Base, BaseModel):
737 __tablename__ = 'repositories'
737 __tablename__ = 'repositories'
738 __table_args__ = (
738 __table_args__ = (
739 UniqueConstraint('repo_name'),
739 UniqueConstraint('repo_name'),
740 Index('r_repo_name_idx', 'repo_name'),
740 Index('r_repo_name_idx', 'repo_name'),
741 {'extend_existing': True, 'mysql_engine': 'InnoDB',
741 {'extend_existing': True, 'mysql_engine': 'InnoDB',
742 'mysql_charset': 'utf8'},
742 'mysql_charset': 'utf8'},
743 )
743 )
744
744
745 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
745 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
746 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
746 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
747 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
747 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
748 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
748 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
749 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
749 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
750 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
750 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
751 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
751 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
752 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
752 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
753 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
753 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
754 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
754 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
755 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
755 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
756 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
756 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
757 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
757 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
758 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
758 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
759 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
759 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
760
760
761 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
761 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
762 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
762 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
763
763
764 user = relationship('User')
764 user = relationship('User')
765 fork = relationship('Repository', remote_side=repo_id)
765 fork = relationship('Repository', remote_side=repo_id)
766 group = relationship('RepoGroup')
766 group = relationship('RepoGroup')
767 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
767 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
768 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
768 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
769 stats = relationship('Statistics', cascade='all', uselist=False)
769 stats = relationship('Statistics', cascade='all', uselist=False)
770
770
771 followers = relationship('UserFollowing',
771 followers = relationship('UserFollowing',
772 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
772 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
773 cascade='all')
773 cascade='all')
774 extra_fields = relationship('RepositoryField',
774 extra_fields = relationship('RepositoryField',
775 cascade="all, delete, delete-orphan")
775 cascade="all, delete, delete-orphan")
776
776
777 logs = relationship('UserLog')
777 logs = relationship('UserLog')
778 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
778 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
779
779
780 pull_requests_org = relationship('PullRequest',
780 pull_requests_org = relationship('PullRequest',
781 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
781 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
782 cascade="all, delete, delete-orphan")
782 cascade="all, delete, delete-orphan")
783
783
784 pull_requests_other = relationship('PullRequest',
784 pull_requests_other = relationship('PullRequest',
785 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
785 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
786 cascade="all, delete, delete-orphan")
786 cascade="all, delete, delete-orphan")
787
787
788 def __unicode__(self):
788 def __unicode__(self):
789 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
789 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
790 self.repo_name)
790 self.repo_name)
791
791
792 @hybrid_property
792 @hybrid_property
793 def locked(self):
793 def locked(self):
794 # always should return [user_id, timelocked]
794 # always should return [user_id, timelocked]
795 if self._locked:
795 if self._locked:
796 _lock_info = self._locked.split(':')
796 _lock_info = self._locked.split(':')
797 return int(_lock_info[0]), _lock_info[1]
797 return int(_lock_info[0]), _lock_info[1]
798 return [None, None]
798 return [None, None]
799
799
800 @locked.setter
800 @locked.setter
801 def locked(self, val):
801 def locked(self, val):
802 if val and isinstance(val, (list, tuple)):
802 if val and isinstance(val, (list, tuple)):
803 self._locked = ':'.join(map(str, val))
803 self._locked = ':'.join(map(str, val))
804 else:
804 else:
805 self._locked = None
805 self._locked = None
806
806
807 @hybrid_property
807 @hybrid_property
808 def changeset_cache(self):
808 def changeset_cache(self):
809 from rhodecode.lib.vcs.backends.base import EmptyChangeset
809 from rhodecode.lib.vcs.backends.base import EmptyChangeset
810 dummy = EmptyChangeset().__json__()
810 dummy = EmptyChangeset().__json__()
811 if not self._changeset_cache:
811 if not self._changeset_cache:
812 return dummy
812 return dummy
813 try:
813 try:
814 return json.loads(self._changeset_cache)
814 return json.loads(self._changeset_cache)
815 except TypeError:
815 except TypeError:
816 return dummy
816 return dummy
817
817
818 @changeset_cache.setter
818 @changeset_cache.setter
819 def changeset_cache(self, val):
819 def changeset_cache(self, val):
820 try:
820 try:
821 self._changeset_cache = json.dumps(val)
821 self._changeset_cache = json.dumps(val)
822 except Exception:
822 except Exception:
823 log.error(traceback.format_exc())
823 log.error(traceback.format_exc())
824
824
825 @classmethod
825 @classmethod
826 def url_sep(cls):
826 def url_sep(cls):
827 return URL_SEP
827 return URL_SEP
828
828
829 @classmethod
829 @classmethod
830 def normalize_repo_name(cls, repo_name):
830 def normalize_repo_name(cls, repo_name):
831 """
831 """
832 Normalizes os specific repo_name to the format internally stored inside
832 Normalizes os specific repo_name to the format internally stored inside
833 dabatabase using URL_SEP
833 dabatabase using URL_SEP
834
834
835 :param cls:
835 :param cls:
836 :param repo_name:
836 :param repo_name:
837 """
837 """
838 return cls.url_sep().join(repo_name.split(os.sep))
838 return cls.url_sep().join(repo_name.split(os.sep))
839
839
840 @classmethod
840 @classmethod
841 def get_by_repo_name(cls, repo_name):
841 def get_by_repo_name(cls, repo_name):
842 q = Session().query(cls).filter(cls.repo_name == repo_name)
842 q = Session().query(cls).filter(cls.repo_name == repo_name)
843 q = q.options(joinedload(Repository.fork))\
843 q = q.options(joinedload(Repository.fork))\
844 .options(joinedload(Repository.user))\
844 .options(joinedload(Repository.user))\
845 .options(joinedload(Repository.group))
845 .options(joinedload(Repository.group))
846 return q.scalar()
846 return q.scalar()
847
847
848 @classmethod
848 @classmethod
849 def get_by_full_path(cls, repo_full_path):
849 def get_by_full_path(cls, repo_full_path):
850 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
850 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
851 repo_name = cls.normalize_repo_name(repo_name)
851 repo_name = cls.normalize_repo_name(repo_name)
852 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
852 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
853
853
854 @classmethod
854 @classmethod
855 def get_repo_forks(cls, repo_id):
855 def get_repo_forks(cls, repo_id):
856 return cls.query().filter(Repository.fork_id == repo_id)
856 return cls.query().filter(Repository.fork_id == repo_id)
857
857
858 @classmethod
858 @classmethod
859 def base_path(cls):
859 def base_path(cls):
860 """
860 """
861 Returns base path when all repos are stored
861 Returns base path when all repos are stored
862
862
863 :param cls:
863 :param cls:
864 """
864 """
865 q = Session().query(RhodeCodeUi)\
865 q = Session().query(RhodeCodeUi)\
866 .filter(RhodeCodeUi.ui_key == cls.url_sep())
866 .filter(RhodeCodeUi.ui_key == cls.url_sep())
867 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
867 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
868 return q.one().ui_value
868 return q.one().ui_value
869
869
870 @property
870 @property
871 def forks(self):
871 def forks(self):
872 """
872 """
873 Return forks of this repo
873 Return forks of this repo
874 """
874 """
875 return Repository.get_repo_forks(self.repo_id)
875 return Repository.get_repo_forks(self.repo_id)
876
876
877 @property
877 @property
878 def parent(self):
878 def parent(self):
879 """
879 """
880 Returns fork parent
880 Returns fork parent
881 """
881 """
882 return self.fork
882 return self.fork
883
883
884 @property
884 @property
885 def just_name(self):
885 def just_name(self):
886 return self.repo_name.split(Repository.url_sep())[-1]
886 return self.repo_name.split(Repository.url_sep())[-1]
887
887
888 @property
888 @property
889 def groups_with_parents(self):
889 def groups_with_parents(self):
890 groups = []
890 groups = []
891 if self.group is None:
891 if self.group is None:
892 return groups
892 return groups
893
893
894 cur_gr = self.group
894 cur_gr = self.group
895 groups.insert(0, cur_gr)
895 groups.insert(0, cur_gr)
896 while 1:
896 while 1:
897 gr = getattr(cur_gr, 'parent_group', None)
897 gr = getattr(cur_gr, 'parent_group', None)
898 cur_gr = cur_gr.parent_group
898 cur_gr = cur_gr.parent_group
899 if gr is None:
899 if gr is None:
900 break
900 break
901 groups.insert(0, gr)
901 groups.insert(0, gr)
902
902
903 return groups
903 return groups
904
904
905 @property
905 @property
906 def groups_and_repo(self):
906 def groups_and_repo(self):
907 return self.groups_with_parents, self.just_name, self.repo_name
907 return self.groups_with_parents, self.just_name, self.repo_name
908
908
909 @LazyProperty
909 @LazyProperty
910 def repo_path(self):
910 def repo_path(self):
911 """
911 """
912 Returns base full path for that repository means where it actually
912 Returns base full path for that repository means where it actually
913 exists on a filesystem
913 exists on a filesystem
914 """
914 """
915 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
915 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
916 Repository.url_sep())
916 Repository.url_sep())
917 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
917 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
918 return q.one().ui_value
918 return q.one().ui_value
919
919
920 @property
920 @property
921 def repo_full_path(self):
921 def repo_full_path(self):
922 p = [self.repo_path]
922 p = [self.repo_path]
923 # we need to split the name by / since this is how we store the
923 # we need to split the name by / since this is how we store the
924 # names in the database, but that eventually needs to be converted
924 # names in the database, but that eventually needs to be converted
925 # into a valid system path
925 # into a valid system path
926 p += self.repo_name.split(Repository.url_sep())
926 p += self.repo_name.split(Repository.url_sep())
927 return os.path.join(*map(safe_unicode, p))
927 return os.path.join(*map(safe_unicode, p))
928
928
929 @property
929 @property
930 def cache_keys(self):
930 def cache_keys(self):
931 """
931 """
932 Returns associated cache keys for that repo
932 Returns associated cache keys for that repo
933 """
933 """
934 return CacheInvalidation.query()\
934 return CacheInvalidation.query()\
935 .filter(CacheInvalidation.cache_args == self.repo_name)\
935 .filter(CacheInvalidation.cache_args == self.repo_name)\
936 .order_by(CacheInvalidation.cache_key)\
936 .order_by(CacheInvalidation.cache_key)\
937 .all()
937 .all()
938
938
939 def get_new_name(self, repo_name):
939 def get_new_name(self, repo_name):
940 """
940 """
941 returns new full repository name based on assigned group and new new
941 returns new full repository name based on assigned group and new new
942
942
943 :param group_name:
943 :param group_name:
944 """
944 """
945 path_prefix = self.group.full_path_splitted if self.group else []
945 path_prefix = self.group.full_path_splitted if self.group else []
946 return Repository.url_sep().join(path_prefix + [repo_name])
946 return Repository.url_sep().join(path_prefix + [repo_name])
947
947
948 @property
948 @property
949 def _ui(self):
949 def _ui(self):
950 """
950 """
951 Creates an db based ui object for this repository
951 Creates an db based ui object for this repository
952 """
952 """
953 from rhodecode.lib.utils import make_ui
953 from rhodecode.lib.utils import make_ui
954 return make_ui('db', clear_session=False)
954 return make_ui('db', clear_session=False)
955
955
956 @classmethod
956 @classmethod
957 def is_valid(cls, repo_name):
957 def is_valid(cls, repo_name):
958 """
958 """
959 returns True if given repo name is a valid filesystem repository
959 returns True if given repo name is a valid filesystem repository
960
960
961 :param cls:
961 :param cls:
962 :param repo_name:
962 :param repo_name:
963 """
963 """
964 from rhodecode.lib.utils import is_valid_repo
964 from rhodecode.lib.utils import is_valid_repo
965
965
966 return is_valid_repo(repo_name, cls.base_path())
966 return is_valid_repo(repo_name, cls.base_path())
967
967
968 def get_api_data(self):
968 def get_api_data(self):
969 """
969 """
970 Common function for generating repo api data
970 Common function for generating repo api data
971
971
972 """
972 """
973 repo = self
973 repo = self
974 data = dict(
974 data = dict(
975 repo_id=repo.repo_id,
975 repo_id=repo.repo_id,
976 repo_name=repo.repo_name,
976 repo_name=repo.repo_name,
977 repo_type=repo.repo_type,
977 repo_type=repo.repo_type,
978 clone_uri=repo.clone_uri,
978 clone_uri=repo.clone_uri,
979 private=repo.private,
979 private=repo.private,
980 created_on=repo.created_on,
980 created_on=repo.created_on,
981 description=repo.description,
981 description=repo.description,
982 landing_rev=repo.landing_rev,
982 landing_rev=repo.landing_rev,
983 owner=repo.user.username,
983 owner=repo.user.username,
984 fork_of=repo.fork.repo_name if repo.fork else None,
984 fork_of=repo.fork.repo_name if repo.fork else None,
985 enable_statistics=repo.enable_statistics,
985 enable_statistics=repo.enable_statistics,
986 enable_locking=repo.enable_locking,
986 enable_locking=repo.enable_locking,
987 enable_downloads=repo.enable_downloads,
987 enable_downloads=repo.enable_downloads,
988 last_changeset=repo.changeset_cache,
988 last_changeset=repo.changeset_cache,
989 locked_by=User.get(self.locked[0]).get_api_data() \
989 locked_by=User.get(self.locked[0]).get_api_data() \
990 if self.locked[0] else None,
990 if self.locked[0] else None,
991 locked_date=time_to_datetime(self.locked[1]) \
991 locked_date=time_to_datetime(self.locked[1]) \
992 if self.locked[1] else None
992 if self.locked[1] else None
993 )
993 )
994 rc_config = RhodeCodeSetting.get_app_settings()
994 rc_config = RhodeCodeSetting.get_app_settings()
995 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
995 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
996 if repository_fields:
996 if repository_fields:
997 for f in self.extra_fields:
997 for f in self.extra_fields:
998 data[f.field_key_prefixed] = f.field_value
998 data[f.field_key_prefixed] = f.field_value
999
999
1000 return data
1000 return data
1001
1001
1002 @classmethod
1002 @classmethod
1003 def lock(cls, repo, user_id):
1003 def lock(cls, repo, user_id):
1004 repo.locked = [user_id, time.time()]
1004 repo.locked = [user_id, time.time()]
1005 Session().add(repo)
1005 Session().add(repo)
1006 Session().commit()
1006 Session().commit()
1007
1007
1008 @classmethod
1008 @classmethod
1009 def unlock(cls, repo):
1009 def unlock(cls, repo):
1010 repo.locked = None
1010 repo.locked = None
1011 Session().add(repo)
1011 Session().add(repo)
1012 Session().commit()
1012 Session().commit()
1013
1013
1014 @classmethod
1014 @classmethod
1015 def getlock(cls, repo):
1015 def getlock(cls, repo):
1016 return repo.locked
1016 return repo.locked
1017
1017
1018 @property
1018 @property
1019 def last_db_change(self):
1019 def last_db_change(self):
1020 return self.updated_on
1020 return self.updated_on
1021
1021
1022 def clone_url(self, **override):
1022 def clone_url(self, **override):
1023 from pylons import url
1023 from pylons import url
1024 from urlparse import urlparse
1024 from urlparse import urlparse
1025 import urllib
1025 import urllib
1026 parsed_url = urlparse(url('home', qualified=True))
1026 parsed_url = urlparse(url('home', qualified=True))
1027 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1027 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1028 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1028 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1029 args = {
1029 args = {
1030 'user': '',
1030 'user': '',
1031 'pass': '',
1031 'pass': '',
1032 'scheme': parsed_url.scheme,
1032 'scheme': parsed_url.scheme,
1033 'netloc': parsed_url.netloc,
1033 'netloc': parsed_url.netloc,
1034 'prefix': decoded_path,
1034 'prefix': decoded_path,
1035 'path': self.repo_name
1035 'path': self.repo_name
1036 }
1036 }
1037
1037
1038 args.update(override)
1038 args.update(override)
1039 return default_clone_uri % args
1039 return default_clone_uri % args
1040
1040
1041 #==========================================================================
1041 #==========================================================================
1042 # SCM PROPERTIES
1042 # SCM PROPERTIES
1043 #==========================================================================
1043 #==========================================================================
1044
1044
1045 def get_changeset(self, rev=None):
1045 def get_changeset(self, rev=None):
1046 return get_changeset_safe(self.scm_instance, rev)
1046 return get_changeset_safe(self.scm_instance, rev)
1047
1047
1048 def get_landing_changeset(self):
1048 def get_landing_changeset(self):
1049 """
1049 """
1050 Returns landing changeset, or if that doesn't exist returns the tip
1050 Returns landing changeset, or if that doesn't exist returns the tip
1051 """
1051 """
1052 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1052 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1053 return cs
1053 return cs
1054
1054
1055 def update_changeset_cache(self, cs_cache=None):
1055 def update_changeset_cache(self, cs_cache=None):
1056 """
1056 """
1057 Update cache of last changeset for repository, keys should be::
1057 Update cache of last changeset for repository, keys should be::
1058
1058
1059 short_id
1059 short_id
1060 raw_id
1060 raw_id
1061 revision
1061 revision
1062 message
1062 message
1063 date
1063 date
1064 author
1064 author
1065
1065
1066 :param cs_cache:
1066 :param cs_cache:
1067 """
1067 """
1068 from rhodecode.lib.vcs.backends.base import BaseChangeset
1068 from rhodecode.lib.vcs.backends.base import BaseChangeset
1069 if cs_cache is None:
1069 if cs_cache is None:
1070 cs_cache = EmptyChangeset()
1070 cs_cache = EmptyChangeset()
1071 # use no-cache version here
1071 # use no-cache version here
1072 scm_repo = self.scm_instance_no_cache()
1072 scm_repo = self.scm_instance_no_cache()
1073 if scm_repo:
1073 if scm_repo:
1074 cs_cache = scm_repo.get_changeset()
1074 cs_cache = scm_repo.get_changeset()
1075
1075
1076 if isinstance(cs_cache, BaseChangeset):
1076 if isinstance(cs_cache, BaseChangeset):
1077 cs_cache = cs_cache.__json__()
1077 cs_cache = cs_cache.__json__()
1078
1078
1079 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1079 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1080 _default = datetime.datetime.fromtimestamp(0)
1080 _default = datetime.datetime.fromtimestamp(0)
1081 last_change = cs_cache.get('date') or _default
1081 last_change = cs_cache.get('date') or _default
1082 log.debug('updated repo %s with new cs cache %s'
1082 log.debug('updated repo %s with new cs cache %s'
1083 % (self.repo_name, cs_cache))
1083 % (self.repo_name, cs_cache))
1084 self.updated_on = last_change
1084 self.updated_on = last_change
1085 self.changeset_cache = cs_cache
1085 self.changeset_cache = cs_cache
1086 Session().add(self)
1086 Session().add(self)
1087 Session().commit()
1087 Session().commit()
1088 else:
1088 else:
1089 log.debug('Skipping repo:%s already with latest changes'
1089 log.debug('Skipping repo:%s already with latest changes'
1090 % self.repo_name)
1090 % self.repo_name)
1091
1091
1092 @property
1092 @property
1093 def tip(self):
1093 def tip(self):
1094 return self.get_changeset('tip')
1094 return self.get_changeset('tip')
1095
1095
1096 @property
1096 @property
1097 def author(self):
1097 def author(self):
1098 return self.tip.author
1098 return self.tip.author
1099
1099
1100 @property
1100 @property
1101 def last_change(self):
1101 def last_change(self):
1102 return self.scm_instance.last_change
1102 return self.scm_instance.last_change
1103
1103
1104 def get_comments(self, revisions=None):
1104 def get_comments(self, revisions=None):
1105 """
1105 """
1106 Returns comments for this repository grouped by revisions
1106 Returns comments for this repository grouped by revisions
1107
1107
1108 :param revisions: filter query by revisions only
1108 :param revisions: filter query by revisions only
1109 """
1109 """
1110 cmts = ChangesetComment.query()\
1110 cmts = ChangesetComment.query()\
1111 .filter(ChangesetComment.repo == self)
1111 .filter(ChangesetComment.repo == self)
1112 if revisions:
1112 if revisions:
1113 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1113 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1114 grouped = defaultdict(list)
1114 grouped = defaultdict(list)
1115 for cmt in cmts.all():
1115 for cmt in cmts.all():
1116 grouped[cmt.revision].append(cmt)
1116 grouped[cmt.revision].append(cmt)
1117 return grouped
1117 return grouped
1118
1118
1119 def statuses(self, revisions=None):
1119 def statuses(self, revisions=None):
1120 """
1120 """
1121 Returns statuses for this repository
1121 Returns statuses for this repository
1122
1122
1123 :param revisions: list of revisions to get statuses for
1123 :param revisions: list of revisions to get statuses for
1124 :type revisions: list
1124 :type revisions: list
1125 """
1125 """
1126
1126
1127 statuses = ChangesetStatus.query()\
1127 statuses = ChangesetStatus.query()\
1128 .filter(ChangesetStatus.repo == self)\
1128 .filter(ChangesetStatus.repo == self)\
1129 .filter(ChangesetStatus.version == 0)
1129 .filter(ChangesetStatus.version == 0)
1130 if revisions:
1130 if revisions:
1131 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1131 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1132 grouped = {}
1132 grouped = {}
1133
1133
1134 #maybe we have open new pullrequest without a status ?
1134 #maybe we have open new pullrequest without a status ?
1135 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1135 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1136 status_lbl = ChangesetStatus.get_status_lbl(stat)
1136 status_lbl = ChangesetStatus.get_status_lbl(stat)
1137 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1137 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1138 for rev in pr.revisions:
1138 for rev in pr.revisions:
1139 pr_id = pr.pull_request_id
1139 pr_id = pr.pull_request_id
1140 pr_repo = pr.other_repo.repo_name
1140 pr_repo = pr.other_repo.repo_name
1141 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1141 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1142
1142
1143 for stat in statuses.all():
1143 for stat in statuses.all():
1144 pr_id = pr_repo = None
1144 pr_id = pr_repo = None
1145 if stat.pull_request:
1145 if stat.pull_request:
1146 pr_id = stat.pull_request.pull_request_id
1146 pr_id = stat.pull_request.pull_request_id
1147 pr_repo = stat.pull_request.other_repo.repo_name
1147 pr_repo = stat.pull_request.other_repo.repo_name
1148 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1148 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1149 pr_id, pr_repo]
1149 pr_id, pr_repo]
1150 return grouped
1150 return grouped
1151
1151
1152 def _repo_size(self):
1152 def _repo_size(self):
1153 from rhodecode.lib import helpers as h
1153 from rhodecode.lib import helpers as h
1154 log.debug('calculating repository size...')
1154 log.debug('calculating repository size...')
1155 return h.format_byte_size(self.scm_instance.size)
1155 return h.format_byte_size(self.scm_instance.size)
1156
1156
1157 #==========================================================================
1157 #==========================================================================
1158 # SCM CACHE INSTANCE
1158 # SCM CACHE INSTANCE
1159 #==========================================================================
1159 #==========================================================================
1160
1160
1161 @property
1161 @property
1162 def invalidate(self):
1162 def invalidate(self):
1163 return CacheInvalidation.invalidate(self.repo_name)
1163 return CacheInvalidation.invalidate(self.repo_name)
1164
1164
1165 def set_invalidate(self):
1165 def set_invalidate(self):
1166 """
1166 """
1167 Mark caches of this repo as invalid.
1167 Mark caches of this repo as invalid.
1168 """
1168 """
1169 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1169 CacheInvalidation.set_invalidate(self.repo_name)
1170
1170
1171 def scm_instance_no_cache(self):
1171 def scm_instance_no_cache(self):
1172 return self.__get_instance()
1172 return self.__get_instance()
1173
1173
1174 @property
1174 @property
1175 def scm_instance(self):
1175 def scm_instance(self):
1176 import rhodecode
1176 import rhodecode
1177 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1177 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1178 if full_cache:
1178 if full_cache:
1179 return self.scm_instance_cached()
1179 return self.scm_instance_cached()
1180 return self.__get_instance()
1180 return self.__get_instance()
1181
1181
1182 def scm_instance_cached(self, cache_map=None):
1182 def scm_instance_cached(self, cache_map=None):
1183 @cache_region('long_term')
1183 @cache_region('long_term')
1184 def _c(repo_name):
1184 def _c(repo_name):
1185 return self.__get_instance()
1185 return self.__get_instance()
1186 rn = self.repo_name
1186 rn = self.repo_name
1187
1187
1188 if cache_map:
1188 if cache_map:
1189 # get using prefilled cache_map
1189 # get using prefilled cache_map
1190 invalidate_repo = cache_map[self.repo_name]
1190 invalidate_repo = cache_map[self.repo_name]
1191 if invalidate_repo:
1191 if invalidate_repo:
1192 invalidate_repo = (None if invalidate_repo.cache_active
1192 invalidate_repo = (None if invalidate_repo.cache_active
1193 else invalidate_repo)
1193 else invalidate_repo)
1194 else:
1194 else:
1195 # get from invalidate
1195 # get from invalidate
1196 invalidate_repo = self.invalidate
1196 invalidate_repo = self.invalidate
1197
1197
1198 if invalidate_repo is not None:
1198 if invalidate_repo is not None:
1199 region_invalidate(_c, None, rn)
1199 region_invalidate(_c, None, rn)
1200 log.debug('Cache for %s invalidated, getting new object' % (rn))
1200 log.debug('Cache for %s invalidated, getting new object' % (rn))
1201 # update our cache
1201 # update our cache
1202 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1202 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1203 else:
1203 else:
1204 log.debug('Getting obj for %s from cache' % (rn))
1204 log.debug('Getting obj for %s from cache' % (rn))
1205 return _c(rn)
1205 return _c(rn)
1206
1206
1207 def __get_instance(self):
1207 def __get_instance(self):
1208 repo_full_path = self.repo_full_path
1208 repo_full_path = self.repo_full_path
1209 try:
1209 try:
1210 alias = get_scm(repo_full_path)[0]
1210 alias = get_scm(repo_full_path)[0]
1211 log.debug('Creating instance of %s repository from %s'
1211 log.debug('Creating instance of %s repository from %s'
1212 % (alias, repo_full_path))
1212 % (alias, repo_full_path))
1213 backend = get_backend(alias)
1213 backend = get_backend(alias)
1214 except VCSError:
1214 except VCSError:
1215 log.error(traceback.format_exc())
1215 log.error(traceback.format_exc())
1216 log.error('Perhaps this repository is in db and not in '
1216 log.error('Perhaps this repository is in db and not in '
1217 'filesystem run rescan repositories with '
1217 'filesystem run rescan repositories with '
1218 '"destroy old data " option from admin panel')
1218 '"destroy old data " option from admin panel')
1219 return
1219 return
1220
1220
1221 if alias == 'hg':
1221 if alias == 'hg':
1222
1222
1223 repo = backend(safe_str(repo_full_path), create=False,
1223 repo = backend(safe_str(repo_full_path), create=False,
1224 baseui=self._ui)
1224 baseui=self._ui)
1225 # skip hidden web repository
1225 # skip hidden web repository
1226 if repo._get_hidden():
1226 if repo._get_hidden():
1227 return
1227 return
1228 else:
1228 else:
1229 repo = backend(repo_full_path, create=False)
1229 repo = backend(repo_full_path, create=False)
1230
1230
1231 return repo
1231 return repo
1232
1232
1233
1233
1234 class RepoGroup(Base, BaseModel):
1234 class RepoGroup(Base, BaseModel):
1235 __tablename__ = 'groups'
1235 __tablename__ = 'groups'
1236 __table_args__ = (
1236 __table_args__ = (
1237 UniqueConstraint('group_name', 'group_parent_id'),
1237 UniqueConstraint('group_name', 'group_parent_id'),
1238 CheckConstraint('group_id != group_parent_id'),
1238 CheckConstraint('group_id != group_parent_id'),
1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1240 'mysql_charset': 'utf8'},
1240 'mysql_charset': 'utf8'},
1241 )
1241 )
1242 __mapper_args__ = {'order_by': 'group_name'}
1242 __mapper_args__ = {'order_by': 'group_name'}
1243
1243
1244 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1244 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1245 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1245 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1246 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1246 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1247 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1247 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1248 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1248 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1249 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1249 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1250
1250
1251 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1251 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1252 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1252 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1253 parent_group = relationship('RepoGroup', remote_side=group_id)
1253 parent_group = relationship('RepoGroup', remote_side=group_id)
1254 user = relationship('User')
1254 user = relationship('User')
1255
1255
1256 def __init__(self, group_name='', parent_group=None):
1256 def __init__(self, group_name='', parent_group=None):
1257 self.group_name = group_name
1257 self.group_name = group_name
1258 self.parent_group = parent_group
1258 self.parent_group = parent_group
1259
1259
1260 def __unicode__(self):
1260 def __unicode__(self):
1261 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1261 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1262 self.group_name)
1262 self.group_name)
1263
1263
1264 @classmethod
1264 @classmethod
1265 def groups_choices(cls, groups=None, show_empty_group=True):
1265 def groups_choices(cls, groups=None, show_empty_group=True):
1266 from webhelpers.html import literal as _literal
1266 from webhelpers.html import literal as _literal
1267 if not groups:
1267 if not groups:
1268 groups = cls.query().all()
1268 groups = cls.query().all()
1269
1269
1270 repo_groups = []
1270 repo_groups = []
1271 if show_empty_group:
1271 if show_empty_group:
1272 repo_groups = [('-1', '-- %s --' % _('top level'))]
1272 repo_groups = [('-1', '-- %s --' % _('top level'))]
1273 sep = ' &raquo; '
1273 sep = ' &raquo; '
1274 _name = lambda k: _literal(sep.join(k))
1274 _name = lambda k: _literal(sep.join(k))
1275
1275
1276 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1276 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1277 for x in groups])
1277 for x in groups])
1278
1278
1279 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1279 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1280 return repo_groups
1280 return repo_groups
1281
1281
1282 @classmethod
1282 @classmethod
1283 def url_sep(cls):
1283 def url_sep(cls):
1284 return URL_SEP
1284 return URL_SEP
1285
1285
1286 @classmethod
1286 @classmethod
1287 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1287 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1288 if case_insensitive:
1288 if case_insensitive:
1289 gr = cls.query()\
1289 gr = cls.query()\
1290 .filter(cls.group_name.ilike(group_name))
1290 .filter(cls.group_name.ilike(group_name))
1291 else:
1291 else:
1292 gr = cls.query()\
1292 gr = cls.query()\
1293 .filter(cls.group_name == group_name)
1293 .filter(cls.group_name == group_name)
1294 if cache:
1294 if cache:
1295 gr = gr.options(FromCache(
1295 gr = gr.options(FromCache(
1296 "sql_cache_short",
1296 "sql_cache_short",
1297 "get_group_%s" % _hash_key(group_name)
1297 "get_group_%s" % _hash_key(group_name)
1298 )
1298 )
1299 )
1299 )
1300 return gr.scalar()
1300 return gr.scalar()
1301
1301
1302 @property
1302 @property
1303 def parents(self):
1303 def parents(self):
1304 parents_recursion_limit = 5
1304 parents_recursion_limit = 5
1305 groups = []
1305 groups = []
1306 if self.parent_group is None:
1306 if self.parent_group is None:
1307 return groups
1307 return groups
1308 cur_gr = self.parent_group
1308 cur_gr = self.parent_group
1309 groups.insert(0, cur_gr)
1309 groups.insert(0, cur_gr)
1310 cnt = 0
1310 cnt = 0
1311 while 1:
1311 while 1:
1312 cnt += 1
1312 cnt += 1
1313 gr = getattr(cur_gr, 'parent_group', None)
1313 gr = getattr(cur_gr, 'parent_group', None)
1314 cur_gr = cur_gr.parent_group
1314 cur_gr = cur_gr.parent_group
1315 if gr is None:
1315 if gr is None:
1316 break
1316 break
1317 if cnt == parents_recursion_limit:
1317 if cnt == parents_recursion_limit:
1318 # this will prevent accidental infinit loops
1318 # this will prevent accidental infinit loops
1319 log.error('group nested more than %s' %
1319 log.error('group nested more than %s' %
1320 parents_recursion_limit)
1320 parents_recursion_limit)
1321 break
1321 break
1322
1322
1323 groups.insert(0, gr)
1323 groups.insert(0, gr)
1324 return groups
1324 return groups
1325
1325
1326 @property
1326 @property
1327 def children(self):
1327 def children(self):
1328 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1328 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1329
1329
1330 @property
1330 @property
1331 def name(self):
1331 def name(self):
1332 return self.group_name.split(RepoGroup.url_sep())[-1]
1332 return self.group_name.split(RepoGroup.url_sep())[-1]
1333
1333
1334 @property
1334 @property
1335 def full_path(self):
1335 def full_path(self):
1336 return self.group_name
1336 return self.group_name
1337
1337
1338 @property
1338 @property
1339 def full_path_splitted(self):
1339 def full_path_splitted(self):
1340 return self.group_name.split(RepoGroup.url_sep())
1340 return self.group_name.split(RepoGroup.url_sep())
1341
1341
1342 @property
1342 @property
1343 def repositories(self):
1343 def repositories(self):
1344 return Repository.query()\
1344 return Repository.query()\
1345 .filter(Repository.group == self)\
1345 .filter(Repository.group == self)\
1346 .order_by(Repository.repo_name)
1346 .order_by(Repository.repo_name)
1347
1347
1348 @property
1348 @property
1349 def repositories_recursive_count(self):
1349 def repositories_recursive_count(self):
1350 cnt = self.repositories.count()
1350 cnt = self.repositories.count()
1351
1351
1352 def children_count(group):
1352 def children_count(group):
1353 cnt = 0
1353 cnt = 0
1354 for child in group.children:
1354 for child in group.children:
1355 cnt += child.repositories.count()
1355 cnt += child.repositories.count()
1356 cnt += children_count(child)
1356 cnt += children_count(child)
1357 return cnt
1357 return cnt
1358
1358
1359 return cnt + children_count(self)
1359 return cnt + children_count(self)
1360
1360
1361 def _recursive_objects(self, include_repos=True):
1361 def _recursive_objects(self, include_repos=True):
1362 all_ = []
1362 all_ = []
1363
1363
1364 def _get_members(root_gr):
1364 def _get_members(root_gr):
1365 if include_repos:
1365 if include_repos:
1366 for r in root_gr.repositories:
1366 for r in root_gr.repositories:
1367 all_.append(r)
1367 all_.append(r)
1368 childs = root_gr.children.all()
1368 childs = root_gr.children.all()
1369 if childs:
1369 if childs:
1370 for gr in childs:
1370 for gr in childs:
1371 all_.append(gr)
1371 all_.append(gr)
1372 _get_members(gr)
1372 _get_members(gr)
1373
1373
1374 _get_members(self)
1374 _get_members(self)
1375 return [self] + all_
1375 return [self] + all_
1376
1376
1377 def recursive_groups_and_repos(self):
1377 def recursive_groups_and_repos(self):
1378 """
1378 """
1379 Recursive return all groups, with repositories in those groups
1379 Recursive return all groups, with repositories in those groups
1380 """
1380 """
1381 return self._recursive_objects()
1381 return self._recursive_objects()
1382
1382
1383 def recursive_groups(self):
1383 def recursive_groups(self):
1384 """
1384 """
1385 Returns all children groups for this group including children of children
1385 Returns all children groups for this group including children of children
1386 """
1386 """
1387 return self._recursive_objects(include_repos=False)
1387 return self._recursive_objects(include_repos=False)
1388
1388
1389 def get_new_name(self, group_name):
1389 def get_new_name(self, group_name):
1390 """
1390 """
1391 returns new full group name based on parent and new name
1391 returns new full group name based on parent and new name
1392
1392
1393 :param group_name:
1393 :param group_name:
1394 """
1394 """
1395 path_prefix = (self.parent_group.full_path_splitted if
1395 path_prefix = (self.parent_group.full_path_splitted if
1396 self.parent_group else [])
1396 self.parent_group else [])
1397 return RepoGroup.url_sep().join(path_prefix + [group_name])
1397 return RepoGroup.url_sep().join(path_prefix + [group_name])
1398
1398
1399
1399
1400 class Permission(Base, BaseModel):
1400 class Permission(Base, BaseModel):
1401 __tablename__ = 'permissions'
1401 __tablename__ = 'permissions'
1402 __table_args__ = (
1402 __table_args__ = (
1403 Index('p_perm_name_idx', 'permission_name'),
1403 Index('p_perm_name_idx', 'permission_name'),
1404 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1404 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1405 'mysql_charset': 'utf8'},
1405 'mysql_charset': 'utf8'},
1406 )
1406 )
1407 PERMS = [
1407 PERMS = [
1408 ('repository.none', _('Repository no access')),
1408 ('repository.none', _('Repository no access')),
1409 ('repository.read', _('Repository read access')),
1409 ('repository.read', _('Repository read access')),
1410 ('repository.write', _('Repository write access')),
1410 ('repository.write', _('Repository write access')),
1411 ('repository.admin', _('Repository admin access')),
1411 ('repository.admin', _('Repository admin access')),
1412
1412
1413 ('group.none', _('Repository group no access')),
1413 ('group.none', _('Repository group no access')),
1414 ('group.read', _('Repository group read access')),
1414 ('group.read', _('Repository group read access')),
1415 ('group.write', _('Repository group write access')),
1415 ('group.write', _('Repository group write access')),
1416 ('group.admin', _('Repository group admin access')),
1416 ('group.admin', _('Repository group admin access')),
1417
1417
1418 ('usergroup.none', _('User group no access')),
1418 ('usergroup.none', _('User group no access')),
1419 ('usergroup.read', _('User group read access')),
1419 ('usergroup.read', _('User group read access')),
1420 ('usergroup.write', _('User group write access')),
1420 ('usergroup.write', _('User group write access')),
1421 ('usergroup.admin', _('User group admin access')),
1421 ('usergroup.admin', _('User group admin access')),
1422
1422
1423 ('hg.admin', _('RhodeCode Administrator')),
1423 ('hg.admin', _('RhodeCode Administrator')),
1424 ('hg.create.none', _('Repository creation disabled')),
1424 ('hg.create.none', _('Repository creation disabled')),
1425 ('hg.create.repository', _('Repository creation enabled')),
1425 ('hg.create.repository', _('Repository creation enabled')),
1426 ('hg.fork.none', _('Repository forking disabled')),
1426 ('hg.fork.none', _('Repository forking disabled')),
1427 ('hg.fork.repository', _('Repository forking enabled')),
1427 ('hg.fork.repository', _('Repository forking enabled')),
1428 ('hg.register.none', _('Register disabled')),
1428 ('hg.register.none', _('Register disabled')),
1429 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1429 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1430 'with manual activation')),
1430 'with manual activation')),
1431
1431
1432 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1432 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1433 'with auto activation')),
1433 'with auto activation')),
1434 ]
1434 ]
1435
1435
1436 # defines which permissions are more important higher the more important
1436 # defines which permissions are more important higher the more important
1437 PERM_WEIGHTS = {
1437 PERM_WEIGHTS = {
1438 'repository.none': 0,
1438 'repository.none': 0,
1439 'repository.read': 1,
1439 'repository.read': 1,
1440 'repository.write': 3,
1440 'repository.write': 3,
1441 'repository.admin': 4,
1441 'repository.admin': 4,
1442
1442
1443 'group.none': 0,
1443 'group.none': 0,
1444 'group.read': 1,
1444 'group.read': 1,
1445 'group.write': 3,
1445 'group.write': 3,
1446 'group.admin': 4,
1446 'group.admin': 4,
1447
1447
1448 'usergroup.none': 0,
1448 'usergroup.none': 0,
1449 'usergroup.read': 1,
1449 'usergroup.read': 1,
1450 'usergroup.write': 3,
1450 'usergroup.write': 3,
1451 'usergroup.admin': 4,
1451 'usergroup.admin': 4,
1452
1452
1453 'hg.fork.none': 0,
1453 'hg.fork.none': 0,
1454 'hg.fork.repository': 1,
1454 'hg.fork.repository': 1,
1455 'hg.create.none': 0,
1455 'hg.create.none': 0,
1456 'hg.create.repository': 1
1456 'hg.create.repository': 1
1457 }
1457 }
1458
1458
1459 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1459 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1460 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1460 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1461 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1461 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1462
1462
1463 def __unicode__(self):
1463 def __unicode__(self):
1464 return u"<%s('%s:%s')>" % (
1464 return u"<%s('%s:%s')>" % (
1465 self.__class__.__name__, self.permission_id, self.permission_name
1465 self.__class__.__name__, self.permission_id, self.permission_name
1466 )
1466 )
1467
1467
1468 @classmethod
1468 @classmethod
1469 def get_by_key(cls, key):
1469 def get_by_key(cls, key):
1470 return cls.query().filter(cls.permission_name == key).scalar()
1470 return cls.query().filter(cls.permission_name == key).scalar()
1471
1471
1472 @classmethod
1472 @classmethod
1473 def get_default_perms(cls, default_user_id):
1473 def get_default_perms(cls, default_user_id):
1474 q = Session().query(UserRepoToPerm, Repository, cls)\
1474 q = Session().query(UserRepoToPerm, Repository, cls)\
1475 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1475 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1476 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1476 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1477 .filter(UserRepoToPerm.user_id == default_user_id)
1477 .filter(UserRepoToPerm.user_id == default_user_id)
1478
1478
1479 return q.all()
1479 return q.all()
1480
1480
1481 @classmethod
1481 @classmethod
1482 def get_default_group_perms(cls, default_user_id):
1482 def get_default_group_perms(cls, default_user_id):
1483 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1483 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1484 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1484 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1485 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1485 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1486 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1486 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1487
1487
1488 return q.all()
1488 return q.all()
1489
1489
1490 @classmethod
1490 @classmethod
1491 def get_default_user_group_perms(cls, default_user_id):
1491 def get_default_user_group_perms(cls, default_user_id):
1492 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1492 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1493 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1493 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1494 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1494 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1495 .filter(UserUserGroupToPerm.user_id == default_user_id)
1495 .filter(UserUserGroupToPerm.user_id == default_user_id)
1496
1496
1497 return q.all()
1497 return q.all()
1498
1498
1499
1499
1500 class UserRepoToPerm(Base, BaseModel):
1500 class UserRepoToPerm(Base, BaseModel):
1501 __tablename__ = 'repo_to_perm'
1501 __tablename__ = 'repo_to_perm'
1502 __table_args__ = (
1502 __table_args__ = (
1503 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1503 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1504 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1504 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1505 'mysql_charset': 'utf8'}
1505 'mysql_charset': 'utf8'}
1506 )
1506 )
1507 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1507 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1508 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1508 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1509 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1509 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1510 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1510 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1511
1511
1512 user = relationship('User')
1512 user = relationship('User')
1513 repository = relationship('Repository')
1513 repository = relationship('Repository')
1514 permission = relationship('Permission')
1514 permission = relationship('Permission')
1515
1515
1516 @classmethod
1516 @classmethod
1517 def create(cls, user, repository, permission):
1517 def create(cls, user, repository, permission):
1518 n = cls()
1518 n = cls()
1519 n.user = user
1519 n.user = user
1520 n.repository = repository
1520 n.repository = repository
1521 n.permission = permission
1521 n.permission = permission
1522 Session().add(n)
1522 Session().add(n)
1523 return n
1523 return n
1524
1524
1525 def __unicode__(self):
1525 def __unicode__(self):
1526 return u'<%s => %s >' % (self.user, self.repository)
1526 return u'<%s => %s >' % (self.user, self.repository)
1527
1527
1528
1528
1529 class UserUserGroupToPerm(Base, BaseModel):
1529 class UserUserGroupToPerm(Base, BaseModel):
1530 __tablename__ = 'user_user_group_to_perm'
1530 __tablename__ = 'user_user_group_to_perm'
1531 __table_args__ = (
1531 __table_args__ = (
1532 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1532 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1533 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1533 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1534 'mysql_charset': 'utf8'}
1534 'mysql_charset': 'utf8'}
1535 )
1535 )
1536 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1536 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1537 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1537 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1538 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1538 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1539 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1539 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1540
1540
1541 user = relationship('User')
1541 user = relationship('User')
1542 user_group = relationship('UserGroup')
1542 user_group = relationship('UserGroup')
1543 permission = relationship('Permission')
1543 permission = relationship('Permission')
1544
1544
1545 @classmethod
1545 @classmethod
1546 def create(cls, user, user_group, permission):
1546 def create(cls, user, user_group, permission):
1547 n = cls()
1547 n = cls()
1548 n.user = user
1548 n.user = user
1549 n.user_group = user_group
1549 n.user_group = user_group
1550 n.permission = permission
1550 n.permission = permission
1551 Session().add(n)
1551 Session().add(n)
1552 return n
1552 return n
1553
1553
1554 def __unicode__(self):
1554 def __unicode__(self):
1555 return u'<%s => %s >' % (self.user, self.user_group)
1555 return u'<%s => %s >' % (self.user, self.user_group)
1556
1556
1557
1557
1558 class UserToPerm(Base, BaseModel):
1558 class UserToPerm(Base, BaseModel):
1559 __tablename__ = 'user_to_perm'
1559 __tablename__ = 'user_to_perm'
1560 __table_args__ = (
1560 __table_args__ = (
1561 UniqueConstraint('user_id', 'permission_id'),
1561 UniqueConstraint('user_id', 'permission_id'),
1562 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1562 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1563 'mysql_charset': 'utf8'}
1563 'mysql_charset': 'utf8'}
1564 )
1564 )
1565 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1565 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1566 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1566 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1567 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1567 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1568
1568
1569 user = relationship('User')
1569 user = relationship('User')
1570 permission = relationship('Permission', lazy='joined')
1570 permission = relationship('Permission', lazy='joined')
1571
1571
1572
1572
1573 class UserGroupRepoToPerm(Base, BaseModel):
1573 class UserGroupRepoToPerm(Base, BaseModel):
1574 __tablename__ = 'users_group_repo_to_perm'
1574 __tablename__ = 'users_group_repo_to_perm'
1575 __table_args__ = (
1575 __table_args__ = (
1576 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1576 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1578 'mysql_charset': 'utf8'}
1578 'mysql_charset': 'utf8'}
1579 )
1579 )
1580 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1580 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1581 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1581 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1582 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1582 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1583 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1583 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1584
1584
1585 users_group = relationship('UserGroup')
1585 users_group = relationship('UserGroup')
1586 permission = relationship('Permission')
1586 permission = relationship('Permission')
1587 repository = relationship('Repository')
1587 repository = relationship('Repository')
1588
1588
1589 @classmethod
1589 @classmethod
1590 def create(cls, users_group, repository, permission):
1590 def create(cls, users_group, repository, permission):
1591 n = cls()
1591 n = cls()
1592 n.users_group = users_group
1592 n.users_group = users_group
1593 n.repository = repository
1593 n.repository = repository
1594 n.permission = permission
1594 n.permission = permission
1595 Session().add(n)
1595 Session().add(n)
1596 return n
1596 return n
1597
1597
1598 def __unicode__(self):
1598 def __unicode__(self):
1599 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1599 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1600
1600
1601
1601
1602 #TODO; not sure if this will be ever used
1602 #TODO; not sure if this will be ever used
1603 class UserGroupUserGroupToPerm(Base, BaseModel):
1603 class UserGroupUserGroupToPerm(Base, BaseModel):
1604 __tablename__ = 'user_group_user_group_to_perm'
1604 __tablename__ = 'user_group_user_group_to_perm'
1605 __table_args__ = (
1605 __table_args__ = (
1606 UniqueConstraint('user_group_id', 'user_group_id', 'permission_id'),
1606 UniqueConstraint('user_group_id', 'user_group_id', 'permission_id'),
1607 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1607 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1608 'mysql_charset': 'utf8'}
1608 'mysql_charset': 'utf8'}
1609 )
1609 )
1610 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1610 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1611 target_user_group_id = Column("target_users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1611 target_user_group_id = Column("target_users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1612 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1612 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1613 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1613 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1614
1614
1615 target_user_group = relationship('UserGroup', remote_side=target_user_group_id, primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1615 target_user_group = relationship('UserGroup', remote_side=target_user_group_id, primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1616 user_group = relationship('UserGroup', remote_side=user_group_id, primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1616 user_group = relationship('UserGroup', remote_side=user_group_id, primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1617 permission = relationship('Permission')
1617 permission = relationship('Permission')
1618
1618
1619 @classmethod
1619 @classmethod
1620 def create(cls, target_user_group, user_group, permission):
1620 def create(cls, target_user_group, user_group, permission):
1621 n = cls()
1621 n = cls()
1622 n.target_user_group = target_user_group
1622 n.target_user_group = target_user_group
1623 n.user_group = user_group
1623 n.user_group = user_group
1624 n.permission = permission
1624 n.permission = permission
1625 Session().add(n)
1625 Session().add(n)
1626 return n
1626 return n
1627
1627
1628 def __unicode__(self):
1628 def __unicode__(self):
1629 return u'<UserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1629 return u'<UserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1630
1630
1631
1631
1632 class UserGroupToPerm(Base, BaseModel):
1632 class UserGroupToPerm(Base, BaseModel):
1633 __tablename__ = 'users_group_to_perm'
1633 __tablename__ = 'users_group_to_perm'
1634 __table_args__ = (
1634 __table_args__ = (
1635 UniqueConstraint('users_group_id', 'permission_id',),
1635 UniqueConstraint('users_group_id', 'permission_id',),
1636 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1636 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1637 'mysql_charset': 'utf8'}
1637 'mysql_charset': 'utf8'}
1638 )
1638 )
1639 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1639 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1640 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1640 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1641 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1641 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1642
1642
1643 users_group = relationship('UserGroup')
1643 users_group = relationship('UserGroup')
1644 permission = relationship('Permission')
1644 permission = relationship('Permission')
1645
1645
1646
1646
1647 class UserRepoGroupToPerm(Base, BaseModel):
1647 class UserRepoGroupToPerm(Base, BaseModel):
1648 __tablename__ = 'user_repo_group_to_perm'
1648 __tablename__ = 'user_repo_group_to_perm'
1649 __table_args__ = (
1649 __table_args__ = (
1650 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1650 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1651 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1651 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1652 'mysql_charset': 'utf8'}
1652 'mysql_charset': 'utf8'}
1653 )
1653 )
1654
1654
1655 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1655 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1656 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1656 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1657 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1657 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1658 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1658 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1659
1659
1660 user = relationship('User')
1660 user = relationship('User')
1661 group = relationship('RepoGroup')
1661 group = relationship('RepoGroup')
1662 permission = relationship('Permission')
1662 permission = relationship('Permission')
1663
1663
1664
1664
1665 class UserGroupRepoGroupToPerm(Base, BaseModel):
1665 class UserGroupRepoGroupToPerm(Base, BaseModel):
1666 __tablename__ = 'users_group_repo_group_to_perm'
1666 __tablename__ = 'users_group_repo_group_to_perm'
1667 __table_args__ = (
1667 __table_args__ = (
1668 UniqueConstraint('users_group_id', 'group_id'),
1668 UniqueConstraint('users_group_id', 'group_id'),
1669 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1669 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1670 'mysql_charset': 'utf8'}
1670 'mysql_charset': 'utf8'}
1671 )
1671 )
1672
1672
1673 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)
1673 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)
1674 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1674 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1675 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1675 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1676 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1676 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1677
1677
1678 users_group = relationship('UserGroup')
1678 users_group = relationship('UserGroup')
1679 permission = relationship('Permission')
1679 permission = relationship('Permission')
1680 group = relationship('RepoGroup')
1680 group = relationship('RepoGroup')
1681
1681
1682
1682
1683 class Statistics(Base, BaseModel):
1683 class Statistics(Base, BaseModel):
1684 __tablename__ = 'statistics'
1684 __tablename__ = 'statistics'
1685 __table_args__ = (
1685 __table_args__ = (
1686 UniqueConstraint('repository_id'),
1686 UniqueConstraint('repository_id'),
1687 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1687 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1688 'mysql_charset': 'utf8'}
1688 'mysql_charset': 'utf8'}
1689 )
1689 )
1690 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1690 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1692 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1692 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1693 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1693 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1694 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1694 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1695 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1695 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1696
1696
1697 repository = relationship('Repository', single_parent=True)
1697 repository = relationship('Repository', single_parent=True)
1698
1698
1699
1699
1700 class UserFollowing(Base, BaseModel):
1700 class UserFollowing(Base, BaseModel):
1701 __tablename__ = 'user_followings'
1701 __tablename__ = 'user_followings'
1702 __table_args__ = (
1702 __table_args__ = (
1703 UniqueConstraint('user_id', 'follows_repository_id'),
1703 UniqueConstraint('user_id', 'follows_repository_id'),
1704 UniqueConstraint('user_id', 'follows_user_id'),
1704 UniqueConstraint('user_id', 'follows_user_id'),
1705 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1705 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1706 'mysql_charset': 'utf8'}
1706 'mysql_charset': 'utf8'}
1707 )
1707 )
1708
1708
1709 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1709 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1710 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1710 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1711 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1711 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1712 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1712 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1713 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1713 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1714
1714
1715 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1715 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1716
1716
1717 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1717 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1718 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1718 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1719
1719
1720 @classmethod
1720 @classmethod
1721 def get_repo_followers(cls, repo_id):
1721 def get_repo_followers(cls, repo_id):
1722 return cls.query().filter(cls.follows_repo_id == repo_id)
1722 return cls.query().filter(cls.follows_repo_id == repo_id)
1723
1723
1724
1724
1725 class CacheInvalidation(Base, BaseModel):
1725 class CacheInvalidation(Base, BaseModel):
1726 __tablename__ = 'cache_invalidation'
1726 __tablename__ = 'cache_invalidation'
1727 __table_args__ = (
1727 __table_args__ = (
1728 UniqueConstraint('cache_key'),
1728 UniqueConstraint('cache_key'),
1729 Index('key_idx', 'cache_key'),
1729 Index('key_idx', 'cache_key'),
1730 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1730 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1731 'mysql_charset': 'utf8'},
1731 'mysql_charset': 'utf8'},
1732 )
1732 )
1733 # cache_id, not used
1733 # cache_id, not used
1734 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1734 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1735 # cache_key as created by _get_cache_key
1735 # cache_key as created by _get_cache_key
1736 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1736 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1737 # cache_args is a repo_name
1737 # cache_args is a repo_name
1738 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1738 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1739 # instance sets cache_active True when it is caching,
1739 # instance sets cache_active True when it is caching,
1740 # other instances set cache_active to False to indicate that this cache is invalid
1740 # other instances set cache_active to False to indicate that this cache is invalid
1741 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1741 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1742
1742
1743 def __init__(self, cache_key, repo_name=''):
1743 def __init__(self, cache_key, repo_name=''):
1744 self.cache_key = cache_key
1744 self.cache_key = cache_key
1745 self.cache_args = repo_name
1745 self.cache_args = repo_name
1746 self.cache_active = False
1746 self.cache_active = False
1747
1747
1748 def __unicode__(self):
1748 def __unicode__(self):
1749 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1749 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1750 self.cache_id, self.cache_key, self.cache_active)
1750 self.cache_id, self.cache_key, self.cache_active)
1751
1751
1752 def _cache_key_partition(self):
1752 def _cache_key_partition(self):
1753 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1753 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1754 return prefix, repo_name, suffix
1754 return prefix, repo_name, suffix
1755
1755
1756 def get_prefix(self):
1756 def get_prefix(self):
1757 """
1757 """
1758 get prefix that might have been used in _get_cache_key to
1758 get prefix that might have been used in _get_cache_key to
1759 generate self.cache_key. Only used for informational purposes
1759 generate self.cache_key. Only used for informational purposes
1760 in repo_edit.html.
1760 in repo_edit.html.
1761 """
1761 """
1762 # prefix, repo_name, suffix
1762 # prefix, repo_name, suffix
1763 return self._cache_key_partition()[0]
1763 return self._cache_key_partition()[0]
1764
1764
1765 def get_suffix(self):
1765 def get_suffix(self):
1766 """
1766 """
1767 get suffix that might have been used in _get_cache_key to
1767 get suffix that might have been used in _get_cache_key to
1768 generate self.cache_key. Only used for informational purposes
1768 generate self.cache_key. Only used for informational purposes
1769 in repo_edit.html.
1769 in repo_edit.html.
1770 """
1770 """
1771 # prefix, repo_name, suffix
1771 # prefix, repo_name, suffix
1772 return self._cache_key_partition()[2]
1772 return self._cache_key_partition()[2]
1773
1773
1774 @classmethod
1774 @classmethod
1775 def _get_cache_key(cls, key):
1775 def _get_cache_key(cls, key):
1776 """
1776 """
1777 Wrapper for generating a unique cache key for this instance and "key".
1777 Wrapper for generating a unique cache key for this instance and "key".
1778 key must / will start with a repo_name which will be stored in .cache_args .
1778 key must / will start with a repo_name which will be stored in .cache_args .
1779 """
1779 """
1780 import rhodecode
1780 import rhodecode
1781 prefix = rhodecode.CONFIG.get('instance_id', '')
1781 prefix = rhodecode.CONFIG.get('instance_id', '')
1782 return "%s%s" % (prefix, key)
1782 return "%s%s" % (prefix, key)
1783
1783
1784 @classmethod
1784 @classmethod
1785 def invalidate(cls, key):
1785 def invalidate(cls, key):
1786 """
1786 """
1787 Returns Invalidation object if the local cache with the given key is invalid,
1787 Returns Invalidation object if the local cache with the given key is invalid,
1788 None otherwise.
1788 None otherwise.
1789 """
1789 """
1790 repo_name = key
1790 repo_name = key
1791 repo_name = remove_suffix(repo_name, '_README')
1791 repo_name = remove_suffix(repo_name, '_README')
1792 repo_name = remove_suffix(repo_name, '_RSS')
1792 repo_name = remove_suffix(repo_name, '_RSS')
1793 repo_name = remove_suffix(repo_name, '_ATOM')
1793 repo_name = remove_suffix(repo_name, '_ATOM')
1794
1794
1795 cache_key = cls._get_cache_key(key)
1795 cache_key = cls._get_cache_key(key)
1796 inv_obj = Session().query(cls).filter(cls.cache_key == cache_key).scalar()
1796 inv_obj = Session().query(cls).filter(cls.cache_key == cache_key).scalar()
1797 if not inv_obj:
1797 if not inv_obj:
1798 try:
1798 try:
1799 inv_obj = CacheInvalidation(cache_key, repo_name)
1799 inv_obj = CacheInvalidation(cache_key, repo_name)
1800 Session().add(inv_obj)
1800 Session().add(inv_obj)
1801 Session().commit()
1801 Session().commit()
1802 except Exception:
1802 except Exception:
1803 log.error(traceback.format_exc())
1803 log.error(traceback.format_exc())
1804 Session().rollback()
1804 Session().rollback()
1805 return
1805 return
1806
1806
1807 if not inv_obj.cache_active:
1807 if not inv_obj.cache_active:
1808 # `cache_active = False` means that this cache
1808 # `cache_active = False` means that this cache
1809 # no longer is valid
1809 # no longer is valid
1810 return inv_obj
1810 return inv_obj
1811
1811
1812 @classmethod
1812 @classmethod
1813 def set_invalidate(cls, repo_name):
1813 def set_invalidate(cls, repo_name):
1814 """
1814 """
1815 Mark all caches of a repo as invalid in the database.
1815 Mark all caches of a repo as invalid in the database.
1816 """
1816 """
1817 invalidated_keys = []
1817 invalidated_keys = []
1818 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1818 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1819
1819
1820 try:
1820 try:
1821 for inv_obj in inv_objs:
1821 for inv_obj in inv_objs:
1822 log.debug('marking %s key for invalidation based on repo_name=%s'
1822 log.debug('marking %s key for invalidation based on repo_name=%s'
1823 % (inv_obj, safe_str(repo_name)))
1823 % (inv_obj, safe_str(repo_name)))
1824 inv_obj.cache_active = False
1824 inv_obj.cache_active = False
1825 invalidated_keys.append(inv_obj.cache_key)
1825 invalidated_keys.append(inv_obj.cache_key)
1826 Session().add(inv_obj)
1826 Session().add(inv_obj)
1827 Session().commit()
1827 Session().commit()
1828 except Exception:
1828 except Exception:
1829 log.error(traceback.format_exc())
1829 log.error(traceback.format_exc())
1830 Session().rollback()
1830 Session().rollback()
1831 return invalidated_keys
1831 return invalidated_keys
1832
1832
1833 @classmethod
1833 @classmethod
1834 def set_valid(cls, cache_key):
1834 def set_valid(cls, cache_key):
1835 """
1835 """
1836 Mark this cache key as active and currently cached
1836 Mark this cache key as active and currently cached
1837 """
1837 """
1838 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1838 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1839 inv_obj.cache_active = True
1839 inv_obj.cache_active = True
1840 Session().add(inv_obj)
1840 Session().add(inv_obj)
1841 Session().commit()
1841 Session().commit()
1842
1842
1843 @classmethod
1843 @classmethod
1844 def get_cache_map(cls):
1844 def get_cache_map(cls):
1845
1845
1846 class cachemapdict(dict):
1846 class cachemapdict(dict):
1847
1847
1848 def __init__(self, *args, **kwargs):
1848 def __init__(self, *args, **kwargs):
1849 self.fixkey = kwargs.pop('fixkey', False)
1849 self.fixkey = kwargs.pop('fixkey', False)
1850 super(cachemapdict, self).__init__(*args, **kwargs)
1850 super(cachemapdict, self).__init__(*args, **kwargs)
1851
1851
1852 def __getattr__(self, name):
1852 def __getattr__(self, name):
1853 cache_key = name
1853 cache_key = name
1854 if self.fixkey:
1854 if self.fixkey:
1855 cache_key = cls._get_cache_key(name)
1855 cache_key = cls._get_cache_key(name)
1856 if cache_key in self.__dict__:
1856 if cache_key in self.__dict__:
1857 return self.__dict__[cache_key]
1857 return self.__dict__[cache_key]
1858 else:
1858 else:
1859 return self[cache_key]
1859 return self[cache_key]
1860
1860
1861 def __getitem__(self, name):
1861 def __getitem__(self, name):
1862 cache_key = name
1862 cache_key = name
1863 if self.fixkey:
1863 if self.fixkey:
1864 cache_key = cls._get_cache_key(name)
1864 cache_key = cls._get_cache_key(name)
1865 try:
1865 try:
1866 return super(cachemapdict, self).__getitem__(cache_key)
1866 return super(cachemapdict, self).__getitem__(cache_key)
1867 except KeyError:
1867 except KeyError:
1868 return None
1868 return None
1869
1869
1870 cache_map = cachemapdict(fixkey=True)
1870 cache_map = cachemapdict(fixkey=True)
1871 for obj in cls.query().all():
1871 for obj in cls.query().all():
1872 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1872 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1873 return cache_map
1873 return cache_map
1874
1874
1875
1875
1876 class ChangesetComment(Base, BaseModel):
1876 class ChangesetComment(Base, BaseModel):
1877 __tablename__ = 'changeset_comments'
1877 __tablename__ = 'changeset_comments'
1878 __table_args__ = (
1878 __table_args__ = (
1879 Index('cc_revision_idx', 'revision'),
1879 Index('cc_revision_idx', 'revision'),
1880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1881 'mysql_charset': 'utf8'},
1881 'mysql_charset': 'utf8'},
1882 )
1882 )
1883 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1883 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1884 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1884 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1885 revision = Column('revision', String(40), nullable=True)
1885 revision = Column('revision', String(40), nullable=True)
1886 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1886 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1887 line_no = Column('line_no', Unicode(10), nullable=True)
1887 line_no = Column('line_no', Unicode(10), nullable=True)
1888 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1888 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1889 f_path = Column('f_path', Unicode(1000), nullable=True)
1889 f_path = Column('f_path', Unicode(1000), nullable=True)
1890 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1890 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1891 text = Column('text', UnicodeText(25000), nullable=False)
1891 text = Column('text', UnicodeText(25000), nullable=False)
1892 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1892 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1893 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1893 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1894
1894
1895 author = relationship('User', lazy='joined')
1895 author = relationship('User', lazy='joined')
1896 repo = relationship('Repository')
1896 repo = relationship('Repository')
1897 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1897 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1898 pull_request = relationship('PullRequest', lazy='joined')
1898 pull_request = relationship('PullRequest', lazy='joined')
1899
1899
1900 @classmethod
1900 @classmethod
1901 def get_users(cls, revision=None, pull_request_id=None):
1901 def get_users(cls, revision=None, pull_request_id=None):
1902 """
1902 """
1903 Returns user associated with this ChangesetComment. ie those
1903 Returns user associated with this ChangesetComment. ie those
1904 who actually commented
1904 who actually commented
1905
1905
1906 :param cls:
1906 :param cls:
1907 :param revision:
1907 :param revision:
1908 """
1908 """
1909 q = Session().query(User)\
1909 q = Session().query(User)\
1910 .join(ChangesetComment.author)
1910 .join(ChangesetComment.author)
1911 if revision:
1911 if revision:
1912 q = q.filter(cls.revision == revision)
1912 q = q.filter(cls.revision == revision)
1913 elif pull_request_id:
1913 elif pull_request_id:
1914 q = q.filter(cls.pull_request_id == pull_request_id)
1914 q = q.filter(cls.pull_request_id == pull_request_id)
1915 return q.all()
1915 return q.all()
1916
1916
1917
1917
1918 class ChangesetStatus(Base, BaseModel):
1918 class ChangesetStatus(Base, BaseModel):
1919 __tablename__ = 'changeset_statuses'
1919 __tablename__ = 'changeset_statuses'
1920 __table_args__ = (
1920 __table_args__ = (
1921 Index('cs_revision_idx', 'revision'),
1921 Index('cs_revision_idx', 'revision'),
1922 Index('cs_version_idx', 'version'),
1922 Index('cs_version_idx', 'version'),
1923 UniqueConstraint('repo_id', 'revision', 'version'),
1923 UniqueConstraint('repo_id', 'revision', 'version'),
1924 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1924 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1925 'mysql_charset': 'utf8'}
1925 'mysql_charset': 'utf8'}
1926 )
1926 )
1927 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1927 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1928 STATUS_APPROVED = 'approved'
1928 STATUS_APPROVED = 'approved'
1929 STATUS_REJECTED = 'rejected'
1929 STATUS_REJECTED = 'rejected'
1930 STATUS_UNDER_REVIEW = 'under_review'
1930 STATUS_UNDER_REVIEW = 'under_review'
1931
1931
1932 STATUSES = [
1932 STATUSES = [
1933 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1933 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1934 (STATUS_APPROVED, _("Approved")),
1934 (STATUS_APPROVED, _("Approved")),
1935 (STATUS_REJECTED, _("Rejected")),
1935 (STATUS_REJECTED, _("Rejected")),
1936 (STATUS_UNDER_REVIEW, _("Under Review")),
1936 (STATUS_UNDER_REVIEW, _("Under Review")),
1937 ]
1937 ]
1938
1938
1939 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1939 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1940 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1940 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1941 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1941 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1942 revision = Column('revision', String(40), nullable=False)
1942 revision = Column('revision', String(40), nullable=False)
1943 status = Column('status', String(128), nullable=False, default=DEFAULT)
1943 status = Column('status', String(128), nullable=False, default=DEFAULT)
1944 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1944 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1945 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1945 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1946 version = Column('version', Integer(), nullable=False, default=0)
1946 version = Column('version', Integer(), nullable=False, default=0)
1947 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1947 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1948
1948
1949 author = relationship('User', lazy='joined')
1949 author = relationship('User', lazy='joined')
1950 repo = relationship('Repository')
1950 repo = relationship('Repository')
1951 comment = relationship('ChangesetComment', lazy='joined')
1951 comment = relationship('ChangesetComment', lazy='joined')
1952 pull_request = relationship('PullRequest', lazy='joined')
1952 pull_request = relationship('PullRequest', lazy='joined')
1953
1953
1954 def __unicode__(self):
1954 def __unicode__(self):
1955 return u"<%s('%s:%s')>" % (
1955 return u"<%s('%s:%s')>" % (
1956 self.__class__.__name__,
1956 self.__class__.__name__,
1957 self.status, self.author
1957 self.status, self.author
1958 )
1958 )
1959
1959
1960 @classmethod
1960 @classmethod
1961 def get_status_lbl(cls, value):
1961 def get_status_lbl(cls, value):
1962 return dict(cls.STATUSES).get(value)
1962 return dict(cls.STATUSES).get(value)
1963
1963
1964 @property
1964 @property
1965 def status_lbl(self):
1965 def status_lbl(self):
1966 return ChangesetStatus.get_status_lbl(self.status)
1966 return ChangesetStatus.get_status_lbl(self.status)
1967
1967
1968
1968
1969 class PullRequest(Base, BaseModel):
1969 class PullRequest(Base, BaseModel):
1970 __tablename__ = 'pull_requests'
1970 __tablename__ = 'pull_requests'
1971 __table_args__ = (
1971 __table_args__ = (
1972 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1972 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1973 'mysql_charset': 'utf8'},
1973 'mysql_charset': 'utf8'},
1974 )
1974 )
1975
1975
1976 STATUS_NEW = u'new'
1976 STATUS_NEW = u'new'
1977 STATUS_OPEN = u'open'
1977 STATUS_OPEN = u'open'
1978 STATUS_CLOSED = u'closed'
1978 STATUS_CLOSED = u'closed'
1979
1979
1980 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1980 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1981 title = Column('title', Unicode(256), nullable=True)
1981 title = Column('title', Unicode(256), nullable=True)
1982 description = Column('description', UnicodeText(10240), nullable=True)
1982 description = Column('description', UnicodeText(10240), nullable=True)
1983 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1983 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1984 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1984 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1985 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1985 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1986 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1986 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1987 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1987 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1988 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1988 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1989 org_ref = Column('org_ref', Unicode(256), nullable=False)
1989 org_ref = Column('org_ref', Unicode(256), nullable=False)
1990 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1990 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1991 other_ref = Column('other_ref', Unicode(256), nullable=False)
1991 other_ref = Column('other_ref', Unicode(256), nullable=False)
1992
1992
1993 @hybrid_property
1993 @hybrid_property
1994 def revisions(self):
1994 def revisions(self):
1995 return self._revisions.split(':')
1995 return self._revisions.split(':')
1996
1996
1997 @revisions.setter
1997 @revisions.setter
1998 def revisions(self, val):
1998 def revisions(self, val):
1999 self._revisions = ':'.join(val)
1999 self._revisions = ':'.join(val)
2000
2000
2001 @property
2001 @property
2002 def org_ref_parts(self):
2002 def org_ref_parts(self):
2003 return self.org_ref.split(':')
2003 return self.org_ref.split(':')
2004
2004
2005 @property
2005 @property
2006 def other_ref_parts(self):
2006 def other_ref_parts(self):
2007 return self.other_ref.split(':')
2007 return self.other_ref.split(':')
2008
2008
2009 author = relationship('User', lazy='joined')
2009 author = relationship('User', lazy='joined')
2010 reviewers = relationship('PullRequestReviewers',
2010 reviewers = relationship('PullRequestReviewers',
2011 cascade="all, delete, delete-orphan")
2011 cascade="all, delete, delete-orphan")
2012 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2012 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2013 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2013 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2014 statuses = relationship('ChangesetStatus')
2014 statuses = relationship('ChangesetStatus')
2015 comments = relationship('ChangesetComment',
2015 comments = relationship('ChangesetComment',
2016 cascade="all, delete, delete-orphan")
2016 cascade="all, delete, delete-orphan")
2017
2017
2018 def is_closed(self):
2018 def is_closed(self):
2019 return self.status == self.STATUS_CLOSED
2019 return self.status == self.STATUS_CLOSED
2020
2020
2021 @property
2021 @property
2022 def last_review_status(self):
2022 def last_review_status(self):
2023 return self.statuses[-1].status if self.statuses else ''
2023 return self.statuses[-1].status if self.statuses else ''
2024
2024
2025 def __json__(self):
2025 def __json__(self):
2026 return dict(
2026 return dict(
2027 revisions=self.revisions
2027 revisions=self.revisions
2028 )
2028 )
2029
2029
2030
2030
2031 class PullRequestReviewers(Base, BaseModel):
2031 class PullRequestReviewers(Base, BaseModel):
2032 __tablename__ = 'pull_request_reviewers'
2032 __tablename__ = 'pull_request_reviewers'
2033 __table_args__ = (
2033 __table_args__ = (
2034 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2034 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2035 'mysql_charset': 'utf8'},
2035 'mysql_charset': 'utf8'},
2036 )
2036 )
2037
2037
2038 def __init__(self, user=None, pull_request=None):
2038 def __init__(self, user=None, pull_request=None):
2039 self.user = user
2039 self.user = user
2040 self.pull_request = pull_request
2040 self.pull_request = pull_request
2041
2041
2042 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2042 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2043 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2043 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2044 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2044 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2045
2045
2046 user = relationship('User')
2046 user = relationship('User')
2047 pull_request = relationship('PullRequest')
2047 pull_request = relationship('PullRequest')
2048
2048
2049
2049
2050 class Notification(Base, BaseModel):
2050 class Notification(Base, BaseModel):
2051 __tablename__ = 'notifications'
2051 __tablename__ = 'notifications'
2052 __table_args__ = (
2052 __table_args__ = (
2053 Index('notification_type_idx', 'type'),
2053 Index('notification_type_idx', 'type'),
2054 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2054 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2055 'mysql_charset': 'utf8'},
2055 'mysql_charset': 'utf8'},
2056 )
2056 )
2057
2057
2058 TYPE_CHANGESET_COMMENT = u'cs_comment'
2058 TYPE_CHANGESET_COMMENT = u'cs_comment'
2059 TYPE_MESSAGE = u'message'
2059 TYPE_MESSAGE = u'message'
2060 TYPE_MENTION = u'mention'
2060 TYPE_MENTION = u'mention'
2061 TYPE_REGISTRATION = u'registration'
2061 TYPE_REGISTRATION = u'registration'
2062 TYPE_PULL_REQUEST = u'pull_request'
2062 TYPE_PULL_REQUEST = u'pull_request'
2063 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2063 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2064
2064
2065 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2065 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2066 subject = Column('subject', Unicode(512), nullable=True)
2066 subject = Column('subject', Unicode(512), nullable=True)
2067 body = Column('body', UnicodeText(50000), nullable=True)
2067 body = Column('body', UnicodeText(50000), nullable=True)
2068 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2068 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2069 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2069 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2070 type_ = Column('type', Unicode(256))
2070 type_ = Column('type', Unicode(256))
2071
2071
2072 created_by_user = relationship('User')
2072 created_by_user = relationship('User')
2073 notifications_to_users = relationship('UserNotification', lazy='joined',
2073 notifications_to_users = relationship('UserNotification', lazy='joined',
2074 cascade="all, delete, delete-orphan")
2074 cascade="all, delete, delete-orphan")
2075
2075
2076 @property
2076 @property
2077 def recipients(self):
2077 def recipients(self):
2078 return [x.user for x in UserNotification.query()\
2078 return [x.user for x in UserNotification.query()\
2079 .filter(UserNotification.notification == self)\
2079 .filter(UserNotification.notification == self)\
2080 .order_by(UserNotification.user_id.asc()).all()]
2080 .order_by(UserNotification.user_id.asc()).all()]
2081
2081
2082 @classmethod
2082 @classmethod
2083 def create(cls, created_by, subject, body, recipients, type_=None):
2083 def create(cls, created_by, subject, body, recipients, type_=None):
2084 if type_ is None:
2084 if type_ is None:
2085 type_ = Notification.TYPE_MESSAGE
2085 type_ = Notification.TYPE_MESSAGE
2086
2086
2087 notification = cls()
2087 notification = cls()
2088 notification.created_by_user = created_by
2088 notification.created_by_user = created_by
2089 notification.subject = subject
2089 notification.subject = subject
2090 notification.body = body
2090 notification.body = body
2091 notification.type_ = type_
2091 notification.type_ = type_
2092 notification.created_on = datetime.datetime.now()
2092 notification.created_on = datetime.datetime.now()
2093
2093
2094 for u in recipients:
2094 for u in recipients:
2095 assoc = UserNotification()
2095 assoc = UserNotification()
2096 assoc.notification = notification
2096 assoc.notification = notification
2097 u.notifications.append(assoc)
2097 u.notifications.append(assoc)
2098 Session().add(notification)
2098 Session().add(notification)
2099 return notification
2099 return notification
2100
2100
2101 @property
2101 @property
2102 def description(self):
2102 def description(self):
2103 from rhodecode.model.notification import NotificationModel
2103 from rhodecode.model.notification import NotificationModel
2104 return NotificationModel().make_description(self)
2104 return NotificationModel().make_description(self)
2105
2105
2106
2106
2107 class UserNotification(Base, BaseModel):
2107 class UserNotification(Base, BaseModel):
2108 __tablename__ = 'user_to_notification'
2108 __tablename__ = 'user_to_notification'
2109 __table_args__ = (
2109 __table_args__ = (
2110 UniqueConstraint('user_id', 'notification_id'),
2110 UniqueConstraint('user_id', 'notification_id'),
2111 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2111 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2112 'mysql_charset': 'utf8'}
2112 'mysql_charset': 'utf8'}
2113 )
2113 )
2114 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2114 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2115 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2115 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2116 read = Column('read', Boolean, default=False)
2116 read = Column('read', Boolean, default=False)
2117 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2117 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2118
2118
2119 user = relationship('User', lazy="joined")
2119 user = relationship('User', lazy="joined")
2120 notification = relationship('Notification', lazy="joined",
2120 notification = relationship('Notification', lazy="joined",
2121 order_by=lambda: Notification.created_on.desc(),)
2121 order_by=lambda: Notification.created_on.desc(),)
2122
2122
2123 def mark_as_read(self):
2123 def mark_as_read(self):
2124 self.read = True
2124 self.read = True
2125 Session().add(self)
2125 Session().add(self)
2126
2126
2127
2127
2128 class DbMigrateVersion(Base, BaseModel):
2128 class DbMigrateVersion(Base, BaseModel):
2129 __tablename__ = 'db_migrate_version'
2129 __tablename__ = 'db_migrate_version'
2130 __table_args__ = (
2130 __table_args__ = (
2131 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2131 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2132 'mysql_charset': 'utf8'},
2132 'mysql_charset': 'utf8'},
2133 )
2133 )
2134 repository_id = Column('repository_id', String(250), primary_key=True)
2134 repository_id = Column('repository_id', String(250), primary_key=True)
2135 repository_path = Column('repository_path', Text)
2135 repository_path = Column('repository_path', Text)
2136 version = Column('version', Integer)
2136 version = Column('version', Integer)
@@ -1,696 +1,694 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 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 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import re
27 import re
28 import time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31 import cStringIO
31 import cStringIO
32 import pkg_resources
32 import pkg_resources
33 from os.path import dirname as dn, join as jn
33 from os.path import dirname as dn, join as jn
34
34
35 from sqlalchemy import func
35 from sqlalchemy import func
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.lib.vcs import get_backend
39 from rhodecode.lib.vcs import get_backend
40 from rhodecode.lib.vcs.exceptions import RepositoryError
40 from rhodecode.lib.vcs.exceptions import RepositoryError
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 from rhodecode.lib.vcs.nodes import FileNode
42 from rhodecode.lib.vcs.nodes import FileNode
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44
44
45 from rhodecode import BACKENDS
45 from rhodecode import BACKENDS
46 from rhodecode.lib import helpers as h
46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\
47 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\
48 _set_extras
48 _set_extras
49 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny,\
49 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny,\
50 HasUserGroupPermissionAnyDecorator, HasUserGroupPermissionAny
50 HasUserGroupPermissionAnyDecorator, HasUserGroupPermissionAny
51 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
51 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
52 action_logger, REMOVED_REPO_PAT
52 action_logger, REMOVED_REPO_PAT
53 from rhodecode.model import BaseModel
53 from rhodecode.model import BaseModel
54 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
54 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
55 UserFollowing, UserLog, User, RepoGroup, PullRequest
55 UserFollowing, UserLog, User, RepoGroup, PullRequest
56 from rhodecode.lib.hooks import log_push_action
56 from rhodecode.lib.hooks import log_push_action
57
57
58 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
59
59
60
60
61 class UserTemp(object):
61 class UserTemp(object):
62 def __init__(self, user_id):
62 def __init__(self, user_id):
63 self.user_id = user_id
63 self.user_id = user_id
64
64
65 def __repr__(self):
65 def __repr__(self):
66 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
66 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
67
67
68
68
69 class RepoTemp(object):
69 class RepoTemp(object):
70 def __init__(self, repo_id):
70 def __init__(self, repo_id):
71 self.repo_id = repo_id
71 self.repo_id = repo_id
72
72
73 def __repr__(self):
73 def __repr__(self):
74 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
74 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
75
75
76
76
77 class CachedRepoList(object):
77 class CachedRepoList(object):
78 """
78 """
79 Cached repo list, uses in-memory cache after initialization, that is
79 Cached repo list, uses in-memory cache after initialization, that is
80 super fast
80 super fast
81 """
81 """
82
82
83 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
83 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
84 self.db_repo_list = db_repo_list
84 self.db_repo_list = db_repo_list
85 self.repos_path = repos_path
85 self.repos_path = repos_path
86 self.order_by = order_by
86 self.order_by = order_by
87 self.reversed = (order_by or '').startswith('-')
87 self.reversed = (order_by or '').startswith('-')
88 if not perm_set:
88 if not perm_set:
89 perm_set = ['repository.read', 'repository.write',
89 perm_set = ['repository.read', 'repository.write',
90 'repository.admin']
90 'repository.admin']
91 self.perm_set = perm_set
91 self.perm_set = perm_set
92
92
93 def __len__(self):
93 def __len__(self):
94 return len(self.db_repo_list)
94 return len(self.db_repo_list)
95
95
96 def __repr__(self):
96 def __repr__(self):
97 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
97 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
98
98
99 def __iter__(self):
99 def __iter__(self):
100 # pre-propagated cache_map to save executing select statements
100 # pre-propagated cache_map to save executing select statements
101 # for each repo
101 # for each repo
102 cache_map = CacheInvalidation.get_cache_map()
102 cache_map = CacheInvalidation.get_cache_map()
103
103
104 for dbr in self.db_repo_list:
104 for dbr in self.db_repo_list:
105 scmr = dbr.scm_instance_cached(cache_map)
105 scmr = dbr.scm_instance_cached(cache_map)
106 # check permission at this level
106 # check permission at this level
107 if not HasRepoPermissionAny(
107 if not HasRepoPermissionAny(
108 *self.perm_set
108 *self.perm_set)(dbr.repo_name, 'get repo check'):
109 )(dbr.repo_name, 'get repo check'):
110 continue
109 continue
111
110
112 try:
111 try:
113 last_change = scmr.last_change
112 last_change = scmr.last_change
114 tip = h.get_changeset_safe(scmr, 'tip')
113 tip = h.get_changeset_safe(scmr, 'tip')
115 except Exception:
114 except Exception:
116 log.error(
115 log.error(
117 '%s this repository is present in database but it '
116 '%s this repository is present in database but it '
118 'cannot be created as an scm instance, org_exc:%s'
117 'cannot be created as an scm instance, org_exc:%s'
119 % (dbr.repo_name, traceback.format_exc())
118 % (dbr.repo_name, traceback.format_exc())
120 )
119 )
121 continue
120 continue
122
121
123 tmp_d = {}
122 tmp_d = {}
124 tmp_d['name'] = dbr.repo_name
123 tmp_d['name'] = dbr.repo_name
125 tmp_d['name_sort'] = tmp_d['name'].lower()
124 tmp_d['name_sort'] = tmp_d['name'].lower()
126 tmp_d['raw_name'] = tmp_d['name'].lower()
125 tmp_d['raw_name'] = tmp_d['name'].lower()
127 tmp_d['description'] = dbr.description
126 tmp_d['description'] = dbr.description
128 tmp_d['description_sort'] = tmp_d['description'].lower()
127 tmp_d['description_sort'] = tmp_d['description'].lower()
129 tmp_d['last_change'] = last_change
128 tmp_d['last_change'] = last_change
130 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
129 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
131 tmp_d['tip'] = tip.raw_id
130 tmp_d['tip'] = tip.raw_id
132 tmp_d['tip_sort'] = tip.revision
131 tmp_d['tip_sort'] = tip.revision
133 tmp_d['rev'] = tip.revision
132 tmp_d['rev'] = tip.revision
134 tmp_d['contact'] = dbr.user.full_contact
133 tmp_d['contact'] = dbr.user.full_contact
135 tmp_d['contact_sort'] = tmp_d['contact']
134 tmp_d['contact_sort'] = tmp_d['contact']
136 tmp_d['owner_sort'] = tmp_d['contact']
135 tmp_d['owner_sort'] = tmp_d['contact']
137 tmp_d['repo_archives'] = list(scmr._get_archives())
136 tmp_d['repo_archives'] = list(scmr._get_archives())
138 tmp_d['last_msg'] = tip.message
137 tmp_d['last_msg'] = tip.message
139 tmp_d['author'] = tip.author
138 tmp_d['author'] = tip.author
140 tmp_d['dbrepo'] = dbr.get_dict()
139 tmp_d['dbrepo'] = dbr.get_dict()
141 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
140 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
142 yield tmp_d
141 yield tmp_d
143
142
144
143
145 class SimpleCachedRepoList(CachedRepoList):
144 class SimpleCachedRepoList(CachedRepoList):
146 """
145 """
147 Lighter version of CachedRepoList without the scm initialisation
146 Lighter version of CachedRepoList without the scm initialisation
148 """
147 """
149
148
150 def __iter__(self):
149 def __iter__(self):
151 for dbr in self.db_repo_list:
150 for dbr in self.db_repo_list:
152 # check permission at this level
151 # check permission at this level
153 if not HasRepoPermissionAny(
152 if not HasRepoPermissionAny(
154 *self.perm_set
153 *self.perm_set)(dbr.repo_name, 'get repo check'):
155 )(dbr.repo_name, 'get repo check'):
156 continue
154 continue
157
155
158 tmp_d = {}
156 tmp_d = {}
159 tmp_d['name'] = dbr.repo_name
157 tmp_d['name'] = dbr.repo_name
160 tmp_d['name_sort'] = tmp_d['name'].lower()
158 tmp_d['name_sort'] = tmp_d['name'].lower()
161 tmp_d['raw_name'] = tmp_d['name'].lower()
159 tmp_d['raw_name'] = tmp_d['name'].lower()
162 tmp_d['description'] = dbr.description
160 tmp_d['description'] = dbr.description
163 tmp_d['description_sort'] = tmp_d['description'].lower()
161 tmp_d['description_sort'] = tmp_d['description'].lower()
164 tmp_d['dbrepo'] = dbr.get_dict()
162 tmp_d['dbrepo'] = dbr.get_dict()
165 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
163 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
166 yield tmp_d
164 yield tmp_d
167
165
168
166
169 class _PermCheckIterator(object):
167 class _PermCheckIterator(object):
170 def __init__(self, obj_list, obj_attr, perm_set, perm_checker):
168 def __init__(self, obj_list, obj_attr, perm_set, perm_checker):
171 """
169 """
172 Creates iterator from given list of objects, additionally
170 Creates iterator from given list of objects, additionally
173 checking permission for them from perm_set var
171 checking permission for them from perm_set var
174
172
175 :param obj_list: list of db objects
173 :param obj_list: list of db objects
176 :param obj_attr: attribute of object to pass into perm_checker
174 :param obj_attr: attribute of object to pass into perm_checker
177 :param perm_set: list of permissions to check
175 :param perm_set: list of permissions to check
178 :param perm_checker: callable to check permissions against
176 :param perm_checker: callable to check permissions against
179 """
177 """
180 self.obj_list = obj_list
178 self.obj_list = obj_list
181 self.obj_attr = obj_attr
179 self.obj_attr = obj_attr
182 self.perm_set = perm_set
180 self.perm_set = perm_set
183 self.perm_checker = perm_checker
181 self.perm_checker = perm_checker
184
182
185 def __len__(self):
183 def __len__(self):
186 return len(self.obj_list)
184 return len(self.obj_list)
187
185
188 def __repr__(self):
186 def __repr__(self):
189 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
187 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
190
188
191 def __iter__(self):
189 def __iter__(self):
192 for db_obj in self.obj_list:
190 for db_obj in self.obj_list:
193 # check permission at this level
191 # check permission at this level
194 name = getattr(db_obj, self.obj_attr, None)
192 name = getattr(db_obj, self.obj_attr, None)
195 if not self.perm_checker(*self.perm_set)(name, self.__class__.__name__):
193 if not self.perm_checker(*self.perm_set)(name, self.__class__.__name__):
196 continue
194 continue
197
195
198 yield db_obj
196 yield db_obj
199
197
200
198
201 class RepoGroupList(_PermCheckIterator):
199 class RepoGroupList(_PermCheckIterator):
202
200
203 def __init__(self, db_repo_group_list, perm_set=None):
201 def __init__(self, db_repo_group_list, perm_set=None):
204 if not perm_set:
202 if not perm_set:
205 perm_set = ['group.read', 'group.write', 'group.admin']
203 perm_set = ['group.read', 'group.write', 'group.admin']
206
204
207 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
205 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
208 obj_attr='group_name', perm_set=perm_set,
206 obj_attr='group_name', perm_set=perm_set,
209 perm_checker=HasReposGroupPermissionAny)
207 perm_checker=HasReposGroupPermissionAny)
210
208
211
209
212 class UserGroupList(_PermCheckIterator):
210 class UserGroupList(_PermCheckIterator):
213
211
214 def __init__(self, db_user_group_list, perm_set=None):
212 def __init__(self, db_user_group_list, perm_set=None):
215 if not perm_set:
213 if not perm_set:
216 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
214 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
217
215
218 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
216 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
219 obj_attr='users_group_name', perm_set=perm_set,
217 obj_attr='users_group_name', perm_set=perm_set,
220 perm_checker=HasUserGroupPermissionAny)
218 perm_checker=HasUserGroupPermissionAny)
221
219
222
220
223 class ScmModel(BaseModel):
221 class ScmModel(BaseModel):
224 """
222 """
225 Generic Scm Model
223 Generic Scm Model
226 """
224 """
227
225
228 def __get_repo(self, instance):
226 def __get_repo(self, instance):
229 cls = Repository
227 cls = Repository
230 if isinstance(instance, cls):
228 if isinstance(instance, cls):
231 return instance
229 return instance
232 elif isinstance(instance, int) or safe_str(instance).isdigit():
230 elif isinstance(instance, int) or safe_str(instance).isdigit():
233 return cls.get(instance)
231 return cls.get(instance)
234 elif isinstance(instance, basestring):
232 elif isinstance(instance, basestring):
235 return cls.get_by_repo_name(instance)
233 return cls.get_by_repo_name(instance)
236 elif instance:
234 elif instance:
237 raise Exception('given object must be int, basestr or Instance'
235 raise Exception('given object must be int, basestr or Instance'
238 ' of %s got %s' % (type(cls), type(instance)))
236 ' of %s got %s' % (type(cls), type(instance)))
239
237
240 @LazyProperty
238 @LazyProperty
241 def repos_path(self):
239 def repos_path(self):
242 """
240 """
243 Get's the repositories root path from database
241 Get's the repositories root path from database
244 """
242 """
245
243
246 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
244 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
247
245
248 return q.ui_value
246 return q.ui_value
249
247
250 def repo_scan(self, repos_path=None):
248 def repo_scan(self, repos_path=None):
251 """
249 """
252 Listing of repositories in given path. This path should not be a
250 Listing of repositories in given path. This path should not be a
253 repository itself. Return a dictionary of repository objects
251 repository itself. Return a dictionary of repository objects
254
252
255 :param repos_path: path to directory containing repositories
253 :param repos_path: path to directory containing repositories
256 """
254 """
257
255
258 if repos_path is None:
256 if repos_path is None:
259 repos_path = self.repos_path
257 repos_path = self.repos_path
260
258
261 log.info('scanning for repositories in %s' % repos_path)
259 log.info('scanning for repositories in %s' % repos_path)
262
260
263 baseui = make_ui('db')
261 baseui = make_ui('db')
264 repos = {}
262 repos = {}
265
263
266 for name, path in get_filesystem_repos(repos_path, recursive=True):
264 for name, path in get_filesystem_repos(repos_path, recursive=True):
267 # name need to be decomposed and put back together using the /
265 # name need to be decomposed and put back together using the /
268 # since this is internal storage separator for rhodecode
266 # since this is internal storage separator for rhodecode
269 name = Repository.normalize_repo_name(name)
267 name = Repository.normalize_repo_name(name)
270
268
271 try:
269 try:
272 if name in repos:
270 if name in repos:
273 raise RepositoryError('Duplicate repository name %s '
271 raise RepositoryError('Duplicate repository name %s '
274 'found in %s' % (name, path))
272 'found in %s' % (name, path))
275 else:
273 else:
276
274
277 klass = get_backend(path[0])
275 klass = get_backend(path[0])
278
276
279 if path[0] == 'hg' and path[0] in BACKENDS.keys():
277 if path[0] == 'hg' and path[0] in BACKENDS.keys():
280 repos[name] = klass(safe_str(path[1]), baseui=baseui)
278 repos[name] = klass(safe_str(path[1]), baseui=baseui)
281
279
282 if path[0] == 'git' and path[0] in BACKENDS.keys():
280 if path[0] == 'git' and path[0] in BACKENDS.keys():
283 repos[name] = klass(path[1])
281 repos[name] = klass(path[1])
284 except OSError:
282 except OSError:
285 continue
283 continue
286 log.debug('found %s paths with repositories' % (len(repos)))
284 log.debug('found %s paths with repositories' % (len(repos)))
287 return repos
285 return repos
288
286
289 def get_repos(self, all_repos=None, sort_key=None, simple=False):
287 def get_repos(self, all_repos=None, sort_key=None, simple=False):
290 """
288 """
291 Get all repos from db and for each repo create it's
289 Get all repos from db and for each repo create it's
292 backend instance and fill that backed with information from database
290 backend instance and fill that backed with information from database
293
291
294 :param all_repos: list of repository names as strings
292 :param all_repos: list of repository names as strings
295 give specific repositories list, good for filtering
293 give specific repositories list, good for filtering
296
294
297 :param sort_key: initial sorting of repos
295 :param sort_key: initial sorting of repos
298 :param simple: use SimpleCachedList - one without the SCM info
296 :param simple: use SimpleCachedList - one without the SCM info
299 """
297 """
300 if all_repos is None:
298 if all_repos is None:
301 all_repos = self.sa.query(Repository)\
299 all_repos = self.sa.query(Repository)\
302 .filter(Repository.group_id == None)\
300 .filter(Repository.group_id == None)\
303 .order_by(func.lower(Repository.repo_name)).all()
301 .order_by(func.lower(Repository.repo_name)).all()
304 if simple:
302 if simple:
305 repo_iter = SimpleCachedRepoList(all_repos,
303 repo_iter = SimpleCachedRepoList(all_repos,
306 repos_path=self.repos_path,
304 repos_path=self.repos_path,
307 order_by=sort_key)
305 order_by=sort_key)
308 else:
306 else:
309 repo_iter = CachedRepoList(all_repos,
307 repo_iter = CachedRepoList(all_repos,
310 repos_path=self.repos_path,
308 repos_path=self.repos_path,
311 order_by=sort_key)
309 order_by=sort_key)
312
310
313 return repo_iter
311 return repo_iter
314
312
315 def get_repos_groups(self, all_groups=None):
313 def get_repos_groups(self, all_groups=None):
316 if all_groups is None:
314 if all_groups is None:
317 all_groups = RepoGroup.query()\
315 all_groups = RepoGroup.query()\
318 .filter(RepoGroup.group_parent_id == None).all()
316 .filter(RepoGroup.group_parent_id == None).all()
319 return [x for x in RepoGroupList(all_groups)]
317 return [x for x in RepoGroupList(all_groups)]
320
318
321 def mark_for_invalidation(self, repo_name):
319 def mark_for_invalidation(self, repo_name):
322 """
320 """
323 Mark caches of this repo invalid in the database.
321 Mark caches of this repo invalid in the database.
324
322
325 :param repo_name: the repo for which caches should be marked invalid
323 :param repo_name: the repo for which caches should be marked invalid
326 """
324 """
327 invalidated_keys = CacheInvalidation.set_invalidate(repo_name)
325 invalidated_keys = CacheInvalidation.set_invalidate(repo_name)
328 repo = Repository.get_by_repo_name(repo_name)
326 repo = Repository.get_by_repo_name(repo_name)
329 if repo:
327 if repo:
330 repo.update_changeset_cache()
328 repo.update_changeset_cache()
331 return invalidated_keys
329 return invalidated_keys
332
330
333 def toggle_following_repo(self, follow_repo_id, user_id):
331 def toggle_following_repo(self, follow_repo_id, user_id):
334
332
335 f = self.sa.query(UserFollowing)\
333 f = self.sa.query(UserFollowing)\
336 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
334 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
337 .filter(UserFollowing.user_id == user_id).scalar()
335 .filter(UserFollowing.user_id == user_id).scalar()
338
336
339 if f is not None:
337 if f is not None:
340 try:
338 try:
341 self.sa.delete(f)
339 self.sa.delete(f)
342 action_logger(UserTemp(user_id),
340 action_logger(UserTemp(user_id),
343 'stopped_following_repo',
341 'stopped_following_repo',
344 RepoTemp(follow_repo_id))
342 RepoTemp(follow_repo_id))
345 return
343 return
346 except Exception:
344 except Exception:
347 log.error(traceback.format_exc())
345 log.error(traceback.format_exc())
348 raise
346 raise
349
347
350 try:
348 try:
351 f = UserFollowing()
349 f = UserFollowing()
352 f.user_id = user_id
350 f.user_id = user_id
353 f.follows_repo_id = follow_repo_id
351 f.follows_repo_id = follow_repo_id
354 self.sa.add(f)
352 self.sa.add(f)
355
353
356 action_logger(UserTemp(user_id),
354 action_logger(UserTemp(user_id),
357 'started_following_repo',
355 'started_following_repo',
358 RepoTemp(follow_repo_id))
356 RepoTemp(follow_repo_id))
359 except Exception:
357 except Exception:
360 log.error(traceback.format_exc())
358 log.error(traceback.format_exc())
361 raise
359 raise
362
360
363 def toggle_following_user(self, follow_user_id, user_id):
361 def toggle_following_user(self, follow_user_id, user_id):
364 f = self.sa.query(UserFollowing)\
362 f = self.sa.query(UserFollowing)\
365 .filter(UserFollowing.follows_user_id == follow_user_id)\
363 .filter(UserFollowing.follows_user_id == follow_user_id)\
366 .filter(UserFollowing.user_id == user_id).scalar()
364 .filter(UserFollowing.user_id == user_id).scalar()
367
365
368 if f is not None:
366 if f is not None:
369 try:
367 try:
370 self.sa.delete(f)
368 self.sa.delete(f)
371 return
369 return
372 except Exception:
370 except Exception:
373 log.error(traceback.format_exc())
371 log.error(traceback.format_exc())
374 raise
372 raise
375
373
376 try:
374 try:
377 f = UserFollowing()
375 f = UserFollowing()
378 f.user_id = user_id
376 f.user_id = user_id
379 f.follows_user_id = follow_user_id
377 f.follows_user_id = follow_user_id
380 self.sa.add(f)
378 self.sa.add(f)
381 except Exception:
379 except Exception:
382 log.error(traceback.format_exc())
380 log.error(traceback.format_exc())
383 raise
381 raise
384
382
385 def is_following_repo(self, repo_name, user_id, cache=False):
383 def is_following_repo(self, repo_name, user_id, cache=False):
386 r = self.sa.query(Repository)\
384 r = self.sa.query(Repository)\
387 .filter(Repository.repo_name == repo_name).scalar()
385 .filter(Repository.repo_name == repo_name).scalar()
388
386
389 f = self.sa.query(UserFollowing)\
387 f = self.sa.query(UserFollowing)\
390 .filter(UserFollowing.follows_repository == r)\
388 .filter(UserFollowing.follows_repository == r)\
391 .filter(UserFollowing.user_id == user_id).scalar()
389 .filter(UserFollowing.user_id == user_id).scalar()
392
390
393 return f is not None
391 return f is not None
394
392
395 def is_following_user(self, username, user_id, cache=False):
393 def is_following_user(self, username, user_id, cache=False):
396 u = User.get_by_username(username)
394 u = User.get_by_username(username)
397
395
398 f = self.sa.query(UserFollowing)\
396 f = self.sa.query(UserFollowing)\
399 .filter(UserFollowing.follows_user == u)\
397 .filter(UserFollowing.follows_user == u)\
400 .filter(UserFollowing.user_id == user_id).scalar()
398 .filter(UserFollowing.user_id == user_id).scalar()
401
399
402 return f is not None
400 return f is not None
403
401
404 def get_followers(self, repo):
402 def get_followers(self, repo):
405 repo = self._get_repo(repo)
403 repo = self._get_repo(repo)
406
404
407 return self.sa.query(UserFollowing)\
405 return self.sa.query(UserFollowing)\
408 .filter(UserFollowing.follows_repository == repo).count()
406 .filter(UserFollowing.follows_repository == repo).count()
409
407
410 def get_forks(self, repo):
408 def get_forks(self, repo):
411 repo = self._get_repo(repo)
409 repo = self._get_repo(repo)
412 return self.sa.query(Repository)\
410 return self.sa.query(Repository)\
413 .filter(Repository.fork == repo).count()
411 .filter(Repository.fork == repo).count()
414
412
415 def get_pull_requests(self, repo):
413 def get_pull_requests(self, repo):
416 repo = self._get_repo(repo)
414 repo = self._get_repo(repo)
417 return self.sa.query(PullRequest)\
415 return self.sa.query(PullRequest)\
418 .filter(PullRequest.other_repo == repo)\
416 .filter(PullRequest.other_repo == repo)\
419 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
417 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
420
418
421 def mark_as_fork(self, repo, fork, user):
419 def mark_as_fork(self, repo, fork, user):
422 repo = self.__get_repo(repo)
420 repo = self.__get_repo(repo)
423 fork = self.__get_repo(fork)
421 fork = self.__get_repo(fork)
424 if fork and repo.repo_id == fork.repo_id:
422 if fork and repo.repo_id == fork.repo_id:
425 raise Exception("Cannot set repository as fork of itself")
423 raise Exception("Cannot set repository as fork of itself")
426 repo.fork = fork
424 repo.fork = fork
427 self.sa.add(repo)
425 self.sa.add(repo)
428 return repo
426 return repo
429
427
430 def _handle_push(self, repo, username, action, repo_name, revisions):
428 def _handle_push(self, repo, username, action, repo_name, revisions):
431 """
429 """
432 Triggers push action hooks
430 Triggers push action hooks
433
431
434 :param repo: SCM repo
432 :param repo: SCM repo
435 :param username: username who pushes
433 :param username: username who pushes
436 :param action: push/push_loca/push_remote
434 :param action: push/push_loca/push_remote
437 :param repo_name: name of repo
435 :param repo_name: name of repo
438 :param revisions: list of revisions that we pushed
436 :param revisions: list of revisions that we pushed
439 """
437 """
440 from rhodecode import CONFIG
438 from rhodecode import CONFIG
441 from rhodecode.lib.base import _get_ip_addr
439 from rhodecode.lib.base import _get_ip_addr
442 try:
440 try:
443 from pylons import request
441 from pylons import request
444 environ = request.environ
442 environ = request.environ
445 except TypeError:
443 except TypeError:
446 # we might use this outside of request context, let's fake the
444 # we might use this outside of request context, let's fake the
447 # environ data
445 # environ data
448 from webob import Request
446 from webob import Request
449 environ = Request.blank('').environ
447 environ = Request.blank('').environ
450
448
451 #trigger push hook
449 #trigger push hook
452 extras = {
450 extras = {
453 'ip': _get_ip_addr(environ),
451 'ip': _get_ip_addr(environ),
454 'username': username,
452 'username': username,
455 'action': 'push_local',
453 'action': 'push_local',
456 'repository': repo_name,
454 'repository': repo_name,
457 'scm': repo.alias,
455 'scm': repo.alias,
458 'config': CONFIG['__file__'],
456 'config': CONFIG['__file__'],
459 'server_url': get_server_url(environ),
457 'server_url': get_server_url(environ),
460 'make_lock': None,
458 'make_lock': None,
461 'locked_by': [None, None]
459 'locked_by': [None, None]
462 }
460 }
463 _scm_repo = repo._repo
461 _scm_repo = repo._repo
464 _set_extras(extras)
462 _set_extras(extras)
465 if repo.alias == 'hg':
463 if repo.alias == 'hg':
466 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
464 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
467 elif repo.alias == 'git':
465 elif repo.alias == 'git':
468 log_push_action(None, _scm_repo, _git_revs=revisions)
466 log_push_action(None, _scm_repo, _git_revs=revisions)
469
467
470 def _get_IMC_module(self, scm_type):
468 def _get_IMC_module(self, scm_type):
471 """
469 """
472 Returns InMemoryCommit class based on scm_type
470 Returns InMemoryCommit class based on scm_type
473
471
474 :param scm_type:
472 :param scm_type:
475 """
473 """
476 if scm_type == 'hg':
474 if scm_type == 'hg':
477 from rhodecode.lib.vcs.backends.hg import \
475 from rhodecode.lib.vcs.backends.hg import \
478 MercurialInMemoryChangeset as IMC
476 MercurialInMemoryChangeset as IMC
479 elif scm_type == 'git':
477 elif scm_type == 'git':
480 from rhodecode.lib.vcs.backends.git import \
478 from rhodecode.lib.vcs.backends.git import \
481 GitInMemoryChangeset as IMC
479 GitInMemoryChangeset as IMC
482 return IMC
480 return IMC
483
481
484 def pull_changes(self, repo, username):
482 def pull_changes(self, repo, username):
485 dbrepo = self.__get_repo(repo)
483 dbrepo = self.__get_repo(repo)
486 clone_uri = dbrepo.clone_uri
484 clone_uri = dbrepo.clone_uri
487 if not clone_uri:
485 if not clone_uri:
488 raise Exception("This repository doesn't have a clone uri")
486 raise Exception("This repository doesn't have a clone uri")
489
487
490 repo = dbrepo.scm_instance
488 repo = dbrepo.scm_instance
491 repo_name = dbrepo.repo_name
489 repo_name = dbrepo.repo_name
492 try:
490 try:
493 if repo.alias == 'git':
491 if repo.alias == 'git':
494 repo.fetch(clone_uri)
492 repo.fetch(clone_uri)
495 else:
493 else:
496 repo.pull(clone_uri)
494 repo.pull(clone_uri)
497 self.mark_for_invalidation(repo_name)
495 self.mark_for_invalidation(repo_name)
498 except Exception:
496 except Exception:
499 log.error(traceback.format_exc())
497 log.error(traceback.format_exc())
500 raise
498 raise
501
499
502 def commit_change(self, repo, repo_name, cs, user, author, message,
500 def commit_change(self, repo, repo_name, cs, user, author, message,
503 content, f_path):
501 content, f_path):
504 """
502 """
505 Commits changes
503 Commits changes
506
504
507 :param repo: SCM instance
505 :param repo: SCM instance
508
506
509 """
507 """
510 user = self._get_user(user)
508 user = self._get_user(user)
511 IMC = self._get_IMC_module(repo.alias)
509 IMC = self._get_IMC_module(repo.alias)
512
510
513 # decoding here will force that we have proper encoded values
511 # decoding here will force that we have proper encoded values
514 # in any other case this will throw exceptions and deny commit
512 # in any other case this will throw exceptions and deny commit
515 content = safe_str(content)
513 content = safe_str(content)
516 path = safe_str(f_path)
514 path = safe_str(f_path)
517 # message and author needs to be unicode
515 # message and author needs to be unicode
518 # proper backend should then translate that into required type
516 # proper backend should then translate that into required type
519 message = safe_unicode(message)
517 message = safe_unicode(message)
520 author = safe_unicode(author)
518 author = safe_unicode(author)
521 m = IMC(repo)
519 m = IMC(repo)
522 m.change(FileNode(path, content))
520 m.change(FileNode(path, content))
523 tip = m.commit(message=message,
521 tip = m.commit(message=message,
524 author=author,
522 author=author,
525 parents=[cs], branch=cs.branch)
523 parents=[cs], branch=cs.branch)
526
524
527 self.mark_for_invalidation(repo_name)
525 self.mark_for_invalidation(repo_name)
528 self._handle_push(repo,
526 self._handle_push(repo,
529 username=user.username,
527 username=user.username,
530 action='push_local',
528 action='push_local',
531 repo_name=repo_name,
529 repo_name=repo_name,
532 revisions=[tip.raw_id])
530 revisions=[tip.raw_id])
533 return tip
531 return tip
534
532
535 def create_node(self, repo, repo_name, cs, user, author, message, content,
533 def create_node(self, repo, repo_name, cs, user, author, message, content,
536 f_path):
534 f_path):
537 user = self._get_user(user)
535 user = self._get_user(user)
538 IMC = self._get_IMC_module(repo.alias)
536 IMC = self._get_IMC_module(repo.alias)
539
537
540 # decoding here will force that we have proper encoded values
538 # decoding here will force that we have proper encoded values
541 # in any other case this will throw exceptions and deny commit
539 # in any other case this will throw exceptions and deny commit
542 if isinstance(content, (basestring,)):
540 if isinstance(content, (basestring,)):
543 content = safe_str(content)
541 content = safe_str(content)
544 elif isinstance(content, (file, cStringIO.OutputType,)):
542 elif isinstance(content, (file, cStringIO.OutputType,)):
545 content = content.read()
543 content = content.read()
546 else:
544 else:
547 raise Exception('Content is of unrecognized type %s' % (
545 raise Exception('Content is of unrecognized type %s' % (
548 type(content)
546 type(content)
549 ))
547 ))
550
548
551 message = safe_unicode(message)
549 message = safe_unicode(message)
552 author = safe_unicode(author)
550 author = safe_unicode(author)
553 path = safe_str(f_path)
551 path = safe_str(f_path)
554 m = IMC(repo)
552 m = IMC(repo)
555
553
556 if isinstance(cs, EmptyChangeset):
554 if isinstance(cs, EmptyChangeset):
557 # EmptyChangeset means we we're editing empty repository
555 # EmptyChangeset means we we're editing empty repository
558 parents = None
556 parents = None
559 else:
557 else:
560 parents = [cs]
558 parents = [cs]
561
559
562 m.add(FileNode(path, content=content))
560 m.add(FileNode(path, content=content))
563 tip = m.commit(message=message,
561 tip = m.commit(message=message,
564 author=author,
562 author=author,
565 parents=parents, branch=cs.branch)
563 parents=parents, branch=cs.branch)
566
564
567 self.mark_for_invalidation(repo_name)
565 self.mark_for_invalidation(repo_name)
568 self._handle_push(repo,
566 self._handle_push(repo,
569 username=user.username,
567 username=user.username,
570 action='push_local',
568 action='push_local',
571 repo_name=repo_name,
569 repo_name=repo_name,
572 revisions=[tip.raw_id])
570 revisions=[tip.raw_id])
573 return tip
571 return tip
574
572
575 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
573 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
576 """
574 """
577 recursive walk in root dir and return a set of all path in that dir
575 recursive walk in root dir and return a set of all path in that dir
578 based on repository walk function
576 based on repository walk function
579
577
580 :param repo_name: name of repository
578 :param repo_name: name of repository
581 :param revision: revision for which to list nodes
579 :param revision: revision for which to list nodes
582 :param root_path: root path to list
580 :param root_path: root path to list
583 :param flat: return as a list, if False returns a dict with decription
581 :param flat: return as a list, if False returns a dict with decription
584
582
585 """
583 """
586 _files = list()
584 _files = list()
587 _dirs = list()
585 _dirs = list()
588 try:
586 try:
589 _repo = self.__get_repo(repo_name)
587 _repo = self.__get_repo(repo_name)
590 changeset = _repo.scm_instance.get_changeset(revision)
588 changeset = _repo.scm_instance.get_changeset(revision)
591 root_path = root_path.lstrip('/')
589 root_path = root_path.lstrip('/')
592 for topnode, dirs, files in changeset.walk(root_path):
590 for topnode, dirs, files in changeset.walk(root_path):
593 for f in files:
591 for f in files:
594 _files.append(f.path if flat else {"name": f.path,
592 _files.append(f.path if flat else {"name": f.path,
595 "type": "file"})
593 "type": "file"})
596 for d in dirs:
594 for d in dirs:
597 _dirs.append(d.path if flat else {"name": d.path,
595 _dirs.append(d.path if flat else {"name": d.path,
598 "type": "dir"})
596 "type": "dir"})
599 except RepositoryError:
597 except RepositoryError:
600 log.debug(traceback.format_exc())
598 log.debug(traceback.format_exc())
601 raise
599 raise
602
600
603 return _dirs, _files
601 return _dirs, _files
604
602
605 def get_unread_journal(self):
603 def get_unread_journal(self):
606 return self.sa.query(UserLog).count()
604 return self.sa.query(UserLog).count()
607
605
608 def get_repo_landing_revs(self, repo=None):
606 def get_repo_landing_revs(self, repo=None):
609 """
607 """
610 Generates select option with tags branches and bookmarks (for hg only)
608 Generates select option with tags branches and bookmarks (for hg only)
611 grouped by type
609 grouped by type
612
610
613 :param repo:
611 :param repo:
614 :type repo:
612 :type repo:
615 """
613 """
616
614
617 hist_l = []
615 hist_l = []
618 choices = []
616 choices = []
619 repo = self.__get_repo(repo)
617 repo = self.__get_repo(repo)
620 hist_l.append(['tip', _('latest tip')])
618 hist_l.append(['tip', _('latest tip')])
621 choices.append('tip')
619 choices.append('tip')
622 if not repo:
620 if not repo:
623 return choices, hist_l
621 return choices, hist_l
624
622
625 repo = repo.scm_instance
623 repo = repo.scm_instance
626
624
627 branches_group = ([(k, k) for k, v in
625 branches_group = ([(k, k) for k, v in
628 repo.branches.iteritems()], _("Branches"))
626 repo.branches.iteritems()], _("Branches"))
629 hist_l.append(branches_group)
627 hist_l.append(branches_group)
630 choices.extend([x[0] for x in branches_group[0]])
628 choices.extend([x[0] for x in branches_group[0]])
631
629
632 if repo.alias == 'hg':
630 if repo.alias == 'hg':
633 bookmarks_group = ([(k, k) for k, v in
631 bookmarks_group = ([(k, k) for k, v in
634 repo.bookmarks.iteritems()], _("Bookmarks"))
632 repo.bookmarks.iteritems()], _("Bookmarks"))
635 hist_l.append(bookmarks_group)
633 hist_l.append(bookmarks_group)
636 choices.extend([x[0] for x in bookmarks_group[0]])
634 choices.extend([x[0] for x in bookmarks_group[0]])
637
635
638 tags_group = ([(k, k) for k, v in
636 tags_group = ([(k, k) for k, v in
639 repo.tags.iteritems()], _("Tags"))
637 repo.tags.iteritems()], _("Tags"))
640 hist_l.append(tags_group)
638 hist_l.append(tags_group)
641 choices.extend([x[0] for x in tags_group[0]])
639 choices.extend([x[0] for x in tags_group[0]])
642
640
643 return choices, hist_l
641 return choices, hist_l
644
642
645 def install_git_hook(self, repo, force_create=False):
643 def install_git_hook(self, repo, force_create=False):
646 """
644 """
647 Creates a rhodecode hook inside a git repository
645 Creates a rhodecode hook inside a git repository
648
646
649 :param repo: Instance of VCS repo
647 :param repo: Instance of VCS repo
650 :param force_create: Create even if same name hook exists
648 :param force_create: Create even if same name hook exists
651 """
649 """
652
650
653 loc = jn(repo.path, 'hooks')
651 loc = jn(repo.path, 'hooks')
654 if not repo.bare:
652 if not repo.bare:
655 loc = jn(repo.path, '.git', 'hooks')
653 loc = jn(repo.path, '.git', 'hooks')
656 if not os.path.isdir(loc):
654 if not os.path.isdir(loc):
657 os.makedirs(loc)
655 os.makedirs(loc)
658
656
659 tmpl_post = pkg_resources.resource_string(
657 tmpl_post = pkg_resources.resource_string(
660 'rhodecode', jn('config', 'post_receive_tmpl.py')
658 'rhodecode', jn('config', 'post_receive_tmpl.py')
661 )
659 )
662 tmpl_pre = pkg_resources.resource_string(
660 tmpl_pre = pkg_resources.resource_string(
663 'rhodecode', jn('config', 'pre_receive_tmpl.py')
661 'rhodecode', jn('config', 'pre_receive_tmpl.py')
664 )
662 )
665
663
666 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
664 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
667 _hook_file = jn(loc, '%s-receive' % h_type)
665 _hook_file = jn(loc, '%s-receive' % h_type)
668 _rhodecode_hook = False
666 _rhodecode_hook = False
669 log.debug('Installing git hook in repo %s' % repo)
667 log.debug('Installing git hook in repo %s' % repo)
670 if os.path.exists(_hook_file):
668 if os.path.exists(_hook_file):
671 # let's take a look at this hook, maybe it's rhodecode ?
669 # let's take a look at this hook, maybe it's rhodecode ?
672 log.debug('hook exists, checking if it is from rhodecode')
670 log.debug('hook exists, checking if it is from rhodecode')
673 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
671 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
674 with open(_hook_file, 'rb') as f:
672 with open(_hook_file, 'rb') as f:
675 data = f.read()
673 data = f.read()
676 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
674 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
677 % 'RC_HOOK_VER').search(data)
675 % 'RC_HOOK_VER').search(data)
678 if matches:
676 if matches:
679 try:
677 try:
680 ver = matches.groups()[0]
678 ver = matches.groups()[0]
681 log.debug('got %s it is rhodecode' % (ver))
679 log.debug('got %s it is rhodecode' % (ver))
682 _rhodecode_hook = True
680 _rhodecode_hook = True
683 except Exception:
681 except Exception:
684 log.error(traceback.format_exc())
682 log.error(traceback.format_exc())
685 else:
683 else:
686 # there is no hook in this dir, so we want to create one
684 # there is no hook in this dir, so we want to create one
687 _rhodecode_hook = True
685 _rhodecode_hook = True
688
686
689 if _rhodecode_hook or force_create:
687 if _rhodecode_hook or force_create:
690 log.debug('writing %s hook file !' % h_type)
688 log.debug('writing %s hook file !' % h_type)
691 with open(_hook_file, 'wb') as f:
689 with open(_hook_file, 'wb') as f:
692 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
690 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
693 f.write(tmpl)
691 f.write(tmpl)
694 os.chmod(_hook_file, 0755)
692 os.chmod(_hook_file, 0755)
695 else:
693 else:
696 log.debug('skipping writing hook file')
694 log.debug('skipping writing hook file')
@@ -1,342 +1,343 b''
1 /**
1 /**
2 * Stylesheets for the context bar
2 * Stylesheets for the context bar
3 */
3 */
4
4
5 #quick .repo_switcher { background-image: url("../images/icons/database.png"); }
5 #quick .repo_switcher { background-image: url("../images/icons/database.png"); }
6 #quick .journal { background-image: url("../images/icons/book.png"); }
6 #quick .journal { background-image: url("../images/icons/book.png"); }
7 #quick .search { background-image: url("../images/icons/search_16.png"); }
7 #quick .search { background-image: url("../images/icons/search_16.png"); }
8 #quick .admin { background-image: url("../images/icons/cog_edit.png"); }
8 #quick .admin { background-image: url("../images/icons/cog_edit.png"); }
9
9
10 #context-bar a.follow { background-image: url("../images/icons/heart.png"); }
10 #context-bar a.follow { background-image: url("../images/icons/heart.png"); }
11 #context-bar a.following { background-image: url("../images/icons/heart_delete.png"); }
11 #context-bar a.following { background-image: url("../images/icons/heart_delete.png"); }
12 #context-bar a.fork { background-image: url("../images/icons/arrow_divide.png"); }
12 #context-bar a.fork { background-image: url("../images/icons/arrow_divide.png"); }
13 #context-bar a.summary { background-image: url("../images/icons/clipboard_16.png"); }
13 #context-bar a.summary { background-image: url("../images/icons/clipboard_16.png"); }
14 #context-bar a.changelogs { background-image: url("../images/icons/time.png"); }
14 #context-bar a.changelogs { background-image: url("../images/icons/time.png"); }
15 #context-bar a.files { background-image: url("../images/icons/file.png"); }
15 #context-bar a.files { background-image: url("../images/icons/file.png"); }
16 #context-bar a.switch-to { background-image: url("../images/icons/arrow_switch.png"); }
16 #context-bar a.switch-to { background-image: url("../images/icons/arrow_switch.png"); }
17 #context-bar a.options { background-image: url("../images/icons/table_gear.png"); }
17 #context-bar a.options { background-image: url("../images/icons/table_gear.png"); }
18 #context-bar a.forks { background-image: url("../images/icons/arrow_divide.png"); }
18 #context-bar a.forks { background-image: url("../images/icons/arrow_divide.png"); }
19 #context-bar a.pull-request { background-image: url("../images/icons/arrow_join.png"); }
19 #context-bar a.pull-request { background-image: url("../images/icons/arrow_join.png"); }
20 #context-bar a.branches { background-image: url("../images/icons/arrow_branch.png"); }
20 #context-bar a.branches { background-image: url("../images/icons/arrow_branch.png"); }
21 #context-bar a.tags { background-image: url("../images/icons/tag_blue.png"); }
21 #context-bar a.tags { background-image: url("../images/icons/tag_blue.png"); }
22 #context-bar a.bookmarks { background-image: url("../images/icons/tag_green.png"); }
22 #context-bar a.bookmarks { background-image: url("../images/icons/tag_green.png"); }
23 #context-bar a.settings { background-image: url("../images/icons/cog.png"); }
23 #context-bar a.settings { background-image: url("../images/icons/cog.png"); }
24 #context-bar a.shortlog { background-image: url("../images/icons/time.png"); }
24 #context-bar a.shortlog { background-image: url("../images/icons/time.png"); }
25 #context-bar a.search { background-image: url("../images/icons/search_16.png"); }
25 #context-bar a.search { background-image: url("../images/icons/search_16.png"); }
26 #context-bar a.admin { background-image: url("../images/icons/cog_edit.png"); }
26 #context-bar a.admin { background-image: url("../images/icons/cog_edit.png"); }
27
27
28 #context-bar a.journal { background-image: url("../images/icons/book.png"); }
28 #context-bar a.journal { background-image: url("../images/icons/book.png"); }
29 #context-bar a.repos { background-image: url("../images/icons/database_edit.png"); }
29 #context-bar a.repos { background-image: url("../images/icons/database_edit.png"); }
30 #context-bar a.repos_groups { background-image: url("../images/icons/database_link.png"); }
30 #context-bar a.repos_groups { background-image: url("../images/icons/database_link.png"); }
31 #context-bar a.users { background-image: url("../images/icons/user_edit.png"); }
31 #context-bar a.users { background-image: url("../images/icons/user_edit.png"); }
32 #context-bar a.groups { background-image: url("../images/icons/group_edit.png"); }
32 #context-bar a.groups { background-image: url("../images/icons/group_edit.png"); }
33 #context-bar a.permissions { background-image: url("../images/icons/key.png"); }
33 #context-bar a.permissions { background-image: url("../images/icons/key.png"); }
34 #context-bar a.ldap { background-image: url("../images/icons/server_key.png"); }
34 #context-bar a.ldap { background-image: url("../images/icons/server_key.png"); }
35 #context-bar a.defaults { background-image: url("../images/icons/wrench.png"); }
35 #context-bar a.defaults { background-image: url("../images/icons/wrench.png"); }
36 #context-bar a.settings { background-image: url("../images/icons/cog_edit.png"); }
36 #context-bar a.settings { background-image: url("../images/icons/cog_edit.png"); }
37 #context-bar a.compare_request { background-image: url('../images/icons/arrow_inout.png')}
37 #context-bar a.compare_request { background-image: url('../images/icons/arrow_inout.png')}
38 #context-bar a.locking_del { background-image: url('../images/icons/lock_delete.png')}
38 #context-bar a.locking_del { background-image: url('../images/icons/lock_delete.png')}
39 #context-bar a.locking_add { background-image: url('../images/icons/lock_add.png')}
39 #context-bar a.locking_add { background-image: url('../images/icons/lock_add.png')}
40
40
41 #content #context-bar {
41 #content #context-bar {
42 position: relative;
42 position: relative;
43 overflow: visible;
43 overflow: visible;
44 background-color: #336699;
44 background-color: #336699;
45 border-top: 1px solid #517da8;
45 border-top: 1px solid #517da8;
46 border-bottom: 1px solid #003162;
46 border-bottom: 1px solid #003162;
47 padding: 0 5px;
47 padding: 0 5px;
48 min-height: 36px;
48 min-height: 36px;
49 }
49 }
50
50
51 #header #header-inner #quick a,
51 #header #header-inner #quick a,
52 #content #context-bar,
52 #content #context-bar,
53 #content #context-bar a {
53 #content #context-bar a {
54 color: #FFFFFF;
54 color: #FFFFFF;
55 }
55 }
56
56
57 #header #header-inner #quick a:hover,
57 #header #header-inner #quick a:hover,
58 #content #context-bar a:hover {
58 #content #context-bar a:hover {
59 text-decoration: none;
59 text-decoration: none;
60 }
60 }
61
61
62 #content #context-bar .icon {
62 #content #context-bar .icon {
63 display: inline-block;
63 display: inline-block;
64 width: 16px;
64 width: 16px;
65 height: 16px;
65 height: 16px;
66 vertical-align: text-bottom;
66 vertical-align: text-bottom;
67 }
67 }
68
68
69 ul.horizontal-list {
69 ul.horizontal-list {
70 display: block;
70 display: block;
71 }
71 }
72
72
73 ul.horizontal-list > li {
73 ul.horizontal-list > li {
74 float: left;
74 float: left;
75 position: relative;
75 position: relative;
76 }
76 }
77
77
78 #header #header-inner #quick ul,
78 #header #header-inner #quick ul,
79 ul.horizontal-list > li ul {
79 ul.horizontal-list > li ul {
80 position: absolute;
80 position: absolute;
81 display: none;
81 display: none;
82 right: 0;
82 right: 0;
83 z-index: 999;
83 z-index: 999;
84 }
84 }
85
85
86 #header #header-inner #quick li:hover > ul,
86 #header #header-inner #quick li:hover > ul,
87 ul.horizontal-list li:hover > ul {
87 ul.horizontal-list li:hover > ul {
88 display: block;
88 display: block;
89 }
89 }
90
90
91 #header #header-inner #quick li ul li,
91 #header #header-inner #quick li ul li,
92 ul.horizontal-list ul li {
92 ul.horizontal-list ul li {
93 position: relative;
93 position: relative;
94 border-bottom: 1px solid rgba(0,0,0,0.1);
94 border-bottom: 1px solid rgba(0,0,0,0.1);
95 border-top: 1px solid rgba(255,255,255,0.1);
95 border-top: 1px solid rgba(255,255,255,0.1);
96 }
96 }
97
97
98 ul.horizontal-list > li ul ul {
98 ul.horizontal-list > li ul ul {
99 position: absolute;
99 position: absolute;
100 right: 100%;
100 right: 100%;
101 top: -1px;
101 top: -1px;
102 min-width: 200px;
102 min-width: 200px;
103 max-height: 400px;
103 max-height: 400px;
104 overflow-x: hidden;
104 overflow-x: hidden;
105 overflow-y: auto;
105 overflow-y: auto;
106 }
106 }
107
107
108 #header #header-inner #quick ul a,
108 #header #header-inner #quick ul a,
109 ul.horizontal-list li a {
109 ul.horizontal-list li a {
110 white-space: nowrap;
110 white-space: nowrap;
111 }
111 }
112
112
113 #breadcrumbs {
113 #breadcrumbs {
114 float: left;
114 float: left;
115 padding: 6px 0 5px 0;
115 padding: 6px 0 5px 0;
116 padding-left: 5px;
116 padding-left: 5px;
117 font-weight: bold;
117 font-weight: bold;
118 font-size: 14px;
118 font-size: 14px;
119 }
119 }
120
120
121 #breadcrumbs span {
121 #breadcrumbs span {
122 font-weight: bold;
122 font-weight: bold;
123 font-size: 1.4em;
123 font-size: 1.4em;
124 }
124 }
125
125
126 #header #header-inner #quick ul,
126 #header #header-inner #quick ul,
127 #revision-changer,
127 #revision-changer,
128 #context-pages,
128 #context-pages,
129 #context-pages ul {
129 #context-pages ul {
130 background: #3b6998; /* Old browsers */
130 background: #3b6998; /* Old browsers */
131 background: -moz-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* FF3.6+ */
131 background: -moz-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* FF3.6+ */
132 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4574a2), color-stop(100%,#2f5d8b)); /* Chrome,Safari4+ */
132 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4574a2), color-stop(100%,#2f5d8b)); /* Chrome,Safari4+ */
133 background: -webkit-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* Chrome10+,Safari5.1+ */
133 background: -webkit-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* Chrome10+,Safari5.1+ */
134 background: -o-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* Opera 11.10+ */
134 background: -o-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* Opera 11.10+ */
135 background: -ms-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* IE10+ */
135 background: -ms-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* IE10+ */
136 background: linear-gradient(to bottom, #4574a2 0%, #2f5d8b 100%); /* W3C */
136 background: linear-gradient(to bottom, #4574a2 0%, #2f5d8b 100%); /* W3C */
137 /*Filter on IE will also use overflow:hidden implicitly, and that would clip our inner menus.*/
137 /*Filter on IE will also use overflow:hidden implicitly, and that would clip our inner menus.*/
138 /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4574a2', endColorstr='#2f5d8b',GradientType=0 ); /* IE6-9 */*/
138 /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4574a2', endColorstr='#2f5d8b',GradientType=0 ); /* IE6-9 */*/
139 }
139 }
140
140
141 #header #header-inner #quick a,
141 #header #header-inner #quick a,
142 #context-actions a,
142 #context-actions a,
143 #context-pages a {
143 #context-pages a {
144 background-repeat: no-repeat;
144 background-repeat: no-repeat;
145 background-position: 10px 50%;
145 background-position: 10px 50%;
146 padding-left: 30px;
146 padding-left: 30px;
147 }
147 }
148
148
149 #quick a,
149 #quick a,
150 #context-pages ul ul a {
150 #context-pages ul ul a {
151 padding-left: 10px;
151 padding-left: 10px;
152 }
152 }
153
153
154 ul#context-actions {
154 ul#context-actions {
155 display: inline-block;
155 display: inline-block;
156 float: right;
156 float: right;
157 border-radius: 4px;
157 border-radius: 4px;
158 background-image: linear-gradient(top, #4574a2 0%, #2f5d8b 100%);
158 background-image: linear-gradient(top, #4574a2 0%, #2f5d8b 100%);
159 }
159 }
160
160
161 #content ul#context-actions li {
161 #content ul#context-actions li {
162 padding: 0px;
162 padding: 0px;
163 border-right: 1px solid rgba(0,0,0,0.1);
163 border-right: 1px solid rgba(0,0,0,0.1);
164 border-left: 1px solid rgba(255,255,255,0.1);
164 border-left: 1px solid rgba(255,255,255,0.1);
165 }
165 }
166
166
167 #context-actions a {
167 #context-actions a {
168 display: block;
168 display: block;
169 cursor: pointer;
169 cursor: pointer;
170 background: none;
170 background: none;
171 border: none;
171 border: none;
172 margin: 0px;
172 margin: 0px;
173 height: auto;
173 height: auto;
174 padding: 10px 10px 10px 30px;
174 padding: 10px 10px 10px 30px;
175 background-repeat: no-repeat;
175 background-repeat: no-repeat;
176 background-position: 10px 50%;
176 background-position: 10px 50%;
177 font-size: 1em;
177 font-size: 1em;
178 }
178 }
179
179
180 #context-actions a {
180 #context-actions a {
181 padding: 11px 10px 12px 30px;
181 padding: 11px 10px 12px 30px;
182 }
182 }
183
183
184 #header #header-inner #quick li:hover,
184 #header #header-inner #quick li:hover,
185 #revision-changer:hover,
185 #revision-changer:hover,
186 #context-pages li:hover,
186 #context-pages li:hover,
187 #context-actions li:hover,
187 #context-actions li:hover,
188 #content #context-actions li:hover,
188 #content #context-actions li:hover,
189 #header #header-inner #quick li.current,
189 #header #header-inner #quick li.current,
190 #context-pages li.current {
190 #context-pages li.current {
191 background: #6388ad; /* Old browsers */
191 background: #6388ad; /* Old browsers */
192 background: -moz-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* FF3.6+ */
192 background: -moz-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* FF3.6+ */
193 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0.1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */
193 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0.1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */
194 background: -webkit-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* Chrome10+,Safari5.1+ */
194 background: -webkit-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* Chrome10+,Safari5.1+ */
195 background: -o-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* Opera 11.10+ */
195 background: -o-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* Opera 11.10+ */
196 background: -ms-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* IE10+ */
196 background: -ms-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* IE10+ */
197 background: linear-gradient(to bottom, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* W3C */
197 background: linear-gradient(to bottom, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* W3C */
198 /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#88bfe8', endColorstr='#70b0e0',GradientType=0 ); /* IE6-9 */*/
198 /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#88bfe8', endColorstr='#70b0e0',GradientType=0 ); /* IE6-9 */*/
199 }
199 }
200
200
201
201
202 #content #context-actions li:first-child {
202 #content #context-actions li:first-child {
203 border-left: none;
203 border-left: none;
204 border-radius: 4px 0 0px 4px;
204 border-radius: 4px 0 0px 4px;
205 }
205 }
206
206
207 #content #context-actions li:last-child {
207 #content #context-actions li:last-child {
208 border-right: none;
208 border-right: none;
209 border-radius: 0 4px 4px 0;
209 border-radius: 0 4px 4px 0;
210 }
210 }
211
211
212 #content #context-actions .icon {
212 #content #context-actions .icon {
213 margin: auto;
213 margin: auto;
214 margin-bottom: 5px;
214 margin-bottom: 5px;
215 display: block;
215 display: block;
216 clear: both;
216 clear: both;
217 float: none;
217 float: none;
218 }
218 }
219
219
220 #content #context-pages .follow .show-following,
220 #content #context-pages .follow .show-following,
221 #content #context-pages .following .show-follow {
221 #content #context-pages .following .show-follow {
222 display: none;
222 display: none;
223 }
223 }
224
224
225 #context-pages {
225 #context-pages {
226 float: right;
226 float: right;
227 border-left: 1px solid rgba(0,0,0,0.1);
227 border-left: 1px solid rgba(0,0,0,0.1);
228 }
228 }
229
229
230 #context-pages li.current {
230 #context-pages li.current {
231 background: #535353; /* Old browsers */
231 background: #535353; /* Old browsers */
232 background: -moz-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* FF3.6+ */
232 background: -moz-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* FF3.6+ */
233 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#5d5d5d), color-stop(100%,#484848)); /* Chrome,Safari4+ */
233 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#5d5d5d), color-stop(100%,#484848)); /* Chrome,Safari4+ */
234 background: -webkit-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* Chrome10+,Safari5.1+ */
234 background: -webkit-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* Chrome10+,Safari5.1+ */
235 background: -o-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* Opera 11.10+ */
235 background: -o-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* Opera 11.10+ */
236 background: -ms-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* IE10+ */
236 background: -ms-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* IE10+ */
237 background: linear-gradient(to bottom, #5d5d5d 0%, #484848 100%); /* W3C */
237 background: linear-gradient(to bottom, #5d5d5d 0%, #484848 100%); /* W3C */
238 }
238 }
239
239
240 #content #context-pages .icon {
240 #content #context-pages .icon {
241 margin-right: 5px;
241 margin-right: 5px;
242 }
242 }
243
243
244 #header #header-inner #quick li,
244 #header #header-inner #quick li,
245 #content #context-pages li {
245 #content #context-pages li {
246 border-right: 1px solid rgba(0,0,0,0.1);
246 border-right: 1px solid rgba(0,0,0,0.1);
247 border-left: 1px solid rgba(255,255,255,0.1);
247 border-left: 1px solid rgba(255,255,255,0.1);
248 padding: 0;
248 padding: 0;
249 }
249 }
250
250 #header #header-inner #quick li:last-child,
251 #header #header-inner #quick li:last-child,
251 #content #context-pages li:last-child {
252 #content #context-pages li:last-child {
252 border-right: none;
253 border-right: none;
253 }
254 }
254
255
255 #header #header-inner #quick > li:first-child {
256 #header #header-inner #quick > li:first-child {
256 border-left: none;
257 border-left: none;
257 }
258 }
258
259
259 #header #header-inner #quick > li:first-child > a {
260 #header #header-inner #quick > li:first-child > a {
260 border-radius: 4px 0 0 4px;
261 border-radius: 4px 0 0 4px;
261 }
262 }
262
263
263 #header #header-inner #quick a,
264 #header #header-inner #quick a,
264 #context-pages a,
265 #context-pages a,
265 #context-pages .admin_menu a {
266 #context-pages .admin_menu a {
266 display: block;
267 display: block;
267 padding: 0px 10px 1px 30px;
268 padding: 0px 10px 1px 30px;
268 padding-left: 30px;
269 padding-left: 30px;
269 line-height: 35px;
270 line-height: 35px;
270 }
271 }
271
272
272 #header #header-inner #quick a.thin,
273 #header #header-inner #quick a.thin,
273 #context-pages a.thin,
274 #context-pages a.thin,
274 #context-pages .admin_menu a.thin {
275 #context-pages .admin_menu a.thin {
275 line-height: 28px !important;
276 line-height: 28px !important;
276 }
277 }
277
278
278 #header #header-inner #quick a#quick_login_link {
279 #header #header-inner #quick a#quick_login_link {
279 padding-left: 0px;
280 padding-left: 0px;
280 }
281 }
281
282
282 #header #header-inner #quick a {
283 #header #header-inner #quick a {
283 overflow: hidden;
284 overflow: hidden;
284 }
285 }
285 #quick a.childs:after,
286 #quick a.childs:after,
286 #revision-changer:before,
287 #revision-changer:before,
287 #context-pages a.childs:after,
288 #context-pages a.childs:after,
288 #context-pages a.dropdown:after {
289 #context-pages a.dropdown:after {
289 content: ' \25BE';
290 content: ' \25BE';
290 }
291 }
291 #context-pages a.childs {
292 #context-pages a.childs {
292 padding-right: 30px;
293 padding-right: 30px;
293 }
294 }
294 #context-pages a.childs:after {
295 #context-pages a.childs:after {
295 position: absolute;
296 position: absolute;
296 float: right;
297 float: right;
297 padding-left: 5px;
298 padding-left: 5px;
298 padding-right: 5px;
299 padding-right: 5px;
299 }
300 }
300
301
301 #revision-changer:before {
302 #revision-changer:before {
302 position: absolute;
303 position: absolute;
303 top: 0px;
304 top: 0px;
304 right: 0px;
305 right: 0px;
305 border-right: 1px solid rgba(0,0,0,0.1);
306 border-right: 1px solid rgba(0,0,0,0.1);
306 height: 25px;
307 height: 25px;
307 padding-top: 10px;
308 padding-top: 10px;
308 padding-right: 10px;
309 padding-right: 10px;
309 }
310 }
310
311
311 #context-pages li:last-child a {
312 #context-pages li:last-child a {
312 padding-right: 10px;
313 padding-right: 10px;
313 }
314 }
314
315
315 #context-bar #revision-changer {
316 #context-bar #revision-changer {
316 position: relative;
317 position: relative;
317 cursor: pointer;
318 cursor: pointer;
318 border: none;
319 border: none;
319 padding: 0;
320 padding: 0;
320 margin: 0;
321 margin: 0;
321 color: #FFFFFF;
322 color: #FFFFFF;
322 font-size: 0.85em;
323 font-size: 0.85em;
323 padding: 2px 15px;
324 padding: 2px 15px;
324 padding-bottom: 3px;
325 padding-bottom: 3px;
325 padding-right: 30px;
326 padding-right: 30px;
326 border-right: 1px solid rgba(255,255,255,0.1);
327 border-right: 1px solid rgba(255,255,255,0.1);
327 }
328 }
328
329
329 #revision-changer .branch-name,
330 #revision-changer .branch-name,
330 #revision-changer .revision {
331 #revision-changer .revision {
331 display: block;
332 display: block;
332 text-align: center;
333 text-align: center;
333 line-height: 1.5em;
334 line-height: 1.5em;
334 }
335 }
335
336
336 #revision-changer .branch-name {
337 #revision-changer .branch-name {
337 font-weight: bold;
338 font-weight: bold;
338 }
339 }
339
340
340 #revision-changer .revision {
341 #revision-changer .revision {
341 text-transform: uppercase;
342 text-transform: uppercase;
342 }
343 }
@@ -1,395 +1,395 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##
2 ##
3 ## See also repo_settings.html
3 ## See also repo_settings.html
4 ##
4 ##
5 <%inherit file="/base/base.html"/>
5 <%inherit file="/base/base.html"/>
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('Edit repository')} ${c.repo_info.repo_name} &middot; ${c.rhodecode_name}
8 ${_('Edit repository')} ${c.repo_info.repo_name} &middot; ${c.rhodecode_name}
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${_('Settings')}
12 ${_('Settings')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('admin')}
16 ${self.menu('admin')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 ${self.context_bar('options')}
20 ${self.context_bar('options')}
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="repo_name">${_('Name')}:</label>
32 <label for="repo_name">${_('Name')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('repo_name',class_="medium")}
35 ${h.text('repo_name',class_="medium")}
36 </div>
36 </div>
37 </div>
37 </div>
38 <div class="field">
38 <div class="field">
39 <div class="label">
39 <div class="label">
40 <label for="clone_uri">${_('Clone uri')}:</label>
40 <label for="clone_uri">${_('Clone uri')}:</label>
41 </div>
41 </div>
42 <div class="input">
42 <div class="input">
43 ${h.text('clone_uri',class_="medium")}
43 ${h.text('clone_uri',class_="medium")}
44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
45 </div>
45 </div>
46 </div>
46 </div>
47 <div class="field">
47 <div class="field">
48 <div class="label">
48 <div class="label">
49 <label for="repo_group">${_('Repository group')}:</label>
49 <label for="repo_group">${_('Repository group')}:</label>
50 </div>
50 </div>
51 <div class="input">
51 <div class="input">
52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
54 </div>
54 </div>
55 </div>
55 </div>
56 <div class="field">
56 <div class="field">
57 <div class="label">
57 <div class="label">
58 <label for="repo_type">${_('Type')}:</label>
58 <label for="repo_type">${_('Type')}:</label>
59 </div>
59 </div>
60 <div class="input">
60 <div class="input">
61 ${h.select('repo_type','hg',c.backends,class_="medium")}
61 ${h.select('repo_type','hg',c.backends,class_="medium")}
62 </div>
62 </div>
63 </div>
63 </div>
64 <div class="field">
64 <div class="field">
65 <div class="label">
65 <div class="label">
66 <label for="repo_landing_rev">${_('Landing revision')}:</label>
66 <label for="repo_landing_rev">${_('Landing revision')}:</label>
67 </div>
67 </div>
68 <div class="input">
68 <div class="input">
69 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
69 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
71 </div>
71 </div>
72 </div>
72 </div>
73 <div class="field">
73 <div class="field">
74 <div class="label label-textarea">
74 <div class="label label-textarea">
75 <label for="repo_description">${_('Description')}:</label>
75 <label for="repo_description">${_('Description')}:</label>
76 </div>
76 </div>
77 <div class="textarea text-area editor">
77 <div class="textarea text-area editor">
78 ${h.textarea('repo_description')}
78 ${h.textarea('repo_description')}
79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
80 </div>
80 </div>
81 </div>
81 </div>
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label label-checkbox">
84 <div class="label label-checkbox">
85 <label for="repo_private">${_('Private repository')}:</label>
85 <label for="repo_private">${_('Private repository')}:</label>
86 </div>
86 </div>
87 <div class="checkboxes">
87 <div class="checkboxes">
88 ${h.checkbox('repo_private',value="True")}
88 ${h.checkbox('repo_private',value="True")}
89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
90 </div>
90 </div>
91 </div>
91 </div>
92 <div class="field">
92 <div class="field">
93 <div class="label label-checkbox">
93 <div class="label label-checkbox">
94 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
94 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
95 </div>
95 </div>
96 <div class="checkboxes">
96 <div class="checkboxes">
97 ${h.checkbox('repo_enable_statistics',value="True")}
97 ${h.checkbox('repo_enable_statistics',value="True")}
98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
99 </div>
99 </div>
100 </div>
100 </div>
101 <div class="field">
101 <div class="field">
102 <div class="label label-checkbox">
102 <div class="label label-checkbox">
103 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
103 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
104 </div>
104 </div>
105 <div class="checkboxes">
105 <div class="checkboxes">
106 ${h.checkbox('repo_enable_downloads',value="True")}
106 ${h.checkbox('repo_enable_downloads',value="True")}
107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
108 </div>
108 </div>
109 </div>
109 </div>
110 <div class="field">
110 <div class="field">
111 <div class="label label-checkbox">
111 <div class="label label-checkbox">
112 <label for="repo_enable_locking">${_('Enable locking')}:</label>
112 <label for="repo_enable_locking">${_('Enable locking')}:</label>
113 </div>
113 </div>
114 <div class="checkboxes">
114 <div class="checkboxes">
115 ${h.checkbox('repo_enable_locking',value="True")}
115 ${h.checkbox('repo_enable_locking',value="True")}
116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
117 </div>
117 </div>
118 </div>
118 </div>
119 <div class="field">
119 <div class="field">
120 <div class="label">
120 <div class="label">
121 <label for="user">${_('Owner')}:</label>
121 <label for="user">${_('Owner')}:</label>
122 </div>
122 </div>
123 <div class="input input-medium ac">
123 <div class="input input-medium ac">
124 <div class="perm_ac">
124 <div class="perm_ac">
125 ${h.text('user',class_='yui-ac-input')}
125 ${h.text('user',class_='yui-ac-input')}
126 <span class="help-block">${_('Change owner of this repository.')}</span>
126 <span class="help-block">${_('Change owner of this repository.')}</span>
127 <div id="owner_container"></div>
127 <div id="owner_container"></div>
128 </div>
128 </div>
129 </div>
129 </div>
130 </div>
130 </div>
131 %if c.visual.repository_fields:
131 %if c.visual.repository_fields:
132 ## EXTRA FIELDS
132 ## EXTRA FIELDS
133 %for field in c.repo_fields:
133 %for field in c.repo_fields:
134 <div class="field">
134 <div class="field">
135 <div class="label">
135 <div class="label">
136 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
136 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
137 </div>
137 </div>
138 <div class="input input-medium">
138 <div class="input input-medium">
139 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
139 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
140 %if field.field_desc:
140 %if field.field_desc:
141 <span class="help-block">${field.field_desc}</span>
141 <span class="help-block">${field.field_desc}</span>
142 %endif
142 %endif
143 </div>
143 </div>
144 </div>
144 </div>
145 %endfor
145 %endfor
146 %endif
146 %endif
147 <div class="buttons">
147 <div class="buttons">
148 ${h.submit('save',_('Save'),class_="ui-btn large")}
148 ${h.submit('save',_('Save'),class_="ui-btn large")}
149 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
149 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
150 </div>
150 </div>
151 </div>
151 </div>
152 </div>
152 </div>
153 ${h.end_form()}
153 ${h.end_form()}
154 </div>
154 </div>
155
155
156 <div class="box box-right">
156 <div class="box box-right">
157 <div class="title">
157 <div class="title">
158 <h5>${_('Permissions')}</h5>
158 <h5>${_('Permissions')}</h5>
159 </div>
159 </div>
160 ${h.form(url('set_repo_perm_member', repo_name=c.repo_info.repo_name),method='post')}
160 ${h.form(url('set_repo_perm_member', repo_name=c.repo_info.repo_name),method='post')}
161 <div class="form">
161 <div class="form">
162 <div class="fields">
162 <div class="fields">
163 <div class="field">
163 <div class="field">
164 <div class="label">
164 <div class="label">
165 <label for="input">${_('Permissions')}:</label>
165 <label for="input">${_('Permissions')}:</label>
166 </div>
166 </div>
167 <div class="input">
167 <div class="input">
168 ${h.hidden('repo_private')}
168 ${h.hidden('repo_private')}
169 <%include file="repo_edit_perms.html"/>
169 <%include file="repo_edit_perms.html"/>
170 </div>
170 </div>
171 </div>
171 </div>
172 <div class="buttons">
172 <div class="buttons">
173 ${h.submit('save',_('Save'),class_="ui-btn large")}
173 ${h.submit('save',_('Save'),class_="ui-btn large")}
174 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
174 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
175 </div>
175 </div>
176 </div>
176 </div>
177 </div>
177 </div>
178 ${h.end_form()}
178 ${h.end_form()}
179 </div>
179 </div>
180
180
181
181
182 <div class="box box-right" style="clear:right">
182 <div class="box box-right" style="clear:right">
183 <div class="title">
183 <div class="title">
184 <h5>${_('Advanced settings')}</h5>
184 <h5>${_('Advanced settings')}</h5>
185 </div>
185 </div>
186
186
187 <h3>${_('Statistics')}</h3>
187 <h3>${_('Statistics')}</h3>
188 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
188 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
189 <div class="form">
189 <div class="form">
190 <div class="fields">
190 <div class="fields">
191 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
191 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
192 <div class="field" style="border:none;color:#888">
192 <div class="field" style="border:none;color:#888">
193 <ul>
193 <ul>
194 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
194 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
195 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
195 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
196 </ul>
196 </ul>
197 </div>
197 </div>
198 </div>
198 </div>
199 </div>
199 </div>
200 ${h.end_form()}
200 ${h.end_form()}
201
201
202 %if c.repo_info.clone_uri:
202 %if c.repo_info.clone_uri:
203 <h3>${_('Remote')}</h3>
203 <h3>${_('Remote')}</h3>
204 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
204 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
205 <div class="form">
205 <div class="form">
206 <div class="fields">
206 <div class="fields">
207 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
207 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
208 <div class="field" style="border:none">
208 <div class="field" style="border:none">
209 <ul>
209 <ul>
210 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
210 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
211 </ul>
211 </ul>
212 </div>
212 </div>
213 </div>
213 </div>
214 </div>
214 </div>
215 ${h.end_form()}
215 ${h.end_form()}
216 %endif
216 %endif
217
217
218 <h3>${_('Cache')}</h3>
218 <h3>${_('Cache')}</h3>
219 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
219 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
220 <div class="form">
220 <div class="form">
221 <div class="fields">
221 <div class="fields">
222 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
222 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
223 <div class="field" style="border:none;color:#888">
223 <div class="field" style="border:none;color:#888">
224 <ul>
224 <ul>
225 <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
225 <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
226 </li>
226 </li>
227 </ul>
227 </ul>
228 </div>
228 </div>
229 <div class="field" style="border:none;">
229 <div class="field" style="border:none;">
230 ${_('List of cached values')}
230 ${_('List of cached values')}
231 <table>
231 <table>
232 <tr>
232 <tr>
233 <th>${_('Prefix')}</th>
233 <th>${_('Prefix')}</th>
234 <th>${_('Key')}</th>
234 <th>${_('Key')}</th>
235 <th>${_('Active')}</th>
235 <th>${_('Active')}</th>
236 </tr>
236 </tr>
237 %for cache in c.repo_info.cache_keys:
237 %for cache in c.repo_info.cache_keys:
238 <tr>
238 <tr>
239 <td>${cache.get_prefix() or '-'}</td>
239 <td>${cache.get_prefix() or '-'}</td>
240 <td>${cache.cache_key}</td>
240 <td>${cache.cache_key}</td>
241 <td>${h.boolicon(cache.cache_active)}</td>
241 <td>${h.boolicon(cache.cache_active)}</td>
242 </tr>
242 </tr>
243 %endfor
243 %endfor
244 </table>
244 </table>
245 </div>
245 </div>
246 </div>
246 </div>
247 </div>
247 </div>
248 ${h.end_form()}
248 ${h.end_form()}
249
249
250 <h3>${_('Public journal')}</h3>
250 <h3>${_('Public journal')}</h3>
251 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
251 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
252 <div class="form">
252 <div class="form">
253 ${h.hidden('auth_token',str(h.get_token()))}
253 ${h.hidden('auth_token',str(h.get_token()))}
254 <div class="field">
254 <div class="field">
255 %if c.in_public_journal:
255 %if c.in_public_journal:
256 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
256 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
257 %else:
257 %else:
258 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
258 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
259 %endif
259 %endif
260 </div>
260 </div>
261 <div class="field" style="border:none;color:#888">
261 <div class="field" style="border:none;color:#888">
262 <ul>
262 <ul>
263 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
263 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
264 </li>
264 </li>
265 </ul>
265 </ul>
266 </div>
266 </div>
267 </div>
267 </div>
268 ${h.end_form()}
268 ${h.end_form()}
269
269
270 <h3>${_('Locking')}</h3>
270 <h3>${_('Locking')}</h3>
271 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
271 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
272 <div class="form">
272 <div class="form">
273 <div class="fields">
273 <div class="fields">
274 %if c.repo_info.locked[0]:
274 %if c.repo_info.locked[0]:
275 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
275 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
276 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
276 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
277 %else:
277 %else:
278 ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
278 ${h.submit('set_lock',_('Lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
279 ${_('Repository is not locked')}
279 ${_('Repository is not locked')}
280 %endif
280 %endif
281 </div>
281 </div>
282 <div class="field" style="border:none;color:#888">
282 <div class="field" style="border:none;color:#888">
283 <ul>
283 <ul>
284 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
284 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
285 </li>
285 </li>
286 </ul>
286 </ul>
287 </div>
287 </div>
288 </div>
288 </div>
289 ${h.end_form()}
289 ${h.end_form()}
290
290
291 <h3>${_('Set as fork of')}</h3>
291 <h3>${_('Set as fork of')}</h3>
292 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
292 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
293 <div class="form">
293 <div class="form">
294 <div class="fields">
294 <div class="fields">
295 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
295 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
296 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
296 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="ui-btn",)}
297 </div>
297 </div>
298 <div class="field" style="border:none;color:#888">
298 <div class="field" style="border:none;color:#888">
299 <ul>
299 <ul>
300 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
300 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
301 </ul>
301 </ul>
302 </div>
302 </div>
303 </div>
303 </div>
304 ${h.end_form()}
304 ${h.end_form()}
305
305
306 <h3>${_('Delete')}</h3>
306 <h3>${_('Delete')}</h3>
307 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
307 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
308 <div class="form">
308 <div class="form">
309 <div class="fields">
309 <div class="fields">
310 <div class="field" style="border:none;color:#888">
310 <div class="field" style="border:none;color:#888">
311 ## <div class="label">
311 ## <div class="label">
312 ## <label for="">${_('Remove repository')}:</label>
312 ## <label for="">${_('Remove repository')}:</label>
313 ## </div>
313 ## </div>
314 <div class="checkboxes">
314 <div class="checkboxes">
315 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
315 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
316 %if c.repo_info.forks.count():
316 %if c.repo_info.forks.count():
317 - ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
317 - ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
318 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
318 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
319 <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
319 <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
320 %endif
320 %endif
321 <ul>
321 <ul>
322 <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}</li>
322 <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}</li>
323 </ul>
323 </ul>
324 </div>
324 </div>
325 </div>
325 </div>
326 </div>
326 </div>
327 </div>
327 </div>
328 ${h.end_form()}
328 ${h.end_form()}
329 </div>
329 </div>
330
330
331 ##TODO: this should be controlled by the VISUAL setting
331 ##TODO: this should be controlled by the VISUAL setting
332 %if c.visual.repository_fields:
332 %if c.visual.repository_fields:
333 <div class="box box-left" style="clear:left">
333 <div class="box box-left" style="clear:left">
334 <!-- box / title -->
334 <!-- box / title -->
335 <div class="title">
335 <div class="title">
336 <h5>${_('Extra fields')}</h5>
336 <h5>${_('Extra fields')}</h5>
337 </div>
337 </div>
338
338
339 <div class="emails_wrap">
339 <div class="emails_wrap">
340 <table class="noborder">
340 <table class="noborder">
341 %for field in c.repo_fields:
341 %for field in c.repo_fields:
342 <tr>
342 <tr>
343 <td>${field.field_label} (${field.field_key})</td>
343 <td>${field.field_label} (${field.field_key})</td>
344 <td>${field.field_type}</td>
344 <td>${field.field_type}</td>
345 <td>
345 <td>
346 ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
346 ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
347 ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id,
347 ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id,
348 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
348 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
349 ${h.end_form()}
349 ${h.end_form()}
350 </td>
350 </td>
351 </tr>
351 </tr>
352 %endfor
352 %endfor
353 </table>
353 </table>
354 </div>
354 </div>
355
355
356 ${h.form(url('create_repo_fields', repo_name=c.repo_info.repo_name),method='put')}
356 ${h.form(url('create_repo_fields', repo_name=c.repo_info.repo_name),method='put')}
357 <div class="form">
357 <div class="form">
358 <!-- fields -->
358 <!-- fields -->
359 <div class="fields">
359 <div class="fields">
360 <div class="field">
360 <div class="field">
361 <div class="label">
361 <div class="label">
362 <label for="new_field_key">${_('New field key')}:</label>
362 <label for="new_field_key">${_('New field key')}:</label>
363 </div>
363 </div>
364 <div class="input">
364 <div class="input">
365 ${h.text('new_field_key', class_='small')}
365 ${h.text('new_field_key', class_='small')}
366 </div>
366 </div>
367 </div>
367 </div>
368 <div class="field">
368 <div class="field">
369 <div class="label">
369 <div class="label">
370 <label for="new_field_label">${_('New field label')}:</label>
370 <label for="new_field_label">${_('New field label')}:</label>
371 </div>
371 </div>
372 <div class="input">
372 <div class="input">
373 ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))}
373 ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))}
374 </div>
374 </div>
375 </div>
375 </div>
376
376
377 <div class="field">
377 <div class="field">
378 <div class="label">
378 <div class="label">
379 <label for="new_field_desc">${_('New field description')}:</label>
379 <label for="new_field_desc">${_('New field description')}:</label>
380 </div>
380 </div>
381 <div class="input">
381 <div class="input">
382 ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))}
382 ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))}
383 </div>
383 </div>
384 </div>
384 </div>
385
385
386 <div class="buttons">
386 <div class="buttons">
387 ${h.submit('save',_('Add'),class_="ui-btn large")}
387 ${h.submit('save',_('Add'),class_="ui-btn large")}
388 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
388 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
389 </div>
389 </div>
390 </div>
390 </div>
391 </div>
391 </div>
392 ${h.end_form()}
392 ${h.end_form()}
393 </div>
393 </div>
394 %endif
394 %endif
395 </%def>
395 </%def>
@@ -1,252 +1,252 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)} &middot; ${c.rhodecode_name}
4 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)} &middot; ${c.rhodecode_name}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${_('Pull request #%s') % c.pull_request.pull_request_id}
8 ${_('Pull request #%s') % c.pull_request.pull_request_id}
9 </%def>
9 </%def>
10
10
11 <%def name="page_nav()">
11 <%def name="page_nav()">
12 ${self.menu('repositories')}
12 ${self.menu('repositories')}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16 ${self.context_bar('showpullrequest')}
16 ${self.context_bar('showpullrequest')}
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22
22
23 <h3 class="${'closed' if c.pull_request.is_closed() else ''}">
23 <h3 class="${'closed' if c.pull_request.is_closed() else ''}">
24 <img src="${h.url('/images/icons/flag_status_%s.png' % str(c.pull_request.last_review_status))}" />
24 <img src="${h.url('/images/icons/flag_status_%s.png' % str(c.pull_request.last_review_status))}" />
25 ${_('Title')}: ${c.pull_request.title}
25 ${_('Title')}: ${c.pull_request.title}
26 %if c.pull_request.is_closed():
26 %if c.pull_request.is_closed():
27 (${_('Closed')})
27 (${_('Closed')})
28 %endif
28 %endif
29 </h3>
29 </h3>
30
30
31 <div class="form">
31 <div class="form">
32 <div id="summary" class="fields">
32 <div id="summary" class="fields">
33 <div class="field">
33 <div class="field">
34 <div class="label-summary">
34 <div class="label-summary">
35 <label>${_('Review status')}:</label>
35 <label>${_('Review status')}:</label>
36 </div>
36 </div>
37 <div class="input">
37 <div class="input">
38 <div class="changeset-status-container" style="float:none;clear:both">
38 <div class="changeset-status-container" style="float:none;clear:both">
39 %if c.current_changeset_status:
39 %if c.current_changeset_status:
40 <div title="${_('Pull request status')}" class="changeset-status-lbl">
40 <div title="${_('Pull request status')}" class="changeset-status-lbl">
41 %if c.pull_request.is_closed():
41 %if c.pull_request.is_closed():
42 ${_('Closed')},
42 ${_('Closed')},
43 %endif
43 %endif
44 ${h.changeset_status_lbl(c.current_changeset_status)}
44 ${h.changeset_status_lbl(c.current_changeset_status)}
45 </div>
45 </div>
46 <div class="changeset-status-ico" style="padding:1px 4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
46 <div class="changeset-status-ico" style="padding:1px 4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
47 %endif
47 %endif
48 </div>
48 </div>
49 </div>
49 </div>
50 </div>
50 </div>
51 <div class="field">
51 <div class="field">
52 <div class="label-summary">
52 <div class="label-summary">
53 <label>${_('Still not reviewed by')}:</label>
53 <label>${_('Still not reviewed by')}:</label>
54 </div>
54 </div>
55 <div class="input">
55 <div class="input">
56 % if len(c.pull_request_pending_reviewers) > 0:
56 % if len(c.pull_request_pending_reviewers) > 0:
57 <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
57 <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
58 %else:
58 %else:
59 <div>${_('Pull request was reviewed by all reviewers')}</div>
59 <div>${_('Pull request was reviewed by all reviewers')}</div>
60 %endif
60 %endif
61 </div>
61 </div>
62 </div>
62 </div>
63 <div class="field">
63 <div class="field">
64 <div class="label-summary">
64 <div class="label-summary">
65 <label>${_('Origin repository')}:</label>
65 <label>${_('Origin repository')}:</label>
66 </div>
66 </div>
67 <div class="input">
67 <div class="input">
68 <div>
68 <div>
69 ##%if h.is_hg(c.pull_request.org_repo):
69 ##%if h.is_hg(c.pull_request.org_repo):
70 ## <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
70 ## <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
71 ##%elif h.is_git(c.pull_request.org_repo):
71 ##%elif h.is_git(c.pull_request.org_repo):
72 ## <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
72 ## <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
73 ##%endif
73 ##%endif
74 <span class="spantag">${c.pull_request.org_ref_parts[0]}: ${c.pull_request.org_ref_parts[1]}</span>
74 <span class="spantag">${c.pull_request.org_ref_parts[0]}: ${c.pull_request.org_ref_parts[1]}</span>
75 <span><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name)}">${c.pull_request.org_repo.clone_url()}</a></span>
75 <span><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name)}">${c.pull_request.org_repo.clone_url()}</a></span>
76 </div>
76 </div>
77 </div>
77 </div>
78 </div>
78 </div>
79 <div class="field">
79 <div class="field">
80 <div class="label-summary">
80 <div class="label-summary">
81 <label>${_('Summary')}:</label>
81 <label>${_('Description')}:</label>
82 </div>
82 </div>
83 <div class="input">
83 <div class="input">
84 <div style="white-space:pre-wrap">${h.literal(c.pull_request.description)}</div>
84 <div style="white-space:pre-wrap">${h.literal(c.pull_request.description)}</div>
85 </div>
85 </div>
86 </div>
86 </div>
87 <div class="field">
87 <div class="field">
88 <div class="label-summary">
88 <div class="label-summary">
89 <label>${_('Created on')}:</label>
89 <label>${_('Created on')}:</label>
90 </div>
90 </div>
91 <div class="input">
91 <div class="input">
92 <div>${h.fmt_date(c.pull_request.created_on)}</div>
92 <div>${h.fmt_date(c.pull_request.created_on)}</div>
93 </div>
93 </div>
94 </div>
94 </div>
95 </div>
95 </div>
96 </div>
96 </div>
97
97
98 <div style="overflow: auto;">
98 <div style="overflow: auto;">
99 ##DIFF
99 ##DIFF
100 <div class="table" style="float:left;clear:none">
100 <div class="table" style="float:left;clear:none">
101 <div id="body" class="diffblock">
101 <div id="body" class="diffblock">
102 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
102 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
103 </div>
103 </div>
104 <div id="changeset_compare_view_content">
104 <div id="changeset_compare_view_content">
105 ##CS
105 ##CS
106 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div>
106 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div>
107 <%include file="/compare/compare_cs.html" />
107 <%include file="/compare/compare_cs.html" />
108
108
109 ## FILES
109 ## FILES
110 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
110 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
111
111
112 % if c.limited_diff:
112 % if c.limited_diff:
113 ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)}
113 ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)}
114 % else:
114 % else:
115 ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}:
115 ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}:
116 %endif
116 %endif
117
117
118 </div>
118 </div>
119 <div class="cs_files">
119 <div class="cs_files">
120 %if not c.files:
120 %if not c.files:
121 <span class="empty_data">${_('No files')}</span>
121 <span class="empty_data">${_('No files')}</span>
122 %endif
122 %endif
123 %for fid, change, f, stat in c.files:
123 %for fid, change, f, stat in c.files:
124 <div class="cs_${change}">
124 <div class="cs_${change}">
125 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
125 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
126 <div class="changes">${h.fancy_file_stats(stat)}</div>
126 <div class="changes">${h.fancy_file_stats(stat)}</div>
127 </div>
127 </div>
128 %endfor
128 %endfor
129 </div>
129 </div>
130 % if c.limited_diff:
130 % if c.limited_diff:
131 <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></h5>
131 <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></h5>
132 % endif
132 % endif
133 </div>
133 </div>
134 </div>
134 </div>
135 ## REVIEWERS
135 ## REVIEWERS
136 <div style="float:left; border-left:1px dashed #eee">
136 <div style="float:left; border-left:1px dashed #eee">
137 <h4>${_('Pull request reviewers')}</h4>
137 <h4>${_('Pull request reviewers')}</h4>
138 <div id="reviewers" style="padding:0px 0px 5px 10px">
138 <div id="reviewers" style="padding:0px 0px 5px 10px">
139 ## members goes here !
139 ## members goes here !
140 <div class="group_members_wrap" style="min-height:45px">
140 <div class="group_members_wrap" style="min-height:45px">
141 <ul id="review_members" class="group_members">
141 <ul id="review_members" class="group_members">
142 %for member,status in c.pull_request_reviewers:
142 %for member,status in c.pull_request_reviewers:
143 <li id="reviewer_${member.user_id}">
143 <li id="reviewer_${member.user_id}">
144 <div class="reviewers_member">
144 <div class="reviewers_member">
145 <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
145 <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
146 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
146 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
147 </div>
147 </div>
148 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
148 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
149 <div style="float:left">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
149 <div style="float:left">${member.full_name} (${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
150 <input type="hidden" value="${member.user_id}" name="review_members" />
150 <input type="hidden" value="${member.user_id}" name="review_members" />
151 %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.user_id == c.rhodecode_user.user_id):
151 %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.user_id == c.rhodecode_user.user_id):
152 <span class="delete_icon action_button" onclick="removeReviewMember(${member.user_id})"></span>
152 <span class="delete_icon action_button" onclick="removeReviewMember(${member.user_id})"></span>
153 %endif
153 %endif
154 </div>
154 </div>
155 </li>
155 </li>
156 %endfor
156 %endfor
157 </ul>
157 </ul>
158 </div>
158 </div>
159 %if not c.pull_request.is_closed():
159 %if not c.pull_request.is_closed():
160 <div class='ac'>
160 <div class='ac'>
161 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
161 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
162 <div class="reviewer_ac">
162 <div class="reviewer_ac">
163 ${h.text('user', class_='yui-ac-input')}
163 ${h.text('user', class_='yui-ac-input')}
164 <span class="help-block">${_('Add or remove reviewer to this pull request.')}</span>
164 <span class="help-block">${_('Add or remove reviewer to this pull request.')}</span>
165 <div id="reviewers_container"></div>
165 <div id="reviewers_container"></div>
166 </div>
166 </div>
167 <div style="padding:0px 10px">
167 <div style="padding:0px 10px">
168 <span id="update_pull_request" class="ui-btn xsmall">${_('Save changes')}</span>
168 <span id="update_pull_request" class="ui-btn xsmall">${_('Save changes')}</span>
169 </div>
169 </div>
170 %endif
170 %endif
171 </div>
171 </div>
172 %endif
172 %endif
173 </div>
173 </div>
174 </div>
174 </div>
175 </div>
175 </div>
176 <script>
176 <script>
177 var _USERS_AC_DATA = ${c.users_array|n};
177 var _USERS_AC_DATA = ${c.users_array|n};
178 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
178 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
179 // TODO: switch this to pyroutes
179 // TODO: switch this to pyroutes
180 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
180 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
181 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
181 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
182
182
183 pyroutes.register('pullrequest_comment', "${url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']);
183 pyroutes.register('pullrequest_comment', "${url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']);
184 pyroutes.register('pullrequest_comment_delete', "${url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s')}", ['repo_name', 'comment_id']);
184 pyroutes.register('pullrequest_comment_delete', "${url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s')}", ['repo_name', 'comment_id']);
185 pyroutes.register('pullrequest_update', "${url('pullrequest_update',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']);
185 pyroutes.register('pullrequest_update', "${url('pullrequest_update',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']);
186
186
187 </script>
187 </script>
188
188
189 ## diff block
189 ## diff block
190 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
190 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
191 %for fid, change, f, stat in c.files:
191 %for fid, change, f, stat in c.files:
192 ${diff_block.diff_block_simple([c.changes[fid]])}
192 ${diff_block.diff_block_simple([c.changes[fid]])}
193 %endfor
193 %endfor
194 % if c.limited_diff:
194 % if c.limited_diff:
195 <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></h4>
195 <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></h4>
196 % endif
196 % endif
197
197
198
198
199 ## template for inline comment form
199 ## template for inline comment form
200 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
200 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
201 ${comment.comment_inline_form()}
201 ${comment.comment_inline_form()}
202
202
203 ## render comments and inlines
203 ## render comments and inlines
204 ${comment.generate_comments(include_pr=True)}
204 ${comment.generate_comments(include_pr=True)}
205
205
206 % if not c.pull_request.is_closed():
206 % if not c.pull_request.is_closed():
207 ## main comment form and it status
207 ## main comment form and it status
208 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
208 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
209 pull_request_id=c.pull_request.pull_request_id),
209 pull_request_id=c.pull_request.pull_request_id),
210 c.current_changeset_status,
210 c.current_changeset_status,
211 close_btn=True, change_status=c.allowed_to_change_status)}
211 close_btn=True, change_status=c.allowed_to_change_status)}
212 %endif
212 %endif
213
213
214 <script type="text/javascript">
214 <script type="text/javascript">
215 YUE.onDOMReady(function(){
215 YUE.onDOMReady(function(){
216 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
216 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
217
217
218 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
218 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
219 var show = 'none';
219 var show = 'none';
220 var target = e.currentTarget;
220 var target = e.currentTarget;
221 if(target.checked){
221 if(target.checked){
222 var show = ''
222 var show = ''
223 }
223 }
224 var boxid = YUD.getAttribute(target,'id_for');
224 var boxid = YUD.getAttribute(target,'id_for');
225 var comments = YUQ('#{0} .inline-comments'.format(boxid));
225 var comments = YUQ('#{0} .inline-comments'.format(boxid));
226 for(c in comments){
226 for(c in comments){
227 YUD.setStyle(comments[c],'display',show);
227 YUD.setStyle(comments[c],'display',show);
228 }
228 }
229 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
229 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
230 for(c in btns){
230 for(c in btns){
231 YUD.setStyle(btns[c],'display',show);
231 YUD.setStyle(btns[c],'display',show);
232 }
232 }
233 })
233 })
234
234
235 YUE.on(YUQ('.line'),'click',function(e){
235 YUE.on(YUQ('.line'),'click',function(e){
236 var tr = e.currentTarget;
236 var tr = e.currentTarget;
237 injectInlineForm(tr);
237 injectInlineForm(tr);
238 });
238 });
239
239
240 // inject comments into they proper positions
240 // inject comments into they proper positions
241 var file_comments = YUQ('.inline-comment-placeholder');
241 var file_comments = YUQ('.inline-comment-placeholder');
242 renderInlineComments(file_comments);
242 renderInlineComments(file_comments);
243
243
244 YUE.on(YUD.get('update_pull_request'),'click',function(e){
244 YUE.on(YUD.get('update_pull_request'),'click',function(e){
245 updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}");
245 updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}");
246 })
246 })
247 })
247 })
248 </script>
248 </script>
249
249
250 </div>
250 </div>
251
251
252 </%def>
252 </%def>
@@ -1,749 +1,749 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s Summary') % c.repo_name} &middot; ${c.rhodecode_name}
4 ${_('%s Summary') % c.repo_name} &middot; ${c.rhodecode_name}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${_('Summary')}
8 ${_('Summary')}
9 </%def>
9 </%def>
10
10
11 <%def name="page_nav()">
11 <%def name="page_nav()">
12 ${self.menu('repositories')}
12 ${self.menu('repositories')}
13 </%def>
13 </%def>
14
14
15 <%def name="head_extra()">
15 <%def name="head_extra()">
16 <link href="${h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
16 <link href="${h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
17 <link href="${h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
17 <link href="${h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
18
18
19 <script>
19 <script>
20 redirect_hash_branch = function(){
20 redirect_hash_branch = function(){
21 var branch = window.location.hash.replace(/^#(.*)/, '$1');
21 var branch = window.location.hash.replace(/^#(.*)/, '$1');
22 if (branch){
22 if (branch){
23 window.location = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}"
23 window.location = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}"
24 .replace('__BRANCH__',branch);
24 .replace('__BRANCH__',branch);
25 }
25 }
26 }
26 }
27 redirect_hash_branch();
27 redirect_hash_branch();
28 window.onhashchange = function() {
28 window.onhashchange = function() {
29 redirect_hash_branch();
29 redirect_hash_branch();
30 };
30 };
31 </script>
31 </script>
32
32
33 </%def>
33 </%def>
34
34
35 <%def name="main()">
35 <%def name="main()">
36 ${self.context_bar('summary')}
36 ${self.context_bar('summary')}
37 <%
37 <%
38 summary = lambda n:{False:'summary-short'}.get(n)
38 summary = lambda n:{False:'summary-short'}.get(n)
39 %>
39 %>
40 %if c.show_stats:
40 %if c.show_stats:
41 <div class="box box-left">
41 <div class="box box-left">
42 %else:
42 %else:
43 <div class="box">
43 <div class="box">
44 %endif
44 %endif
45 <!-- box / title -->
45 <!-- box / title -->
46 <div class="title">
46 <div class="title">
47 ${self.breadcrumbs()}
47 ${self.breadcrumbs()}
48 </div>
48 </div>
49 <!-- end box / title -->
49 <!-- end box / title -->
50 <div class="form">
50 <div class="form">
51 <div id="summary" class="fields">
51 <div id="summary" class="fields">
52
52
53 <div class="field">
53 <div class="field">
54 <div class="label-summary">
54 <div class="label-summary">
55 <label>${_('Name')}:</label>
55 <label>${_('Name')}:</label>
56 </div>
56 </div>
57 <div class="input ${summary(c.show_stats)}">
57 <div class="input ${summary(c.show_stats)}">
58
58
59 ## locking icon
59 ## locking icon
60 %if c.rhodecode_db_repo.enable_locking:
60 %if c.rhodecode_db_repo.enable_locking:
61 %if c.rhodecode_db_repo.locked[0]:
61 %if c.rhodecode_db_repo.locked[0]:
62 <span class="locking_locked tooltip" title="${_('Repository locked by %s') % h.person_by_id(c.rhodecode_db_repo.locked[0])}"></span>
62 <span class="locking_locked tooltip" title="${_('Repository locked by %s') % h.person_by_id(c.rhodecode_db_repo.locked[0])}"></span>
63 %else:
63 %else:
64 <span class="locking_unlocked tooltip" title="${_('Repository unlocked')}"></span>
64 <span class="locking_unlocked tooltip" title="${_('Repository unlocked')}"></span>
65 %endif
65 %endif
66 %endif
66 %endif
67 ##REPO TYPE
67 ##REPO TYPE
68 %if h.is_hg(c.dbrepo):
68 %if h.is_hg(c.dbrepo):
69 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
69 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
70 %endif
70 %endif
71 %if h.is_git(c.dbrepo):
71 %if h.is_git(c.dbrepo):
72 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
72 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
73 %endif
73 %endif
74
74
75 ##PUBLIC/PRIVATE
75 ##PUBLIC/PRIVATE
76 %if c.dbrepo.private:
76 %if c.dbrepo.private:
77 <img style="margin-bottom:2px" class="icon" title="${_('Private repository')}" alt="${_('Private repository')}" src="${h.url('/images/icons/lock.png')}"/>
77 <img style="margin-bottom:2px" class="icon" title="${_('Private repository')}" alt="${_('Private repository')}" src="${h.url('/images/icons/lock.png')}"/>
78 %else:
78 %else:
79 <img style="margin-bottom:2px" class="icon" title="${_('Public repository')}" alt="${_('Public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
79 <img style="margin-bottom:2px" class="icon" title="${_('Public repository')}" alt="${_('Public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
80 %endif
80 %endif
81
81
82 ##REPO NAME
82 ##REPO NAME
83 <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
83 <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
84
84
85 ##FORK
85 ##FORK
86 %if c.dbrepo.fork:
86 %if c.dbrepo.fork:
87 <div style="margin-top:5px;clear:both">
87 <div style="margin-top:5px;clear:both">
88 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('Public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
88 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('Public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
89 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
89 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
90 </a>
90 </a>
91 </div>
91 </div>
92 %endif
92 %endif
93 ##REMOTE
93 ##REMOTE
94 %if c.dbrepo.clone_uri:
94 %if c.dbrepo.clone_uri:
95 <div style="margin-top:5px;clear:both">
95 <div style="margin-top:5px;clear:both">
96 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('Remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
96 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('Remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
97 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
97 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
98 </a>
98 </a>
99 </div>
99 </div>
100 %endif
100 %endif
101 </div>
101 </div>
102 </div>
102 </div>
103
103
104 <div class="field">
104 <div class="field">
105 <div class="label-summary">
105 <div class="label-summary">
106 <label>${_('Description')}:</label>
106 <label>${_('Description')}:</label>
107 </div>
107 </div>
108 %if c.visual.stylify_metatags:
108 %if c.visual.stylify_metatags:
109 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(c.dbrepo.description))}</div>
109 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(c.dbrepo.description))}</div>
110 %else:
110 %else:
111 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
111 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
112 %endif
112 %endif
113 </div>
113 </div>
114
114
115 <div class="field">
115 <div class="field">
116 <div class="label-summary">
116 <div class="label-summary">
117 <label>${_('Contact')}:</label>
117 <label>${_('Contact')}:</label>
118 </div>
118 </div>
119 <div class="input ${summary(c.show_stats)}">
119 <div class="input ${summary(c.show_stats)}">
120 <div class="gravatar">
120 <div class="gravatar">
121 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
121 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
122 </div>
122 </div>
123 ${_('Username')}: ${c.dbrepo.user.username}<br/>
123 ${_('Username')}: ${c.dbrepo.user.username}<br/>
124 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
124 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
125 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
125 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
126 </div>
126 </div>
127 </div>
127 </div>
128
128
129 <div class="field">
129 <div class="field">
130 <div class="label-summary">
130 <div class="label-summary">
131 <label>${_('Clone url')}:</label>
131 <label>${_('Clone url')}:</label>
132 </div>
132 </div>
133 <div class="input ${summary(c.show_stats)}">
133 <div class="input ${summary(c.show_stats)}">
134 <input style="width:${'75%' if c.show_stats else '80%'}" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
134 <input style="width:${'75%' if c.show_stats else '80%'}" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
135 <input style="display:none;width:${'75%' if c.show_stats else '80%'}" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
135 <input style="display:none;width:${'75%' if c.show_stats else '80%'}" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
136 <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
136 <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
137 <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
137 <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
138 </div>
138 </div>
139 </div>
139 </div>
140
140
141 <div class="field">
141 <div class="field">
142 <div class="label-summary">
142 <div class="label-summary">
143 <label>${_('Trending files')}:</label>
143 <label>${_('Trending files')}:</label>
144 </div>
144 </div>
145 <div class="input ${summary(c.show_stats)}">
145 <div class="input ${summary(c.show_stats)}">
146 %if c.show_stats:
146 %if c.show_stats:
147 <div id="lang_stats"></div>
147 <div id="lang_stats"></div>
148 %else:
148 %else:
149 ${_('Statistics are disabled for this repository')}
149 ${_('Statistics are disabled for this repository')}
150 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
150 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
151 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
151 ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
152 %endif
152 %endif
153 %endif
153 %endif
154 </div>
154 </div>
155 </div>
155 </div>
156
156
157 <div class="field">
157 <div class="field">
158 <div class="label-summary">
158 <div class="label-summary">
159 <label>${_('Download')}:</label>
159 <label>${_('Download')}:</label>
160 </div>
160 </div>
161 <div class="input ${summary(c.show_stats)}">
161 <div class="input ${summary(c.show_stats)}">
162 %if len(c.rhodecode_repo.revisions) == 0:
162 %if len(c.rhodecode_repo.revisions) == 0:
163 ${_('There are no downloads yet')}
163 ${_('There are no downloads yet')}
164 %elif not c.enable_downloads:
164 %elif not c.enable_downloads:
165 ${_('Downloads are disabled for this repository')}
165 ${_('Downloads are disabled for this repository')}
166 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
166 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
167 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
167 ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
168 %endif
168 %endif
169 %else:
169 %else:
170 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
170 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
171 <span id="${'zip_link'}">${h.link_to(_('Download as zip'), h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
171 <span id="${'zip_link'}">${h.link_to(_('Download as zip'), h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
172 <span style="vertical-align: bottom">
172 <span style="vertical-align: bottom">
173 <input id="archive_subrepos" type="checkbox" name="subrepos" />
173 <input id="archive_subrepos" type="checkbox" name="subrepos" />
174 <label for="archive_subrepos" class="tooltip" title="${h.tooltip(_('Check this to download archive with subrepos'))}" >${_('with subrepos')}</label>
174 <label for="archive_subrepos" class="tooltip" title="${h.tooltip(_('Check this to download archive with subrepos'))}" >${_('with subrepos')}</label>
175 </span>
175 </span>
176 %endif
176 %endif
177 </div>
177 </div>
178 </div>
178 </div>
179 </div>
179 </div>
180 <div id="summary-menu-stats">
180 <div id="summary-menu-stats">
181 <ul>
181 <ul>
182 <li>
182 <li>
183 <a class="followers" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
183 <a class="followers" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
184 ${_('Followers')}
184 ${_('Followers')}
185 <span style="float:right" id="current_followers_count">${c.repository_followers}</span>
185 <span style="float:right" id="current_followers_count">${c.repository_followers}</span>
186 </a>
186 </a>
187 </li>
187 </li>
188 <li>
188 <li>
189 <a class="forks" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
189 <a class="forks" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
190 ${_('Forks')}
190 ${_('Forks')}
191 <span style="float:right">${c.repository_forks}</span>
191 <span style="float:right">${c.repository_forks}</span>
192 </a>
192 </a>
193 </li>
193 </li>
194
194
195 %if c.rhodecode_user.username != 'default':
195 %if c.rhodecode_user.username != 'default':
196 <li class="repo_size">
196 <li class="repo_size">
197 <a href="#" class="repo-size" onclick="javascript:showRepoSize('repo_size_2','${c.dbrepo.repo_name}','${str(h.get_token())}')">${_('Repository Size')}</a>
197 <a href="#" class="repo-size" onclick="javascript:showRepoSize('repo_size_2','${c.dbrepo.repo_name}','${str(h.get_token())}')">${_('Repository Size')}</a>
198 <span id="repo_size_2"></span>
198 <span id="repo_size_2"></span>
199 </li>
199 </li>
200 %endif
200 %endif
201
201
202 <li>
202 <li>
203 %if c.rhodecode_user.username != 'default':
203 %if c.rhodecode_user.username != 'default':
204 ${h.link_to(_('Feed'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='feed')}
204 ${h.link_to(_('Feed'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='feed')}
205 %else:
205 %else:
206 ${h.link_to(_('Feed'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='feed')}
206 ${h.link_to(_('Feed'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='feed')}
207 %endif
207 %endif
208 </li>
208 </li>
209
209
210 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
210 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
211 <li>
211 <li>
212 ${h.link_to(_('Settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}
212 ${h.link_to(_('Settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}
213 </li>
213 </li>
214 %endif
214 %endif
215 </ul>
215 </ul>
216 </div>
216 </div>
217 </div>
217 </div>
218 </div>
218 </div>
219
219
220 %if c.show_stats:
220 %if c.show_stats:
221 <div class="box box-right" style="min-height:455px">
221 <div class="box box-right" style="min-height:455px">
222 <!-- box / title -->
222 <!-- box / title -->
223 <div class="title">
223 <div class="title">
224 <h5>${_('Commit activity by day / author')}</h5>
224 <h5>${_('Commit activity by day / author')}</h5>
225 </div>
225 </div>
226
226
227 <div class="graph">
227 <div class="graph">
228 <div style="padding:0 10px 10px 17px;">
228 <div style="padding:0 10px 10px 17px;">
229 %if c.no_data:
229 %if c.no_data:
230 ${c.no_data_msg}
230 ${c.no_data_msg}
231 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
231 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
232 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
232 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
233 %endif
233 %endif
234 %else:
234 %else:
235 ${_('Stats gathered: ')} ${c.stats_percentage}%
235 ${_('Stats gathered: ')} ${c.stats_percentage}%
236 %endif
236 %endif
237 </div>
237 </div>
238 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
238 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
239 <div style="clear: both;height: 10px"></div>
239 <div style="clear: both;height: 10px"></div>
240 <div id="overview" style="width:450px;height:100px;float:left"></div>
240 <div id="overview" style="width:450px;height:100px;float:left"></div>
241
241
242 <div id="legend_data" style="clear:both;margin-top:10px;">
242 <div id="legend_data" style="clear:both;margin-top:10px;">
243 <div id="legend_container"></div>
243 <div id="legend_container"></div>
244 <div id="legend_choices">
244 <div id="legend_choices">
245 <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
245 <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
246 </div>
246 </div>
247 </div>
247 </div>
248 </div>
248 </div>
249 </div>
249 </div>
250 %endif
250 %endif
251
251
252 <div class="box">
252 <div class="box">
253 <div class="title">
253 <div class="title">
254 <div class="breadcrumbs">
254 <div class="breadcrumbs">
255 %if c.repo_changesets:
255 %if c.repo_changesets:
256 ${h.link_to(_('Latest changes'),h.url('changelog_home',repo_name=c.repo_name))}
256 ${h.link_to(_('Latest changes'),h.url('changelog_home',repo_name=c.repo_name))}
257 %else:
257 %else:
258 ${_('Quick start')}
258 ${_('Quick start')}
259 %endif
259 %endif
260 </div>
260 </div>
261 </div>
261 </div>
262 <div class="table">
262 <div class="table">
263 <div id="shortlog_data">
263 <div id="shortlog_data">
264 <%include file='../shortlog/shortlog_data.html'/>
264 <%include file='../shortlog/shortlog_data.html'/>
265 </div>
265 </div>
266 </div>
266 </div>
267 </div>
267 </div>
268
268
269 %if c.readme_data:
269 %if c.readme_data:
270 <div id="readme" class="anchor">
270 <div id="readme" class="anchor">
271 <div class="box" style="background-color: #FAFAFA">
271 <div class="box" style="background-color: #FAFAFA">
272 <div class="title" title="${_("Readme file at revision '%s'" % c.rhodecode_db_repo.landing_rev)}">
272 <div class="title" title="${_("Readme file at revision '%s'" % c.rhodecode_db_repo.landing_rev)}">
273 <div class="breadcrumbs">
273 <div class="breadcrumbs">
274 <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
274 <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
275 <a class="permalink" href="#readme" title="${_('Permalink to this readme')}">&para;</a>
275 <a class="permalink" href="#readme" title="${_('Permalink to this readme')}">&para;</a>
276 </div>
276 </div>
277 </div>
277 </div>
278 <div class="readme">
278 <div class="readme">
279 <div class="readme_box">
279 <div class="readme_box">
280 ${c.readme_data|n}
280 ${c.readme_data|n}
281 </div>
281 </div>
282 </div>
282 </div>
283 </div>
283 </div>
284 </div>
284 </div>
285 %endif
285 %endif
286
286
287 <script type="text/javascript">
287 <script type="text/javascript">
288 var clone_url = 'clone_url';
288 var clone_url = 'clone_url';
289 YUE.on(clone_url,'click',function(e){
289 YUE.on(clone_url,'click',function(e){
290 if(YUD.hasClass(clone_url,'selected')){
290 if(YUD.hasClass(clone_url,'selected')){
291 return
291 return
292 }
292 }
293 else{
293 else{
294 YUD.addClass(clone_url,'selected');
294 YUD.addClass(clone_url,'selected');
295 YUD.get(clone_url).select();
295 YUD.get(clone_url).select();
296 }
296 }
297 })
297 })
298
298
299 YUE.on('clone_by_name','click',function(e){
299 YUE.on('clone_by_name','click',function(e){
300 // show url by name and hide name button
300 // show url by name and hide name button
301 YUD.setStyle('clone_url','display','');
301 YUD.setStyle('clone_url','display','');
302 YUD.setStyle('clone_by_name','display','none');
302 YUD.setStyle('clone_by_name','display','none');
303
303
304 // hide url by id and show name button
304 // hide url by id and show name button
305 YUD.setStyle('clone_by_id','display','');
305 YUD.setStyle('clone_by_id','display','');
306 YUD.setStyle('clone_url_id','display','none');
306 YUD.setStyle('clone_url_id','display','none');
307
307
308 })
308 })
309 YUE.on('clone_by_id','click',function(e){
309 YUE.on('clone_by_id','click',function(e){
310
310
311 // show url by id and hide id button
311 // show url by id and hide id button
312 YUD.setStyle('clone_by_id','display','none');
312 YUD.setStyle('clone_by_id','display','none');
313 YUD.setStyle('clone_url_id','display','');
313 YUD.setStyle('clone_url_id','display','');
314
314
315 // hide url by name and show id button
315 // hide url by name and show id button
316 YUD.setStyle('clone_by_name','display','');
316 YUD.setStyle('clone_by_name','display','');
317 YUD.setStyle('clone_url','display','none');
317 YUD.setStyle('clone_url','display','none');
318 })
318 })
319
319
320
320
321 var tmpl_links = {};
321 var tmpl_links = {};
322 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
322 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
323 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
323 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
324 %endfor
324 %endfor
325
325
326 YUE.on(['download_options','archive_subrepos'],'change',function(e){
326 YUE.on(['download_options','archive_subrepos'],'change',function(e){
327 var sm = YUD.get('download_options');
327 var sm = YUD.get('download_options');
328 var new_cs = sm.options[sm.selectedIndex];
328 var new_cs = sm.options[sm.selectedIndex];
329
329
330 for(k in tmpl_links){
330 for(k in tmpl_links){
331 var s = YUD.get(k+'_link');
331 var s = YUD.get(k+'_link');
332 if(s){
332 if(s){
333 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
333 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
334 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
334 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
335 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
335 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
336
336
337 var url = tmpl_links[k].replace('__CS__',new_cs.value);
337 var url = tmpl_links[k].replace('__CS__',new_cs.value);
338 var subrepos = YUD.get('archive_subrepos').checked;
338 var subrepos = YUD.get('archive_subrepos').checked;
339 url = url.replace('__SUB__',subrepos);
339 url = url.replace('__SUB__',subrepos);
340 url = url.replace('__NAME__',title_tmpl);
340 url = url.replace('__NAME__',title_tmpl);
341 s.innerHTML = url
341 s.innerHTML = url
342 }
342 }
343 }
343 }
344 });
344 });
345 </script>
345 </script>
346 %if c.show_stats:
346 %if c.show_stats:
347 <script type="text/javascript">
347 <script type="text/javascript">
348 var data = ${c.trending_languages|n};
348 var data = ${c.trending_languages|n};
349 var total = 0;
349 var total = 0;
350 var no_data = true;
350 var no_data = true;
351 var tbl = document.createElement('table');
351 var tbl = document.createElement('table');
352 tbl.setAttribute('class','trending_language_tbl');
352 tbl.setAttribute('class','trending_language_tbl');
353 var cnt = 0;
353 var cnt = 0;
354 for (var i=0;i<data.length;i++){
354 for (var i=0;i<data.length;i++){
355 total+= data[i][1].count;
355 total+= data[i][1].count;
356 }
356 }
357 for (var i=0;i<data.length;i++){
357 for (var i=0;i<data.length;i++){
358 cnt += 1;
358 cnt += 1;
359 no_data = false;
359 no_data = false;
360
360
361 var hide = cnt>2;
361 var hide = cnt>2;
362 var tr = document.createElement('tr');
362 var tr = document.createElement('tr');
363 if (hide){
363 if (hide){
364 tr.setAttribute('style','display:none');
364 tr.setAttribute('style','display:none');
365 tr.setAttribute('class','stats_hidden');
365 tr.setAttribute('class','stats_hidden');
366 }
366 }
367 var k = data[i][0];
367 var k = data[i][0];
368 var obj = data[i][1];
368 var obj = data[i][1];
369 var percentage = Math.round((obj.count/total*100),2);
369 var percentage = Math.round((obj.count/total*100),2);
370
370
371 var td1 = document.createElement('td');
371 var td1 = document.createElement('td');
372 td1.width = 150;
372 td1.width = 150;
373 var trending_language_label = document.createElement('div');
373 var trending_language_label = document.createElement('div');
374 trending_language_label.innerHTML = obj.desc+" ("+k+")";
374 trending_language_label.innerHTML = obj.desc+" ("+k+")";
375 td1.appendChild(trending_language_label);
375 td1.appendChild(trending_language_label);
376
376
377 var td2 = document.createElement('td');
377 var td2 = document.createElement('td');
378 td2.setAttribute('style','padding-right:14px !important');
378 td2.setAttribute('style','padding-right:14px !important');
379 var trending_language = document.createElement('div');
379 var trending_language = document.createElement('div');
380 var nr_files = obj.count+" ${_('files')}";
380 var nr_files = obj.count+" ${_('files')}";
381
381
382 trending_language.title = k+" "+nr_files;
382 trending_language.title = k+" "+nr_files;
383
383
384 if (percentage>22){
384 if (percentage>22){
385 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
385 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
386 }
386 }
387 else{
387 else{
388 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
388 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
389 }
389 }
390
390
391 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
391 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
392 trending_language.style.width=percentage+"%";
392 trending_language.style.width=percentage+"%";
393 td2.appendChild(trending_language);
393 td2.appendChild(trending_language);
394
394
395 tr.appendChild(td1);
395 tr.appendChild(td1);
396 tr.appendChild(td2);
396 tr.appendChild(td2);
397 tbl.appendChild(tr);
397 tbl.appendChild(tr);
398 if(cnt == 3){
398 if(cnt == 3){
399 var show_more = document.createElement('tr');
399 var show_more = document.createElement('tr');
400 var td = document.createElement('td');
400 var td = document.createElement('td');
401 lnk = document.createElement('a');
401 lnk = document.createElement('a');
402
402
403 lnk.href='#';
403 lnk.href='#';
404 lnk.innerHTML = "${_('Show more')}";
404 lnk.innerHTML = "${_('Show more')}";
405 lnk.id='code_stats_show_more';
405 lnk.id='code_stats_show_more';
406 td.appendChild(lnk);
406 td.appendChild(lnk);
407
407
408 show_more.appendChild(td);
408 show_more.appendChild(td);
409 show_more.appendChild(document.createElement('td'));
409 show_more.appendChild(document.createElement('td'));
410 tbl.appendChild(show_more);
410 tbl.appendChild(show_more);
411 }
411 }
412
412
413 }
413 }
414
414
415 YUD.get('lang_stats').appendChild(tbl);
415 YUD.get('lang_stats').appendChild(tbl);
416 YUE.on('code_stats_show_more','click',function(){
416 YUE.on('code_stats_show_more','click',function(){
417 l = YUD.getElementsByClassName('stats_hidden')
417 l = YUD.getElementsByClassName('stats_hidden')
418 for (e in l){
418 for (e in l){
419 YUD.setStyle(l[e],'display','');
419 YUD.setStyle(l[e],'display','');
420 };
420 };
421 YUD.setStyle(YUD.get('code_stats_show_more'),
421 YUD.setStyle(YUD.get('code_stats_show_more'),
422 'display','none');
422 'display','none');
423 });
423 });
424 </script>
424 </script>
425 <script type="text/javascript">
425 <script type="text/javascript">
426 /**
426 /**
427 * Plots summary graph
427 * Plots summary graph
428 *
428 *
429 * @class SummaryPlot
429 * @class SummaryPlot
430 * @param {from} initial from for detailed graph
430 * @param {from} initial from for detailed graph
431 * @param {to} initial to for detailed graph
431 * @param {to} initial to for detailed graph
432 * @param {dataset}
432 * @param {dataset}
433 * @param {overview_dataset}
433 * @param {overview_dataset}
434 */
434 */
435 function SummaryPlot(from,to,dataset,overview_dataset) {
435 function SummaryPlot(from,to,dataset,overview_dataset) {
436 var initial_ranges = {
436 var initial_ranges = {
437 "xaxis":{
437 "xaxis":{
438 "from":from,
438 "from":from,
439 "to":to,
439 "to":to,
440 },
440 },
441 };
441 };
442 var dataset = dataset;
442 var dataset = dataset;
443 var overview_dataset = [overview_dataset];
443 var overview_dataset = [overview_dataset];
444 var choiceContainer = YUD.get("legend_choices");
444 var choiceContainer = YUD.get("legend_choices");
445 var choiceContainerTable = YUD.get("legend_choices_tables");
445 var choiceContainerTable = YUD.get("legend_choices_tables");
446 var plotContainer = YUD.get('commit_history');
446 var plotContainer = YUD.get('commit_history');
447 var overviewContainer = YUD.get('overview');
447 var overviewContainer = YUD.get('overview');
448
448
449 var plot_options = {
449 var plot_options = {
450 bars: {show:true,align:'center',lineWidth:4},
450 bars: {show:true,align:'center',lineWidth:4},
451 legend: {show:true, container:"legend_container"},
451 legend: {show:true, container:"legend_container"},
452 points: {show:true,radius:0,fill:false},
452 points: {show:true,radius:0,fill:false},
453 yaxis: {tickDecimals:0,},
453 yaxis: {tickDecimals:0,},
454 xaxis: {
454 xaxis: {
455 mode: "time",
455 mode: "time",
456 timeformat: "%d/%m",
456 timeformat: "%d/%m",
457 min:from,
457 min:from,
458 max:to,
458 max:to,
459 },
459 },
460 grid: {
460 grid: {
461 hoverable: true,
461 hoverable: true,
462 clickable: true,
462 clickable: true,
463 autoHighlight:true,
463 autoHighlight:true,
464 color: "#999"
464 color: "#999"
465 },
465 },
466 //selection: {mode: "x"}
466 //selection: {mode: "x"}
467 };
467 };
468 var overview_options = {
468 var overview_options = {
469 legend:{show:false},
469 legend:{show:false},
470 bars: {show:true,barWidth: 2,},
470 bars: {show:true,barWidth: 2,},
471 shadowSize: 0,
471 shadowSize: 0,
472 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
472 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
473 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
473 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
474 grid: {color: "#999",},
474 grid: {color: "#999",},
475 selection: {mode: "x"}
475 selection: {mode: "x"}
476 };
476 };
477
477
478 /**
478 /**
479 *get dummy data needed in few places
479 *get dummy data needed in few places
480 */
480 */
481 function getDummyData(label){
481 function getDummyData(label){
482 return {"label":label,
482 return {"label":label,
483 "data":[{"time":0,
483 "data":[{"time":0,
484 "commits":0,
484 "commits":0,
485 "added":0,
485 "added":0,
486 "changed":0,
486 "changed":0,
487 "removed":0,
487 "removed":0,
488 }],
488 }],
489 "schema":["commits"],
489 "schema":["commits"],
490 "color":'#ffffff',
490 "color":'#ffffff',
491 }
491 }
492 }
492 }
493
493
494 /**
494 /**
495 * generate checkboxes accordindly to data
495 * generate checkboxes accordindly to data
496 * @param keys
496 * @param keys
497 * @returns
497 * @returns
498 */
498 */
499 function generateCheckboxes(data) {
499 function generateCheckboxes(data) {
500 //append checkboxes
500 //append checkboxes
501 var i = 0;
501 var i = 0;
502 choiceContainerTable.innerHTML = '';
502 choiceContainerTable.innerHTML = '';
503 for(var pos in data) {
503 for(var pos in data) {
504
504
505 data[pos].color = i;
505 data[pos].color = i;
506 i++;
506 i++;
507 if(data[pos].label != ''){
507 if(data[pos].label != ''){
508 choiceContainerTable.innerHTML +=
508 choiceContainerTable.innerHTML +=
509 '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
509 '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
510 <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
510 <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
511 }
511 }
512 }
512 }
513 }
513 }
514
514
515 /**
515 /**
516 * ToolTip show
516 * ToolTip show
517 */
517 */
518 function showTooltip(x, y, contents) {
518 function showTooltip(x, y, contents) {
519 var div=document.getElementById('tooltip');
519 var div=document.getElementById('tooltip');
520 if(!div) {
520 if(!div) {
521 div = document.createElement('div');
521 div = document.createElement('div');
522 div.id="tooltip";
522 div.id="tooltip";
523 div.style.position="absolute";
523 div.style.position="absolute";
524 div.style.border='1px solid #fdd';
524 div.style.border='1px solid #fdd';
525 div.style.padding='2px';
525 div.style.padding='2px';
526 div.style.backgroundColor='#fee';
526 div.style.backgroundColor='#fee';
527 document.body.appendChild(div);
527 document.body.appendChild(div);
528 }
528 }
529 YUD.setStyle(div, 'opacity', 0);
529 YUD.setStyle(div, 'opacity', 0);
530 div.innerHTML = contents;
530 div.innerHTML = contents;
531 div.style.top=(y + 5) + "px";
531 div.style.top=(y + 5) + "px";
532 div.style.left=(x + 5) + "px";
532 div.style.left=(x + 5) + "px";
533
533
534 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
534 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
535 anim.animate();
535 anim.animate();
536 }
536 }
537
537
538 /**
538 /**
539 * This function will detect if selected period has some changesets
539 * This function will detect if selected period has some changesets
540 for this user if it does this data is then pushed for displaying
540 for this user if it does this data is then pushed for displaying
541 Additionally it will only display users that are selected by the checkbox
541 Additionally it will only display users that are selected by the checkbox
542 */
542 */
543 function getDataAccordingToRanges(ranges) {
543 function getDataAccordingToRanges(ranges) {
544
544
545 var data = [];
545 var data = [];
546 var new_dataset = {};
546 var new_dataset = {};
547 var keys = [];
547 var keys = [];
548 var max_commits = 0;
548 var max_commits = 0;
549 for(var key in dataset){
549 for(var key in dataset){
550
550
551 for(var ds in dataset[key].data){
551 for(var ds in dataset[key].data){
552 commit_data = dataset[key].data[ds];
552 commit_data = dataset[key].data[ds];
553 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
553 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
554
554
555 if(new_dataset[key] === undefined){
555 if(new_dataset[key] === undefined){
556 new_dataset[key] = {data:[],schema:["commits"],label:key};
556 new_dataset[key] = {data:[],schema:["commits"],label:key};
557 }
557 }
558 new_dataset[key].data.push(commit_data);
558 new_dataset[key].data.push(commit_data);
559 }
559 }
560 }
560 }
561 if (new_dataset[key] !== undefined){
561 if (new_dataset[key] !== undefined){
562 data.push(new_dataset[key]);
562 data.push(new_dataset[key]);
563 }
563 }
564 }
564 }
565
565
566 if (data.length > 0){
566 if (data.length > 0){
567 return data;
567 return data;
568 }
568 }
569 else{
569 else{
570 //just return dummy data for graph to plot itself
570 //just return dummy data for graph to plot itself
571 return [getDummyData('')];
571 return [getDummyData('')];
572 }
572 }
573 }
573 }
574
574
575 /**
575 /**
576 * redraw using new checkbox data
576 * redraw using new checkbox data
577 */
577 */
578 function plotchoiced(e,args){
578 function plotchoiced(e,args){
579 var cur_data = args[0];
579 var cur_data = args[0];
580 var cur_ranges = args[1];
580 var cur_ranges = args[1];
581
581
582 var new_data = [];
582 var new_data = [];
583 var inputs = choiceContainer.getElementsByTagName("input");
583 var inputs = choiceContainer.getElementsByTagName("input");
584
584
585 //show only checked labels
585 //show only checked labels
586 for(var i=0; i<inputs.length; i++) {
586 for(var i=0; i<inputs.length; i++) {
587 var checkbox_key = inputs[i].name;
587 var checkbox_key = inputs[i].name;
588
588
589 if(inputs[i].checked){
589 if(inputs[i].checked){
590 for(var d in cur_data){
590 for(var d in cur_data){
591 if(cur_data[d].label == checkbox_key){
591 if(cur_data[d].label == checkbox_key){
592 new_data.push(cur_data[d]);
592 new_data.push(cur_data[d]);
593 }
593 }
594 }
594 }
595 }
595 }
596 else{
596 else{
597 //push dummy data to not hide the label
597 //push dummy data to not hide the label
598 new_data.push(getDummyData(checkbox_key));
598 new_data.push(getDummyData(checkbox_key));
599 }
599 }
600 }
600 }
601
601
602 var new_options = YAHOO.lang.merge(plot_options, {
602 var new_options = YAHOO.lang.merge(plot_options, {
603 xaxis: {
603 xaxis: {
604 min: cur_ranges.xaxis.from,
604 min: cur_ranges.xaxis.from,
605 max: cur_ranges.xaxis.to,
605 max: cur_ranges.xaxis.to,
606 mode:"time",
606 mode:"time",
607 timeformat: "%d/%m",
607 timeformat: "%d/%m",
608 },
608 },
609 });
609 });
610 if (!new_data){
610 if (!new_data){
611 new_data = [[0,1]];
611 new_data = [[0,1]];
612 }
612 }
613 // do the zooming
613 // do the zooming
614 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
614 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
615
615
616 plot.subscribe("plotselected", plotselected);
616 plot.subscribe("plotselected", plotselected);
617
617
618 //resubscribe plothover
618 //resubscribe plothover
619 plot.subscribe("plothover", plothover);
619 plot.subscribe("plothover", plothover);
620
620
621 // don't fire event on the overview to prevent eternal loop
621 // don't fire event on the overview to prevent eternal loop
622 overview.setSelection(cur_ranges, true);
622 overview.setSelection(cur_ranges, true);
623
623
624 }
624 }
625
625
626 /**
626 /**
627 * plot only selected items from overview
627 * plot only selected items from overview
628 * @param ranges
628 * @param ranges
629 * @returns
629 * @returns
630 */
630 */
631 function plotselected(ranges,cur_data) {
631 function plotselected(ranges,cur_data) {
632 //updates the data for new plot
632 //updates the data for new plot
633 var data = getDataAccordingToRanges(ranges);
633 var data = getDataAccordingToRanges(ranges);
634 generateCheckboxes(data);
634 generateCheckboxes(data);
635
635
636 var new_options = YAHOO.lang.merge(plot_options, {
636 var new_options = YAHOO.lang.merge(plot_options, {
637 xaxis: {
637 xaxis: {
638 min: ranges.xaxis.from,
638 min: ranges.xaxis.from,
639 max: ranges.xaxis.to,
639 max: ranges.xaxis.to,
640 mode:"time",
640 mode:"time",
641 timeformat: "%d/%m",
641 timeformat: "%d/%m",
642 },
642 },
643 });
643 });
644 // do the zooming
644 // do the zooming
645 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
645 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
646
646
647 plot.subscribe("plotselected", plotselected);
647 plot.subscribe("plotselected", plotselected);
648
648
649 //resubscribe plothover
649 //resubscribe plothover
650 plot.subscribe("plothover", plothover);
650 plot.subscribe("plothover", plothover);
651
651
652 // don't fire event on the overview to prevent eternal loop
652 // don't fire event on the overview to prevent eternal loop
653 overview.setSelection(ranges, true);
653 overview.setSelection(ranges, true);
654
654
655 //resubscribe choiced
655 //resubscribe choiced
656 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
656 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
657 }
657 }
658
658
659 var previousPoint = null;
659 var previousPoint = null;
660
660
661 function plothover(o) {
661 function plothover(o) {
662 var pos = o.pos;
662 var pos = o.pos;
663 var item = o.item;
663 var item = o.item;
664
664
665 //YUD.get("x").innerHTML = pos.x.toFixed(2);
665 //YUD.get("x").innerHTML = pos.x.toFixed(2);
666 //YUD.get("y").innerHTML = pos.y.toFixed(2);
666 //YUD.get("y").innerHTML = pos.y.toFixed(2);
667 if (item) {
667 if (item) {
668 if (previousPoint != item.datapoint) {
668 if (previousPoint != item.datapoint) {
669 previousPoint = item.datapoint;
669 previousPoint = item.datapoint;
670
670
671 var tooltip = YUD.get("tooltip");
671 var tooltip = YUD.get("tooltip");
672 if(tooltip) {
672 if(tooltip) {
673 tooltip.parentNode.removeChild(tooltip);
673 tooltip.parentNode.removeChild(tooltip);
674 }
674 }
675 var x = item.datapoint.x.toFixed(2);
675 var x = item.datapoint.x.toFixed(2);
676 var y = item.datapoint.y.toFixed(2);
676 var y = item.datapoint.y.toFixed(2);
677
677
678 if (!item.series.label){
678 if (!item.series.label){
679 item.series.label = 'commits';
679 item.series.label = 'commits';
680 }
680 }
681 var d = new Date(x*1000);
681 var d = new Date(x*1000);
682 var fd = d.toDateString()
682 var fd = d.toDateString()
683 var nr_commits = parseInt(y);
683 var nr_commits = parseInt(y);
684
684
685 var cur_data = dataset[item.series.label].data[item.dataIndex];
685 var cur_data = dataset[item.series.label].data[item.dataIndex];
686 var added = cur_data.added;
686 var added = cur_data.added;
687 var changed = cur_data.changed;
687 var changed = cur_data.changed;
688 var removed = cur_data.removed;
688 var removed = cur_data.removed;
689
689
690 var nr_commits_suffix = " ${_('commits')} ";
690 var nr_commits_suffix = " ${_('commits')} ";
691 var added_suffix = " ${_('files added')} ";
691 var added_suffix = " ${_('files added')} ";
692 var changed_suffix = " ${_('files changed')} ";
692 var changed_suffix = " ${_('files changed')} ";
693 var removed_suffix = " ${_('files removed')} ";
693 var removed_suffix = " ${_('files removed')} ";
694
694
695 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
695 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
696 if(added==1){added_suffix=" ${_('file added')} ";}
696 if(added==1){added_suffix=" ${_('file added')} ";}
697 if(changed==1){changed_suffix=" ${_('file changed')} ";}
697 if(changed==1){changed_suffix=" ${_('file changed')} ";}
698 if(removed==1){removed_suffix=" ${_('file removed')} ";}
698 if(removed==1){removed_suffix=" ${_('file removed')} ";}
699
699
700 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
700 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
701 +'<br/>'+
701 +'<br/>'+
702 nr_commits + nr_commits_suffix+'<br/>'+
702 nr_commits + nr_commits_suffix+'<br/>'+
703 added + added_suffix +'<br/>'+
703 added + added_suffix +'<br/>'+
704 changed + changed_suffix + '<br/>'+
704 changed + changed_suffix + '<br/>'+
705 removed + removed_suffix + '<br/>');
705 removed + removed_suffix + '<br/>');
706 }
706 }
707 }
707 }
708 else {
708 else {
709 var tooltip = YUD.get("tooltip");
709 var tooltip = YUD.get("tooltip");
710
710
711 if(tooltip) {
711 if(tooltip) {
712 tooltip.parentNode.removeChild(tooltip);
712 tooltip.parentNode.removeChild(tooltip);
713 }
713 }
714 previousPoint = null;
714 previousPoint = null;
715 }
715 }
716 }
716 }
717
717
718 /**
718 /**
719 * MAIN EXECUTION
719 * MAIN EXECUTION
720 */
720 */
721
721
722 var data = getDataAccordingToRanges(initial_ranges);
722 var data = getDataAccordingToRanges(initial_ranges);
723 generateCheckboxes(data);
723 generateCheckboxes(data);
724
724
725 //main plot
725 //main plot
726 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
726 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
727
727
728 //overview
728 //overview
729 var overview = YAHOO.widget.Flot(overviewContainer,
729 var overview = YAHOO.widget.Flot(overviewContainer,
730 overview_dataset, overview_options);
730 overview_dataset, overview_options);
731
731
732 //show initial selection on overview
732 //show initial selection on overview
733 overview.setSelection(initial_ranges);
733 overview.setSelection(initial_ranges);
734
734
735 plot.subscribe("plotselected", plotselected);
735 plot.subscribe("plotselected", plotselected);
736 plot.subscribe("plothover", plothover)
736 plot.subscribe("plothover", plothover)
737
737
738 overview.subscribe("plotselected", function (ranges) {
738 overview.subscribe("plotselected", function (ranges) {
739 plot.setSelection(ranges);
739 plot.setSelection(ranges);
740 });
740 });
741
741
742 // user choices on overview
742 // user choices on overview
743 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
743 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
744 }
744 }
745 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
745 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
746 </script>
746 </script>
747 %endif
747 %endif
748
748
749 </%def>
749 </%def>
General Comments 0
You need to be logged in to leave comments. Login now