##// END OF EJS Templates
default-reviewers: changes for source/target branch distinction and new rule name....
marcink -
r2435:56737c0a default
parent child Browse files
Show More
@@ -0,0 +1,41 b''
1 import logging
2
3 from sqlalchemy import *
4
5 from rhodecode.model import meta
6 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
7
8 log = logging.getLogger(__name__)
9
10
11 def upgrade(migrate_engine):
12 """
13 Upgrade operations go here.
14 Don't create your own engine; bind migrate_engine to your metadata
15 """
16 _reset_base(migrate_engine)
17 from rhodecode.lib.dbmigrate.schema import db_4_11_0_0 as db
18
19 review_rule_table = db.RepoReviewRule.__table__
20
21 target_branch_pattern = Column(
22 "target_branch_pattern",
23 UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*')
24 target_branch_pattern.create(table=review_rule_table)
25
26 review_rule_name = Column('review_rule_name', String(255))
27 review_rule_name.create(table=review_rule_table)
28
29 # issue fixups
30 fixups(db, meta.Session)
31
32
33 def downgrade(migrate_engine):
34 meta = MetaData()
35 meta.bind = migrate_engine
36
37
38 def fixups(models, _SESSION):
39 pass
40
41
@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pyramid
43 # link to config for pyramid
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 82 # defines current db version for migrations
54 __dbversion__ = 83 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'https://code.rhodecode.com'
58 __url__ = 'https://code.rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,4381 +1,4400 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37 from sqlalchemy import (
37 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
38 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
41 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false
42 from sqlalchemy.sql.expression import true, false
43 from sqlalchemy.sql.functions import coalesce, count # noqa
43 from sqlalchemy.sql.functions import coalesce, count # noqa
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
45 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # noqa
48 from sqlalchemy.exc import IntegrityError # noqa
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from beaker.cache import cache_region
50 from beaker.cache import cache_region
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
52
52
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54
54
55 from rhodecode.translation import _
55 from rhodecode.translation import _
56 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.utils2 import (
58 from rhodecode.lib.utils2 import (
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 glob2re, StrictAttributeDict, cleaned_uri)
61 glob2re, StrictAttributeDict, cleaned_uri)
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 JsonRaw
63 JsonRaw
64 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.encrypt import AESCipher
66 from rhodecode.lib.encrypt import AESCipher
67
67
68 from rhodecode.model.meta import Base, Session
68 from rhodecode.model.meta import Base, Session
69
69
70 URL_SEP = '/'
70 URL_SEP = '/'
71 log = logging.getLogger(__name__)
71 log = logging.getLogger(__name__)
72
72
73 # =============================================================================
73 # =============================================================================
74 # BASE CLASSES
74 # BASE CLASSES
75 # =============================================================================
75 # =============================================================================
76
76
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # beaker.session.secret if first is not set.
78 # beaker.session.secret if first is not set.
79 # and initialized at environment.py
79 # and initialized at environment.py
80 ENCRYPTION_KEY = None
80 ENCRYPTION_KEY = None
81
81
82 # used to sort permissions by types, '#' used here is not allowed to be in
82 # used to sort permissions by types, '#' used here is not allowed to be in
83 # usernames, and it's very early in sorted string.printable table.
83 # usernames, and it's very early in sorted string.printable table.
84 PERMISSION_TYPE_SORT = {
84 PERMISSION_TYPE_SORT = {
85 'admin': '####',
85 'admin': '####',
86 'write': '###',
86 'write': '###',
87 'read': '##',
87 'read': '##',
88 'none': '#',
88 'none': '#',
89 }
89 }
90
90
91
91
92 def display_user_sort(obj):
92 def display_user_sort(obj):
93 """
93 """
94 Sort function used to sort permissions in .permissions() function of
94 Sort function used to sort permissions in .permissions() function of
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 of all other resources
96 of all other resources
97 """
97 """
98
98
99 if obj.username == User.DEFAULT_USER:
99 if obj.username == User.DEFAULT_USER:
100 return '#####'
100 return '#####'
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 return prefix + obj.username
102 return prefix + obj.username
103
103
104
104
105 def display_user_group_sort(obj):
105 def display_user_group_sort(obj):
106 """
106 """
107 Sort function used to sort permissions in .permissions() function of
107 Sort function used to sort permissions in .permissions() function of
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 of all other resources
109 of all other resources
110 """
110 """
111
111
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 return prefix + obj.users_group_name
113 return prefix + obj.users_group_name
114
114
115
115
116 def _hash_key(k):
116 def _hash_key(k):
117 return md5_safe(k)
117 return md5_safe(k)
118
118
119
119
120 def in_filter_generator(qry, items, limit=500):
120 def in_filter_generator(qry, items, limit=500):
121 """
121 """
122 Splits IN() into multiple with OR
122 Splits IN() into multiple with OR
123 e.g.::
123 e.g.::
124 cnt = Repository.query().filter(
124 cnt = Repository.query().filter(
125 or_(
125 or_(
126 *in_filter_generator(Repository.repo_id, range(100000))
126 *in_filter_generator(Repository.repo_id, range(100000))
127 )).count()
127 )).count()
128 """
128 """
129 if not items:
129 if not items:
130 # empty list will cause empty query which might cause security issues
130 # empty list will cause empty query which might cause security issues
131 # this can lead to hidden unpleasant results
131 # this can lead to hidden unpleasant results
132 items = [-1]
132 items = [-1]
133
133
134 parts = []
134 parts = []
135 for chunk in xrange(0, len(items), limit):
135 for chunk in xrange(0, len(items), limit):
136 parts.append(
136 parts.append(
137 qry.in_(items[chunk: chunk + limit])
137 qry.in_(items[chunk: chunk + limit])
138 )
138 )
139
139
140 return parts
140 return parts
141
141
142
142
143 class EncryptedTextValue(TypeDecorator):
143 class EncryptedTextValue(TypeDecorator):
144 """
144 """
145 Special column for encrypted long text data, use like::
145 Special column for encrypted long text data, use like::
146
146
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
148
148
149 This column is intelligent so if value is in unencrypted form it return
149 This column is intelligent so if value is in unencrypted form it return
150 unencrypted form, but on save it always encrypts
150 unencrypted form, but on save it always encrypts
151 """
151 """
152 impl = Text
152 impl = Text
153
153
154 def process_bind_param(self, value, dialect):
154 def process_bind_param(self, value, dialect):
155 if not value:
155 if not value:
156 return value
156 return value
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
158 # protect against double encrypting if someone manually starts
158 # protect against double encrypting if someone manually starts
159 # doing
159 # doing
160 raise ValueError('value needs to be in unencrypted format, ie. '
160 raise ValueError('value needs to be in unencrypted format, ie. '
161 'not starting with enc$aes')
161 'not starting with enc$aes')
162 return 'enc$aes_hmac$%s' % AESCipher(
162 return 'enc$aes_hmac$%s' % AESCipher(
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
164
164
165 def process_result_value(self, value, dialect):
165 def process_result_value(self, value, dialect):
166 import rhodecode
166 import rhodecode
167
167
168 if not value:
168 if not value:
169 return value
169 return value
170
170
171 parts = value.split('$', 3)
171 parts = value.split('$', 3)
172 if not len(parts) == 3:
172 if not len(parts) == 3:
173 # probably not encrypted values
173 # probably not encrypted values
174 return value
174 return value
175 else:
175 else:
176 if parts[0] != 'enc':
176 if parts[0] != 'enc':
177 # parts ok but without our header ?
177 # parts ok but without our header ?
178 return value
178 return value
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
180 'rhodecode.encrypted_values.strict') or True)
180 'rhodecode.encrypted_values.strict') or True)
181 # at that stage we know it's our encryption
181 # at that stage we know it's our encryption
182 if parts[1] == 'aes':
182 if parts[1] == 'aes':
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
184 elif parts[1] == 'aes_hmac':
184 elif parts[1] == 'aes_hmac':
185 decrypted_data = AESCipher(
185 decrypted_data = AESCipher(
186 ENCRYPTION_KEY, hmac=True,
186 ENCRYPTION_KEY, hmac=True,
187 strict_verification=enc_strict_mode).decrypt(parts[2])
187 strict_verification=enc_strict_mode).decrypt(parts[2])
188 else:
188 else:
189 raise ValueError(
189 raise ValueError(
190 'Encryption type part is wrong, must be `aes` '
190 'Encryption type part is wrong, must be `aes` '
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
192 return decrypted_data
192 return decrypted_data
193
193
194
194
195 class BaseModel(object):
195 class BaseModel(object):
196 """
196 """
197 Base Model for all classes
197 Base Model for all classes
198 """
198 """
199
199
200 @classmethod
200 @classmethod
201 def _get_keys(cls):
201 def _get_keys(cls):
202 """return column names for this model """
202 """return column names for this model """
203 return class_mapper(cls).c.keys()
203 return class_mapper(cls).c.keys()
204
204
205 def get_dict(self):
205 def get_dict(self):
206 """
206 """
207 return dict with keys and values corresponding
207 return dict with keys and values corresponding
208 to this model data """
208 to this model data """
209
209
210 d = {}
210 d = {}
211 for k in self._get_keys():
211 for k in self._get_keys():
212 d[k] = getattr(self, k)
212 d[k] = getattr(self, k)
213
213
214 # also use __json__() if present to get additional fields
214 # also use __json__() if present to get additional fields
215 _json_attr = getattr(self, '__json__', None)
215 _json_attr = getattr(self, '__json__', None)
216 if _json_attr:
216 if _json_attr:
217 # update with attributes from __json__
217 # update with attributes from __json__
218 if callable(_json_attr):
218 if callable(_json_attr):
219 _json_attr = _json_attr()
219 _json_attr = _json_attr()
220 for k, val in _json_attr.iteritems():
220 for k, val in _json_attr.iteritems():
221 d[k] = val
221 d[k] = val
222 return d
222 return d
223
223
224 def get_appstruct(self):
224 def get_appstruct(self):
225 """return list with keys and values tuples corresponding
225 """return list with keys and values tuples corresponding
226 to this model data """
226 to this model data """
227
227
228 lst = []
228 lst = []
229 for k in self._get_keys():
229 for k in self._get_keys():
230 lst.append((k, getattr(self, k),))
230 lst.append((k, getattr(self, k),))
231 return lst
231 return lst
232
232
233 def populate_obj(self, populate_dict):
233 def populate_obj(self, populate_dict):
234 """populate model with data from given populate_dict"""
234 """populate model with data from given populate_dict"""
235
235
236 for k in self._get_keys():
236 for k in self._get_keys():
237 if k in populate_dict:
237 if k in populate_dict:
238 setattr(self, k, populate_dict[k])
238 setattr(self, k, populate_dict[k])
239
239
240 @classmethod
240 @classmethod
241 def query(cls):
241 def query(cls):
242 return Session().query(cls)
242 return Session().query(cls)
243
243
244 @classmethod
244 @classmethod
245 def get(cls, id_):
245 def get(cls, id_):
246 if id_:
246 if id_:
247 return cls.query().get(id_)
247 return cls.query().get(id_)
248
248
249 @classmethod
249 @classmethod
250 def get_or_404(cls, id_):
250 def get_or_404(cls, id_):
251 from pyramid.httpexceptions import HTTPNotFound
251 from pyramid.httpexceptions import HTTPNotFound
252
252
253 try:
253 try:
254 id_ = int(id_)
254 id_ = int(id_)
255 except (TypeError, ValueError):
255 except (TypeError, ValueError):
256 raise HTTPNotFound()
256 raise HTTPNotFound()
257
257
258 res = cls.query().get(id_)
258 res = cls.query().get(id_)
259 if not res:
259 if not res:
260 raise HTTPNotFound()
260 raise HTTPNotFound()
261 return res
261 return res
262
262
263 @classmethod
263 @classmethod
264 def getAll(cls):
264 def getAll(cls):
265 # deprecated and left for backward compatibility
265 # deprecated and left for backward compatibility
266 return cls.get_all()
266 return cls.get_all()
267
267
268 @classmethod
268 @classmethod
269 def get_all(cls):
269 def get_all(cls):
270 return cls.query().all()
270 return cls.query().all()
271
271
272 @classmethod
272 @classmethod
273 def delete(cls, id_):
273 def delete(cls, id_):
274 obj = cls.query().get(id_)
274 obj = cls.query().get(id_)
275 Session().delete(obj)
275 Session().delete(obj)
276
276
277 @classmethod
277 @classmethod
278 def identity_cache(cls, session, attr_name, value):
278 def identity_cache(cls, session, attr_name, value):
279 exist_in_session = []
279 exist_in_session = []
280 for (item_cls, pkey), instance in session.identity_map.items():
280 for (item_cls, pkey), instance in session.identity_map.items():
281 if cls == item_cls and getattr(instance, attr_name) == value:
281 if cls == item_cls and getattr(instance, attr_name) == value:
282 exist_in_session.append(instance)
282 exist_in_session.append(instance)
283 if exist_in_session:
283 if exist_in_session:
284 if len(exist_in_session) == 1:
284 if len(exist_in_session) == 1:
285 return exist_in_session[0]
285 return exist_in_session[0]
286 log.exception(
286 log.exception(
287 'multiple objects with attr %s and '
287 'multiple objects with attr %s and '
288 'value %s found with same name: %r',
288 'value %s found with same name: %r',
289 attr_name, value, exist_in_session)
289 attr_name, value, exist_in_session)
290
290
291 def __repr__(self):
291 def __repr__(self):
292 if hasattr(self, '__unicode__'):
292 if hasattr(self, '__unicode__'):
293 # python repr needs to return str
293 # python repr needs to return str
294 try:
294 try:
295 return safe_str(self.__unicode__())
295 return safe_str(self.__unicode__())
296 except UnicodeDecodeError:
296 except UnicodeDecodeError:
297 pass
297 pass
298 return '<DB:%s>' % (self.__class__.__name__)
298 return '<DB:%s>' % (self.__class__.__name__)
299
299
300
300
301 class RhodeCodeSetting(Base, BaseModel):
301 class RhodeCodeSetting(Base, BaseModel):
302 __tablename__ = 'rhodecode_settings'
302 __tablename__ = 'rhodecode_settings'
303 __table_args__ = (
303 __table_args__ = (
304 UniqueConstraint('app_settings_name'),
304 UniqueConstraint('app_settings_name'),
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
307 )
307 )
308
308
309 SETTINGS_TYPES = {
309 SETTINGS_TYPES = {
310 'str': safe_str,
310 'str': safe_str,
311 'int': safe_int,
311 'int': safe_int,
312 'unicode': safe_unicode,
312 'unicode': safe_unicode,
313 'bool': str2bool,
313 'bool': str2bool,
314 'list': functools.partial(aslist, sep=',')
314 'list': functools.partial(aslist, sep=',')
315 }
315 }
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
317 GLOBAL_CONF_KEY = 'app_settings'
317 GLOBAL_CONF_KEY = 'app_settings'
318
318
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
323
323
324 def __init__(self, key='', val='', type='unicode'):
324 def __init__(self, key='', val='', type='unicode'):
325 self.app_settings_name = key
325 self.app_settings_name = key
326 self.app_settings_type = type
326 self.app_settings_type = type
327 self.app_settings_value = val
327 self.app_settings_value = val
328
328
329 @validates('_app_settings_value')
329 @validates('_app_settings_value')
330 def validate_settings_value(self, key, val):
330 def validate_settings_value(self, key, val):
331 assert type(val) == unicode
331 assert type(val) == unicode
332 return val
332 return val
333
333
334 @hybrid_property
334 @hybrid_property
335 def app_settings_value(self):
335 def app_settings_value(self):
336 v = self._app_settings_value
336 v = self._app_settings_value
337 _type = self.app_settings_type
337 _type = self.app_settings_type
338 if _type:
338 if _type:
339 _type = self.app_settings_type.split('.')[0]
339 _type = self.app_settings_type.split('.')[0]
340 # decode the encrypted value
340 # decode the encrypted value
341 if 'encrypted' in self.app_settings_type:
341 if 'encrypted' in self.app_settings_type:
342 cipher = EncryptedTextValue()
342 cipher = EncryptedTextValue()
343 v = safe_unicode(cipher.process_result_value(v, None))
343 v = safe_unicode(cipher.process_result_value(v, None))
344
344
345 converter = self.SETTINGS_TYPES.get(_type) or \
345 converter = self.SETTINGS_TYPES.get(_type) or \
346 self.SETTINGS_TYPES['unicode']
346 self.SETTINGS_TYPES['unicode']
347 return converter(v)
347 return converter(v)
348
348
349 @app_settings_value.setter
349 @app_settings_value.setter
350 def app_settings_value(self, val):
350 def app_settings_value(self, val):
351 """
351 """
352 Setter that will always make sure we use unicode in app_settings_value
352 Setter that will always make sure we use unicode in app_settings_value
353
353
354 :param val:
354 :param val:
355 """
355 """
356 val = safe_unicode(val)
356 val = safe_unicode(val)
357 # encode the encrypted value
357 # encode the encrypted value
358 if 'encrypted' in self.app_settings_type:
358 if 'encrypted' in self.app_settings_type:
359 cipher = EncryptedTextValue()
359 cipher = EncryptedTextValue()
360 val = safe_unicode(cipher.process_bind_param(val, None))
360 val = safe_unicode(cipher.process_bind_param(val, None))
361 self._app_settings_value = val
361 self._app_settings_value = val
362
362
363 @hybrid_property
363 @hybrid_property
364 def app_settings_type(self):
364 def app_settings_type(self):
365 return self._app_settings_type
365 return self._app_settings_type
366
366
367 @app_settings_type.setter
367 @app_settings_type.setter
368 def app_settings_type(self, val):
368 def app_settings_type(self, val):
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
370 raise Exception('type must be one of %s got %s'
370 raise Exception('type must be one of %s got %s'
371 % (self.SETTINGS_TYPES.keys(), val))
371 % (self.SETTINGS_TYPES.keys(), val))
372 self._app_settings_type = val
372 self._app_settings_type = val
373
373
374 def __unicode__(self):
374 def __unicode__(self):
375 return u"<%s('%s:%s[%s]')>" % (
375 return u"<%s('%s:%s[%s]')>" % (
376 self.__class__.__name__,
376 self.__class__.__name__,
377 self.app_settings_name, self.app_settings_value,
377 self.app_settings_name, self.app_settings_value,
378 self.app_settings_type
378 self.app_settings_type
379 )
379 )
380
380
381
381
382 class RhodeCodeUi(Base, BaseModel):
382 class RhodeCodeUi(Base, BaseModel):
383 __tablename__ = 'rhodecode_ui'
383 __tablename__ = 'rhodecode_ui'
384 __table_args__ = (
384 __table_args__ = (
385 UniqueConstraint('ui_key'),
385 UniqueConstraint('ui_key'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
391 # HG
391 # HG
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
393 HOOK_PULL = 'outgoing.pull_logger'
393 HOOK_PULL = 'outgoing.pull_logger'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
396 HOOK_PUSH = 'changegroup.push_logger'
396 HOOK_PUSH = 'changegroup.push_logger'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
398
398
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
400 # git part is currently hardcoded.
400 # git part is currently hardcoded.
401
401
402 # SVN PATTERNS
402 # SVN PATTERNS
403 SVN_BRANCH_ID = 'vcs_svn_branch'
403 SVN_BRANCH_ID = 'vcs_svn_branch'
404 SVN_TAG_ID = 'vcs_svn_tag'
404 SVN_TAG_ID = 'vcs_svn_tag'
405
405
406 ui_id = Column(
406 ui_id = Column(
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
408 primary_key=True)
408 primary_key=True)
409 ui_section = Column(
409 ui_section = Column(
410 "ui_section", String(255), nullable=True, unique=None, default=None)
410 "ui_section", String(255), nullable=True, unique=None, default=None)
411 ui_key = Column(
411 ui_key = Column(
412 "ui_key", String(255), nullable=True, unique=None, default=None)
412 "ui_key", String(255), nullable=True, unique=None, default=None)
413 ui_value = Column(
413 ui_value = Column(
414 "ui_value", String(255), nullable=True, unique=None, default=None)
414 "ui_value", String(255), nullable=True, unique=None, default=None)
415 ui_active = Column(
415 ui_active = Column(
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
417
417
418 def __repr__(self):
418 def __repr__(self):
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
420 self.ui_key, self.ui_value)
420 self.ui_key, self.ui_value)
421
421
422
422
423 class RepoRhodeCodeSetting(Base, BaseModel):
423 class RepoRhodeCodeSetting(Base, BaseModel):
424 __tablename__ = 'repo_rhodecode_settings'
424 __tablename__ = 'repo_rhodecode_settings'
425 __table_args__ = (
425 __table_args__ = (
426 UniqueConstraint(
426 UniqueConstraint(
427 'app_settings_name', 'repository_id',
427 'app_settings_name', 'repository_id',
428 name='uq_repo_rhodecode_setting_name_repo_id'),
428 name='uq_repo_rhodecode_setting_name_repo_id'),
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
431 )
431 )
432
432
433 repository_id = Column(
433 repository_id = Column(
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
435 nullable=False)
435 nullable=False)
436 app_settings_id = Column(
436 app_settings_id = Column(
437 "app_settings_id", Integer(), nullable=False, unique=True,
437 "app_settings_id", Integer(), nullable=False, unique=True,
438 default=None, primary_key=True)
438 default=None, primary_key=True)
439 app_settings_name = Column(
439 app_settings_name = Column(
440 "app_settings_name", String(255), nullable=True, unique=None,
440 "app_settings_name", String(255), nullable=True, unique=None,
441 default=None)
441 default=None)
442 _app_settings_value = Column(
442 _app_settings_value = Column(
443 "app_settings_value", String(4096), nullable=True, unique=None,
443 "app_settings_value", String(4096), nullable=True, unique=None,
444 default=None)
444 default=None)
445 _app_settings_type = Column(
445 _app_settings_type = Column(
446 "app_settings_type", String(255), nullable=True, unique=None,
446 "app_settings_type", String(255), nullable=True, unique=None,
447 default=None)
447 default=None)
448
448
449 repository = relationship('Repository')
449 repository = relationship('Repository')
450
450
451 def __init__(self, repository_id, key='', val='', type='unicode'):
451 def __init__(self, repository_id, key='', val='', type='unicode'):
452 self.repository_id = repository_id
452 self.repository_id = repository_id
453 self.app_settings_name = key
453 self.app_settings_name = key
454 self.app_settings_type = type
454 self.app_settings_type = type
455 self.app_settings_value = val
455 self.app_settings_value = val
456
456
457 @validates('_app_settings_value')
457 @validates('_app_settings_value')
458 def validate_settings_value(self, key, val):
458 def validate_settings_value(self, key, val):
459 assert type(val) == unicode
459 assert type(val) == unicode
460 return val
460 return val
461
461
462 @hybrid_property
462 @hybrid_property
463 def app_settings_value(self):
463 def app_settings_value(self):
464 v = self._app_settings_value
464 v = self._app_settings_value
465 type_ = self.app_settings_type
465 type_ = self.app_settings_type
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
468 return converter(v)
468 return converter(v)
469
469
470 @app_settings_value.setter
470 @app_settings_value.setter
471 def app_settings_value(self, val):
471 def app_settings_value(self, val):
472 """
472 """
473 Setter that will always make sure we use unicode in app_settings_value
473 Setter that will always make sure we use unicode in app_settings_value
474
474
475 :param val:
475 :param val:
476 """
476 """
477 self._app_settings_value = safe_unicode(val)
477 self._app_settings_value = safe_unicode(val)
478
478
479 @hybrid_property
479 @hybrid_property
480 def app_settings_type(self):
480 def app_settings_type(self):
481 return self._app_settings_type
481 return self._app_settings_type
482
482
483 @app_settings_type.setter
483 @app_settings_type.setter
484 def app_settings_type(self, val):
484 def app_settings_type(self, val):
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
486 if val not in SETTINGS_TYPES:
486 if val not in SETTINGS_TYPES:
487 raise Exception('type must be one of %s got %s'
487 raise Exception('type must be one of %s got %s'
488 % (SETTINGS_TYPES.keys(), val))
488 % (SETTINGS_TYPES.keys(), val))
489 self._app_settings_type = val
489 self._app_settings_type = val
490
490
491 def __unicode__(self):
491 def __unicode__(self):
492 return u"<%s('%s:%s:%s[%s]')>" % (
492 return u"<%s('%s:%s:%s[%s]')>" % (
493 self.__class__.__name__, self.repository.repo_name,
493 self.__class__.__name__, self.repository.repo_name,
494 self.app_settings_name, self.app_settings_value,
494 self.app_settings_name, self.app_settings_value,
495 self.app_settings_type
495 self.app_settings_type
496 )
496 )
497
497
498
498
499 class RepoRhodeCodeUi(Base, BaseModel):
499 class RepoRhodeCodeUi(Base, BaseModel):
500 __tablename__ = 'repo_rhodecode_ui'
500 __tablename__ = 'repo_rhodecode_ui'
501 __table_args__ = (
501 __table_args__ = (
502 UniqueConstraint(
502 UniqueConstraint(
503 'repository_id', 'ui_section', 'ui_key',
503 'repository_id', 'ui_section', 'ui_key',
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
507 )
507 )
508
508
509 repository_id = Column(
509 repository_id = Column(
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
511 nullable=False)
511 nullable=False)
512 ui_id = Column(
512 ui_id = Column(
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
514 primary_key=True)
514 primary_key=True)
515 ui_section = Column(
515 ui_section = Column(
516 "ui_section", String(255), nullable=True, unique=None, default=None)
516 "ui_section", String(255), nullable=True, unique=None, default=None)
517 ui_key = Column(
517 ui_key = Column(
518 "ui_key", String(255), nullable=True, unique=None, default=None)
518 "ui_key", String(255), nullable=True, unique=None, default=None)
519 ui_value = Column(
519 ui_value = Column(
520 "ui_value", String(255), nullable=True, unique=None, default=None)
520 "ui_value", String(255), nullable=True, unique=None, default=None)
521 ui_active = Column(
521 ui_active = Column(
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
523
523
524 repository = relationship('Repository')
524 repository = relationship('Repository')
525
525
526 def __repr__(self):
526 def __repr__(self):
527 return '<%s[%s:%s]%s=>%s]>' % (
527 return '<%s[%s:%s]%s=>%s]>' % (
528 self.__class__.__name__, self.repository.repo_name,
528 self.__class__.__name__, self.repository.repo_name,
529 self.ui_section, self.ui_key, self.ui_value)
529 self.ui_section, self.ui_key, self.ui_value)
530
530
531
531
532 class User(Base, BaseModel):
532 class User(Base, BaseModel):
533 __tablename__ = 'users'
533 __tablename__ = 'users'
534 __table_args__ = (
534 __table_args__ = (
535 UniqueConstraint('username'), UniqueConstraint('email'),
535 UniqueConstraint('username'), UniqueConstraint('email'),
536 Index('u_username_idx', 'username'),
536 Index('u_username_idx', 'username'),
537 Index('u_email_idx', 'email'),
537 Index('u_email_idx', 'email'),
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
540 )
540 )
541 DEFAULT_USER = 'default'
541 DEFAULT_USER = 'default'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
544
544
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
555
555
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
562
562
563 user_log = relationship('UserLog')
563 user_log = relationship('UserLog')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
565
565
566 repositories = relationship('Repository')
566 repositories = relationship('Repository')
567 repository_groups = relationship('RepoGroup')
567 repository_groups = relationship('RepoGroup')
568 user_groups = relationship('UserGroup')
568 user_groups = relationship('UserGroup')
569
569
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
572
572
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
576
576
577 group_member = relationship('UserGroupMember', cascade='all')
577 group_member = relationship('UserGroupMember', cascade='all')
578
578
579 notifications = relationship('UserNotification', cascade='all')
579 notifications = relationship('UserNotification', cascade='all')
580 # notifications assigned to this user
580 # notifications assigned to this user
581 user_created_notifications = relationship('Notification', cascade='all')
581 user_created_notifications = relationship('Notification', cascade='all')
582 # comments created by this user
582 # comments created by this user
583 user_comments = relationship('ChangesetComment', cascade='all')
583 user_comments = relationship('ChangesetComment', cascade='all')
584 # user profile extra info
584 # user profile extra info
585 user_emails = relationship('UserEmailMap', cascade='all')
585 user_emails = relationship('UserEmailMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
589
589
590 # gists
590 # gists
591 user_gists = relationship('Gist', cascade='all')
591 user_gists = relationship('Gist', cascade='all')
592 # user pull requests
592 # user pull requests
593 user_pull_requests = relationship('PullRequest', cascade='all')
593 user_pull_requests = relationship('PullRequest', cascade='all')
594 # external identities
594 # external identities
595 extenal_identities = relationship(
595 extenal_identities = relationship(
596 'ExternalIdentity',
596 'ExternalIdentity',
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
598 cascade='all')
598 cascade='all')
599 # review rules
599 # review rules
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
601
601
602 def __unicode__(self):
602 def __unicode__(self):
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
604 self.user_id, self.username)
604 self.user_id, self.username)
605
605
606 @hybrid_property
606 @hybrid_property
607 def email(self):
607 def email(self):
608 return self._email
608 return self._email
609
609
610 @email.setter
610 @email.setter
611 def email(self, val):
611 def email(self, val):
612 self._email = val.lower() if val else None
612 self._email = val.lower() if val else None
613
613
614 @hybrid_property
614 @hybrid_property
615 def first_name(self):
615 def first_name(self):
616 from rhodecode.lib import helpers as h
616 from rhodecode.lib import helpers as h
617 if self.name:
617 if self.name:
618 return h.escape(self.name)
618 return h.escape(self.name)
619 return self.name
619 return self.name
620
620
621 @hybrid_property
621 @hybrid_property
622 def last_name(self):
622 def last_name(self):
623 from rhodecode.lib import helpers as h
623 from rhodecode.lib import helpers as h
624 if self.lastname:
624 if self.lastname:
625 return h.escape(self.lastname)
625 return h.escape(self.lastname)
626 return self.lastname
626 return self.lastname
627
627
628 @hybrid_property
628 @hybrid_property
629 def api_key(self):
629 def api_key(self):
630 """
630 """
631 Fetch if exist an auth-token with role ALL connected to this user
631 Fetch if exist an auth-token with role ALL connected to this user
632 """
632 """
633 user_auth_token = UserApiKeys.query()\
633 user_auth_token = UserApiKeys.query()\
634 .filter(UserApiKeys.user_id == self.user_id)\
634 .filter(UserApiKeys.user_id == self.user_id)\
635 .filter(or_(UserApiKeys.expires == -1,
635 .filter(or_(UserApiKeys.expires == -1,
636 UserApiKeys.expires >= time.time()))\
636 UserApiKeys.expires >= time.time()))\
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
638 if user_auth_token:
638 if user_auth_token:
639 user_auth_token = user_auth_token.api_key
639 user_auth_token = user_auth_token.api_key
640
640
641 return user_auth_token
641 return user_auth_token
642
642
643 @api_key.setter
643 @api_key.setter
644 def api_key(self, val):
644 def api_key(self, val):
645 # don't allow to set API key this is deprecated for now
645 # don't allow to set API key this is deprecated for now
646 self._api_key = None
646 self._api_key = None
647
647
648 @property
648 @property
649 def reviewer_pull_requests(self):
649 def reviewer_pull_requests(self):
650 return PullRequestReviewers.query() \
650 return PullRequestReviewers.query() \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
653 .all()
653 .all()
654
654
655 @property
655 @property
656 def firstname(self):
656 def firstname(self):
657 # alias for future
657 # alias for future
658 return self.name
658 return self.name
659
659
660 @property
660 @property
661 def emails(self):
661 def emails(self):
662 other = UserEmailMap.query()\
662 other = UserEmailMap.query()\
663 .filter(UserEmailMap.user == self) \
663 .filter(UserEmailMap.user == self) \
664 .order_by(UserEmailMap.email_id.asc()) \
664 .order_by(UserEmailMap.email_id.asc()) \
665 .all()
665 .all()
666 return [self.email] + [x.email for x in other]
666 return [self.email] + [x.email for x in other]
667
667
668 @property
668 @property
669 def auth_tokens(self):
669 def auth_tokens(self):
670 auth_tokens = self.get_auth_tokens()
670 auth_tokens = self.get_auth_tokens()
671 return [x.api_key for x in auth_tokens]
671 return [x.api_key for x in auth_tokens]
672
672
673 def get_auth_tokens(self):
673 def get_auth_tokens(self):
674 return UserApiKeys.query()\
674 return UserApiKeys.query()\
675 .filter(UserApiKeys.user == self)\
675 .filter(UserApiKeys.user == self)\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
677 .all()
677 .all()
678
678
679 @LazyProperty
679 @LazyProperty
680 def feed_token(self):
680 def feed_token(self):
681 return self.get_feed_token()
681 return self.get_feed_token()
682
682
683 def get_feed_token(self, cache=True):
683 def get_feed_token(self, cache=True):
684 feed_tokens = UserApiKeys.query()\
684 feed_tokens = UserApiKeys.query()\
685 .filter(UserApiKeys.user == self)\
685 .filter(UserApiKeys.user == self)\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
687 if cache:
687 if cache:
688 feed_tokens = feed_tokens.options(
688 feed_tokens = feed_tokens.options(
689 FromCache("long_term", "get_user_feed_token_%s" % self.user_id))
689 FromCache("long_term", "get_user_feed_token_%s" % self.user_id))
690
690
691 feed_tokens = feed_tokens.all()
691 feed_tokens = feed_tokens.all()
692 if feed_tokens:
692 if feed_tokens:
693 return feed_tokens[0].api_key
693 return feed_tokens[0].api_key
694 return 'NO_FEED_TOKEN_AVAILABLE'
694 return 'NO_FEED_TOKEN_AVAILABLE'
695
695
696 @classmethod
696 @classmethod
697 def get(cls, user_id, cache=False):
697 def get(cls, user_id, cache=False):
698 if not user_id:
698 if not user_id:
699 return
699 return
700
700
701 user = cls.query()
701 user = cls.query()
702 if cache:
702 if cache:
703 user = user.options(
703 user = user.options(
704 FromCache("sql_cache_short", "get_users_%s" % user_id))
704 FromCache("sql_cache_short", "get_users_%s" % user_id))
705 return user.get(user_id)
705 return user.get(user_id)
706
706
707 @classmethod
707 @classmethod
708 def extra_valid_auth_tokens(cls, user, role=None):
708 def extra_valid_auth_tokens(cls, user, role=None):
709 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
709 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
710 .filter(or_(UserApiKeys.expires == -1,
710 .filter(or_(UserApiKeys.expires == -1,
711 UserApiKeys.expires >= time.time()))
711 UserApiKeys.expires >= time.time()))
712 if role:
712 if role:
713 tokens = tokens.filter(or_(UserApiKeys.role == role,
713 tokens = tokens.filter(or_(UserApiKeys.role == role,
714 UserApiKeys.role == UserApiKeys.ROLE_ALL))
714 UserApiKeys.role == UserApiKeys.ROLE_ALL))
715 return tokens.all()
715 return tokens.all()
716
716
717 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
717 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
718 from rhodecode.lib import auth
718 from rhodecode.lib import auth
719
719
720 log.debug('Trying to authenticate user: %s via auth-token, '
720 log.debug('Trying to authenticate user: %s via auth-token, '
721 'and roles: %s', self, roles)
721 'and roles: %s', self, roles)
722
722
723 if not auth_token:
723 if not auth_token:
724 return False
724 return False
725
725
726 crypto_backend = auth.crypto_backend()
726 crypto_backend = auth.crypto_backend()
727
727
728 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
728 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
729 tokens_q = UserApiKeys.query()\
729 tokens_q = UserApiKeys.query()\
730 .filter(UserApiKeys.user_id == self.user_id)\
730 .filter(UserApiKeys.user_id == self.user_id)\
731 .filter(or_(UserApiKeys.expires == -1,
731 .filter(or_(UserApiKeys.expires == -1,
732 UserApiKeys.expires >= time.time()))
732 UserApiKeys.expires >= time.time()))
733
733
734 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
734 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
735
735
736 plain_tokens = []
736 plain_tokens = []
737 hash_tokens = []
737 hash_tokens = []
738
738
739 for token in tokens_q.all():
739 for token in tokens_q.all():
740 # verify scope first
740 # verify scope first
741 if token.repo_id:
741 if token.repo_id:
742 # token has a scope, we need to verify it
742 # token has a scope, we need to verify it
743 if scope_repo_id != token.repo_id:
743 if scope_repo_id != token.repo_id:
744 log.debug(
744 log.debug(
745 'Scope mismatch: token has a set repo scope: %s, '
745 'Scope mismatch: token has a set repo scope: %s, '
746 'and calling scope is:%s, skipping further checks',
746 'and calling scope is:%s, skipping further checks',
747 token.repo, scope_repo_id)
747 token.repo, scope_repo_id)
748 # token has a scope, and it doesn't match, skip token
748 # token has a scope, and it doesn't match, skip token
749 continue
749 continue
750
750
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
752 hash_tokens.append(token.api_key)
752 hash_tokens.append(token.api_key)
753 else:
753 else:
754 plain_tokens.append(token.api_key)
754 plain_tokens.append(token.api_key)
755
755
756 is_plain_match = auth_token in plain_tokens
756 is_plain_match = auth_token in plain_tokens
757 if is_plain_match:
757 if is_plain_match:
758 return True
758 return True
759
759
760 for hashed in hash_tokens:
760 for hashed in hash_tokens:
761 # TODO(marcink): this is expensive to calculate, but most secure
761 # TODO(marcink): this is expensive to calculate, but most secure
762 match = crypto_backend.hash_check(auth_token, hashed)
762 match = crypto_backend.hash_check(auth_token, hashed)
763 if match:
763 if match:
764 return True
764 return True
765
765
766 return False
766 return False
767
767
768 @property
768 @property
769 def ip_addresses(self):
769 def ip_addresses(self):
770 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
770 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
771 return [x.ip_addr for x in ret]
771 return [x.ip_addr for x in ret]
772
772
773 @property
773 @property
774 def username_and_name(self):
774 def username_and_name(self):
775 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
775 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
776
776
777 @property
777 @property
778 def username_or_name_or_email(self):
778 def username_or_name_or_email(self):
779 full_name = self.full_name if self.full_name is not ' ' else None
779 full_name = self.full_name if self.full_name is not ' ' else None
780 return self.username or full_name or self.email
780 return self.username or full_name or self.email
781
781
782 @property
782 @property
783 def full_name(self):
783 def full_name(self):
784 return '%s %s' % (self.first_name, self.last_name)
784 return '%s %s' % (self.first_name, self.last_name)
785
785
786 @property
786 @property
787 def full_name_or_username(self):
787 def full_name_or_username(self):
788 return ('%s %s' % (self.first_name, self.last_name)
788 return ('%s %s' % (self.first_name, self.last_name)
789 if (self.first_name and self.last_name) else self.username)
789 if (self.first_name and self.last_name) else self.username)
790
790
791 @property
791 @property
792 def full_contact(self):
792 def full_contact(self):
793 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
793 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
794
794
795 @property
795 @property
796 def short_contact(self):
796 def short_contact(self):
797 return '%s %s' % (self.first_name, self.last_name)
797 return '%s %s' % (self.first_name, self.last_name)
798
798
799 @property
799 @property
800 def is_admin(self):
800 def is_admin(self):
801 return self.admin
801 return self.admin
802
802
803 def AuthUser(self, **kwargs):
803 def AuthUser(self, **kwargs):
804 """
804 """
805 Returns instance of AuthUser for this user
805 Returns instance of AuthUser for this user
806 """
806 """
807 from rhodecode.lib.auth import AuthUser
807 from rhodecode.lib.auth import AuthUser
808 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
808 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
809
809
810 @hybrid_property
810 @hybrid_property
811 def user_data(self):
811 def user_data(self):
812 if not self._user_data:
812 if not self._user_data:
813 return {}
813 return {}
814
814
815 try:
815 try:
816 return json.loads(self._user_data)
816 return json.loads(self._user_data)
817 except TypeError:
817 except TypeError:
818 return {}
818 return {}
819
819
820 @user_data.setter
820 @user_data.setter
821 def user_data(self, val):
821 def user_data(self, val):
822 if not isinstance(val, dict):
822 if not isinstance(val, dict):
823 raise Exception('user_data must be dict, got %s' % type(val))
823 raise Exception('user_data must be dict, got %s' % type(val))
824 try:
824 try:
825 self._user_data = json.dumps(val)
825 self._user_data = json.dumps(val)
826 except Exception:
826 except Exception:
827 log.error(traceback.format_exc())
827 log.error(traceback.format_exc())
828
828
829 @classmethod
829 @classmethod
830 def get_by_username(cls, username, case_insensitive=False,
830 def get_by_username(cls, username, case_insensitive=False,
831 cache=False, identity_cache=False):
831 cache=False, identity_cache=False):
832 session = Session()
832 session = Session()
833
833
834 if case_insensitive:
834 if case_insensitive:
835 q = cls.query().filter(
835 q = cls.query().filter(
836 func.lower(cls.username) == func.lower(username))
836 func.lower(cls.username) == func.lower(username))
837 else:
837 else:
838 q = cls.query().filter(cls.username == username)
838 q = cls.query().filter(cls.username == username)
839
839
840 if cache:
840 if cache:
841 if identity_cache:
841 if identity_cache:
842 val = cls.identity_cache(session, 'username', username)
842 val = cls.identity_cache(session, 'username', username)
843 if val:
843 if val:
844 return val
844 return val
845 else:
845 else:
846 cache_key = "get_user_by_name_%s" % _hash_key(username)
846 cache_key = "get_user_by_name_%s" % _hash_key(username)
847 q = q.options(
847 q = q.options(
848 FromCache("sql_cache_short", cache_key))
848 FromCache("sql_cache_short", cache_key))
849
849
850 return q.scalar()
850 return q.scalar()
851
851
852 @classmethod
852 @classmethod
853 def get_by_auth_token(cls, auth_token, cache=False):
853 def get_by_auth_token(cls, auth_token, cache=False):
854 q = UserApiKeys.query()\
854 q = UserApiKeys.query()\
855 .filter(UserApiKeys.api_key == auth_token)\
855 .filter(UserApiKeys.api_key == auth_token)\
856 .filter(or_(UserApiKeys.expires == -1,
856 .filter(or_(UserApiKeys.expires == -1,
857 UserApiKeys.expires >= time.time()))
857 UserApiKeys.expires >= time.time()))
858 if cache:
858 if cache:
859 q = q.options(
859 q = q.options(
860 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
860 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
861
861
862 match = q.first()
862 match = q.first()
863 if match:
863 if match:
864 return match.user
864 return match.user
865
865
866 @classmethod
866 @classmethod
867 def get_by_email(cls, email, case_insensitive=False, cache=False):
867 def get_by_email(cls, email, case_insensitive=False, cache=False):
868
868
869 if case_insensitive:
869 if case_insensitive:
870 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
870 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
871
871
872 else:
872 else:
873 q = cls.query().filter(cls.email == email)
873 q = cls.query().filter(cls.email == email)
874
874
875 email_key = _hash_key(email)
875 email_key = _hash_key(email)
876 if cache:
876 if cache:
877 q = q.options(
877 q = q.options(
878 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
878 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
879
879
880 ret = q.scalar()
880 ret = q.scalar()
881 if ret is None:
881 if ret is None:
882 q = UserEmailMap.query()
882 q = UserEmailMap.query()
883 # try fetching in alternate email map
883 # try fetching in alternate email map
884 if case_insensitive:
884 if case_insensitive:
885 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
885 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
886 else:
886 else:
887 q = q.filter(UserEmailMap.email == email)
887 q = q.filter(UserEmailMap.email == email)
888 q = q.options(joinedload(UserEmailMap.user))
888 q = q.options(joinedload(UserEmailMap.user))
889 if cache:
889 if cache:
890 q = q.options(
890 q = q.options(
891 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
891 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
892 ret = getattr(q.scalar(), 'user', None)
892 ret = getattr(q.scalar(), 'user', None)
893
893
894 return ret
894 return ret
895
895
896 @classmethod
896 @classmethod
897 def get_from_cs_author(cls, author):
897 def get_from_cs_author(cls, author):
898 """
898 """
899 Tries to get User objects out of commit author string
899 Tries to get User objects out of commit author string
900
900
901 :param author:
901 :param author:
902 """
902 """
903 from rhodecode.lib.helpers import email, author_name
903 from rhodecode.lib.helpers import email, author_name
904 # Valid email in the attribute passed, see if they're in the system
904 # Valid email in the attribute passed, see if they're in the system
905 _email = email(author)
905 _email = email(author)
906 if _email:
906 if _email:
907 user = cls.get_by_email(_email, case_insensitive=True)
907 user = cls.get_by_email(_email, case_insensitive=True)
908 if user:
908 if user:
909 return user
909 return user
910 # Maybe we can match by username?
910 # Maybe we can match by username?
911 _author = author_name(author)
911 _author = author_name(author)
912 user = cls.get_by_username(_author, case_insensitive=True)
912 user = cls.get_by_username(_author, case_insensitive=True)
913 if user:
913 if user:
914 return user
914 return user
915
915
916 def update_userdata(self, **kwargs):
916 def update_userdata(self, **kwargs):
917 usr = self
917 usr = self
918 old = usr.user_data
918 old = usr.user_data
919 old.update(**kwargs)
919 old.update(**kwargs)
920 usr.user_data = old
920 usr.user_data = old
921 Session().add(usr)
921 Session().add(usr)
922 log.debug('updated userdata with ', kwargs)
922 log.debug('updated userdata with ', kwargs)
923
923
924 def update_lastlogin(self):
924 def update_lastlogin(self):
925 """Update user lastlogin"""
925 """Update user lastlogin"""
926 self.last_login = datetime.datetime.now()
926 self.last_login = datetime.datetime.now()
927 Session().add(self)
927 Session().add(self)
928 log.debug('updated user %s lastlogin', self.username)
928 log.debug('updated user %s lastlogin', self.username)
929
929
930 def update_lastactivity(self):
930 def update_lastactivity(self):
931 """Update user lastactivity"""
931 """Update user lastactivity"""
932 self.last_activity = datetime.datetime.now()
932 self.last_activity = datetime.datetime.now()
933 Session().add(self)
933 Session().add(self)
934 log.debug('updated user `%s` last activity', self.username)
934 log.debug('updated user `%s` last activity', self.username)
935
935
936 def update_password(self, new_password):
936 def update_password(self, new_password):
937 from rhodecode.lib.auth import get_crypt_password
937 from rhodecode.lib.auth import get_crypt_password
938
938
939 self.password = get_crypt_password(new_password)
939 self.password = get_crypt_password(new_password)
940 Session().add(self)
940 Session().add(self)
941
941
942 @classmethod
942 @classmethod
943 def get_first_super_admin(cls):
943 def get_first_super_admin(cls):
944 user = User.query().filter(User.admin == true()).first()
944 user = User.query().filter(User.admin == true()).first()
945 if user is None:
945 if user is None:
946 raise Exception('FATAL: Missing administrative account!')
946 raise Exception('FATAL: Missing administrative account!')
947 return user
947 return user
948
948
949 @classmethod
949 @classmethod
950 def get_all_super_admins(cls):
950 def get_all_super_admins(cls):
951 """
951 """
952 Returns all admin accounts sorted by username
952 Returns all admin accounts sorted by username
953 """
953 """
954 return User.query().filter(User.admin == true())\
954 return User.query().filter(User.admin == true())\
955 .order_by(User.username.asc()).all()
955 .order_by(User.username.asc()).all()
956
956
957 @classmethod
957 @classmethod
958 def get_default_user(cls, cache=False, refresh=False):
958 def get_default_user(cls, cache=False, refresh=False):
959 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
959 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
960 if user is None:
960 if user is None:
961 raise Exception('FATAL: Missing default account!')
961 raise Exception('FATAL: Missing default account!')
962 if refresh:
962 if refresh:
963 # The default user might be based on outdated state which
963 # The default user might be based on outdated state which
964 # has been loaded from the cache.
964 # has been loaded from the cache.
965 # A call to refresh() ensures that the
965 # A call to refresh() ensures that the
966 # latest state from the database is used.
966 # latest state from the database is used.
967 Session().refresh(user)
967 Session().refresh(user)
968 return user
968 return user
969
969
970 def _get_default_perms(self, user, suffix=''):
970 def _get_default_perms(self, user, suffix=''):
971 from rhodecode.model.permission import PermissionModel
971 from rhodecode.model.permission import PermissionModel
972 return PermissionModel().get_default_perms(user.user_perms, suffix)
972 return PermissionModel().get_default_perms(user.user_perms, suffix)
973
973
974 def get_default_perms(self, suffix=''):
974 def get_default_perms(self, suffix=''):
975 return self._get_default_perms(self, suffix)
975 return self._get_default_perms(self, suffix)
976
976
977 def get_api_data(self, include_secrets=False, details='full'):
977 def get_api_data(self, include_secrets=False, details='full'):
978 """
978 """
979 Common function for generating user related data for API
979 Common function for generating user related data for API
980
980
981 :param include_secrets: By default secrets in the API data will be replaced
981 :param include_secrets: By default secrets in the API data will be replaced
982 by a placeholder value to prevent exposing this data by accident. In case
982 by a placeholder value to prevent exposing this data by accident. In case
983 this data shall be exposed, set this flag to ``True``.
983 this data shall be exposed, set this flag to ``True``.
984
984
985 :param details: details can be 'basic|full' basic gives only a subset of
985 :param details: details can be 'basic|full' basic gives only a subset of
986 the available user information that includes user_id, name and emails.
986 the available user information that includes user_id, name and emails.
987 """
987 """
988 user = self
988 user = self
989 user_data = self.user_data
989 user_data = self.user_data
990 data = {
990 data = {
991 'user_id': user.user_id,
991 'user_id': user.user_id,
992 'username': user.username,
992 'username': user.username,
993 'firstname': user.name,
993 'firstname': user.name,
994 'lastname': user.lastname,
994 'lastname': user.lastname,
995 'email': user.email,
995 'email': user.email,
996 'emails': user.emails,
996 'emails': user.emails,
997 }
997 }
998 if details == 'basic':
998 if details == 'basic':
999 return data
999 return data
1000
1000
1001 auth_token_length = 40
1001 auth_token_length = 40
1002 auth_token_replacement = '*' * auth_token_length
1002 auth_token_replacement = '*' * auth_token_length
1003
1003
1004 extras = {
1004 extras = {
1005 'auth_tokens': [auth_token_replacement],
1005 'auth_tokens': [auth_token_replacement],
1006 'active': user.active,
1006 'active': user.active,
1007 'admin': user.admin,
1007 'admin': user.admin,
1008 'extern_type': user.extern_type,
1008 'extern_type': user.extern_type,
1009 'extern_name': user.extern_name,
1009 'extern_name': user.extern_name,
1010 'last_login': user.last_login,
1010 'last_login': user.last_login,
1011 'last_activity': user.last_activity,
1011 'last_activity': user.last_activity,
1012 'ip_addresses': user.ip_addresses,
1012 'ip_addresses': user.ip_addresses,
1013 'language': user_data.get('language')
1013 'language': user_data.get('language')
1014 }
1014 }
1015 data.update(extras)
1015 data.update(extras)
1016
1016
1017 if include_secrets:
1017 if include_secrets:
1018 data['auth_tokens'] = user.auth_tokens
1018 data['auth_tokens'] = user.auth_tokens
1019 return data
1019 return data
1020
1020
1021 def __json__(self):
1021 def __json__(self):
1022 data = {
1022 data = {
1023 'full_name': self.full_name,
1023 'full_name': self.full_name,
1024 'full_name_or_username': self.full_name_or_username,
1024 'full_name_or_username': self.full_name_or_username,
1025 'short_contact': self.short_contact,
1025 'short_contact': self.short_contact,
1026 'full_contact': self.full_contact,
1026 'full_contact': self.full_contact,
1027 }
1027 }
1028 data.update(self.get_api_data())
1028 data.update(self.get_api_data())
1029 return data
1029 return data
1030
1030
1031
1031
1032 class UserApiKeys(Base, BaseModel):
1032 class UserApiKeys(Base, BaseModel):
1033 __tablename__ = 'user_api_keys'
1033 __tablename__ = 'user_api_keys'
1034 __table_args__ = (
1034 __table_args__ = (
1035 Index('uak_api_key_idx', 'api_key', unique=True),
1035 Index('uak_api_key_idx', 'api_key', unique=True),
1036 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1036 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1039 )
1039 )
1040 __mapper_args__ = {}
1040 __mapper_args__ = {}
1041
1041
1042 # ApiKey role
1042 # ApiKey role
1043 ROLE_ALL = 'token_role_all'
1043 ROLE_ALL = 'token_role_all'
1044 ROLE_HTTP = 'token_role_http'
1044 ROLE_HTTP = 'token_role_http'
1045 ROLE_VCS = 'token_role_vcs'
1045 ROLE_VCS = 'token_role_vcs'
1046 ROLE_API = 'token_role_api'
1046 ROLE_API = 'token_role_api'
1047 ROLE_FEED = 'token_role_feed'
1047 ROLE_FEED = 'token_role_feed'
1048 ROLE_PASSWORD_RESET = 'token_password_reset'
1048 ROLE_PASSWORD_RESET = 'token_password_reset'
1049
1049
1050 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1050 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1051
1051
1052 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1052 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1054 api_key = Column("api_key", String(255), nullable=False, unique=True)
1054 api_key = Column("api_key", String(255), nullable=False, unique=True)
1055 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1055 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1056 expires = Column('expires', Float(53), nullable=False)
1056 expires = Column('expires', Float(53), nullable=False)
1057 role = Column('role', String(255), nullable=True)
1057 role = Column('role', String(255), nullable=True)
1058 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1058 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1059
1059
1060 # scope columns
1060 # scope columns
1061 repo_id = Column(
1061 repo_id = Column(
1062 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1062 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1063 nullable=True, unique=None, default=None)
1063 nullable=True, unique=None, default=None)
1064 repo = relationship('Repository', lazy='joined')
1064 repo = relationship('Repository', lazy='joined')
1065
1065
1066 repo_group_id = Column(
1066 repo_group_id = Column(
1067 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1067 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1068 nullable=True, unique=None, default=None)
1068 nullable=True, unique=None, default=None)
1069 repo_group = relationship('RepoGroup', lazy='joined')
1069 repo_group = relationship('RepoGroup', lazy='joined')
1070
1070
1071 user = relationship('User', lazy='joined')
1071 user = relationship('User', lazy='joined')
1072
1072
1073 def __unicode__(self):
1073 def __unicode__(self):
1074 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1074 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1075
1075
1076 def __json__(self):
1076 def __json__(self):
1077 data = {
1077 data = {
1078 'auth_token': self.api_key,
1078 'auth_token': self.api_key,
1079 'role': self.role,
1079 'role': self.role,
1080 'scope': self.scope_humanized,
1080 'scope': self.scope_humanized,
1081 'expired': self.expired
1081 'expired': self.expired
1082 }
1082 }
1083 return data
1083 return data
1084
1084
1085 def get_api_data(self, include_secrets=False):
1085 def get_api_data(self, include_secrets=False):
1086 data = self.__json__()
1086 data = self.__json__()
1087 if include_secrets:
1087 if include_secrets:
1088 return data
1088 return data
1089 else:
1089 else:
1090 data['auth_token'] = self.token_obfuscated
1090 data['auth_token'] = self.token_obfuscated
1091 return data
1091 return data
1092
1092
1093 @hybrid_property
1093 @hybrid_property
1094 def description_safe(self):
1094 def description_safe(self):
1095 from rhodecode.lib import helpers as h
1095 from rhodecode.lib import helpers as h
1096 return h.escape(self.description)
1096 return h.escape(self.description)
1097
1097
1098 @property
1098 @property
1099 def expired(self):
1099 def expired(self):
1100 if self.expires == -1:
1100 if self.expires == -1:
1101 return False
1101 return False
1102 return time.time() > self.expires
1102 return time.time() > self.expires
1103
1103
1104 @classmethod
1104 @classmethod
1105 def _get_role_name(cls, role):
1105 def _get_role_name(cls, role):
1106 return {
1106 return {
1107 cls.ROLE_ALL: _('all'),
1107 cls.ROLE_ALL: _('all'),
1108 cls.ROLE_HTTP: _('http/web interface'),
1108 cls.ROLE_HTTP: _('http/web interface'),
1109 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1109 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1110 cls.ROLE_API: _('api calls'),
1110 cls.ROLE_API: _('api calls'),
1111 cls.ROLE_FEED: _('feed access'),
1111 cls.ROLE_FEED: _('feed access'),
1112 }.get(role, role)
1112 }.get(role, role)
1113
1113
1114 @property
1114 @property
1115 def role_humanized(self):
1115 def role_humanized(self):
1116 return self._get_role_name(self.role)
1116 return self._get_role_name(self.role)
1117
1117
1118 def _get_scope(self):
1118 def _get_scope(self):
1119 if self.repo:
1119 if self.repo:
1120 return repr(self.repo)
1120 return repr(self.repo)
1121 if self.repo_group:
1121 if self.repo_group:
1122 return repr(self.repo_group) + ' (recursive)'
1122 return repr(self.repo_group) + ' (recursive)'
1123 return 'global'
1123 return 'global'
1124
1124
1125 @property
1125 @property
1126 def scope_humanized(self):
1126 def scope_humanized(self):
1127 return self._get_scope()
1127 return self._get_scope()
1128
1128
1129 @property
1129 @property
1130 def token_obfuscated(self):
1130 def token_obfuscated(self):
1131 if self.api_key:
1131 if self.api_key:
1132 return self.api_key[:4] + "****"
1132 return self.api_key[:4] + "****"
1133
1133
1134
1134
1135 class UserEmailMap(Base, BaseModel):
1135 class UserEmailMap(Base, BaseModel):
1136 __tablename__ = 'user_email_map'
1136 __tablename__ = 'user_email_map'
1137 __table_args__ = (
1137 __table_args__ = (
1138 Index('uem_email_idx', 'email'),
1138 Index('uem_email_idx', 'email'),
1139 UniqueConstraint('email'),
1139 UniqueConstraint('email'),
1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1141 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1141 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1142 )
1142 )
1143 __mapper_args__ = {}
1143 __mapper_args__ = {}
1144
1144
1145 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1145 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1147 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1147 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1148 user = relationship('User', lazy='joined')
1148 user = relationship('User', lazy='joined')
1149
1149
1150 @validates('_email')
1150 @validates('_email')
1151 def validate_email(self, key, email):
1151 def validate_email(self, key, email):
1152 # check if this email is not main one
1152 # check if this email is not main one
1153 main_email = Session().query(User).filter(User.email == email).scalar()
1153 main_email = Session().query(User).filter(User.email == email).scalar()
1154 if main_email is not None:
1154 if main_email is not None:
1155 raise AttributeError('email %s is present is user table' % email)
1155 raise AttributeError('email %s is present is user table' % email)
1156 return email
1156 return email
1157
1157
1158 @hybrid_property
1158 @hybrid_property
1159 def email(self):
1159 def email(self):
1160 return self._email
1160 return self._email
1161
1161
1162 @email.setter
1162 @email.setter
1163 def email(self, val):
1163 def email(self, val):
1164 self._email = val.lower() if val else None
1164 self._email = val.lower() if val else None
1165
1165
1166
1166
1167 class UserIpMap(Base, BaseModel):
1167 class UserIpMap(Base, BaseModel):
1168 __tablename__ = 'user_ip_map'
1168 __tablename__ = 'user_ip_map'
1169 __table_args__ = (
1169 __table_args__ = (
1170 UniqueConstraint('user_id', 'ip_addr'),
1170 UniqueConstraint('user_id', 'ip_addr'),
1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1172 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1172 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1173 )
1173 )
1174 __mapper_args__ = {}
1174 __mapper_args__ = {}
1175
1175
1176 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1176 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1177 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1177 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1178 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1178 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1179 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1179 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1180 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1180 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1181 user = relationship('User', lazy='joined')
1181 user = relationship('User', lazy='joined')
1182
1182
1183 @hybrid_property
1183 @hybrid_property
1184 def description_safe(self):
1184 def description_safe(self):
1185 from rhodecode.lib import helpers as h
1185 from rhodecode.lib import helpers as h
1186 return h.escape(self.description)
1186 return h.escape(self.description)
1187
1187
1188 @classmethod
1188 @classmethod
1189 def _get_ip_range(cls, ip_addr):
1189 def _get_ip_range(cls, ip_addr):
1190 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1190 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1191 return [str(net.network_address), str(net.broadcast_address)]
1191 return [str(net.network_address), str(net.broadcast_address)]
1192
1192
1193 def __json__(self):
1193 def __json__(self):
1194 return {
1194 return {
1195 'ip_addr': self.ip_addr,
1195 'ip_addr': self.ip_addr,
1196 'ip_range': self._get_ip_range(self.ip_addr),
1196 'ip_range': self._get_ip_range(self.ip_addr),
1197 }
1197 }
1198
1198
1199 def __unicode__(self):
1199 def __unicode__(self):
1200 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1200 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1201 self.user_id, self.ip_addr)
1201 self.user_id, self.ip_addr)
1202
1202
1203
1203
1204 class UserSshKeys(Base, BaseModel):
1204 class UserSshKeys(Base, BaseModel):
1205 __tablename__ = 'user_ssh_keys'
1205 __tablename__ = 'user_ssh_keys'
1206 __table_args__ = (
1206 __table_args__ = (
1207 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1207 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1208
1208
1209 UniqueConstraint('ssh_key_fingerprint'),
1209 UniqueConstraint('ssh_key_fingerprint'),
1210
1210
1211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1213 )
1213 )
1214 __mapper_args__ = {}
1214 __mapper_args__ = {}
1215
1215
1216 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1216 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1217 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1217 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1218 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1218 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1219
1219
1220 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1220 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1221
1221
1222 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1222 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1223 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1223 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1224 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1224 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1225
1225
1226 user = relationship('User', lazy='joined')
1226 user = relationship('User', lazy='joined')
1227
1227
1228 def __json__(self):
1228 def __json__(self):
1229 data = {
1229 data = {
1230 'ssh_fingerprint': self.ssh_key_fingerprint,
1230 'ssh_fingerprint': self.ssh_key_fingerprint,
1231 'description': self.description,
1231 'description': self.description,
1232 'created_on': self.created_on
1232 'created_on': self.created_on
1233 }
1233 }
1234 return data
1234 return data
1235
1235
1236 def get_api_data(self):
1236 def get_api_data(self):
1237 data = self.__json__()
1237 data = self.__json__()
1238 return data
1238 return data
1239
1239
1240
1240
1241 class UserLog(Base, BaseModel):
1241 class UserLog(Base, BaseModel):
1242 __tablename__ = 'user_logs'
1242 __tablename__ = 'user_logs'
1243 __table_args__ = (
1243 __table_args__ = (
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1246 )
1246 )
1247 VERSION_1 = 'v1'
1247 VERSION_1 = 'v1'
1248 VERSION_2 = 'v2'
1248 VERSION_2 = 'v2'
1249 VERSIONS = [VERSION_1, VERSION_2]
1249 VERSIONS = [VERSION_1, VERSION_2]
1250
1250
1251 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1251 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1253 username = Column("username", String(255), nullable=True, unique=None, default=None)
1253 username = Column("username", String(255), nullable=True, unique=None, default=None)
1254 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1254 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1255 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1255 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1256 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1256 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1257 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1257 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1258 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1258 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1259
1259
1260 version = Column("version", String(255), nullable=True, default=VERSION_1)
1260 version = Column("version", String(255), nullable=True, default=VERSION_1)
1261 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1261 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1262 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1262 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1263
1263
1264 def __unicode__(self):
1264 def __unicode__(self):
1265 return u"<%s('id:%s:%s')>" % (
1265 return u"<%s('id:%s:%s')>" % (
1266 self.__class__.__name__, self.repository_name, self.action)
1266 self.__class__.__name__, self.repository_name, self.action)
1267
1267
1268 def __json__(self):
1268 def __json__(self):
1269 return {
1269 return {
1270 'user_id': self.user_id,
1270 'user_id': self.user_id,
1271 'username': self.username,
1271 'username': self.username,
1272 'repository_id': self.repository_id,
1272 'repository_id': self.repository_id,
1273 'repository_name': self.repository_name,
1273 'repository_name': self.repository_name,
1274 'user_ip': self.user_ip,
1274 'user_ip': self.user_ip,
1275 'action_date': self.action_date,
1275 'action_date': self.action_date,
1276 'action': self.action,
1276 'action': self.action,
1277 }
1277 }
1278
1278
1279 @hybrid_property
1279 @hybrid_property
1280 def entry_id(self):
1280 def entry_id(self):
1281 return self.user_log_id
1281 return self.user_log_id
1282
1282
1283 @property
1283 @property
1284 def action_as_day(self):
1284 def action_as_day(self):
1285 return datetime.date(*self.action_date.timetuple()[:3])
1285 return datetime.date(*self.action_date.timetuple()[:3])
1286
1286
1287 user = relationship('User')
1287 user = relationship('User')
1288 repository = relationship('Repository', cascade='')
1288 repository = relationship('Repository', cascade='')
1289
1289
1290
1290
1291 class UserGroup(Base, BaseModel):
1291 class UserGroup(Base, BaseModel):
1292 __tablename__ = 'users_groups'
1292 __tablename__ = 'users_groups'
1293 __table_args__ = (
1293 __table_args__ = (
1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1295 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1295 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1296 )
1296 )
1297
1297
1298 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1298 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1299 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1299 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1300 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1300 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1301 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1301 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1302 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1302 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1304 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1304 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1305 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1305 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1306
1306
1307 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1307 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1308 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1308 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1309 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1309 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1310 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1310 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1311 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1311 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1312 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1312 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1313
1313
1314 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1314 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1315 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1315 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1316
1316
1317 @classmethod
1317 @classmethod
1318 def _load_group_data(cls, column):
1318 def _load_group_data(cls, column):
1319 if not column:
1319 if not column:
1320 return {}
1320 return {}
1321
1321
1322 try:
1322 try:
1323 return json.loads(column) or {}
1323 return json.loads(column) or {}
1324 except TypeError:
1324 except TypeError:
1325 return {}
1325 return {}
1326
1326
1327 @hybrid_property
1327 @hybrid_property
1328 def description_safe(self):
1328 def description_safe(self):
1329 from rhodecode.lib import helpers as h
1329 from rhodecode.lib import helpers as h
1330 return h.escape(self.description)
1330 return h.escape(self.description)
1331
1331
1332 @hybrid_property
1332 @hybrid_property
1333 def group_data(self):
1333 def group_data(self):
1334 return self._load_group_data(self._group_data)
1334 return self._load_group_data(self._group_data)
1335
1335
1336 @group_data.expression
1336 @group_data.expression
1337 def group_data(self, **kwargs):
1337 def group_data(self, **kwargs):
1338 return self._group_data
1338 return self._group_data
1339
1339
1340 @group_data.setter
1340 @group_data.setter
1341 def group_data(self, val):
1341 def group_data(self, val):
1342 try:
1342 try:
1343 self._group_data = json.dumps(val)
1343 self._group_data = json.dumps(val)
1344 except Exception:
1344 except Exception:
1345 log.error(traceback.format_exc())
1345 log.error(traceback.format_exc())
1346
1346
1347 def __unicode__(self):
1347 def __unicode__(self):
1348 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1348 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1349 self.users_group_id,
1349 self.users_group_id,
1350 self.users_group_name)
1350 self.users_group_name)
1351
1351
1352 @classmethod
1352 @classmethod
1353 def get_by_group_name(cls, group_name, cache=False,
1353 def get_by_group_name(cls, group_name, cache=False,
1354 case_insensitive=False):
1354 case_insensitive=False):
1355 if case_insensitive:
1355 if case_insensitive:
1356 q = cls.query().filter(func.lower(cls.users_group_name) ==
1356 q = cls.query().filter(func.lower(cls.users_group_name) ==
1357 func.lower(group_name))
1357 func.lower(group_name))
1358
1358
1359 else:
1359 else:
1360 q = cls.query().filter(cls.users_group_name == group_name)
1360 q = cls.query().filter(cls.users_group_name == group_name)
1361 if cache:
1361 if cache:
1362 q = q.options(
1362 q = q.options(
1363 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1363 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1364 return q.scalar()
1364 return q.scalar()
1365
1365
1366 @classmethod
1366 @classmethod
1367 def get(cls, user_group_id, cache=False):
1367 def get(cls, user_group_id, cache=False):
1368 if not user_group_id:
1368 if not user_group_id:
1369 return
1369 return
1370
1370
1371 user_group = cls.query()
1371 user_group = cls.query()
1372 if cache:
1372 if cache:
1373 user_group = user_group.options(
1373 user_group = user_group.options(
1374 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1374 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1375 return user_group.get(user_group_id)
1375 return user_group.get(user_group_id)
1376
1376
1377 def permissions(self, with_admins=True, with_owner=True):
1377 def permissions(self, with_admins=True, with_owner=True):
1378 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1378 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1379 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1379 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1380 joinedload(UserUserGroupToPerm.user),
1380 joinedload(UserUserGroupToPerm.user),
1381 joinedload(UserUserGroupToPerm.permission),)
1381 joinedload(UserUserGroupToPerm.permission),)
1382
1382
1383 # get owners and admins and permissions. We do a trick of re-writing
1383 # get owners and admins and permissions. We do a trick of re-writing
1384 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1384 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1385 # has a global reference and changing one object propagates to all
1385 # has a global reference and changing one object propagates to all
1386 # others. This means if admin is also an owner admin_row that change
1386 # others. This means if admin is also an owner admin_row that change
1387 # would propagate to both objects
1387 # would propagate to both objects
1388 perm_rows = []
1388 perm_rows = []
1389 for _usr in q.all():
1389 for _usr in q.all():
1390 usr = AttributeDict(_usr.user.get_dict())
1390 usr = AttributeDict(_usr.user.get_dict())
1391 usr.permission = _usr.permission.permission_name
1391 usr.permission = _usr.permission.permission_name
1392 perm_rows.append(usr)
1392 perm_rows.append(usr)
1393
1393
1394 # filter the perm rows by 'default' first and then sort them by
1394 # filter the perm rows by 'default' first and then sort them by
1395 # admin,write,read,none permissions sorted again alphabetically in
1395 # admin,write,read,none permissions sorted again alphabetically in
1396 # each group
1396 # each group
1397 perm_rows = sorted(perm_rows, key=display_user_sort)
1397 perm_rows = sorted(perm_rows, key=display_user_sort)
1398
1398
1399 _admin_perm = 'usergroup.admin'
1399 _admin_perm = 'usergroup.admin'
1400 owner_row = []
1400 owner_row = []
1401 if with_owner:
1401 if with_owner:
1402 usr = AttributeDict(self.user.get_dict())
1402 usr = AttributeDict(self.user.get_dict())
1403 usr.owner_row = True
1403 usr.owner_row = True
1404 usr.permission = _admin_perm
1404 usr.permission = _admin_perm
1405 owner_row.append(usr)
1405 owner_row.append(usr)
1406
1406
1407 super_admin_rows = []
1407 super_admin_rows = []
1408 if with_admins:
1408 if with_admins:
1409 for usr in User.get_all_super_admins():
1409 for usr in User.get_all_super_admins():
1410 # if this admin is also owner, don't double the record
1410 # if this admin is also owner, don't double the record
1411 if usr.user_id == owner_row[0].user_id:
1411 if usr.user_id == owner_row[0].user_id:
1412 owner_row[0].admin_row = True
1412 owner_row[0].admin_row = True
1413 else:
1413 else:
1414 usr = AttributeDict(usr.get_dict())
1414 usr = AttributeDict(usr.get_dict())
1415 usr.admin_row = True
1415 usr.admin_row = True
1416 usr.permission = _admin_perm
1416 usr.permission = _admin_perm
1417 super_admin_rows.append(usr)
1417 super_admin_rows.append(usr)
1418
1418
1419 return super_admin_rows + owner_row + perm_rows
1419 return super_admin_rows + owner_row + perm_rows
1420
1420
1421 def permission_user_groups(self):
1421 def permission_user_groups(self):
1422 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1422 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1423 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1423 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1424 joinedload(UserGroupUserGroupToPerm.target_user_group),
1424 joinedload(UserGroupUserGroupToPerm.target_user_group),
1425 joinedload(UserGroupUserGroupToPerm.permission),)
1425 joinedload(UserGroupUserGroupToPerm.permission),)
1426
1426
1427 perm_rows = []
1427 perm_rows = []
1428 for _user_group in q.all():
1428 for _user_group in q.all():
1429 usr = AttributeDict(_user_group.user_group.get_dict())
1429 usr = AttributeDict(_user_group.user_group.get_dict())
1430 usr.permission = _user_group.permission.permission_name
1430 usr.permission = _user_group.permission.permission_name
1431 perm_rows.append(usr)
1431 perm_rows.append(usr)
1432
1432
1433 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1433 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1434 return perm_rows
1434 return perm_rows
1435
1435
1436 def _get_default_perms(self, user_group, suffix=''):
1436 def _get_default_perms(self, user_group, suffix=''):
1437 from rhodecode.model.permission import PermissionModel
1437 from rhodecode.model.permission import PermissionModel
1438 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1438 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1439
1439
1440 def get_default_perms(self, suffix=''):
1440 def get_default_perms(self, suffix=''):
1441 return self._get_default_perms(self, suffix)
1441 return self._get_default_perms(self, suffix)
1442
1442
1443 def get_api_data(self, with_group_members=True, include_secrets=False):
1443 def get_api_data(self, with_group_members=True, include_secrets=False):
1444 """
1444 """
1445 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1445 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1446 basically forwarded.
1446 basically forwarded.
1447
1447
1448 """
1448 """
1449 user_group = self
1449 user_group = self
1450 data = {
1450 data = {
1451 'users_group_id': user_group.users_group_id,
1451 'users_group_id': user_group.users_group_id,
1452 'group_name': user_group.users_group_name,
1452 'group_name': user_group.users_group_name,
1453 'group_description': user_group.user_group_description,
1453 'group_description': user_group.user_group_description,
1454 'active': user_group.users_group_active,
1454 'active': user_group.users_group_active,
1455 'owner': user_group.user.username,
1455 'owner': user_group.user.username,
1456 'owner_email': user_group.user.email,
1456 'owner_email': user_group.user.email,
1457 }
1457 }
1458
1458
1459 if with_group_members:
1459 if with_group_members:
1460 users = []
1460 users = []
1461 for user in user_group.members:
1461 for user in user_group.members:
1462 user = user.user
1462 user = user.user
1463 users.append(user.get_api_data(include_secrets=include_secrets))
1463 users.append(user.get_api_data(include_secrets=include_secrets))
1464 data['users'] = users
1464 data['users'] = users
1465
1465
1466 return data
1466 return data
1467
1467
1468
1468
1469 class UserGroupMember(Base, BaseModel):
1469 class UserGroupMember(Base, BaseModel):
1470 __tablename__ = 'users_groups_members'
1470 __tablename__ = 'users_groups_members'
1471 __table_args__ = (
1471 __table_args__ = (
1472 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1472 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1473 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1473 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1474 )
1474 )
1475
1475
1476 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1476 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1477 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1477 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1479
1479
1480 user = relationship('User', lazy='joined')
1480 user = relationship('User', lazy='joined')
1481 users_group = relationship('UserGroup')
1481 users_group = relationship('UserGroup')
1482
1482
1483 def __init__(self, gr_id='', u_id=''):
1483 def __init__(self, gr_id='', u_id=''):
1484 self.users_group_id = gr_id
1484 self.users_group_id = gr_id
1485 self.user_id = u_id
1485 self.user_id = u_id
1486
1486
1487
1487
1488 class RepositoryField(Base, BaseModel):
1488 class RepositoryField(Base, BaseModel):
1489 __tablename__ = 'repositories_fields'
1489 __tablename__ = 'repositories_fields'
1490 __table_args__ = (
1490 __table_args__ = (
1491 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1491 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1493 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1493 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1494 )
1494 )
1495 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1495 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1496
1496
1497 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1497 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1498 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1498 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1499 field_key = Column("field_key", String(250))
1499 field_key = Column("field_key", String(250))
1500 field_label = Column("field_label", String(1024), nullable=False)
1500 field_label = Column("field_label", String(1024), nullable=False)
1501 field_value = Column("field_value", String(10000), nullable=False)
1501 field_value = Column("field_value", String(10000), nullable=False)
1502 field_desc = Column("field_desc", String(1024), nullable=False)
1502 field_desc = Column("field_desc", String(1024), nullable=False)
1503 field_type = Column("field_type", String(255), nullable=False, unique=None)
1503 field_type = Column("field_type", String(255), nullable=False, unique=None)
1504 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1504 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1505
1505
1506 repository = relationship('Repository')
1506 repository = relationship('Repository')
1507
1507
1508 @property
1508 @property
1509 def field_key_prefixed(self):
1509 def field_key_prefixed(self):
1510 return 'ex_%s' % self.field_key
1510 return 'ex_%s' % self.field_key
1511
1511
1512 @classmethod
1512 @classmethod
1513 def un_prefix_key(cls, key):
1513 def un_prefix_key(cls, key):
1514 if key.startswith(cls.PREFIX):
1514 if key.startswith(cls.PREFIX):
1515 return key[len(cls.PREFIX):]
1515 return key[len(cls.PREFIX):]
1516 return key
1516 return key
1517
1517
1518 @classmethod
1518 @classmethod
1519 def get_by_key_name(cls, key, repo):
1519 def get_by_key_name(cls, key, repo):
1520 row = cls.query()\
1520 row = cls.query()\
1521 .filter(cls.repository == repo)\
1521 .filter(cls.repository == repo)\
1522 .filter(cls.field_key == key).scalar()
1522 .filter(cls.field_key == key).scalar()
1523 return row
1523 return row
1524
1524
1525
1525
1526 class Repository(Base, BaseModel):
1526 class Repository(Base, BaseModel):
1527 __tablename__ = 'repositories'
1527 __tablename__ = 'repositories'
1528 __table_args__ = (
1528 __table_args__ = (
1529 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1529 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1531 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1531 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1532 )
1532 )
1533 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1533 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1534 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1534 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1535
1535
1536 STATE_CREATED = 'repo_state_created'
1536 STATE_CREATED = 'repo_state_created'
1537 STATE_PENDING = 'repo_state_pending'
1537 STATE_PENDING = 'repo_state_pending'
1538 STATE_ERROR = 'repo_state_error'
1538 STATE_ERROR = 'repo_state_error'
1539
1539
1540 LOCK_AUTOMATIC = 'lock_auto'
1540 LOCK_AUTOMATIC = 'lock_auto'
1541 LOCK_API = 'lock_api'
1541 LOCK_API = 'lock_api'
1542 LOCK_WEB = 'lock_web'
1542 LOCK_WEB = 'lock_web'
1543 LOCK_PULL = 'lock_pull'
1543 LOCK_PULL = 'lock_pull'
1544
1544
1545 NAME_SEP = URL_SEP
1545 NAME_SEP = URL_SEP
1546
1546
1547 repo_id = Column(
1547 repo_id = Column(
1548 "repo_id", Integer(), nullable=False, unique=True, default=None,
1548 "repo_id", Integer(), nullable=False, unique=True, default=None,
1549 primary_key=True)
1549 primary_key=True)
1550 _repo_name = Column(
1550 _repo_name = Column(
1551 "repo_name", Text(), nullable=False, default=None)
1551 "repo_name", Text(), nullable=False, default=None)
1552 _repo_name_hash = Column(
1552 _repo_name_hash = Column(
1553 "repo_name_hash", String(255), nullable=False, unique=True)
1553 "repo_name_hash", String(255), nullable=False, unique=True)
1554 repo_state = Column("repo_state", String(255), nullable=True)
1554 repo_state = Column("repo_state", String(255), nullable=True)
1555
1555
1556 clone_uri = Column(
1556 clone_uri = Column(
1557 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1557 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1558 default=None)
1558 default=None)
1559 repo_type = Column(
1559 repo_type = Column(
1560 "repo_type", String(255), nullable=False, unique=False, default=None)
1560 "repo_type", String(255), nullable=False, unique=False, default=None)
1561 user_id = Column(
1561 user_id = Column(
1562 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1562 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1563 unique=False, default=None)
1563 unique=False, default=None)
1564 private = Column(
1564 private = Column(
1565 "private", Boolean(), nullable=True, unique=None, default=None)
1565 "private", Boolean(), nullable=True, unique=None, default=None)
1566 enable_statistics = Column(
1566 enable_statistics = Column(
1567 "statistics", Boolean(), nullable=True, unique=None, default=True)
1567 "statistics", Boolean(), nullable=True, unique=None, default=True)
1568 enable_downloads = Column(
1568 enable_downloads = Column(
1569 "downloads", Boolean(), nullable=True, unique=None, default=True)
1569 "downloads", Boolean(), nullable=True, unique=None, default=True)
1570 description = Column(
1570 description = Column(
1571 "description", String(10000), nullable=True, unique=None, default=None)
1571 "description", String(10000), nullable=True, unique=None, default=None)
1572 created_on = Column(
1572 created_on = Column(
1573 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1573 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1574 default=datetime.datetime.now)
1574 default=datetime.datetime.now)
1575 updated_on = Column(
1575 updated_on = Column(
1576 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1576 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1577 default=datetime.datetime.now)
1577 default=datetime.datetime.now)
1578 _landing_revision = Column(
1578 _landing_revision = Column(
1579 "landing_revision", String(255), nullable=False, unique=False,
1579 "landing_revision", String(255), nullable=False, unique=False,
1580 default=None)
1580 default=None)
1581 enable_locking = Column(
1581 enable_locking = Column(
1582 "enable_locking", Boolean(), nullable=False, unique=None,
1582 "enable_locking", Boolean(), nullable=False, unique=None,
1583 default=False)
1583 default=False)
1584 _locked = Column(
1584 _locked = Column(
1585 "locked", String(255), nullable=True, unique=False, default=None)
1585 "locked", String(255), nullable=True, unique=False, default=None)
1586 _changeset_cache = Column(
1586 _changeset_cache = Column(
1587 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1587 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1588
1588
1589 fork_id = Column(
1589 fork_id = Column(
1590 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1590 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1591 nullable=True, unique=False, default=None)
1591 nullable=True, unique=False, default=None)
1592 group_id = Column(
1592 group_id = Column(
1593 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1593 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1594 unique=False, default=None)
1594 unique=False, default=None)
1595
1595
1596 user = relationship('User', lazy='joined')
1596 user = relationship('User', lazy='joined')
1597 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1597 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1598 group = relationship('RepoGroup', lazy='joined')
1598 group = relationship('RepoGroup', lazy='joined')
1599 repo_to_perm = relationship(
1599 repo_to_perm = relationship(
1600 'UserRepoToPerm', cascade='all',
1600 'UserRepoToPerm', cascade='all',
1601 order_by='UserRepoToPerm.repo_to_perm_id')
1601 order_by='UserRepoToPerm.repo_to_perm_id')
1602 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1602 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1603 stats = relationship('Statistics', cascade='all', uselist=False)
1603 stats = relationship('Statistics', cascade='all', uselist=False)
1604
1604
1605 followers = relationship(
1605 followers = relationship(
1606 'UserFollowing',
1606 'UserFollowing',
1607 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1607 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1608 cascade='all')
1608 cascade='all')
1609 extra_fields = relationship(
1609 extra_fields = relationship(
1610 'RepositoryField', cascade="all, delete, delete-orphan")
1610 'RepositoryField', cascade="all, delete, delete-orphan")
1611 logs = relationship('UserLog')
1611 logs = relationship('UserLog')
1612 comments = relationship(
1612 comments = relationship(
1613 'ChangesetComment', cascade="all, delete, delete-orphan")
1613 'ChangesetComment', cascade="all, delete, delete-orphan")
1614 pull_requests_source = relationship(
1614 pull_requests_source = relationship(
1615 'PullRequest',
1615 'PullRequest',
1616 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1616 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1617 cascade="all, delete, delete-orphan")
1617 cascade="all, delete, delete-orphan")
1618 pull_requests_target = relationship(
1618 pull_requests_target = relationship(
1619 'PullRequest',
1619 'PullRequest',
1620 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1620 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1621 cascade="all, delete, delete-orphan")
1621 cascade="all, delete, delete-orphan")
1622 ui = relationship('RepoRhodeCodeUi', cascade="all")
1622 ui = relationship('RepoRhodeCodeUi', cascade="all")
1623 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1623 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1624 integrations = relationship('Integration',
1624 integrations = relationship('Integration',
1625 cascade="all, delete, delete-orphan")
1625 cascade="all, delete, delete-orphan")
1626
1626
1627 def __unicode__(self):
1627 def __unicode__(self):
1628 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1628 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1629 safe_unicode(self.repo_name))
1629 safe_unicode(self.repo_name))
1630
1630
1631 @hybrid_property
1631 @hybrid_property
1632 def description_safe(self):
1632 def description_safe(self):
1633 from rhodecode.lib import helpers as h
1633 from rhodecode.lib import helpers as h
1634 return h.escape(self.description)
1634 return h.escape(self.description)
1635
1635
1636 @hybrid_property
1636 @hybrid_property
1637 def landing_rev(self):
1637 def landing_rev(self):
1638 # always should return [rev_type, rev]
1638 # always should return [rev_type, rev]
1639 if self._landing_revision:
1639 if self._landing_revision:
1640 _rev_info = self._landing_revision.split(':')
1640 _rev_info = self._landing_revision.split(':')
1641 if len(_rev_info) < 2:
1641 if len(_rev_info) < 2:
1642 _rev_info.insert(0, 'rev')
1642 _rev_info.insert(0, 'rev')
1643 return [_rev_info[0], _rev_info[1]]
1643 return [_rev_info[0], _rev_info[1]]
1644 return [None, None]
1644 return [None, None]
1645
1645
1646 @landing_rev.setter
1646 @landing_rev.setter
1647 def landing_rev(self, val):
1647 def landing_rev(self, val):
1648 if ':' not in val:
1648 if ':' not in val:
1649 raise ValueError('value must be delimited with `:` and consist '
1649 raise ValueError('value must be delimited with `:` and consist '
1650 'of <rev_type>:<rev>, got %s instead' % val)
1650 'of <rev_type>:<rev>, got %s instead' % val)
1651 self._landing_revision = val
1651 self._landing_revision = val
1652
1652
1653 @hybrid_property
1653 @hybrid_property
1654 def locked(self):
1654 def locked(self):
1655 if self._locked:
1655 if self._locked:
1656 user_id, timelocked, reason = self._locked.split(':')
1656 user_id, timelocked, reason = self._locked.split(':')
1657 lock_values = int(user_id), timelocked, reason
1657 lock_values = int(user_id), timelocked, reason
1658 else:
1658 else:
1659 lock_values = [None, None, None]
1659 lock_values = [None, None, None]
1660 return lock_values
1660 return lock_values
1661
1661
1662 @locked.setter
1662 @locked.setter
1663 def locked(self, val):
1663 def locked(self, val):
1664 if val and isinstance(val, (list, tuple)):
1664 if val and isinstance(val, (list, tuple)):
1665 self._locked = ':'.join(map(str, val))
1665 self._locked = ':'.join(map(str, val))
1666 else:
1666 else:
1667 self._locked = None
1667 self._locked = None
1668
1668
1669 @hybrid_property
1669 @hybrid_property
1670 def changeset_cache(self):
1670 def changeset_cache(self):
1671 from rhodecode.lib.vcs.backends.base import EmptyCommit
1671 from rhodecode.lib.vcs.backends.base import EmptyCommit
1672 dummy = EmptyCommit().__json__()
1672 dummy = EmptyCommit().__json__()
1673 if not self._changeset_cache:
1673 if not self._changeset_cache:
1674 return dummy
1674 return dummy
1675 try:
1675 try:
1676 return json.loads(self._changeset_cache)
1676 return json.loads(self._changeset_cache)
1677 except TypeError:
1677 except TypeError:
1678 return dummy
1678 return dummy
1679 except Exception:
1679 except Exception:
1680 log.error(traceback.format_exc())
1680 log.error(traceback.format_exc())
1681 return dummy
1681 return dummy
1682
1682
1683 @changeset_cache.setter
1683 @changeset_cache.setter
1684 def changeset_cache(self, val):
1684 def changeset_cache(self, val):
1685 try:
1685 try:
1686 self._changeset_cache = json.dumps(val)
1686 self._changeset_cache = json.dumps(val)
1687 except Exception:
1687 except Exception:
1688 log.error(traceback.format_exc())
1688 log.error(traceback.format_exc())
1689
1689
1690 @hybrid_property
1690 @hybrid_property
1691 def repo_name(self):
1691 def repo_name(self):
1692 return self._repo_name
1692 return self._repo_name
1693
1693
1694 @repo_name.setter
1694 @repo_name.setter
1695 def repo_name(self, value):
1695 def repo_name(self, value):
1696 self._repo_name = value
1696 self._repo_name = value
1697 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1697 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1698
1698
1699 @classmethod
1699 @classmethod
1700 def normalize_repo_name(cls, repo_name):
1700 def normalize_repo_name(cls, repo_name):
1701 """
1701 """
1702 Normalizes os specific repo_name to the format internally stored inside
1702 Normalizes os specific repo_name to the format internally stored inside
1703 database using URL_SEP
1703 database using URL_SEP
1704
1704
1705 :param cls:
1705 :param cls:
1706 :param repo_name:
1706 :param repo_name:
1707 """
1707 """
1708 return cls.NAME_SEP.join(repo_name.split(os.sep))
1708 return cls.NAME_SEP.join(repo_name.split(os.sep))
1709
1709
1710 @classmethod
1710 @classmethod
1711 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1711 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1712 session = Session()
1712 session = Session()
1713 q = session.query(cls).filter(cls.repo_name == repo_name)
1713 q = session.query(cls).filter(cls.repo_name == repo_name)
1714
1714
1715 if cache:
1715 if cache:
1716 if identity_cache:
1716 if identity_cache:
1717 val = cls.identity_cache(session, 'repo_name', repo_name)
1717 val = cls.identity_cache(session, 'repo_name', repo_name)
1718 if val:
1718 if val:
1719 return val
1719 return val
1720 else:
1720 else:
1721 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1721 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1722 q = q.options(
1722 q = q.options(
1723 FromCache("sql_cache_short", cache_key))
1723 FromCache("sql_cache_short", cache_key))
1724
1724
1725 return q.scalar()
1725 return q.scalar()
1726
1726
1727 @classmethod
1727 @classmethod
1728 def get_by_id_or_repo_name(cls, repoid):
1728 def get_by_id_or_repo_name(cls, repoid):
1729 if isinstance(repoid, (int, long)):
1729 if isinstance(repoid, (int, long)):
1730 try:
1730 try:
1731 repo = cls.get(repoid)
1731 repo = cls.get(repoid)
1732 except ValueError:
1732 except ValueError:
1733 repo = None
1733 repo = None
1734 else:
1734 else:
1735 repo = cls.get_by_repo_name(repoid)
1735 repo = cls.get_by_repo_name(repoid)
1736 return repo
1736 return repo
1737
1737
1738 @classmethod
1738 @classmethod
1739 def get_by_full_path(cls, repo_full_path):
1739 def get_by_full_path(cls, repo_full_path):
1740 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1740 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1741 repo_name = cls.normalize_repo_name(repo_name)
1741 repo_name = cls.normalize_repo_name(repo_name)
1742 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1742 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1743
1743
1744 @classmethod
1744 @classmethod
1745 def get_repo_forks(cls, repo_id):
1745 def get_repo_forks(cls, repo_id):
1746 return cls.query().filter(Repository.fork_id == repo_id)
1746 return cls.query().filter(Repository.fork_id == repo_id)
1747
1747
1748 @classmethod
1748 @classmethod
1749 def base_path(cls):
1749 def base_path(cls):
1750 """
1750 """
1751 Returns base path when all repos are stored
1751 Returns base path when all repos are stored
1752
1752
1753 :param cls:
1753 :param cls:
1754 """
1754 """
1755 q = Session().query(RhodeCodeUi)\
1755 q = Session().query(RhodeCodeUi)\
1756 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1756 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1757 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1757 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1758 return q.one().ui_value
1758 return q.one().ui_value
1759
1759
1760 @classmethod
1760 @classmethod
1761 def is_valid(cls, repo_name):
1761 def is_valid(cls, repo_name):
1762 """
1762 """
1763 returns True if given repo name is a valid filesystem repository
1763 returns True if given repo name is a valid filesystem repository
1764
1764
1765 :param cls:
1765 :param cls:
1766 :param repo_name:
1766 :param repo_name:
1767 """
1767 """
1768 from rhodecode.lib.utils import is_valid_repo
1768 from rhodecode.lib.utils import is_valid_repo
1769
1769
1770 return is_valid_repo(repo_name, cls.base_path())
1770 return is_valid_repo(repo_name, cls.base_path())
1771
1771
1772 @classmethod
1772 @classmethod
1773 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1773 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1774 case_insensitive=True):
1774 case_insensitive=True):
1775 q = Repository.query()
1775 q = Repository.query()
1776
1776
1777 if not isinstance(user_id, Optional):
1777 if not isinstance(user_id, Optional):
1778 q = q.filter(Repository.user_id == user_id)
1778 q = q.filter(Repository.user_id == user_id)
1779
1779
1780 if not isinstance(group_id, Optional):
1780 if not isinstance(group_id, Optional):
1781 q = q.filter(Repository.group_id == group_id)
1781 q = q.filter(Repository.group_id == group_id)
1782
1782
1783 if case_insensitive:
1783 if case_insensitive:
1784 q = q.order_by(func.lower(Repository.repo_name))
1784 q = q.order_by(func.lower(Repository.repo_name))
1785 else:
1785 else:
1786 q = q.order_by(Repository.repo_name)
1786 q = q.order_by(Repository.repo_name)
1787 return q.all()
1787 return q.all()
1788
1788
1789 @property
1789 @property
1790 def forks(self):
1790 def forks(self):
1791 """
1791 """
1792 Return forks of this repo
1792 Return forks of this repo
1793 """
1793 """
1794 return Repository.get_repo_forks(self.repo_id)
1794 return Repository.get_repo_forks(self.repo_id)
1795
1795
1796 @property
1796 @property
1797 def parent(self):
1797 def parent(self):
1798 """
1798 """
1799 Returns fork parent
1799 Returns fork parent
1800 """
1800 """
1801 return self.fork
1801 return self.fork
1802
1802
1803 @property
1803 @property
1804 def just_name(self):
1804 def just_name(self):
1805 return self.repo_name.split(self.NAME_SEP)[-1]
1805 return self.repo_name.split(self.NAME_SEP)[-1]
1806
1806
1807 @property
1807 @property
1808 def groups_with_parents(self):
1808 def groups_with_parents(self):
1809 groups = []
1809 groups = []
1810 if self.group is None:
1810 if self.group is None:
1811 return groups
1811 return groups
1812
1812
1813 cur_gr = self.group
1813 cur_gr = self.group
1814 groups.insert(0, cur_gr)
1814 groups.insert(0, cur_gr)
1815 while 1:
1815 while 1:
1816 gr = getattr(cur_gr, 'parent_group', None)
1816 gr = getattr(cur_gr, 'parent_group', None)
1817 cur_gr = cur_gr.parent_group
1817 cur_gr = cur_gr.parent_group
1818 if gr is None:
1818 if gr is None:
1819 break
1819 break
1820 groups.insert(0, gr)
1820 groups.insert(0, gr)
1821
1821
1822 return groups
1822 return groups
1823
1823
1824 @property
1824 @property
1825 def groups_and_repo(self):
1825 def groups_and_repo(self):
1826 return self.groups_with_parents, self
1826 return self.groups_with_parents, self
1827
1827
1828 @LazyProperty
1828 @LazyProperty
1829 def repo_path(self):
1829 def repo_path(self):
1830 """
1830 """
1831 Returns base full path for that repository means where it actually
1831 Returns base full path for that repository means where it actually
1832 exists on a filesystem
1832 exists on a filesystem
1833 """
1833 """
1834 q = Session().query(RhodeCodeUi).filter(
1834 q = Session().query(RhodeCodeUi).filter(
1835 RhodeCodeUi.ui_key == self.NAME_SEP)
1835 RhodeCodeUi.ui_key == self.NAME_SEP)
1836 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1836 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1837 return q.one().ui_value
1837 return q.one().ui_value
1838
1838
1839 @property
1839 @property
1840 def repo_full_path(self):
1840 def repo_full_path(self):
1841 p = [self.repo_path]
1841 p = [self.repo_path]
1842 # we need to split the name by / since this is how we store the
1842 # we need to split the name by / since this is how we store the
1843 # names in the database, but that eventually needs to be converted
1843 # names in the database, but that eventually needs to be converted
1844 # into a valid system path
1844 # into a valid system path
1845 p += self.repo_name.split(self.NAME_SEP)
1845 p += self.repo_name.split(self.NAME_SEP)
1846 return os.path.join(*map(safe_unicode, p))
1846 return os.path.join(*map(safe_unicode, p))
1847
1847
1848 @property
1848 @property
1849 def cache_keys(self):
1849 def cache_keys(self):
1850 """
1850 """
1851 Returns associated cache keys for that repo
1851 Returns associated cache keys for that repo
1852 """
1852 """
1853 return CacheKey.query()\
1853 return CacheKey.query()\
1854 .filter(CacheKey.cache_args == self.repo_name)\
1854 .filter(CacheKey.cache_args == self.repo_name)\
1855 .order_by(CacheKey.cache_key)\
1855 .order_by(CacheKey.cache_key)\
1856 .all()
1856 .all()
1857
1857
1858 def get_new_name(self, repo_name):
1858 def get_new_name(self, repo_name):
1859 """
1859 """
1860 returns new full repository name based on assigned group and new new
1860 returns new full repository name based on assigned group and new new
1861
1861
1862 :param group_name:
1862 :param group_name:
1863 """
1863 """
1864 path_prefix = self.group.full_path_splitted if self.group else []
1864 path_prefix = self.group.full_path_splitted if self.group else []
1865 return self.NAME_SEP.join(path_prefix + [repo_name])
1865 return self.NAME_SEP.join(path_prefix + [repo_name])
1866
1866
1867 @property
1867 @property
1868 def _config(self):
1868 def _config(self):
1869 """
1869 """
1870 Returns db based config object.
1870 Returns db based config object.
1871 """
1871 """
1872 from rhodecode.lib.utils import make_db_config
1872 from rhodecode.lib.utils import make_db_config
1873 return make_db_config(clear_session=False, repo=self)
1873 return make_db_config(clear_session=False, repo=self)
1874
1874
1875 def permissions(self, with_admins=True, with_owner=True):
1875 def permissions(self, with_admins=True, with_owner=True):
1876 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1876 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1877 q = q.options(joinedload(UserRepoToPerm.repository),
1877 q = q.options(joinedload(UserRepoToPerm.repository),
1878 joinedload(UserRepoToPerm.user),
1878 joinedload(UserRepoToPerm.user),
1879 joinedload(UserRepoToPerm.permission),)
1879 joinedload(UserRepoToPerm.permission),)
1880
1880
1881 # get owners and admins and permissions. We do a trick of re-writing
1881 # get owners and admins and permissions. We do a trick of re-writing
1882 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1882 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1883 # has a global reference and changing one object propagates to all
1883 # has a global reference and changing one object propagates to all
1884 # others. This means if admin is also an owner admin_row that change
1884 # others. This means if admin is also an owner admin_row that change
1885 # would propagate to both objects
1885 # would propagate to both objects
1886 perm_rows = []
1886 perm_rows = []
1887 for _usr in q.all():
1887 for _usr in q.all():
1888 usr = AttributeDict(_usr.user.get_dict())
1888 usr = AttributeDict(_usr.user.get_dict())
1889 usr.permission = _usr.permission.permission_name
1889 usr.permission = _usr.permission.permission_name
1890 perm_rows.append(usr)
1890 perm_rows.append(usr)
1891
1891
1892 # filter the perm rows by 'default' first and then sort them by
1892 # filter the perm rows by 'default' first and then sort them by
1893 # admin,write,read,none permissions sorted again alphabetically in
1893 # admin,write,read,none permissions sorted again alphabetically in
1894 # each group
1894 # each group
1895 perm_rows = sorted(perm_rows, key=display_user_sort)
1895 perm_rows = sorted(perm_rows, key=display_user_sort)
1896
1896
1897 _admin_perm = 'repository.admin'
1897 _admin_perm = 'repository.admin'
1898 owner_row = []
1898 owner_row = []
1899 if with_owner:
1899 if with_owner:
1900 usr = AttributeDict(self.user.get_dict())
1900 usr = AttributeDict(self.user.get_dict())
1901 usr.owner_row = True
1901 usr.owner_row = True
1902 usr.permission = _admin_perm
1902 usr.permission = _admin_perm
1903 owner_row.append(usr)
1903 owner_row.append(usr)
1904
1904
1905 super_admin_rows = []
1905 super_admin_rows = []
1906 if with_admins:
1906 if with_admins:
1907 for usr in User.get_all_super_admins():
1907 for usr in User.get_all_super_admins():
1908 # if this admin is also owner, don't double the record
1908 # if this admin is also owner, don't double the record
1909 if usr.user_id == owner_row[0].user_id:
1909 if usr.user_id == owner_row[0].user_id:
1910 owner_row[0].admin_row = True
1910 owner_row[0].admin_row = True
1911 else:
1911 else:
1912 usr = AttributeDict(usr.get_dict())
1912 usr = AttributeDict(usr.get_dict())
1913 usr.admin_row = True
1913 usr.admin_row = True
1914 usr.permission = _admin_perm
1914 usr.permission = _admin_perm
1915 super_admin_rows.append(usr)
1915 super_admin_rows.append(usr)
1916
1916
1917 return super_admin_rows + owner_row + perm_rows
1917 return super_admin_rows + owner_row + perm_rows
1918
1918
1919 def permission_user_groups(self):
1919 def permission_user_groups(self):
1920 q = UserGroupRepoToPerm.query().filter(
1920 q = UserGroupRepoToPerm.query().filter(
1921 UserGroupRepoToPerm.repository == self)
1921 UserGroupRepoToPerm.repository == self)
1922 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1922 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1923 joinedload(UserGroupRepoToPerm.users_group),
1923 joinedload(UserGroupRepoToPerm.users_group),
1924 joinedload(UserGroupRepoToPerm.permission),)
1924 joinedload(UserGroupRepoToPerm.permission),)
1925
1925
1926 perm_rows = []
1926 perm_rows = []
1927 for _user_group in q.all():
1927 for _user_group in q.all():
1928 usr = AttributeDict(_user_group.users_group.get_dict())
1928 usr = AttributeDict(_user_group.users_group.get_dict())
1929 usr.permission = _user_group.permission.permission_name
1929 usr.permission = _user_group.permission.permission_name
1930 perm_rows.append(usr)
1930 perm_rows.append(usr)
1931
1931
1932 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1932 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1933 return perm_rows
1933 return perm_rows
1934
1934
1935 def get_api_data(self, include_secrets=False):
1935 def get_api_data(self, include_secrets=False):
1936 """
1936 """
1937 Common function for generating repo api data
1937 Common function for generating repo api data
1938
1938
1939 :param include_secrets: See :meth:`User.get_api_data`.
1939 :param include_secrets: See :meth:`User.get_api_data`.
1940
1940
1941 """
1941 """
1942 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1942 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1943 # move this methods on models level.
1943 # move this methods on models level.
1944 from rhodecode.model.settings import SettingsModel
1944 from rhodecode.model.settings import SettingsModel
1945 from rhodecode.model.repo import RepoModel
1945 from rhodecode.model.repo import RepoModel
1946
1946
1947 repo = self
1947 repo = self
1948 _user_id, _time, _reason = self.locked
1948 _user_id, _time, _reason = self.locked
1949
1949
1950 data = {
1950 data = {
1951 'repo_id': repo.repo_id,
1951 'repo_id': repo.repo_id,
1952 'repo_name': repo.repo_name,
1952 'repo_name': repo.repo_name,
1953 'repo_type': repo.repo_type,
1953 'repo_type': repo.repo_type,
1954 'clone_uri': repo.clone_uri or '',
1954 'clone_uri': repo.clone_uri or '',
1955 'url': RepoModel().get_url(self),
1955 'url': RepoModel().get_url(self),
1956 'private': repo.private,
1956 'private': repo.private,
1957 'created_on': repo.created_on,
1957 'created_on': repo.created_on,
1958 'description': repo.description_safe,
1958 'description': repo.description_safe,
1959 'landing_rev': repo.landing_rev,
1959 'landing_rev': repo.landing_rev,
1960 'owner': repo.user.username,
1960 'owner': repo.user.username,
1961 'fork_of': repo.fork.repo_name if repo.fork else None,
1961 'fork_of': repo.fork.repo_name if repo.fork else None,
1962 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1962 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1963 'enable_statistics': repo.enable_statistics,
1963 'enable_statistics': repo.enable_statistics,
1964 'enable_locking': repo.enable_locking,
1964 'enable_locking': repo.enable_locking,
1965 'enable_downloads': repo.enable_downloads,
1965 'enable_downloads': repo.enable_downloads,
1966 'last_changeset': repo.changeset_cache,
1966 'last_changeset': repo.changeset_cache,
1967 'locked_by': User.get(_user_id).get_api_data(
1967 'locked_by': User.get(_user_id).get_api_data(
1968 include_secrets=include_secrets) if _user_id else None,
1968 include_secrets=include_secrets) if _user_id else None,
1969 'locked_date': time_to_datetime(_time) if _time else None,
1969 'locked_date': time_to_datetime(_time) if _time else None,
1970 'lock_reason': _reason if _reason else None,
1970 'lock_reason': _reason if _reason else None,
1971 }
1971 }
1972
1972
1973 # TODO: mikhail: should be per-repo settings here
1973 # TODO: mikhail: should be per-repo settings here
1974 rc_config = SettingsModel().get_all_settings()
1974 rc_config = SettingsModel().get_all_settings()
1975 repository_fields = str2bool(
1975 repository_fields = str2bool(
1976 rc_config.get('rhodecode_repository_fields'))
1976 rc_config.get('rhodecode_repository_fields'))
1977 if repository_fields:
1977 if repository_fields:
1978 for f in self.extra_fields:
1978 for f in self.extra_fields:
1979 data[f.field_key_prefixed] = f.field_value
1979 data[f.field_key_prefixed] = f.field_value
1980
1980
1981 return data
1981 return data
1982
1982
1983 @classmethod
1983 @classmethod
1984 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1984 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1985 if not lock_time:
1985 if not lock_time:
1986 lock_time = time.time()
1986 lock_time = time.time()
1987 if not lock_reason:
1987 if not lock_reason:
1988 lock_reason = cls.LOCK_AUTOMATIC
1988 lock_reason = cls.LOCK_AUTOMATIC
1989 repo.locked = [user_id, lock_time, lock_reason]
1989 repo.locked = [user_id, lock_time, lock_reason]
1990 Session().add(repo)
1990 Session().add(repo)
1991 Session().commit()
1991 Session().commit()
1992
1992
1993 @classmethod
1993 @classmethod
1994 def unlock(cls, repo):
1994 def unlock(cls, repo):
1995 repo.locked = None
1995 repo.locked = None
1996 Session().add(repo)
1996 Session().add(repo)
1997 Session().commit()
1997 Session().commit()
1998
1998
1999 @classmethod
1999 @classmethod
2000 def getlock(cls, repo):
2000 def getlock(cls, repo):
2001 return repo.locked
2001 return repo.locked
2002
2002
2003 def is_user_lock(self, user_id):
2003 def is_user_lock(self, user_id):
2004 if self.lock[0]:
2004 if self.lock[0]:
2005 lock_user_id = safe_int(self.lock[0])
2005 lock_user_id = safe_int(self.lock[0])
2006 user_id = safe_int(user_id)
2006 user_id = safe_int(user_id)
2007 # both are ints, and they are equal
2007 # both are ints, and they are equal
2008 return all([lock_user_id, user_id]) and lock_user_id == user_id
2008 return all([lock_user_id, user_id]) and lock_user_id == user_id
2009
2009
2010 return False
2010 return False
2011
2011
2012 def get_locking_state(self, action, user_id, only_when_enabled=True):
2012 def get_locking_state(self, action, user_id, only_when_enabled=True):
2013 """
2013 """
2014 Checks locking on this repository, if locking is enabled and lock is
2014 Checks locking on this repository, if locking is enabled and lock is
2015 present returns a tuple of make_lock, locked, locked_by.
2015 present returns a tuple of make_lock, locked, locked_by.
2016 make_lock can have 3 states None (do nothing) True, make lock
2016 make_lock can have 3 states None (do nothing) True, make lock
2017 False release lock, This value is later propagated to hooks, which
2017 False release lock, This value is later propagated to hooks, which
2018 do the locking. Think about this as signals passed to hooks what to do.
2018 do the locking. Think about this as signals passed to hooks what to do.
2019
2019
2020 """
2020 """
2021 # TODO: johbo: This is part of the business logic and should be moved
2021 # TODO: johbo: This is part of the business logic and should be moved
2022 # into the RepositoryModel.
2022 # into the RepositoryModel.
2023
2023
2024 if action not in ('push', 'pull'):
2024 if action not in ('push', 'pull'):
2025 raise ValueError("Invalid action value: %s" % repr(action))
2025 raise ValueError("Invalid action value: %s" % repr(action))
2026
2026
2027 # defines if locked error should be thrown to user
2027 # defines if locked error should be thrown to user
2028 currently_locked = False
2028 currently_locked = False
2029 # defines if new lock should be made, tri-state
2029 # defines if new lock should be made, tri-state
2030 make_lock = None
2030 make_lock = None
2031 repo = self
2031 repo = self
2032 user = User.get(user_id)
2032 user = User.get(user_id)
2033
2033
2034 lock_info = repo.locked
2034 lock_info = repo.locked
2035
2035
2036 if repo and (repo.enable_locking or not only_when_enabled):
2036 if repo and (repo.enable_locking or not only_when_enabled):
2037 if action == 'push':
2037 if action == 'push':
2038 # check if it's already locked !, if it is compare users
2038 # check if it's already locked !, if it is compare users
2039 locked_by_user_id = lock_info[0]
2039 locked_by_user_id = lock_info[0]
2040 if user.user_id == locked_by_user_id:
2040 if user.user_id == locked_by_user_id:
2041 log.debug(
2041 log.debug(
2042 'Got `push` action from user %s, now unlocking', user)
2042 'Got `push` action from user %s, now unlocking', user)
2043 # unlock if we have push from user who locked
2043 # unlock if we have push from user who locked
2044 make_lock = False
2044 make_lock = False
2045 else:
2045 else:
2046 # we're not the same user who locked, ban with
2046 # we're not the same user who locked, ban with
2047 # code defined in settings (default is 423 HTTP Locked) !
2047 # code defined in settings (default is 423 HTTP Locked) !
2048 log.debug('Repo %s is currently locked by %s', repo, user)
2048 log.debug('Repo %s is currently locked by %s', repo, user)
2049 currently_locked = True
2049 currently_locked = True
2050 elif action == 'pull':
2050 elif action == 'pull':
2051 # [0] user [1] date
2051 # [0] user [1] date
2052 if lock_info[0] and lock_info[1]:
2052 if lock_info[0] and lock_info[1]:
2053 log.debug('Repo %s is currently locked by %s', repo, user)
2053 log.debug('Repo %s is currently locked by %s', repo, user)
2054 currently_locked = True
2054 currently_locked = True
2055 else:
2055 else:
2056 log.debug('Setting lock on repo %s by %s', repo, user)
2056 log.debug('Setting lock on repo %s by %s', repo, user)
2057 make_lock = True
2057 make_lock = True
2058
2058
2059 else:
2059 else:
2060 log.debug('Repository %s do not have locking enabled', repo)
2060 log.debug('Repository %s do not have locking enabled', repo)
2061
2061
2062 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2062 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2063 make_lock, currently_locked, lock_info)
2063 make_lock, currently_locked, lock_info)
2064
2064
2065 from rhodecode.lib.auth import HasRepoPermissionAny
2065 from rhodecode.lib.auth import HasRepoPermissionAny
2066 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2066 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2067 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2067 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2068 # if we don't have at least write permission we cannot make a lock
2068 # if we don't have at least write permission we cannot make a lock
2069 log.debug('lock state reset back to FALSE due to lack '
2069 log.debug('lock state reset back to FALSE due to lack '
2070 'of at least read permission')
2070 'of at least read permission')
2071 make_lock = False
2071 make_lock = False
2072
2072
2073 return make_lock, currently_locked, lock_info
2073 return make_lock, currently_locked, lock_info
2074
2074
2075 @property
2075 @property
2076 def last_db_change(self):
2076 def last_db_change(self):
2077 return self.updated_on
2077 return self.updated_on
2078
2078
2079 @property
2079 @property
2080 def clone_uri_hidden(self):
2080 def clone_uri_hidden(self):
2081 clone_uri = self.clone_uri
2081 clone_uri = self.clone_uri
2082 if clone_uri:
2082 if clone_uri:
2083 import urlobject
2083 import urlobject
2084 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2084 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2085 if url_obj.password:
2085 if url_obj.password:
2086 clone_uri = url_obj.with_password('*****')
2086 clone_uri = url_obj.with_password('*****')
2087 return clone_uri
2087 return clone_uri
2088
2088
2089 def clone_url(self, **override):
2089 def clone_url(self, **override):
2090 from rhodecode.model.settings import SettingsModel
2090 from rhodecode.model.settings import SettingsModel
2091
2091
2092 uri_tmpl = None
2092 uri_tmpl = None
2093 if 'with_id' in override:
2093 if 'with_id' in override:
2094 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2094 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2095 del override['with_id']
2095 del override['with_id']
2096
2096
2097 if 'uri_tmpl' in override:
2097 if 'uri_tmpl' in override:
2098 uri_tmpl = override['uri_tmpl']
2098 uri_tmpl = override['uri_tmpl']
2099 del override['uri_tmpl']
2099 del override['uri_tmpl']
2100
2100
2101 # we didn't override our tmpl from **overrides
2101 # we didn't override our tmpl from **overrides
2102 if not uri_tmpl:
2102 if not uri_tmpl:
2103 rc_config = SettingsModel().get_all_settings(cache=True)
2103 rc_config = SettingsModel().get_all_settings(cache=True)
2104 uri_tmpl = rc_config.get(
2104 uri_tmpl = rc_config.get(
2105 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2105 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2106
2106
2107 request = get_current_request()
2107 request = get_current_request()
2108 return get_clone_url(request=request,
2108 return get_clone_url(request=request,
2109 uri_tmpl=uri_tmpl,
2109 uri_tmpl=uri_tmpl,
2110 repo_name=self.repo_name,
2110 repo_name=self.repo_name,
2111 repo_id=self.repo_id, **override)
2111 repo_id=self.repo_id, **override)
2112
2112
2113 def set_state(self, state):
2113 def set_state(self, state):
2114 self.repo_state = state
2114 self.repo_state = state
2115 Session().add(self)
2115 Session().add(self)
2116 #==========================================================================
2116 #==========================================================================
2117 # SCM PROPERTIES
2117 # SCM PROPERTIES
2118 #==========================================================================
2118 #==========================================================================
2119
2119
2120 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2120 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2121 return get_commit_safe(
2121 return get_commit_safe(
2122 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2122 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2123
2123
2124 def get_changeset(self, rev=None, pre_load=None):
2124 def get_changeset(self, rev=None, pre_load=None):
2125 warnings.warn("Use get_commit", DeprecationWarning)
2125 warnings.warn("Use get_commit", DeprecationWarning)
2126 commit_id = None
2126 commit_id = None
2127 commit_idx = None
2127 commit_idx = None
2128 if isinstance(rev, basestring):
2128 if isinstance(rev, basestring):
2129 commit_id = rev
2129 commit_id = rev
2130 else:
2130 else:
2131 commit_idx = rev
2131 commit_idx = rev
2132 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2132 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2133 pre_load=pre_load)
2133 pre_load=pre_load)
2134
2134
2135 def get_landing_commit(self):
2135 def get_landing_commit(self):
2136 """
2136 """
2137 Returns landing commit, or if that doesn't exist returns the tip
2137 Returns landing commit, or if that doesn't exist returns the tip
2138 """
2138 """
2139 _rev_type, _rev = self.landing_rev
2139 _rev_type, _rev = self.landing_rev
2140 commit = self.get_commit(_rev)
2140 commit = self.get_commit(_rev)
2141 if isinstance(commit, EmptyCommit):
2141 if isinstance(commit, EmptyCommit):
2142 return self.get_commit()
2142 return self.get_commit()
2143 return commit
2143 return commit
2144
2144
2145 def update_commit_cache(self, cs_cache=None, config=None):
2145 def update_commit_cache(self, cs_cache=None, config=None):
2146 """
2146 """
2147 Update cache of last changeset for repository, keys should be::
2147 Update cache of last changeset for repository, keys should be::
2148
2148
2149 short_id
2149 short_id
2150 raw_id
2150 raw_id
2151 revision
2151 revision
2152 parents
2152 parents
2153 message
2153 message
2154 date
2154 date
2155 author
2155 author
2156
2156
2157 :param cs_cache:
2157 :param cs_cache:
2158 """
2158 """
2159 from rhodecode.lib.vcs.backends.base import BaseChangeset
2159 from rhodecode.lib.vcs.backends.base import BaseChangeset
2160 if cs_cache is None:
2160 if cs_cache is None:
2161 # use no-cache version here
2161 # use no-cache version here
2162 scm_repo = self.scm_instance(cache=False, config=config)
2162 scm_repo = self.scm_instance(cache=False, config=config)
2163 if scm_repo:
2163 if scm_repo:
2164 cs_cache = scm_repo.get_commit(
2164 cs_cache = scm_repo.get_commit(
2165 pre_load=["author", "date", "message", "parents"])
2165 pre_load=["author", "date", "message", "parents"])
2166 else:
2166 else:
2167 cs_cache = EmptyCommit()
2167 cs_cache = EmptyCommit()
2168
2168
2169 if isinstance(cs_cache, BaseChangeset):
2169 if isinstance(cs_cache, BaseChangeset):
2170 cs_cache = cs_cache.__json__()
2170 cs_cache = cs_cache.__json__()
2171
2171
2172 def is_outdated(new_cs_cache):
2172 def is_outdated(new_cs_cache):
2173 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2173 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2174 new_cs_cache['revision'] != self.changeset_cache['revision']):
2174 new_cs_cache['revision'] != self.changeset_cache['revision']):
2175 return True
2175 return True
2176 return False
2176 return False
2177
2177
2178 # check if we have maybe already latest cached revision
2178 # check if we have maybe already latest cached revision
2179 if is_outdated(cs_cache) or not self.changeset_cache:
2179 if is_outdated(cs_cache) or not self.changeset_cache:
2180 _default = datetime.datetime.fromtimestamp(0)
2180 _default = datetime.datetime.fromtimestamp(0)
2181 last_change = cs_cache.get('date') or _default
2181 last_change = cs_cache.get('date') or _default
2182 log.debug('updated repo %s with new cs cache %s',
2182 log.debug('updated repo %s with new cs cache %s',
2183 self.repo_name, cs_cache)
2183 self.repo_name, cs_cache)
2184 self.updated_on = last_change
2184 self.updated_on = last_change
2185 self.changeset_cache = cs_cache
2185 self.changeset_cache = cs_cache
2186 Session().add(self)
2186 Session().add(self)
2187 Session().commit()
2187 Session().commit()
2188 else:
2188 else:
2189 log.debug('Skipping update_commit_cache for repo:`%s` '
2189 log.debug('Skipping update_commit_cache for repo:`%s` '
2190 'commit already with latest changes', self.repo_name)
2190 'commit already with latest changes', self.repo_name)
2191
2191
2192 @property
2192 @property
2193 def tip(self):
2193 def tip(self):
2194 return self.get_commit('tip')
2194 return self.get_commit('tip')
2195
2195
2196 @property
2196 @property
2197 def author(self):
2197 def author(self):
2198 return self.tip.author
2198 return self.tip.author
2199
2199
2200 @property
2200 @property
2201 def last_change(self):
2201 def last_change(self):
2202 return self.scm_instance().last_change
2202 return self.scm_instance().last_change
2203
2203
2204 def get_comments(self, revisions=None):
2204 def get_comments(self, revisions=None):
2205 """
2205 """
2206 Returns comments for this repository grouped by revisions
2206 Returns comments for this repository grouped by revisions
2207
2207
2208 :param revisions: filter query by revisions only
2208 :param revisions: filter query by revisions only
2209 """
2209 """
2210 cmts = ChangesetComment.query()\
2210 cmts = ChangesetComment.query()\
2211 .filter(ChangesetComment.repo == self)
2211 .filter(ChangesetComment.repo == self)
2212 if revisions:
2212 if revisions:
2213 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2213 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2214 grouped = collections.defaultdict(list)
2214 grouped = collections.defaultdict(list)
2215 for cmt in cmts.all():
2215 for cmt in cmts.all():
2216 grouped[cmt.revision].append(cmt)
2216 grouped[cmt.revision].append(cmt)
2217 return grouped
2217 return grouped
2218
2218
2219 def statuses(self, revisions=None):
2219 def statuses(self, revisions=None):
2220 """
2220 """
2221 Returns statuses for this repository
2221 Returns statuses for this repository
2222
2222
2223 :param revisions: list of revisions to get statuses for
2223 :param revisions: list of revisions to get statuses for
2224 """
2224 """
2225 statuses = ChangesetStatus.query()\
2225 statuses = ChangesetStatus.query()\
2226 .filter(ChangesetStatus.repo == self)\
2226 .filter(ChangesetStatus.repo == self)\
2227 .filter(ChangesetStatus.version == 0)
2227 .filter(ChangesetStatus.version == 0)
2228
2228
2229 if revisions:
2229 if revisions:
2230 # Try doing the filtering in chunks to avoid hitting limits
2230 # Try doing the filtering in chunks to avoid hitting limits
2231 size = 500
2231 size = 500
2232 status_results = []
2232 status_results = []
2233 for chunk in xrange(0, len(revisions), size):
2233 for chunk in xrange(0, len(revisions), size):
2234 status_results += statuses.filter(
2234 status_results += statuses.filter(
2235 ChangesetStatus.revision.in_(
2235 ChangesetStatus.revision.in_(
2236 revisions[chunk: chunk+size])
2236 revisions[chunk: chunk+size])
2237 ).all()
2237 ).all()
2238 else:
2238 else:
2239 status_results = statuses.all()
2239 status_results = statuses.all()
2240
2240
2241 grouped = {}
2241 grouped = {}
2242
2242
2243 # maybe we have open new pullrequest without a status?
2243 # maybe we have open new pullrequest without a status?
2244 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2244 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2245 status_lbl = ChangesetStatus.get_status_lbl(stat)
2245 status_lbl = ChangesetStatus.get_status_lbl(stat)
2246 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2246 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2247 for rev in pr.revisions:
2247 for rev in pr.revisions:
2248 pr_id = pr.pull_request_id
2248 pr_id = pr.pull_request_id
2249 pr_repo = pr.target_repo.repo_name
2249 pr_repo = pr.target_repo.repo_name
2250 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2250 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2251
2251
2252 for stat in status_results:
2252 for stat in status_results:
2253 pr_id = pr_repo = None
2253 pr_id = pr_repo = None
2254 if stat.pull_request:
2254 if stat.pull_request:
2255 pr_id = stat.pull_request.pull_request_id
2255 pr_id = stat.pull_request.pull_request_id
2256 pr_repo = stat.pull_request.target_repo.repo_name
2256 pr_repo = stat.pull_request.target_repo.repo_name
2257 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2257 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2258 pr_id, pr_repo]
2258 pr_id, pr_repo]
2259 return grouped
2259 return grouped
2260
2260
2261 # ==========================================================================
2261 # ==========================================================================
2262 # SCM CACHE INSTANCE
2262 # SCM CACHE INSTANCE
2263 # ==========================================================================
2263 # ==========================================================================
2264
2264
2265 def scm_instance(self, **kwargs):
2265 def scm_instance(self, **kwargs):
2266 import rhodecode
2266 import rhodecode
2267
2267
2268 # Passing a config will not hit the cache currently only used
2268 # Passing a config will not hit the cache currently only used
2269 # for repo2dbmapper
2269 # for repo2dbmapper
2270 config = kwargs.pop('config', None)
2270 config = kwargs.pop('config', None)
2271 cache = kwargs.pop('cache', None)
2271 cache = kwargs.pop('cache', None)
2272 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2272 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2273 # if cache is NOT defined use default global, else we have a full
2273 # if cache is NOT defined use default global, else we have a full
2274 # control over cache behaviour
2274 # control over cache behaviour
2275 if cache is None and full_cache and not config:
2275 if cache is None and full_cache and not config:
2276 return self._get_instance_cached()
2276 return self._get_instance_cached()
2277 return self._get_instance(cache=bool(cache), config=config)
2277 return self._get_instance(cache=bool(cache), config=config)
2278
2278
2279 def _get_instance_cached(self):
2279 def _get_instance_cached(self):
2280 @cache_region('long_term')
2280 @cache_region('long_term')
2281 def _get_repo(cache_key):
2281 def _get_repo(cache_key):
2282 return self._get_instance()
2282 return self._get_instance()
2283
2283
2284 invalidator_context = CacheKey.repo_context_cache(
2284 invalidator_context = CacheKey.repo_context_cache(
2285 _get_repo, self.repo_name, None, thread_scoped=True)
2285 _get_repo, self.repo_name, None, thread_scoped=True)
2286
2286
2287 with invalidator_context as context:
2287 with invalidator_context as context:
2288 context.invalidate()
2288 context.invalidate()
2289 repo = context.compute()
2289 repo = context.compute()
2290
2290
2291 return repo
2291 return repo
2292
2292
2293 def _get_instance(self, cache=True, config=None):
2293 def _get_instance(self, cache=True, config=None):
2294 config = config or self._config
2294 config = config or self._config
2295 custom_wire = {
2295 custom_wire = {
2296 'cache': cache # controls the vcs.remote cache
2296 'cache': cache # controls the vcs.remote cache
2297 }
2297 }
2298 repo = get_vcs_instance(
2298 repo = get_vcs_instance(
2299 repo_path=safe_str(self.repo_full_path),
2299 repo_path=safe_str(self.repo_full_path),
2300 config=config,
2300 config=config,
2301 with_wire=custom_wire,
2301 with_wire=custom_wire,
2302 create=False,
2302 create=False,
2303 _vcs_alias=self.repo_type)
2303 _vcs_alias=self.repo_type)
2304
2304
2305 return repo
2305 return repo
2306
2306
2307 def __json__(self):
2307 def __json__(self):
2308 return {'landing_rev': self.landing_rev}
2308 return {'landing_rev': self.landing_rev}
2309
2309
2310 def get_dict(self):
2310 def get_dict(self):
2311
2311
2312 # Since we transformed `repo_name` to a hybrid property, we need to
2312 # Since we transformed `repo_name` to a hybrid property, we need to
2313 # keep compatibility with the code which uses `repo_name` field.
2313 # keep compatibility with the code which uses `repo_name` field.
2314
2314
2315 result = super(Repository, self).get_dict()
2315 result = super(Repository, self).get_dict()
2316 result['repo_name'] = result.pop('_repo_name', None)
2316 result['repo_name'] = result.pop('_repo_name', None)
2317 return result
2317 return result
2318
2318
2319
2319
2320 class RepoGroup(Base, BaseModel):
2320 class RepoGroup(Base, BaseModel):
2321 __tablename__ = 'groups'
2321 __tablename__ = 'groups'
2322 __table_args__ = (
2322 __table_args__ = (
2323 UniqueConstraint('group_name', 'group_parent_id'),
2323 UniqueConstraint('group_name', 'group_parent_id'),
2324 CheckConstraint('group_id != group_parent_id'),
2324 CheckConstraint('group_id != group_parent_id'),
2325 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2325 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2326 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2326 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2327 )
2327 )
2328 __mapper_args__ = {'order_by': 'group_name'}
2328 __mapper_args__ = {'order_by': 'group_name'}
2329
2329
2330 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2330 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2331
2331
2332 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2332 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2333 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2333 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2334 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2334 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2335 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2335 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2336 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2336 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2337 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2337 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2338 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2338 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2339 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2339 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2340 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2340 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2341
2341
2342 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2342 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2343 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2343 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2344 parent_group = relationship('RepoGroup', remote_side=group_id)
2344 parent_group = relationship('RepoGroup', remote_side=group_id)
2345 user = relationship('User')
2345 user = relationship('User')
2346 integrations = relationship('Integration',
2346 integrations = relationship('Integration',
2347 cascade="all, delete, delete-orphan")
2347 cascade="all, delete, delete-orphan")
2348
2348
2349 def __init__(self, group_name='', parent_group=None):
2349 def __init__(self, group_name='', parent_group=None):
2350 self.group_name = group_name
2350 self.group_name = group_name
2351 self.parent_group = parent_group
2351 self.parent_group = parent_group
2352
2352
2353 def __unicode__(self):
2353 def __unicode__(self):
2354 return u"<%s('id:%s:%s')>" % (
2354 return u"<%s('id:%s:%s')>" % (
2355 self.__class__.__name__, self.group_id, self.group_name)
2355 self.__class__.__name__, self.group_id, self.group_name)
2356
2356
2357 @hybrid_property
2357 @hybrid_property
2358 def description_safe(self):
2358 def description_safe(self):
2359 from rhodecode.lib import helpers as h
2359 from rhodecode.lib import helpers as h
2360 return h.escape(self.group_description)
2360 return h.escape(self.group_description)
2361
2361
2362 @classmethod
2362 @classmethod
2363 def _generate_choice(cls, repo_group):
2363 def _generate_choice(cls, repo_group):
2364 from webhelpers.html import literal as _literal
2364 from webhelpers.html import literal as _literal
2365 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2365 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2366 return repo_group.group_id, _name(repo_group.full_path_splitted)
2366 return repo_group.group_id, _name(repo_group.full_path_splitted)
2367
2367
2368 @classmethod
2368 @classmethod
2369 def groups_choices(cls, groups=None, show_empty_group=True):
2369 def groups_choices(cls, groups=None, show_empty_group=True):
2370 if not groups:
2370 if not groups:
2371 groups = cls.query().all()
2371 groups = cls.query().all()
2372
2372
2373 repo_groups = []
2373 repo_groups = []
2374 if show_empty_group:
2374 if show_empty_group:
2375 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2375 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2376
2376
2377 repo_groups.extend([cls._generate_choice(x) for x in groups])
2377 repo_groups.extend([cls._generate_choice(x) for x in groups])
2378
2378
2379 repo_groups = sorted(
2379 repo_groups = sorted(
2380 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2380 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2381 return repo_groups
2381 return repo_groups
2382
2382
2383 @classmethod
2383 @classmethod
2384 def url_sep(cls):
2384 def url_sep(cls):
2385 return URL_SEP
2385 return URL_SEP
2386
2386
2387 @classmethod
2387 @classmethod
2388 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2388 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2389 if case_insensitive:
2389 if case_insensitive:
2390 gr = cls.query().filter(func.lower(cls.group_name)
2390 gr = cls.query().filter(func.lower(cls.group_name)
2391 == func.lower(group_name))
2391 == func.lower(group_name))
2392 else:
2392 else:
2393 gr = cls.query().filter(cls.group_name == group_name)
2393 gr = cls.query().filter(cls.group_name == group_name)
2394 if cache:
2394 if cache:
2395 name_key = _hash_key(group_name)
2395 name_key = _hash_key(group_name)
2396 gr = gr.options(
2396 gr = gr.options(
2397 FromCache("sql_cache_short", "get_group_%s" % name_key))
2397 FromCache("sql_cache_short", "get_group_%s" % name_key))
2398 return gr.scalar()
2398 return gr.scalar()
2399
2399
2400 @classmethod
2400 @classmethod
2401 def get_user_personal_repo_group(cls, user_id):
2401 def get_user_personal_repo_group(cls, user_id):
2402 user = User.get(user_id)
2402 user = User.get(user_id)
2403 if user.username == User.DEFAULT_USER:
2403 if user.username == User.DEFAULT_USER:
2404 return None
2404 return None
2405
2405
2406 return cls.query()\
2406 return cls.query()\
2407 .filter(cls.personal == true()) \
2407 .filter(cls.personal == true()) \
2408 .filter(cls.user == user).scalar()
2408 .filter(cls.user == user).scalar()
2409
2409
2410 @classmethod
2410 @classmethod
2411 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2411 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2412 case_insensitive=True):
2412 case_insensitive=True):
2413 q = RepoGroup.query()
2413 q = RepoGroup.query()
2414
2414
2415 if not isinstance(user_id, Optional):
2415 if not isinstance(user_id, Optional):
2416 q = q.filter(RepoGroup.user_id == user_id)
2416 q = q.filter(RepoGroup.user_id == user_id)
2417
2417
2418 if not isinstance(group_id, Optional):
2418 if not isinstance(group_id, Optional):
2419 q = q.filter(RepoGroup.group_parent_id == group_id)
2419 q = q.filter(RepoGroup.group_parent_id == group_id)
2420
2420
2421 if case_insensitive:
2421 if case_insensitive:
2422 q = q.order_by(func.lower(RepoGroup.group_name))
2422 q = q.order_by(func.lower(RepoGroup.group_name))
2423 else:
2423 else:
2424 q = q.order_by(RepoGroup.group_name)
2424 q = q.order_by(RepoGroup.group_name)
2425 return q.all()
2425 return q.all()
2426
2426
2427 @property
2427 @property
2428 def parents(self):
2428 def parents(self):
2429 parents_recursion_limit = 10
2429 parents_recursion_limit = 10
2430 groups = []
2430 groups = []
2431 if self.parent_group is None:
2431 if self.parent_group is None:
2432 return groups
2432 return groups
2433 cur_gr = self.parent_group
2433 cur_gr = self.parent_group
2434 groups.insert(0, cur_gr)
2434 groups.insert(0, cur_gr)
2435 cnt = 0
2435 cnt = 0
2436 while 1:
2436 while 1:
2437 cnt += 1
2437 cnt += 1
2438 gr = getattr(cur_gr, 'parent_group', None)
2438 gr = getattr(cur_gr, 'parent_group', None)
2439 cur_gr = cur_gr.parent_group
2439 cur_gr = cur_gr.parent_group
2440 if gr is None:
2440 if gr is None:
2441 break
2441 break
2442 if cnt == parents_recursion_limit:
2442 if cnt == parents_recursion_limit:
2443 # this will prevent accidental infinit loops
2443 # this will prevent accidental infinit loops
2444 log.error(('more than %s parents found for group %s, stopping '
2444 log.error(('more than %s parents found for group %s, stopping '
2445 'recursive parent fetching' % (parents_recursion_limit, self)))
2445 'recursive parent fetching' % (parents_recursion_limit, self)))
2446 break
2446 break
2447
2447
2448 groups.insert(0, gr)
2448 groups.insert(0, gr)
2449 return groups
2449 return groups
2450
2450
2451 @property
2451 @property
2452 def last_db_change(self):
2452 def last_db_change(self):
2453 return self.updated_on
2453 return self.updated_on
2454
2454
2455 @property
2455 @property
2456 def children(self):
2456 def children(self):
2457 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2457 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2458
2458
2459 @property
2459 @property
2460 def name(self):
2460 def name(self):
2461 return self.group_name.split(RepoGroup.url_sep())[-1]
2461 return self.group_name.split(RepoGroup.url_sep())[-1]
2462
2462
2463 @property
2463 @property
2464 def full_path(self):
2464 def full_path(self):
2465 return self.group_name
2465 return self.group_name
2466
2466
2467 @property
2467 @property
2468 def full_path_splitted(self):
2468 def full_path_splitted(self):
2469 return self.group_name.split(RepoGroup.url_sep())
2469 return self.group_name.split(RepoGroup.url_sep())
2470
2470
2471 @property
2471 @property
2472 def repositories(self):
2472 def repositories(self):
2473 return Repository.query()\
2473 return Repository.query()\
2474 .filter(Repository.group == self)\
2474 .filter(Repository.group == self)\
2475 .order_by(Repository.repo_name)
2475 .order_by(Repository.repo_name)
2476
2476
2477 @property
2477 @property
2478 def repositories_recursive_count(self):
2478 def repositories_recursive_count(self):
2479 cnt = self.repositories.count()
2479 cnt = self.repositories.count()
2480
2480
2481 def children_count(group):
2481 def children_count(group):
2482 cnt = 0
2482 cnt = 0
2483 for child in group.children:
2483 for child in group.children:
2484 cnt += child.repositories.count()
2484 cnt += child.repositories.count()
2485 cnt += children_count(child)
2485 cnt += children_count(child)
2486 return cnt
2486 return cnt
2487
2487
2488 return cnt + children_count(self)
2488 return cnt + children_count(self)
2489
2489
2490 def _recursive_objects(self, include_repos=True):
2490 def _recursive_objects(self, include_repos=True):
2491 all_ = []
2491 all_ = []
2492
2492
2493 def _get_members(root_gr):
2493 def _get_members(root_gr):
2494 if include_repos:
2494 if include_repos:
2495 for r in root_gr.repositories:
2495 for r in root_gr.repositories:
2496 all_.append(r)
2496 all_.append(r)
2497 childs = root_gr.children.all()
2497 childs = root_gr.children.all()
2498 if childs:
2498 if childs:
2499 for gr in childs:
2499 for gr in childs:
2500 all_.append(gr)
2500 all_.append(gr)
2501 _get_members(gr)
2501 _get_members(gr)
2502
2502
2503 _get_members(self)
2503 _get_members(self)
2504 return [self] + all_
2504 return [self] + all_
2505
2505
2506 def recursive_groups_and_repos(self):
2506 def recursive_groups_and_repos(self):
2507 """
2507 """
2508 Recursive return all groups, with repositories in those groups
2508 Recursive return all groups, with repositories in those groups
2509 """
2509 """
2510 return self._recursive_objects()
2510 return self._recursive_objects()
2511
2511
2512 def recursive_groups(self):
2512 def recursive_groups(self):
2513 """
2513 """
2514 Returns all children groups for this group including children of children
2514 Returns all children groups for this group including children of children
2515 """
2515 """
2516 return self._recursive_objects(include_repos=False)
2516 return self._recursive_objects(include_repos=False)
2517
2517
2518 def get_new_name(self, group_name):
2518 def get_new_name(self, group_name):
2519 """
2519 """
2520 returns new full group name based on parent and new name
2520 returns new full group name based on parent and new name
2521
2521
2522 :param group_name:
2522 :param group_name:
2523 """
2523 """
2524 path_prefix = (self.parent_group.full_path_splitted if
2524 path_prefix = (self.parent_group.full_path_splitted if
2525 self.parent_group else [])
2525 self.parent_group else [])
2526 return RepoGroup.url_sep().join(path_prefix + [group_name])
2526 return RepoGroup.url_sep().join(path_prefix + [group_name])
2527
2527
2528 def permissions(self, with_admins=True, with_owner=True):
2528 def permissions(self, with_admins=True, with_owner=True):
2529 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2529 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2530 q = q.options(joinedload(UserRepoGroupToPerm.group),
2530 q = q.options(joinedload(UserRepoGroupToPerm.group),
2531 joinedload(UserRepoGroupToPerm.user),
2531 joinedload(UserRepoGroupToPerm.user),
2532 joinedload(UserRepoGroupToPerm.permission),)
2532 joinedload(UserRepoGroupToPerm.permission),)
2533
2533
2534 # get owners and admins and permissions. We do a trick of re-writing
2534 # get owners and admins and permissions. We do a trick of re-writing
2535 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2535 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2536 # has a global reference and changing one object propagates to all
2536 # has a global reference and changing one object propagates to all
2537 # others. This means if admin is also an owner admin_row that change
2537 # others. This means if admin is also an owner admin_row that change
2538 # would propagate to both objects
2538 # would propagate to both objects
2539 perm_rows = []
2539 perm_rows = []
2540 for _usr in q.all():
2540 for _usr in q.all():
2541 usr = AttributeDict(_usr.user.get_dict())
2541 usr = AttributeDict(_usr.user.get_dict())
2542 usr.permission = _usr.permission.permission_name
2542 usr.permission = _usr.permission.permission_name
2543 perm_rows.append(usr)
2543 perm_rows.append(usr)
2544
2544
2545 # filter the perm rows by 'default' first and then sort them by
2545 # filter the perm rows by 'default' first and then sort them by
2546 # admin,write,read,none permissions sorted again alphabetically in
2546 # admin,write,read,none permissions sorted again alphabetically in
2547 # each group
2547 # each group
2548 perm_rows = sorted(perm_rows, key=display_user_sort)
2548 perm_rows = sorted(perm_rows, key=display_user_sort)
2549
2549
2550 _admin_perm = 'group.admin'
2550 _admin_perm = 'group.admin'
2551 owner_row = []
2551 owner_row = []
2552 if with_owner:
2552 if with_owner:
2553 usr = AttributeDict(self.user.get_dict())
2553 usr = AttributeDict(self.user.get_dict())
2554 usr.owner_row = True
2554 usr.owner_row = True
2555 usr.permission = _admin_perm
2555 usr.permission = _admin_perm
2556 owner_row.append(usr)
2556 owner_row.append(usr)
2557
2557
2558 super_admin_rows = []
2558 super_admin_rows = []
2559 if with_admins:
2559 if with_admins:
2560 for usr in User.get_all_super_admins():
2560 for usr in User.get_all_super_admins():
2561 # if this admin is also owner, don't double the record
2561 # if this admin is also owner, don't double the record
2562 if usr.user_id == owner_row[0].user_id:
2562 if usr.user_id == owner_row[0].user_id:
2563 owner_row[0].admin_row = True
2563 owner_row[0].admin_row = True
2564 else:
2564 else:
2565 usr = AttributeDict(usr.get_dict())
2565 usr = AttributeDict(usr.get_dict())
2566 usr.admin_row = True
2566 usr.admin_row = True
2567 usr.permission = _admin_perm
2567 usr.permission = _admin_perm
2568 super_admin_rows.append(usr)
2568 super_admin_rows.append(usr)
2569
2569
2570 return super_admin_rows + owner_row + perm_rows
2570 return super_admin_rows + owner_row + perm_rows
2571
2571
2572 def permission_user_groups(self):
2572 def permission_user_groups(self):
2573 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2573 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2574 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2574 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2575 joinedload(UserGroupRepoGroupToPerm.users_group),
2575 joinedload(UserGroupRepoGroupToPerm.users_group),
2576 joinedload(UserGroupRepoGroupToPerm.permission),)
2576 joinedload(UserGroupRepoGroupToPerm.permission),)
2577
2577
2578 perm_rows = []
2578 perm_rows = []
2579 for _user_group in q.all():
2579 for _user_group in q.all():
2580 usr = AttributeDict(_user_group.users_group.get_dict())
2580 usr = AttributeDict(_user_group.users_group.get_dict())
2581 usr.permission = _user_group.permission.permission_name
2581 usr.permission = _user_group.permission.permission_name
2582 perm_rows.append(usr)
2582 perm_rows.append(usr)
2583
2583
2584 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2584 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2585 return perm_rows
2585 return perm_rows
2586
2586
2587 def get_api_data(self):
2587 def get_api_data(self):
2588 """
2588 """
2589 Common function for generating api data
2589 Common function for generating api data
2590
2590
2591 """
2591 """
2592 group = self
2592 group = self
2593 data = {
2593 data = {
2594 'group_id': group.group_id,
2594 'group_id': group.group_id,
2595 'group_name': group.group_name,
2595 'group_name': group.group_name,
2596 'group_description': group.description_safe,
2596 'group_description': group.description_safe,
2597 'parent_group': group.parent_group.group_name if group.parent_group else None,
2597 'parent_group': group.parent_group.group_name if group.parent_group else None,
2598 'repositories': [x.repo_name for x in group.repositories],
2598 'repositories': [x.repo_name for x in group.repositories],
2599 'owner': group.user.username,
2599 'owner': group.user.username,
2600 }
2600 }
2601 return data
2601 return data
2602
2602
2603
2603
2604 class Permission(Base, BaseModel):
2604 class Permission(Base, BaseModel):
2605 __tablename__ = 'permissions'
2605 __tablename__ = 'permissions'
2606 __table_args__ = (
2606 __table_args__ = (
2607 Index('p_perm_name_idx', 'permission_name'),
2607 Index('p_perm_name_idx', 'permission_name'),
2608 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2608 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2609 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2609 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2610 )
2610 )
2611 PERMS = [
2611 PERMS = [
2612 ('hg.admin', _('RhodeCode Super Administrator')),
2612 ('hg.admin', _('RhodeCode Super Administrator')),
2613
2613
2614 ('repository.none', _('Repository no access')),
2614 ('repository.none', _('Repository no access')),
2615 ('repository.read', _('Repository read access')),
2615 ('repository.read', _('Repository read access')),
2616 ('repository.write', _('Repository write access')),
2616 ('repository.write', _('Repository write access')),
2617 ('repository.admin', _('Repository admin access')),
2617 ('repository.admin', _('Repository admin access')),
2618
2618
2619 ('group.none', _('Repository group no access')),
2619 ('group.none', _('Repository group no access')),
2620 ('group.read', _('Repository group read access')),
2620 ('group.read', _('Repository group read access')),
2621 ('group.write', _('Repository group write access')),
2621 ('group.write', _('Repository group write access')),
2622 ('group.admin', _('Repository group admin access')),
2622 ('group.admin', _('Repository group admin access')),
2623
2623
2624 ('usergroup.none', _('User group no access')),
2624 ('usergroup.none', _('User group no access')),
2625 ('usergroup.read', _('User group read access')),
2625 ('usergroup.read', _('User group read access')),
2626 ('usergroup.write', _('User group write access')),
2626 ('usergroup.write', _('User group write access')),
2627 ('usergroup.admin', _('User group admin access')),
2627 ('usergroup.admin', _('User group admin access')),
2628
2628
2629 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2629 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2630 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2630 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2631
2631
2632 ('hg.usergroup.create.false', _('User Group creation disabled')),
2632 ('hg.usergroup.create.false', _('User Group creation disabled')),
2633 ('hg.usergroup.create.true', _('User Group creation enabled')),
2633 ('hg.usergroup.create.true', _('User Group creation enabled')),
2634
2634
2635 ('hg.create.none', _('Repository creation disabled')),
2635 ('hg.create.none', _('Repository creation disabled')),
2636 ('hg.create.repository', _('Repository creation enabled')),
2636 ('hg.create.repository', _('Repository creation enabled')),
2637 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2637 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2638 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2638 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2639
2639
2640 ('hg.fork.none', _('Repository forking disabled')),
2640 ('hg.fork.none', _('Repository forking disabled')),
2641 ('hg.fork.repository', _('Repository forking enabled')),
2641 ('hg.fork.repository', _('Repository forking enabled')),
2642
2642
2643 ('hg.register.none', _('Registration disabled')),
2643 ('hg.register.none', _('Registration disabled')),
2644 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2644 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2645 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2645 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2646
2646
2647 ('hg.password_reset.enabled', _('Password reset enabled')),
2647 ('hg.password_reset.enabled', _('Password reset enabled')),
2648 ('hg.password_reset.hidden', _('Password reset hidden')),
2648 ('hg.password_reset.hidden', _('Password reset hidden')),
2649 ('hg.password_reset.disabled', _('Password reset disabled')),
2649 ('hg.password_reset.disabled', _('Password reset disabled')),
2650
2650
2651 ('hg.extern_activate.manual', _('Manual activation of external account')),
2651 ('hg.extern_activate.manual', _('Manual activation of external account')),
2652 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2652 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2653
2653
2654 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2654 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2655 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2655 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2656 ]
2656 ]
2657
2657
2658 # definition of system default permissions for DEFAULT user
2658 # definition of system default permissions for DEFAULT user
2659 DEFAULT_USER_PERMISSIONS = [
2659 DEFAULT_USER_PERMISSIONS = [
2660 'repository.read',
2660 'repository.read',
2661 'group.read',
2661 'group.read',
2662 'usergroup.read',
2662 'usergroup.read',
2663 'hg.create.repository',
2663 'hg.create.repository',
2664 'hg.repogroup.create.false',
2664 'hg.repogroup.create.false',
2665 'hg.usergroup.create.false',
2665 'hg.usergroup.create.false',
2666 'hg.create.write_on_repogroup.true',
2666 'hg.create.write_on_repogroup.true',
2667 'hg.fork.repository',
2667 'hg.fork.repository',
2668 'hg.register.manual_activate',
2668 'hg.register.manual_activate',
2669 'hg.password_reset.enabled',
2669 'hg.password_reset.enabled',
2670 'hg.extern_activate.auto',
2670 'hg.extern_activate.auto',
2671 'hg.inherit_default_perms.true',
2671 'hg.inherit_default_perms.true',
2672 ]
2672 ]
2673
2673
2674 # defines which permissions are more important higher the more important
2674 # defines which permissions are more important higher the more important
2675 # Weight defines which permissions are more important.
2675 # Weight defines which permissions are more important.
2676 # The higher number the more important.
2676 # The higher number the more important.
2677 PERM_WEIGHTS = {
2677 PERM_WEIGHTS = {
2678 'repository.none': 0,
2678 'repository.none': 0,
2679 'repository.read': 1,
2679 'repository.read': 1,
2680 'repository.write': 3,
2680 'repository.write': 3,
2681 'repository.admin': 4,
2681 'repository.admin': 4,
2682
2682
2683 'group.none': 0,
2683 'group.none': 0,
2684 'group.read': 1,
2684 'group.read': 1,
2685 'group.write': 3,
2685 'group.write': 3,
2686 'group.admin': 4,
2686 'group.admin': 4,
2687
2687
2688 'usergroup.none': 0,
2688 'usergroup.none': 0,
2689 'usergroup.read': 1,
2689 'usergroup.read': 1,
2690 'usergroup.write': 3,
2690 'usergroup.write': 3,
2691 'usergroup.admin': 4,
2691 'usergroup.admin': 4,
2692
2692
2693 'hg.repogroup.create.false': 0,
2693 'hg.repogroup.create.false': 0,
2694 'hg.repogroup.create.true': 1,
2694 'hg.repogroup.create.true': 1,
2695
2695
2696 'hg.usergroup.create.false': 0,
2696 'hg.usergroup.create.false': 0,
2697 'hg.usergroup.create.true': 1,
2697 'hg.usergroup.create.true': 1,
2698
2698
2699 'hg.fork.none': 0,
2699 'hg.fork.none': 0,
2700 'hg.fork.repository': 1,
2700 'hg.fork.repository': 1,
2701 'hg.create.none': 0,
2701 'hg.create.none': 0,
2702 'hg.create.repository': 1
2702 'hg.create.repository': 1
2703 }
2703 }
2704
2704
2705 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2705 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2706 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2706 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2707 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2707 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2708
2708
2709 def __unicode__(self):
2709 def __unicode__(self):
2710 return u"<%s('%s:%s')>" % (
2710 return u"<%s('%s:%s')>" % (
2711 self.__class__.__name__, self.permission_id, self.permission_name
2711 self.__class__.__name__, self.permission_id, self.permission_name
2712 )
2712 )
2713
2713
2714 @classmethod
2714 @classmethod
2715 def get_by_key(cls, key):
2715 def get_by_key(cls, key):
2716 return cls.query().filter(cls.permission_name == key).scalar()
2716 return cls.query().filter(cls.permission_name == key).scalar()
2717
2717
2718 @classmethod
2718 @classmethod
2719 def get_default_repo_perms(cls, user_id, repo_id=None):
2719 def get_default_repo_perms(cls, user_id, repo_id=None):
2720 q = Session().query(UserRepoToPerm, Repository, Permission)\
2720 q = Session().query(UserRepoToPerm, Repository, Permission)\
2721 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2721 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2722 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2722 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2723 .filter(UserRepoToPerm.user_id == user_id)
2723 .filter(UserRepoToPerm.user_id == user_id)
2724 if repo_id:
2724 if repo_id:
2725 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2725 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2726 return q.all()
2726 return q.all()
2727
2727
2728 @classmethod
2728 @classmethod
2729 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2729 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2730 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2730 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2731 .join(
2731 .join(
2732 Permission,
2732 Permission,
2733 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2733 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2734 .join(
2734 .join(
2735 Repository,
2735 Repository,
2736 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2736 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2737 .join(
2737 .join(
2738 UserGroup,
2738 UserGroup,
2739 UserGroupRepoToPerm.users_group_id ==
2739 UserGroupRepoToPerm.users_group_id ==
2740 UserGroup.users_group_id)\
2740 UserGroup.users_group_id)\
2741 .join(
2741 .join(
2742 UserGroupMember,
2742 UserGroupMember,
2743 UserGroupRepoToPerm.users_group_id ==
2743 UserGroupRepoToPerm.users_group_id ==
2744 UserGroupMember.users_group_id)\
2744 UserGroupMember.users_group_id)\
2745 .filter(
2745 .filter(
2746 UserGroupMember.user_id == user_id,
2746 UserGroupMember.user_id == user_id,
2747 UserGroup.users_group_active == true())
2747 UserGroup.users_group_active == true())
2748 if repo_id:
2748 if repo_id:
2749 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2749 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2750 return q.all()
2750 return q.all()
2751
2751
2752 @classmethod
2752 @classmethod
2753 def get_default_group_perms(cls, user_id, repo_group_id=None):
2753 def get_default_group_perms(cls, user_id, repo_group_id=None):
2754 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2754 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2755 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2755 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2756 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2756 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2757 .filter(UserRepoGroupToPerm.user_id == user_id)
2757 .filter(UserRepoGroupToPerm.user_id == user_id)
2758 if repo_group_id:
2758 if repo_group_id:
2759 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2759 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2760 return q.all()
2760 return q.all()
2761
2761
2762 @classmethod
2762 @classmethod
2763 def get_default_group_perms_from_user_group(
2763 def get_default_group_perms_from_user_group(
2764 cls, user_id, repo_group_id=None):
2764 cls, user_id, repo_group_id=None):
2765 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2765 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2766 .join(
2766 .join(
2767 Permission,
2767 Permission,
2768 UserGroupRepoGroupToPerm.permission_id ==
2768 UserGroupRepoGroupToPerm.permission_id ==
2769 Permission.permission_id)\
2769 Permission.permission_id)\
2770 .join(
2770 .join(
2771 RepoGroup,
2771 RepoGroup,
2772 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2772 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2773 .join(
2773 .join(
2774 UserGroup,
2774 UserGroup,
2775 UserGroupRepoGroupToPerm.users_group_id ==
2775 UserGroupRepoGroupToPerm.users_group_id ==
2776 UserGroup.users_group_id)\
2776 UserGroup.users_group_id)\
2777 .join(
2777 .join(
2778 UserGroupMember,
2778 UserGroupMember,
2779 UserGroupRepoGroupToPerm.users_group_id ==
2779 UserGroupRepoGroupToPerm.users_group_id ==
2780 UserGroupMember.users_group_id)\
2780 UserGroupMember.users_group_id)\
2781 .filter(
2781 .filter(
2782 UserGroupMember.user_id == user_id,
2782 UserGroupMember.user_id == user_id,
2783 UserGroup.users_group_active == true())
2783 UserGroup.users_group_active == true())
2784 if repo_group_id:
2784 if repo_group_id:
2785 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2785 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2786 return q.all()
2786 return q.all()
2787
2787
2788 @classmethod
2788 @classmethod
2789 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2789 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2790 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2790 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2791 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2791 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2792 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2792 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2793 .filter(UserUserGroupToPerm.user_id == user_id)
2793 .filter(UserUserGroupToPerm.user_id == user_id)
2794 if user_group_id:
2794 if user_group_id:
2795 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2795 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2796 return q.all()
2796 return q.all()
2797
2797
2798 @classmethod
2798 @classmethod
2799 def get_default_user_group_perms_from_user_group(
2799 def get_default_user_group_perms_from_user_group(
2800 cls, user_id, user_group_id=None):
2800 cls, user_id, user_group_id=None):
2801 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2801 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2802 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2802 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2803 .join(
2803 .join(
2804 Permission,
2804 Permission,
2805 UserGroupUserGroupToPerm.permission_id ==
2805 UserGroupUserGroupToPerm.permission_id ==
2806 Permission.permission_id)\
2806 Permission.permission_id)\
2807 .join(
2807 .join(
2808 TargetUserGroup,
2808 TargetUserGroup,
2809 UserGroupUserGroupToPerm.target_user_group_id ==
2809 UserGroupUserGroupToPerm.target_user_group_id ==
2810 TargetUserGroup.users_group_id)\
2810 TargetUserGroup.users_group_id)\
2811 .join(
2811 .join(
2812 UserGroup,
2812 UserGroup,
2813 UserGroupUserGroupToPerm.user_group_id ==
2813 UserGroupUserGroupToPerm.user_group_id ==
2814 UserGroup.users_group_id)\
2814 UserGroup.users_group_id)\
2815 .join(
2815 .join(
2816 UserGroupMember,
2816 UserGroupMember,
2817 UserGroupUserGroupToPerm.user_group_id ==
2817 UserGroupUserGroupToPerm.user_group_id ==
2818 UserGroupMember.users_group_id)\
2818 UserGroupMember.users_group_id)\
2819 .filter(
2819 .filter(
2820 UserGroupMember.user_id == user_id,
2820 UserGroupMember.user_id == user_id,
2821 UserGroup.users_group_active == true())
2821 UserGroup.users_group_active == true())
2822 if user_group_id:
2822 if user_group_id:
2823 q = q.filter(
2823 q = q.filter(
2824 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2824 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2825
2825
2826 return q.all()
2826 return q.all()
2827
2827
2828
2828
2829 class UserRepoToPerm(Base, BaseModel):
2829 class UserRepoToPerm(Base, BaseModel):
2830 __tablename__ = 'repo_to_perm'
2830 __tablename__ = 'repo_to_perm'
2831 __table_args__ = (
2831 __table_args__ = (
2832 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2832 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2833 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2833 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2834 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2834 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2835 )
2835 )
2836 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2836 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2837 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2837 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2838 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2838 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2839 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2839 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2840
2840
2841 user = relationship('User')
2841 user = relationship('User')
2842 repository = relationship('Repository')
2842 repository = relationship('Repository')
2843 permission = relationship('Permission')
2843 permission = relationship('Permission')
2844
2844
2845 @classmethod
2845 @classmethod
2846 def create(cls, user, repository, permission):
2846 def create(cls, user, repository, permission):
2847 n = cls()
2847 n = cls()
2848 n.user = user
2848 n.user = user
2849 n.repository = repository
2849 n.repository = repository
2850 n.permission = permission
2850 n.permission = permission
2851 Session().add(n)
2851 Session().add(n)
2852 return n
2852 return n
2853
2853
2854 def __unicode__(self):
2854 def __unicode__(self):
2855 return u'<%s => %s >' % (self.user, self.repository)
2855 return u'<%s => %s >' % (self.user, self.repository)
2856
2856
2857
2857
2858 class UserUserGroupToPerm(Base, BaseModel):
2858 class UserUserGroupToPerm(Base, BaseModel):
2859 __tablename__ = 'user_user_group_to_perm'
2859 __tablename__ = 'user_user_group_to_perm'
2860 __table_args__ = (
2860 __table_args__ = (
2861 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2861 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2862 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2862 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2863 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2863 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2864 )
2864 )
2865 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2865 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2866 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2866 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2867 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2867 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2868 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2868 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2869
2869
2870 user = relationship('User')
2870 user = relationship('User')
2871 user_group = relationship('UserGroup')
2871 user_group = relationship('UserGroup')
2872 permission = relationship('Permission')
2872 permission = relationship('Permission')
2873
2873
2874 @classmethod
2874 @classmethod
2875 def create(cls, user, user_group, permission):
2875 def create(cls, user, user_group, permission):
2876 n = cls()
2876 n = cls()
2877 n.user = user
2877 n.user = user
2878 n.user_group = user_group
2878 n.user_group = user_group
2879 n.permission = permission
2879 n.permission = permission
2880 Session().add(n)
2880 Session().add(n)
2881 return n
2881 return n
2882
2882
2883 def __unicode__(self):
2883 def __unicode__(self):
2884 return u'<%s => %s >' % (self.user, self.user_group)
2884 return u'<%s => %s >' % (self.user, self.user_group)
2885
2885
2886
2886
2887 class UserToPerm(Base, BaseModel):
2887 class UserToPerm(Base, BaseModel):
2888 __tablename__ = 'user_to_perm'
2888 __tablename__ = 'user_to_perm'
2889 __table_args__ = (
2889 __table_args__ = (
2890 UniqueConstraint('user_id', 'permission_id'),
2890 UniqueConstraint('user_id', 'permission_id'),
2891 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2891 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2892 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2892 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2893 )
2893 )
2894 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2894 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2895 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2895 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2896 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2896 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2897
2897
2898 user = relationship('User')
2898 user = relationship('User')
2899 permission = relationship('Permission', lazy='joined')
2899 permission = relationship('Permission', lazy='joined')
2900
2900
2901 def __unicode__(self):
2901 def __unicode__(self):
2902 return u'<%s => %s >' % (self.user, self.permission)
2902 return u'<%s => %s >' % (self.user, self.permission)
2903
2903
2904
2904
2905 class UserGroupRepoToPerm(Base, BaseModel):
2905 class UserGroupRepoToPerm(Base, BaseModel):
2906 __tablename__ = 'users_group_repo_to_perm'
2906 __tablename__ = 'users_group_repo_to_perm'
2907 __table_args__ = (
2907 __table_args__ = (
2908 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2908 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2909 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2909 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2910 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2910 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2911 )
2911 )
2912 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2912 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2913 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2913 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2914 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2914 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2915 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2915 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2916
2916
2917 users_group = relationship('UserGroup')
2917 users_group = relationship('UserGroup')
2918 permission = relationship('Permission')
2918 permission = relationship('Permission')
2919 repository = relationship('Repository')
2919 repository = relationship('Repository')
2920
2920
2921 @classmethod
2921 @classmethod
2922 def create(cls, users_group, repository, permission):
2922 def create(cls, users_group, repository, permission):
2923 n = cls()
2923 n = cls()
2924 n.users_group = users_group
2924 n.users_group = users_group
2925 n.repository = repository
2925 n.repository = repository
2926 n.permission = permission
2926 n.permission = permission
2927 Session().add(n)
2927 Session().add(n)
2928 return n
2928 return n
2929
2929
2930 def __unicode__(self):
2930 def __unicode__(self):
2931 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2931 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2932
2932
2933
2933
2934 class UserGroupUserGroupToPerm(Base, BaseModel):
2934 class UserGroupUserGroupToPerm(Base, BaseModel):
2935 __tablename__ = 'user_group_user_group_to_perm'
2935 __tablename__ = 'user_group_user_group_to_perm'
2936 __table_args__ = (
2936 __table_args__ = (
2937 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2937 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2938 CheckConstraint('target_user_group_id != user_group_id'),
2938 CheckConstraint('target_user_group_id != user_group_id'),
2939 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2939 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2940 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2940 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2941 )
2941 )
2942 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2942 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2943 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2943 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2944 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2944 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2945 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2945 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2946
2946
2947 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2947 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2948 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2948 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2949 permission = relationship('Permission')
2949 permission = relationship('Permission')
2950
2950
2951 @classmethod
2951 @classmethod
2952 def create(cls, target_user_group, user_group, permission):
2952 def create(cls, target_user_group, user_group, permission):
2953 n = cls()
2953 n = cls()
2954 n.target_user_group = target_user_group
2954 n.target_user_group = target_user_group
2955 n.user_group = user_group
2955 n.user_group = user_group
2956 n.permission = permission
2956 n.permission = permission
2957 Session().add(n)
2957 Session().add(n)
2958 return n
2958 return n
2959
2959
2960 def __unicode__(self):
2960 def __unicode__(self):
2961 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2961 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2962
2962
2963
2963
2964 class UserGroupToPerm(Base, BaseModel):
2964 class UserGroupToPerm(Base, BaseModel):
2965 __tablename__ = 'users_group_to_perm'
2965 __tablename__ = 'users_group_to_perm'
2966 __table_args__ = (
2966 __table_args__ = (
2967 UniqueConstraint('users_group_id', 'permission_id',),
2967 UniqueConstraint('users_group_id', 'permission_id',),
2968 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2968 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2969 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2969 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2970 )
2970 )
2971 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2971 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2972 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2972 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2973 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2973 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2974
2974
2975 users_group = relationship('UserGroup')
2975 users_group = relationship('UserGroup')
2976 permission = relationship('Permission')
2976 permission = relationship('Permission')
2977
2977
2978
2978
2979 class UserRepoGroupToPerm(Base, BaseModel):
2979 class UserRepoGroupToPerm(Base, BaseModel):
2980 __tablename__ = 'user_repo_group_to_perm'
2980 __tablename__ = 'user_repo_group_to_perm'
2981 __table_args__ = (
2981 __table_args__ = (
2982 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2982 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2983 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2983 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2984 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2984 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2985 )
2985 )
2986
2986
2987 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2987 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2988 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2988 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2989 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2989 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2990 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2990 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2991
2991
2992 user = relationship('User')
2992 user = relationship('User')
2993 group = relationship('RepoGroup')
2993 group = relationship('RepoGroup')
2994 permission = relationship('Permission')
2994 permission = relationship('Permission')
2995
2995
2996 @classmethod
2996 @classmethod
2997 def create(cls, user, repository_group, permission):
2997 def create(cls, user, repository_group, permission):
2998 n = cls()
2998 n = cls()
2999 n.user = user
2999 n.user = user
3000 n.group = repository_group
3000 n.group = repository_group
3001 n.permission = permission
3001 n.permission = permission
3002 Session().add(n)
3002 Session().add(n)
3003 return n
3003 return n
3004
3004
3005
3005
3006 class UserGroupRepoGroupToPerm(Base, BaseModel):
3006 class UserGroupRepoGroupToPerm(Base, BaseModel):
3007 __tablename__ = 'users_group_repo_group_to_perm'
3007 __tablename__ = 'users_group_repo_group_to_perm'
3008 __table_args__ = (
3008 __table_args__ = (
3009 UniqueConstraint('users_group_id', 'group_id'),
3009 UniqueConstraint('users_group_id', 'group_id'),
3010 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3010 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3011 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3011 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3012 )
3012 )
3013
3013
3014 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)
3014 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)
3015 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3015 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3016 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3016 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3017 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3017 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3018
3018
3019 users_group = relationship('UserGroup')
3019 users_group = relationship('UserGroup')
3020 permission = relationship('Permission')
3020 permission = relationship('Permission')
3021 group = relationship('RepoGroup')
3021 group = relationship('RepoGroup')
3022
3022
3023 @classmethod
3023 @classmethod
3024 def create(cls, user_group, repository_group, permission):
3024 def create(cls, user_group, repository_group, permission):
3025 n = cls()
3025 n = cls()
3026 n.users_group = user_group
3026 n.users_group = user_group
3027 n.group = repository_group
3027 n.group = repository_group
3028 n.permission = permission
3028 n.permission = permission
3029 Session().add(n)
3029 Session().add(n)
3030 return n
3030 return n
3031
3031
3032 def __unicode__(self):
3032 def __unicode__(self):
3033 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3033 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3034
3034
3035
3035
3036 class Statistics(Base, BaseModel):
3036 class Statistics(Base, BaseModel):
3037 __tablename__ = 'statistics'
3037 __tablename__ = 'statistics'
3038 __table_args__ = (
3038 __table_args__ = (
3039 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3039 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3040 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3040 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3041 )
3041 )
3042 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3042 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3043 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3043 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3044 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3044 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3045 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3045 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3046 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3046 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3047 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3047 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3048
3048
3049 repository = relationship('Repository', single_parent=True)
3049 repository = relationship('Repository', single_parent=True)
3050
3050
3051
3051
3052 class UserFollowing(Base, BaseModel):
3052 class UserFollowing(Base, BaseModel):
3053 __tablename__ = 'user_followings'
3053 __tablename__ = 'user_followings'
3054 __table_args__ = (
3054 __table_args__ = (
3055 UniqueConstraint('user_id', 'follows_repository_id'),
3055 UniqueConstraint('user_id', 'follows_repository_id'),
3056 UniqueConstraint('user_id', 'follows_user_id'),
3056 UniqueConstraint('user_id', 'follows_user_id'),
3057 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3057 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3058 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3058 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3059 )
3059 )
3060
3060
3061 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3061 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3062 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3062 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3063 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3063 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3064 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3064 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3065 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3065 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3066
3066
3067 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3067 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3068
3068
3069 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3069 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3070 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3070 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3071
3071
3072 @classmethod
3072 @classmethod
3073 def get_repo_followers(cls, repo_id):
3073 def get_repo_followers(cls, repo_id):
3074 return cls.query().filter(cls.follows_repo_id == repo_id)
3074 return cls.query().filter(cls.follows_repo_id == repo_id)
3075
3075
3076
3076
3077 class CacheKey(Base, BaseModel):
3077 class CacheKey(Base, BaseModel):
3078 __tablename__ = 'cache_invalidation'
3078 __tablename__ = 'cache_invalidation'
3079 __table_args__ = (
3079 __table_args__ = (
3080 UniqueConstraint('cache_key'),
3080 UniqueConstraint('cache_key'),
3081 Index('key_idx', 'cache_key'),
3081 Index('key_idx', 'cache_key'),
3082 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3082 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3083 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3083 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3084 )
3084 )
3085 CACHE_TYPE_ATOM = 'ATOM'
3085 CACHE_TYPE_ATOM = 'ATOM'
3086 CACHE_TYPE_RSS = 'RSS'
3086 CACHE_TYPE_RSS = 'RSS'
3087 CACHE_TYPE_README = 'README'
3087 CACHE_TYPE_README = 'README'
3088
3088
3089 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3089 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3090 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3090 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3091 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3091 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3092 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3092 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3093
3093
3094 def __init__(self, cache_key, cache_args=''):
3094 def __init__(self, cache_key, cache_args=''):
3095 self.cache_key = cache_key
3095 self.cache_key = cache_key
3096 self.cache_args = cache_args
3096 self.cache_args = cache_args
3097 self.cache_active = False
3097 self.cache_active = False
3098
3098
3099 def __unicode__(self):
3099 def __unicode__(self):
3100 return u"<%s('%s:%s[%s]')>" % (
3100 return u"<%s('%s:%s[%s]')>" % (
3101 self.__class__.__name__,
3101 self.__class__.__name__,
3102 self.cache_id, self.cache_key, self.cache_active)
3102 self.cache_id, self.cache_key, self.cache_active)
3103
3103
3104 def _cache_key_partition(self):
3104 def _cache_key_partition(self):
3105 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3105 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3106 return prefix, repo_name, suffix
3106 return prefix, repo_name, suffix
3107
3107
3108 def get_prefix(self):
3108 def get_prefix(self):
3109 """
3109 """
3110 Try to extract prefix from existing cache key. The key could consist
3110 Try to extract prefix from existing cache key. The key could consist
3111 of prefix, repo_name, suffix
3111 of prefix, repo_name, suffix
3112 """
3112 """
3113 # this returns prefix, repo_name, suffix
3113 # this returns prefix, repo_name, suffix
3114 return self._cache_key_partition()[0]
3114 return self._cache_key_partition()[0]
3115
3115
3116 def get_suffix(self):
3116 def get_suffix(self):
3117 """
3117 """
3118 get suffix that might have been used in _get_cache_key to
3118 get suffix that might have been used in _get_cache_key to
3119 generate self.cache_key. Only used for informational purposes
3119 generate self.cache_key. Only used for informational purposes
3120 in repo_edit.mako.
3120 in repo_edit.mako.
3121 """
3121 """
3122 # prefix, repo_name, suffix
3122 # prefix, repo_name, suffix
3123 return self._cache_key_partition()[2]
3123 return self._cache_key_partition()[2]
3124
3124
3125 @classmethod
3125 @classmethod
3126 def delete_all_cache(cls):
3126 def delete_all_cache(cls):
3127 """
3127 """
3128 Delete all cache keys from database.
3128 Delete all cache keys from database.
3129 Should only be run when all instances are down and all entries
3129 Should only be run when all instances are down and all entries
3130 thus stale.
3130 thus stale.
3131 """
3131 """
3132 cls.query().delete()
3132 cls.query().delete()
3133 Session().commit()
3133 Session().commit()
3134
3134
3135 @classmethod
3135 @classmethod
3136 def get_cache_key(cls, repo_name, cache_type):
3136 def get_cache_key(cls, repo_name, cache_type):
3137 """
3137 """
3138
3138
3139 Generate a cache key for this process of RhodeCode instance.
3139 Generate a cache key for this process of RhodeCode instance.
3140 Prefix most likely will be process id or maybe explicitly set
3140 Prefix most likely will be process id or maybe explicitly set
3141 instance_id from .ini file.
3141 instance_id from .ini file.
3142 """
3142 """
3143 import rhodecode
3143 import rhodecode
3144 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3144 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3145
3145
3146 repo_as_unicode = safe_unicode(repo_name)
3146 repo_as_unicode = safe_unicode(repo_name)
3147 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3147 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3148 if cache_type else repo_as_unicode
3148 if cache_type else repo_as_unicode
3149
3149
3150 return u'{}{}'.format(prefix, key)
3150 return u'{}{}'.format(prefix, key)
3151
3151
3152 @classmethod
3152 @classmethod
3153 def set_invalidate(cls, repo_name, delete=False):
3153 def set_invalidate(cls, repo_name, delete=False):
3154 """
3154 """
3155 Mark all caches of a repo as invalid in the database.
3155 Mark all caches of a repo as invalid in the database.
3156 """
3156 """
3157
3157
3158 try:
3158 try:
3159 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3159 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3160 if delete:
3160 if delete:
3161 log.debug('cache objects deleted for repo %s',
3161 log.debug('cache objects deleted for repo %s',
3162 safe_str(repo_name))
3162 safe_str(repo_name))
3163 qry.delete()
3163 qry.delete()
3164 else:
3164 else:
3165 log.debug('cache objects marked as invalid for repo %s',
3165 log.debug('cache objects marked as invalid for repo %s',
3166 safe_str(repo_name))
3166 safe_str(repo_name))
3167 qry.update({"cache_active": False})
3167 qry.update({"cache_active": False})
3168
3168
3169 Session().commit()
3169 Session().commit()
3170 except Exception:
3170 except Exception:
3171 log.exception(
3171 log.exception(
3172 'Cache key invalidation failed for repository %s',
3172 'Cache key invalidation failed for repository %s',
3173 safe_str(repo_name))
3173 safe_str(repo_name))
3174 Session().rollback()
3174 Session().rollback()
3175
3175
3176 @classmethod
3176 @classmethod
3177 def get_active_cache(cls, cache_key):
3177 def get_active_cache(cls, cache_key):
3178 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3178 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3179 if inv_obj:
3179 if inv_obj:
3180 return inv_obj
3180 return inv_obj
3181 return None
3181 return None
3182
3182
3183 @classmethod
3183 @classmethod
3184 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3184 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3185 thread_scoped=False):
3185 thread_scoped=False):
3186 """
3186 """
3187 @cache_region('long_term')
3187 @cache_region('long_term')
3188 def _heavy_calculation(cache_key):
3188 def _heavy_calculation(cache_key):
3189 return 'result'
3189 return 'result'
3190
3190
3191 cache_context = CacheKey.repo_context_cache(
3191 cache_context = CacheKey.repo_context_cache(
3192 _heavy_calculation, repo_name, cache_type)
3192 _heavy_calculation, repo_name, cache_type)
3193
3193
3194 with cache_context as context:
3194 with cache_context as context:
3195 context.invalidate()
3195 context.invalidate()
3196 computed = context.compute()
3196 computed = context.compute()
3197
3197
3198 assert computed == 'result'
3198 assert computed == 'result'
3199 """
3199 """
3200 from rhodecode.lib import caches
3200 from rhodecode.lib import caches
3201 return caches.InvalidationContext(
3201 return caches.InvalidationContext(
3202 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3202 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3203
3203
3204
3204
3205 class ChangesetComment(Base, BaseModel):
3205 class ChangesetComment(Base, BaseModel):
3206 __tablename__ = 'changeset_comments'
3206 __tablename__ = 'changeset_comments'
3207 __table_args__ = (
3207 __table_args__ = (
3208 Index('cc_revision_idx', 'revision'),
3208 Index('cc_revision_idx', 'revision'),
3209 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3209 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3210 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3210 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3211 )
3211 )
3212
3212
3213 COMMENT_OUTDATED = u'comment_outdated'
3213 COMMENT_OUTDATED = u'comment_outdated'
3214 COMMENT_TYPE_NOTE = u'note'
3214 COMMENT_TYPE_NOTE = u'note'
3215 COMMENT_TYPE_TODO = u'todo'
3215 COMMENT_TYPE_TODO = u'todo'
3216 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3216 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3217
3217
3218 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3218 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3219 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3219 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3220 revision = Column('revision', String(40), nullable=True)
3220 revision = Column('revision', String(40), nullable=True)
3221 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3221 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3222 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3222 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3223 line_no = Column('line_no', Unicode(10), nullable=True)
3223 line_no = Column('line_no', Unicode(10), nullable=True)
3224 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3224 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3225 f_path = Column('f_path', Unicode(1000), nullable=True)
3225 f_path = Column('f_path', Unicode(1000), nullable=True)
3226 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3226 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3227 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3227 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3228 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3228 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3229 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3229 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3230 renderer = Column('renderer', Unicode(64), nullable=True)
3230 renderer = Column('renderer', Unicode(64), nullable=True)
3231 display_state = Column('display_state', Unicode(128), nullable=True)
3231 display_state = Column('display_state', Unicode(128), nullable=True)
3232
3232
3233 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3233 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3234 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3234 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3235 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3235 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3236 author = relationship('User', lazy='joined')
3236 author = relationship('User', lazy='joined')
3237 repo = relationship('Repository')
3237 repo = relationship('Repository')
3238 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3238 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3239 pull_request = relationship('PullRequest', lazy='joined')
3239 pull_request = relationship('PullRequest', lazy='joined')
3240 pull_request_version = relationship('PullRequestVersion')
3240 pull_request_version = relationship('PullRequestVersion')
3241
3241
3242 @classmethod
3242 @classmethod
3243 def get_users(cls, revision=None, pull_request_id=None):
3243 def get_users(cls, revision=None, pull_request_id=None):
3244 """
3244 """
3245 Returns user associated with this ChangesetComment. ie those
3245 Returns user associated with this ChangesetComment. ie those
3246 who actually commented
3246 who actually commented
3247
3247
3248 :param cls:
3248 :param cls:
3249 :param revision:
3249 :param revision:
3250 """
3250 """
3251 q = Session().query(User)\
3251 q = Session().query(User)\
3252 .join(ChangesetComment.author)
3252 .join(ChangesetComment.author)
3253 if revision:
3253 if revision:
3254 q = q.filter(cls.revision == revision)
3254 q = q.filter(cls.revision == revision)
3255 elif pull_request_id:
3255 elif pull_request_id:
3256 q = q.filter(cls.pull_request_id == pull_request_id)
3256 q = q.filter(cls.pull_request_id == pull_request_id)
3257 return q.all()
3257 return q.all()
3258
3258
3259 @classmethod
3259 @classmethod
3260 def get_index_from_version(cls, pr_version, versions):
3260 def get_index_from_version(cls, pr_version, versions):
3261 num_versions = [x.pull_request_version_id for x in versions]
3261 num_versions = [x.pull_request_version_id for x in versions]
3262 try:
3262 try:
3263 return num_versions.index(pr_version) +1
3263 return num_versions.index(pr_version) +1
3264 except (IndexError, ValueError):
3264 except (IndexError, ValueError):
3265 return
3265 return
3266
3266
3267 @property
3267 @property
3268 def outdated(self):
3268 def outdated(self):
3269 return self.display_state == self.COMMENT_OUTDATED
3269 return self.display_state == self.COMMENT_OUTDATED
3270
3270
3271 def outdated_at_version(self, version):
3271 def outdated_at_version(self, version):
3272 """
3272 """
3273 Checks if comment is outdated for given pull request version
3273 Checks if comment is outdated for given pull request version
3274 """
3274 """
3275 return self.outdated and self.pull_request_version_id != version
3275 return self.outdated and self.pull_request_version_id != version
3276
3276
3277 def older_than_version(self, version):
3277 def older_than_version(self, version):
3278 """
3278 """
3279 Checks if comment is made from previous version than given
3279 Checks if comment is made from previous version than given
3280 """
3280 """
3281 if version is None:
3281 if version is None:
3282 return self.pull_request_version_id is not None
3282 return self.pull_request_version_id is not None
3283
3283
3284 return self.pull_request_version_id < version
3284 return self.pull_request_version_id < version
3285
3285
3286 @property
3286 @property
3287 def resolved(self):
3287 def resolved(self):
3288 return self.resolved_by[0] if self.resolved_by else None
3288 return self.resolved_by[0] if self.resolved_by else None
3289
3289
3290 @property
3290 @property
3291 def is_todo(self):
3291 def is_todo(self):
3292 return self.comment_type == self.COMMENT_TYPE_TODO
3292 return self.comment_type == self.COMMENT_TYPE_TODO
3293
3293
3294 @property
3294 @property
3295 def is_inline(self):
3295 def is_inline(self):
3296 return self.line_no and self.f_path
3296 return self.line_no and self.f_path
3297
3297
3298 def get_index_version(self, versions):
3298 def get_index_version(self, versions):
3299 return self.get_index_from_version(
3299 return self.get_index_from_version(
3300 self.pull_request_version_id, versions)
3300 self.pull_request_version_id, versions)
3301
3301
3302 def __repr__(self):
3302 def __repr__(self):
3303 if self.comment_id:
3303 if self.comment_id:
3304 return '<DB:Comment #%s>' % self.comment_id
3304 return '<DB:Comment #%s>' % self.comment_id
3305 else:
3305 else:
3306 return '<DB:Comment at %#x>' % id(self)
3306 return '<DB:Comment at %#x>' % id(self)
3307
3307
3308 def get_api_data(self):
3308 def get_api_data(self):
3309 comment = self
3309 comment = self
3310 data = {
3310 data = {
3311 'comment_id': comment.comment_id,
3311 'comment_id': comment.comment_id,
3312 'comment_type': comment.comment_type,
3312 'comment_type': comment.comment_type,
3313 'comment_text': comment.text,
3313 'comment_text': comment.text,
3314 'comment_status': comment.status_change,
3314 'comment_status': comment.status_change,
3315 'comment_f_path': comment.f_path,
3315 'comment_f_path': comment.f_path,
3316 'comment_lineno': comment.line_no,
3316 'comment_lineno': comment.line_no,
3317 'comment_author': comment.author,
3317 'comment_author': comment.author,
3318 'comment_created_on': comment.created_on
3318 'comment_created_on': comment.created_on
3319 }
3319 }
3320 return data
3320 return data
3321
3321
3322 def __json__(self):
3322 def __json__(self):
3323 data = dict()
3323 data = dict()
3324 data.update(self.get_api_data())
3324 data.update(self.get_api_data())
3325 return data
3325 return data
3326
3326
3327
3327
3328 class ChangesetStatus(Base, BaseModel):
3328 class ChangesetStatus(Base, BaseModel):
3329 __tablename__ = 'changeset_statuses'
3329 __tablename__ = 'changeset_statuses'
3330 __table_args__ = (
3330 __table_args__ = (
3331 Index('cs_revision_idx', 'revision'),
3331 Index('cs_revision_idx', 'revision'),
3332 Index('cs_version_idx', 'version'),
3332 Index('cs_version_idx', 'version'),
3333 UniqueConstraint('repo_id', 'revision', 'version'),
3333 UniqueConstraint('repo_id', 'revision', 'version'),
3334 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3334 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3335 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3335 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3336 )
3336 )
3337 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3337 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3338 STATUS_APPROVED = 'approved'
3338 STATUS_APPROVED = 'approved'
3339 STATUS_REJECTED = 'rejected'
3339 STATUS_REJECTED = 'rejected'
3340 STATUS_UNDER_REVIEW = 'under_review'
3340 STATUS_UNDER_REVIEW = 'under_review'
3341
3341
3342 STATUSES = [
3342 STATUSES = [
3343 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3343 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3344 (STATUS_APPROVED, _("Approved")),
3344 (STATUS_APPROVED, _("Approved")),
3345 (STATUS_REJECTED, _("Rejected")),
3345 (STATUS_REJECTED, _("Rejected")),
3346 (STATUS_UNDER_REVIEW, _("Under Review")),
3346 (STATUS_UNDER_REVIEW, _("Under Review")),
3347 ]
3347 ]
3348
3348
3349 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3349 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3350 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3350 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3351 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3351 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3352 revision = Column('revision', String(40), nullable=False)
3352 revision = Column('revision', String(40), nullable=False)
3353 status = Column('status', String(128), nullable=False, default=DEFAULT)
3353 status = Column('status', String(128), nullable=False, default=DEFAULT)
3354 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3354 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3355 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3355 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3356 version = Column('version', Integer(), nullable=False, default=0)
3356 version = Column('version', Integer(), nullable=False, default=0)
3357 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3357 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3358
3358
3359 author = relationship('User', lazy='joined')
3359 author = relationship('User', lazy='joined')
3360 repo = relationship('Repository')
3360 repo = relationship('Repository')
3361 comment = relationship('ChangesetComment', lazy='joined')
3361 comment = relationship('ChangesetComment', lazy='joined')
3362 pull_request = relationship('PullRequest', lazy='joined')
3362 pull_request = relationship('PullRequest', lazy='joined')
3363
3363
3364 def __unicode__(self):
3364 def __unicode__(self):
3365 return u"<%s('%s[v%s]:%s')>" % (
3365 return u"<%s('%s[v%s]:%s')>" % (
3366 self.__class__.__name__,
3366 self.__class__.__name__,
3367 self.status, self.version, self.author
3367 self.status, self.version, self.author
3368 )
3368 )
3369
3369
3370 @classmethod
3370 @classmethod
3371 def get_status_lbl(cls, value):
3371 def get_status_lbl(cls, value):
3372 return dict(cls.STATUSES).get(value)
3372 return dict(cls.STATUSES).get(value)
3373
3373
3374 @property
3374 @property
3375 def status_lbl(self):
3375 def status_lbl(self):
3376 return ChangesetStatus.get_status_lbl(self.status)
3376 return ChangesetStatus.get_status_lbl(self.status)
3377
3377
3378 def get_api_data(self):
3378 def get_api_data(self):
3379 status = self
3379 status = self
3380 data = {
3380 data = {
3381 'status_id': status.changeset_status_id,
3381 'status_id': status.changeset_status_id,
3382 'status': status.status,
3382 'status': status.status,
3383 }
3383 }
3384 return data
3384 return data
3385
3385
3386 def __json__(self):
3386 def __json__(self):
3387 data = dict()
3387 data = dict()
3388 data.update(self.get_api_data())
3388 data.update(self.get_api_data())
3389 return data
3389 return data
3390
3390
3391
3391
3392 class _PullRequestBase(BaseModel):
3392 class _PullRequestBase(BaseModel):
3393 """
3393 """
3394 Common attributes of pull request and version entries.
3394 Common attributes of pull request and version entries.
3395 """
3395 """
3396
3396
3397 # .status values
3397 # .status values
3398 STATUS_NEW = u'new'
3398 STATUS_NEW = u'new'
3399 STATUS_OPEN = u'open'
3399 STATUS_OPEN = u'open'
3400 STATUS_CLOSED = u'closed'
3400 STATUS_CLOSED = u'closed'
3401
3401
3402 title = Column('title', Unicode(255), nullable=True)
3402 title = Column('title', Unicode(255), nullable=True)
3403 description = Column(
3403 description = Column(
3404 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3404 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3405 nullable=True)
3405 nullable=True)
3406 # new/open/closed status of pull request (not approve/reject/etc)
3406 # new/open/closed status of pull request (not approve/reject/etc)
3407 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3407 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3408 created_on = Column(
3408 created_on = Column(
3409 'created_on', DateTime(timezone=False), nullable=False,
3409 'created_on', DateTime(timezone=False), nullable=False,
3410 default=datetime.datetime.now)
3410 default=datetime.datetime.now)
3411 updated_on = Column(
3411 updated_on = Column(
3412 'updated_on', DateTime(timezone=False), nullable=False,
3412 'updated_on', DateTime(timezone=False), nullable=False,
3413 default=datetime.datetime.now)
3413 default=datetime.datetime.now)
3414
3414
3415 @declared_attr
3415 @declared_attr
3416 def user_id(cls):
3416 def user_id(cls):
3417 return Column(
3417 return Column(
3418 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3418 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3419 unique=None)
3419 unique=None)
3420
3420
3421 # 500 revisions max
3421 # 500 revisions max
3422 _revisions = Column(
3422 _revisions = Column(
3423 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3423 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3424
3424
3425 @declared_attr
3425 @declared_attr
3426 def source_repo_id(cls):
3426 def source_repo_id(cls):
3427 # TODO: dan: rename column to source_repo_id
3427 # TODO: dan: rename column to source_repo_id
3428 return Column(
3428 return Column(
3429 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3429 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3430 nullable=False)
3430 nullable=False)
3431
3431
3432 source_ref = Column('org_ref', Unicode(255), nullable=False)
3432 source_ref = Column('org_ref', Unicode(255), nullable=False)
3433
3433
3434 @declared_attr
3434 @declared_attr
3435 def target_repo_id(cls):
3435 def target_repo_id(cls):
3436 # TODO: dan: rename column to target_repo_id
3436 # TODO: dan: rename column to target_repo_id
3437 return Column(
3437 return Column(
3438 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3438 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3439 nullable=False)
3439 nullable=False)
3440
3440
3441 target_ref = Column('other_ref', Unicode(255), nullable=False)
3441 target_ref = Column('other_ref', Unicode(255), nullable=False)
3442 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3442 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3443
3443
3444 # TODO: dan: rename column to last_merge_source_rev
3444 # TODO: dan: rename column to last_merge_source_rev
3445 _last_merge_source_rev = Column(
3445 _last_merge_source_rev = Column(
3446 'last_merge_org_rev', String(40), nullable=True)
3446 'last_merge_org_rev', String(40), nullable=True)
3447 # TODO: dan: rename column to last_merge_target_rev
3447 # TODO: dan: rename column to last_merge_target_rev
3448 _last_merge_target_rev = Column(
3448 _last_merge_target_rev = Column(
3449 'last_merge_other_rev', String(40), nullable=True)
3449 'last_merge_other_rev', String(40), nullable=True)
3450 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3450 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3451 merge_rev = Column('merge_rev', String(40), nullable=True)
3451 merge_rev = Column('merge_rev', String(40), nullable=True)
3452
3452
3453 reviewer_data = Column(
3453 reviewer_data = Column(
3454 'reviewer_data_json', MutationObj.as_mutable(
3454 'reviewer_data_json', MutationObj.as_mutable(
3455 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3455 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3456
3456
3457 @property
3457 @property
3458 def reviewer_data_json(self):
3458 def reviewer_data_json(self):
3459 return json.dumps(self.reviewer_data)
3459 return json.dumps(self.reviewer_data)
3460
3460
3461 @hybrid_property
3461 @hybrid_property
3462 def description_safe(self):
3462 def description_safe(self):
3463 from rhodecode.lib import helpers as h
3463 from rhodecode.lib import helpers as h
3464 return h.escape(self.description)
3464 return h.escape(self.description)
3465
3465
3466 @hybrid_property
3466 @hybrid_property
3467 def revisions(self):
3467 def revisions(self):
3468 return self._revisions.split(':') if self._revisions else []
3468 return self._revisions.split(':') if self._revisions else []
3469
3469
3470 @revisions.setter
3470 @revisions.setter
3471 def revisions(self, val):
3471 def revisions(self, val):
3472 self._revisions = ':'.join(val)
3472 self._revisions = ':'.join(val)
3473
3473
3474 @hybrid_property
3474 @hybrid_property
3475 def last_merge_status(self):
3475 def last_merge_status(self):
3476 return safe_int(self._last_merge_status)
3476 return safe_int(self._last_merge_status)
3477
3477
3478 @last_merge_status.setter
3478 @last_merge_status.setter
3479 def last_merge_status(self, val):
3479 def last_merge_status(self, val):
3480 self._last_merge_status = val
3480 self._last_merge_status = val
3481
3481
3482 @declared_attr
3482 @declared_attr
3483 def author(cls):
3483 def author(cls):
3484 return relationship('User', lazy='joined')
3484 return relationship('User', lazy='joined')
3485
3485
3486 @declared_attr
3486 @declared_attr
3487 def source_repo(cls):
3487 def source_repo(cls):
3488 return relationship(
3488 return relationship(
3489 'Repository',
3489 'Repository',
3490 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3490 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3491
3491
3492 @property
3492 @property
3493 def source_ref_parts(self):
3493 def source_ref_parts(self):
3494 return self.unicode_to_reference(self.source_ref)
3494 return self.unicode_to_reference(self.source_ref)
3495
3495
3496 @declared_attr
3496 @declared_attr
3497 def target_repo(cls):
3497 def target_repo(cls):
3498 return relationship(
3498 return relationship(
3499 'Repository',
3499 'Repository',
3500 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3500 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3501
3501
3502 @property
3502 @property
3503 def target_ref_parts(self):
3503 def target_ref_parts(self):
3504 return self.unicode_to_reference(self.target_ref)
3504 return self.unicode_to_reference(self.target_ref)
3505
3505
3506 @property
3506 @property
3507 def shadow_merge_ref(self):
3507 def shadow_merge_ref(self):
3508 return self.unicode_to_reference(self._shadow_merge_ref)
3508 return self.unicode_to_reference(self._shadow_merge_ref)
3509
3509
3510 @shadow_merge_ref.setter
3510 @shadow_merge_ref.setter
3511 def shadow_merge_ref(self, ref):
3511 def shadow_merge_ref(self, ref):
3512 self._shadow_merge_ref = self.reference_to_unicode(ref)
3512 self._shadow_merge_ref = self.reference_to_unicode(ref)
3513
3513
3514 def unicode_to_reference(self, raw):
3514 def unicode_to_reference(self, raw):
3515 """
3515 """
3516 Convert a unicode (or string) to a reference object.
3516 Convert a unicode (or string) to a reference object.
3517 If unicode evaluates to False it returns None.
3517 If unicode evaluates to False it returns None.
3518 """
3518 """
3519 if raw:
3519 if raw:
3520 refs = raw.split(':')
3520 refs = raw.split(':')
3521 return Reference(*refs)
3521 return Reference(*refs)
3522 else:
3522 else:
3523 return None
3523 return None
3524
3524
3525 def reference_to_unicode(self, ref):
3525 def reference_to_unicode(self, ref):
3526 """
3526 """
3527 Convert a reference object to unicode.
3527 Convert a reference object to unicode.
3528 If reference is None it returns None.
3528 If reference is None it returns None.
3529 """
3529 """
3530 if ref:
3530 if ref:
3531 return u':'.join(ref)
3531 return u':'.join(ref)
3532 else:
3532 else:
3533 return None
3533 return None
3534
3534
3535 def get_api_data(self, with_merge_state=True):
3535 def get_api_data(self, with_merge_state=True):
3536 from rhodecode.model.pull_request import PullRequestModel
3536 from rhodecode.model.pull_request import PullRequestModel
3537
3537
3538 pull_request = self
3538 pull_request = self
3539 if with_merge_state:
3539 if with_merge_state:
3540 merge_status = PullRequestModel().merge_status(pull_request)
3540 merge_status = PullRequestModel().merge_status(pull_request)
3541 merge_state = {
3541 merge_state = {
3542 'status': merge_status[0],
3542 'status': merge_status[0],
3543 'message': safe_unicode(merge_status[1]),
3543 'message': safe_unicode(merge_status[1]),
3544 }
3544 }
3545 else:
3545 else:
3546 merge_state = {'status': 'not_available',
3546 merge_state = {'status': 'not_available',
3547 'message': 'not_available'}
3547 'message': 'not_available'}
3548
3548
3549 merge_data = {
3549 merge_data = {
3550 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3550 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3551 'reference': (
3551 'reference': (
3552 pull_request.shadow_merge_ref._asdict()
3552 pull_request.shadow_merge_ref._asdict()
3553 if pull_request.shadow_merge_ref else None),
3553 if pull_request.shadow_merge_ref else None),
3554 }
3554 }
3555
3555
3556 data = {
3556 data = {
3557 'pull_request_id': pull_request.pull_request_id,
3557 'pull_request_id': pull_request.pull_request_id,
3558 'url': PullRequestModel().get_url(pull_request),
3558 'url': PullRequestModel().get_url(pull_request),
3559 'title': pull_request.title,
3559 'title': pull_request.title,
3560 'description': pull_request.description,
3560 'description': pull_request.description,
3561 'status': pull_request.status,
3561 'status': pull_request.status,
3562 'created_on': pull_request.created_on,
3562 'created_on': pull_request.created_on,
3563 'updated_on': pull_request.updated_on,
3563 'updated_on': pull_request.updated_on,
3564 'commit_ids': pull_request.revisions,
3564 'commit_ids': pull_request.revisions,
3565 'review_status': pull_request.calculated_review_status(),
3565 'review_status': pull_request.calculated_review_status(),
3566 'mergeable': merge_state,
3566 'mergeable': merge_state,
3567 'source': {
3567 'source': {
3568 'clone_url': pull_request.source_repo.clone_url(),
3568 'clone_url': pull_request.source_repo.clone_url(),
3569 'repository': pull_request.source_repo.repo_name,
3569 'repository': pull_request.source_repo.repo_name,
3570 'reference': {
3570 'reference': {
3571 'name': pull_request.source_ref_parts.name,
3571 'name': pull_request.source_ref_parts.name,
3572 'type': pull_request.source_ref_parts.type,
3572 'type': pull_request.source_ref_parts.type,
3573 'commit_id': pull_request.source_ref_parts.commit_id,
3573 'commit_id': pull_request.source_ref_parts.commit_id,
3574 },
3574 },
3575 },
3575 },
3576 'target': {
3576 'target': {
3577 'clone_url': pull_request.target_repo.clone_url(),
3577 'clone_url': pull_request.target_repo.clone_url(),
3578 'repository': pull_request.target_repo.repo_name,
3578 'repository': pull_request.target_repo.repo_name,
3579 'reference': {
3579 'reference': {
3580 'name': pull_request.target_ref_parts.name,
3580 'name': pull_request.target_ref_parts.name,
3581 'type': pull_request.target_ref_parts.type,
3581 'type': pull_request.target_ref_parts.type,
3582 'commit_id': pull_request.target_ref_parts.commit_id,
3582 'commit_id': pull_request.target_ref_parts.commit_id,
3583 },
3583 },
3584 },
3584 },
3585 'merge': merge_data,
3585 'merge': merge_data,
3586 'author': pull_request.author.get_api_data(include_secrets=False,
3586 'author': pull_request.author.get_api_data(include_secrets=False,
3587 details='basic'),
3587 details='basic'),
3588 'reviewers': [
3588 'reviewers': [
3589 {
3589 {
3590 'user': reviewer.get_api_data(include_secrets=False,
3590 'user': reviewer.get_api_data(include_secrets=False,
3591 details='basic'),
3591 details='basic'),
3592 'reasons': reasons,
3592 'reasons': reasons,
3593 'review_status': st[0][1].status if st else 'not_reviewed',
3593 'review_status': st[0][1].status if st else 'not_reviewed',
3594 }
3594 }
3595 for reviewer, reasons, mandatory, st in
3595 for reviewer, reasons, mandatory, st in
3596 pull_request.reviewers_statuses()
3596 pull_request.reviewers_statuses()
3597 ]
3597 ]
3598 }
3598 }
3599
3599
3600 return data
3600 return data
3601
3601
3602
3602
3603 class PullRequest(Base, _PullRequestBase):
3603 class PullRequest(Base, _PullRequestBase):
3604 __tablename__ = 'pull_requests'
3604 __tablename__ = 'pull_requests'
3605 __table_args__ = (
3605 __table_args__ = (
3606 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3606 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3607 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3607 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3608 )
3608 )
3609
3609
3610 pull_request_id = Column(
3610 pull_request_id = Column(
3611 'pull_request_id', Integer(), nullable=False, primary_key=True)
3611 'pull_request_id', Integer(), nullable=False, primary_key=True)
3612
3612
3613 def __repr__(self):
3613 def __repr__(self):
3614 if self.pull_request_id:
3614 if self.pull_request_id:
3615 return '<DB:PullRequest #%s>' % self.pull_request_id
3615 return '<DB:PullRequest #%s>' % self.pull_request_id
3616 else:
3616 else:
3617 return '<DB:PullRequest at %#x>' % id(self)
3617 return '<DB:PullRequest at %#x>' % id(self)
3618
3618
3619 reviewers = relationship('PullRequestReviewers',
3619 reviewers = relationship('PullRequestReviewers',
3620 cascade="all, delete, delete-orphan")
3620 cascade="all, delete, delete-orphan")
3621 statuses = relationship('ChangesetStatus',
3621 statuses = relationship('ChangesetStatus',
3622 cascade="all, delete, delete-orphan")
3622 cascade="all, delete, delete-orphan")
3623 comments = relationship('ChangesetComment',
3623 comments = relationship('ChangesetComment',
3624 cascade="all, delete, delete-orphan")
3624 cascade="all, delete, delete-orphan")
3625 versions = relationship('PullRequestVersion',
3625 versions = relationship('PullRequestVersion',
3626 cascade="all, delete, delete-orphan",
3626 cascade="all, delete, delete-orphan",
3627 lazy='dynamic')
3627 lazy='dynamic')
3628
3628
3629 @classmethod
3629 @classmethod
3630 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3630 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3631 internal_methods=None):
3631 internal_methods=None):
3632
3632
3633 class PullRequestDisplay(object):
3633 class PullRequestDisplay(object):
3634 """
3634 """
3635 Special object wrapper for showing PullRequest data via Versions
3635 Special object wrapper for showing PullRequest data via Versions
3636 It mimics PR object as close as possible. This is read only object
3636 It mimics PR object as close as possible. This is read only object
3637 just for display
3637 just for display
3638 """
3638 """
3639
3639
3640 def __init__(self, attrs, internal=None):
3640 def __init__(self, attrs, internal=None):
3641 self.attrs = attrs
3641 self.attrs = attrs
3642 # internal have priority over the given ones via attrs
3642 # internal have priority over the given ones via attrs
3643 self.internal = internal or ['versions']
3643 self.internal = internal or ['versions']
3644
3644
3645 def __getattr__(self, item):
3645 def __getattr__(self, item):
3646 if item in self.internal:
3646 if item in self.internal:
3647 return getattr(self, item)
3647 return getattr(self, item)
3648 try:
3648 try:
3649 return self.attrs[item]
3649 return self.attrs[item]
3650 except KeyError:
3650 except KeyError:
3651 raise AttributeError(
3651 raise AttributeError(
3652 '%s object has no attribute %s' % (self, item))
3652 '%s object has no attribute %s' % (self, item))
3653
3653
3654 def __repr__(self):
3654 def __repr__(self):
3655 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3655 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3656
3656
3657 def versions(self):
3657 def versions(self):
3658 return pull_request_obj.versions.order_by(
3658 return pull_request_obj.versions.order_by(
3659 PullRequestVersion.pull_request_version_id).all()
3659 PullRequestVersion.pull_request_version_id).all()
3660
3660
3661 def is_closed(self):
3661 def is_closed(self):
3662 return pull_request_obj.is_closed()
3662 return pull_request_obj.is_closed()
3663
3663
3664 @property
3664 @property
3665 def pull_request_version_id(self):
3665 def pull_request_version_id(self):
3666 return getattr(pull_request_obj, 'pull_request_version_id', None)
3666 return getattr(pull_request_obj, 'pull_request_version_id', None)
3667
3667
3668 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3668 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3669
3669
3670 attrs.author = StrictAttributeDict(
3670 attrs.author = StrictAttributeDict(
3671 pull_request_obj.author.get_api_data())
3671 pull_request_obj.author.get_api_data())
3672 if pull_request_obj.target_repo:
3672 if pull_request_obj.target_repo:
3673 attrs.target_repo = StrictAttributeDict(
3673 attrs.target_repo = StrictAttributeDict(
3674 pull_request_obj.target_repo.get_api_data())
3674 pull_request_obj.target_repo.get_api_data())
3675 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3675 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3676
3676
3677 if pull_request_obj.source_repo:
3677 if pull_request_obj.source_repo:
3678 attrs.source_repo = StrictAttributeDict(
3678 attrs.source_repo = StrictAttributeDict(
3679 pull_request_obj.source_repo.get_api_data())
3679 pull_request_obj.source_repo.get_api_data())
3680 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3680 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3681
3681
3682 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3682 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3683 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3683 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3684 attrs.revisions = pull_request_obj.revisions
3684 attrs.revisions = pull_request_obj.revisions
3685
3685
3686 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3686 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3687 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3687 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3688 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3688 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3689
3689
3690 return PullRequestDisplay(attrs, internal=internal_methods)
3690 return PullRequestDisplay(attrs, internal=internal_methods)
3691
3691
3692 def is_closed(self):
3692 def is_closed(self):
3693 return self.status == self.STATUS_CLOSED
3693 return self.status == self.STATUS_CLOSED
3694
3694
3695 def __json__(self):
3695 def __json__(self):
3696 return {
3696 return {
3697 'revisions': self.revisions,
3697 'revisions': self.revisions,
3698 }
3698 }
3699
3699
3700 def calculated_review_status(self):
3700 def calculated_review_status(self):
3701 from rhodecode.model.changeset_status import ChangesetStatusModel
3701 from rhodecode.model.changeset_status import ChangesetStatusModel
3702 return ChangesetStatusModel().calculated_review_status(self)
3702 return ChangesetStatusModel().calculated_review_status(self)
3703
3703
3704 def reviewers_statuses(self):
3704 def reviewers_statuses(self):
3705 from rhodecode.model.changeset_status import ChangesetStatusModel
3705 from rhodecode.model.changeset_status import ChangesetStatusModel
3706 return ChangesetStatusModel().reviewers_statuses(self)
3706 return ChangesetStatusModel().reviewers_statuses(self)
3707
3707
3708 @property
3708 @property
3709 def workspace_id(self):
3709 def workspace_id(self):
3710 from rhodecode.model.pull_request import PullRequestModel
3710 from rhodecode.model.pull_request import PullRequestModel
3711 return PullRequestModel()._workspace_id(self)
3711 return PullRequestModel()._workspace_id(self)
3712
3712
3713 def get_shadow_repo(self):
3713 def get_shadow_repo(self):
3714 workspace_id = self.workspace_id
3714 workspace_id = self.workspace_id
3715 vcs_obj = self.target_repo.scm_instance()
3715 vcs_obj = self.target_repo.scm_instance()
3716 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3716 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3717 workspace_id)
3717 workspace_id)
3718 return vcs_obj._get_shadow_instance(shadow_repository_path)
3718 return vcs_obj._get_shadow_instance(shadow_repository_path)
3719
3719
3720
3720
3721 class PullRequestVersion(Base, _PullRequestBase):
3721 class PullRequestVersion(Base, _PullRequestBase):
3722 __tablename__ = 'pull_request_versions'
3722 __tablename__ = 'pull_request_versions'
3723 __table_args__ = (
3723 __table_args__ = (
3724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3725 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3725 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3726 )
3726 )
3727
3727
3728 pull_request_version_id = Column(
3728 pull_request_version_id = Column(
3729 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3729 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3730 pull_request_id = Column(
3730 pull_request_id = Column(
3731 'pull_request_id', Integer(),
3731 'pull_request_id', Integer(),
3732 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3732 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3733 pull_request = relationship('PullRequest')
3733 pull_request = relationship('PullRequest')
3734
3734
3735 def __repr__(self):
3735 def __repr__(self):
3736 if self.pull_request_version_id:
3736 if self.pull_request_version_id:
3737 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3737 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3738 else:
3738 else:
3739 return '<DB:PullRequestVersion at %#x>' % id(self)
3739 return '<DB:PullRequestVersion at %#x>' % id(self)
3740
3740
3741 @property
3741 @property
3742 def reviewers(self):
3742 def reviewers(self):
3743 return self.pull_request.reviewers
3743 return self.pull_request.reviewers
3744
3744
3745 @property
3745 @property
3746 def versions(self):
3746 def versions(self):
3747 return self.pull_request.versions
3747 return self.pull_request.versions
3748
3748
3749 def is_closed(self):
3749 def is_closed(self):
3750 # calculate from original
3750 # calculate from original
3751 return self.pull_request.status == self.STATUS_CLOSED
3751 return self.pull_request.status == self.STATUS_CLOSED
3752
3752
3753 def calculated_review_status(self):
3753 def calculated_review_status(self):
3754 return self.pull_request.calculated_review_status()
3754 return self.pull_request.calculated_review_status()
3755
3755
3756 def reviewers_statuses(self):
3756 def reviewers_statuses(self):
3757 return self.pull_request.reviewers_statuses()
3757 return self.pull_request.reviewers_statuses()
3758
3758
3759
3759
3760 class PullRequestReviewers(Base, BaseModel):
3760 class PullRequestReviewers(Base, BaseModel):
3761 __tablename__ = 'pull_request_reviewers'
3761 __tablename__ = 'pull_request_reviewers'
3762 __table_args__ = (
3762 __table_args__ = (
3763 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3763 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3764 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3764 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3765 )
3765 )
3766
3766
3767 @hybrid_property
3767 @hybrid_property
3768 def reasons(self):
3768 def reasons(self):
3769 if not self._reasons:
3769 if not self._reasons:
3770 return []
3770 return []
3771 return self._reasons
3771 return self._reasons
3772
3772
3773 @reasons.setter
3773 @reasons.setter
3774 def reasons(self, val):
3774 def reasons(self, val):
3775 val = val or []
3775 val = val or []
3776 if any(not isinstance(x, basestring) for x in val):
3776 if any(not isinstance(x, basestring) for x in val):
3777 raise Exception('invalid reasons type, must be list of strings')
3777 raise Exception('invalid reasons type, must be list of strings')
3778 self._reasons = val
3778 self._reasons = val
3779
3779
3780 pull_requests_reviewers_id = Column(
3780 pull_requests_reviewers_id = Column(
3781 'pull_requests_reviewers_id', Integer(), nullable=False,
3781 'pull_requests_reviewers_id', Integer(), nullable=False,
3782 primary_key=True)
3782 primary_key=True)
3783 pull_request_id = Column(
3783 pull_request_id = Column(
3784 "pull_request_id", Integer(),
3784 "pull_request_id", Integer(),
3785 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3785 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3786 user_id = Column(
3786 user_id = Column(
3787 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3787 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3788 _reasons = Column(
3788 _reasons = Column(
3789 'reason', MutationList.as_mutable(
3789 'reason', MutationList.as_mutable(
3790 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3790 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3791 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3791 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3792 user = relationship('User')
3792 user = relationship('User')
3793 pull_request = relationship('PullRequest')
3793 pull_request = relationship('PullRequest')
3794
3794
3795
3795
3796 class Notification(Base, BaseModel):
3796 class Notification(Base, BaseModel):
3797 __tablename__ = 'notifications'
3797 __tablename__ = 'notifications'
3798 __table_args__ = (
3798 __table_args__ = (
3799 Index('notification_type_idx', 'type'),
3799 Index('notification_type_idx', 'type'),
3800 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3800 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3801 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3801 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3802 )
3802 )
3803
3803
3804 TYPE_CHANGESET_COMMENT = u'cs_comment'
3804 TYPE_CHANGESET_COMMENT = u'cs_comment'
3805 TYPE_MESSAGE = u'message'
3805 TYPE_MESSAGE = u'message'
3806 TYPE_MENTION = u'mention'
3806 TYPE_MENTION = u'mention'
3807 TYPE_REGISTRATION = u'registration'
3807 TYPE_REGISTRATION = u'registration'
3808 TYPE_PULL_REQUEST = u'pull_request'
3808 TYPE_PULL_REQUEST = u'pull_request'
3809 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3809 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3810
3810
3811 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3811 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3812 subject = Column('subject', Unicode(512), nullable=True)
3812 subject = Column('subject', Unicode(512), nullable=True)
3813 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3813 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3814 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3814 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3815 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3815 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3816 type_ = Column('type', Unicode(255))
3816 type_ = Column('type', Unicode(255))
3817
3817
3818 created_by_user = relationship('User')
3818 created_by_user = relationship('User')
3819 notifications_to_users = relationship('UserNotification', lazy='joined',
3819 notifications_to_users = relationship('UserNotification', lazy='joined',
3820 cascade="all, delete, delete-orphan")
3820 cascade="all, delete, delete-orphan")
3821
3821
3822 @property
3822 @property
3823 def recipients(self):
3823 def recipients(self):
3824 return [x.user for x in UserNotification.query()\
3824 return [x.user for x in UserNotification.query()\
3825 .filter(UserNotification.notification == self)\
3825 .filter(UserNotification.notification == self)\
3826 .order_by(UserNotification.user_id.asc()).all()]
3826 .order_by(UserNotification.user_id.asc()).all()]
3827
3827
3828 @classmethod
3828 @classmethod
3829 def create(cls, created_by, subject, body, recipients, type_=None):
3829 def create(cls, created_by, subject, body, recipients, type_=None):
3830 if type_ is None:
3830 if type_ is None:
3831 type_ = Notification.TYPE_MESSAGE
3831 type_ = Notification.TYPE_MESSAGE
3832
3832
3833 notification = cls()
3833 notification = cls()
3834 notification.created_by_user = created_by
3834 notification.created_by_user = created_by
3835 notification.subject = subject
3835 notification.subject = subject
3836 notification.body = body
3836 notification.body = body
3837 notification.type_ = type_
3837 notification.type_ = type_
3838 notification.created_on = datetime.datetime.now()
3838 notification.created_on = datetime.datetime.now()
3839
3839
3840 for u in recipients:
3840 for u in recipients:
3841 assoc = UserNotification()
3841 assoc = UserNotification()
3842 assoc.notification = notification
3842 assoc.notification = notification
3843
3843
3844 # if created_by is inside recipients mark his notification
3844 # if created_by is inside recipients mark his notification
3845 # as read
3845 # as read
3846 if u.user_id == created_by.user_id:
3846 if u.user_id == created_by.user_id:
3847 assoc.read = True
3847 assoc.read = True
3848
3848
3849 u.notifications.append(assoc)
3849 u.notifications.append(assoc)
3850 Session().add(notification)
3850 Session().add(notification)
3851
3851
3852 return notification
3852 return notification
3853
3853
3854
3854
3855 class UserNotification(Base, BaseModel):
3855 class UserNotification(Base, BaseModel):
3856 __tablename__ = 'user_to_notification'
3856 __tablename__ = 'user_to_notification'
3857 __table_args__ = (
3857 __table_args__ = (
3858 UniqueConstraint('user_id', 'notification_id'),
3858 UniqueConstraint('user_id', 'notification_id'),
3859 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3859 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3860 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3860 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3861 )
3861 )
3862 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3862 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3863 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3863 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3864 read = Column('read', Boolean, default=False)
3864 read = Column('read', Boolean, default=False)
3865 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3865 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3866
3866
3867 user = relationship('User', lazy="joined")
3867 user = relationship('User', lazy="joined")
3868 notification = relationship('Notification', lazy="joined",
3868 notification = relationship('Notification', lazy="joined",
3869 order_by=lambda: Notification.created_on.desc(),)
3869 order_by=lambda: Notification.created_on.desc(),)
3870
3870
3871 def mark_as_read(self):
3871 def mark_as_read(self):
3872 self.read = True
3872 self.read = True
3873 Session().add(self)
3873 Session().add(self)
3874
3874
3875
3875
3876 class Gist(Base, BaseModel):
3876 class Gist(Base, BaseModel):
3877 __tablename__ = 'gists'
3877 __tablename__ = 'gists'
3878 __table_args__ = (
3878 __table_args__ = (
3879 Index('g_gist_access_id_idx', 'gist_access_id'),
3879 Index('g_gist_access_id_idx', 'gist_access_id'),
3880 Index('g_created_on_idx', 'created_on'),
3880 Index('g_created_on_idx', 'created_on'),
3881 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3881 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3882 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3882 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3883 )
3883 )
3884 GIST_PUBLIC = u'public'
3884 GIST_PUBLIC = u'public'
3885 GIST_PRIVATE = u'private'
3885 GIST_PRIVATE = u'private'
3886 DEFAULT_FILENAME = u'gistfile1.txt'
3886 DEFAULT_FILENAME = u'gistfile1.txt'
3887
3887
3888 ACL_LEVEL_PUBLIC = u'acl_public'
3888 ACL_LEVEL_PUBLIC = u'acl_public'
3889 ACL_LEVEL_PRIVATE = u'acl_private'
3889 ACL_LEVEL_PRIVATE = u'acl_private'
3890
3890
3891 gist_id = Column('gist_id', Integer(), primary_key=True)
3891 gist_id = Column('gist_id', Integer(), primary_key=True)
3892 gist_access_id = Column('gist_access_id', Unicode(250))
3892 gist_access_id = Column('gist_access_id', Unicode(250))
3893 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3893 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3894 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3894 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3895 gist_expires = Column('gist_expires', Float(53), nullable=False)
3895 gist_expires = Column('gist_expires', Float(53), nullable=False)
3896 gist_type = Column('gist_type', Unicode(128), nullable=False)
3896 gist_type = Column('gist_type', Unicode(128), nullable=False)
3897 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3897 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3898 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3898 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3899 acl_level = Column('acl_level', Unicode(128), nullable=True)
3899 acl_level = Column('acl_level', Unicode(128), nullable=True)
3900
3900
3901 owner = relationship('User')
3901 owner = relationship('User')
3902
3902
3903 def __repr__(self):
3903 def __repr__(self):
3904 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3904 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3905
3905
3906 @hybrid_property
3906 @hybrid_property
3907 def description_safe(self):
3907 def description_safe(self):
3908 from rhodecode.lib import helpers as h
3908 from rhodecode.lib import helpers as h
3909 return h.escape(self.gist_description)
3909 return h.escape(self.gist_description)
3910
3910
3911 @classmethod
3911 @classmethod
3912 def get_or_404(cls, id_):
3912 def get_or_404(cls, id_):
3913 from pyramid.httpexceptions import HTTPNotFound
3913 from pyramid.httpexceptions import HTTPNotFound
3914
3914
3915 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3915 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3916 if not res:
3916 if not res:
3917 raise HTTPNotFound()
3917 raise HTTPNotFound()
3918 return res
3918 return res
3919
3919
3920 @classmethod
3920 @classmethod
3921 def get_by_access_id(cls, gist_access_id):
3921 def get_by_access_id(cls, gist_access_id):
3922 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3922 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3923
3923
3924 def gist_url(self):
3924 def gist_url(self):
3925 from rhodecode.model.gist import GistModel
3925 from rhodecode.model.gist import GistModel
3926 return GistModel().get_url(self)
3926 return GistModel().get_url(self)
3927
3927
3928 @classmethod
3928 @classmethod
3929 def base_path(cls):
3929 def base_path(cls):
3930 """
3930 """
3931 Returns base path when all gists are stored
3931 Returns base path when all gists are stored
3932
3932
3933 :param cls:
3933 :param cls:
3934 """
3934 """
3935 from rhodecode.model.gist import GIST_STORE_LOC
3935 from rhodecode.model.gist import GIST_STORE_LOC
3936 q = Session().query(RhodeCodeUi)\
3936 q = Session().query(RhodeCodeUi)\
3937 .filter(RhodeCodeUi.ui_key == URL_SEP)
3937 .filter(RhodeCodeUi.ui_key == URL_SEP)
3938 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3938 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3939 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3939 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3940
3940
3941 def get_api_data(self):
3941 def get_api_data(self):
3942 """
3942 """
3943 Common function for generating gist related data for API
3943 Common function for generating gist related data for API
3944 """
3944 """
3945 gist = self
3945 gist = self
3946 data = {
3946 data = {
3947 'gist_id': gist.gist_id,
3947 'gist_id': gist.gist_id,
3948 'type': gist.gist_type,
3948 'type': gist.gist_type,
3949 'access_id': gist.gist_access_id,
3949 'access_id': gist.gist_access_id,
3950 'description': gist.gist_description,
3950 'description': gist.gist_description,
3951 'url': gist.gist_url(),
3951 'url': gist.gist_url(),
3952 'expires': gist.gist_expires,
3952 'expires': gist.gist_expires,
3953 'created_on': gist.created_on,
3953 'created_on': gist.created_on,
3954 'modified_at': gist.modified_at,
3954 'modified_at': gist.modified_at,
3955 'content': None,
3955 'content': None,
3956 'acl_level': gist.acl_level,
3956 'acl_level': gist.acl_level,
3957 }
3957 }
3958 return data
3958 return data
3959
3959
3960 def __json__(self):
3960 def __json__(self):
3961 data = dict(
3961 data = dict(
3962 )
3962 )
3963 data.update(self.get_api_data())
3963 data.update(self.get_api_data())
3964 return data
3964 return data
3965 # SCM functions
3965 # SCM functions
3966
3966
3967 def scm_instance(self, **kwargs):
3967 def scm_instance(self, **kwargs):
3968 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3968 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3969 return get_vcs_instance(
3969 return get_vcs_instance(
3970 repo_path=safe_str(full_repo_path), create=False)
3970 repo_path=safe_str(full_repo_path), create=False)
3971
3971
3972
3972
3973 class ExternalIdentity(Base, BaseModel):
3973 class ExternalIdentity(Base, BaseModel):
3974 __tablename__ = 'external_identities'
3974 __tablename__ = 'external_identities'
3975 __table_args__ = (
3975 __table_args__ = (
3976 Index('local_user_id_idx', 'local_user_id'),
3976 Index('local_user_id_idx', 'local_user_id'),
3977 Index('external_id_idx', 'external_id'),
3977 Index('external_id_idx', 'external_id'),
3978 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3978 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3979 'mysql_charset': 'utf8'})
3979 'mysql_charset': 'utf8'})
3980
3980
3981 external_id = Column('external_id', Unicode(255), default=u'',
3981 external_id = Column('external_id', Unicode(255), default=u'',
3982 primary_key=True)
3982 primary_key=True)
3983 external_username = Column('external_username', Unicode(1024), default=u'')
3983 external_username = Column('external_username', Unicode(1024), default=u'')
3984 local_user_id = Column('local_user_id', Integer(),
3984 local_user_id = Column('local_user_id', Integer(),
3985 ForeignKey('users.user_id'), primary_key=True)
3985 ForeignKey('users.user_id'), primary_key=True)
3986 provider_name = Column('provider_name', Unicode(255), default=u'',
3986 provider_name = Column('provider_name', Unicode(255), default=u'',
3987 primary_key=True)
3987 primary_key=True)
3988 access_token = Column('access_token', String(1024), default=u'')
3988 access_token = Column('access_token', String(1024), default=u'')
3989 alt_token = Column('alt_token', String(1024), default=u'')
3989 alt_token = Column('alt_token', String(1024), default=u'')
3990 token_secret = Column('token_secret', String(1024), default=u'')
3990 token_secret = Column('token_secret', String(1024), default=u'')
3991
3991
3992 @classmethod
3992 @classmethod
3993 def by_external_id_and_provider(cls, external_id, provider_name,
3993 def by_external_id_and_provider(cls, external_id, provider_name,
3994 local_user_id=None):
3994 local_user_id=None):
3995 """
3995 """
3996 Returns ExternalIdentity instance based on search params
3996 Returns ExternalIdentity instance based on search params
3997
3997
3998 :param external_id:
3998 :param external_id:
3999 :param provider_name:
3999 :param provider_name:
4000 :return: ExternalIdentity
4000 :return: ExternalIdentity
4001 """
4001 """
4002 query = cls.query()
4002 query = cls.query()
4003 query = query.filter(cls.external_id == external_id)
4003 query = query.filter(cls.external_id == external_id)
4004 query = query.filter(cls.provider_name == provider_name)
4004 query = query.filter(cls.provider_name == provider_name)
4005 if local_user_id:
4005 if local_user_id:
4006 query = query.filter(cls.local_user_id == local_user_id)
4006 query = query.filter(cls.local_user_id == local_user_id)
4007 return query.first()
4007 return query.first()
4008
4008
4009 @classmethod
4009 @classmethod
4010 def user_by_external_id_and_provider(cls, external_id, provider_name):
4010 def user_by_external_id_and_provider(cls, external_id, provider_name):
4011 """
4011 """
4012 Returns User instance based on search params
4012 Returns User instance based on search params
4013
4013
4014 :param external_id:
4014 :param external_id:
4015 :param provider_name:
4015 :param provider_name:
4016 :return: User
4016 :return: User
4017 """
4017 """
4018 query = User.query()
4018 query = User.query()
4019 query = query.filter(cls.external_id == external_id)
4019 query = query.filter(cls.external_id == external_id)
4020 query = query.filter(cls.provider_name == provider_name)
4020 query = query.filter(cls.provider_name == provider_name)
4021 query = query.filter(User.user_id == cls.local_user_id)
4021 query = query.filter(User.user_id == cls.local_user_id)
4022 return query.first()
4022 return query.first()
4023
4023
4024 @classmethod
4024 @classmethod
4025 def by_local_user_id(cls, local_user_id):
4025 def by_local_user_id(cls, local_user_id):
4026 """
4026 """
4027 Returns all tokens for user
4027 Returns all tokens for user
4028
4028
4029 :param local_user_id:
4029 :param local_user_id:
4030 :return: ExternalIdentity
4030 :return: ExternalIdentity
4031 """
4031 """
4032 query = cls.query()
4032 query = cls.query()
4033 query = query.filter(cls.local_user_id == local_user_id)
4033 query = query.filter(cls.local_user_id == local_user_id)
4034 return query
4034 return query
4035
4035
4036
4036
4037 class Integration(Base, BaseModel):
4037 class Integration(Base, BaseModel):
4038 __tablename__ = 'integrations'
4038 __tablename__ = 'integrations'
4039 __table_args__ = (
4039 __table_args__ = (
4040 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4040 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4041 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4041 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4042 )
4042 )
4043
4043
4044 integration_id = Column('integration_id', Integer(), primary_key=True)
4044 integration_id = Column('integration_id', Integer(), primary_key=True)
4045 integration_type = Column('integration_type', String(255))
4045 integration_type = Column('integration_type', String(255))
4046 enabled = Column('enabled', Boolean(), nullable=False)
4046 enabled = Column('enabled', Boolean(), nullable=False)
4047 name = Column('name', String(255), nullable=False)
4047 name = Column('name', String(255), nullable=False)
4048 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4048 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4049 default=False)
4049 default=False)
4050
4050
4051 settings = Column(
4051 settings = Column(
4052 'settings_json', MutationObj.as_mutable(
4052 'settings_json', MutationObj.as_mutable(
4053 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4053 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4054 repo_id = Column(
4054 repo_id = Column(
4055 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4055 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4056 nullable=True, unique=None, default=None)
4056 nullable=True, unique=None, default=None)
4057 repo = relationship('Repository', lazy='joined')
4057 repo = relationship('Repository', lazy='joined')
4058
4058
4059 repo_group_id = Column(
4059 repo_group_id = Column(
4060 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4060 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4061 nullable=True, unique=None, default=None)
4061 nullable=True, unique=None, default=None)
4062 repo_group = relationship('RepoGroup', lazy='joined')
4062 repo_group = relationship('RepoGroup', lazy='joined')
4063
4063
4064 @property
4064 @property
4065 def scope(self):
4065 def scope(self):
4066 if self.repo:
4066 if self.repo:
4067 return repr(self.repo)
4067 return repr(self.repo)
4068 if self.repo_group:
4068 if self.repo_group:
4069 if self.child_repos_only:
4069 if self.child_repos_only:
4070 return repr(self.repo_group) + ' (child repos only)'
4070 return repr(self.repo_group) + ' (child repos only)'
4071 else:
4071 else:
4072 return repr(self.repo_group) + ' (recursive)'
4072 return repr(self.repo_group) + ' (recursive)'
4073 if self.child_repos_only:
4073 if self.child_repos_only:
4074 return 'root_repos'
4074 return 'root_repos'
4075 return 'global'
4075 return 'global'
4076
4076
4077 def __repr__(self):
4077 def __repr__(self):
4078 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4078 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4079
4079
4080
4080
4081 class RepoReviewRuleUser(Base, BaseModel):
4081 class RepoReviewRuleUser(Base, BaseModel):
4082 __tablename__ = 'repo_review_rules_users'
4082 __tablename__ = 'repo_review_rules_users'
4083 __table_args__ = (
4083 __table_args__ = (
4084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4086 )
4086 )
4087 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4087 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4088 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4088 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4089 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4089 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4090 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4090 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4091 user = relationship('User')
4091 user = relationship('User')
4092
4092
4093 def rule_data(self):
4093 def rule_data(self):
4094 return {
4094 return {
4095 'mandatory': self.mandatory
4095 'mandatory': self.mandatory
4096 }
4096 }
4097
4097
4098
4098
4099 class RepoReviewRuleUserGroup(Base, BaseModel):
4099 class RepoReviewRuleUserGroup(Base, BaseModel):
4100 __tablename__ = 'repo_review_rules_users_groups'
4100 __tablename__ = 'repo_review_rules_users_groups'
4101 __table_args__ = (
4101 __table_args__ = (
4102 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4102 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4103 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4103 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4104 )
4104 )
4105 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4105 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4106 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4106 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4107 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4107 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4108 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4108 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4109 users_group = relationship('UserGroup')
4109 users_group = relationship('UserGroup')
4110
4110
4111 def rule_data(self):
4111 def rule_data(self):
4112 return {
4112 return {
4113 'mandatory': self.mandatory
4113 'mandatory': self.mandatory
4114 }
4114 }
4115
4115
4116
4116
4117 class RepoReviewRule(Base, BaseModel):
4117 class RepoReviewRule(Base, BaseModel):
4118 __tablename__ = 'repo_review_rules'
4118 __tablename__ = 'repo_review_rules'
4119 __table_args__ = (
4119 __table_args__ = (
4120 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4120 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4121 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4121 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4122 )
4122 )
4123
4123
4124 repo_review_rule_id = Column(
4124 repo_review_rule_id = Column(
4125 'repo_review_rule_id', Integer(), primary_key=True)
4125 'repo_review_rule_id', Integer(), primary_key=True)
4126 repo_id = Column(
4126 repo_id = Column(
4127 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4127 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4128 repo = relationship('Repository', backref='review_rules')
4128 repo = relationship('Repository', backref='review_rules')
4129
4129
4130 review_rule_name = Column('review_rule_name', String(255))
4130 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4131 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4132 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4131 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4133 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4132
4134
4133 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4135 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4134 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4136 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4135 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4137 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4136 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4138 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4137
4139
4138 rule_users = relationship('RepoReviewRuleUser')
4140 rule_users = relationship('RepoReviewRuleUser')
4139 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4141 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4140
4142
4141 @hybrid_property
4142 def branch_pattern(self):
4143 return self._branch_pattern or '*'
4144
4145 def _validate_glob(self, value):
4143 def _validate_glob(self, value):
4146 re.compile('^' + glob2re(value) + '$')
4144 re.compile('^' + glob2re(value) + '$')
4147
4145
4148 @branch_pattern.setter
4146 @hybrid_property
4149 def branch_pattern(self, value):
4147 def source_branch_pattern(self):
4148 return self._branch_pattern or '*'
4149
4150 @source_branch_pattern.setter
4151 def source_branch_pattern(self, value):
4150 self._validate_glob(value)
4152 self._validate_glob(value)
4151 self._branch_pattern = value or '*'
4153 self._branch_pattern = value or '*'
4152
4154
4153 @hybrid_property
4155 @hybrid_property
4156 def target_branch_pattern(self):
4157 return self._target_branch_pattern or '*'
4158
4159 @target_branch_pattern.setter
4160 def target_branch_pattern(self, value):
4161 self._validate_glob(value)
4162 self._target_branch_pattern = value or '*'
4163
4164 @hybrid_property
4154 def file_pattern(self):
4165 def file_pattern(self):
4155 return self._file_pattern or '*'
4166 return self._file_pattern or '*'
4156
4167
4157 @file_pattern.setter
4168 @file_pattern.setter
4158 def file_pattern(self, value):
4169 def file_pattern(self, value):
4159 self._validate_glob(value)
4170 self._validate_glob(value)
4160 self._file_pattern = value or '*'
4171 self._file_pattern = value or '*'
4161
4172
4162 def matches(self, branch, files_changed):
4173 def matches(self, source_branch, target_branch, files_changed):
4163 """
4174 """
4164 Check if this review rule matches a branch/files in a pull request
4175 Check if this review rule matches a branch/files in a pull request
4165
4176
4166 :param branch: branch name for the commit
4177 :param branch: branch name for the commit
4167 :param files_changed: list of file paths changed in the pull request
4178 :param files_changed: list of file paths changed in the pull request
4168 """
4179 """
4169
4180
4170 branch = branch or ''
4181 source_branch = source_branch or ''
4182 target_branch = target_branch or ''
4171 files_changed = files_changed or []
4183 files_changed = files_changed or []
4172
4184
4173 branch_matches = True
4185 branch_matches = True
4174 if branch:
4186 if source_branch or target_branch:
4175 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4187 source_branch_regex = re.compile(
4176 branch_matches = bool(branch_regex.search(branch))
4188 '^' + glob2re(self.source_branch_pattern) + '$')
4189 target_branch_regex = re.compile(
4190 '^' + glob2re(self.target_branch_pattern) + '$')
4191
4192 branch_matches = (
4193 bool(source_branch_regex.search(source_branch)) and
4194 bool(target_branch_regex.search(target_branch))
4195 )
4177
4196
4178 files_matches = True
4197 files_matches = True
4179 if self.file_pattern != '*':
4198 if self.file_pattern != '*':
4180 files_matches = False
4199 files_matches = False
4181 file_regex = re.compile(glob2re(self.file_pattern))
4200 file_regex = re.compile(glob2re(self.file_pattern))
4182 for filename in files_changed:
4201 for filename in files_changed:
4183 if file_regex.search(filename):
4202 if file_regex.search(filename):
4184 files_matches = True
4203 files_matches = True
4185 break
4204 break
4186
4205
4187 return branch_matches and files_matches
4206 return branch_matches and files_matches
4188
4207
4189 @property
4208 @property
4190 def review_users(self):
4209 def review_users(self):
4191 """ Returns the users which this rule applies to """
4210 """ Returns the users which this rule applies to """
4192
4211
4193 users = collections.OrderedDict()
4212 users = collections.OrderedDict()
4194
4213
4195 for rule_user in self.rule_users:
4214 for rule_user in self.rule_users:
4196 if rule_user.user.active:
4215 if rule_user.user.active:
4197 if rule_user.user not in users:
4216 if rule_user.user not in users:
4198 users[rule_user.user.username] = {
4217 users[rule_user.user.username] = {
4199 'user': rule_user.user,
4218 'user': rule_user.user,
4200 'source': 'user',
4219 'source': 'user',
4201 'source_data': {},
4220 'source_data': {},
4202 'data': rule_user.rule_data()
4221 'data': rule_user.rule_data()
4203 }
4222 }
4204
4223
4205 for rule_user_group in self.rule_user_groups:
4224 for rule_user_group in self.rule_user_groups:
4206 source_data = {
4225 source_data = {
4207 'name': rule_user_group.users_group.users_group_name,
4226 'name': rule_user_group.users_group.users_group_name,
4208 'members': len(rule_user_group.users_group.members)
4227 'members': len(rule_user_group.users_group.members)
4209 }
4228 }
4210 for member in rule_user_group.users_group.members:
4229 for member in rule_user_group.users_group.members:
4211 if member.user.active:
4230 if member.user.active:
4212 users[member.user.username] = {
4231 users[member.user.username] = {
4213 'user': member.user,
4232 'user': member.user,
4214 'source': 'user_group',
4233 'source': 'user_group',
4215 'source_data': source_data,
4234 'source_data': source_data,
4216 'data': rule_user_group.rule_data()
4235 'data': rule_user_group.rule_data()
4217 }
4236 }
4218
4237
4219 return users
4238 return users
4220
4239
4221 def __repr__(self):
4240 def __repr__(self):
4222 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4241 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4223 self.repo_review_rule_id, self.repo)
4242 self.repo_review_rule_id, self.repo)
4224
4243
4225
4244
4226 class ScheduleEntry(Base, BaseModel):
4245 class ScheduleEntry(Base, BaseModel):
4227 __tablename__ = 'schedule_entries'
4246 __tablename__ = 'schedule_entries'
4228 __table_args__ = (
4247 __table_args__ = (
4229 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4248 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4230 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4249 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4231 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4232 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4251 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4233 )
4252 )
4234 schedule_types = ['crontab', 'timedelta', 'integer']
4253 schedule_types = ['crontab', 'timedelta', 'integer']
4235 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4254 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4236
4255
4237 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4256 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4238 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4257 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4239 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4258 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4240
4259
4241 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4260 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4242 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4261 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4243
4262
4244 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4263 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4245 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4264 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4246
4265
4247 # task
4266 # task
4248 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4267 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4249 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4268 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4250 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4269 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4251 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4270 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4252
4271
4253 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4272 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4254 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4273 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4255
4274
4256 @hybrid_property
4275 @hybrid_property
4257 def schedule_type(self):
4276 def schedule_type(self):
4258 return self._schedule_type
4277 return self._schedule_type
4259
4278
4260 @schedule_type.setter
4279 @schedule_type.setter
4261 def schedule_type(self, val):
4280 def schedule_type(self, val):
4262 if val not in self.schedule_types:
4281 if val not in self.schedule_types:
4263 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4282 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4264 val, self.schedule_type))
4283 val, self.schedule_type))
4265
4284
4266 self._schedule_type = val
4285 self._schedule_type = val
4267
4286
4268 @classmethod
4287 @classmethod
4269 def get_uid(cls, obj):
4288 def get_uid(cls, obj):
4270 args = obj.task_args
4289 args = obj.task_args
4271 kwargs = obj.task_kwargs
4290 kwargs = obj.task_kwargs
4272 if isinstance(args, JsonRaw):
4291 if isinstance(args, JsonRaw):
4273 try:
4292 try:
4274 args = json.loads(args)
4293 args = json.loads(args)
4275 except ValueError:
4294 except ValueError:
4276 args = tuple()
4295 args = tuple()
4277
4296
4278 if isinstance(kwargs, JsonRaw):
4297 if isinstance(kwargs, JsonRaw):
4279 try:
4298 try:
4280 kwargs = json.loads(kwargs)
4299 kwargs = json.loads(kwargs)
4281 except ValueError:
4300 except ValueError:
4282 kwargs = dict()
4301 kwargs = dict()
4283
4302
4284 dot_notation = obj.task_dot_notation
4303 dot_notation = obj.task_dot_notation
4285 val = '.'.join(map(safe_str, [
4304 val = '.'.join(map(safe_str, [
4286 sorted(dot_notation), args, sorted(kwargs.items())]))
4305 sorted(dot_notation), args, sorted(kwargs.items())]))
4287 return hashlib.sha1(val).hexdigest()
4306 return hashlib.sha1(val).hexdigest()
4288
4307
4289 @classmethod
4308 @classmethod
4290 def get_by_schedule_name(cls, schedule_name):
4309 def get_by_schedule_name(cls, schedule_name):
4291 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4310 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4292
4311
4293 @classmethod
4312 @classmethod
4294 def get_by_schedule_id(cls, schedule_id):
4313 def get_by_schedule_id(cls, schedule_id):
4295 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4314 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4296
4315
4297 @property
4316 @property
4298 def task(self):
4317 def task(self):
4299 return self.task_dot_notation
4318 return self.task_dot_notation
4300
4319
4301 @property
4320 @property
4302 def schedule(self):
4321 def schedule(self):
4303 from rhodecode.lib.celerylib.utils import raw_2_schedule
4322 from rhodecode.lib.celerylib.utils import raw_2_schedule
4304 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4323 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4305 return schedule
4324 return schedule
4306
4325
4307 @property
4326 @property
4308 def args(self):
4327 def args(self):
4309 try:
4328 try:
4310 return list(self.task_args or [])
4329 return list(self.task_args or [])
4311 except ValueError:
4330 except ValueError:
4312 return list()
4331 return list()
4313
4332
4314 @property
4333 @property
4315 def kwargs(self):
4334 def kwargs(self):
4316 try:
4335 try:
4317 return dict(self.task_kwargs or {})
4336 return dict(self.task_kwargs or {})
4318 except ValueError:
4337 except ValueError:
4319 return dict()
4338 return dict()
4320
4339
4321 def _as_raw(self, val):
4340 def _as_raw(self, val):
4322 if hasattr(val, 'de_coerce'):
4341 if hasattr(val, 'de_coerce'):
4323 val = val.de_coerce()
4342 val = val.de_coerce()
4324 if val:
4343 if val:
4325 val = json.dumps(val)
4344 val = json.dumps(val)
4326
4345
4327 return val
4346 return val
4328
4347
4329 @property
4348 @property
4330 def schedule_definition_raw(self):
4349 def schedule_definition_raw(self):
4331 return self._as_raw(self.schedule_definition)
4350 return self._as_raw(self.schedule_definition)
4332
4351
4333 @property
4352 @property
4334 def args_raw(self):
4353 def args_raw(self):
4335 return self._as_raw(self.task_args)
4354 return self._as_raw(self.task_args)
4336
4355
4337 @property
4356 @property
4338 def kwargs_raw(self):
4357 def kwargs_raw(self):
4339 return self._as_raw(self.task_kwargs)
4358 return self._as_raw(self.task_kwargs)
4340
4359
4341 def __repr__(self):
4360 def __repr__(self):
4342 return '<DB:ScheduleEntry({}:{})>'.format(
4361 return '<DB:ScheduleEntry({}:{})>'.format(
4343 self.schedule_entry_id, self.schedule_name)
4362 self.schedule_entry_id, self.schedule_name)
4344
4363
4345
4364
4346 @event.listens_for(ScheduleEntry, 'before_update')
4365 @event.listens_for(ScheduleEntry, 'before_update')
4347 def update_task_uid(mapper, connection, target):
4366 def update_task_uid(mapper, connection, target):
4348 target.task_uid = ScheduleEntry.get_uid(target)
4367 target.task_uid = ScheduleEntry.get_uid(target)
4349
4368
4350
4369
4351 @event.listens_for(ScheduleEntry, 'before_insert')
4370 @event.listens_for(ScheduleEntry, 'before_insert')
4352 def set_task_uid(mapper, connection, target):
4371 def set_task_uid(mapper, connection, target):
4353 target.task_uid = ScheduleEntry.get_uid(target)
4372 target.task_uid = ScheduleEntry.get_uid(target)
4354
4373
4355
4374
4356 class DbMigrateVersion(Base, BaseModel):
4375 class DbMigrateVersion(Base, BaseModel):
4357 __tablename__ = 'db_migrate_version'
4376 __tablename__ = 'db_migrate_version'
4358 __table_args__ = (
4377 __table_args__ = (
4359 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4378 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4360 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4379 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4361 )
4380 )
4362 repository_id = Column('repository_id', String(250), primary_key=True)
4381 repository_id = Column('repository_id', String(250), primary_key=True)
4363 repository_path = Column('repository_path', Text)
4382 repository_path = Column('repository_path', Text)
4364 version = Column('version', Integer)
4383 version = Column('version', Integer)
4365
4384
4366
4385
4367 class DbSession(Base, BaseModel):
4386 class DbSession(Base, BaseModel):
4368 __tablename__ = 'db_session'
4387 __tablename__ = 'db_session'
4369 __table_args__ = (
4388 __table_args__ = (
4370 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4389 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4371 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4390 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4372 )
4391 )
4373
4392
4374 def __repr__(self):
4393 def __repr__(self):
4375 return '<DB:DbSession({})>'.format(self.id)
4394 return '<DB:DbSession({})>'.format(self.id)
4376
4395
4377 id = Column('id', Integer())
4396 id = Column('id', Integer())
4378 namespace = Column('namespace', String(255), primary_key=True)
4397 namespace = Column('namespace', String(255), primary_key=True)
4379 accessed = Column('accessed', DateTime, nullable=False)
4398 accessed = Column('accessed', DateTime, nullable=False)
4380 created = Column('created', DateTime, nullable=False)
4399 created = Column('created', DateTime, nullable=False)
4381 data = Column('data', PickleType, nullable=False)
4400 data = Column('data', PickleType, nullable=False)
@@ -1,2408 +1,2413 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'fonts';
8 @import 'fonts';
9 @import 'variables';
9 @import 'variables';
10 @import 'bootstrap-variables';
10 @import 'bootstrap-variables';
11 @import 'form-bootstrap';
11 @import 'form-bootstrap';
12 @import 'codemirror';
12 @import 'codemirror';
13 @import 'legacy_code_styles';
13 @import 'legacy_code_styles';
14 @import 'readme-box';
14 @import 'readme-box';
15 @import 'progress-bar';
15 @import 'progress-bar';
16
16
17 @import 'type';
17 @import 'type';
18 @import 'alerts';
18 @import 'alerts';
19 @import 'buttons';
19 @import 'buttons';
20 @import 'tags';
20 @import 'tags';
21 @import 'code-block';
21 @import 'code-block';
22 @import 'examples';
22 @import 'examples';
23 @import 'login';
23 @import 'login';
24 @import 'main-content';
24 @import 'main-content';
25 @import 'select2';
25 @import 'select2';
26 @import 'comments';
26 @import 'comments';
27 @import 'panels-bootstrap';
27 @import 'panels-bootstrap';
28 @import 'panels';
28 @import 'panels';
29 @import 'deform';
29 @import 'deform';
30
30
31 //--- BASE ------------------//
31 //--- BASE ------------------//
32 .noscript-error {
32 .noscript-error {
33 top: 0;
33 top: 0;
34 left: 0;
34 left: 0;
35 width: 100%;
35 width: 100%;
36 z-index: 101;
36 z-index: 101;
37 text-align: center;
37 text-align: center;
38 font-family: @text-semibold;
38 font-family: @text-semibold;
39 font-size: 120%;
39 font-size: 120%;
40 color: white;
40 color: white;
41 background-color: @alert2;
41 background-color: @alert2;
42 padding: 5px 0 5px 0;
42 padding: 5px 0 5px 0;
43 }
43 }
44
44
45 html {
45 html {
46 display: table;
46 display: table;
47 height: 100%;
47 height: 100%;
48 width: 100%;
48 width: 100%;
49 }
49 }
50
50
51 body {
51 body {
52 display: table-cell;
52 display: table-cell;
53 width: 100%;
53 width: 100%;
54 }
54 }
55
55
56 //--- LAYOUT ------------------//
56 //--- LAYOUT ------------------//
57
57
58 .hidden{
58 .hidden{
59 display: none !important;
59 display: none !important;
60 }
60 }
61
61
62 .box{
62 .box{
63 float: left;
63 float: left;
64 width: 100%;
64 width: 100%;
65 }
65 }
66
66
67 .browser-header {
67 .browser-header {
68 clear: both;
68 clear: both;
69 }
69 }
70 .main {
70 .main {
71 clear: both;
71 clear: both;
72 padding:0 0 @pagepadding;
72 padding:0 0 @pagepadding;
73 height: auto;
73 height: auto;
74
74
75 &:after { //clearfix
75 &:after { //clearfix
76 content:"";
76 content:"";
77 clear:both;
77 clear:both;
78 width:100%;
78 width:100%;
79 display:block;
79 display:block;
80 }
80 }
81 }
81 }
82
82
83 .action-link{
83 .action-link{
84 margin-left: @padding;
84 margin-left: @padding;
85 padding-left: @padding;
85 padding-left: @padding;
86 border-left: @border-thickness solid @border-default-color;
86 border-left: @border-thickness solid @border-default-color;
87 }
87 }
88
88
89 input + .action-link, .action-link.first{
89 input + .action-link, .action-link.first{
90 border-left: none;
90 border-left: none;
91 }
91 }
92
92
93 .action-link.last{
93 .action-link.last{
94 margin-right: @padding;
94 margin-right: @padding;
95 padding-right: @padding;
95 padding-right: @padding;
96 }
96 }
97
97
98 .action-link.active,
98 .action-link.active,
99 .action-link.active a{
99 .action-link.active a{
100 color: @grey4;
100 color: @grey4;
101 }
101 }
102
102
103 .action-link.disabled {
103 .action-link.disabled {
104 color: @grey4;
104 color: @grey4;
105 cursor: inherit;
105 cursor: inherit;
106 }
106 }
107
107
108 .clipboard-action {
108 .clipboard-action {
109 cursor: pointer;
109 cursor: pointer;
110 }
110 }
111
111
112 ul.simple-list{
112 ul.simple-list{
113 list-style: none;
113 list-style: none;
114 margin: 0;
114 margin: 0;
115 padding: 0;
115 padding: 0;
116 }
116 }
117
117
118 .main-content {
118 .main-content {
119 padding-bottom: @pagepadding;
119 padding-bottom: @pagepadding;
120 }
120 }
121
121
122 .wide-mode-wrapper {
122 .wide-mode-wrapper {
123 max-width:4000px !important;
123 max-width:4000px !important;
124 }
124 }
125
125
126 .wrapper {
126 .wrapper {
127 position: relative;
127 position: relative;
128 max-width: @wrapper-maxwidth;
128 max-width: @wrapper-maxwidth;
129 margin: 0 auto;
129 margin: 0 auto;
130 }
130 }
131
131
132 #content {
132 #content {
133 clear: both;
133 clear: both;
134 padding: 0 @contentpadding;
134 padding: 0 @contentpadding;
135 }
135 }
136
136
137 .advanced-settings-fields{
137 .advanced-settings-fields{
138 input{
138 input{
139 margin-left: @textmargin;
139 margin-left: @textmargin;
140 margin-right: @padding/2;
140 margin-right: @padding/2;
141 }
141 }
142 }
142 }
143
143
144 .cs_files_title {
144 .cs_files_title {
145 margin: @pagepadding 0 0;
145 margin: @pagepadding 0 0;
146 }
146 }
147
147
148 input.inline[type="file"] {
148 input.inline[type="file"] {
149 display: inline;
149 display: inline;
150 }
150 }
151
151
152 .error_page {
152 .error_page {
153 margin: 10% auto;
153 margin: 10% auto;
154
154
155 h1 {
155 h1 {
156 color: @grey2;
156 color: @grey2;
157 }
157 }
158
158
159 .alert {
159 .alert {
160 margin: @padding 0;
160 margin: @padding 0;
161 }
161 }
162
162
163 .error-branding {
163 .error-branding {
164 font-family: @text-semibold;
164 font-family: @text-semibold;
165 color: @grey4;
165 color: @grey4;
166 }
166 }
167
167
168 .error_message {
168 .error_message {
169 font-family: @text-regular;
169 font-family: @text-regular;
170 }
170 }
171
171
172 .sidebar {
172 .sidebar {
173 min-height: 275px;
173 min-height: 275px;
174 margin: 0;
174 margin: 0;
175 padding: 0 0 @sidebarpadding @sidebarpadding;
175 padding: 0 0 @sidebarpadding @sidebarpadding;
176 border: none;
176 border: none;
177 }
177 }
178
178
179 .main-content {
179 .main-content {
180 position: relative;
180 position: relative;
181 margin: 0 @sidebarpadding @sidebarpadding;
181 margin: 0 @sidebarpadding @sidebarpadding;
182 padding: 0 0 0 @sidebarpadding;
182 padding: 0 0 0 @sidebarpadding;
183 border-left: @border-thickness solid @grey5;
183 border-left: @border-thickness solid @grey5;
184
184
185 @media (max-width:767px) {
185 @media (max-width:767px) {
186 clear: both;
186 clear: both;
187 width: 100%;
187 width: 100%;
188 margin: 0;
188 margin: 0;
189 border: none;
189 border: none;
190 }
190 }
191 }
191 }
192
192
193 .inner-column {
193 .inner-column {
194 float: left;
194 float: left;
195 width: 29.75%;
195 width: 29.75%;
196 min-height: 150px;
196 min-height: 150px;
197 margin: @sidebarpadding 2% 0 0;
197 margin: @sidebarpadding 2% 0 0;
198 padding: 0 2% 0 0;
198 padding: 0 2% 0 0;
199 border-right: @border-thickness solid @grey5;
199 border-right: @border-thickness solid @grey5;
200
200
201 @media (max-width:767px) {
201 @media (max-width:767px) {
202 clear: both;
202 clear: both;
203 width: 100%;
203 width: 100%;
204 border: none;
204 border: none;
205 }
205 }
206
206
207 ul {
207 ul {
208 padding-left: 1.25em;
208 padding-left: 1.25em;
209 }
209 }
210
210
211 &:last-child {
211 &:last-child {
212 margin: @sidebarpadding 0 0;
212 margin: @sidebarpadding 0 0;
213 border: none;
213 border: none;
214 }
214 }
215
215
216 h4 {
216 h4 {
217 margin: 0 0 @padding;
217 margin: 0 0 @padding;
218 font-family: @text-semibold;
218 font-family: @text-semibold;
219 }
219 }
220 }
220 }
221 }
221 }
222 .error-page-logo {
222 .error-page-logo {
223 width: 130px;
223 width: 130px;
224 height: 160px;
224 height: 160px;
225 }
225 }
226
226
227 // HEADER
227 // HEADER
228 .header {
228 .header {
229
229
230 // TODO: johbo: Fix login pages, so that they work without a min-height
230 // TODO: johbo: Fix login pages, so that they work without a min-height
231 // for the header and then remove the min-height. I chose a smaller value
231 // for the header and then remove the min-height. I chose a smaller value
232 // intentionally here to avoid rendering issues in the main navigation.
232 // intentionally here to avoid rendering issues in the main navigation.
233 min-height: 49px;
233 min-height: 49px;
234
234
235 position: relative;
235 position: relative;
236 vertical-align: bottom;
236 vertical-align: bottom;
237 padding: 0 @header-padding;
237 padding: 0 @header-padding;
238 background-color: @grey2;
238 background-color: @grey2;
239 color: @grey5;
239 color: @grey5;
240
240
241 .title {
241 .title {
242 overflow: visible;
242 overflow: visible;
243 }
243 }
244
244
245 &:before,
245 &:before,
246 &:after {
246 &:after {
247 content: "";
247 content: "";
248 clear: both;
248 clear: both;
249 width: 100%;
249 width: 100%;
250 }
250 }
251
251
252 // TODO: johbo: Avoids breaking "Repositories" chooser
252 // TODO: johbo: Avoids breaking "Repositories" chooser
253 .select2-container .select2-choice .select2-arrow {
253 .select2-container .select2-choice .select2-arrow {
254 display: none;
254 display: none;
255 }
255 }
256 }
256 }
257
257
258 #header-inner {
258 #header-inner {
259 &.title {
259 &.title {
260 margin: 0;
260 margin: 0;
261 }
261 }
262 &:before,
262 &:before,
263 &:after {
263 &:after {
264 content: "";
264 content: "";
265 clear: both;
265 clear: both;
266 }
266 }
267 }
267 }
268
268
269 // Gists
269 // Gists
270 #files_data {
270 #files_data {
271 clear: both; //for firefox
271 clear: both; //for firefox
272 }
272 }
273 #gistid {
273 #gistid {
274 margin-right: @padding;
274 margin-right: @padding;
275 }
275 }
276
276
277 // Global Settings Editor
277 // Global Settings Editor
278 .textarea.editor {
278 .textarea.editor {
279 float: left;
279 float: left;
280 position: relative;
280 position: relative;
281 max-width: @texteditor-width;
281 max-width: @texteditor-width;
282
282
283 select {
283 select {
284 position: absolute;
284 position: absolute;
285 top:10px;
285 top:10px;
286 right:0;
286 right:0;
287 }
287 }
288
288
289 .CodeMirror {
289 .CodeMirror {
290 margin: 0;
290 margin: 0;
291 }
291 }
292
292
293 .help-block {
293 .help-block {
294 margin: 0 0 @padding;
294 margin: 0 0 @padding;
295 padding:.5em;
295 padding:.5em;
296 background-color: @grey6;
296 background-color: @grey6;
297 &.pre-formatting {
297 &.pre-formatting {
298 white-space: pre;
298 white-space: pre;
299 }
299 }
300 }
300 }
301 }
301 }
302
302
303 ul.auth_plugins {
303 ul.auth_plugins {
304 margin: @padding 0 @padding @legend-width;
304 margin: @padding 0 @padding @legend-width;
305 padding: 0;
305 padding: 0;
306
306
307 li {
307 li {
308 margin-bottom: @padding;
308 margin-bottom: @padding;
309 line-height: 1em;
309 line-height: 1em;
310 list-style-type: none;
310 list-style-type: none;
311
311
312 .auth_buttons .btn {
312 .auth_buttons .btn {
313 margin-right: @padding;
313 margin-right: @padding;
314 }
314 }
315
315
316 &:before { content: none; }
316 &:before { content: none; }
317 }
317 }
318 }
318 }
319
319
320
320
321 // My Account PR list
321 // My Account PR list
322
322
323 #show_closed {
323 #show_closed {
324 margin: 0 1em 0 0;
324 margin: 0 1em 0 0;
325 }
325 }
326
326
327 .pullrequestlist {
327 .pullrequestlist {
328 .closed {
328 .closed {
329 background-color: @grey6;
329 background-color: @grey6;
330 }
330 }
331 .td-status {
331 .td-status {
332 padding-left: .5em;
332 padding-left: .5em;
333 }
333 }
334 .log-container .truncate {
334 .log-container .truncate {
335 height: 2.75em;
335 height: 2.75em;
336 white-space: pre-line;
336 white-space: pre-line;
337 }
337 }
338 table.rctable .user {
338 table.rctable .user {
339 padding-left: 0;
339 padding-left: 0;
340 }
340 }
341 table.rctable {
341 table.rctable {
342 td.td-description,
342 td.td-description,
343 .rc-user {
343 .rc-user {
344 min-width: auto;
344 min-width: auto;
345 }
345 }
346 }
346 }
347 }
347 }
348
348
349 // Pull Requests
349 // Pull Requests
350
350
351 .pullrequests_section_head {
351 .pullrequests_section_head {
352 display: block;
352 display: block;
353 clear: both;
353 clear: both;
354 margin: @padding 0;
354 margin: @padding 0;
355 font-family: @text-bold;
355 font-family: @text-bold;
356 }
356 }
357
357
358 .pr-origininfo, .pr-targetinfo {
358 .pr-origininfo, .pr-targetinfo {
359 position: relative;
359 position: relative;
360
360
361 .tag {
361 .tag {
362 display: inline-block;
362 display: inline-block;
363 margin: 0 1em .5em 0;
363 margin: 0 1em .5em 0;
364 }
364 }
365
365
366 .clone-url {
366 .clone-url {
367 display: inline-block;
367 display: inline-block;
368 margin: 0 0 .5em 0;
368 margin: 0 0 .5em 0;
369 padding: 0;
369 padding: 0;
370 line-height: 1.2em;
370 line-height: 1.2em;
371 }
371 }
372 }
372 }
373
373
374 .pr-mergeinfo {
374 .pr-mergeinfo {
375 min-width: 95% !important;
375 min-width: 95% !important;
376 padding: 0 !important;
376 padding: 0 !important;
377 border: 0;
377 border: 0;
378 }
378 }
379 .pr-mergeinfo-copy {
379 .pr-mergeinfo-copy {
380 padding: 0 0;
380 padding: 0 0;
381 }
381 }
382
382
383 .pr-pullinfo {
383 .pr-pullinfo {
384 min-width: 95% !important;
384 min-width: 95% !important;
385 padding: 0 !important;
385 padding: 0 !important;
386 border: 0;
386 border: 0;
387 }
387 }
388 .pr-pullinfo-copy {
388 .pr-pullinfo-copy {
389 padding: 0 0;
389 padding: 0 0;
390 }
390 }
391
391
392
392
393 #pr-title-input {
393 #pr-title-input {
394 width: 72%;
394 width: 72%;
395 font-size: 1em;
395 font-size: 1em;
396 font-family: @text-bold;
396 font-family: @text-bold;
397 margin: 0;
397 margin: 0;
398 padding: 0 0 0 @padding/4;
398 padding: 0 0 0 @padding/4;
399 line-height: 1.7em;
399 line-height: 1.7em;
400 color: @text-color;
400 color: @text-color;
401 letter-spacing: .02em;
401 letter-spacing: .02em;
402 }
402 }
403
403
404 #pullrequest_title {
404 #pullrequest_title {
405 width: 100%;
405 width: 100%;
406 box-sizing: border-box;
406 box-sizing: border-box;
407 }
407 }
408
408
409 #pr_open_message {
409 #pr_open_message {
410 border: @border-thickness solid #fff;
410 border: @border-thickness solid #fff;
411 border-radius: @border-radius;
411 border-radius: @border-radius;
412 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
412 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
413 text-align: left;
413 text-align: left;
414 overflow: hidden;
414 overflow: hidden;
415 }
415 }
416
416
417 .pr-submit-button {
417 .pr-submit-button {
418 float: right;
418 float: right;
419 margin: 0 0 0 5px;
419 margin: 0 0 0 5px;
420 }
420 }
421
421
422 .pr-spacing-container {
422 .pr-spacing-container {
423 padding: 20px;
423 padding: 20px;
424 clear: both
424 clear: both
425 }
425 }
426
426
427 #pr-description-input {
427 #pr-description-input {
428 margin-bottom: 0;
428 margin-bottom: 0;
429 }
429 }
430
430
431 .pr-description-label {
431 .pr-description-label {
432 vertical-align: top;
432 vertical-align: top;
433 }
433 }
434
434
435 .perms_section_head {
435 .perms_section_head {
436 min-width: 625px;
436 min-width: 625px;
437
437
438 h2 {
438 h2 {
439 margin-bottom: 0;
439 margin-bottom: 0;
440 }
440 }
441
441
442 .label-checkbox {
442 .label-checkbox {
443 float: left;
443 float: left;
444 }
444 }
445
445
446 &.field {
446 &.field {
447 margin: @space 0 @padding;
447 margin: @space 0 @padding;
448 }
448 }
449
449
450 &:first-child.field {
450 &:first-child.field {
451 margin-top: 0;
451 margin-top: 0;
452
452
453 .label {
453 .label {
454 margin-top: 0;
454 margin-top: 0;
455 padding-top: 0;
455 padding-top: 0;
456 }
456 }
457
457
458 .radios {
458 .radios {
459 padding-top: 0;
459 padding-top: 0;
460 }
460 }
461 }
461 }
462
462
463 .radios {
463 .radios {
464 position: relative;
464 position: relative;
465 width: 405px;
465 width: 405px;
466 }
466 }
467 }
467 }
468
468
469 //--- MODULES ------------------//
469 //--- MODULES ------------------//
470
470
471
471
472 // Server Announcement
472 // Server Announcement
473 #server-announcement {
473 #server-announcement {
474 width: 95%;
474 width: 95%;
475 margin: @padding auto;
475 margin: @padding auto;
476 padding: @padding;
476 padding: @padding;
477 border-width: 2px;
477 border-width: 2px;
478 border-style: solid;
478 border-style: solid;
479 .border-radius(2px);
479 .border-radius(2px);
480 font-family: @text-bold;
480 font-family: @text-bold;
481
481
482 &.info { border-color: @alert4; background-color: @alert4-inner; }
482 &.info { border-color: @alert4; background-color: @alert4-inner; }
483 &.warning { border-color: @alert3; background-color: @alert3-inner; }
483 &.warning { border-color: @alert3; background-color: @alert3-inner; }
484 &.error { border-color: @alert2; background-color: @alert2-inner; }
484 &.error { border-color: @alert2; background-color: @alert2-inner; }
485 &.success { border-color: @alert1; background-color: @alert1-inner; }
485 &.success { border-color: @alert1; background-color: @alert1-inner; }
486 &.neutral { border-color: @grey3; background-color: @grey6; }
486 &.neutral { border-color: @grey3; background-color: @grey6; }
487 }
487 }
488
488
489 // Fixed Sidebar Column
489 // Fixed Sidebar Column
490 .sidebar-col-wrapper {
490 .sidebar-col-wrapper {
491 padding-left: @sidebar-all-width;
491 padding-left: @sidebar-all-width;
492
492
493 .sidebar {
493 .sidebar {
494 width: @sidebar-width;
494 width: @sidebar-width;
495 margin-left: -@sidebar-all-width;
495 margin-left: -@sidebar-all-width;
496 }
496 }
497 }
497 }
498
498
499 .sidebar-col-wrapper.scw-small {
499 .sidebar-col-wrapper.scw-small {
500 padding-left: @sidebar-small-all-width;
500 padding-left: @sidebar-small-all-width;
501
501
502 .sidebar {
502 .sidebar {
503 width: @sidebar-small-width;
503 width: @sidebar-small-width;
504 margin-left: -@sidebar-small-all-width;
504 margin-left: -@sidebar-small-all-width;
505 }
505 }
506 }
506 }
507
507
508
508
509 // FOOTER
509 // FOOTER
510 #footer {
510 #footer {
511 padding: 0;
511 padding: 0;
512 text-align: center;
512 text-align: center;
513 vertical-align: middle;
513 vertical-align: middle;
514 color: @grey2;
514 color: @grey2;
515 background-color: @grey6;
515 background-color: @grey6;
516
516
517 p {
517 p {
518 margin: 0;
518 margin: 0;
519 padding: 1em;
519 padding: 1em;
520 line-height: 1em;
520 line-height: 1em;
521 }
521 }
522
522
523 .server-instance { //server instance
523 .server-instance { //server instance
524 display: none;
524 display: none;
525 }
525 }
526
526
527 .title {
527 .title {
528 float: none;
528 float: none;
529 margin: 0 auto;
529 margin: 0 auto;
530 }
530 }
531 }
531 }
532
532
533 button.close {
533 button.close {
534 padding: 0;
534 padding: 0;
535 cursor: pointer;
535 cursor: pointer;
536 background: transparent;
536 background: transparent;
537 border: 0;
537 border: 0;
538 .box-shadow(none);
538 .box-shadow(none);
539 -webkit-appearance: none;
539 -webkit-appearance: none;
540 }
540 }
541
541
542 .close {
542 .close {
543 float: right;
543 float: right;
544 font-size: 21px;
544 font-size: 21px;
545 font-family: @text-bootstrap;
545 font-family: @text-bootstrap;
546 line-height: 1em;
546 line-height: 1em;
547 font-weight: bold;
547 font-weight: bold;
548 color: @grey2;
548 color: @grey2;
549
549
550 &:hover,
550 &:hover,
551 &:focus {
551 &:focus {
552 color: @grey1;
552 color: @grey1;
553 text-decoration: none;
553 text-decoration: none;
554 cursor: pointer;
554 cursor: pointer;
555 }
555 }
556 }
556 }
557
557
558 // GRID
558 // GRID
559 .sorting,
559 .sorting,
560 .sorting_desc,
560 .sorting_desc,
561 .sorting_asc {
561 .sorting_asc {
562 cursor: pointer;
562 cursor: pointer;
563 }
563 }
564 .sorting_desc:after {
564 .sorting_desc:after {
565 content: "\00A0\25B2";
565 content: "\00A0\25B2";
566 font-size: .75em;
566 font-size: .75em;
567 }
567 }
568 .sorting_asc:after {
568 .sorting_asc:after {
569 content: "\00A0\25BC";
569 content: "\00A0\25BC";
570 font-size: .68em;
570 font-size: .68em;
571 }
571 }
572
572
573
573
574 .user_auth_tokens {
574 .user_auth_tokens {
575
575
576 &.truncate {
576 &.truncate {
577 white-space: nowrap;
577 white-space: nowrap;
578 overflow: hidden;
578 overflow: hidden;
579 text-overflow: ellipsis;
579 text-overflow: ellipsis;
580 }
580 }
581
581
582 .fields .field .input {
582 .fields .field .input {
583 margin: 0;
583 margin: 0;
584 }
584 }
585
585
586 input#description {
586 input#description {
587 width: 100px;
587 width: 100px;
588 margin: 0;
588 margin: 0;
589 }
589 }
590
590
591 .drop-menu {
591 .drop-menu {
592 // TODO: johbo: Remove this, should work out of the box when
592 // TODO: johbo: Remove this, should work out of the box when
593 // having multiple inputs inline
593 // having multiple inputs inline
594 margin: 0 0 0 5px;
594 margin: 0 0 0 5px;
595 }
595 }
596 }
596 }
597 #user_list_table {
597 #user_list_table {
598 .closed {
598 .closed {
599 background-color: @grey6;
599 background-color: @grey6;
600 }
600 }
601 }
601 }
602
602
603
603
604 input {
604 input {
605 &.disabled {
605 &.disabled {
606 opacity: .5;
606 opacity: .5;
607 }
607 }
608 }
608 }
609
609
610 // remove extra padding in firefox
610 // remove extra padding in firefox
611 input::-moz-focus-inner { border:0; padding:0 }
611 input::-moz-focus-inner { border:0; padding:0 }
612
612
613 .adjacent input {
613 .adjacent input {
614 margin-bottom: @padding;
614 margin-bottom: @padding;
615 }
615 }
616
616
617 .permissions_boxes {
617 .permissions_boxes {
618 display: block;
618 display: block;
619 }
619 }
620
620
621 //TODO: lisa: this should be in tables
621 //TODO: lisa: this should be in tables
622 .show_more_col {
622 .show_more_col {
623 width: 20px;
623 width: 20px;
624 }
624 }
625
625
626 //FORMS
626 //FORMS
627
627
628 .medium-inline,
628 .medium-inline,
629 input#description.medium-inline {
629 input#description.medium-inline {
630 display: inline;
630 display: inline;
631 width: @medium-inline-input-width;
631 width: @medium-inline-input-width;
632 min-width: 100px;
632 min-width: 100px;
633 }
633 }
634
634
635 select {
635 select {
636 //reset
636 //reset
637 -webkit-appearance: none;
637 -webkit-appearance: none;
638 -moz-appearance: none;
638 -moz-appearance: none;
639
639
640 display: inline-block;
640 display: inline-block;
641 height: 28px;
641 height: 28px;
642 width: auto;
642 width: auto;
643 margin: 0 @padding @padding 0;
643 margin: 0 @padding @padding 0;
644 padding: 0 18px 0 8px;
644 padding: 0 18px 0 8px;
645 line-height:1em;
645 line-height:1em;
646 font-size: @basefontsize;
646 font-size: @basefontsize;
647 border: @border-thickness solid @rcblue;
647 border: @border-thickness solid @rcblue;
648 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
648 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
649 color: @rcblue;
649 color: @rcblue;
650
650
651 &:after {
651 &:after {
652 content: "\00A0\25BE";
652 content: "\00A0\25BE";
653 }
653 }
654
654
655 &:focus {
655 &:focus {
656 outline: none;
656 outline: none;
657 }
657 }
658 }
658 }
659
659
660 option {
660 option {
661 &:focus {
661 &:focus {
662 outline: none;
662 outline: none;
663 }
663 }
664 }
664 }
665
665
666 input,
666 input,
667 textarea {
667 textarea {
668 padding: @input-padding;
668 padding: @input-padding;
669 border: @input-border-thickness solid @border-highlight-color;
669 border: @input-border-thickness solid @border-highlight-color;
670 .border-radius (@border-radius);
670 .border-radius (@border-radius);
671 font-family: @text-light;
671 font-family: @text-light;
672 font-size: @basefontsize;
672 font-size: @basefontsize;
673
673
674 &.input-sm {
674 &.input-sm {
675 padding: 5px;
675 padding: 5px;
676 }
676 }
677
677
678 &#description {
678 &#description {
679 min-width: @input-description-minwidth;
679 min-width: @input-description-minwidth;
680 min-height: 1em;
680 min-height: 1em;
681 padding: 10px;
681 padding: 10px;
682 }
682 }
683 }
683 }
684
684
685 .field-sm {
685 .field-sm {
686 input,
686 input,
687 textarea {
687 textarea {
688 padding: 5px;
688 padding: 5px;
689 }
689 }
690 }
690 }
691
691
692 textarea {
692 textarea {
693 display: block;
693 display: block;
694 clear: both;
694 clear: both;
695 width: 100%;
695 width: 100%;
696 min-height: 100px;
696 min-height: 100px;
697 margin-bottom: @padding;
697 margin-bottom: @padding;
698 .box-sizing(border-box);
698 .box-sizing(border-box);
699 overflow: auto;
699 overflow: auto;
700 }
700 }
701
701
702 label {
702 label {
703 font-family: @text-light;
703 font-family: @text-light;
704 }
704 }
705
705
706 // GRAVATARS
706 // GRAVATARS
707 // centers gravatar on username to the right
707 // centers gravatar on username to the right
708
708
709 .gravatar {
709 .gravatar {
710 display: inline;
710 display: inline;
711 min-width: 16px;
711 min-width: 16px;
712 min-height: 16px;
712 min-height: 16px;
713 margin: -5px 0;
713 margin: -5px 0;
714 padding: 0;
714 padding: 0;
715 line-height: 1em;
715 line-height: 1em;
716 border: 1px solid @grey4;
716 border: 1px solid @grey4;
717 box-sizing: content-box;
717 box-sizing: content-box;
718
718
719 &.gravatar-large {
719 &.gravatar-large {
720 margin: -0.5em .25em -0.5em 0;
720 margin: -0.5em .25em -0.5em 0;
721 }
721 }
722
722
723 & + .user {
723 & + .user {
724 display: inline;
724 display: inline;
725 margin: 0;
725 margin: 0;
726 padding: 0 0 0 .17em;
726 padding: 0 0 0 .17em;
727 line-height: 1em;
727 line-height: 1em;
728 }
728 }
729 }
729 }
730
730
731 .user-inline-data {
731 .user-inline-data {
732 display: inline-block;
732 display: inline-block;
733 float: left;
733 float: left;
734 padding-left: .5em;
734 padding-left: .5em;
735 line-height: 1.3em;
735 line-height: 1.3em;
736 }
736 }
737
737
738 .rc-user { // gravatar + user wrapper
738 .rc-user { // gravatar + user wrapper
739 float: left;
739 float: left;
740 position: relative;
740 position: relative;
741 min-width: 100px;
741 min-width: 100px;
742 max-width: 200px;
742 max-width: 200px;
743 min-height: (@gravatar-size + @border-thickness * 2); // account for border
743 min-height: (@gravatar-size + @border-thickness * 2); // account for border
744 display: block;
744 display: block;
745 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
745 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
746
746
747
747
748 .gravatar {
748 .gravatar {
749 display: block;
749 display: block;
750 position: absolute;
750 position: absolute;
751 top: 0;
751 top: 0;
752 left: 0;
752 left: 0;
753 min-width: @gravatar-size;
753 min-width: @gravatar-size;
754 min-height: @gravatar-size;
754 min-height: @gravatar-size;
755 margin: 0;
755 margin: 0;
756 }
756 }
757
757
758 .user {
758 .user {
759 display: block;
759 display: block;
760 max-width: 175px;
760 max-width: 175px;
761 padding-top: 2px;
761 padding-top: 2px;
762 overflow: hidden;
762 overflow: hidden;
763 text-overflow: ellipsis;
763 text-overflow: ellipsis;
764 }
764 }
765 }
765 }
766
766
767 .gist-gravatar,
767 .gist-gravatar,
768 .journal_container {
768 .journal_container {
769 .gravatar-large {
769 .gravatar-large {
770 margin: 0 .5em -10px 0;
770 margin: 0 .5em -10px 0;
771 }
771 }
772 }
772 }
773
773
774
774
775 // ADMIN SETTINGS
775 // ADMIN SETTINGS
776
776
777 // Tag Patterns
777 // Tag Patterns
778 .tag_patterns {
778 .tag_patterns {
779 .tag_input {
779 .tag_input {
780 margin-bottom: @padding;
780 margin-bottom: @padding;
781 }
781 }
782 }
782 }
783
783
784 .locked_input {
784 .locked_input {
785 position: relative;
785 position: relative;
786
786
787 input {
787 input {
788 display: inline;
788 display: inline;
789 margin: 3px 5px 0px 0px;
789 margin: 3px 5px 0px 0px;
790 }
790 }
791
791
792 br {
792 br {
793 display: none;
793 display: none;
794 }
794 }
795
795
796 .error-message {
796 .error-message {
797 float: left;
797 float: left;
798 width: 100%;
798 width: 100%;
799 }
799 }
800
800
801 .lock_input_button {
801 .lock_input_button {
802 display: inline;
802 display: inline;
803 }
803 }
804
804
805 .help-block {
805 .help-block {
806 clear: both;
806 clear: both;
807 }
807 }
808 }
808 }
809
809
810 // Notifications
810 // Notifications
811
811
812 .notifications_buttons {
812 .notifications_buttons {
813 margin: 0 0 @space 0;
813 margin: 0 0 @space 0;
814 padding: 0;
814 padding: 0;
815
815
816 .btn {
816 .btn {
817 display: inline-block;
817 display: inline-block;
818 }
818 }
819 }
819 }
820
820
821 .notification-list {
821 .notification-list {
822
822
823 div {
823 div {
824 display: inline-block;
824 display: inline-block;
825 vertical-align: middle;
825 vertical-align: middle;
826 }
826 }
827
827
828 .container {
828 .container {
829 display: block;
829 display: block;
830 margin: 0 0 @padding 0;
830 margin: 0 0 @padding 0;
831 }
831 }
832
832
833 .delete-notifications {
833 .delete-notifications {
834 margin-left: @padding;
834 margin-left: @padding;
835 text-align: right;
835 text-align: right;
836 cursor: pointer;
836 cursor: pointer;
837 }
837 }
838
838
839 .read-notifications {
839 .read-notifications {
840 margin-left: @padding/2;
840 margin-left: @padding/2;
841 text-align: right;
841 text-align: right;
842 width: 35px;
842 width: 35px;
843 cursor: pointer;
843 cursor: pointer;
844 }
844 }
845
845
846 .icon-minus-sign {
846 .icon-minus-sign {
847 color: @alert2;
847 color: @alert2;
848 }
848 }
849
849
850 .icon-ok-sign {
850 .icon-ok-sign {
851 color: @alert1;
851 color: @alert1;
852 }
852 }
853 }
853 }
854
854
855 .user_settings {
855 .user_settings {
856 float: left;
856 float: left;
857 clear: both;
857 clear: both;
858 display: block;
858 display: block;
859 width: 100%;
859 width: 100%;
860
860
861 .gravatar_box {
861 .gravatar_box {
862 margin-bottom: @padding;
862 margin-bottom: @padding;
863
863
864 &:after {
864 &:after {
865 content: " ";
865 content: " ";
866 clear: both;
866 clear: both;
867 width: 100%;
867 width: 100%;
868 }
868 }
869 }
869 }
870
870
871 .fields .field {
871 .fields .field {
872 clear: both;
872 clear: both;
873 }
873 }
874 }
874 }
875
875
876 .advanced_settings {
876 .advanced_settings {
877 margin-bottom: @space;
877 margin-bottom: @space;
878
878
879 .help-block {
879 .help-block {
880 margin-left: 0;
880 margin-left: 0;
881 }
881 }
882
882
883 button + .help-block {
883 button + .help-block {
884 margin-top: @padding;
884 margin-top: @padding;
885 }
885 }
886 }
886 }
887
887
888 // admin settings radio buttons and labels
888 // admin settings radio buttons and labels
889 .label-2 {
889 .label-2 {
890 float: left;
890 float: left;
891 width: @label2-width;
891 width: @label2-width;
892
892
893 label {
893 label {
894 color: @grey1;
894 color: @grey1;
895 }
895 }
896 }
896 }
897 .checkboxes {
897 .checkboxes {
898 float: left;
898 float: left;
899 width: @checkboxes-width;
899 width: @checkboxes-width;
900 margin-bottom: @padding;
900 margin-bottom: @padding;
901
901
902 .checkbox {
902 .checkbox {
903 width: 100%;
903 width: 100%;
904
904
905 label {
905 label {
906 margin: 0;
906 margin: 0;
907 padding: 0;
907 padding: 0;
908 }
908 }
909 }
909 }
910
910
911 .checkbox + .checkbox {
911 .checkbox + .checkbox {
912 display: inline-block;
912 display: inline-block;
913 }
913 }
914
914
915 label {
915 label {
916 margin-right: 1em;
916 margin-right: 1em;
917 }
917 }
918 }
918 }
919
919
920 // CHANGELOG
920 // CHANGELOG
921 .container_header {
921 .container_header {
922 float: left;
922 float: left;
923 display: block;
923 display: block;
924 width: 100%;
924 width: 100%;
925 margin: @padding 0 @padding;
925 margin: @padding 0 @padding;
926
926
927 #filter_changelog {
927 #filter_changelog {
928 float: left;
928 float: left;
929 margin-right: @padding;
929 margin-right: @padding;
930 }
930 }
931
931
932 .breadcrumbs_light {
932 .breadcrumbs_light {
933 display: inline-block;
933 display: inline-block;
934 }
934 }
935 }
935 }
936
936
937 .info_box {
937 .info_box {
938 float: right;
938 float: right;
939 }
939 }
940
940
941
941
942 #graph_nodes {
942 #graph_nodes {
943 padding-top: 43px;
943 padding-top: 43px;
944 }
944 }
945
945
946 #graph_content{
946 #graph_content{
947
947
948 // adjust for table headers so that graph renders properly
948 // adjust for table headers so that graph renders properly
949 // #graph_nodes padding - table cell padding
949 // #graph_nodes padding - table cell padding
950 padding-top: (@space - (@basefontsize * 2.4));
950 padding-top: (@space - (@basefontsize * 2.4));
951
951
952 &.graph_full_width {
952 &.graph_full_width {
953 width: 100%;
953 width: 100%;
954 max-width: 100%;
954 max-width: 100%;
955 }
955 }
956 }
956 }
957
957
958 #graph {
958 #graph {
959 .flag_status {
959 .flag_status {
960 margin: 0;
960 margin: 0;
961 }
961 }
962
962
963 .pagination-left {
963 .pagination-left {
964 float: left;
964 float: left;
965 clear: both;
965 clear: both;
966 }
966 }
967
967
968 .log-container {
968 .log-container {
969 max-width: 345px;
969 max-width: 345px;
970
970
971 .message{
971 .message{
972 max-width: 340px;
972 max-width: 340px;
973 }
973 }
974 }
974 }
975
975
976 .graph-col-wrapper {
976 .graph-col-wrapper {
977 padding-left: 110px;
977 padding-left: 110px;
978
978
979 #graph_nodes {
979 #graph_nodes {
980 width: 100px;
980 width: 100px;
981 margin-left: -110px;
981 margin-left: -110px;
982 float: left;
982 float: left;
983 clear: left;
983 clear: left;
984 }
984 }
985 }
985 }
986
986
987 .load-more-commits {
987 .load-more-commits {
988 text-align: center;
988 text-align: center;
989 }
989 }
990 .load-more-commits:hover {
990 .load-more-commits:hover {
991 background-color: @grey7;
991 background-color: @grey7;
992 }
992 }
993 .load-more-commits {
993 .load-more-commits {
994 a {
994 a {
995 display: block;
995 display: block;
996 }
996 }
997 }
997 }
998 }
998 }
999
999
1000 #filter_changelog {
1000 #filter_changelog {
1001 float: left;
1001 float: left;
1002 }
1002 }
1003
1003
1004
1004
1005 //--- THEME ------------------//
1005 //--- THEME ------------------//
1006
1006
1007 #logo {
1007 #logo {
1008 float: left;
1008 float: left;
1009 margin: 9px 0 0 0;
1009 margin: 9px 0 0 0;
1010
1010
1011 .header {
1011 .header {
1012 background-color: transparent;
1012 background-color: transparent;
1013 }
1013 }
1014
1014
1015 a {
1015 a {
1016 display: inline-block;
1016 display: inline-block;
1017 }
1017 }
1018
1018
1019 img {
1019 img {
1020 height:30px;
1020 height:30px;
1021 }
1021 }
1022 }
1022 }
1023
1023
1024 .logo-wrapper {
1024 .logo-wrapper {
1025 float:left;
1025 float:left;
1026 }
1026 }
1027
1027
1028 .branding{
1028 .branding{
1029 float: left;
1029 float: left;
1030 padding: 9px 2px;
1030 padding: 9px 2px;
1031 line-height: 1em;
1031 line-height: 1em;
1032 font-size: @navigation-fontsize;
1032 font-size: @navigation-fontsize;
1033 }
1033 }
1034
1034
1035 img {
1035 img {
1036 border: none;
1036 border: none;
1037 outline: none;
1037 outline: none;
1038 }
1038 }
1039 user-profile-header
1039 user-profile-header
1040 label {
1040 label {
1041
1041
1042 input[type="checkbox"] {
1042 input[type="checkbox"] {
1043 margin-right: 1em;
1043 margin-right: 1em;
1044 }
1044 }
1045 input[type="radio"] {
1045 input[type="radio"] {
1046 margin-right: 1em;
1046 margin-right: 1em;
1047 }
1047 }
1048 }
1048 }
1049
1049
1050 .flag_status {
1050 .flag_status {
1051 margin: 2px 8px 6px 2px;
1051 margin: 2px 8px 6px 2px;
1052 &.under_review {
1052 &.under_review {
1053 .circle(5px, @alert3);
1053 .circle(5px, @alert3);
1054 }
1054 }
1055 &.approved {
1055 &.approved {
1056 .circle(5px, @alert1);
1056 .circle(5px, @alert1);
1057 }
1057 }
1058 &.rejected,
1058 &.rejected,
1059 &.forced_closed{
1059 &.forced_closed{
1060 .circle(5px, @alert2);
1060 .circle(5px, @alert2);
1061 }
1061 }
1062 &.not_reviewed {
1062 &.not_reviewed {
1063 .circle(5px, @grey5);
1063 .circle(5px, @grey5);
1064 }
1064 }
1065 }
1065 }
1066
1066
1067 .flag_status_comment_box {
1067 .flag_status_comment_box {
1068 margin: 5px 6px 0px 2px;
1068 margin: 5px 6px 0px 2px;
1069 }
1069 }
1070 .test_pattern_preview {
1070 .test_pattern_preview {
1071 margin: @space 0;
1071 margin: @space 0;
1072
1072
1073 p {
1073 p {
1074 margin-bottom: 0;
1074 margin-bottom: 0;
1075 border-bottom: @border-thickness solid @border-default-color;
1075 border-bottom: @border-thickness solid @border-default-color;
1076 color: @grey3;
1076 color: @grey3;
1077 }
1077 }
1078
1078
1079 .btn {
1079 .btn {
1080 margin-bottom: @padding;
1080 margin-bottom: @padding;
1081 }
1081 }
1082 }
1082 }
1083 #test_pattern_result {
1083 #test_pattern_result {
1084 display: none;
1084 display: none;
1085 &:extend(pre);
1085 &:extend(pre);
1086 padding: .9em;
1086 padding: .9em;
1087 color: @grey3;
1087 color: @grey3;
1088 background-color: @grey7;
1088 background-color: @grey7;
1089 border-right: @border-thickness solid @border-default-color;
1089 border-right: @border-thickness solid @border-default-color;
1090 border-bottom: @border-thickness solid @border-default-color;
1090 border-bottom: @border-thickness solid @border-default-color;
1091 border-left: @border-thickness solid @border-default-color;
1091 border-left: @border-thickness solid @border-default-color;
1092 }
1092 }
1093
1093
1094 #repo_vcs_settings {
1094 #repo_vcs_settings {
1095 #inherit_overlay_vcs_default {
1095 #inherit_overlay_vcs_default {
1096 display: none;
1096 display: none;
1097 }
1097 }
1098 #inherit_overlay_vcs_custom {
1098 #inherit_overlay_vcs_custom {
1099 display: custom;
1099 display: custom;
1100 }
1100 }
1101 &.inherited {
1101 &.inherited {
1102 #inherit_overlay_vcs_default {
1102 #inherit_overlay_vcs_default {
1103 display: block;
1103 display: block;
1104 }
1104 }
1105 #inherit_overlay_vcs_custom {
1105 #inherit_overlay_vcs_custom {
1106 display: none;
1106 display: none;
1107 }
1107 }
1108 }
1108 }
1109 }
1109 }
1110
1110
1111 .issue-tracker-link {
1111 .issue-tracker-link {
1112 color: @rcblue;
1112 color: @rcblue;
1113 }
1113 }
1114
1114
1115 // Issue Tracker Table Show/Hide
1115 // Issue Tracker Table Show/Hide
1116 #repo_issue_tracker {
1116 #repo_issue_tracker {
1117 #inherit_overlay {
1117 #inherit_overlay {
1118 display: none;
1118 display: none;
1119 }
1119 }
1120 #custom_overlay {
1120 #custom_overlay {
1121 display: custom;
1121 display: custom;
1122 }
1122 }
1123 &.inherited {
1123 &.inherited {
1124 #inherit_overlay {
1124 #inherit_overlay {
1125 display: block;
1125 display: block;
1126 }
1126 }
1127 #custom_overlay {
1127 #custom_overlay {
1128 display: none;
1128 display: none;
1129 }
1129 }
1130 }
1130 }
1131 }
1131 }
1132 table.issuetracker {
1132 table.issuetracker {
1133 &.readonly {
1133 &.readonly {
1134 tr, td {
1134 tr, td {
1135 color: @grey3;
1135 color: @grey3;
1136 }
1136 }
1137 }
1137 }
1138 .edit {
1138 .edit {
1139 display: none;
1139 display: none;
1140 }
1140 }
1141 .editopen {
1141 .editopen {
1142 .edit {
1142 .edit {
1143 display: inline;
1143 display: inline;
1144 }
1144 }
1145 .entry {
1145 .entry {
1146 display: none;
1146 display: none;
1147 }
1147 }
1148 }
1148 }
1149 tr td.td-action {
1149 tr td.td-action {
1150 min-width: 117px;
1150 min-width: 117px;
1151 }
1151 }
1152 td input {
1152 td input {
1153 max-width: none;
1153 max-width: none;
1154 min-width: 30px;
1154 min-width: 30px;
1155 width: 80%;
1155 width: 80%;
1156 }
1156 }
1157 .issuetracker_pref input {
1157 .issuetracker_pref input {
1158 width: 40%;
1158 width: 40%;
1159 }
1159 }
1160 input.edit_issuetracker_update {
1160 input.edit_issuetracker_update {
1161 margin-right: 0;
1161 margin-right: 0;
1162 width: auto;
1162 width: auto;
1163 }
1163 }
1164 }
1164 }
1165
1165
1166 table.integrations {
1166 table.integrations {
1167 .td-icon {
1167 .td-icon {
1168 width: 20px;
1168 width: 20px;
1169 .integration-icon {
1169 .integration-icon {
1170 height: 20px;
1170 height: 20px;
1171 width: 20px;
1171 width: 20px;
1172 }
1172 }
1173 }
1173 }
1174 }
1174 }
1175
1175
1176 .integrations {
1176 .integrations {
1177 a.integration-box {
1177 a.integration-box {
1178 color: @text-color;
1178 color: @text-color;
1179 &:hover {
1179 &:hover {
1180 .panel {
1180 .panel {
1181 background: #fbfbfb;
1181 background: #fbfbfb;
1182 }
1182 }
1183 }
1183 }
1184 .integration-icon {
1184 .integration-icon {
1185 width: 30px;
1185 width: 30px;
1186 height: 30px;
1186 height: 30px;
1187 margin-right: 20px;
1187 margin-right: 20px;
1188 float: left;
1188 float: left;
1189 }
1189 }
1190
1190
1191 .panel-body {
1191 .panel-body {
1192 padding: 10px;
1192 padding: 10px;
1193 }
1193 }
1194 .panel {
1194 .panel {
1195 margin-bottom: 10px;
1195 margin-bottom: 10px;
1196 }
1196 }
1197 h2 {
1197 h2 {
1198 display: inline-block;
1198 display: inline-block;
1199 margin: 0;
1199 margin: 0;
1200 min-width: 140px;
1200 min-width: 140px;
1201 }
1201 }
1202 }
1202 }
1203 a.integration-box.dummy-integration {
1203 a.integration-box.dummy-integration {
1204 color: @grey4
1204 color: @grey4
1205 }
1205 }
1206 }
1206 }
1207
1207
1208 //Permissions Settings
1208 //Permissions Settings
1209 #add_perm {
1209 #add_perm {
1210 margin: 0 0 @padding;
1210 margin: 0 0 @padding;
1211 cursor: pointer;
1211 cursor: pointer;
1212 }
1212 }
1213
1213
1214 .perm_ac {
1214 .perm_ac {
1215 input {
1215 input {
1216 width: 95%;
1216 width: 95%;
1217 }
1217 }
1218 }
1218 }
1219
1219
1220 .autocomplete-suggestions {
1220 .autocomplete-suggestions {
1221 width: auto !important; // overrides autocomplete.js
1221 width: auto !important; // overrides autocomplete.js
1222 margin: 0;
1222 margin: 0;
1223 border: @border-thickness solid @rcblue;
1223 border: @border-thickness solid @rcblue;
1224 border-radius: @border-radius;
1224 border-radius: @border-radius;
1225 color: @rcblue;
1225 color: @rcblue;
1226 background-color: white;
1226 background-color: white;
1227 }
1227 }
1228 .autocomplete-selected {
1228 .autocomplete-selected {
1229 background: #F0F0F0;
1229 background: #F0F0F0;
1230 }
1230 }
1231 .ac-container-wrap {
1231 .ac-container-wrap {
1232 margin: 0;
1232 margin: 0;
1233 padding: 8px;
1233 padding: 8px;
1234 border-bottom: @border-thickness solid @rclightblue;
1234 border-bottom: @border-thickness solid @rclightblue;
1235 list-style-type: none;
1235 list-style-type: none;
1236 cursor: pointer;
1236 cursor: pointer;
1237
1237
1238 &:hover {
1238 &:hover {
1239 background-color: @rclightblue;
1239 background-color: @rclightblue;
1240 }
1240 }
1241
1241
1242 img {
1242 img {
1243 height: @gravatar-size;
1243 height: @gravatar-size;
1244 width: @gravatar-size;
1244 width: @gravatar-size;
1245 margin-right: 1em;
1245 margin-right: 1em;
1246 }
1246 }
1247
1247
1248 strong {
1248 strong {
1249 font-weight: normal;
1249 font-weight: normal;
1250 }
1250 }
1251 }
1251 }
1252
1252
1253 // Settings Dropdown
1253 // Settings Dropdown
1254 .user-menu .container {
1254 .user-menu .container {
1255 padding: 0 4px;
1255 padding: 0 4px;
1256 margin: 0;
1256 margin: 0;
1257 }
1257 }
1258
1258
1259 .user-menu .gravatar {
1259 .user-menu .gravatar {
1260 cursor: pointer;
1260 cursor: pointer;
1261 }
1261 }
1262
1262
1263 .codeblock {
1263 .codeblock {
1264 margin-bottom: @padding;
1264 margin-bottom: @padding;
1265 clear: both;
1265 clear: both;
1266
1266
1267 .stats{
1267 .stats{
1268 overflow: hidden;
1268 overflow: hidden;
1269 }
1269 }
1270
1270
1271 .message{
1271 .message{
1272 textarea{
1272 textarea{
1273 margin: 0;
1273 margin: 0;
1274 }
1274 }
1275 }
1275 }
1276
1276
1277 .code-header {
1277 .code-header {
1278 .stats {
1278 .stats {
1279 line-height: 2em;
1279 line-height: 2em;
1280
1280
1281 .revision_id {
1281 .revision_id {
1282 margin-left: 0;
1282 margin-left: 0;
1283 }
1283 }
1284 .buttons {
1284 .buttons {
1285 padding-right: 0;
1285 padding-right: 0;
1286 }
1286 }
1287 }
1287 }
1288
1288
1289 .item{
1289 .item{
1290 margin-right: 0.5em;
1290 margin-right: 0.5em;
1291 }
1291 }
1292 }
1292 }
1293
1293
1294 #editor_container{
1294 #editor_container{
1295 position: relative;
1295 position: relative;
1296 margin: @padding;
1296 margin: @padding;
1297 }
1297 }
1298 }
1298 }
1299
1299
1300 #file_history_container {
1300 #file_history_container {
1301 display: none;
1301 display: none;
1302 }
1302 }
1303
1303
1304 .file-history-inner {
1304 .file-history-inner {
1305 margin-bottom: 10px;
1305 margin-bottom: 10px;
1306 }
1306 }
1307
1307
1308 // Pull Requests
1308 // Pull Requests
1309 .summary-details {
1309 .summary-details {
1310 width: 72%;
1310 width: 72%;
1311 }
1311 }
1312 .pr-summary {
1312 .pr-summary {
1313 border-bottom: @border-thickness solid @grey5;
1313 border-bottom: @border-thickness solid @grey5;
1314 margin-bottom: @space;
1314 margin-bottom: @space;
1315 }
1315 }
1316 .reviewers-title {
1316 .reviewers-title {
1317 width: 25%;
1317 width: 25%;
1318 min-width: 200px;
1318 min-width: 200px;
1319 }
1319 }
1320 .reviewers {
1320 .reviewers {
1321 width: 25%;
1321 width: 25%;
1322 min-width: 200px;
1322 min-width: 200px;
1323 }
1323 }
1324 .reviewers ul li {
1324 .reviewers ul li {
1325 position: relative;
1325 position: relative;
1326 width: 100%;
1326 width: 100%;
1327 margin-bottom: 8px;
1327 margin-bottom: 8px;
1328 }
1328 }
1329
1329
1330 .reviewer_entry {
1330 .reviewer_entry {
1331 min-height: 55px;
1331 min-height: 55px;
1332 }
1332 }
1333
1333
1334 .reviewers_member {
1334 .reviewers_member {
1335 width: 100%;
1335 width: 100%;
1336 overflow: auto;
1336 overflow: auto;
1337 }
1337 }
1338
1339 .reviewer_reason_container {
1340 padding-left: 20px;
1341 }
1342
1338 .reviewer_reason {
1343 .reviewer_reason {
1339 padding-left: 20px;
1340 }
1344 }
1345
1341 .reviewer_status {
1346 .reviewer_status {
1342 display: inline-block;
1347 display: inline-block;
1343 vertical-align: top;
1348 vertical-align: top;
1344 width: 7%;
1349 width: 7%;
1345 min-width: 20px;
1350 min-width: 20px;
1346 height: 1.2em;
1351 height: 1.2em;
1347 margin-top: 3px;
1352 margin-top: 3px;
1348 line-height: 1em;
1353 line-height: 1em;
1349 }
1354 }
1350
1355
1351 .reviewer_name {
1356 .reviewer_name {
1352 display: inline-block;
1357 display: inline-block;
1353 max-width: 83%;
1358 max-width: 83%;
1354 padding-right: 20px;
1359 padding-right: 20px;
1355 vertical-align: middle;
1360 vertical-align: middle;
1356 line-height: 1;
1361 line-height: 1;
1357
1362
1358 .rc-user {
1363 .rc-user {
1359 min-width: 0;
1364 min-width: 0;
1360 margin: -2px 1em 0 0;
1365 margin: -2px 1em 0 0;
1361 }
1366 }
1362
1367
1363 .reviewer {
1368 .reviewer {
1364 float: left;
1369 float: left;
1365 }
1370 }
1366 }
1371 }
1367
1372
1368 .reviewer_member_mandatory,
1373 .reviewer_member_mandatory,
1369 .reviewer_member_mandatory_remove,
1374 .reviewer_member_mandatory_remove,
1370 .reviewer_member_remove {
1375 .reviewer_member_remove {
1371 position: absolute;
1376 position: absolute;
1372 right: 0;
1377 right: 0;
1373 top: 0;
1378 top: 0;
1374 width: 16px;
1379 width: 16px;
1375 margin-bottom: 10px;
1380 margin-bottom: 10px;
1376 padding: 0;
1381 padding: 0;
1377 color: black;
1382 color: black;
1378 }
1383 }
1379
1384
1380 .reviewer_member_mandatory_remove {
1385 .reviewer_member_mandatory_remove {
1381 color: @grey4;
1386 color: @grey4;
1382 }
1387 }
1383
1388
1384 .reviewer_member_mandatory {
1389 .reviewer_member_mandatory {
1385 padding-top:20px;
1390 padding-top:20px;
1386 }
1391 }
1387
1392
1388 .reviewer_member_status {
1393 .reviewer_member_status {
1389 margin-top: 5px;
1394 margin-top: 5px;
1390 }
1395 }
1391 .pr-summary #summary{
1396 .pr-summary #summary{
1392 width: 100%;
1397 width: 100%;
1393 }
1398 }
1394 .pr-summary .action_button:hover {
1399 .pr-summary .action_button:hover {
1395 border: 0;
1400 border: 0;
1396 cursor: pointer;
1401 cursor: pointer;
1397 }
1402 }
1398 .pr-details-title {
1403 .pr-details-title {
1399 padding-bottom: 8px;
1404 padding-bottom: 8px;
1400 border-bottom: @border-thickness solid @grey5;
1405 border-bottom: @border-thickness solid @grey5;
1401
1406
1402 .action_button.disabled {
1407 .action_button.disabled {
1403 color: @grey4;
1408 color: @grey4;
1404 cursor: inherit;
1409 cursor: inherit;
1405 }
1410 }
1406 .action_button {
1411 .action_button {
1407 color: @rcblue;
1412 color: @rcblue;
1408 }
1413 }
1409 }
1414 }
1410 .pr-details-content {
1415 .pr-details-content {
1411 margin-top: @textmargin;
1416 margin-top: @textmargin;
1412 margin-bottom: @textmargin;
1417 margin-bottom: @textmargin;
1413 }
1418 }
1414 .pr-description {
1419 .pr-description {
1415 white-space:pre-wrap;
1420 white-space:pre-wrap;
1416 }
1421 }
1417
1422
1418 .pr-reviewer-rules {
1423 .pr-reviewer-rules {
1419 padding: 10px 0px 20px 0px;
1424 padding: 10px 0px 20px 0px;
1420 }
1425 }
1421
1426
1422 .group_members {
1427 .group_members {
1423 margin-top: 0;
1428 margin-top: 0;
1424 padding: 0;
1429 padding: 0;
1425 list-style: outside none none;
1430 list-style: outside none none;
1426
1431
1427 img {
1432 img {
1428 height: @gravatar-size;
1433 height: @gravatar-size;
1429 width: @gravatar-size;
1434 width: @gravatar-size;
1430 margin-right: .5em;
1435 margin-right: .5em;
1431 margin-left: 3px;
1436 margin-left: 3px;
1432 }
1437 }
1433
1438
1434 .to-delete {
1439 .to-delete {
1435 .user {
1440 .user {
1436 text-decoration: line-through;
1441 text-decoration: line-through;
1437 }
1442 }
1438 }
1443 }
1439 }
1444 }
1440
1445
1441 .compare_view_commits_title {
1446 .compare_view_commits_title {
1442 .disabled {
1447 .disabled {
1443 cursor: inherit;
1448 cursor: inherit;
1444 &:hover{
1449 &:hover{
1445 background-color: inherit;
1450 background-color: inherit;
1446 color: inherit;
1451 color: inherit;
1447 }
1452 }
1448 }
1453 }
1449 }
1454 }
1450
1455
1451 .subtitle-compare {
1456 .subtitle-compare {
1452 margin: -15px 0px 0px 0px;
1457 margin: -15px 0px 0px 0px;
1453 }
1458 }
1454
1459
1455 .comments-summary-td {
1460 .comments-summary-td {
1456 border-top: 1px dashed @grey5;
1461 border-top: 1px dashed @grey5;
1457 }
1462 }
1458
1463
1459 // new entry in group_members
1464 // new entry in group_members
1460 .td-author-new-entry {
1465 .td-author-new-entry {
1461 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1466 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1462 }
1467 }
1463
1468
1464 .usergroup_member_remove {
1469 .usergroup_member_remove {
1465 width: 16px;
1470 width: 16px;
1466 margin-bottom: 10px;
1471 margin-bottom: 10px;
1467 padding: 0;
1472 padding: 0;
1468 color: black !important;
1473 color: black !important;
1469 cursor: pointer;
1474 cursor: pointer;
1470 }
1475 }
1471
1476
1472 .reviewer_ac .ac-input {
1477 .reviewer_ac .ac-input {
1473 width: 92%;
1478 width: 92%;
1474 margin-bottom: 1em;
1479 margin-bottom: 1em;
1475 }
1480 }
1476
1481
1477 .compare_view_commits tr{
1482 .compare_view_commits tr{
1478 height: 20px;
1483 height: 20px;
1479 }
1484 }
1480 .compare_view_commits td {
1485 .compare_view_commits td {
1481 vertical-align: top;
1486 vertical-align: top;
1482 padding-top: 10px;
1487 padding-top: 10px;
1483 }
1488 }
1484 .compare_view_commits .author {
1489 .compare_view_commits .author {
1485 margin-left: 5px;
1490 margin-left: 5px;
1486 }
1491 }
1487
1492
1488 .compare_view_commits {
1493 .compare_view_commits {
1489 .color-a {
1494 .color-a {
1490 color: @alert1;
1495 color: @alert1;
1491 }
1496 }
1492
1497
1493 .color-c {
1498 .color-c {
1494 color: @color3;
1499 color: @color3;
1495 }
1500 }
1496
1501
1497 .color-r {
1502 .color-r {
1498 color: @color5;
1503 color: @color5;
1499 }
1504 }
1500
1505
1501 .color-a-bg {
1506 .color-a-bg {
1502 background-color: @alert1;
1507 background-color: @alert1;
1503 }
1508 }
1504
1509
1505 .color-c-bg {
1510 .color-c-bg {
1506 background-color: @alert3;
1511 background-color: @alert3;
1507 }
1512 }
1508
1513
1509 .color-r-bg {
1514 .color-r-bg {
1510 background-color: @alert2;
1515 background-color: @alert2;
1511 }
1516 }
1512
1517
1513 .color-a-border {
1518 .color-a-border {
1514 border: 1px solid @alert1;
1519 border: 1px solid @alert1;
1515 }
1520 }
1516
1521
1517 .color-c-border {
1522 .color-c-border {
1518 border: 1px solid @alert3;
1523 border: 1px solid @alert3;
1519 }
1524 }
1520
1525
1521 .color-r-border {
1526 .color-r-border {
1522 border: 1px solid @alert2;
1527 border: 1px solid @alert2;
1523 }
1528 }
1524
1529
1525 .commit-change-indicator {
1530 .commit-change-indicator {
1526 width: 15px;
1531 width: 15px;
1527 height: 15px;
1532 height: 15px;
1528 position: relative;
1533 position: relative;
1529 left: 15px;
1534 left: 15px;
1530 }
1535 }
1531
1536
1532 .commit-change-content {
1537 .commit-change-content {
1533 text-align: center;
1538 text-align: center;
1534 vertical-align: middle;
1539 vertical-align: middle;
1535 line-height: 15px;
1540 line-height: 15px;
1536 }
1541 }
1537 }
1542 }
1538
1543
1539 .compare_view_files {
1544 .compare_view_files {
1540 width: 100%;
1545 width: 100%;
1541
1546
1542 td {
1547 td {
1543 vertical-align: middle;
1548 vertical-align: middle;
1544 }
1549 }
1545 }
1550 }
1546
1551
1547 .compare_view_filepath {
1552 .compare_view_filepath {
1548 color: @grey1;
1553 color: @grey1;
1549 }
1554 }
1550
1555
1551 .show_more {
1556 .show_more {
1552 display: inline-block;
1557 display: inline-block;
1553 position: relative;
1558 position: relative;
1554 vertical-align: middle;
1559 vertical-align: middle;
1555 width: 4px;
1560 width: 4px;
1556 height: @basefontsize;
1561 height: @basefontsize;
1557
1562
1558 &:after {
1563 &:after {
1559 content: "\00A0\25BE";
1564 content: "\00A0\25BE";
1560 display: inline-block;
1565 display: inline-block;
1561 width:10px;
1566 width:10px;
1562 line-height: 5px;
1567 line-height: 5px;
1563 font-size: 12px;
1568 font-size: 12px;
1564 cursor: pointer;
1569 cursor: pointer;
1565 }
1570 }
1566 }
1571 }
1567
1572
1568 .journal_more .show_more {
1573 .journal_more .show_more {
1569 display: inline;
1574 display: inline;
1570
1575
1571 &:after {
1576 &:after {
1572 content: none;
1577 content: none;
1573 }
1578 }
1574 }
1579 }
1575
1580
1576 .open .show_more:after,
1581 .open .show_more:after,
1577 .select2-dropdown-open .show_more:after {
1582 .select2-dropdown-open .show_more:after {
1578 .rotate(180deg);
1583 .rotate(180deg);
1579 margin-left: 4px;
1584 margin-left: 4px;
1580 }
1585 }
1581
1586
1582
1587
1583 .compare_view_commits .collapse_commit:after {
1588 .compare_view_commits .collapse_commit:after {
1584 cursor: pointer;
1589 cursor: pointer;
1585 content: "\00A0\25B4";
1590 content: "\00A0\25B4";
1586 margin-left: -3px;
1591 margin-left: -3px;
1587 font-size: 17px;
1592 font-size: 17px;
1588 color: @grey4;
1593 color: @grey4;
1589 }
1594 }
1590
1595
1591 .diff_links {
1596 .diff_links {
1592 margin-left: 8px;
1597 margin-left: 8px;
1593 }
1598 }
1594
1599
1595 div.ancestor {
1600 div.ancestor {
1596 margin: -30px 0px;
1601 margin: -30px 0px;
1597 }
1602 }
1598
1603
1599 .cs_icon_td input[type="checkbox"] {
1604 .cs_icon_td input[type="checkbox"] {
1600 display: none;
1605 display: none;
1601 }
1606 }
1602
1607
1603 .cs_icon_td .expand_file_icon:after {
1608 .cs_icon_td .expand_file_icon:after {
1604 cursor: pointer;
1609 cursor: pointer;
1605 content: "\00A0\25B6";
1610 content: "\00A0\25B6";
1606 font-size: 12px;
1611 font-size: 12px;
1607 color: @grey4;
1612 color: @grey4;
1608 }
1613 }
1609
1614
1610 .cs_icon_td .collapse_file_icon:after {
1615 .cs_icon_td .collapse_file_icon:after {
1611 cursor: pointer;
1616 cursor: pointer;
1612 content: "\00A0\25BC";
1617 content: "\00A0\25BC";
1613 font-size: 12px;
1618 font-size: 12px;
1614 color: @grey4;
1619 color: @grey4;
1615 }
1620 }
1616
1621
1617 /*new binary
1622 /*new binary
1618 NEW_FILENODE = 1
1623 NEW_FILENODE = 1
1619 DEL_FILENODE = 2
1624 DEL_FILENODE = 2
1620 MOD_FILENODE = 3
1625 MOD_FILENODE = 3
1621 RENAMED_FILENODE = 4
1626 RENAMED_FILENODE = 4
1622 COPIED_FILENODE = 5
1627 COPIED_FILENODE = 5
1623 CHMOD_FILENODE = 6
1628 CHMOD_FILENODE = 6
1624 BIN_FILENODE = 7
1629 BIN_FILENODE = 7
1625 */
1630 */
1626 .cs_files_expand {
1631 .cs_files_expand {
1627 font-size: @basefontsize + 5px;
1632 font-size: @basefontsize + 5px;
1628 line-height: 1.8em;
1633 line-height: 1.8em;
1629 float: right;
1634 float: right;
1630 }
1635 }
1631
1636
1632 .cs_files_expand span{
1637 .cs_files_expand span{
1633 color: @rcblue;
1638 color: @rcblue;
1634 cursor: pointer;
1639 cursor: pointer;
1635 }
1640 }
1636 .cs_files {
1641 .cs_files {
1637 clear: both;
1642 clear: both;
1638 padding-bottom: @padding;
1643 padding-bottom: @padding;
1639
1644
1640 .cur_cs {
1645 .cur_cs {
1641 margin: 10px 2px;
1646 margin: 10px 2px;
1642 font-weight: bold;
1647 font-weight: bold;
1643 }
1648 }
1644
1649
1645 .node {
1650 .node {
1646 float: left;
1651 float: left;
1647 }
1652 }
1648
1653
1649 .changes {
1654 .changes {
1650 float: right;
1655 float: right;
1651 color: white;
1656 color: white;
1652 font-size: @basefontsize - 4px;
1657 font-size: @basefontsize - 4px;
1653 margin-top: 4px;
1658 margin-top: 4px;
1654 opacity: 0.6;
1659 opacity: 0.6;
1655 filter: Alpha(opacity=60); /* IE8 and earlier */
1660 filter: Alpha(opacity=60); /* IE8 and earlier */
1656
1661
1657 .added {
1662 .added {
1658 background-color: @alert1;
1663 background-color: @alert1;
1659 float: left;
1664 float: left;
1660 text-align: center;
1665 text-align: center;
1661 }
1666 }
1662
1667
1663 .deleted {
1668 .deleted {
1664 background-color: @alert2;
1669 background-color: @alert2;
1665 float: left;
1670 float: left;
1666 text-align: center;
1671 text-align: center;
1667 }
1672 }
1668
1673
1669 .bin {
1674 .bin {
1670 background-color: @alert1;
1675 background-color: @alert1;
1671 text-align: center;
1676 text-align: center;
1672 }
1677 }
1673
1678
1674 /*new binary*/
1679 /*new binary*/
1675 .bin.bin1 {
1680 .bin.bin1 {
1676 background-color: @alert1;
1681 background-color: @alert1;
1677 text-align: center;
1682 text-align: center;
1678 }
1683 }
1679
1684
1680 /*deleted binary*/
1685 /*deleted binary*/
1681 .bin.bin2 {
1686 .bin.bin2 {
1682 background-color: @alert2;
1687 background-color: @alert2;
1683 text-align: center;
1688 text-align: center;
1684 }
1689 }
1685
1690
1686 /*mod binary*/
1691 /*mod binary*/
1687 .bin.bin3 {
1692 .bin.bin3 {
1688 background-color: @grey2;
1693 background-color: @grey2;
1689 text-align: center;
1694 text-align: center;
1690 }
1695 }
1691
1696
1692 /*rename file*/
1697 /*rename file*/
1693 .bin.bin4 {
1698 .bin.bin4 {
1694 background-color: @alert4;
1699 background-color: @alert4;
1695 text-align: center;
1700 text-align: center;
1696 }
1701 }
1697
1702
1698 /*copied file*/
1703 /*copied file*/
1699 .bin.bin5 {
1704 .bin.bin5 {
1700 background-color: @alert4;
1705 background-color: @alert4;
1701 text-align: center;
1706 text-align: center;
1702 }
1707 }
1703
1708
1704 /*chmod file*/
1709 /*chmod file*/
1705 .bin.bin6 {
1710 .bin.bin6 {
1706 background-color: @grey2;
1711 background-color: @grey2;
1707 text-align: center;
1712 text-align: center;
1708 }
1713 }
1709 }
1714 }
1710 }
1715 }
1711
1716
1712 .cs_files .cs_added, .cs_files .cs_A,
1717 .cs_files .cs_added, .cs_files .cs_A,
1713 .cs_files .cs_added, .cs_files .cs_M,
1718 .cs_files .cs_added, .cs_files .cs_M,
1714 .cs_files .cs_added, .cs_files .cs_D {
1719 .cs_files .cs_added, .cs_files .cs_D {
1715 height: 16px;
1720 height: 16px;
1716 padding-right: 10px;
1721 padding-right: 10px;
1717 margin-top: 7px;
1722 margin-top: 7px;
1718 text-align: left;
1723 text-align: left;
1719 }
1724 }
1720
1725
1721 .cs_icon_td {
1726 .cs_icon_td {
1722 min-width: 16px;
1727 min-width: 16px;
1723 width: 16px;
1728 width: 16px;
1724 }
1729 }
1725
1730
1726 .pull-request-merge {
1731 .pull-request-merge {
1727 border: 1px solid @grey5;
1732 border: 1px solid @grey5;
1728 padding: 10px 0px 20px;
1733 padding: 10px 0px 20px;
1729 margin-top: 10px;
1734 margin-top: 10px;
1730 margin-bottom: 20px;
1735 margin-bottom: 20px;
1731 }
1736 }
1732
1737
1733 .pull-request-merge ul {
1738 .pull-request-merge ul {
1734 padding: 0px 0px;
1739 padding: 0px 0px;
1735 }
1740 }
1736
1741
1737 .pull-request-merge li:before{
1742 .pull-request-merge li:before{
1738 content:none;
1743 content:none;
1739 }
1744 }
1740
1745
1741 .pull-request-merge .pull-request-wrap {
1746 .pull-request-merge .pull-request-wrap {
1742 height: auto;
1747 height: auto;
1743 padding: 0px 0px;
1748 padding: 0px 0px;
1744 text-align: right;
1749 text-align: right;
1745 }
1750 }
1746
1751
1747 .pull-request-merge span {
1752 .pull-request-merge span {
1748 margin-right: 5px;
1753 margin-right: 5px;
1749 }
1754 }
1750
1755
1751 .pull-request-merge-actions {
1756 .pull-request-merge-actions {
1752 min-height: 30px;
1757 min-height: 30px;
1753 padding: 0px 0px;
1758 padding: 0px 0px;
1754 }
1759 }
1755
1760
1756 .pull-request-merge-info {
1761 .pull-request-merge-info {
1757 padding: 0px 5px 5px 0px;
1762 padding: 0px 5px 5px 0px;
1758 }
1763 }
1759
1764
1760 .merge-status {
1765 .merge-status {
1761 margin-right: 5px;
1766 margin-right: 5px;
1762 }
1767 }
1763
1768
1764 .merge-message {
1769 .merge-message {
1765 font-size: 1.2em
1770 font-size: 1.2em
1766 }
1771 }
1767
1772
1768 .merge-message.success i,
1773 .merge-message.success i,
1769 .merge-icon.success i {
1774 .merge-icon.success i {
1770 color:@alert1;
1775 color:@alert1;
1771 }
1776 }
1772
1777
1773 .merge-message.warning i,
1778 .merge-message.warning i,
1774 .merge-icon.warning i {
1779 .merge-icon.warning i {
1775 color: @alert3;
1780 color: @alert3;
1776 }
1781 }
1777
1782
1778 .merge-message.error i,
1783 .merge-message.error i,
1779 .merge-icon.error i {
1784 .merge-icon.error i {
1780 color:@alert2;
1785 color:@alert2;
1781 }
1786 }
1782
1787
1783 .pr-versions {
1788 .pr-versions {
1784 font-size: 1.1em;
1789 font-size: 1.1em;
1785
1790
1786 table {
1791 table {
1787 padding: 0px 5px;
1792 padding: 0px 5px;
1788 }
1793 }
1789
1794
1790 td {
1795 td {
1791 line-height: 15px;
1796 line-height: 15px;
1792 }
1797 }
1793
1798
1794 .flag_status {
1799 .flag_status {
1795 margin: 0;
1800 margin: 0;
1796 }
1801 }
1797
1802
1798 .compare-radio-button {
1803 .compare-radio-button {
1799 position: relative;
1804 position: relative;
1800 top: -3px;
1805 top: -3px;
1801 }
1806 }
1802 }
1807 }
1803
1808
1804
1809
1805 #close_pull_request {
1810 #close_pull_request {
1806 margin-right: 0px;
1811 margin-right: 0px;
1807 }
1812 }
1808
1813
1809 .empty_data {
1814 .empty_data {
1810 color: @grey4;
1815 color: @grey4;
1811 }
1816 }
1812
1817
1813 #changeset_compare_view_content {
1818 #changeset_compare_view_content {
1814 margin-bottom: @space;
1819 margin-bottom: @space;
1815 clear: both;
1820 clear: both;
1816 width: 100%;
1821 width: 100%;
1817 box-sizing: border-box;
1822 box-sizing: border-box;
1818 .border-radius(@border-radius);
1823 .border-radius(@border-radius);
1819
1824
1820 .help-block {
1825 .help-block {
1821 margin: @padding 0;
1826 margin: @padding 0;
1822 color: @text-color;
1827 color: @text-color;
1823 &.pre-formatting {
1828 &.pre-formatting {
1824 white-space: pre;
1829 white-space: pre;
1825 }
1830 }
1826 }
1831 }
1827
1832
1828 .empty_data {
1833 .empty_data {
1829 margin: @padding 0;
1834 margin: @padding 0;
1830 }
1835 }
1831
1836
1832 .alert {
1837 .alert {
1833 margin-bottom: @space;
1838 margin-bottom: @space;
1834 }
1839 }
1835 }
1840 }
1836
1841
1837 .table_disp {
1842 .table_disp {
1838 .status {
1843 .status {
1839 width: auto;
1844 width: auto;
1840
1845
1841 .flag_status {
1846 .flag_status {
1842 float: left;
1847 float: left;
1843 }
1848 }
1844 }
1849 }
1845 }
1850 }
1846
1851
1847
1852
1848 .creation_in_progress {
1853 .creation_in_progress {
1849 color: @grey4
1854 color: @grey4
1850 }
1855 }
1851
1856
1852 .status_box_menu {
1857 .status_box_menu {
1853 margin: 0;
1858 margin: 0;
1854 }
1859 }
1855
1860
1856 .notification-table{
1861 .notification-table{
1857 margin-bottom: @space;
1862 margin-bottom: @space;
1858 display: table;
1863 display: table;
1859 width: 100%;
1864 width: 100%;
1860
1865
1861 .container{
1866 .container{
1862 display: table-row;
1867 display: table-row;
1863
1868
1864 .notification-header{
1869 .notification-header{
1865 border-bottom: @border-thickness solid @border-default-color;
1870 border-bottom: @border-thickness solid @border-default-color;
1866 }
1871 }
1867
1872
1868 .notification-subject{
1873 .notification-subject{
1869 display: table-cell;
1874 display: table-cell;
1870 }
1875 }
1871 }
1876 }
1872 }
1877 }
1873
1878
1874 // Notifications
1879 // Notifications
1875 .notification-header{
1880 .notification-header{
1876 display: table;
1881 display: table;
1877 width: 100%;
1882 width: 100%;
1878 padding: floor(@basefontsize/2) 0;
1883 padding: floor(@basefontsize/2) 0;
1879 line-height: 1em;
1884 line-height: 1em;
1880
1885
1881 .desc, .delete-notifications, .read-notifications{
1886 .desc, .delete-notifications, .read-notifications{
1882 display: table-cell;
1887 display: table-cell;
1883 text-align: left;
1888 text-align: left;
1884 }
1889 }
1885
1890
1886 .desc{
1891 .desc{
1887 width: 1163px;
1892 width: 1163px;
1888 }
1893 }
1889
1894
1890 .delete-notifications, .read-notifications{
1895 .delete-notifications, .read-notifications{
1891 width: 35px;
1896 width: 35px;
1892 min-width: 35px; //fixes when only one button is displayed
1897 min-width: 35px; //fixes when only one button is displayed
1893 }
1898 }
1894 }
1899 }
1895
1900
1896 .notification-body {
1901 .notification-body {
1897 .markdown-block,
1902 .markdown-block,
1898 .rst-block {
1903 .rst-block {
1899 padding: @padding 0;
1904 padding: @padding 0;
1900 }
1905 }
1901
1906
1902 .notification-subject {
1907 .notification-subject {
1903 padding: @textmargin 0;
1908 padding: @textmargin 0;
1904 border-bottom: @border-thickness solid @border-default-color;
1909 border-bottom: @border-thickness solid @border-default-color;
1905 }
1910 }
1906 }
1911 }
1907
1912
1908
1913
1909 .notifications_buttons{
1914 .notifications_buttons{
1910 float: right;
1915 float: right;
1911 }
1916 }
1912
1917
1913 #notification-status{
1918 #notification-status{
1914 display: inline;
1919 display: inline;
1915 }
1920 }
1916
1921
1917 // Repositories
1922 // Repositories
1918
1923
1919 #summary.fields{
1924 #summary.fields{
1920 display: table;
1925 display: table;
1921
1926
1922 .field{
1927 .field{
1923 display: table-row;
1928 display: table-row;
1924
1929
1925 .label-summary{
1930 .label-summary{
1926 display: table-cell;
1931 display: table-cell;
1927 min-width: @label-summary-minwidth;
1932 min-width: @label-summary-minwidth;
1928 padding-top: @padding/2;
1933 padding-top: @padding/2;
1929 padding-bottom: @padding/2;
1934 padding-bottom: @padding/2;
1930 padding-right: @padding/2;
1935 padding-right: @padding/2;
1931 }
1936 }
1932
1937
1933 .input{
1938 .input{
1934 display: table-cell;
1939 display: table-cell;
1935 padding: @padding/2;
1940 padding: @padding/2;
1936
1941
1937 input{
1942 input{
1938 min-width: 29em;
1943 min-width: 29em;
1939 padding: @padding/4;
1944 padding: @padding/4;
1940 }
1945 }
1941 }
1946 }
1942 .statistics, .downloads{
1947 .statistics, .downloads{
1943 .disabled{
1948 .disabled{
1944 color: @grey4;
1949 color: @grey4;
1945 }
1950 }
1946 }
1951 }
1947 }
1952 }
1948 }
1953 }
1949
1954
1950 #summary{
1955 #summary{
1951 width: 70%;
1956 width: 70%;
1952 }
1957 }
1953
1958
1954
1959
1955 // Journal
1960 // Journal
1956 .journal.title {
1961 .journal.title {
1957 h5 {
1962 h5 {
1958 float: left;
1963 float: left;
1959 margin: 0;
1964 margin: 0;
1960 width: 70%;
1965 width: 70%;
1961 }
1966 }
1962
1967
1963 ul {
1968 ul {
1964 float: right;
1969 float: right;
1965 display: inline-block;
1970 display: inline-block;
1966 margin: 0;
1971 margin: 0;
1967 width: 30%;
1972 width: 30%;
1968 text-align: right;
1973 text-align: right;
1969
1974
1970 li {
1975 li {
1971 display: inline;
1976 display: inline;
1972 font-size: @journal-fontsize;
1977 font-size: @journal-fontsize;
1973 line-height: 1em;
1978 line-height: 1em;
1974
1979
1975 &:before { content: none; }
1980 &:before { content: none; }
1976 }
1981 }
1977 }
1982 }
1978 }
1983 }
1979
1984
1980 .filterexample {
1985 .filterexample {
1981 position: absolute;
1986 position: absolute;
1982 top: 95px;
1987 top: 95px;
1983 left: @contentpadding;
1988 left: @contentpadding;
1984 color: @rcblue;
1989 color: @rcblue;
1985 font-size: 11px;
1990 font-size: 11px;
1986 font-family: @text-regular;
1991 font-family: @text-regular;
1987 cursor: help;
1992 cursor: help;
1988
1993
1989 &:hover {
1994 &:hover {
1990 color: @rcdarkblue;
1995 color: @rcdarkblue;
1991 }
1996 }
1992
1997
1993 @media (max-width:768px) {
1998 @media (max-width:768px) {
1994 position: relative;
1999 position: relative;
1995 top: auto;
2000 top: auto;
1996 left: auto;
2001 left: auto;
1997 display: block;
2002 display: block;
1998 }
2003 }
1999 }
2004 }
2000
2005
2001
2006
2002 #journal{
2007 #journal{
2003 margin-bottom: @space;
2008 margin-bottom: @space;
2004
2009
2005 .journal_day{
2010 .journal_day{
2006 margin-bottom: @textmargin/2;
2011 margin-bottom: @textmargin/2;
2007 padding-bottom: @textmargin/2;
2012 padding-bottom: @textmargin/2;
2008 font-size: @journal-fontsize;
2013 font-size: @journal-fontsize;
2009 border-bottom: @border-thickness solid @border-default-color;
2014 border-bottom: @border-thickness solid @border-default-color;
2010 }
2015 }
2011
2016
2012 .journal_container{
2017 .journal_container{
2013 margin-bottom: @space;
2018 margin-bottom: @space;
2014
2019
2015 .journal_user{
2020 .journal_user{
2016 display: inline-block;
2021 display: inline-block;
2017 }
2022 }
2018 .journal_action_container{
2023 .journal_action_container{
2019 display: block;
2024 display: block;
2020 margin-top: @textmargin;
2025 margin-top: @textmargin;
2021
2026
2022 div{
2027 div{
2023 display: inline;
2028 display: inline;
2024 }
2029 }
2025
2030
2026 div.journal_action_params{
2031 div.journal_action_params{
2027 display: block;
2032 display: block;
2028 }
2033 }
2029
2034
2030 div.journal_repo:after{
2035 div.journal_repo:after{
2031 content: "\A";
2036 content: "\A";
2032 white-space: pre;
2037 white-space: pre;
2033 }
2038 }
2034
2039
2035 div.date{
2040 div.date{
2036 display: block;
2041 display: block;
2037 margin-bottom: @textmargin;
2042 margin-bottom: @textmargin;
2038 }
2043 }
2039 }
2044 }
2040 }
2045 }
2041 }
2046 }
2042
2047
2043 // Files
2048 // Files
2044 .edit-file-title {
2049 .edit-file-title {
2045 border-bottom: @border-thickness solid @border-default-color;
2050 border-bottom: @border-thickness solid @border-default-color;
2046
2051
2047 .breadcrumbs {
2052 .breadcrumbs {
2048 margin-bottom: 0;
2053 margin-bottom: 0;
2049 }
2054 }
2050 }
2055 }
2051
2056
2052 .edit-file-fieldset {
2057 .edit-file-fieldset {
2053 margin-top: @sidebarpadding;
2058 margin-top: @sidebarpadding;
2054
2059
2055 .fieldset {
2060 .fieldset {
2056 .left-label {
2061 .left-label {
2057 width: 13%;
2062 width: 13%;
2058 }
2063 }
2059 .right-content {
2064 .right-content {
2060 width: 87%;
2065 width: 87%;
2061 max-width: 100%;
2066 max-width: 100%;
2062 }
2067 }
2063 .filename-label {
2068 .filename-label {
2064 margin-top: 13px;
2069 margin-top: 13px;
2065 }
2070 }
2066 .commit-message-label {
2071 .commit-message-label {
2067 margin-top: 4px;
2072 margin-top: 4px;
2068 }
2073 }
2069 .file-upload-input {
2074 .file-upload-input {
2070 input {
2075 input {
2071 display: none;
2076 display: none;
2072 }
2077 }
2073 margin-top: 10px;
2078 margin-top: 10px;
2074 }
2079 }
2075 .file-upload-label {
2080 .file-upload-label {
2076 margin-top: 10px;
2081 margin-top: 10px;
2077 }
2082 }
2078 p {
2083 p {
2079 margin-top: 5px;
2084 margin-top: 5px;
2080 }
2085 }
2081
2086
2082 }
2087 }
2083 .custom-path-link {
2088 .custom-path-link {
2084 margin-left: 5px;
2089 margin-left: 5px;
2085 }
2090 }
2086 #commit {
2091 #commit {
2087 resize: vertical;
2092 resize: vertical;
2088 }
2093 }
2089 }
2094 }
2090
2095
2091 .delete-file-preview {
2096 .delete-file-preview {
2092 max-height: 250px;
2097 max-height: 250px;
2093 }
2098 }
2094
2099
2095 .new-file,
2100 .new-file,
2096 #filter_activate,
2101 #filter_activate,
2097 #filter_deactivate {
2102 #filter_deactivate {
2098 float: left;
2103 float: left;
2099 margin: 0 0 0 15px;
2104 margin: 0 0 0 15px;
2100 }
2105 }
2101
2106
2102 h3.files_location{
2107 h3.files_location{
2103 line-height: 2.4em;
2108 line-height: 2.4em;
2104 }
2109 }
2105
2110
2106 .browser-nav {
2111 .browser-nav {
2107 display: table;
2112 display: table;
2108 margin-bottom: @space;
2113 margin-bottom: @space;
2109
2114
2110
2115
2111 .info_box {
2116 .info_box {
2112 display: inline-table;
2117 display: inline-table;
2113 height: 2.5em;
2118 height: 2.5em;
2114
2119
2115 .browser-cur-rev, .info_box_elem {
2120 .browser-cur-rev, .info_box_elem {
2116 display: table-cell;
2121 display: table-cell;
2117 vertical-align: middle;
2122 vertical-align: middle;
2118 }
2123 }
2119
2124
2120 .info_box_elem {
2125 .info_box_elem {
2121 border-top: @border-thickness solid @rcblue;
2126 border-top: @border-thickness solid @rcblue;
2122 border-bottom: @border-thickness solid @rcblue;
2127 border-bottom: @border-thickness solid @rcblue;
2123
2128
2124 #at_rev, a {
2129 #at_rev, a {
2125 padding: 0.6em 0.9em;
2130 padding: 0.6em 0.9em;
2126 margin: 0;
2131 margin: 0;
2127 .box-shadow(none);
2132 .box-shadow(none);
2128 border: 0;
2133 border: 0;
2129 height: 12px;
2134 height: 12px;
2130 }
2135 }
2131
2136
2132 input#at_rev {
2137 input#at_rev {
2133 max-width: 50px;
2138 max-width: 50px;
2134 text-align: right;
2139 text-align: right;
2135 }
2140 }
2136
2141
2137 &.previous {
2142 &.previous {
2138 border: @border-thickness solid @rcblue;
2143 border: @border-thickness solid @rcblue;
2139 .disabled {
2144 .disabled {
2140 color: @grey4;
2145 color: @grey4;
2141 cursor: not-allowed;
2146 cursor: not-allowed;
2142 }
2147 }
2143 }
2148 }
2144
2149
2145 &.next {
2150 &.next {
2146 border: @border-thickness solid @rcblue;
2151 border: @border-thickness solid @rcblue;
2147 .disabled {
2152 .disabled {
2148 color: @grey4;
2153 color: @grey4;
2149 cursor: not-allowed;
2154 cursor: not-allowed;
2150 }
2155 }
2151 }
2156 }
2152 }
2157 }
2153
2158
2154 .browser-cur-rev {
2159 .browser-cur-rev {
2155
2160
2156 span{
2161 span{
2157 margin: 0;
2162 margin: 0;
2158 color: @rcblue;
2163 color: @rcblue;
2159 height: 12px;
2164 height: 12px;
2160 display: inline-block;
2165 display: inline-block;
2161 padding: 0.7em 1em ;
2166 padding: 0.7em 1em ;
2162 border: @border-thickness solid @rcblue;
2167 border: @border-thickness solid @rcblue;
2163 margin-right: @padding;
2168 margin-right: @padding;
2164 }
2169 }
2165 }
2170 }
2166 }
2171 }
2167
2172
2168 .search_activate {
2173 .search_activate {
2169 display: table-cell;
2174 display: table-cell;
2170 vertical-align: middle;
2175 vertical-align: middle;
2171
2176
2172 input, label{
2177 input, label{
2173 margin: 0;
2178 margin: 0;
2174 padding: 0;
2179 padding: 0;
2175 }
2180 }
2176
2181
2177 input{
2182 input{
2178 margin-left: @textmargin;
2183 margin-left: @textmargin;
2179 }
2184 }
2180
2185
2181 }
2186 }
2182 }
2187 }
2183
2188
2184 .browser-cur-rev{
2189 .browser-cur-rev{
2185 margin-bottom: @textmargin;
2190 margin-bottom: @textmargin;
2186 }
2191 }
2187
2192
2188 #node_filter_box_loading{
2193 #node_filter_box_loading{
2189 .info_text;
2194 .info_text;
2190 }
2195 }
2191
2196
2192 .browser-search {
2197 .browser-search {
2193 margin: -25px 0px 5px 0px;
2198 margin: -25px 0px 5px 0px;
2194 }
2199 }
2195
2200
2196 .node-filter {
2201 .node-filter {
2197 font-size: @repo-title-fontsize;
2202 font-size: @repo-title-fontsize;
2198 padding: 4px 0px 0px 0px;
2203 padding: 4px 0px 0px 0px;
2199
2204
2200 .node-filter-path {
2205 .node-filter-path {
2201 float: left;
2206 float: left;
2202 color: @grey4;
2207 color: @grey4;
2203 }
2208 }
2204 .node-filter-input {
2209 .node-filter-input {
2205 float: left;
2210 float: left;
2206 margin: -2px 0px 0px 2px;
2211 margin: -2px 0px 0px 2px;
2207 input {
2212 input {
2208 padding: 2px;
2213 padding: 2px;
2209 border: none;
2214 border: none;
2210 font-size: @repo-title-fontsize;
2215 font-size: @repo-title-fontsize;
2211 }
2216 }
2212 }
2217 }
2213 }
2218 }
2214
2219
2215
2220
2216 .browser-result{
2221 .browser-result{
2217 td a{
2222 td a{
2218 margin-left: 0.5em;
2223 margin-left: 0.5em;
2219 display: inline-block;
2224 display: inline-block;
2220
2225
2221 em{
2226 em{
2222 font-family: @text-bold;
2227 font-family: @text-bold;
2223 }
2228 }
2224 }
2229 }
2225 }
2230 }
2226
2231
2227 .browser-highlight{
2232 .browser-highlight{
2228 background-color: @grey5-alpha;
2233 background-color: @grey5-alpha;
2229 }
2234 }
2230
2235
2231
2236
2232 // Search
2237 // Search
2233
2238
2234 .search-form{
2239 .search-form{
2235 #q {
2240 #q {
2236 width: @search-form-width;
2241 width: @search-form-width;
2237 }
2242 }
2238 .fields{
2243 .fields{
2239 margin: 0 0 @space;
2244 margin: 0 0 @space;
2240 }
2245 }
2241
2246
2242 label{
2247 label{
2243 display: inline-block;
2248 display: inline-block;
2244 margin-right: @textmargin;
2249 margin-right: @textmargin;
2245 padding-top: 0.25em;
2250 padding-top: 0.25em;
2246 }
2251 }
2247
2252
2248
2253
2249 .results{
2254 .results{
2250 clear: both;
2255 clear: both;
2251 margin: 0 0 @padding;
2256 margin: 0 0 @padding;
2252 }
2257 }
2253 }
2258 }
2254
2259
2255 div.search-feedback-items {
2260 div.search-feedback-items {
2256 display: inline-block;
2261 display: inline-block;
2257 padding:0px 0px 0px 96px;
2262 padding:0px 0px 0px 96px;
2258 }
2263 }
2259
2264
2260 div.search-code-body {
2265 div.search-code-body {
2261 background-color: #ffffff; padding: 5px 0 5px 10px;
2266 background-color: #ffffff; padding: 5px 0 5px 10px;
2262 pre {
2267 pre {
2263 .match { background-color: #faffa6;}
2268 .match { background-color: #faffa6;}
2264 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2269 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2265 }
2270 }
2266 }
2271 }
2267
2272
2268 .expand_commit.search {
2273 .expand_commit.search {
2269 .show_more.open {
2274 .show_more.open {
2270 height: auto;
2275 height: auto;
2271 max-height: none;
2276 max-height: none;
2272 }
2277 }
2273 }
2278 }
2274
2279
2275 .search-results {
2280 .search-results {
2276
2281
2277 h2 {
2282 h2 {
2278 margin-bottom: 0;
2283 margin-bottom: 0;
2279 }
2284 }
2280 .codeblock {
2285 .codeblock {
2281 border: none;
2286 border: none;
2282 background: transparent;
2287 background: transparent;
2283 }
2288 }
2284
2289
2285 .codeblock-header {
2290 .codeblock-header {
2286 border: none;
2291 border: none;
2287 background: transparent;
2292 background: transparent;
2288 }
2293 }
2289
2294
2290 .code-body {
2295 .code-body {
2291 border: @border-thickness solid @border-default-color;
2296 border: @border-thickness solid @border-default-color;
2292 .border-radius(@border-radius);
2297 .border-radius(@border-radius);
2293 }
2298 }
2294
2299
2295 .td-commit {
2300 .td-commit {
2296 &:extend(pre);
2301 &:extend(pre);
2297 border-bottom: @border-thickness solid @border-default-color;
2302 border-bottom: @border-thickness solid @border-default-color;
2298 }
2303 }
2299
2304
2300 .message {
2305 .message {
2301 height: auto;
2306 height: auto;
2302 max-width: 350px;
2307 max-width: 350px;
2303 white-space: normal;
2308 white-space: normal;
2304 text-overflow: initial;
2309 text-overflow: initial;
2305 overflow: visible;
2310 overflow: visible;
2306
2311
2307 .match { background-color: #faffa6;}
2312 .match { background-color: #faffa6;}
2308 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2313 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2309 }
2314 }
2310
2315
2311 }
2316 }
2312
2317
2313 table.rctable td.td-search-results div {
2318 table.rctable td.td-search-results div {
2314 max-width: 100%;
2319 max-width: 100%;
2315 }
2320 }
2316
2321
2317 #tip-box, .tip-box{
2322 #tip-box, .tip-box{
2318 padding: @menupadding/2;
2323 padding: @menupadding/2;
2319 display: block;
2324 display: block;
2320 border: @border-thickness solid @border-highlight-color;
2325 border: @border-thickness solid @border-highlight-color;
2321 .border-radius(@border-radius);
2326 .border-radius(@border-radius);
2322 background-color: white;
2327 background-color: white;
2323 z-index: 99;
2328 z-index: 99;
2324 white-space: pre-wrap;
2329 white-space: pre-wrap;
2325 }
2330 }
2326
2331
2327 #linktt {
2332 #linktt {
2328 width: 79px;
2333 width: 79px;
2329 }
2334 }
2330
2335
2331 #help_kb .modal-content{
2336 #help_kb .modal-content{
2332 max-width: 750px;
2337 max-width: 750px;
2333 margin: 10% auto;
2338 margin: 10% auto;
2334
2339
2335 table{
2340 table{
2336 td,th{
2341 td,th{
2337 border-bottom: none;
2342 border-bottom: none;
2338 line-height: 2.5em;
2343 line-height: 2.5em;
2339 }
2344 }
2340 th{
2345 th{
2341 padding-bottom: @textmargin/2;
2346 padding-bottom: @textmargin/2;
2342 }
2347 }
2343 td.keys{
2348 td.keys{
2344 text-align: center;
2349 text-align: center;
2345 }
2350 }
2346 }
2351 }
2347
2352
2348 .block-left{
2353 .block-left{
2349 width: 45%;
2354 width: 45%;
2350 margin-right: 5%;
2355 margin-right: 5%;
2351 }
2356 }
2352 .modal-footer{
2357 .modal-footer{
2353 clear: both;
2358 clear: both;
2354 }
2359 }
2355 .key.tag{
2360 .key.tag{
2356 padding: 0.5em;
2361 padding: 0.5em;
2357 background-color: @rcblue;
2362 background-color: @rcblue;
2358 color: white;
2363 color: white;
2359 border-color: @rcblue;
2364 border-color: @rcblue;
2360 .box-shadow(none);
2365 .box-shadow(none);
2361 }
2366 }
2362 }
2367 }
2363
2368
2364
2369
2365
2370
2366 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2371 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2367
2372
2368 @import 'statistics-graph';
2373 @import 'statistics-graph';
2369 @import 'tables';
2374 @import 'tables';
2370 @import 'forms';
2375 @import 'forms';
2371 @import 'diff';
2376 @import 'diff';
2372 @import 'summary';
2377 @import 'summary';
2373 @import 'navigation';
2378 @import 'navigation';
2374
2379
2375 //--- SHOW/HIDE SECTIONS --//
2380 //--- SHOW/HIDE SECTIONS --//
2376
2381
2377 .btn-collapse {
2382 .btn-collapse {
2378 float: right;
2383 float: right;
2379 text-align: right;
2384 text-align: right;
2380 font-family: @text-light;
2385 font-family: @text-light;
2381 font-size: @basefontsize;
2386 font-size: @basefontsize;
2382 cursor: pointer;
2387 cursor: pointer;
2383 border: none;
2388 border: none;
2384 color: @rcblue;
2389 color: @rcblue;
2385 }
2390 }
2386
2391
2387 table.rctable,
2392 table.rctable,
2388 table.dataTable {
2393 table.dataTable {
2389 .btn-collapse {
2394 .btn-collapse {
2390 float: right;
2395 float: right;
2391 text-align: right;
2396 text-align: right;
2392 }
2397 }
2393 }
2398 }
2394
2399
2395
2400
2396 // TODO: johbo: Fix for IE10, this avoids that we see a border
2401 // TODO: johbo: Fix for IE10, this avoids that we see a border
2397 // and padding around checkboxes and radio boxes. Move to the right place,
2402 // and padding around checkboxes and radio boxes. Move to the right place,
2398 // or better: Remove this once we did the form refactoring.
2403 // or better: Remove this once we did the form refactoring.
2399 input[type=checkbox],
2404 input[type=checkbox],
2400 input[type=radio] {
2405 input[type=radio] {
2401 padding: 0;
2406 padding: 0;
2402 border: none;
2407 border: none;
2403 }
2408 }
2404
2409
2405 .toggle-ajax-spinner{
2410 .toggle-ajax-spinner{
2406 height: 16px;
2411 height: 16px;
2407 width: 16px;
2412 width: 16px;
2408 }
2413 }
@@ -1,866 +1,869 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
5 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <span id="pr-title">
12 <span id="pr-title">
13 ${c.pull_request.title}
13 ${c.pull_request.title}
14 %if c.pull_request.is_closed():
14 %if c.pull_request.is_closed():
15 (${_('Closed')})
15 (${_('Closed')})
16 %endif
16 %endif
17 </span>
17 </span>
18 <div id="pr-title-edit" class="input" style="display: none;">
18 <div id="pr-title-edit" class="input" style="display: none;">
19 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
19 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
20 </div>
20 </div>
21 </%def>
21 </%def>
22
22
23 <%def name="menu_bar_nav()">
23 <%def name="menu_bar_nav()">
24 ${self.menu_items(active='repositories')}
24 ${self.menu_items(active='repositories')}
25 </%def>
25 </%def>
26
26
27 <%def name="menu_bar_subnav()">
27 <%def name="menu_bar_subnav()">
28 ${self.repo_menu(active='showpullrequest')}
28 ${self.repo_menu(active='showpullrequest')}
29 </%def>
29 </%def>
30
30
31 <%def name="main()">
31 <%def name="main()">
32
32
33 <script type="text/javascript">
33 <script type="text/javascript">
34 // TODO: marcink switch this to pyroutes
34 // TODO: marcink switch this to pyroutes
35 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
35 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
36 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
36 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 </script>
37 </script>
38 <div class="box">
38 <div class="box">
39
39
40 <div class="title">
40 <div class="title">
41 ${self.repo_page_title(c.rhodecode_db_repo)}
41 ${self.repo_page_title(c.rhodecode_db_repo)}
42 </div>
42 </div>
43
43
44 ${self.breadcrumbs()}
44 ${self.breadcrumbs()}
45
45
46 <div class="box pr-summary">
46 <div class="box pr-summary">
47
47
48 <div class="summary-details block-left">
48 <div class="summary-details block-left">
49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
50 <div class="pr-details-title">
50 <div class="pr-details-title">
51 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
51 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
52 %if c.allowed_to_update:
52 %if c.allowed_to_update:
53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
54 % if c.allowed_to_delete:
54 % if c.allowed_to_delete:
55 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
55 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
57 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
57 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
58 ${h.end_form()}
58 ${h.end_form()}
59 % else:
59 % else:
60 ${_('Delete')}
60 ${_('Delete')}
61 % endif
61 % endif
62 </div>
62 </div>
63 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
63 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
64 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
64 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
65 %endif
65 %endif
66 </div>
66 </div>
67
67
68 <div id="summary" class="fields pr-details-content">
68 <div id="summary" class="fields pr-details-content">
69 <div class="field">
69 <div class="field">
70 <div class="label-summary">
70 <div class="label-summary">
71 <label>${_('Source')}:</label>
71 <label>${_('Source')}:</label>
72 </div>
72 </div>
73 <div class="input">
73 <div class="input">
74 <div class="pr-origininfo">
74 <div class="pr-origininfo">
75 ## branch link is only valid if it is a branch
75 ## branch link is only valid if it is a branch
76 <span class="tag">
76 <span class="tag">
77 %if c.pull_request.source_ref_parts.type == 'branch':
77 %if c.pull_request.source_ref_parts.type == 'branch':
78 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
78 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
79 %else:
79 %else:
80 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
80 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
81 %endif
81 %endif
82 </span>
82 </span>
83 <span class="clone-url">
83 <span class="clone-url">
84 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
84 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
85 </span>
85 </span>
86 <br/>
86 <br/>
87 % if c.ancestor_commit:
87 % if c.ancestor_commit:
88 ${_('Common ancestor')}:
88 ${_('Common ancestor')}:
89 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
89 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
90 % endif
90 % endif
91 </div>
91 </div>
92 %if h.is_hg(c.pull_request.source_repo):
92 %if h.is_hg(c.pull_request.source_repo):
93 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
93 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
94 %elif h.is_git(c.pull_request.source_repo):
94 %elif h.is_git(c.pull_request.source_repo):
95 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
95 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
96 %endif
96 %endif
97
97
98 <div class="">
98 <div class="">
99 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
99 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
100 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
100 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
101 </div>
101 </div>
102
102
103 </div>
103 </div>
104 </div>
104 </div>
105 <div class="field">
105 <div class="field">
106 <div class="label-summary">
106 <div class="label-summary">
107 <label>${_('Target')}:</label>
107 <label>${_('Target')}:</label>
108 </div>
108 </div>
109 <div class="input">
109 <div class="input">
110 <div class="pr-targetinfo">
110 <div class="pr-targetinfo">
111 ## branch link is only valid if it is a branch
111 ## branch link is only valid if it is a branch
112 <span class="tag">
112 <span class="tag">
113 %if c.pull_request.target_ref_parts.type == 'branch':
113 %if c.pull_request.target_ref_parts.type == 'branch':
114 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
114 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
115 %else:
115 %else:
116 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
116 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
117 %endif
117 %endif
118 </span>
118 </span>
119 <span class="clone-url">
119 <span class="clone-url">
120 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
120 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
121 </span>
121 </span>
122 </div>
122 </div>
123 </div>
123 </div>
124 </div>
124 </div>
125
125
126 ## Link to the shadow repository.
126 ## Link to the shadow repository.
127 <div class="field">
127 <div class="field">
128 <div class="label-summary">
128 <div class="label-summary">
129 <label>${_('Merge')}:</label>
129 <label>${_('Merge')}:</label>
130 </div>
130 </div>
131 <div class="input">
131 <div class="input">
132 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
132 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
133 %if h.is_hg(c.pull_request.target_repo):
133 %if h.is_hg(c.pull_request.target_repo):
134 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
134 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
135 %elif h.is_git(c.pull_request.target_repo):
135 %elif h.is_git(c.pull_request.target_repo):
136 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
136 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
137 %endif
137 %endif
138 <div class="">
138 <div class="">
139 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
139 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
140 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
140 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
141 </div>
141 </div>
142 % else:
142 % else:
143 <div class="">
143 <div class="">
144 ${_('Shadow repository data not available')}.
144 ${_('Shadow repository data not available')}.
145 </div>
145 </div>
146 % endif
146 % endif
147 </div>
147 </div>
148 </div>
148 </div>
149
149
150 <div class="field">
150 <div class="field">
151 <div class="label-summary">
151 <div class="label-summary">
152 <label>${_('Review')}:</label>
152 <label>${_('Review')}:</label>
153 </div>
153 </div>
154 <div class="input">
154 <div class="input">
155 %if c.pull_request_review_status:
155 %if c.pull_request_review_status:
156 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
156 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
157 <span class="changeset-status-lbl tooltip">
157 <span class="changeset-status-lbl tooltip">
158 %if c.pull_request.is_closed():
158 %if c.pull_request.is_closed():
159 ${_('Closed')},
159 ${_('Closed')},
160 %endif
160 %endif
161 ${h.commit_status_lbl(c.pull_request_review_status)}
161 ${h.commit_status_lbl(c.pull_request_review_status)}
162 </span>
162 </span>
163 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
163 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
164 %endif
164 %endif
165 </div>
165 </div>
166 </div>
166 </div>
167 <div class="field">
167 <div class="field">
168 <div class="pr-description-label label-summary">
168 <div class="pr-description-label label-summary">
169 <label>${_('Description')}:</label>
169 <label>${_('Description')}:</label>
170 </div>
170 </div>
171 <div id="pr-desc" class="input">
171 <div id="pr-desc" class="input">
172 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
172 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
173 </div>
173 </div>
174 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
174 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
175 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
175 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
176 </div>
176 </div>
177 </div>
177 </div>
178
178
179 <div class="field">
179 <div class="field">
180 <div class="label-summary">
180 <div class="label-summary">
181 <label>${_('Versions')}:</label>
181 <label>${_('Versions')}:</label>
182 </div>
182 </div>
183
183
184 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
184 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
185 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
185 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
186
186
187 <div class="pr-versions">
187 <div class="pr-versions">
188 % if c.show_version_changes:
188 % if c.show_version_changes:
189 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
189 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
190 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
190 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
191 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
191 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
192 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
192 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
193 data-toggle-off="${_('Hide all versions of this pull request')}">
193 data-toggle-off="${_('Hide all versions of this pull request')}">
194 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
194 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
195 </a>
195 </a>
196 <table>
196 <table>
197 ## SHOW ALL VERSIONS OF PR
197 ## SHOW ALL VERSIONS OF PR
198 <% ver_pr = None %>
198 <% ver_pr = None %>
199
199
200 % for data in reversed(list(enumerate(c.versions, 1))):
200 % for data in reversed(list(enumerate(c.versions, 1))):
201 <% ver_pos = data[0] %>
201 <% ver_pos = data[0] %>
202 <% ver = data[1] %>
202 <% ver = data[1] %>
203 <% ver_pr = ver.pull_request_version_id %>
203 <% ver_pr = ver.pull_request_version_id %>
204 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
204 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
205
205
206 <tr class="version-pr" style="display: ${display_row}">
206 <tr class="version-pr" style="display: ${display_row}">
207 <td>
207 <td>
208 <code>
208 <code>
209 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
209 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
210 </code>
210 </code>
211 </td>
211 </td>
212 <td>
212 <td>
213 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
213 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
214 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
214 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
215 </td>
215 </td>
216 <td>
216 <td>
217 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
217 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
218 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
218 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
219 </div>
219 </div>
220 </td>
220 </td>
221 <td>
221 <td>
222 % if c.at_version_num != ver_pr:
222 % if c.at_version_num != ver_pr:
223 <i class="icon-comment"></i>
223 <i class="icon-comment"></i>
224 <code class="tooltip" title="${_('Comment from pull request version {0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
224 <code class="tooltip" title="${_('Comment from pull request version {0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
225 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
225 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
226 </code>
226 </code>
227 % endif
227 % endif
228 </td>
228 </td>
229 <td>
229 <td>
230 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
230 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
231 </td>
231 </td>
232 <td>
232 <td>
233 ${h.age_component(ver.updated_on, time_is_local=True)}
233 ${h.age_component(ver.updated_on, time_is_local=True)}
234 </td>
234 </td>
235 </tr>
235 </tr>
236 % endfor
236 % endfor
237
237
238 <tr>
238 <tr>
239 <td colspan="6">
239 <td colspan="6">
240 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
240 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
241 data-label-text-locked="${_('select versions to show changes')}"
241 data-label-text-locked="${_('select versions to show changes')}"
242 data-label-text-diff="${_('show changes between versions')}"
242 data-label-text-diff="${_('show changes between versions')}"
243 data-label-text-show="${_('show pull request for this version')}"
243 data-label-text-show="${_('show pull request for this version')}"
244 >
244 >
245 ${_('select versions to show changes')}
245 ${_('select versions to show changes')}
246 </button>
246 </button>
247 </td>
247 </td>
248 </tr>
248 </tr>
249
249
250 ## show comment/inline comments summary
250 ## show comment/inline comments summary
251 <%def name="comments_summary()">
251 <%def name="comments_summary()">
252 <tr>
252 <tr>
253 <td colspan="6" class="comments-summary-td">
253 <td colspan="6" class="comments-summary-td">
254
254
255 % if c.at_version:
255 % if c.at_version:
256 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
256 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
257 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
257 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
258 ${_('Comments at this version')}:
258 ${_('Comments at this version')}:
259 % else:
259 % else:
260 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
260 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
261 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
261 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
262 ${_('Comments for this pull request')}:
262 ${_('Comments for this pull request')}:
263 % endif
263 % endif
264
264
265
265
266 %if general_comm_count_ver:
266 %if general_comm_count_ver:
267 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
267 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
268 %else:
268 %else:
269 ${_("%d General ") % general_comm_count_ver}
269 ${_("%d General ") % general_comm_count_ver}
270 %endif
270 %endif
271
271
272 %if inline_comm_count_ver:
272 %if inline_comm_count_ver:
273 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
273 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
274 %else:
274 %else:
275 , ${_("%d Inline") % inline_comm_count_ver}
275 , ${_("%d Inline") % inline_comm_count_ver}
276 %endif
276 %endif
277
277
278 %if outdated_comm_count_ver:
278 %if outdated_comm_count_ver:
279 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
279 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
280 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
280 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
281 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
281 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
282 %else:
282 %else:
283 , ${_("%d Outdated") % outdated_comm_count_ver}
283 , ${_("%d Outdated") % outdated_comm_count_ver}
284 %endif
284 %endif
285 </td>
285 </td>
286 </tr>
286 </tr>
287 </%def>
287 </%def>
288 ${comments_summary()}
288 ${comments_summary()}
289 </table>
289 </table>
290 % else:
290 % else:
291 <div class="input">
291 <div class="input">
292 ${_('Pull request versions not available')}.
292 ${_('Pull request versions not available')}.
293 </div>
293 </div>
294 <div>
294 <div>
295 <table>
295 <table>
296 ${comments_summary()}
296 ${comments_summary()}
297 </table>
297 </table>
298 </div>
298 </div>
299 % endif
299 % endif
300 </div>
300 </div>
301 </div>
301 </div>
302
302
303 <div id="pr-save" class="field" style="display: none;">
303 <div id="pr-save" class="field" style="display: none;">
304 <div class="label-summary"></div>
304 <div class="label-summary"></div>
305 <div class="input">
305 <div class="input">
306 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
306 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
307 </div>
307 </div>
308 </div>
308 </div>
309 </div>
309 </div>
310 </div>
310 </div>
311 <div>
311 <div>
312 ## AUTHOR
312 ## AUTHOR
313 <div class="reviewers-title block-right">
313 <div class="reviewers-title block-right">
314 <div class="pr-details-title">
314 <div class="pr-details-title">
315 ${_('Author of this pull request')}
315 ${_('Author of this pull request')}
316 </div>
316 </div>
317 </div>
317 </div>
318 <div class="block-right pr-details-content reviewers">
318 <div class="block-right pr-details-content reviewers">
319 <ul class="group_members">
319 <ul class="group_members">
320 <li>
320 <li>
321 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
321 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
322 </li>
322 </li>
323 </ul>
323 </ul>
324 </div>
324 </div>
325
325
326 ## REVIEW RULES
326 ## REVIEW RULES
327 <div id="review_rules" style="display: none" class="reviewers-title block-right">
327 <div id="review_rules" style="display: none" class="reviewers-title block-right">
328 <div class="pr-details-title">
328 <div class="pr-details-title">
329 ${_('Reviewer rules')}
329 ${_('Reviewer rules')}
330 %if c.allowed_to_update:
330 %if c.allowed_to_update:
331 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
331 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
332 %endif
332 %endif
333 </div>
333 </div>
334 <div class="pr-reviewer-rules">
334 <div class="pr-reviewer-rules">
335 ## review rules will be appended here, by default reviewers logic
335 ## review rules will be appended here, by default reviewers logic
336 </div>
336 </div>
337 <input id="review_data" type="hidden" name="review_data" value="">
337 <input id="review_data" type="hidden" name="review_data" value="">
338 </div>
338 </div>
339
339
340 ## REVIEWERS
340 ## REVIEWERS
341 <div class="reviewers-title block-right">
341 <div class="reviewers-title block-right">
342 <div class="pr-details-title">
342 <div class="pr-details-title">
343 ${_('Pull request reviewers')}
343 ${_('Pull request reviewers')} / <a href="#toggleReasons" onclick="$('.reviewer_reason').toggle(); return false">${_('show reasons')}</a>
344 %if c.allowed_to_update:
344 %if c.allowed_to_update:
345 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
345 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
346 %endif
346 %endif
347 </div>
347 </div>
348 </div>
348 </div>
349 <div id="reviewers" class="block-right pr-details-content reviewers">
349 <div id="reviewers" class="block-right pr-details-content reviewers">
350 ## members goes here !
350 ## members goes here !
351 <input type="hidden" name="__start__" value="review_members:sequence">
351 <input type="hidden" name="__start__" value="review_members:sequence">
352 <ul id="review_members" class="group_members">
352 <ul id="review_members" class="group_members">
353 %for member,reasons,mandatory,status in c.pull_request_reviewers:
353 %for member,reasons,mandatory,status in c.pull_request_reviewers:
354 <li id="reviewer_${member.user_id}" class="reviewer_entry">
354 <li id="reviewer_${member.user_id}">
355 <div class="reviewers_member">
355 <div class="reviewers_member">
356 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
356 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
357 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
357 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
358 </div>
358 </div>
359 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
359 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
360 ${self.gravatar_with_user(member.email, 16)}
360 ${self.gravatar_with_user(member.email, 16)}
361 </div>
361 </div>
362 <input type="hidden" name="__start__" value="reviewer:mapping">
362 <input type="hidden" name="__start__" value="reviewer:mapping">
363 <input type="hidden" name="__start__" value="reasons:sequence">
363 <input type="hidden" name="__start__" value="reasons:sequence">
364 % if reasons:
365 <div class="reviewer_reason_container">
364 %for reason in reasons:
366 %for reason in reasons:
365 <div class="reviewer_reason">- ${reason}</div>
367 <div class="reviewer_reason" style="display: none">- ${reason}</div>
366 <input type="hidden" name="reason" value="${reason}">
368 <input type="hidden" name="reason" value="${reason}">
367
368 %endfor
369 %endfor
370 </div>
371 % endif
369 <input type="hidden" name="__end__" value="reasons:sequence">
372 <input type="hidden" name="__end__" value="reasons:sequence">
370 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
373 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="user_id" />
371 <input type="hidden" name="mandatory" value="${mandatory}"/>
374 <input type="hidden" name="mandatory" value="${mandatory}"/>
372 <input type="hidden" name="__end__" value="reviewer:mapping">
375 <input type="hidden" name="__end__" value="reviewer:mapping">
373 % if mandatory:
376 % if mandatory:
374 <div class="reviewer_member_mandatory_remove">
377 <div class="reviewer_member_mandatory_remove">
375 <i class="icon-remove-sign"></i>
378 <i class="icon-remove-sign"></i>
376 </div>
379 </div>
377 <div class="reviewer_member_mandatory">
380 <div class="reviewer_member_mandatory">
378 <i class="icon-lock" title="${h.tooltip(_('Mandatory reviewer'))}"></i>
381 <i class="icon-lock" title="${h.tooltip(_('Mandatory reviewer'))}"></i>
379 </div>
382 </div>
380 % else:
383 % else:
381 %if c.allowed_to_update:
384 %if c.allowed_to_update:
382 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
385 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
383 <i class="icon-remove-sign" ></i>
386 <i class="icon-remove-sign" ></i>
384 </div>
387 </div>
385 %endif
388 %endif
386 % endif
389 % endif
387 </div>
390 </div>
388 </li>
391 </li>
389 %endfor
392 %endfor
390 </ul>
393 </ul>
391 <input type="hidden" name="__end__" value="review_members:sequence">
394 <input type="hidden" name="__end__" value="review_members:sequence">
392
395
393 %if not c.pull_request.is_closed():
396 %if not c.pull_request.is_closed():
394 <div id="add_reviewer" class="ac" style="display: none;">
397 <div id="add_reviewer" class="ac" style="display: none;">
395 %if c.allowed_to_update:
398 %if c.allowed_to_update:
396 % if not c.forbid_adding_reviewers:
399 % if not c.forbid_adding_reviewers:
397 <div id="add_reviewer_input" class="reviewer_ac">
400 <div id="add_reviewer_input" class="reviewer_ac">
398 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
401 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
399 <div id="reviewers_container"></div>
402 <div id="reviewers_container"></div>
400 </div>
403 </div>
401 % endif
404 % endif
402 <div class="pull-right">
405 <div class="pull-right">
403 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
406 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
404 </div>
407 </div>
405 %endif
408 %endif
406 </div>
409 </div>
407 %endif
410 %endif
408 </div>
411 </div>
409 </div>
412 </div>
410 </div>
413 </div>
411 <div class="box">
414 <div class="box">
412 ##DIFF
415 ##DIFF
413 <div class="table" >
416 <div class="table" >
414 <div id="changeset_compare_view_content">
417 <div id="changeset_compare_view_content">
415 ##CS
418 ##CS
416 % if c.missing_requirements:
419 % if c.missing_requirements:
417 <div class="box">
420 <div class="box">
418 <div class="alert alert-warning">
421 <div class="alert alert-warning">
419 <div>
422 <div>
420 <strong>${_('Missing requirements:')}</strong>
423 <strong>${_('Missing requirements:')}</strong>
421 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
424 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
422 </div>
425 </div>
423 </div>
426 </div>
424 </div>
427 </div>
425 % elif c.missing_commits:
428 % elif c.missing_commits:
426 <div class="box">
429 <div class="box">
427 <div class="alert alert-warning">
430 <div class="alert alert-warning">
428 <div>
431 <div>
429 <strong>${_('Missing commits')}:</strong>
432 <strong>${_('Missing commits')}:</strong>
430 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
433 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
431 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
434 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
432 </div>
435 </div>
433 </div>
436 </div>
434 </div>
437 </div>
435 % endif
438 % endif
436
439
437 <div class="compare_view_commits_title">
440 <div class="compare_view_commits_title">
438 % if not c.compare_mode:
441 % if not c.compare_mode:
439
442
440 % if c.at_version_pos:
443 % if c.at_version_pos:
441 <h4>
444 <h4>
442 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
445 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
443 </h4>
446 </h4>
444 % endif
447 % endif
445
448
446 <div class="pull-left">
449 <div class="pull-left">
447 <div class="btn-group">
450 <div class="btn-group">
448 <a
451 <a
449 class="btn"
452 class="btn"
450 href="#"
453 href="#"
451 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
454 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
452 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
455 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
453 </a>
456 </a>
454 <a
457 <a
455 class="btn"
458 class="btn"
456 href="#"
459 href="#"
457 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
460 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
458 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
461 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
459 </a>
462 </a>
460 </div>
463 </div>
461 </div>
464 </div>
462
465
463 <div class="pull-right">
466 <div class="pull-right">
464 % if c.allowed_to_update and not c.pull_request.is_closed():
467 % if c.allowed_to_update and not c.pull_request.is_closed():
465 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
468 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
466 % else:
469 % else:
467 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
470 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
468 % endif
471 % endif
469
472
470 </div>
473 </div>
471 % endif
474 % endif
472 </div>
475 </div>
473
476
474 % if not c.missing_commits:
477 % if not c.missing_commits:
475 % if c.compare_mode:
478 % if c.compare_mode:
476 % if c.at_version:
479 % if c.at_version:
477 <h4>
480 <h4>
478 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
481 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
479 </h4>
482 </h4>
480
483
481 <div class="subtitle-compare">
484 <div class="subtitle-compare">
482 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
485 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
483 </div>
486 </div>
484
487
485 <div class="container">
488 <div class="container">
486 <table class="rctable compare_view_commits">
489 <table class="rctable compare_view_commits">
487 <tr>
490 <tr>
488 <th></th>
491 <th></th>
489 <th>${_('Time')}</th>
492 <th>${_('Time')}</th>
490 <th>${_('Author')}</th>
493 <th>${_('Author')}</th>
491 <th>${_('Commit')}</th>
494 <th>${_('Commit')}</th>
492 <th></th>
495 <th></th>
493 <th>${_('Description')}</th>
496 <th>${_('Description')}</th>
494 </tr>
497 </tr>
495
498
496 % for c_type, commit in c.commit_changes:
499 % for c_type, commit in c.commit_changes:
497 % if c_type in ['a', 'r']:
500 % if c_type in ['a', 'r']:
498 <%
501 <%
499 if c_type == 'a':
502 if c_type == 'a':
500 cc_title = _('Commit added in displayed changes')
503 cc_title = _('Commit added in displayed changes')
501 elif c_type == 'r':
504 elif c_type == 'r':
502 cc_title = _('Commit removed in displayed changes')
505 cc_title = _('Commit removed in displayed changes')
503 else:
506 else:
504 cc_title = ''
507 cc_title = ''
505 %>
508 %>
506 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
509 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
507 <td>
510 <td>
508 <div class="commit-change-indicator color-${c_type}-border">
511 <div class="commit-change-indicator color-${c_type}-border">
509 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
512 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
510 ${c_type.upper()}
513 ${c_type.upper()}
511 </div>
514 </div>
512 </div>
515 </div>
513 </td>
516 </td>
514 <td class="td-time">
517 <td class="td-time">
515 ${h.age_component(commit.date)}
518 ${h.age_component(commit.date)}
516 </td>
519 </td>
517 <td class="td-user">
520 <td class="td-user">
518 ${base.gravatar_with_user(commit.author, 16)}
521 ${base.gravatar_with_user(commit.author, 16)}
519 </td>
522 </td>
520 <td class="td-hash">
523 <td class="td-hash">
521 <code>
524 <code>
522 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
525 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
523 r${commit.revision}:${h.short_id(commit.raw_id)}
526 r${commit.revision}:${h.short_id(commit.raw_id)}
524 </a>
527 </a>
525 ${h.hidden('revisions', commit.raw_id)}
528 ${h.hidden('revisions', commit.raw_id)}
526 </code>
529 </code>
527 </td>
530 </td>
528 <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}">
531 <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}">
529 <div class="show_more_col">
532 <div class="show_more_col">
530 <i class="show_more"></i>
533 <i class="show_more"></i>
531 </div>
534 </div>
532 </td>
535 </td>
533 <td class="mid td-description">
536 <td class="mid td-description">
534 <div class="log-container truncate-wrap">
537 <div class="log-container truncate-wrap">
535 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">
538 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">
536 ${h.urlify_commit_message(commit.message, c.repo_name)}
539 ${h.urlify_commit_message(commit.message, c.repo_name)}
537 </div>
540 </div>
538 </div>
541 </div>
539 </td>
542 </td>
540 </tr>
543 </tr>
541 % endif
544 % endif
542 % endfor
545 % endfor
543 </table>
546 </table>
544 </div>
547 </div>
545
548
546 <script>
549 <script>
547 $('.expand_commit').on('click',function(e){
550 $('.expand_commit').on('click',function(e){
548 var target_expand = $(this);
551 var target_expand = $(this);
549 var cid = target_expand.data('commitId');
552 var cid = target_expand.data('commitId');
550
553
551 if (target_expand.hasClass('open')){
554 if (target_expand.hasClass('open')){
552 $('#c-'+cid).css({
555 $('#c-'+cid).css({
553 'height': '1.5em',
556 'height': '1.5em',
554 'white-space': 'nowrap',
557 'white-space': 'nowrap',
555 'text-overflow': 'ellipsis',
558 'text-overflow': 'ellipsis',
556 'overflow':'hidden'
559 'overflow':'hidden'
557 });
560 });
558 target_expand.removeClass('open');
561 target_expand.removeClass('open');
559 }
562 }
560 else {
563 else {
561 $('#c-'+cid).css({
564 $('#c-'+cid).css({
562 'height': 'auto',
565 'height': 'auto',
563 'white-space': 'pre-line',
566 'white-space': 'pre-line',
564 'text-overflow': 'initial',
567 'text-overflow': 'initial',
565 'overflow':'visible'
568 'overflow':'visible'
566 });
569 });
567 target_expand.addClass('open');
570 target_expand.addClass('open');
568 }
571 }
569 });
572 });
570 </script>
573 </script>
571
574
572 % endif
575 % endif
573
576
574 % else:
577 % else:
575 <%include file="/compare/compare_commits.mako" />
578 <%include file="/compare/compare_commits.mako" />
576 % endif
579 % endif
577
580
578 <div class="cs_files">
581 <div class="cs_files">
579 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
582 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
580 ${cbdiffs.render_diffset_menu()}
583 ${cbdiffs.render_diffset_menu()}
581 ${cbdiffs.render_diffset(
584 ${cbdiffs.render_diffset(
582 c.diffset, use_comments=True,
585 c.diffset, use_comments=True,
583 collapse_when_files_over=30,
586 collapse_when_files_over=30,
584 disable_new_comments=not c.allowed_to_comment,
587 disable_new_comments=not c.allowed_to_comment,
585 deleted_files_comments=c.deleted_files_comments)}
588 deleted_files_comments=c.deleted_files_comments)}
586 </div>
589 </div>
587 % else:
590 % else:
588 ## skipping commits we need to clear the view for missing commits
591 ## skipping commits we need to clear the view for missing commits
589 <div style="clear:both;"></div>
592 <div style="clear:both;"></div>
590 % endif
593 % endif
591
594
592 </div>
595 </div>
593 </div>
596 </div>
594
597
595 ## template for inline comment form
598 ## template for inline comment form
596 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
599 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
597
600
598 ## render general comments
601 ## render general comments
599
602
600 <div id="comment-tr-show">
603 <div id="comment-tr-show">
601 <div class="comment">
604 <div class="comment">
602 % if general_outdated_comm_count_ver:
605 % if general_outdated_comm_count_ver:
603 <div class="meta">
606 <div class="meta">
604 % if general_outdated_comm_count_ver == 1:
607 % if general_outdated_comm_count_ver == 1:
605 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
608 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
606 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
609 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
607 % else:
610 % else:
608 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
611 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
609 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
612 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
610 % endif
613 % endif
611 </div>
614 </div>
612 % endif
615 % endif
613 </div>
616 </div>
614 </div>
617 </div>
615
618
616 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
619 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
617
620
618 % if not c.pull_request.is_closed():
621 % if not c.pull_request.is_closed():
619 ## merge status, and merge action
622 ## merge status, and merge action
620 <div class="pull-request-merge">
623 <div class="pull-request-merge">
621 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
624 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
622 </div>
625 </div>
623
626
624 ## main comment form and it status
627 ## main comment form and it status
625 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
628 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
626 pull_request_id=c.pull_request.pull_request_id),
629 pull_request_id=c.pull_request.pull_request_id),
627 c.pull_request_review_status,
630 c.pull_request_review_status,
628 is_pull_request=True, change_status=c.allowed_to_change_status)}
631 is_pull_request=True, change_status=c.allowed_to_change_status)}
629 %endif
632 %endif
630
633
631 <script type="text/javascript">
634 <script type="text/javascript">
632 if (location.hash) {
635 if (location.hash) {
633 var result = splitDelimitedHash(location.hash);
636 var result = splitDelimitedHash(location.hash);
634 var line = $('html').find(result.loc);
637 var line = $('html').find(result.loc);
635 // show hidden comments if we use location.hash
638 // show hidden comments if we use location.hash
636 if (line.hasClass('comment-general')) {
639 if (line.hasClass('comment-general')) {
637 $(line).show();
640 $(line).show();
638 } else if (line.hasClass('comment-inline')) {
641 } else if (line.hasClass('comment-inline')) {
639 $(line).show();
642 $(line).show();
640 var $cb = $(line).closest('.cb');
643 var $cb = $(line).closest('.cb');
641 $cb.removeClass('cb-collapsed')
644 $cb.removeClass('cb-collapsed')
642 }
645 }
643 if (line.length > 0){
646 if (line.length > 0){
644 offsetScroll(line, 70);
647 offsetScroll(line, 70);
645 }
648 }
646 }
649 }
647
650
648 versionController = new VersionController();
651 versionController = new VersionController();
649 versionController.init();
652 versionController.init();
650
653
651 reviewersController = new ReviewersController();
654 reviewersController = new ReviewersController();
652
655
653 $(function(){
656 $(function(){
654
657
655 // custom code mirror
658 // custom code mirror
656 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
659 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
657
660
658 var PRDetails = {
661 var PRDetails = {
659 editButton: $('#open_edit_pullrequest'),
662 editButton: $('#open_edit_pullrequest'),
660 closeButton: $('#close_edit_pullrequest'),
663 closeButton: $('#close_edit_pullrequest'),
661 deleteButton: $('#delete_pullrequest'),
664 deleteButton: $('#delete_pullrequest'),
662 viewFields: $('#pr-desc, #pr-title'),
665 viewFields: $('#pr-desc, #pr-title'),
663 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
666 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
664
667
665 init: function() {
668 init: function() {
666 var that = this;
669 var that = this;
667 this.editButton.on('click', function(e) { that.edit(); });
670 this.editButton.on('click', function(e) { that.edit(); });
668 this.closeButton.on('click', function(e) { that.view(); });
671 this.closeButton.on('click', function(e) { that.view(); });
669 },
672 },
670
673
671 edit: function(event) {
674 edit: function(event) {
672 this.viewFields.hide();
675 this.viewFields.hide();
673 this.editButton.hide();
676 this.editButton.hide();
674 this.deleteButton.hide();
677 this.deleteButton.hide();
675 this.closeButton.show();
678 this.closeButton.show();
676 this.editFields.show();
679 this.editFields.show();
677 codeMirrorInstance.refresh();
680 codeMirrorInstance.refresh();
678 },
681 },
679
682
680 view: function(event) {
683 view: function(event) {
681 this.editButton.show();
684 this.editButton.show();
682 this.deleteButton.show();
685 this.deleteButton.show();
683 this.editFields.hide();
686 this.editFields.hide();
684 this.closeButton.hide();
687 this.closeButton.hide();
685 this.viewFields.show();
688 this.viewFields.show();
686 }
689 }
687 };
690 };
688
691
689 var ReviewersPanel = {
692 var ReviewersPanel = {
690 editButton: $('#open_edit_reviewers'),
693 editButton: $('#open_edit_reviewers'),
691 closeButton: $('#close_edit_reviewers'),
694 closeButton: $('#close_edit_reviewers'),
692 addButton: $('#add_reviewer'),
695 addButton: $('#add_reviewer'),
693 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove,.reviewer_member_mandatory'),
696 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove,.reviewer_member_mandatory'),
694
697
695 init: function() {
698 init: function() {
696 var self = this;
699 var self = this;
697 this.editButton.on('click', function(e) { self.edit(); });
700 this.editButton.on('click', function(e) { self.edit(); });
698 this.closeButton.on('click', function(e) { self.close(); });
701 this.closeButton.on('click', function(e) { self.close(); });
699 },
702 },
700
703
701 edit: function(event) {
704 edit: function(event) {
702 this.editButton.hide();
705 this.editButton.hide();
703 this.closeButton.show();
706 this.closeButton.show();
704 this.addButton.show();
707 this.addButton.show();
705 this.removeButtons.css('visibility', 'visible');
708 this.removeButtons.css('visibility', 'visible');
706 // review rules
709 // review rules
707 reviewersController.loadReviewRules(
710 reviewersController.loadReviewRules(
708 ${c.pull_request.reviewer_data_json | n});
711 ${c.pull_request.reviewer_data_json | n});
709 },
712 },
710
713
711 close: function(event) {
714 close: function(event) {
712 this.editButton.show();
715 this.editButton.show();
713 this.closeButton.hide();
716 this.closeButton.hide();
714 this.addButton.hide();
717 this.addButton.hide();
715 this.removeButtons.css('visibility', 'hidden');
718 this.removeButtons.css('visibility', 'hidden');
716 // hide review rules
719 // hide review rules
717 reviewersController.hideReviewRules()
720 reviewersController.hideReviewRules()
718 }
721 }
719 };
722 };
720
723
721 PRDetails.init();
724 PRDetails.init();
722 ReviewersPanel.init();
725 ReviewersPanel.init();
723
726
724 showOutdated = function(self){
727 showOutdated = function(self){
725 $('.comment-inline.comment-outdated').show();
728 $('.comment-inline.comment-outdated').show();
726 $('.filediff-outdated').show();
729 $('.filediff-outdated').show();
727 $('.showOutdatedComments').hide();
730 $('.showOutdatedComments').hide();
728 $('.hideOutdatedComments').show();
731 $('.hideOutdatedComments').show();
729 };
732 };
730
733
731 hideOutdated = function(self){
734 hideOutdated = function(self){
732 $('.comment-inline.comment-outdated').hide();
735 $('.comment-inline.comment-outdated').hide();
733 $('.filediff-outdated').hide();
736 $('.filediff-outdated').hide();
734 $('.hideOutdatedComments').hide();
737 $('.hideOutdatedComments').hide();
735 $('.showOutdatedComments').show();
738 $('.showOutdatedComments').show();
736 };
739 };
737
740
738 refreshMergeChecks = function(){
741 refreshMergeChecks = function(){
739 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
742 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
740 $('.pull-request-merge').css('opacity', 0.3);
743 $('.pull-request-merge').css('opacity', 0.3);
741 $('.action-buttons-extra').css('opacity', 0.3);
744 $('.action-buttons-extra').css('opacity', 0.3);
742
745
743 $('.pull-request-merge').load(
746 $('.pull-request-merge').load(
744 loadUrl, function() {
747 loadUrl, function() {
745 $('.pull-request-merge').css('opacity', 1);
748 $('.pull-request-merge').css('opacity', 1);
746
749
747 $('.action-buttons-extra').css('opacity', 1);
750 $('.action-buttons-extra').css('opacity', 1);
748 injectCloseAction();
751 injectCloseAction();
749 }
752 }
750 );
753 );
751 };
754 };
752
755
753 injectCloseAction = function() {
756 injectCloseAction = function() {
754 var closeAction = $('#close-pull-request-action').html();
757 var closeAction = $('#close-pull-request-action').html();
755 var $actionButtons = $('.action-buttons-extra');
758 var $actionButtons = $('.action-buttons-extra');
756 // clear the action before
759 // clear the action before
757 $actionButtons.html("");
760 $actionButtons.html("");
758 $actionButtons.html(closeAction);
761 $actionButtons.html(closeAction);
759 };
762 };
760
763
761 closePullRequest = function (status) {
764 closePullRequest = function (status) {
762 // inject closing flag
765 // inject closing flag
763 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
766 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
764 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
767 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
765 $(generalCommentForm.submitForm).submit();
768 $(generalCommentForm.submitForm).submit();
766 };
769 };
767
770
768 $('#show-outdated-comments').on('click', function(e){
771 $('#show-outdated-comments').on('click', function(e){
769 var button = $(this);
772 var button = $(this);
770 var outdated = $('.comment-outdated');
773 var outdated = $('.comment-outdated');
771
774
772 if (button.html() === "(Show)") {
775 if (button.html() === "(Show)") {
773 button.html("(Hide)");
776 button.html("(Hide)");
774 outdated.show();
777 outdated.show();
775 } else {
778 } else {
776 button.html("(Show)");
779 button.html("(Show)");
777 outdated.hide();
780 outdated.hide();
778 }
781 }
779 });
782 });
780
783
781 $('.show-inline-comments').on('change', function(e){
784 $('.show-inline-comments').on('change', function(e){
782 var show = 'none';
785 var show = 'none';
783 var target = e.currentTarget;
786 var target = e.currentTarget;
784 if(target.checked){
787 if(target.checked){
785 show = ''
788 show = ''
786 }
789 }
787 var boxid = $(target).attr('id_for');
790 var boxid = $(target).attr('id_for');
788 var comments = $('#{0} .inline-comments'.format(boxid));
791 var comments = $('#{0} .inline-comments'.format(boxid));
789 var fn_display = function(idx){
792 var fn_display = function(idx){
790 $(this).css('display', show);
793 $(this).css('display', show);
791 };
794 };
792 $(comments).each(fn_display);
795 $(comments).each(fn_display);
793 var btns = $('#{0} .inline-comments-button'.format(boxid));
796 var btns = $('#{0} .inline-comments-button'.format(boxid));
794 $(btns).each(fn_display);
797 $(btns).each(fn_display);
795 });
798 });
796
799
797 $('#merge_pull_request_form').submit(function() {
800 $('#merge_pull_request_form').submit(function() {
798 if (!$('#merge_pull_request').attr('disabled')) {
801 if (!$('#merge_pull_request').attr('disabled')) {
799 $('#merge_pull_request').attr('disabled', 'disabled');
802 $('#merge_pull_request').attr('disabled', 'disabled');
800 }
803 }
801 return true;
804 return true;
802 });
805 });
803
806
804 $('#edit_pull_request').on('click', function(e){
807 $('#edit_pull_request').on('click', function(e){
805 var title = $('#pr-title-input').val();
808 var title = $('#pr-title-input').val();
806 var description = codeMirrorInstance.getValue();
809 var description = codeMirrorInstance.getValue();
807 editPullRequest(
810 editPullRequest(
808 "${c.repo_name}", "${c.pull_request.pull_request_id}",
811 "${c.repo_name}", "${c.pull_request.pull_request_id}",
809 title, description);
812 title, description);
810 });
813 });
811
814
812 $('#update_pull_request').on('click', function(e){
815 $('#update_pull_request').on('click', function(e){
813 $(this).attr('disabled', 'disabled');
816 $(this).attr('disabled', 'disabled');
814 $(this).addClass('disabled');
817 $(this).addClass('disabled');
815 $(this).html(_gettext('Saving...'));
818 $(this).html(_gettext('Saving...'));
816 reviewersController.updateReviewers(
819 reviewersController.updateReviewers(
817 "${c.repo_name}", "${c.pull_request.pull_request_id}");
820 "${c.repo_name}", "${c.pull_request.pull_request_id}");
818 });
821 });
819
822
820 $('#update_commits').on('click', function(e){
823 $('#update_commits').on('click', function(e){
821 var isDisabled = !$(e.currentTarget).attr('disabled');
824 var isDisabled = !$(e.currentTarget).attr('disabled');
822 $(e.currentTarget).attr('disabled', 'disabled');
825 $(e.currentTarget).attr('disabled', 'disabled');
823 $(e.currentTarget).addClass('disabled');
826 $(e.currentTarget).addClass('disabled');
824 $(e.currentTarget).removeClass('btn-primary');
827 $(e.currentTarget).removeClass('btn-primary');
825 $(e.currentTarget).text(_gettext('Updating...'));
828 $(e.currentTarget).text(_gettext('Updating...'));
826 if(isDisabled){
829 if(isDisabled){
827 updateCommits(
830 updateCommits(
828 "${c.repo_name}", "${c.pull_request.pull_request_id}");
831 "${c.repo_name}", "${c.pull_request.pull_request_id}");
829 }
832 }
830 });
833 });
831 // fixing issue with caches on firefox
834 // fixing issue with caches on firefox
832 $('#update_commits').removeAttr("disabled");
835 $('#update_commits').removeAttr("disabled");
833
836
834 $('.show-inline-comments').on('click', function(e){
837 $('.show-inline-comments').on('click', function(e){
835 var boxid = $(this).attr('data-comment-id');
838 var boxid = $(this).attr('data-comment-id');
836 var button = $(this);
839 var button = $(this);
837
840
838 if(button.hasClass("comments-visible")) {
841 if(button.hasClass("comments-visible")) {
839 $('#{0} .inline-comments'.format(boxid)).each(function(index){
842 $('#{0} .inline-comments'.format(boxid)).each(function(index){
840 $(this).hide();
843 $(this).hide();
841 });
844 });
842 button.removeClass("comments-visible");
845 button.removeClass("comments-visible");
843 } else {
846 } else {
844 $('#{0} .inline-comments'.format(boxid)).each(function(index){
847 $('#{0} .inline-comments'.format(boxid)).each(function(index){
845 $(this).show();
848 $(this).show();
846 });
849 });
847 button.addClass("comments-visible");
850 button.addClass("comments-visible");
848 }
851 }
849 });
852 });
850
853
851 // register submit callback on commentForm form to track TODOs
854 // register submit callback on commentForm form to track TODOs
852 window.commentFormGlobalSubmitSuccessCallback = function(){
855 window.commentFormGlobalSubmitSuccessCallback = function(){
853 refreshMergeChecks();
856 refreshMergeChecks();
854 };
857 };
855 // initial injection
858 // initial injection
856 injectCloseAction();
859 injectCloseAction();
857
860
858 ReviewerAutoComplete('#user');
861 ReviewerAutoComplete('#user');
859
862
860 })
863 })
861 </script>
864 </script>
862
865
863 </div>
866 </div>
864 </div>
867 </div>
865
868
866 </%def>
869 </%def>
General Comments 0
You need to be logged in to leave comments. Login now