##// END OF EJS Templates
comments: made few UI changes/improvements for how we show comments status, ribbons and general comment data.
marcink -
r4411:569617be default
parent child Browse files
Show More
@@ -1,5635 +1,5640 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, cast, TypeDecorator, event,
40 or_, and_, not_, func, cast, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers2.text import remove_formatting
55 from webhelpers2.text import remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
73
73
74 URL_SEP = '/'
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76
76
77 # =============================================================================
77 # =============================================================================
78 # BASE CLASSES
78 # BASE CLASSES
79 # =============================================================================
79 # =============================================================================
80
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY = None
84 ENCRYPTION_KEY = None
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
89 'admin': '####',
90 'write': '###',
90 'write': '###',
91 'read': '##',
91 'read': '##',
92 'none': '#',
92 'none': '#',
93 }
93 }
94
94
95
95
96 def display_user_sort(obj):
96 def display_user_sort(obj):
97 """
97 """
98 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
100 of all other resources
101 """
101 """
102
102
103 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 return prefix + obj.username
106 return prefix + obj.username
107
107
108
108
109 def display_user_group_sort(obj):
109 def display_user_group_sort(obj):
110 """
110 """
111 Sort function used to sort permissions in .permissions() function of
111 Sort function used to sort permissions in .permissions() function of
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
113 of all other resources
113 of all other resources
114 """
114 """
115
115
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
117 return prefix + obj.users_group_name
117 return prefix + obj.users_group_name
118
118
119
119
120 def _hash_key(k):
120 def _hash_key(k):
121 return sha1_safe(k)
121 return sha1_safe(k)
122
122
123
123
124 def in_filter_generator(qry, items, limit=500):
124 def in_filter_generator(qry, items, limit=500):
125 """
125 """
126 Splits IN() into multiple with OR
126 Splits IN() into multiple with OR
127 e.g.::
127 e.g.::
128 cnt = Repository.query().filter(
128 cnt = Repository.query().filter(
129 or_(
129 or_(
130 *in_filter_generator(Repository.repo_id, range(100000))
130 *in_filter_generator(Repository.repo_id, range(100000))
131 )).count()
131 )).count()
132 """
132 """
133 if not items:
133 if not items:
134 # empty list will cause empty query which might cause security issues
134 # empty list will cause empty query which might cause security issues
135 # this can lead to hidden unpleasant results
135 # this can lead to hidden unpleasant results
136 items = [-1]
136 items = [-1]
137
137
138 parts = []
138 parts = []
139 for chunk in xrange(0, len(items), limit):
139 for chunk in xrange(0, len(items), limit):
140 parts.append(
140 parts.append(
141 qry.in_(items[chunk: chunk + limit])
141 qry.in_(items[chunk: chunk + limit])
142 )
142 )
143
143
144 return parts
144 return parts
145
145
146
146
147 base_table_args = {
147 base_table_args = {
148 'extend_existing': True,
148 'extend_existing': True,
149 'mysql_engine': 'InnoDB',
149 'mysql_engine': 'InnoDB',
150 'mysql_charset': 'utf8',
150 'mysql_charset': 'utf8',
151 'sqlite_autoincrement': True
151 'sqlite_autoincrement': True
152 }
152 }
153
153
154
154
155 class EncryptedTextValue(TypeDecorator):
155 class EncryptedTextValue(TypeDecorator):
156 """
156 """
157 Special column for encrypted long text data, use like::
157 Special column for encrypted long text data, use like::
158
158
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
160
160
161 This column is intelligent so if value is in unencrypted form it return
161 This column is intelligent so if value is in unencrypted form it return
162 unencrypted form, but on save it always encrypts
162 unencrypted form, but on save it always encrypts
163 """
163 """
164 impl = Text
164 impl = Text
165
165
166 def process_bind_param(self, value, dialect):
166 def process_bind_param(self, value, dialect):
167 """
167 """
168 Setter for storing value
168 Setter for storing value
169 """
169 """
170 import rhodecode
170 import rhodecode
171 if not value:
171 if not value:
172 return value
172 return value
173
173
174 # protect against double encrypting if values is already encrypted
174 # protect against double encrypting if values is already encrypted
175 if value.startswith('enc$aes$') \
175 if value.startswith('enc$aes$') \
176 or value.startswith('enc$aes_hmac$') \
176 or value.startswith('enc$aes_hmac$') \
177 or value.startswith('enc2$'):
177 or value.startswith('enc2$'):
178 raise ValueError('value needs to be in unencrypted format, '
178 raise ValueError('value needs to be in unencrypted format, '
179 'ie. not starting with enc$ or enc2$')
179 'ie. not starting with enc$ or enc2$')
180
180
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
182 if algo == 'aes':
182 if algo == 'aes':
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
184 elif algo == 'fernet':
184 elif algo == 'fernet':
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
186 else:
186 else:
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
188
188
189 def process_result_value(self, value, dialect):
189 def process_result_value(self, value, dialect):
190 """
190 """
191 Getter for retrieving value
191 Getter for retrieving value
192 """
192 """
193
193
194 import rhodecode
194 import rhodecode
195 if not value:
195 if not value:
196 return value
196 return value
197
197
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
200 if algo == 'aes':
200 if algo == 'aes':
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
202 elif algo == 'fernet':
202 elif algo == 'fernet':
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
204 else:
204 else:
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
206 return decrypted_data
206 return decrypted_data
207
207
208
208
209 class BaseModel(object):
209 class BaseModel(object):
210 """
210 """
211 Base Model for all classes
211 Base Model for all classes
212 """
212 """
213
213
214 @classmethod
214 @classmethod
215 def _get_keys(cls):
215 def _get_keys(cls):
216 """return column names for this model """
216 """return column names for this model """
217 return class_mapper(cls).c.keys()
217 return class_mapper(cls).c.keys()
218
218
219 def get_dict(self):
219 def get_dict(self):
220 """
220 """
221 return dict with keys and values corresponding
221 return dict with keys and values corresponding
222 to this model data """
222 to this model data """
223
223
224 d = {}
224 d = {}
225 for k in self._get_keys():
225 for k in self._get_keys():
226 d[k] = getattr(self, k)
226 d[k] = getattr(self, k)
227
227
228 # also use __json__() if present to get additional fields
228 # also use __json__() if present to get additional fields
229 _json_attr = getattr(self, '__json__', None)
229 _json_attr = getattr(self, '__json__', None)
230 if _json_attr:
230 if _json_attr:
231 # update with attributes from __json__
231 # update with attributes from __json__
232 if callable(_json_attr):
232 if callable(_json_attr):
233 _json_attr = _json_attr()
233 _json_attr = _json_attr()
234 for k, val in _json_attr.iteritems():
234 for k, val in _json_attr.iteritems():
235 d[k] = val
235 d[k] = val
236 return d
236 return d
237
237
238 def get_appstruct(self):
238 def get_appstruct(self):
239 """return list with keys and values tuples corresponding
239 """return list with keys and values tuples corresponding
240 to this model data """
240 to this model data """
241
241
242 lst = []
242 lst = []
243 for k in self._get_keys():
243 for k in self._get_keys():
244 lst.append((k, getattr(self, k),))
244 lst.append((k, getattr(self, k),))
245 return lst
245 return lst
246
246
247 def populate_obj(self, populate_dict):
247 def populate_obj(self, populate_dict):
248 """populate model with data from given populate_dict"""
248 """populate model with data from given populate_dict"""
249
249
250 for k in self._get_keys():
250 for k in self._get_keys():
251 if k in populate_dict:
251 if k in populate_dict:
252 setattr(self, k, populate_dict[k])
252 setattr(self, k, populate_dict[k])
253
253
254 @classmethod
254 @classmethod
255 def query(cls):
255 def query(cls):
256 return Session().query(cls)
256 return Session().query(cls)
257
257
258 @classmethod
258 @classmethod
259 def get(cls, id_):
259 def get(cls, id_):
260 if id_:
260 if id_:
261 return cls.query().get(id_)
261 return cls.query().get(id_)
262
262
263 @classmethod
263 @classmethod
264 def get_or_404(cls, id_):
264 def get_or_404(cls, id_):
265 from pyramid.httpexceptions import HTTPNotFound
265 from pyramid.httpexceptions import HTTPNotFound
266
266
267 try:
267 try:
268 id_ = int(id_)
268 id_ = int(id_)
269 except (TypeError, ValueError):
269 except (TypeError, ValueError):
270 raise HTTPNotFound()
270 raise HTTPNotFound()
271
271
272 res = cls.query().get(id_)
272 res = cls.query().get(id_)
273 if not res:
273 if not res:
274 raise HTTPNotFound()
274 raise HTTPNotFound()
275 return res
275 return res
276
276
277 @classmethod
277 @classmethod
278 def getAll(cls):
278 def getAll(cls):
279 # deprecated and left for backward compatibility
279 # deprecated and left for backward compatibility
280 return cls.get_all()
280 return cls.get_all()
281
281
282 @classmethod
282 @classmethod
283 def get_all(cls):
283 def get_all(cls):
284 return cls.query().all()
284 return cls.query().all()
285
285
286 @classmethod
286 @classmethod
287 def delete(cls, id_):
287 def delete(cls, id_):
288 obj = cls.query().get(id_)
288 obj = cls.query().get(id_)
289 Session().delete(obj)
289 Session().delete(obj)
290
290
291 @classmethod
291 @classmethod
292 def identity_cache(cls, session, attr_name, value):
292 def identity_cache(cls, session, attr_name, value):
293 exist_in_session = []
293 exist_in_session = []
294 for (item_cls, pkey), instance in session.identity_map.items():
294 for (item_cls, pkey), instance in session.identity_map.items():
295 if cls == item_cls and getattr(instance, attr_name) == value:
295 if cls == item_cls and getattr(instance, attr_name) == value:
296 exist_in_session.append(instance)
296 exist_in_session.append(instance)
297 if exist_in_session:
297 if exist_in_session:
298 if len(exist_in_session) == 1:
298 if len(exist_in_session) == 1:
299 return exist_in_session[0]
299 return exist_in_session[0]
300 log.exception(
300 log.exception(
301 'multiple objects with attr %s and '
301 'multiple objects with attr %s and '
302 'value %s found with same name: %r',
302 'value %s found with same name: %r',
303 attr_name, value, exist_in_session)
303 attr_name, value, exist_in_session)
304
304
305 def __repr__(self):
305 def __repr__(self):
306 if hasattr(self, '__unicode__'):
306 if hasattr(self, '__unicode__'):
307 # python repr needs to return str
307 # python repr needs to return str
308 try:
308 try:
309 return safe_str(self.__unicode__())
309 return safe_str(self.__unicode__())
310 except UnicodeDecodeError:
310 except UnicodeDecodeError:
311 pass
311 pass
312 return '<DB:%s>' % (self.__class__.__name__)
312 return '<DB:%s>' % (self.__class__.__name__)
313
313
314
314
315 class RhodeCodeSetting(Base, BaseModel):
315 class RhodeCodeSetting(Base, BaseModel):
316 __tablename__ = 'rhodecode_settings'
316 __tablename__ = 'rhodecode_settings'
317 __table_args__ = (
317 __table_args__ = (
318 UniqueConstraint('app_settings_name'),
318 UniqueConstraint('app_settings_name'),
319 base_table_args
319 base_table_args
320 )
320 )
321
321
322 SETTINGS_TYPES = {
322 SETTINGS_TYPES = {
323 'str': safe_str,
323 'str': safe_str,
324 'int': safe_int,
324 'int': safe_int,
325 'unicode': safe_unicode,
325 'unicode': safe_unicode,
326 'bool': str2bool,
326 'bool': str2bool,
327 'list': functools.partial(aslist, sep=',')
327 'list': functools.partial(aslist, sep=',')
328 }
328 }
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
330 GLOBAL_CONF_KEY = 'app_settings'
330 GLOBAL_CONF_KEY = 'app_settings'
331
331
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
336
336
337 def __init__(self, key='', val='', type='unicode'):
337 def __init__(self, key='', val='', type='unicode'):
338 self.app_settings_name = key
338 self.app_settings_name = key
339 self.app_settings_type = type
339 self.app_settings_type = type
340 self.app_settings_value = val
340 self.app_settings_value = val
341
341
342 @validates('_app_settings_value')
342 @validates('_app_settings_value')
343 def validate_settings_value(self, key, val):
343 def validate_settings_value(self, key, val):
344 assert type(val) == unicode
344 assert type(val) == unicode
345 return val
345 return val
346
346
347 @hybrid_property
347 @hybrid_property
348 def app_settings_value(self):
348 def app_settings_value(self):
349 v = self._app_settings_value
349 v = self._app_settings_value
350 _type = self.app_settings_type
350 _type = self.app_settings_type
351 if _type:
351 if _type:
352 _type = self.app_settings_type.split('.')[0]
352 _type = self.app_settings_type.split('.')[0]
353 # decode the encrypted value
353 # decode the encrypted value
354 if 'encrypted' in self.app_settings_type:
354 if 'encrypted' in self.app_settings_type:
355 cipher = EncryptedTextValue()
355 cipher = EncryptedTextValue()
356 v = safe_unicode(cipher.process_result_value(v, None))
356 v = safe_unicode(cipher.process_result_value(v, None))
357
357
358 converter = self.SETTINGS_TYPES.get(_type) or \
358 converter = self.SETTINGS_TYPES.get(_type) or \
359 self.SETTINGS_TYPES['unicode']
359 self.SETTINGS_TYPES['unicode']
360 return converter(v)
360 return converter(v)
361
361
362 @app_settings_value.setter
362 @app_settings_value.setter
363 def app_settings_value(self, val):
363 def app_settings_value(self, val):
364 """
364 """
365 Setter that will always make sure we use unicode in app_settings_value
365 Setter that will always make sure we use unicode in app_settings_value
366
366
367 :param val:
367 :param val:
368 """
368 """
369 val = safe_unicode(val)
369 val = safe_unicode(val)
370 # encode the encrypted value
370 # encode the encrypted value
371 if 'encrypted' in self.app_settings_type:
371 if 'encrypted' in self.app_settings_type:
372 cipher = EncryptedTextValue()
372 cipher = EncryptedTextValue()
373 val = safe_unicode(cipher.process_bind_param(val, None))
373 val = safe_unicode(cipher.process_bind_param(val, None))
374 self._app_settings_value = val
374 self._app_settings_value = val
375
375
376 @hybrid_property
376 @hybrid_property
377 def app_settings_type(self):
377 def app_settings_type(self):
378 return self._app_settings_type
378 return self._app_settings_type
379
379
380 @app_settings_type.setter
380 @app_settings_type.setter
381 def app_settings_type(self, val):
381 def app_settings_type(self, val):
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
383 raise Exception('type must be one of %s got %s'
383 raise Exception('type must be one of %s got %s'
384 % (self.SETTINGS_TYPES.keys(), val))
384 % (self.SETTINGS_TYPES.keys(), val))
385 self._app_settings_type = val
385 self._app_settings_type = val
386
386
387 @classmethod
387 @classmethod
388 def get_by_prefix(cls, prefix):
388 def get_by_prefix(cls, prefix):
389 return RhodeCodeSetting.query()\
389 return RhodeCodeSetting.query()\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
391 .all()
391 .all()
392
392
393 def __unicode__(self):
393 def __unicode__(self):
394 return u"<%s('%s:%s[%s]')>" % (
394 return u"<%s('%s:%s[%s]')>" % (
395 self.__class__.__name__,
395 self.__class__.__name__,
396 self.app_settings_name, self.app_settings_value,
396 self.app_settings_name, self.app_settings_value,
397 self.app_settings_type
397 self.app_settings_type
398 )
398 )
399
399
400
400
401 class RhodeCodeUi(Base, BaseModel):
401 class RhodeCodeUi(Base, BaseModel):
402 __tablename__ = 'rhodecode_ui'
402 __tablename__ = 'rhodecode_ui'
403 __table_args__ = (
403 __table_args__ = (
404 UniqueConstraint('ui_key'),
404 UniqueConstraint('ui_key'),
405 base_table_args
405 base_table_args
406 )
406 )
407
407
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
409 # HG
409 # HG
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
411 HOOK_PULL = 'outgoing.pull_logger'
411 HOOK_PULL = 'outgoing.pull_logger'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
414 HOOK_PUSH = 'changegroup.push_logger'
414 HOOK_PUSH = 'changegroup.push_logger'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
416
416
417 HOOKS_BUILTIN = [
417 HOOKS_BUILTIN = [
418 HOOK_PRE_PULL,
418 HOOK_PRE_PULL,
419 HOOK_PULL,
419 HOOK_PULL,
420 HOOK_PRE_PUSH,
420 HOOK_PRE_PUSH,
421 HOOK_PRETX_PUSH,
421 HOOK_PRETX_PUSH,
422 HOOK_PUSH,
422 HOOK_PUSH,
423 HOOK_PUSH_KEY,
423 HOOK_PUSH_KEY,
424 ]
424 ]
425
425
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
427 # git part is currently hardcoded.
427 # git part is currently hardcoded.
428
428
429 # SVN PATTERNS
429 # SVN PATTERNS
430 SVN_BRANCH_ID = 'vcs_svn_branch'
430 SVN_BRANCH_ID = 'vcs_svn_branch'
431 SVN_TAG_ID = 'vcs_svn_tag'
431 SVN_TAG_ID = 'vcs_svn_tag'
432
432
433 ui_id = Column(
433 ui_id = Column(
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
435 primary_key=True)
435 primary_key=True)
436 ui_section = Column(
436 ui_section = Column(
437 "ui_section", String(255), nullable=True, unique=None, default=None)
437 "ui_section", String(255), nullable=True, unique=None, default=None)
438 ui_key = Column(
438 ui_key = Column(
439 "ui_key", String(255), nullable=True, unique=None, default=None)
439 "ui_key", String(255), nullable=True, unique=None, default=None)
440 ui_value = Column(
440 ui_value = Column(
441 "ui_value", String(255), nullable=True, unique=None, default=None)
441 "ui_value", String(255), nullable=True, unique=None, default=None)
442 ui_active = Column(
442 ui_active = Column(
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
444
444
445 def __repr__(self):
445 def __repr__(self):
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
447 self.ui_key, self.ui_value)
447 self.ui_key, self.ui_value)
448
448
449
449
450 class RepoRhodeCodeSetting(Base, BaseModel):
450 class RepoRhodeCodeSetting(Base, BaseModel):
451 __tablename__ = 'repo_rhodecode_settings'
451 __tablename__ = 'repo_rhodecode_settings'
452 __table_args__ = (
452 __table_args__ = (
453 UniqueConstraint(
453 UniqueConstraint(
454 'app_settings_name', 'repository_id',
454 'app_settings_name', 'repository_id',
455 name='uq_repo_rhodecode_setting_name_repo_id'),
455 name='uq_repo_rhodecode_setting_name_repo_id'),
456 base_table_args
456 base_table_args
457 )
457 )
458
458
459 repository_id = Column(
459 repository_id = Column(
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
461 nullable=False)
461 nullable=False)
462 app_settings_id = Column(
462 app_settings_id = Column(
463 "app_settings_id", Integer(), nullable=False, unique=True,
463 "app_settings_id", Integer(), nullable=False, unique=True,
464 default=None, primary_key=True)
464 default=None, primary_key=True)
465 app_settings_name = Column(
465 app_settings_name = Column(
466 "app_settings_name", String(255), nullable=True, unique=None,
466 "app_settings_name", String(255), nullable=True, unique=None,
467 default=None)
467 default=None)
468 _app_settings_value = Column(
468 _app_settings_value = Column(
469 "app_settings_value", String(4096), nullable=True, unique=None,
469 "app_settings_value", String(4096), nullable=True, unique=None,
470 default=None)
470 default=None)
471 _app_settings_type = Column(
471 _app_settings_type = Column(
472 "app_settings_type", String(255), nullable=True, unique=None,
472 "app_settings_type", String(255), nullable=True, unique=None,
473 default=None)
473 default=None)
474
474
475 repository = relationship('Repository')
475 repository = relationship('Repository')
476
476
477 def __init__(self, repository_id, key='', val='', type='unicode'):
477 def __init__(self, repository_id, key='', val='', type='unicode'):
478 self.repository_id = repository_id
478 self.repository_id = repository_id
479 self.app_settings_name = key
479 self.app_settings_name = key
480 self.app_settings_type = type
480 self.app_settings_type = type
481 self.app_settings_value = val
481 self.app_settings_value = val
482
482
483 @validates('_app_settings_value')
483 @validates('_app_settings_value')
484 def validate_settings_value(self, key, val):
484 def validate_settings_value(self, key, val):
485 assert type(val) == unicode
485 assert type(val) == unicode
486 return val
486 return val
487
487
488 @hybrid_property
488 @hybrid_property
489 def app_settings_value(self):
489 def app_settings_value(self):
490 v = self._app_settings_value
490 v = self._app_settings_value
491 type_ = self.app_settings_type
491 type_ = self.app_settings_type
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
494 return converter(v)
494 return converter(v)
495
495
496 @app_settings_value.setter
496 @app_settings_value.setter
497 def app_settings_value(self, val):
497 def app_settings_value(self, val):
498 """
498 """
499 Setter that will always make sure we use unicode in app_settings_value
499 Setter that will always make sure we use unicode in app_settings_value
500
500
501 :param val:
501 :param val:
502 """
502 """
503 self._app_settings_value = safe_unicode(val)
503 self._app_settings_value = safe_unicode(val)
504
504
505 @hybrid_property
505 @hybrid_property
506 def app_settings_type(self):
506 def app_settings_type(self):
507 return self._app_settings_type
507 return self._app_settings_type
508
508
509 @app_settings_type.setter
509 @app_settings_type.setter
510 def app_settings_type(self, val):
510 def app_settings_type(self, val):
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 if val not in SETTINGS_TYPES:
512 if val not in SETTINGS_TYPES:
513 raise Exception('type must be one of %s got %s'
513 raise Exception('type must be one of %s got %s'
514 % (SETTINGS_TYPES.keys(), val))
514 % (SETTINGS_TYPES.keys(), val))
515 self._app_settings_type = val
515 self._app_settings_type = val
516
516
517 def __unicode__(self):
517 def __unicode__(self):
518 return u"<%s('%s:%s:%s[%s]')>" % (
518 return u"<%s('%s:%s:%s[%s]')>" % (
519 self.__class__.__name__, self.repository.repo_name,
519 self.__class__.__name__, self.repository.repo_name,
520 self.app_settings_name, self.app_settings_value,
520 self.app_settings_name, self.app_settings_value,
521 self.app_settings_type
521 self.app_settings_type
522 )
522 )
523
523
524
524
525 class RepoRhodeCodeUi(Base, BaseModel):
525 class RepoRhodeCodeUi(Base, BaseModel):
526 __tablename__ = 'repo_rhodecode_ui'
526 __tablename__ = 'repo_rhodecode_ui'
527 __table_args__ = (
527 __table_args__ = (
528 UniqueConstraint(
528 UniqueConstraint(
529 'repository_id', 'ui_section', 'ui_key',
529 'repository_id', 'ui_section', 'ui_key',
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
531 base_table_args
531 base_table_args
532 )
532 )
533
533
534 repository_id = Column(
534 repository_id = Column(
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
536 nullable=False)
536 nullable=False)
537 ui_id = Column(
537 ui_id = Column(
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
539 primary_key=True)
539 primary_key=True)
540 ui_section = Column(
540 ui_section = Column(
541 "ui_section", String(255), nullable=True, unique=None, default=None)
541 "ui_section", String(255), nullable=True, unique=None, default=None)
542 ui_key = Column(
542 ui_key = Column(
543 "ui_key", String(255), nullable=True, unique=None, default=None)
543 "ui_key", String(255), nullable=True, unique=None, default=None)
544 ui_value = Column(
544 ui_value = Column(
545 "ui_value", String(255), nullable=True, unique=None, default=None)
545 "ui_value", String(255), nullable=True, unique=None, default=None)
546 ui_active = Column(
546 ui_active = Column(
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
548
548
549 repository = relationship('Repository')
549 repository = relationship('Repository')
550
550
551 def __repr__(self):
551 def __repr__(self):
552 return '<%s[%s:%s]%s=>%s]>' % (
552 return '<%s[%s:%s]%s=>%s]>' % (
553 self.__class__.__name__, self.repository.repo_name,
553 self.__class__.__name__, self.repository.repo_name,
554 self.ui_section, self.ui_key, self.ui_value)
554 self.ui_section, self.ui_key, self.ui_value)
555
555
556
556
557 class User(Base, BaseModel):
557 class User(Base, BaseModel):
558 __tablename__ = 'users'
558 __tablename__ = 'users'
559 __table_args__ = (
559 __table_args__ = (
560 UniqueConstraint('username'), UniqueConstraint('email'),
560 UniqueConstraint('username'), UniqueConstraint('email'),
561 Index('u_username_idx', 'username'),
561 Index('u_username_idx', 'username'),
562 Index('u_email_idx', 'email'),
562 Index('u_email_idx', 'email'),
563 base_table_args
563 base_table_args
564 )
564 )
565
565
566 DEFAULT_USER = 'default'
566 DEFAULT_USER = 'default'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
569
569
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
581
581
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
588
588
589 user_log = relationship('UserLog')
589 user_log = relationship('UserLog')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
591
591
592 repositories = relationship('Repository')
592 repositories = relationship('Repository')
593 repository_groups = relationship('RepoGroup')
593 repository_groups = relationship('RepoGroup')
594 user_groups = relationship('UserGroup')
594 user_groups = relationship('UserGroup')
595
595
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
598
598
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
602
602
603 group_member = relationship('UserGroupMember', cascade='all')
603 group_member = relationship('UserGroupMember', cascade='all')
604
604
605 notifications = relationship('UserNotification', cascade='all')
605 notifications = relationship('UserNotification', cascade='all')
606 # notifications assigned to this user
606 # notifications assigned to this user
607 user_created_notifications = relationship('Notification', cascade='all')
607 user_created_notifications = relationship('Notification', cascade='all')
608 # comments created by this user
608 # comments created by this user
609 user_comments = relationship('ChangesetComment', cascade='all')
609 user_comments = relationship('ChangesetComment', cascade='all')
610 # user profile extra info
610 # user profile extra info
611 user_emails = relationship('UserEmailMap', cascade='all')
611 user_emails = relationship('UserEmailMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
615
615
616 # gists
616 # gists
617 user_gists = relationship('Gist', cascade='all')
617 user_gists = relationship('Gist', cascade='all')
618 # user pull requests
618 # user pull requests
619 user_pull_requests = relationship('PullRequest', cascade='all')
619 user_pull_requests = relationship('PullRequest', cascade='all')
620
620
621 # external identities
621 # external identities
622 external_identities = relationship(
622 external_identities = relationship(
623 'ExternalIdentity',
623 'ExternalIdentity',
624 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
624 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
625 cascade='all')
625 cascade='all')
626 # review rules
626 # review rules
627 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
627 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
628
628
629 # artifacts owned
629 # artifacts owned
630 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
630 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
631
631
632 # no cascade, set NULL
632 # no cascade, set NULL
633 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
633 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
634
634
635 def __unicode__(self):
635 def __unicode__(self):
636 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
636 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
637 self.user_id, self.username)
637 self.user_id, self.username)
638
638
639 @hybrid_property
639 @hybrid_property
640 def email(self):
640 def email(self):
641 return self._email
641 return self._email
642
642
643 @email.setter
643 @email.setter
644 def email(self, val):
644 def email(self, val):
645 self._email = val.lower() if val else None
645 self._email = val.lower() if val else None
646
646
647 @hybrid_property
647 @hybrid_property
648 def first_name(self):
648 def first_name(self):
649 from rhodecode.lib import helpers as h
649 from rhodecode.lib import helpers as h
650 if self.name:
650 if self.name:
651 return h.escape(self.name)
651 return h.escape(self.name)
652 return self.name
652 return self.name
653
653
654 @hybrid_property
654 @hybrid_property
655 def last_name(self):
655 def last_name(self):
656 from rhodecode.lib import helpers as h
656 from rhodecode.lib import helpers as h
657 if self.lastname:
657 if self.lastname:
658 return h.escape(self.lastname)
658 return h.escape(self.lastname)
659 return self.lastname
659 return self.lastname
660
660
661 @hybrid_property
661 @hybrid_property
662 def api_key(self):
662 def api_key(self):
663 """
663 """
664 Fetch if exist an auth-token with role ALL connected to this user
664 Fetch if exist an auth-token with role ALL connected to this user
665 """
665 """
666 user_auth_token = UserApiKeys.query()\
666 user_auth_token = UserApiKeys.query()\
667 .filter(UserApiKeys.user_id == self.user_id)\
667 .filter(UserApiKeys.user_id == self.user_id)\
668 .filter(or_(UserApiKeys.expires == -1,
668 .filter(or_(UserApiKeys.expires == -1,
669 UserApiKeys.expires >= time.time()))\
669 UserApiKeys.expires >= time.time()))\
670 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
670 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
671 if user_auth_token:
671 if user_auth_token:
672 user_auth_token = user_auth_token.api_key
672 user_auth_token = user_auth_token.api_key
673
673
674 return user_auth_token
674 return user_auth_token
675
675
676 @api_key.setter
676 @api_key.setter
677 def api_key(self, val):
677 def api_key(self, val):
678 # don't allow to set API key this is deprecated for now
678 # don't allow to set API key this is deprecated for now
679 self._api_key = None
679 self._api_key = None
680
680
681 @property
681 @property
682 def reviewer_pull_requests(self):
682 def reviewer_pull_requests(self):
683 return PullRequestReviewers.query() \
683 return PullRequestReviewers.query() \
684 .options(joinedload(PullRequestReviewers.pull_request)) \
684 .options(joinedload(PullRequestReviewers.pull_request)) \
685 .filter(PullRequestReviewers.user_id == self.user_id) \
685 .filter(PullRequestReviewers.user_id == self.user_id) \
686 .all()
686 .all()
687
687
688 @property
688 @property
689 def firstname(self):
689 def firstname(self):
690 # alias for future
690 # alias for future
691 return self.name
691 return self.name
692
692
693 @property
693 @property
694 def emails(self):
694 def emails(self):
695 other = UserEmailMap.query()\
695 other = UserEmailMap.query()\
696 .filter(UserEmailMap.user == self) \
696 .filter(UserEmailMap.user == self) \
697 .order_by(UserEmailMap.email_id.asc()) \
697 .order_by(UserEmailMap.email_id.asc()) \
698 .all()
698 .all()
699 return [self.email] + [x.email for x in other]
699 return [self.email] + [x.email for x in other]
700
700
701 def emails_cached(self):
701 def emails_cached(self):
702 emails = UserEmailMap.query()\
702 emails = UserEmailMap.query()\
703 .filter(UserEmailMap.user == self) \
703 .filter(UserEmailMap.user == self) \
704 .order_by(UserEmailMap.email_id.asc())
704 .order_by(UserEmailMap.email_id.asc())
705
705
706 emails = emails.options(
706 emails = emails.options(
707 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
707 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
708 )
708 )
709
709
710 return [self.email] + [x.email for x in emails]
710 return [self.email] + [x.email for x in emails]
711
711
712 @property
712 @property
713 def auth_tokens(self):
713 def auth_tokens(self):
714 auth_tokens = self.get_auth_tokens()
714 auth_tokens = self.get_auth_tokens()
715 return [x.api_key for x in auth_tokens]
715 return [x.api_key for x in auth_tokens]
716
716
717 def get_auth_tokens(self):
717 def get_auth_tokens(self):
718 return UserApiKeys.query()\
718 return UserApiKeys.query()\
719 .filter(UserApiKeys.user == self)\
719 .filter(UserApiKeys.user == self)\
720 .order_by(UserApiKeys.user_api_key_id.asc())\
720 .order_by(UserApiKeys.user_api_key_id.asc())\
721 .all()
721 .all()
722
722
723 @LazyProperty
723 @LazyProperty
724 def feed_token(self):
724 def feed_token(self):
725 return self.get_feed_token()
725 return self.get_feed_token()
726
726
727 def get_feed_token(self, cache=True):
727 def get_feed_token(self, cache=True):
728 feed_tokens = UserApiKeys.query()\
728 feed_tokens = UserApiKeys.query()\
729 .filter(UserApiKeys.user == self)\
729 .filter(UserApiKeys.user == self)\
730 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
730 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
731 if cache:
731 if cache:
732 feed_tokens = feed_tokens.options(
732 feed_tokens = feed_tokens.options(
733 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
733 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
734
734
735 feed_tokens = feed_tokens.all()
735 feed_tokens = feed_tokens.all()
736 if feed_tokens:
736 if feed_tokens:
737 return feed_tokens[0].api_key
737 return feed_tokens[0].api_key
738 return 'NO_FEED_TOKEN_AVAILABLE'
738 return 'NO_FEED_TOKEN_AVAILABLE'
739
739
740 @LazyProperty
740 @LazyProperty
741 def artifact_token(self):
741 def artifact_token(self):
742 return self.get_artifact_token()
742 return self.get_artifact_token()
743
743
744 def get_artifact_token(self, cache=True):
744 def get_artifact_token(self, cache=True):
745 artifacts_tokens = UserApiKeys.query()\
745 artifacts_tokens = UserApiKeys.query()\
746 .filter(UserApiKeys.user == self)\
746 .filter(UserApiKeys.user == self)\
747 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
747 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
748 if cache:
748 if cache:
749 artifacts_tokens = artifacts_tokens.options(
749 artifacts_tokens = artifacts_tokens.options(
750 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
750 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
751
751
752 artifacts_tokens = artifacts_tokens.all()
752 artifacts_tokens = artifacts_tokens.all()
753 if artifacts_tokens:
753 if artifacts_tokens:
754 return artifacts_tokens[0].api_key
754 return artifacts_tokens[0].api_key
755 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
755 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
756
756
757 @classmethod
757 @classmethod
758 def get(cls, user_id, cache=False):
758 def get(cls, user_id, cache=False):
759 if not user_id:
759 if not user_id:
760 return
760 return
761
761
762 user = cls.query()
762 user = cls.query()
763 if cache:
763 if cache:
764 user = user.options(
764 user = user.options(
765 FromCache("sql_cache_short", "get_users_%s" % user_id))
765 FromCache("sql_cache_short", "get_users_%s" % user_id))
766 return user.get(user_id)
766 return user.get(user_id)
767
767
768 @classmethod
768 @classmethod
769 def extra_valid_auth_tokens(cls, user, role=None):
769 def extra_valid_auth_tokens(cls, user, role=None):
770 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
770 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
771 .filter(or_(UserApiKeys.expires == -1,
771 .filter(or_(UserApiKeys.expires == -1,
772 UserApiKeys.expires >= time.time()))
772 UserApiKeys.expires >= time.time()))
773 if role:
773 if role:
774 tokens = tokens.filter(or_(UserApiKeys.role == role,
774 tokens = tokens.filter(or_(UserApiKeys.role == role,
775 UserApiKeys.role == UserApiKeys.ROLE_ALL))
775 UserApiKeys.role == UserApiKeys.ROLE_ALL))
776 return tokens.all()
776 return tokens.all()
777
777
778 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
778 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
779 from rhodecode.lib import auth
779 from rhodecode.lib import auth
780
780
781 log.debug('Trying to authenticate user: %s via auth-token, '
781 log.debug('Trying to authenticate user: %s via auth-token, '
782 'and roles: %s', self, roles)
782 'and roles: %s', self, roles)
783
783
784 if not auth_token:
784 if not auth_token:
785 return False
785 return False
786
786
787 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
787 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
788 tokens_q = UserApiKeys.query()\
788 tokens_q = UserApiKeys.query()\
789 .filter(UserApiKeys.user_id == self.user_id)\
789 .filter(UserApiKeys.user_id == self.user_id)\
790 .filter(or_(UserApiKeys.expires == -1,
790 .filter(or_(UserApiKeys.expires == -1,
791 UserApiKeys.expires >= time.time()))
791 UserApiKeys.expires >= time.time()))
792
792
793 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
793 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
794
794
795 crypto_backend = auth.crypto_backend()
795 crypto_backend = auth.crypto_backend()
796 enc_token_map = {}
796 enc_token_map = {}
797 plain_token_map = {}
797 plain_token_map = {}
798 for token in tokens_q:
798 for token in tokens_q:
799 if token.api_key.startswith(crypto_backend.ENC_PREF):
799 if token.api_key.startswith(crypto_backend.ENC_PREF):
800 enc_token_map[token.api_key] = token
800 enc_token_map[token.api_key] = token
801 else:
801 else:
802 plain_token_map[token.api_key] = token
802 plain_token_map[token.api_key] = token
803 log.debug(
803 log.debug(
804 'Found %s plain and %s encrypted tokens to check for authentication for this user',
804 'Found %s plain and %s encrypted tokens to check for authentication for this user',
805 len(plain_token_map), len(enc_token_map))
805 len(plain_token_map), len(enc_token_map))
806
806
807 # plain token match comes first
807 # plain token match comes first
808 match = plain_token_map.get(auth_token)
808 match = plain_token_map.get(auth_token)
809
809
810 # check encrypted tokens now
810 # check encrypted tokens now
811 if not match:
811 if not match:
812 for token_hash, token in enc_token_map.items():
812 for token_hash, token in enc_token_map.items():
813 # NOTE(marcink): this is expensive to calculate, but most secure
813 # NOTE(marcink): this is expensive to calculate, but most secure
814 if crypto_backend.hash_check(auth_token, token_hash):
814 if crypto_backend.hash_check(auth_token, token_hash):
815 match = token
815 match = token
816 break
816 break
817
817
818 if match:
818 if match:
819 log.debug('Found matching token %s', match)
819 log.debug('Found matching token %s', match)
820 if match.repo_id:
820 if match.repo_id:
821 log.debug('Found scope, checking for scope match of token %s', match)
821 log.debug('Found scope, checking for scope match of token %s', match)
822 if match.repo_id == scope_repo_id:
822 if match.repo_id == scope_repo_id:
823 return True
823 return True
824 else:
824 else:
825 log.debug(
825 log.debug(
826 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
826 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
827 'and calling scope is:%s, skipping further checks',
827 'and calling scope is:%s, skipping further checks',
828 match.repo, scope_repo_id)
828 match.repo, scope_repo_id)
829 return False
829 return False
830 else:
830 else:
831 return True
831 return True
832
832
833 return False
833 return False
834
834
835 @property
835 @property
836 def ip_addresses(self):
836 def ip_addresses(self):
837 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
837 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
838 return [x.ip_addr for x in ret]
838 return [x.ip_addr for x in ret]
839
839
840 @property
840 @property
841 def username_and_name(self):
841 def username_and_name(self):
842 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
842 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
843
843
844 @property
844 @property
845 def username_or_name_or_email(self):
845 def username_or_name_or_email(self):
846 full_name = self.full_name if self.full_name is not ' ' else None
846 full_name = self.full_name if self.full_name is not ' ' else None
847 return self.username or full_name or self.email
847 return self.username or full_name or self.email
848
848
849 @property
849 @property
850 def full_name(self):
850 def full_name(self):
851 return '%s %s' % (self.first_name, self.last_name)
851 return '%s %s' % (self.first_name, self.last_name)
852
852
853 @property
853 @property
854 def full_name_or_username(self):
854 def full_name_or_username(self):
855 return ('%s %s' % (self.first_name, self.last_name)
855 return ('%s %s' % (self.first_name, self.last_name)
856 if (self.first_name and self.last_name) else self.username)
856 if (self.first_name and self.last_name) else self.username)
857
857
858 @property
858 @property
859 def full_contact(self):
859 def full_contact(self):
860 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
860 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
861
861
862 @property
862 @property
863 def short_contact(self):
863 def short_contact(self):
864 return '%s %s' % (self.first_name, self.last_name)
864 return '%s %s' % (self.first_name, self.last_name)
865
865
866 @property
866 @property
867 def is_admin(self):
867 def is_admin(self):
868 return self.admin
868 return self.admin
869
869
870 @property
870 @property
871 def language(self):
871 def language(self):
872 return self.user_data.get('language')
872 return self.user_data.get('language')
873
873
874 def AuthUser(self, **kwargs):
874 def AuthUser(self, **kwargs):
875 """
875 """
876 Returns instance of AuthUser for this user
876 Returns instance of AuthUser for this user
877 """
877 """
878 from rhodecode.lib.auth import AuthUser
878 from rhodecode.lib.auth import AuthUser
879 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
879 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
880
880
881 @hybrid_property
881 @hybrid_property
882 def user_data(self):
882 def user_data(self):
883 if not self._user_data:
883 if not self._user_data:
884 return {}
884 return {}
885
885
886 try:
886 try:
887 return json.loads(self._user_data)
887 return json.loads(self._user_data)
888 except TypeError:
888 except TypeError:
889 return {}
889 return {}
890
890
891 @user_data.setter
891 @user_data.setter
892 def user_data(self, val):
892 def user_data(self, val):
893 if not isinstance(val, dict):
893 if not isinstance(val, dict):
894 raise Exception('user_data must be dict, got %s' % type(val))
894 raise Exception('user_data must be dict, got %s' % type(val))
895 try:
895 try:
896 self._user_data = json.dumps(val)
896 self._user_data = json.dumps(val)
897 except Exception:
897 except Exception:
898 log.error(traceback.format_exc())
898 log.error(traceback.format_exc())
899
899
900 @classmethod
900 @classmethod
901 def get_by_username(cls, username, case_insensitive=False,
901 def get_by_username(cls, username, case_insensitive=False,
902 cache=False, identity_cache=False):
902 cache=False, identity_cache=False):
903 session = Session()
903 session = Session()
904
904
905 if case_insensitive:
905 if case_insensitive:
906 q = cls.query().filter(
906 q = cls.query().filter(
907 func.lower(cls.username) == func.lower(username))
907 func.lower(cls.username) == func.lower(username))
908 else:
908 else:
909 q = cls.query().filter(cls.username == username)
909 q = cls.query().filter(cls.username == username)
910
910
911 if cache:
911 if cache:
912 if identity_cache:
912 if identity_cache:
913 val = cls.identity_cache(session, 'username', username)
913 val = cls.identity_cache(session, 'username', username)
914 if val:
914 if val:
915 return val
915 return val
916 else:
916 else:
917 cache_key = "get_user_by_name_%s" % _hash_key(username)
917 cache_key = "get_user_by_name_%s" % _hash_key(username)
918 q = q.options(
918 q = q.options(
919 FromCache("sql_cache_short", cache_key))
919 FromCache("sql_cache_short", cache_key))
920
920
921 return q.scalar()
921 return q.scalar()
922
922
923 @classmethod
923 @classmethod
924 def get_by_auth_token(cls, auth_token, cache=False):
924 def get_by_auth_token(cls, auth_token, cache=False):
925 q = UserApiKeys.query()\
925 q = UserApiKeys.query()\
926 .filter(UserApiKeys.api_key == auth_token)\
926 .filter(UserApiKeys.api_key == auth_token)\
927 .filter(or_(UserApiKeys.expires == -1,
927 .filter(or_(UserApiKeys.expires == -1,
928 UserApiKeys.expires >= time.time()))
928 UserApiKeys.expires >= time.time()))
929 if cache:
929 if cache:
930 q = q.options(
930 q = q.options(
931 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
931 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
932
932
933 match = q.first()
933 match = q.first()
934 if match:
934 if match:
935 return match.user
935 return match.user
936
936
937 @classmethod
937 @classmethod
938 def get_by_email(cls, email, case_insensitive=False, cache=False):
938 def get_by_email(cls, email, case_insensitive=False, cache=False):
939
939
940 if case_insensitive:
940 if case_insensitive:
941 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
941 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
942
942
943 else:
943 else:
944 q = cls.query().filter(cls.email == email)
944 q = cls.query().filter(cls.email == email)
945
945
946 email_key = _hash_key(email)
946 email_key = _hash_key(email)
947 if cache:
947 if cache:
948 q = q.options(
948 q = q.options(
949 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
949 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
950
950
951 ret = q.scalar()
951 ret = q.scalar()
952 if ret is None:
952 if ret is None:
953 q = UserEmailMap.query()
953 q = UserEmailMap.query()
954 # try fetching in alternate email map
954 # try fetching in alternate email map
955 if case_insensitive:
955 if case_insensitive:
956 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
956 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
957 else:
957 else:
958 q = q.filter(UserEmailMap.email == email)
958 q = q.filter(UserEmailMap.email == email)
959 q = q.options(joinedload(UserEmailMap.user))
959 q = q.options(joinedload(UserEmailMap.user))
960 if cache:
960 if cache:
961 q = q.options(
961 q = q.options(
962 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
962 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
963 ret = getattr(q.scalar(), 'user', None)
963 ret = getattr(q.scalar(), 'user', None)
964
964
965 return ret
965 return ret
966
966
967 @classmethod
967 @classmethod
968 def get_from_cs_author(cls, author):
968 def get_from_cs_author(cls, author):
969 """
969 """
970 Tries to get User objects out of commit author string
970 Tries to get User objects out of commit author string
971
971
972 :param author:
972 :param author:
973 """
973 """
974 from rhodecode.lib.helpers import email, author_name
974 from rhodecode.lib.helpers import email, author_name
975 # Valid email in the attribute passed, see if they're in the system
975 # Valid email in the attribute passed, see if they're in the system
976 _email = email(author)
976 _email = email(author)
977 if _email:
977 if _email:
978 user = cls.get_by_email(_email, case_insensitive=True)
978 user = cls.get_by_email(_email, case_insensitive=True)
979 if user:
979 if user:
980 return user
980 return user
981 # Maybe we can match by username?
981 # Maybe we can match by username?
982 _author = author_name(author)
982 _author = author_name(author)
983 user = cls.get_by_username(_author, case_insensitive=True)
983 user = cls.get_by_username(_author, case_insensitive=True)
984 if user:
984 if user:
985 return user
985 return user
986
986
987 def update_userdata(self, **kwargs):
987 def update_userdata(self, **kwargs):
988 usr = self
988 usr = self
989 old = usr.user_data
989 old = usr.user_data
990 old.update(**kwargs)
990 old.update(**kwargs)
991 usr.user_data = old
991 usr.user_data = old
992 Session().add(usr)
992 Session().add(usr)
993 log.debug('updated userdata with %s', kwargs)
993 log.debug('updated userdata with %s', kwargs)
994
994
995 def update_lastlogin(self):
995 def update_lastlogin(self):
996 """Update user lastlogin"""
996 """Update user lastlogin"""
997 self.last_login = datetime.datetime.now()
997 self.last_login = datetime.datetime.now()
998 Session().add(self)
998 Session().add(self)
999 log.debug('updated user %s lastlogin', self.username)
999 log.debug('updated user %s lastlogin', self.username)
1000
1000
1001 def update_password(self, new_password):
1001 def update_password(self, new_password):
1002 from rhodecode.lib.auth import get_crypt_password
1002 from rhodecode.lib.auth import get_crypt_password
1003
1003
1004 self.password = get_crypt_password(new_password)
1004 self.password = get_crypt_password(new_password)
1005 Session().add(self)
1005 Session().add(self)
1006
1006
1007 @classmethod
1007 @classmethod
1008 def get_first_super_admin(cls):
1008 def get_first_super_admin(cls):
1009 user = User.query()\
1009 user = User.query()\
1010 .filter(User.admin == true()) \
1010 .filter(User.admin == true()) \
1011 .order_by(User.user_id.asc()) \
1011 .order_by(User.user_id.asc()) \
1012 .first()
1012 .first()
1013
1013
1014 if user is None:
1014 if user is None:
1015 raise Exception('FATAL: Missing administrative account!')
1015 raise Exception('FATAL: Missing administrative account!')
1016 return user
1016 return user
1017
1017
1018 @classmethod
1018 @classmethod
1019 def get_all_super_admins(cls, only_active=False):
1019 def get_all_super_admins(cls, only_active=False):
1020 """
1020 """
1021 Returns all admin accounts sorted by username
1021 Returns all admin accounts sorted by username
1022 """
1022 """
1023 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1023 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1024 if only_active:
1024 if only_active:
1025 qry = qry.filter(User.active == true())
1025 qry = qry.filter(User.active == true())
1026 return qry.all()
1026 return qry.all()
1027
1027
1028 @classmethod
1028 @classmethod
1029 def get_all_user_ids(cls, only_active=True):
1029 def get_all_user_ids(cls, only_active=True):
1030 """
1030 """
1031 Returns all users IDs
1031 Returns all users IDs
1032 """
1032 """
1033 qry = Session().query(User.user_id)
1033 qry = Session().query(User.user_id)
1034
1034
1035 if only_active:
1035 if only_active:
1036 qry = qry.filter(User.active == true())
1036 qry = qry.filter(User.active == true())
1037 return [x.user_id for x in qry]
1037 return [x.user_id for x in qry]
1038
1038
1039 @classmethod
1039 @classmethod
1040 def get_default_user(cls, cache=False, refresh=False):
1040 def get_default_user(cls, cache=False, refresh=False):
1041 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1041 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1042 if user is None:
1042 if user is None:
1043 raise Exception('FATAL: Missing default account!')
1043 raise Exception('FATAL: Missing default account!')
1044 if refresh:
1044 if refresh:
1045 # The default user might be based on outdated state which
1045 # The default user might be based on outdated state which
1046 # has been loaded from the cache.
1046 # has been loaded from the cache.
1047 # A call to refresh() ensures that the
1047 # A call to refresh() ensures that the
1048 # latest state from the database is used.
1048 # latest state from the database is used.
1049 Session().refresh(user)
1049 Session().refresh(user)
1050 return user
1050 return user
1051
1051
1052 @classmethod
1052 @classmethod
1053 def get_default_user_id(cls):
1053 def get_default_user_id(cls):
1054 import rhodecode
1054 import rhodecode
1055 return rhodecode.CONFIG['default_user_id']
1055 return rhodecode.CONFIG['default_user_id']
1056
1056
1057 def _get_default_perms(self, user, suffix=''):
1057 def _get_default_perms(self, user, suffix=''):
1058 from rhodecode.model.permission import PermissionModel
1058 from rhodecode.model.permission import PermissionModel
1059 return PermissionModel().get_default_perms(user.user_perms, suffix)
1059 return PermissionModel().get_default_perms(user.user_perms, suffix)
1060
1060
1061 def get_default_perms(self, suffix=''):
1061 def get_default_perms(self, suffix=''):
1062 return self._get_default_perms(self, suffix)
1062 return self._get_default_perms(self, suffix)
1063
1063
1064 def get_api_data(self, include_secrets=False, details='full'):
1064 def get_api_data(self, include_secrets=False, details='full'):
1065 """
1065 """
1066 Common function for generating user related data for API
1066 Common function for generating user related data for API
1067
1067
1068 :param include_secrets: By default secrets in the API data will be replaced
1068 :param include_secrets: By default secrets in the API data will be replaced
1069 by a placeholder value to prevent exposing this data by accident. In case
1069 by a placeholder value to prevent exposing this data by accident. In case
1070 this data shall be exposed, set this flag to ``True``.
1070 this data shall be exposed, set this flag to ``True``.
1071
1071
1072 :param details: details can be 'basic|full' basic gives only a subset of
1072 :param details: details can be 'basic|full' basic gives only a subset of
1073 the available user information that includes user_id, name and emails.
1073 the available user information that includes user_id, name and emails.
1074 """
1074 """
1075 user = self
1075 user = self
1076 user_data = self.user_data
1076 user_data = self.user_data
1077 data = {
1077 data = {
1078 'user_id': user.user_id,
1078 'user_id': user.user_id,
1079 'username': user.username,
1079 'username': user.username,
1080 'firstname': user.name,
1080 'firstname': user.name,
1081 'lastname': user.lastname,
1081 'lastname': user.lastname,
1082 'description': user.description,
1082 'description': user.description,
1083 'email': user.email,
1083 'email': user.email,
1084 'emails': user.emails,
1084 'emails': user.emails,
1085 }
1085 }
1086 if details == 'basic':
1086 if details == 'basic':
1087 return data
1087 return data
1088
1088
1089 auth_token_length = 40
1089 auth_token_length = 40
1090 auth_token_replacement = '*' * auth_token_length
1090 auth_token_replacement = '*' * auth_token_length
1091
1091
1092 extras = {
1092 extras = {
1093 'auth_tokens': [auth_token_replacement],
1093 'auth_tokens': [auth_token_replacement],
1094 'active': user.active,
1094 'active': user.active,
1095 'admin': user.admin,
1095 'admin': user.admin,
1096 'extern_type': user.extern_type,
1096 'extern_type': user.extern_type,
1097 'extern_name': user.extern_name,
1097 'extern_name': user.extern_name,
1098 'last_login': user.last_login,
1098 'last_login': user.last_login,
1099 'last_activity': user.last_activity,
1099 'last_activity': user.last_activity,
1100 'ip_addresses': user.ip_addresses,
1100 'ip_addresses': user.ip_addresses,
1101 'language': user_data.get('language')
1101 'language': user_data.get('language')
1102 }
1102 }
1103 data.update(extras)
1103 data.update(extras)
1104
1104
1105 if include_secrets:
1105 if include_secrets:
1106 data['auth_tokens'] = user.auth_tokens
1106 data['auth_tokens'] = user.auth_tokens
1107 return data
1107 return data
1108
1108
1109 def __json__(self):
1109 def __json__(self):
1110 data = {
1110 data = {
1111 'full_name': self.full_name,
1111 'full_name': self.full_name,
1112 'full_name_or_username': self.full_name_or_username,
1112 'full_name_or_username': self.full_name_or_username,
1113 'short_contact': self.short_contact,
1113 'short_contact': self.short_contact,
1114 'full_contact': self.full_contact,
1114 'full_contact': self.full_contact,
1115 }
1115 }
1116 data.update(self.get_api_data())
1116 data.update(self.get_api_data())
1117 return data
1117 return data
1118
1118
1119
1119
1120 class UserApiKeys(Base, BaseModel):
1120 class UserApiKeys(Base, BaseModel):
1121 __tablename__ = 'user_api_keys'
1121 __tablename__ = 'user_api_keys'
1122 __table_args__ = (
1122 __table_args__ = (
1123 Index('uak_api_key_idx', 'api_key'),
1123 Index('uak_api_key_idx', 'api_key'),
1124 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1124 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1125 base_table_args
1125 base_table_args
1126 )
1126 )
1127 __mapper_args__ = {}
1127 __mapper_args__ = {}
1128
1128
1129 # ApiKey role
1129 # ApiKey role
1130 ROLE_ALL = 'token_role_all'
1130 ROLE_ALL = 'token_role_all'
1131 ROLE_HTTP = 'token_role_http'
1131 ROLE_HTTP = 'token_role_http'
1132 ROLE_VCS = 'token_role_vcs'
1132 ROLE_VCS = 'token_role_vcs'
1133 ROLE_API = 'token_role_api'
1133 ROLE_API = 'token_role_api'
1134 ROLE_FEED = 'token_role_feed'
1134 ROLE_FEED = 'token_role_feed'
1135 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1135 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1136 ROLE_PASSWORD_RESET = 'token_password_reset'
1136 ROLE_PASSWORD_RESET = 'token_password_reset'
1137
1137
1138 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1138 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1139
1139
1140 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1140 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1141 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1141 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1142 api_key = Column("api_key", String(255), nullable=False, unique=True)
1142 api_key = Column("api_key", String(255), nullable=False, unique=True)
1143 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1143 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1144 expires = Column('expires', Float(53), nullable=False)
1144 expires = Column('expires', Float(53), nullable=False)
1145 role = Column('role', String(255), nullable=True)
1145 role = Column('role', String(255), nullable=True)
1146 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1146 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1147
1147
1148 # scope columns
1148 # scope columns
1149 repo_id = Column(
1149 repo_id = Column(
1150 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1150 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1151 nullable=True, unique=None, default=None)
1151 nullable=True, unique=None, default=None)
1152 repo = relationship('Repository', lazy='joined')
1152 repo = relationship('Repository', lazy='joined')
1153
1153
1154 repo_group_id = Column(
1154 repo_group_id = Column(
1155 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1155 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1156 nullable=True, unique=None, default=None)
1156 nullable=True, unique=None, default=None)
1157 repo_group = relationship('RepoGroup', lazy='joined')
1157 repo_group = relationship('RepoGroup', lazy='joined')
1158
1158
1159 user = relationship('User', lazy='joined')
1159 user = relationship('User', lazy='joined')
1160
1160
1161 def __unicode__(self):
1161 def __unicode__(self):
1162 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1162 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1163
1163
1164 def __json__(self):
1164 def __json__(self):
1165 data = {
1165 data = {
1166 'auth_token': self.api_key,
1166 'auth_token': self.api_key,
1167 'role': self.role,
1167 'role': self.role,
1168 'scope': self.scope_humanized,
1168 'scope': self.scope_humanized,
1169 'expired': self.expired
1169 'expired': self.expired
1170 }
1170 }
1171 return data
1171 return data
1172
1172
1173 def get_api_data(self, include_secrets=False):
1173 def get_api_data(self, include_secrets=False):
1174 data = self.__json__()
1174 data = self.__json__()
1175 if include_secrets:
1175 if include_secrets:
1176 return data
1176 return data
1177 else:
1177 else:
1178 data['auth_token'] = self.token_obfuscated
1178 data['auth_token'] = self.token_obfuscated
1179 return data
1179 return data
1180
1180
1181 @hybrid_property
1181 @hybrid_property
1182 def description_safe(self):
1182 def description_safe(self):
1183 from rhodecode.lib import helpers as h
1183 from rhodecode.lib import helpers as h
1184 return h.escape(self.description)
1184 return h.escape(self.description)
1185
1185
1186 @property
1186 @property
1187 def expired(self):
1187 def expired(self):
1188 if self.expires == -1:
1188 if self.expires == -1:
1189 return False
1189 return False
1190 return time.time() > self.expires
1190 return time.time() > self.expires
1191
1191
1192 @classmethod
1192 @classmethod
1193 def _get_role_name(cls, role):
1193 def _get_role_name(cls, role):
1194 return {
1194 return {
1195 cls.ROLE_ALL: _('all'),
1195 cls.ROLE_ALL: _('all'),
1196 cls.ROLE_HTTP: _('http/web interface'),
1196 cls.ROLE_HTTP: _('http/web interface'),
1197 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1197 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1198 cls.ROLE_API: _('api calls'),
1198 cls.ROLE_API: _('api calls'),
1199 cls.ROLE_FEED: _('feed access'),
1199 cls.ROLE_FEED: _('feed access'),
1200 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1200 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1201 }.get(role, role)
1201 }.get(role, role)
1202
1202
1203 @property
1203 @property
1204 def role_humanized(self):
1204 def role_humanized(self):
1205 return self._get_role_name(self.role)
1205 return self._get_role_name(self.role)
1206
1206
1207 def _get_scope(self):
1207 def _get_scope(self):
1208 if self.repo:
1208 if self.repo:
1209 return 'Repository: {}'.format(self.repo.repo_name)
1209 return 'Repository: {}'.format(self.repo.repo_name)
1210 if self.repo_group:
1210 if self.repo_group:
1211 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1211 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1212 return 'Global'
1212 return 'Global'
1213
1213
1214 @property
1214 @property
1215 def scope_humanized(self):
1215 def scope_humanized(self):
1216 return self._get_scope()
1216 return self._get_scope()
1217
1217
1218 @property
1218 @property
1219 def token_obfuscated(self):
1219 def token_obfuscated(self):
1220 if self.api_key:
1220 if self.api_key:
1221 return self.api_key[:4] + "****"
1221 return self.api_key[:4] + "****"
1222
1222
1223
1223
1224 class UserEmailMap(Base, BaseModel):
1224 class UserEmailMap(Base, BaseModel):
1225 __tablename__ = 'user_email_map'
1225 __tablename__ = 'user_email_map'
1226 __table_args__ = (
1226 __table_args__ = (
1227 Index('uem_email_idx', 'email'),
1227 Index('uem_email_idx', 'email'),
1228 UniqueConstraint('email'),
1228 UniqueConstraint('email'),
1229 base_table_args
1229 base_table_args
1230 )
1230 )
1231 __mapper_args__ = {}
1231 __mapper_args__ = {}
1232
1232
1233 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1233 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1234 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1234 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1235 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1235 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1236 user = relationship('User', lazy='joined')
1236 user = relationship('User', lazy='joined')
1237
1237
1238 @validates('_email')
1238 @validates('_email')
1239 def validate_email(self, key, email):
1239 def validate_email(self, key, email):
1240 # check if this email is not main one
1240 # check if this email is not main one
1241 main_email = Session().query(User).filter(User.email == email).scalar()
1241 main_email = Session().query(User).filter(User.email == email).scalar()
1242 if main_email is not None:
1242 if main_email is not None:
1243 raise AttributeError('email %s is present is user table' % email)
1243 raise AttributeError('email %s is present is user table' % email)
1244 return email
1244 return email
1245
1245
1246 @hybrid_property
1246 @hybrid_property
1247 def email(self):
1247 def email(self):
1248 return self._email
1248 return self._email
1249
1249
1250 @email.setter
1250 @email.setter
1251 def email(self, val):
1251 def email(self, val):
1252 self._email = val.lower() if val else None
1252 self._email = val.lower() if val else None
1253
1253
1254
1254
1255 class UserIpMap(Base, BaseModel):
1255 class UserIpMap(Base, BaseModel):
1256 __tablename__ = 'user_ip_map'
1256 __tablename__ = 'user_ip_map'
1257 __table_args__ = (
1257 __table_args__ = (
1258 UniqueConstraint('user_id', 'ip_addr'),
1258 UniqueConstraint('user_id', 'ip_addr'),
1259 base_table_args
1259 base_table_args
1260 )
1260 )
1261 __mapper_args__ = {}
1261 __mapper_args__ = {}
1262
1262
1263 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1263 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1264 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1264 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1265 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1265 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1266 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1266 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1267 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1267 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1268 user = relationship('User', lazy='joined')
1268 user = relationship('User', lazy='joined')
1269
1269
1270 @hybrid_property
1270 @hybrid_property
1271 def description_safe(self):
1271 def description_safe(self):
1272 from rhodecode.lib import helpers as h
1272 from rhodecode.lib import helpers as h
1273 return h.escape(self.description)
1273 return h.escape(self.description)
1274
1274
1275 @classmethod
1275 @classmethod
1276 def _get_ip_range(cls, ip_addr):
1276 def _get_ip_range(cls, ip_addr):
1277 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1277 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1278 return [str(net.network_address), str(net.broadcast_address)]
1278 return [str(net.network_address), str(net.broadcast_address)]
1279
1279
1280 def __json__(self):
1280 def __json__(self):
1281 return {
1281 return {
1282 'ip_addr': self.ip_addr,
1282 'ip_addr': self.ip_addr,
1283 'ip_range': self._get_ip_range(self.ip_addr),
1283 'ip_range': self._get_ip_range(self.ip_addr),
1284 }
1284 }
1285
1285
1286 def __unicode__(self):
1286 def __unicode__(self):
1287 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1287 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1288 self.user_id, self.ip_addr)
1288 self.user_id, self.ip_addr)
1289
1289
1290
1290
1291 class UserSshKeys(Base, BaseModel):
1291 class UserSshKeys(Base, BaseModel):
1292 __tablename__ = 'user_ssh_keys'
1292 __tablename__ = 'user_ssh_keys'
1293 __table_args__ = (
1293 __table_args__ = (
1294 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1294 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1295
1295
1296 UniqueConstraint('ssh_key_fingerprint'),
1296 UniqueConstraint('ssh_key_fingerprint'),
1297
1297
1298 base_table_args
1298 base_table_args
1299 )
1299 )
1300 __mapper_args__ = {}
1300 __mapper_args__ = {}
1301
1301
1302 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1302 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1303 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1303 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1304 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1304 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1305
1305
1306 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1306 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1307
1307
1308 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1308 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1309 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1309 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1310 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1310 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1311
1311
1312 user = relationship('User', lazy='joined')
1312 user = relationship('User', lazy='joined')
1313
1313
1314 def __json__(self):
1314 def __json__(self):
1315 data = {
1315 data = {
1316 'ssh_fingerprint': self.ssh_key_fingerprint,
1316 'ssh_fingerprint': self.ssh_key_fingerprint,
1317 'description': self.description,
1317 'description': self.description,
1318 'created_on': self.created_on
1318 'created_on': self.created_on
1319 }
1319 }
1320 return data
1320 return data
1321
1321
1322 def get_api_data(self):
1322 def get_api_data(self):
1323 data = self.__json__()
1323 data = self.__json__()
1324 return data
1324 return data
1325
1325
1326
1326
1327 class UserLog(Base, BaseModel):
1327 class UserLog(Base, BaseModel):
1328 __tablename__ = 'user_logs'
1328 __tablename__ = 'user_logs'
1329 __table_args__ = (
1329 __table_args__ = (
1330 base_table_args,
1330 base_table_args,
1331 )
1331 )
1332
1332
1333 VERSION_1 = 'v1'
1333 VERSION_1 = 'v1'
1334 VERSION_2 = 'v2'
1334 VERSION_2 = 'v2'
1335 VERSIONS = [VERSION_1, VERSION_2]
1335 VERSIONS = [VERSION_1, VERSION_2]
1336
1336
1337 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1337 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1338 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1338 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1339 username = Column("username", String(255), nullable=True, unique=None, default=None)
1339 username = Column("username", String(255), nullable=True, unique=None, default=None)
1340 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1340 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1341 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1341 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1342 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1342 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1343 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1343 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1344 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1344 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1345
1345
1346 version = Column("version", String(255), nullable=True, default=VERSION_1)
1346 version = Column("version", String(255), nullable=True, default=VERSION_1)
1347 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1347 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1348 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1348 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1349
1349
1350 def __unicode__(self):
1350 def __unicode__(self):
1351 return u"<%s('id:%s:%s')>" % (
1351 return u"<%s('id:%s:%s')>" % (
1352 self.__class__.__name__, self.repository_name, self.action)
1352 self.__class__.__name__, self.repository_name, self.action)
1353
1353
1354 def __json__(self):
1354 def __json__(self):
1355 return {
1355 return {
1356 'user_id': self.user_id,
1356 'user_id': self.user_id,
1357 'username': self.username,
1357 'username': self.username,
1358 'repository_id': self.repository_id,
1358 'repository_id': self.repository_id,
1359 'repository_name': self.repository_name,
1359 'repository_name': self.repository_name,
1360 'user_ip': self.user_ip,
1360 'user_ip': self.user_ip,
1361 'action_date': self.action_date,
1361 'action_date': self.action_date,
1362 'action': self.action,
1362 'action': self.action,
1363 }
1363 }
1364
1364
1365 @hybrid_property
1365 @hybrid_property
1366 def entry_id(self):
1366 def entry_id(self):
1367 return self.user_log_id
1367 return self.user_log_id
1368
1368
1369 @property
1369 @property
1370 def action_as_day(self):
1370 def action_as_day(self):
1371 return datetime.date(*self.action_date.timetuple()[:3])
1371 return datetime.date(*self.action_date.timetuple()[:3])
1372
1372
1373 user = relationship('User')
1373 user = relationship('User')
1374 repository = relationship('Repository', cascade='')
1374 repository = relationship('Repository', cascade='')
1375
1375
1376
1376
1377 class UserGroup(Base, BaseModel):
1377 class UserGroup(Base, BaseModel):
1378 __tablename__ = 'users_groups'
1378 __tablename__ = 'users_groups'
1379 __table_args__ = (
1379 __table_args__ = (
1380 base_table_args,
1380 base_table_args,
1381 )
1381 )
1382
1382
1383 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1383 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1384 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1384 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1385 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1385 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1386 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1386 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1387 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1387 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1388 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1388 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1389 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1389 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1390 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1390 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1391
1391
1392 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1392 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1393 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1393 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1394 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1394 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1395 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1395 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1396 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1396 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1397 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1397 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1398
1398
1399 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1399 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1400 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1400 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1401
1401
1402 @classmethod
1402 @classmethod
1403 def _load_group_data(cls, column):
1403 def _load_group_data(cls, column):
1404 if not column:
1404 if not column:
1405 return {}
1405 return {}
1406
1406
1407 try:
1407 try:
1408 return json.loads(column) or {}
1408 return json.loads(column) or {}
1409 except TypeError:
1409 except TypeError:
1410 return {}
1410 return {}
1411
1411
1412 @hybrid_property
1412 @hybrid_property
1413 def description_safe(self):
1413 def description_safe(self):
1414 from rhodecode.lib import helpers as h
1414 from rhodecode.lib import helpers as h
1415 return h.escape(self.user_group_description)
1415 return h.escape(self.user_group_description)
1416
1416
1417 @hybrid_property
1417 @hybrid_property
1418 def group_data(self):
1418 def group_data(self):
1419 return self._load_group_data(self._group_data)
1419 return self._load_group_data(self._group_data)
1420
1420
1421 @group_data.expression
1421 @group_data.expression
1422 def group_data(self, **kwargs):
1422 def group_data(self, **kwargs):
1423 return self._group_data
1423 return self._group_data
1424
1424
1425 @group_data.setter
1425 @group_data.setter
1426 def group_data(self, val):
1426 def group_data(self, val):
1427 try:
1427 try:
1428 self._group_data = json.dumps(val)
1428 self._group_data = json.dumps(val)
1429 except Exception:
1429 except Exception:
1430 log.error(traceback.format_exc())
1430 log.error(traceback.format_exc())
1431
1431
1432 @classmethod
1432 @classmethod
1433 def _load_sync(cls, group_data):
1433 def _load_sync(cls, group_data):
1434 if group_data:
1434 if group_data:
1435 return group_data.get('extern_type')
1435 return group_data.get('extern_type')
1436
1436
1437 @property
1437 @property
1438 def sync(self):
1438 def sync(self):
1439 return self._load_sync(self.group_data)
1439 return self._load_sync(self.group_data)
1440
1440
1441 def __unicode__(self):
1441 def __unicode__(self):
1442 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1442 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1443 self.users_group_id,
1443 self.users_group_id,
1444 self.users_group_name)
1444 self.users_group_name)
1445
1445
1446 @classmethod
1446 @classmethod
1447 def get_by_group_name(cls, group_name, cache=False,
1447 def get_by_group_name(cls, group_name, cache=False,
1448 case_insensitive=False):
1448 case_insensitive=False):
1449 if case_insensitive:
1449 if case_insensitive:
1450 q = cls.query().filter(func.lower(cls.users_group_name) ==
1450 q = cls.query().filter(func.lower(cls.users_group_name) ==
1451 func.lower(group_name))
1451 func.lower(group_name))
1452
1452
1453 else:
1453 else:
1454 q = cls.query().filter(cls.users_group_name == group_name)
1454 q = cls.query().filter(cls.users_group_name == group_name)
1455 if cache:
1455 if cache:
1456 q = q.options(
1456 q = q.options(
1457 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1457 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1458 return q.scalar()
1458 return q.scalar()
1459
1459
1460 @classmethod
1460 @classmethod
1461 def get(cls, user_group_id, cache=False):
1461 def get(cls, user_group_id, cache=False):
1462 if not user_group_id:
1462 if not user_group_id:
1463 return
1463 return
1464
1464
1465 user_group = cls.query()
1465 user_group = cls.query()
1466 if cache:
1466 if cache:
1467 user_group = user_group.options(
1467 user_group = user_group.options(
1468 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1468 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1469 return user_group.get(user_group_id)
1469 return user_group.get(user_group_id)
1470
1470
1471 def permissions(self, with_admins=True, with_owner=True,
1471 def permissions(self, with_admins=True, with_owner=True,
1472 expand_from_user_groups=False):
1472 expand_from_user_groups=False):
1473 """
1473 """
1474 Permissions for user groups
1474 Permissions for user groups
1475 """
1475 """
1476 _admin_perm = 'usergroup.admin'
1476 _admin_perm = 'usergroup.admin'
1477
1477
1478 owner_row = []
1478 owner_row = []
1479 if with_owner:
1479 if with_owner:
1480 usr = AttributeDict(self.user.get_dict())
1480 usr = AttributeDict(self.user.get_dict())
1481 usr.owner_row = True
1481 usr.owner_row = True
1482 usr.permission = _admin_perm
1482 usr.permission = _admin_perm
1483 owner_row.append(usr)
1483 owner_row.append(usr)
1484
1484
1485 super_admin_ids = []
1485 super_admin_ids = []
1486 super_admin_rows = []
1486 super_admin_rows = []
1487 if with_admins:
1487 if with_admins:
1488 for usr in User.get_all_super_admins():
1488 for usr in User.get_all_super_admins():
1489 super_admin_ids.append(usr.user_id)
1489 super_admin_ids.append(usr.user_id)
1490 # if this admin is also owner, don't double the record
1490 # if this admin is also owner, don't double the record
1491 if usr.user_id == owner_row[0].user_id:
1491 if usr.user_id == owner_row[0].user_id:
1492 owner_row[0].admin_row = True
1492 owner_row[0].admin_row = True
1493 else:
1493 else:
1494 usr = AttributeDict(usr.get_dict())
1494 usr = AttributeDict(usr.get_dict())
1495 usr.admin_row = True
1495 usr.admin_row = True
1496 usr.permission = _admin_perm
1496 usr.permission = _admin_perm
1497 super_admin_rows.append(usr)
1497 super_admin_rows.append(usr)
1498
1498
1499 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1499 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1500 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1500 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1501 joinedload(UserUserGroupToPerm.user),
1501 joinedload(UserUserGroupToPerm.user),
1502 joinedload(UserUserGroupToPerm.permission),)
1502 joinedload(UserUserGroupToPerm.permission),)
1503
1503
1504 # get owners and admins and permissions. We do a trick of re-writing
1504 # get owners and admins and permissions. We do a trick of re-writing
1505 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1505 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1506 # has a global reference and changing one object propagates to all
1506 # has a global reference and changing one object propagates to all
1507 # others. This means if admin is also an owner admin_row that change
1507 # others. This means if admin is also an owner admin_row that change
1508 # would propagate to both objects
1508 # would propagate to both objects
1509 perm_rows = []
1509 perm_rows = []
1510 for _usr in q.all():
1510 for _usr in q.all():
1511 usr = AttributeDict(_usr.user.get_dict())
1511 usr = AttributeDict(_usr.user.get_dict())
1512 # if this user is also owner/admin, mark as duplicate record
1512 # if this user is also owner/admin, mark as duplicate record
1513 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1513 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1514 usr.duplicate_perm = True
1514 usr.duplicate_perm = True
1515 usr.permission = _usr.permission.permission_name
1515 usr.permission = _usr.permission.permission_name
1516 perm_rows.append(usr)
1516 perm_rows.append(usr)
1517
1517
1518 # filter the perm rows by 'default' first and then sort them by
1518 # filter the perm rows by 'default' first and then sort them by
1519 # admin,write,read,none permissions sorted again alphabetically in
1519 # admin,write,read,none permissions sorted again alphabetically in
1520 # each group
1520 # each group
1521 perm_rows = sorted(perm_rows, key=display_user_sort)
1521 perm_rows = sorted(perm_rows, key=display_user_sort)
1522
1522
1523 user_groups_rows = []
1523 user_groups_rows = []
1524 if expand_from_user_groups:
1524 if expand_from_user_groups:
1525 for ug in self.permission_user_groups(with_members=True):
1525 for ug in self.permission_user_groups(with_members=True):
1526 for user_data in ug.members:
1526 for user_data in ug.members:
1527 user_groups_rows.append(user_data)
1527 user_groups_rows.append(user_data)
1528
1528
1529 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1529 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1530
1530
1531 def permission_user_groups(self, with_members=False):
1531 def permission_user_groups(self, with_members=False):
1532 q = UserGroupUserGroupToPerm.query()\
1532 q = UserGroupUserGroupToPerm.query()\
1533 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1533 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1534 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1534 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1535 joinedload(UserGroupUserGroupToPerm.target_user_group),
1535 joinedload(UserGroupUserGroupToPerm.target_user_group),
1536 joinedload(UserGroupUserGroupToPerm.permission),)
1536 joinedload(UserGroupUserGroupToPerm.permission),)
1537
1537
1538 perm_rows = []
1538 perm_rows = []
1539 for _user_group in q.all():
1539 for _user_group in q.all():
1540 entry = AttributeDict(_user_group.user_group.get_dict())
1540 entry = AttributeDict(_user_group.user_group.get_dict())
1541 entry.permission = _user_group.permission.permission_name
1541 entry.permission = _user_group.permission.permission_name
1542 if with_members:
1542 if with_members:
1543 entry.members = [x.user.get_dict()
1543 entry.members = [x.user.get_dict()
1544 for x in _user_group.user_group.members]
1544 for x in _user_group.user_group.members]
1545 perm_rows.append(entry)
1545 perm_rows.append(entry)
1546
1546
1547 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1547 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1548 return perm_rows
1548 return perm_rows
1549
1549
1550 def _get_default_perms(self, user_group, suffix=''):
1550 def _get_default_perms(self, user_group, suffix=''):
1551 from rhodecode.model.permission import PermissionModel
1551 from rhodecode.model.permission import PermissionModel
1552 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1552 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1553
1553
1554 def get_default_perms(self, suffix=''):
1554 def get_default_perms(self, suffix=''):
1555 return self._get_default_perms(self, suffix)
1555 return self._get_default_perms(self, suffix)
1556
1556
1557 def get_api_data(self, with_group_members=True, include_secrets=False):
1557 def get_api_data(self, with_group_members=True, include_secrets=False):
1558 """
1558 """
1559 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1559 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1560 basically forwarded.
1560 basically forwarded.
1561
1561
1562 """
1562 """
1563 user_group = self
1563 user_group = self
1564 data = {
1564 data = {
1565 'users_group_id': user_group.users_group_id,
1565 'users_group_id': user_group.users_group_id,
1566 'group_name': user_group.users_group_name,
1566 'group_name': user_group.users_group_name,
1567 'group_description': user_group.user_group_description,
1567 'group_description': user_group.user_group_description,
1568 'active': user_group.users_group_active,
1568 'active': user_group.users_group_active,
1569 'owner': user_group.user.username,
1569 'owner': user_group.user.username,
1570 'sync': user_group.sync,
1570 'sync': user_group.sync,
1571 'owner_email': user_group.user.email,
1571 'owner_email': user_group.user.email,
1572 }
1572 }
1573
1573
1574 if with_group_members:
1574 if with_group_members:
1575 users = []
1575 users = []
1576 for user in user_group.members:
1576 for user in user_group.members:
1577 user = user.user
1577 user = user.user
1578 users.append(user.get_api_data(include_secrets=include_secrets))
1578 users.append(user.get_api_data(include_secrets=include_secrets))
1579 data['users'] = users
1579 data['users'] = users
1580
1580
1581 return data
1581 return data
1582
1582
1583
1583
1584 class UserGroupMember(Base, BaseModel):
1584 class UserGroupMember(Base, BaseModel):
1585 __tablename__ = 'users_groups_members'
1585 __tablename__ = 'users_groups_members'
1586 __table_args__ = (
1586 __table_args__ = (
1587 base_table_args,
1587 base_table_args,
1588 )
1588 )
1589
1589
1590 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1590 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1591 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1593
1593
1594 user = relationship('User', lazy='joined')
1594 user = relationship('User', lazy='joined')
1595 users_group = relationship('UserGroup')
1595 users_group = relationship('UserGroup')
1596
1596
1597 def __init__(self, gr_id='', u_id=''):
1597 def __init__(self, gr_id='', u_id=''):
1598 self.users_group_id = gr_id
1598 self.users_group_id = gr_id
1599 self.user_id = u_id
1599 self.user_id = u_id
1600
1600
1601
1601
1602 class RepositoryField(Base, BaseModel):
1602 class RepositoryField(Base, BaseModel):
1603 __tablename__ = 'repositories_fields'
1603 __tablename__ = 'repositories_fields'
1604 __table_args__ = (
1604 __table_args__ = (
1605 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1605 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1606 base_table_args,
1606 base_table_args,
1607 )
1607 )
1608
1608
1609 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1609 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1610
1610
1611 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1611 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1612 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1612 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1613 field_key = Column("field_key", String(250))
1613 field_key = Column("field_key", String(250))
1614 field_label = Column("field_label", String(1024), nullable=False)
1614 field_label = Column("field_label", String(1024), nullable=False)
1615 field_value = Column("field_value", String(10000), nullable=False)
1615 field_value = Column("field_value", String(10000), nullable=False)
1616 field_desc = Column("field_desc", String(1024), nullable=False)
1616 field_desc = Column("field_desc", String(1024), nullable=False)
1617 field_type = Column("field_type", String(255), nullable=False, unique=None)
1617 field_type = Column("field_type", String(255), nullable=False, unique=None)
1618 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1618 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1619
1619
1620 repository = relationship('Repository')
1620 repository = relationship('Repository')
1621
1621
1622 @property
1622 @property
1623 def field_key_prefixed(self):
1623 def field_key_prefixed(self):
1624 return 'ex_%s' % self.field_key
1624 return 'ex_%s' % self.field_key
1625
1625
1626 @classmethod
1626 @classmethod
1627 def un_prefix_key(cls, key):
1627 def un_prefix_key(cls, key):
1628 if key.startswith(cls.PREFIX):
1628 if key.startswith(cls.PREFIX):
1629 return key[len(cls.PREFIX):]
1629 return key[len(cls.PREFIX):]
1630 return key
1630 return key
1631
1631
1632 @classmethod
1632 @classmethod
1633 def get_by_key_name(cls, key, repo):
1633 def get_by_key_name(cls, key, repo):
1634 row = cls.query()\
1634 row = cls.query()\
1635 .filter(cls.repository == repo)\
1635 .filter(cls.repository == repo)\
1636 .filter(cls.field_key == key).scalar()
1636 .filter(cls.field_key == key).scalar()
1637 return row
1637 return row
1638
1638
1639
1639
1640 class Repository(Base, BaseModel):
1640 class Repository(Base, BaseModel):
1641 __tablename__ = 'repositories'
1641 __tablename__ = 'repositories'
1642 __table_args__ = (
1642 __table_args__ = (
1643 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1643 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1644 base_table_args,
1644 base_table_args,
1645 )
1645 )
1646 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1646 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1647 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1647 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1648 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1648 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1649
1649
1650 STATE_CREATED = 'repo_state_created'
1650 STATE_CREATED = 'repo_state_created'
1651 STATE_PENDING = 'repo_state_pending'
1651 STATE_PENDING = 'repo_state_pending'
1652 STATE_ERROR = 'repo_state_error'
1652 STATE_ERROR = 'repo_state_error'
1653
1653
1654 LOCK_AUTOMATIC = 'lock_auto'
1654 LOCK_AUTOMATIC = 'lock_auto'
1655 LOCK_API = 'lock_api'
1655 LOCK_API = 'lock_api'
1656 LOCK_WEB = 'lock_web'
1656 LOCK_WEB = 'lock_web'
1657 LOCK_PULL = 'lock_pull'
1657 LOCK_PULL = 'lock_pull'
1658
1658
1659 NAME_SEP = URL_SEP
1659 NAME_SEP = URL_SEP
1660
1660
1661 repo_id = Column(
1661 repo_id = Column(
1662 "repo_id", Integer(), nullable=False, unique=True, default=None,
1662 "repo_id", Integer(), nullable=False, unique=True, default=None,
1663 primary_key=True)
1663 primary_key=True)
1664 _repo_name = Column(
1664 _repo_name = Column(
1665 "repo_name", Text(), nullable=False, default=None)
1665 "repo_name", Text(), nullable=False, default=None)
1666 repo_name_hash = Column(
1666 repo_name_hash = Column(
1667 "repo_name_hash", String(255), nullable=False, unique=True)
1667 "repo_name_hash", String(255), nullable=False, unique=True)
1668 repo_state = Column("repo_state", String(255), nullable=True)
1668 repo_state = Column("repo_state", String(255), nullable=True)
1669
1669
1670 clone_uri = Column(
1670 clone_uri = Column(
1671 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1671 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1672 default=None)
1672 default=None)
1673 push_uri = Column(
1673 push_uri = Column(
1674 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1674 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1675 default=None)
1675 default=None)
1676 repo_type = Column(
1676 repo_type = Column(
1677 "repo_type", String(255), nullable=False, unique=False, default=None)
1677 "repo_type", String(255), nullable=False, unique=False, default=None)
1678 user_id = Column(
1678 user_id = Column(
1679 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1679 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1680 unique=False, default=None)
1680 unique=False, default=None)
1681 private = Column(
1681 private = Column(
1682 "private", Boolean(), nullable=True, unique=None, default=None)
1682 "private", Boolean(), nullable=True, unique=None, default=None)
1683 archived = Column(
1683 archived = Column(
1684 "archived", Boolean(), nullable=True, unique=None, default=None)
1684 "archived", Boolean(), nullable=True, unique=None, default=None)
1685 enable_statistics = Column(
1685 enable_statistics = Column(
1686 "statistics", Boolean(), nullable=True, unique=None, default=True)
1686 "statistics", Boolean(), nullable=True, unique=None, default=True)
1687 enable_downloads = Column(
1687 enable_downloads = Column(
1688 "downloads", Boolean(), nullable=True, unique=None, default=True)
1688 "downloads", Boolean(), nullable=True, unique=None, default=True)
1689 description = Column(
1689 description = Column(
1690 "description", String(10000), nullable=True, unique=None, default=None)
1690 "description", String(10000), nullable=True, unique=None, default=None)
1691 created_on = Column(
1691 created_on = Column(
1692 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1692 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1693 default=datetime.datetime.now)
1693 default=datetime.datetime.now)
1694 updated_on = Column(
1694 updated_on = Column(
1695 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1695 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1696 default=datetime.datetime.now)
1696 default=datetime.datetime.now)
1697 _landing_revision = Column(
1697 _landing_revision = Column(
1698 "landing_revision", String(255), nullable=False, unique=False,
1698 "landing_revision", String(255), nullable=False, unique=False,
1699 default=None)
1699 default=None)
1700 enable_locking = Column(
1700 enable_locking = Column(
1701 "enable_locking", Boolean(), nullable=False, unique=None,
1701 "enable_locking", Boolean(), nullable=False, unique=None,
1702 default=False)
1702 default=False)
1703 _locked = Column(
1703 _locked = Column(
1704 "locked", String(255), nullable=True, unique=False, default=None)
1704 "locked", String(255), nullable=True, unique=False, default=None)
1705 _changeset_cache = Column(
1705 _changeset_cache = Column(
1706 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1706 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1707
1707
1708 fork_id = Column(
1708 fork_id = Column(
1709 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1709 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1710 nullable=True, unique=False, default=None)
1710 nullable=True, unique=False, default=None)
1711 group_id = Column(
1711 group_id = Column(
1712 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1712 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1713 unique=False, default=None)
1713 unique=False, default=None)
1714
1714
1715 user = relationship('User', lazy='joined')
1715 user = relationship('User', lazy='joined')
1716 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1716 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1717 group = relationship('RepoGroup', lazy='joined')
1717 group = relationship('RepoGroup', lazy='joined')
1718 repo_to_perm = relationship(
1718 repo_to_perm = relationship(
1719 'UserRepoToPerm', cascade='all',
1719 'UserRepoToPerm', cascade='all',
1720 order_by='UserRepoToPerm.repo_to_perm_id')
1720 order_by='UserRepoToPerm.repo_to_perm_id')
1721 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1721 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1722 stats = relationship('Statistics', cascade='all', uselist=False)
1722 stats = relationship('Statistics', cascade='all', uselist=False)
1723
1723
1724 followers = relationship(
1724 followers = relationship(
1725 'UserFollowing',
1725 'UserFollowing',
1726 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1726 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1727 cascade='all')
1727 cascade='all')
1728 extra_fields = relationship(
1728 extra_fields = relationship(
1729 'RepositoryField', cascade="all, delete-orphan")
1729 'RepositoryField', cascade="all, delete-orphan")
1730 logs = relationship('UserLog')
1730 logs = relationship('UserLog')
1731 comments = relationship(
1731 comments = relationship(
1732 'ChangesetComment', cascade="all, delete-orphan")
1732 'ChangesetComment', cascade="all, delete-orphan")
1733 pull_requests_source = relationship(
1733 pull_requests_source = relationship(
1734 'PullRequest',
1734 'PullRequest',
1735 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1735 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1736 cascade="all, delete-orphan")
1736 cascade="all, delete-orphan")
1737 pull_requests_target = relationship(
1737 pull_requests_target = relationship(
1738 'PullRequest',
1738 'PullRequest',
1739 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1739 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1740 cascade="all, delete-orphan")
1740 cascade="all, delete-orphan")
1741 ui = relationship('RepoRhodeCodeUi', cascade="all")
1741 ui = relationship('RepoRhodeCodeUi', cascade="all")
1742 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1742 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1743 integrations = relationship('Integration', cascade="all, delete-orphan")
1743 integrations = relationship('Integration', cascade="all, delete-orphan")
1744
1744
1745 scoped_tokens = relationship('UserApiKeys', cascade="all")
1745 scoped_tokens = relationship('UserApiKeys', cascade="all")
1746
1746
1747 # no cascade, set NULL
1747 # no cascade, set NULL
1748 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1748 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1749
1749
1750 def __unicode__(self):
1750 def __unicode__(self):
1751 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1751 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1752 safe_unicode(self.repo_name))
1752 safe_unicode(self.repo_name))
1753
1753
1754 @hybrid_property
1754 @hybrid_property
1755 def description_safe(self):
1755 def description_safe(self):
1756 from rhodecode.lib import helpers as h
1756 from rhodecode.lib import helpers as h
1757 return h.escape(self.description)
1757 return h.escape(self.description)
1758
1758
1759 @hybrid_property
1759 @hybrid_property
1760 def landing_rev(self):
1760 def landing_rev(self):
1761 # always should return [rev_type, rev], e.g ['branch', 'master']
1761 # always should return [rev_type, rev], e.g ['branch', 'master']
1762 if self._landing_revision:
1762 if self._landing_revision:
1763 _rev_info = self._landing_revision.split(':')
1763 _rev_info = self._landing_revision.split(':')
1764 if len(_rev_info) < 2:
1764 if len(_rev_info) < 2:
1765 _rev_info.insert(0, 'rev')
1765 _rev_info.insert(0, 'rev')
1766 return [_rev_info[0], _rev_info[1]]
1766 return [_rev_info[0], _rev_info[1]]
1767 return [None, None]
1767 return [None, None]
1768
1768
1769 @property
1769 @property
1770 def landing_ref_type(self):
1770 def landing_ref_type(self):
1771 return self.landing_rev[0]
1771 return self.landing_rev[0]
1772
1772
1773 @property
1773 @property
1774 def landing_ref_name(self):
1774 def landing_ref_name(self):
1775 return self.landing_rev[1]
1775 return self.landing_rev[1]
1776
1776
1777 @landing_rev.setter
1777 @landing_rev.setter
1778 def landing_rev(self, val):
1778 def landing_rev(self, val):
1779 if ':' not in val:
1779 if ':' not in val:
1780 raise ValueError('value must be delimited with `:` and consist '
1780 raise ValueError('value must be delimited with `:` and consist '
1781 'of <rev_type>:<rev>, got %s instead' % val)
1781 'of <rev_type>:<rev>, got %s instead' % val)
1782 self._landing_revision = val
1782 self._landing_revision = val
1783
1783
1784 @hybrid_property
1784 @hybrid_property
1785 def locked(self):
1785 def locked(self):
1786 if self._locked:
1786 if self._locked:
1787 user_id, timelocked, reason = self._locked.split(':')
1787 user_id, timelocked, reason = self._locked.split(':')
1788 lock_values = int(user_id), timelocked, reason
1788 lock_values = int(user_id), timelocked, reason
1789 else:
1789 else:
1790 lock_values = [None, None, None]
1790 lock_values = [None, None, None]
1791 return lock_values
1791 return lock_values
1792
1792
1793 @locked.setter
1793 @locked.setter
1794 def locked(self, val):
1794 def locked(self, val):
1795 if val and isinstance(val, (list, tuple)):
1795 if val and isinstance(val, (list, tuple)):
1796 self._locked = ':'.join(map(str, val))
1796 self._locked = ':'.join(map(str, val))
1797 else:
1797 else:
1798 self._locked = None
1798 self._locked = None
1799
1799
1800 @classmethod
1800 @classmethod
1801 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1801 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1802 from rhodecode.lib.vcs.backends.base import EmptyCommit
1802 from rhodecode.lib.vcs.backends.base import EmptyCommit
1803 dummy = EmptyCommit().__json__()
1803 dummy = EmptyCommit().__json__()
1804 if not changeset_cache_raw:
1804 if not changeset_cache_raw:
1805 dummy['source_repo_id'] = repo_id
1805 dummy['source_repo_id'] = repo_id
1806 return json.loads(json.dumps(dummy))
1806 return json.loads(json.dumps(dummy))
1807
1807
1808 try:
1808 try:
1809 return json.loads(changeset_cache_raw)
1809 return json.loads(changeset_cache_raw)
1810 except TypeError:
1810 except TypeError:
1811 return dummy
1811 return dummy
1812 except Exception:
1812 except Exception:
1813 log.error(traceback.format_exc())
1813 log.error(traceback.format_exc())
1814 return dummy
1814 return dummy
1815
1815
1816 @hybrid_property
1816 @hybrid_property
1817 def changeset_cache(self):
1817 def changeset_cache(self):
1818 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1818 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1819
1819
1820 @changeset_cache.setter
1820 @changeset_cache.setter
1821 def changeset_cache(self, val):
1821 def changeset_cache(self, val):
1822 try:
1822 try:
1823 self._changeset_cache = json.dumps(val)
1823 self._changeset_cache = json.dumps(val)
1824 except Exception:
1824 except Exception:
1825 log.error(traceback.format_exc())
1825 log.error(traceback.format_exc())
1826
1826
1827 @hybrid_property
1827 @hybrid_property
1828 def repo_name(self):
1828 def repo_name(self):
1829 return self._repo_name
1829 return self._repo_name
1830
1830
1831 @repo_name.setter
1831 @repo_name.setter
1832 def repo_name(self, value):
1832 def repo_name(self, value):
1833 self._repo_name = value
1833 self._repo_name = value
1834 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1834 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1835
1835
1836 @classmethod
1836 @classmethod
1837 def normalize_repo_name(cls, repo_name):
1837 def normalize_repo_name(cls, repo_name):
1838 """
1838 """
1839 Normalizes os specific repo_name to the format internally stored inside
1839 Normalizes os specific repo_name to the format internally stored inside
1840 database using URL_SEP
1840 database using URL_SEP
1841
1841
1842 :param cls:
1842 :param cls:
1843 :param repo_name:
1843 :param repo_name:
1844 """
1844 """
1845 return cls.NAME_SEP.join(repo_name.split(os.sep))
1845 return cls.NAME_SEP.join(repo_name.split(os.sep))
1846
1846
1847 @classmethod
1847 @classmethod
1848 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1848 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1849 session = Session()
1849 session = Session()
1850 q = session.query(cls).filter(cls.repo_name == repo_name)
1850 q = session.query(cls).filter(cls.repo_name == repo_name)
1851
1851
1852 if cache:
1852 if cache:
1853 if identity_cache:
1853 if identity_cache:
1854 val = cls.identity_cache(session, 'repo_name', repo_name)
1854 val = cls.identity_cache(session, 'repo_name', repo_name)
1855 if val:
1855 if val:
1856 return val
1856 return val
1857 else:
1857 else:
1858 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1858 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1859 q = q.options(
1859 q = q.options(
1860 FromCache("sql_cache_short", cache_key))
1860 FromCache("sql_cache_short", cache_key))
1861
1861
1862 return q.scalar()
1862 return q.scalar()
1863
1863
1864 @classmethod
1864 @classmethod
1865 def get_by_id_or_repo_name(cls, repoid):
1865 def get_by_id_or_repo_name(cls, repoid):
1866 if isinstance(repoid, (int, long)):
1866 if isinstance(repoid, (int, long)):
1867 try:
1867 try:
1868 repo = cls.get(repoid)
1868 repo = cls.get(repoid)
1869 except ValueError:
1869 except ValueError:
1870 repo = None
1870 repo = None
1871 else:
1871 else:
1872 repo = cls.get_by_repo_name(repoid)
1872 repo = cls.get_by_repo_name(repoid)
1873 return repo
1873 return repo
1874
1874
1875 @classmethod
1875 @classmethod
1876 def get_by_full_path(cls, repo_full_path):
1876 def get_by_full_path(cls, repo_full_path):
1877 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1877 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1878 repo_name = cls.normalize_repo_name(repo_name)
1878 repo_name = cls.normalize_repo_name(repo_name)
1879 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1879 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1880
1880
1881 @classmethod
1881 @classmethod
1882 def get_repo_forks(cls, repo_id):
1882 def get_repo_forks(cls, repo_id):
1883 return cls.query().filter(Repository.fork_id == repo_id)
1883 return cls.query().filter(Repository.fork_id == repo_id)
1884
1884
1885 @classmethod
1885 @classmethod
1886 def base_path(cls):
1886 def base_path(cls):
1887 """
1887 """
1888 Returns base path when all repos are stored
1888 Returns base path when all repos are stored
1889
1889
1890 :param cls:
1890 :param cls:
1891 """
1891 """
1892 q = Session().query(RhodeCodeUi)\
1892 q = Session().query(RhodeCodeUi)\
1893 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1893 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1894 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1894 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1895 return q.one().ui_value
1895 return q.one().ui_value
1896
1896
1897 @classmethod
1897 @classmethod
1898 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1898 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1899 case_insensitive=True, archived=False):
1899 case_insensitive=True, archived=False):
1900 q = Repository.query()
1900 q = Repository.query()
1901
1901
1902 if not archived:
1902 if not archived:
1903 q = q.filter(Repository.archived.isnot(true()))
1903 q = q.filter(Repository.archived.isnot(true()))
1904
1904
1905 if not isinstance(user_id, Optional):
1905 if not isinstance(user_id, Optional):
1906 q = q.filter(Repository.user_id == user_id)
1906 q = q.filter(Repository.user_id == user_id)
1907
1907
1908 if not isinstance(group_id, Optional):
1908 if not isinstance(group_id, Optional):
1909 q = q.filter(Repository.group_id == group_id)
1909 q = q.filter(Repository.group_id == group_id)
1910
1910
1911 if case_insensitive:
1911 if case_insensitive:
1912 q = q.order_by(func.lower(Repository.repo_name))
1912 q = q.order_by(func.lower(Repository.repo_name))
1913 else:
1913 else:
1914 q = q.order_by(Repository.repo_name)
1914 q = q.order_by(Repository.repo_name)
1915
1915
1916 return q.all()
1916 return q.all()
1917
1917
1918 @property
1918 @property
1919 def repo_uid(self):
1919 def repo_uid(self):
1920 return '_{}'.format(self.repo_id)
1920 return '_{}'.format(self.repo_id)
1921
1921
1922 @property
1922 @property
1923 def forks(self):
1923 def forks(self):
1924 """
1924 """
1925 Return forks of this repo
1925 Return forks of this repo
1926 """
1926 """
1927 return Repository.get_repo_forks(self.repo_id)
1927 return Repository.get_repo_forks(self.repo_id)
1928
1928
1929 @property
1929 @property
1930 def parent(self):
1930 def parent(self):
1931 """
1931 """
1932 Returns fork parent
1932 Returns fork parent
1933 """
1933 """
1934 return self.fork
1934 return self.fork
1935
1935
1936 @property
1936 @property
1937 def just_name(self):
1937 def just_name(self):
1938 return self.repo_name.split(self.NAME_SEP)[-1]
1938 return self.repo_name.split(self.NAME_SEP)[-1]
1939
1939
1940 @property
1940 @property
1941 def groups_with_parents(self):
1941 def groups_with_parents(self):
1942 groups = []
1942 groups = []
1943 if self.group is None:
1943 if self.group is None:
1944 return groups
1944 return groups
1945
1945
1946 cur_gr = self.group
1946 cur_gr = self.group
1947 groups.insert(0, cur_gr)
1947 groups.insert(0, cur_gr)
1948 while 1:
1948 while 1:
1949 gr = getattr(cur_gr, 'parent_group', None)
1949 gr = getattr(cur_gr, 'parent_group', None)
1950 cur_gr = cur_gr.parent_group
1950 cur_gr = cur_gr.parent_group
1951 if gr is None:
1951 if gr is None:
1952 break
1952 break
1953 groups.insert(0, gr)
1953 groups.insert(0, gr)
1954
1954
1955 return groups
1955 return groups
1956
1956
1957 @property
1957 @property
1958 def groups_and_repo(self):
1958 def groups_and_repo(self):
1959 return self.groups_with_parents, self
1959 return self.groups_with_parents, self
1960
1960
1961 @LazyProperty
1961 @LazyProperty
1962 def repo_path(self):
1962 def repo_path(self):
1963 """
1963 """
1964 Returns base full path for that repository means where it actually
1964 Returns base full path for that repository means where it actually
1965 exists on a filesystem
1965 exists on a filesystem
1966 """
1966 """
1967 q = Session().query(RhodeCodeUi).filter(
1967 q = Session().query(RhodeCodeUi).filter(
1968 RhodeCodeUi.ui_key == self.NAME_SEP)
1968 RhodeCodeUi.ui_key == self.NAME_SEP)
1969 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1969 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1970 return q.one().ui_value
1970 return q.one().ui_value
1971
1971
1972 @property
1972 @property
1973 def repo_full_path(self):
1973 def repo_full_path(self):
1974 p = [self.repo_path]
1974 p = [self.repo_path]
1975 # we need to split the name by / since this is how we store the
1975 # we need to split the name by / since this is how we store the
1976 # names in the database, but that eventually needs to be converted
1976 # names in the database, but that eventually needs to be converted
1977 # into a valid system path
1977 # into a valid system path
1978 p += self.repo_name.split(self.NAME_SEP)
1978 p += self.repo_name.split(self.NAME_SEP)
1979 return os.path.join(*map(safe_unicode, p))
1979 return os.path.join(*map(safe_unicode, p))
1980
1980
1981 @property
1981 @property
1982 def cache_keys(self):
1982 def cache_keys(self):
1983 """
1983 """
1984 Returns associated cache keys for that repo
1984 Returns associated cache keys for that repo
1985 """
1985 """
1986 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1986 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1987 repo_id=self.repo_id)
1987 repo_id=self.repo_id)
1988 return CacheKey.query()\
1988 return CacheKey.query()\
1989 .filter(CacheKey.cache_args == invalidation_namespace)\
1989 .filter(CacheKey.cache_args == invalidation_namespace)\
1990 .order_by(CacheKey.cache_key)\
1990 .order_by(CacheKey.cache_key)\
1991 .all()
1991 .all()
1992
1992
1993 @property
1993 @property
1994 def cached_diffs_relative_dir(self):
1994 def cached_diffs_relative_dir(self):
1995 """
1995 """
1996 Return a relative to the repository store path of cached diffs
1996 Return a relative to the repository store path of cached diffs
1997 used for safe display for users, who shouldn't know the absolute store
1997 used for safe display for users, who shouldn't know the absolute store
1998 path
1998 path
1999 """
1999 """
2000 return os.path.join(
2000 return os.path.join(
2001 os.path.dirname(self.repo_name),
2001 os.path.dirname(self.repo_name),
2002 self.cached_diffs_dir.split(os.path.sep)[-1])
2002 self.cached_diffs_dir.split(os.path.sep)[-1])
2003
2003
2004 @property
2004 @property
2005 def cached_diffs_dir(self):
2005 def cached_diffs_dir(self):
2006 path = self.repo_full_path
2006 path = self.repo_full_path
2007 return os.path.join(
2007 return os.path.join(
2008 os.path.dirname(path),
2008 os.path.dirname(path),
2009 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2009 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2010
2010
2011 def cached_diffs(self):
2011 def cached_diffs(self):
2012 diff_cache_dir = self.cached_diffs_dir
2012 diff_cache_dir = self.cached_diffs_dir
2013 if os.path.isdir(diff_cache_dir):
2013 if os.path.isdir(diff_cache_dir):
2014 return os.listdir(diff_cache_dir)
2014 return os.listdir(diff_cache_dir)
2015 return []
2015 return []
2016
2016
2017 def shadow_repos(self):
2017 def shadow_repos(self):
2018 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2018 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2019 return [
2019 return [
2020 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2020 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2021 if x.startswith(shadow_repos_pattern)]
2021 if x.startswith(shadow_repos_pattern)]
2022
2022
2023 def get_new_name(self, repo_name):
2023 def get_new_name(self, repo_name):
2024 """
2024 """
2025 returns new full repository name based on assigned group and new new
2025 returns new full repository name based on assigned group and new new
2026
2026
2027 :param group_name:
2027 :param group_name:
2028 """
2028 """
2029 path_prefix = self.group.full_path_splitted if self.group else []
2029 path_prefix = self.group.full_path_splitted if self.group else []
2030 return self.NAME_SEP.join(path_prefix + [repo_name])
2030 return self.NAME_SEP.join(path_prefix + [repo_name])
2031
2031
2032 @property
2032 @property
2033 def _config(self):
2033 def _config(self):
2034 """
2034 """
2035 Returns db based config object.
2035 Returns db based config object.
2036 """
2036 """
2037 from rhodecode.lib.utils import make_db_config
2037 from rhodecode.lib.utils import make_db_config
2038 return make_db_config(clear_session=False, repo=self)
2038 return make_db_config(clear_session=False, repo=self)
2039
2039
2040 def permissions(self, with_admins=True, with_owner=True,
2040 def permissions(self, with_admins=True, with_owner=True,
2041 expand_from_user_groups=False):
2041 expand_from_user_groups=False):
2042 """
2042 """
2043 Permissions for repositories
2043 Permissions for repositories
2044 """
2044 """
2045 _admin_perm = 'repository.admin'
2045 _admin_perm = 'repository.admin'
2046
2046
2047 owner_row = []
2047 owner_row = []
2048 if with_owner:
2048 if with_owner:
2049 usr = AttributeDict(self.user.get_dict())
2049 usr = AttributeDict(self.user.get_dict())
2050 usr.owner_row = True
2050 usr.owner_row = True
2051 usr.permission = _admin_perm
2051 usr.permission = _admin_perm
2052 usr.permission_id = None
2052 usr.permission_id = None
2053 owner_row.append(usr)
2053 owner_row.append(usr)
2054
2054
2055 super_admin_ids = []
2055 super_admin_ids = []
2056 super_admin_rows = []
2056 super_admin_rows = []
2057 if with_admins:
2057 if with_admins:
2058 for usr in User.get_all_super_admins():
2058 for usr in User.get_all_super_admins():
2059 super_admin_ids.append(usr.user_id)
2059 super_admin_ids.append(usr.user_id)
2060 # if this admin is also owner, don't double the record
2060 # if this admin is also owner, don't double the record
2061 if usr.user_id == owner_row[0].user_id:
2061 if usr.user_id == owner_row[0].user_id:
2062 owner_row[0].admin_row = True
2062 owner_row[0].admin_row = True
2063 else:
2063 else:
2064 usr = AttributeDict(usr.get_dict())
2064 usr = AttributeDict(usr.get_dict())
2065 usr.admin_row = True
2065 usr.admin_row = True
2066 usr.permission = _admin_perm
2066 usr.permission = _admin_perm
2067 usr.permission_id = None
2067 usr.permission_id = None
2068 super_admin_rows.append(usr)
2068 super_admin_rows.append(usr)
2069
2069
2070 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2070 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2071 q = q.options(joinedload(UserRepoToPerm.repository),
2071 q = q.options(joinedload(UserRepoToPerm.repository),
2072 joinedload(UserRepoToPerm.user),
2072 joinedload(UserRepoToPerm.user),
2073 joinedload(UserRepoToPerm.permission),)
2073 joinedload(UserRepoToPerm.permission),)
2074
2074
2075 # get owners and admins and permissions. We do a trick of re-writing
2075 # get owners and admins and permissions. We do a trick of re-writing
2076 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2076 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2077 # has a global reference and changing one object propagates to all
2077 # has a global reference and changing one object propagates to all
2078 # others. This means if admin is also an owner admin_row that change
2078 # others. This means if admin is also an owner admin_row that change
2079 # would propagate to both objects
2079 # would propagate to both objects
2080 perm_rows = []
2080 perm_rows = []
2081 for _usr in q.all():
2081 for _usr in q.all():
2082 usr = AttributeDict(_usr.user.get_dict())
2082 usr = AttributeDict(_usr.user.get_dict())
2083 # if this user is also owner/admin, mark as duplicate record
2083 # if this user is also owner/admin, mark as duplicate record
2084 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2084 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2085 usr.duplicate_perm = True
2085 usr.duplicate_perm = True
2086 # also check if this permission is maybe used by branch_permissions
2086 # also check if this permission is maybe used by branch_permissions
2087 if _usr.branch_perm_entry:
2087 if _usr.branch_perm_entry:
2088 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2088 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2089
2089
2090 usr.permission = _usr.permission.permission_name
2090 usr.permission = _usr.permission.permission_name
2091 usr.permission_id = _usr.repo_to_perm_id
2091 usr.permission_id = _usr.repo_to_perm_id
2092 perm_rows.append(usr)
2092 perm_rows.append(usr)
2093
2093
2094 # filter the perm rows by 'default' first and then sort them by
2094 # filter the perm rows by 'default' first and then sort them by
2095 # admin,write,read,none permissions sorted again alphabetically in
2095 # admin,write,read,none permissions sorted again alphabetically in
2096 # each group
2096 # each group
2097 perm_rows = sorted(perm_rows, key=display_user_sort)
2097 perm_rows = sorted(perm_rows, key=display_user_sort)
2098
2098
2099 user_groups_rows = []
2099 user_groups_rows = []
2100 if expand_from_user_groups:
2100 if expand_from_user_groups:
2101 for ug in self.permission_user_groups(with_members=True):
2101 for ug in self.permission_user_groups(with_members=True):
2102 for user_data in ug.members:
2102 for user_data in ug.members:
2103 user_groups_rows.append(user_data)
2103 user_groups_rows.append(user_data)
2104
2104
2105 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2105 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2106
2106
2107 def permission_user_groups(self, with_members=True):
2107 def permission_user_groups(self, with_members=True):
2108 q = UserGroupRepoToPerm.query()\
2108 q = UserGroupRepoToPerm.query()\
2109 .filter(UserGroupRepoToPerm.repository == self)
2109 .filter(UserGroupRepoToPerm.repository == self)
2110 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2110 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2111 joinedload(UserGroupRepoToPerm.users_group),
2111 joinedload(UserGroupRepoToPerm.users_group),
2112 joinedload(UserGroupRepoToPerm.permission),)
2112 joinedload(UserGroupRepoToPerm.permission),)
2113
2113
2114 perm_rows = []
2114 perm_rows = []
2115 for _user_group in q.all():
2115 for _user_group in q.all():
2116 entry = AttributeDict(_user_group.users_group.get_dict())
2116 entry = AttributeDict(_user_group.users_group.get_dict())
2117 entry.permission = _user_group.permission.permission_name
2117 entry.permission = _user_group.permission.permission_name
2118 if with_members:
2118 if with_members:
2119 entry.members = [x.user.get_dict()
2119 entry.members = [x.user.get_dict()
2120 for x in _user_group.users_group.members]
2120 for x in _user_group.users_group.members]
2121 perm_rows.append(entry)
2121 perm_rows.append(entry)
2122
2122
2123 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2123 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2124 return perm_rows
2124 return perm_rows
2125
2125
2126 def get_api_data(self, include_secrets=False):
2126 def get_api_data(self, include_secrets=False):
2127 """
2127 """
2128 Common function for generating repo api data
2128 Common function for generating repo api data
2129
2129
2130 :param include_secrets: See :meth:`User.get_api_data`.
2130 :param include_secrets: See :meth:`User.get_api_data`.
2131
2131
2132 """
2132 """
2133 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2133 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2134 # move this methods on models level.
2134 # move this methods on models level.
2135 from rhodecode.model.settings import SettingsModel
2135 from rhodecode.model.settings import SettingsModel
2136 from rhodecode.model.repo import RepoModel
2136 from rhodecode.model.repo import RepoModel
2137
2137
2138 repo = self
2138 repo = self
2139 _user_id, _time, _reason = self.locked
2139 _user_id, _time, _reason = self.locked
2140
2140
2141 data = {
2141 data = {
2142 'repo_id': repo.repo_id,
2142 'repo_id': repo.repo_id,
2143 'repo_name': repo.repo_name,
2143 'repo_name': repo.repo_name,
2144 'repo_type': repo.repo_type,
2144 'repo_type': repo.repo_type,
2145 'clone_uri': repo.clone_uri or '',
2145 'clone_uri': repo.clone_uri or '',
2146 'push_uri': repo.push_uri or '',
2146 'push_uri': repo.push_uri or '',
2147 'url': RepoModel().get_url(self),
2147 'url': RepoModel().get_url(self),
2148 'private': repo.private,
2148 'private': repo.private,
2149 'created_on': repo.created_on,
2149 'created_on': repo.created_on,
2150 'description': repo.description_safe,
2150 'description': repo.description_safe,
2151 'landing_rev': repo.landing_rev,
2151 'landing_rev': repo.landing_rev,
2152 'owner': repo.user.username,
2152 'owner': repo.user.username,
2153 'fork_of': repo.fork.repo_name if repo.fork else None,
2153 'fork_of': repo.fork.repo_name if repo.fork else None,
2154 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2154 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2155 'enable_statistics': repo.enable_statistics,
2155 'enable_statistics': repo.enable_statistics,
2156 'enable_locking': repo.enable_locking,
2156 'enable_locking': repo.enable_locking,
2157 'enable_downloads': repo.enable_downloads,
2157 'enable_downloads': repo.enable_downloads,
2158 'last_changeset': repo.changeset_cache,
2158 'last_changeset': repo.changeset_cache,
2159 'locked_by': User.get(_user_id).get_api_data(
2159 'locked_by': User.get(_user_id).get_api_data(
2160 include_secrets=include_secrets) if _user_id else None,
2160 include_secrets=include_secrets) if _user_id else None,
2161 'locked_date': time_to_datetime(_time) if _time else None,
2161 'locked_date': time_to_datetime(_time) if _time else None,
2162 'lock_reason': _reason if _reason else None,
2162 'lock_reason': _reason if _reason else None,
2163 }
2163 }
2164
2164
2165 # TODO: mikhail: should be per-repo settings here
2165 # TODO: mikhail: should be per-repo settings here
2166 rc_config = SettingsModel().get_all_settings()
2166 rc_config = SettingsModel().get_all_settings()
2167 repository_fields = str2bool(
2167 repository_fields = str2bool(
2168 rc_config.get('rhodecode_repository_fields'))
2168 rc_config.get('rhodecode_repository_fields'))
2169 if repository_fields:
2169 if repository_fields:
2170 for f in self.extra_fields:
2170 for f in self.extra_fields:
2171 data[f.field_key_prefixed] = f.field_value
2171 data[f.field_key_prefixed] = f.field_value
2172
2172
2173 return data
2173 return data
2174
2174
2175 @classmethod
2175 @classmethod
2176 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2176 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2177 if not lock_time:
2177 if not lock_time:
2178 lock_time = time.time()
2178 lock_time = time.time()
2179 if not lock_reason:
2179 if not lock_reason:
2180 lock_reason = cls.LOCK_AUTOMATIC
2180 lock_reason = cls.LOCK_AUTOMATIC
2181 repo.locked = [user_id, lock_time, lock_reason]
2181 repo.locked = [user_id, lock_time, lock_reason]
2182 Session().add(repo)
2182 Session().add(repo)
2183 Session().commit()
2183 Session().commit()
2184
2184
2185 @classmethod
2185 @classmethod
2186 def unlock(cls, repo):
2186 def unlock(cls, repo):
2187 repo.locked = None
2187 repo.locked = None
2188 Session().add(repo)
2188 Session().add(repo)
2189 Session().commit()
2189 Session().commit()
2190
2190
2191 @classmethod
2191 @classmethod
2192 def getlock(cls, repo):
2192 def getlock(cls, repo):
2193 return repo.locked
2193 return repo.locked
2194
2194
2195 def is_user_lock(self, user_id):
2195 def is_user_lock(self, user_id):
2196 if self.lock[0]:
2196 if self.lock[0]:
2197 lock_user_id = safe_int(self.lock[0])
2197 lock_user_id = safe_int(self.lock[0])
2198 user_id = safe_int(user_id)
2198 user_id = safe_int(user_id)
2199 # both are ints, and they are equal
2199 # both are ints, and they are equal
2200 return all([lock_user_id, user_id]) and lock_user_id == user_id
2200 return all([lock_user_id, user_id]) and lock_user_id == user_id
2201
2201
2202 return False
2202 return False
2203
2203
2204 def get_locking_state(self, action, user_id, only_when_enabled=True):
2204 def get_locking_state(self, action, user_id, only_when_enabled=True):
2205 """
2205 """
2206 Checks locking on this repository, if locking is enabled and lock is
2206 Checks locking on this repository, if locking is enabled and lock is
2207 present returns a tuple of make_lock, locked, locked_by.
2207 present returns a tuple of make_lock, locked, locked_by.
2208 make_lock can have 3 states None (do nothing) True, make lock
2208 make_lock can have 3 states None (do nothing) True, make lock
2209 False release lock, This value is later propagated to hooks, which
2209 False release lock, This value is later propagated to hooks, which
2210 do the locking. Think about this as signals passed to hooks what to do.
2210 do the locking. Think about this as signals passed to hooks what to do.
2211
2211
2212 """
2212 """
2213 # TODO: johbo: This is part of the business logic and should be moved
2213 # TODO: johbo: This is part of the business logic and should be moved
2214 # into the RepositoryModel.
2214 # into the RepositoryModel.
2215
2215
2216 if action not in ('push', 'pull'):
2216 if action not in ('push', 'pull'):
2217 raise ValueError("Invalid action value: %s" % repr(action))
2217 raise ValueError("Invalid action value: %s" % repr(action))
2218
2218
2219 # defines if locked error should be thrown to user
2219 # defines if locked error should be thrown to user
2220 currently_locked = False
2220 currently_locked = False
2221 # defines if new lock should be made, tri-state
2221 # defines if new lock should be made, tri-state
2222 make_lock = None
2222 make_lock = None
2223 repo = self
2223 repo = self
2224 user = User.get(user_id)
2224 user = User.get(user_id)
2225
2225
2226 lock_info = repo.locked
2226 lock_info = repo.locked
2227
2227
2228 if repo and (repo.enable_locking or not only_when_enabled):
2228 if repo and (repo.enable_locking or not only_when_enabled):
2229 if action == 'push':
2229 if action == 'push':
2230 # check if it's already locked !, if it is compare users
2230 # check if it's already locked !, if it is compare users
2231 locked_by_user_id = lock_info[0]
2231 locked_by_user_id = lock_info[0]
2232 if user.user_id == locked_by_user_id:
2232 if user.user_id == locked_by_user_id:
2233 log.debug(
2233 log.debug(
2234 'Got `push` action from user %s, now unlocking', user)
2234 'Got `push` action from user %s, now unlocking', user)
2235 # unlock if we have push from user who locked
2235 # unlock if we have push from user who locked
2236 make_lock = False
2236 make_lock = False
2237 else:
2237 else:
2238 # we're not the same user who locked, ban with
2238 # we're not the same user who locked, ban with
2239 # code defined in settings (default is 423 HTTP Locked) !
2239 # code defined in settings (default is 423 HTTP Locked) !
2240 log.debug('Repo %s is currently locked by %s', repo, user)
2240 log.debug('Repo %s is currently locked by %s', repo, user)
2241 currently_locked = True
2241 currently_locked = True
2242 elif action == 'pull':
2242 elif action == 'pull':
2243 # [0] user [1] date
2243 # [0] user [1] date
2244 if lock_info[0] and lock_info[1]:
2244 if lock_info[0] and lock_info[1]:
2245 log.debug('Repo %s is currently locked by %s', repo, user)
2245 log.debug('Repo %s is currently locked by %s', repo, user)
2246 currently_locked = True
2246 currently_locked = True
2247 else:
2247 else:
2248 log.debug('Setting lock on repo %s by %s', repo, user)
2248 log.debug('Setting lock on repo %s by %s', repo, user)
2249 make_lock = True
2249 make_lock = True
2250
2250
2251 else:
2251 else:
2252 log.debug('Repository %s do not have locking enabled', repo)
2252 log.debug('Repository %s do not have locking enabled', repo)
2253
2253
2254 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2254 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2255 make_lock, currently_locked, lock_info)
2255 make_lock, currently_locked, lock_info)
2256
2256
2257 from rhodecode.lib.auth import HasRepoPermissionAny
2257 from rhodecode.lib.auth import HasRepoPermissionAny
2258 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2258 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2259 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2259 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2260 # if we don't have at least write permission we cannot make a lock
2260 # if we don't have at least write permission we cannot make a lock
2261 log.debug('lock state reset back to FALSE due to lack '
2261 log.debug('lock state reset back to FALSE due to lack '
2262 'of at least read permission')
2262 'of at least read permission')
2263 make_lock = False
2263 make_lock = False
2264
2264
2265 return make_lock, currently_locked, lock_info
2265 return make_lock, currently_locked, lock_info
2266
2266
2267 @property
2267 @property
2268 def last_commit_cache_update_diff(self):
2268 def last_commit_cache_update_diff(self):
2269 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2269 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2270
2270
2271 @classmethod
2271 @classmethod
2272 def _load_commit_change(cls, last_commit_cache):
2272 def _load_commit_change(cls, last_commit_cache):
2273 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2273 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2274 empty_date = datetime.datetime.fromtimestamp(0)
2274 empty_date = datetime.datetime.fromtimestamp(0)
2275 date_latest = last_commit_cache.get('date', empty_date)
2275 date_latest = last_commit_cache.get('date', empty_date)
2276 try:
2276 try:
2277 return parse_datetime(date_latest)
2277 return parse_datetime(date_latest)
2278 except Exception:
2278 except Exception:
2279 return empty_date
2279 return empty_date
2280
2280
2281 @property
2281 @property
2282 def last_commit_change(self):
2282 def last_commit_change(self):
2283 return self._load_commit_change(self.changeset_cache)
2283 return self._load_commit_change(self.changeset_cache)
2284
2284
2285 @property
2285 @property
2286 def last_db_change(self):
2286 def last_db_change(self):
2287 return self.updated_on
2287 return self.updated_on
2288
2288
2289 @property
2289 @property
2290 def clone_uri_hidden(self):
2290 def clone_uri_hidden(self):
2291 clone_uri = self.clone_uri
2291 clone_uri = self.clone_uri
2292 if clone_uri:
2292 if clone_uri:
2293 import urlobject
2293 import urlobject
2294 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2294 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2295 if url_obj.password:
2295 if url_obj.password:
2296 clone_uri = url_obj.with_password('*****')
2296 clone_uri = url_obj.with_password('*****')
2297 return clone_uri
2297 return clone_uri
2298
2298
2299 @property
2299 @property
2300 def push_uri_hidden(self):
2300 def push_uri_hidden(self):
2301 push_uri = self.push_uri
2301 push_uri = self.push_uri
2302 if push_uri:
2302 if push_uri:
2303 import urlobject
2303 import urlobject
2304 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2304 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2305 if url_obj.password:
2305 if url_obj.password:
2306 push_uri = url_obj.with_password('*****')
2306 push_uri = url_obj.with_password('*****')
2307 return push_uri
2307 return push_uri
2308
2308
2309 def clone_url(self, **override):
2309 def clone_url(self, **override):
2310 from rhodecode.model.settings import SettingsModel
2310 from rhodecode.model.settings import SettingsModel
2311
2311
2312 uri_tmpl = None
2312 uri_tmpl = None
2313 if 'with_id' in override:
2313 if 'with_id' in override:
2314 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2314 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2315 del override['with_id']
2315 del override['with_id']
2316
2316
2317 if 'uri_tmpl' in override:
2317 if 'uri_tmpl' in override:
2318 uri_tmpl = override['uri_tmpl']
2318 uri_tmpl = override['uri_tmpl']
2319 del override['uri_tmpl']
2319 del override['uri_tmpl']
2320
2320
2321 ssh = False
2321 ssh = False
2322 if 'ssh' in override:
2322 if 'ssh' in override:
2323 ssh = True
2323 ssh = True
2324 del override['ssh']
2324 del override['ssh']
2325
2325
2326 # we didn't override our tmpl from **overrides
2326 # we didn't override our tmpl from **overrides
2327 request = get_current_request()
2327 request = get_current_request()
2328 if not uri_tmpl:
2328 if not uri_tmpl:
2329 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2329 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2330 rc_config = request.call_context.rc_config
2330 rc_config = request.call_context.rc_config
2331 else:
2331 else:
2332 rc_config = SettingsModel().get_all_settings(cache=True)
2332 rc_config = SettingsModel().get_all_settings(cache=True)
2333
2333
2334 if ssh:
2334 if ssh:
2335 uri_tmpl = rc_config.get(
2335 uri_tmpl = rc_config.get(
2336 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2336 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2337
2337
2338 else:
2338 else:
2339 uri_tmpl = rc_config.get(
2339 uri_tmpl = rc_config.get(
2340 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2340 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2341
2341
2342 return get_clone_url(request=request,
2342 return get_clone_url(request=request,
2343 uri_tmpl=uri_tmpl,
2343 uri_tmpl=uri_tmpl,
2344 repo_name=self.repo_name,
2344 repo_name=self.repo_name,
2345 repo_id=self.repo_id,
2345 repo_id=self.repo_id,
2346 repo_type=self.repo_type,
2346 repo_type=self.repo_type,
2347 **override)
2347 **override)
2348
2348
2349 def set_state(self, state):
2349 def set_state(self, state):
2350 self.repo_state = state
2350 self.repo_state = state
2351 Session().add(self)
2351 Session().add(self)
2352 #==========================================================================
2352 #==========================================================================
2353 # SCM PROPERTIES
2353 # SCM PROPERTIES
2354 #==========================================================================
2354 #==========================================================================
2355
2355
2356 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2356 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2357 return get_commit_safe(
2357 return get_commit_safe(
2358 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2358 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2359 maybe_unreachable=maybe_unreachable)
2359 maybe_unreachable=maybe_unreachable)
2360
2360
2361 def get_changeset(self, rev=None, pre_load=None):
2361 def get_changeset(self, rev=None, pre_load=None):
2362 warnings.warn("Use get_commit", DeprecationWarning)
2362 warnings.warn("Use get_commit", DeprecationWarning)
2363 commit_id = None
2363 commit_id = None
2364 commit_idx = None
2364 commit_idx = None
2365 if isinstance(rev, compat.string_types):
2365 if isinstance(rev, compat.string_types):
2366 commit_id = rev
2366 commit_id = rev
2367 else:
2367 else:
2368 commit_idx = rev
2368 commit_idx = rev
2369 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2369 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2370 pre_load=pre_load)
2370 pre_load=pre_load)
2371
2371
2372 def get_landing_commit(self):
2372 def get_landing_commit(self):
2373 """
2373 """
2374 Returns landing commit, or if that doesn't exist returns the tip
2374 Returns landing commit, or if that doesn't exist returns the tip
2375 """
2375 """
2376 _rev_type, _rev = self.landing_rev
2376 _rev_type, _rev = self.landing_rev
2377 commit = self.get_commit(_rev)
2377 commit = self.get_commit(_rev)
2378 if isinstance(commit, EmptyCommit):
2378 if isinstance(commit, EmptyCommit):
2379 return self.get_commit()
2379 return self.get_commit()
2380 return commit
2380 return commit
2381
2381
2382 def flush_commit_cache(self):
2382 def flush_commit_cache(self):
2383 self.update_commit_cache(cs_cache={'raw_id':'0'})
2383 self.update_commit_cache(cs_cache={'raw_id':'0'})
2384 self.update_commit_cache()
2384 self.update_commit_cache()
2385
2385
2386 def update_commit_cache(self, cs_cache=None, config=None):
2386 def update_commit_cache(self, cs_cache=None, config=None):
2387 """
2387 """
2388 Update cache of last commit for repository
2388 Update cache of last commit for repository
2389 cache_keys should be::
2389 cache_keys should be::
2390
2390
2391 source_repo_id
2391 source_repo_id
2392 short_id
2392 short_id
2393 raw_id
2393 raw_id
2394 revision
2394 revision
2395 parents
2395 parents
2396 message
2396 message
2397 date
2397 date
2398 author
2398 author
2399 updated_on
2399 updated_on
2400
2400
2401 """
2401 """
2402 from rhodecode.lib.vcs.backends.base import BaseChangeset
2402 from rhodecode.lib.vcs.backends.base import BaseChangeset
2403 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2403 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2404 empty_date = datetime.datetime.fromtimestamp(0)
2404 empty_date = datetime.datetime.fromtimestamp(0)
2405
2405
2406 if cs_cache is None:
2406 if cs_cache is None:
2407 # use no-cache version here
2407 # use no-cache version here
2408 try:
2408 try:
2409 scm_repo = self.scm_instance(cache=False, config=config)
2409 scm_repo = self.scm_instance(cache=False, config=config)
2410 except VCSError:
2410 except VCSError:
2411 scm_repo = None
2411 scm_repo = None
2412 empty = scm_repo is None or scm_repo.is_empty()
2412 empty = scm_repo is None or scm_repo.is_empty()
2413
2413
2414 if not empty:
2414 if not empty:
2415 cs_cache = scm_repo.get_commit(
2415 cs_cache = scm_repo.get_commit(
2416 pre_load=["author", "date", "message", "parents", "branch"])
2416 pre_load=["author", "date", "message", "parents", "branch"])
2417 else:
2417 else:
2418 cs_cache = EmptyCommit()
2418 cs_cache = EmptyCommit()
2419
2419
2420 if isinstance(cs_cache, BaseChangeset):
2420 if isinstance(cs_cache, BaseChangeset):
2421 cs_cache = cs_cache.__json__()
2421 cs_cache = cs_cache.__json__()
2422
2422
2423 def is_outdated(new_cs_cache):
2423 def is_outdated(new_cs_cache):
2424 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2424 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2425 new_cs_cache['revision'] != self.changeset_cache['revision']):
2425 new_cs_cache['revision'] != self.changeset_cache['revision']):
2426 return True
2426 return True
2427 return False
2427 return False
2428
2428
2429 # check if we have maybe already latest cached revision
2429 # check if we have maybe already latest cached revision
2430 if is_outdated(cs_cache) or not self.changeset_cache:
2430 if is_outdated(cs_cache) or not self.changeset_cache:
2431 _current_datetime = datetime.datetime.utcnow()
2431 _current_datetime = datetime.datetime.utcnow()
2432 last_change = cs_cache.get('date') or _current_datetime
2432 last_change = cs_cache.get('date') or _current_datetime
2433 # we check if last update is newer than the new value
2433 # we check if last update is newer than the new value
2434 # if yes, we use the current timestamp instead. Imagine you get
2434 # if yes, we use the current timestamp instead. Imagine you get
2435 # old commit pushed 1y ago, we'd set last update 1y to ago.
2435 # old commit pushed 1y ago, we'd set last update 1y to ago.
2436 last_change_timestamp = datetime_to_time(last_change)
2436 last_change_timestamp = datetime_to_time(last_change)
2437 current_timestamp = datetime_to_time(last_change)
2437 current_timestamp = datetime_to_time(last_change)
2438 if last_change_timestamp > current_timestamp and not empty:
2438 if last_change_timestamp > current_timestamp and not empty:
2439 cs_cache['date'] = _current_datetime
2439 cs_cache['date'] = _current_datetime
2440
2440
2441 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2441 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2442 cs_cache['updated_on'] = time.time()
2442 cs_cache['updated_on'] = time.time()
2443 self.changeset_cache = cs_cache
2443 self.changeset_cache = cs_cache
2444 self.updated_on = last_change
2444 self.updated_on = last_change
2445 Session().add(self)
2445 Session().add(self)
2446 Session().commit()
2446 Session().commit()
2447
2447
2448 else:
2448 else:
2449 if empty:
2449 if empty:
2450 cs_cache = EmptyCommit().__json__()
2450 cs_cache = EmptyCommit().__json__()
2451 else:
2451 else:
2452 cs_cache = self.changeset_cache
2452 cs_cache = self.changeset_cache
2453
2453
2454 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2454 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2455
2455
2456 cs_cache['updated_on'] = time.time()
2456 cs_cache['updated_on'] = time.time()
2457 self.changeset_cache = cs_cache
2457 self.changeset_cache = cs_cache
2458 self.updated_on = _date_latest
2458 self.updated_on = _date_latest
2459 Session().add(self)
2459 Session().add(self)
2460 Session().commit()
2460 Session().commit()
2461
2461
2462 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2462 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2463 self.repo_name, cs_cache, _date_latest)
2463 self.repo_name, cs_cache, _date_latest)
2464
2464
2465 @property
2465 @property
2466 def tip(self):
2466 def tip(self):
2467 return self.get_commit('tip')
2467 return self.get_commit('tip')
2468
2468
2469 @property
2469 @property
2470 def author(self):
2470 def author(self):
2471 return self.tip.author
2471 return self.tip.author
2472
2472
2473 @property
2473 @property
2474 def last_change(self):
2474 def last_change(self):
2475 return self.scm_instance().last_change
2475 return self.scm_instance().last_change
2476
2476
2477 def get_comments(self, revisions=None):
2477 def get_comments(self, revisions=None):
2478 """
2478 """
2479 Returns comments for this repository grouped by revisions
2479 Returns comments for this repository grouped by revisions
2480
2480
2481 :param revisions: filter query by revisions only
2481 :param revisions: filter query by revisions only
2482 """
2482 """
2483 cmts = ChangesetComment.query()\
2483 cmts = ChangesetComment.query()\
2484 .filter(ChangesetComment.repo == self)
2484 .filter(ChangesetComment.repo == self)
2485 if revisions:
2485 if revisions:
2486 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2486 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2487 grouped = collections.defaultdict(list)
2487 grouped = collections.defaultdict(list)
2488 for cmt in cmts.all():
2488 for cmt in cmts.all():
2489 grouped[cmt.revision].append(cmt)
2489 grouped[cmt.revision].append(cmt)
2490 return grouped
2490 return grouped
2491
2491
2492 def statuses(self, revisions=None):
2492 def statuses(self, revisions=None):
2493 """
2493 """
2494 Returns statuses for this repository
2494 Returns statuses for this repository
2495
2495
2496 :param revisions: list of revisions to get statuses for
2496 :param revisions: list of revisions to get statuses for
2497 """
2497 """
2498 statuses = ChangesetStatus.query()\
2498 statuses = ChangesetStatus.query()\
2499 .filter(ChangesetStatus.repo == self)\
2499 .filter(ChangesetStatus.repo == self)\
2500 .filter(ChangesetStatus.version == 0)
2500 .filter(ChangesetStatus.version == 0)
2501
2501
2502 if revisions:
2502 if revisions:
2503 # Try doing the filtering in chunks to avoid hitting limits
2503 # Try doing the filtering in chunks to avoid hitting limits
2504 size = 500
2504 size = 500
2505 status_results = []
2505 status_results = []
2506 for chunk in xrange(0, len(revisions), size):
2506 for chunk in xrange(0, len(revisions), size):
2507 status_results += statuses.filter(
2507 status_results += statuses.filter(
2508 ChangesetStatus.revision.in_(
2508 ChangesetStatus.revision.in_(
2509 revisions[chunk: chunk+size])
2509 revisions[chunk: chunk+size])
2510 ).all()
2510 ).all()
2511 else:
2511 else:
2512 status_results = statuses.all()
2512 status_results = statuses.all()
2513
2513
2514 grouped = {}
2514 grouped = {}
2515
2515
2516 # maybe we have open new pullrequest without a status?
2516 # maybe we have open new pullrequest without a status?
2517 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2517 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2518 status_lbl = ChangesetStatus.get_status_lbl(stat)
2518 status_lbl = ChangesetStatus.get_status_lbl(stat)
2519 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2519 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2520 for rev in pr.revisions:
2520 for rev in pr.revisions:
2521 pr_id = pr.pull_request_id
2521 pr_id = pr.pull_request_id
2522 pr_repo = pr.target_repo.repo_name
2522 pr_repo = pr.target_repo.repo_name
2523 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2523 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2524
2524
2525 for stat in status_results:
2525 for stat in status_results:
2526 pr_id = pr_repo = None
2526 pr_id = pr_repo = None
2527 if stat.pull_request:
2527 if stat.pull_request:
2528 pr_id = stat.pull_request.pull_request_id
2528 pr_id = stat.pull_request.pull_request_id
2529 pr_repo = stat.pull_request.target_repo.repo_name
2529 pr_repo = stat.pull_request.target_repo.repo_name
2530 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2530 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2531 pr_id, pr_repo]
2531 pr_id, pr_repo]
2532 return grouped
2532 return grouped
2533
2533
2534 # ==========================================================================
2534 # ==========================================================================
2535 # SCM CACHE INSTANCE
2535 # SCM CACHE INSTANCE
2536 # ==========================================================================
2536 # ==========================================================================
2537
2537
2538 def scm_instance(self, **kwargs):
2538 def scm_instance(self, **kwargs):
2539 import rhodecode
2539 import rhodecode
2540
2540
2541 # Passing a config will not hit the cache currently only used
2541 # Passing a config will not hit the cache currently only used
2542 # for repo2dbmapper
2542 # for repo2dbmapper
2543 config = kwargs.pop('config', None)
2543 config = kwargs.pop('config', None)
2544 cache = kwargs.pop('cache', None)
2544 cache = kwargs.pop('cache', None)
2545 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2545 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2546 if vcs_full_cache is not None:
2546 if vcs_full_cache is not None:
2547 # allows override global config
2547 # allows override global config
2548 full_cache = vcs_full_cache
2548 full_cache = vcs_full_cache
2549 else:
2549 else:
2550 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2550 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2551 # if cache is NOT defined use default global, else we have a full
2551 # if cache is NOT defined use default global, else we have a full
2552 # control over cache behaviour
2552 # control over cache behaviour
2553 if cache is None and full_cache and not config:
2553 if cache is None and full_cache and not config:
2554 log.debug('Initializing pure cached instance for %s', self.repo_path)
2554 log.debug('Initializing pure cached instance for %s', self.repo_path)
2555 return self._get_instance_cached()
2555 return self._get_instance_cached()
2556
2556
2557 # cache here is sent to the "vcs server"
2557 # cache here is sent to the "vcs server"
2558 return self._get_instance(cache=bool(cache), config=config)
2558 return self._get_instance(cache=bool(cache), config=config)
2559
2559
2560 def _get_instance_cached(self):
2560 def _get_instance_cached(self):
2561 from rhodecode.lib import rc_cache
2561 from rhodecode.lib import rc_cache
2562
2562
2563 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2563 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2564 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2564 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2565 repo_id=self.repo_id)
2565 repo_id=self.repo_id)
2566 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2566 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2567
2567
2568 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2568 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2569 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2569 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2570 return self._get_instance(repo_state_uid=_cache_state_uid)
2570 return self._get_instance(repo_state_uid=_cache_state_uid)
2571
2571
2572 # we must use thread scoped cache here,
2572 # we must use thread scoped cache here,
2573 # because each thread of gevent needs it's own not shared connection and cache
2573 # because each thread of gevent needs it's own not shared connection and cache
2574 # we also alter `args` so the cache key is individual for every green thread.
2574 # we also alter `args` so the cache key is individual for every green thread.
2575 inv_context_manager = rc_cache.InvalidationContext(
2575 inv_context_manager = rc_cache.InvalidationContext(
2576 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2576 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2577 thread_scoped=True)
2577 thread_scoped=True)
2578 with inv_context_manager as invalidation_context:
2578 with inv_context_manager as invalidation_context:
2579 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2579 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2580 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2580 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2581
2581
2582 # re-compute and store cache if we get invalidate signal
2582 # re-compute and store cache if we get invalidate signal
2583 if invalidation_context.should_invalidate():
2583 if invalidation_context.should_invalidate():
2584 instance = get_instance_cached.refresh(*args)
2584 instance = get_instance_cached.refresh(*args)
2585 else:
2585 else:
2586 instance = get_instance_cached(*args)
2586 instance = get_instance_cached(*args)
2587
2587
2588 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2588 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2589 return instance
2589 return instance
2590
2590
2591 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2591 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2592 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2592 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2593 self.repo_type, self.repo_path, cache)
2593 self.repo_type, self.repo_path, cache)
2594 config = config or self._config
2594 config = config or self._config
2595 custom_wire = {
2595 custom_wire = {
2596 'cache': cache, # controls the vcs.remote cache
2596 'cache': cache, # controls the vcs.remote cache
2597 'repo_state_uid': repo_state_uid
2597 'repo_state_uid': repo_state_uid
2598 }
2598 }
2599 repo = get_vcs_instance(
2599 repo = get_vcs_instance(
2600 repo_path=safe_str(self.repo_full_path),
2600 repo_path=safe_str(self.repo_full_path),
2601 config=config,
2601 config=config,
2602 with_wire=custom_wire,
2602 with_wire=custom_wire,
2603 create=False,
2603 create=False,
2604 _vcs_alias=self.repo_type)
2604 _vcs_alias=self.repo_type)
2605 if repo is not None:
2605 if repo is not None:
2606 repo.count() # cache rebuild
2606 repo.count() # cache rebuild
2607 return repo
2607 return repo
2608
2608
2609 def get_shadow_repository_path(self, workspace_id):
2609 def get_shadow_repository_path(self, workspace_id):
2610 from rhodecode.lib.vcs.backends.base import BaseRepository
2610 from rhodecode.lib.vcs.backends.base import BaseRepository
2611 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2611 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2612 self.repo_full_path, self.repo_id, workspace_id)
2612 self.repo_full_path, self.repo_id, workspace_id)
2613 return shadow_repo_path
2613 return shadow_repo_path
2614
2614
2615 def __json__(self):
2615 def __json__(self):
2616 return {'landing_rev': self.landing_rev}
2616 return {'landing_rev': self.landing_rev}
2617
2617
2618 def get_dict(self):
2618 def get_dict(self):
2619
2619
2620 # Since we transformed `repo_name` to a hybrid property, we need to
2620 # Since we transformed `repo_name` to a hybrid property, we need to
2621 # keep compatibility with the code which uses `repo_name` field.
2621 # keep compatibility with the code which uses `repo_name` field.
2622
2622
2623 result = super(Repository, self).get_dict()
2623 result = super(Repository, self).get_dict()
2624 result['repo_name'] = result.pop('_repo_name', None)
2624 result['repo_name'] = result.pop('_repo_name', None)
2625 return result
2625 return result
2626
2626
2627
2627
2628 class RepoGroup(Base, BaseModel):
2628 class RepoGroup(Base, BaseModel):
2629 __tablename__ = 'groups'
2629 __tablename__ = 'groups'
2630 __table_args__ = (
2630 __table_args__ = (
2631 UniqueConstraint('group_name', 'group_parent_id'),
2631 UniqueConstraint('group_name', 'group_parent_id'),
2632 base_table_args,
2632 base_table_args,
2633 )
2633 )
2634 __mapper_args__ = {'order_by': 'group_name'}
2634 __mapper_args__ = {'order_by': 'group_name'}
2635
2635
2636 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2636 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2637
2637
2638 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2638 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2639 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2639 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2640 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2640 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2641 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2641 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2642 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2642 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2643 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2643 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2644 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2644 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2645 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2645 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2646 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2646 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2647 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2647 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2648 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2648 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2649
2649
2650 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2650 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2651 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2651 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2652 parent_group = relationship('RepoGroup', remote_side=group_id)
2652 parent_group = relationship('RepoGroup', remote_side=group_id)
2653 user = relationship('User')
2653 user = relationship('User')
2654 integrations = relationship('Integration', cascade="all, delete-orphan")
2654 integrations = relationship('Integration', cascade="all, delete-orphan")
2655
2655
2656 # no cascade, set NULL
2656 # no cascade, set NULL
2657 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2657 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2658
2658
2659 def __init__(self, group_name='', parent_group=None):
2659 def __init__(self, group_name='', parent_group=None):
2660 self.group_name = group_name
2660 self.group_name = group_name
2661 self.parent_group = parent_group
2661 self.parent_group = parent_group
2662
2662
2663 def __unicode__(self):
2663 def __unicode__(self):
2664 return u"<%s('id:%s:%s')>" % (
2664 return u"<%s('id:%s:%s')>" % (
2665 self.__class__.__name__, self.group_id, self.group_name)
2665 self.__class__.__name__, self.group_id, self.group_name)
2666
2666
2667 @hybrid_property
2667 @hybrid_property
2668 def group_name(self):
2668 def group_name(self):
2669 return self._group_name
2669 return self._group_name
2670
2670
2671 @group_name.setter
2671 @group_name.setter
2672 def group_name(self, value):
2672 def group_name(self, value):
2673 self._group_name = value
2673 self._group_name = value
2674 self.group_name_hash = self.hash_repo_group_name(value)
2674 self.group_name_hash = self.hash_repo_group_name(value)
2675
2675
2676 @classmethod
2676 @classmethod
2677 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2677 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2678 from rhodecode.lib.vcs.backends.base import EmptyCommit
2678 from rhodecode.lib.vcs.backends.base import EmptyCommit
2679 dummy = EmptyCommit().__json__()
2679 dummy = EmptyCommit().__json__()
2680 if not changeset_cache_raw:
2680 if not changeset_cache_raw:
2681 dummy['source_repo_id'] = repo_id
2681 dummy['source_repo_id'] = repo_id
2682 return json.loads(json.dumps(dummy))
2682 return json.loads(json.dumps(dummy))
2683
2683
2684 try:
2684 try:
2685 return json.loads(changeset_cache_raw)
2685 return json.loads(changeset_cache_raw)
2686 except TypeError:
2686 except TypeError:
2687 return dummy
2687 return dummy
2688 except Exception:
2688 except Exception:
2689 log.error(traceback.format_exc())
2689 log.error(traceback.format_exc())
2690 return dummy
2690 return dummy
2691
2691
2692 @hybrid_property
2692 @hybrid_property
2693 def changeset_cache(self):
2693 def changeset_cache(self):
2694 return self._load_changeset_cache('', self._changeset_cache)
2694 return self._load_changeset_cache('', self._changeset_cache)
2695
2695
2696 @changeset_cache.setter
2696 @changeset_cache.setter
2697 def changeset_cache(self, val):
2697 def changeset_cache(self, val):
2698 try:
2698 try:
2699 self._changeset_cache = json.dumps(val)
2699 self._changeset_cache = json.dumps(val)
2700 except Exception:
2700 except Exception:
2701 log.error(traceback.format_exc())
2701 log.error(traceback.format_exc())
2702
2702
2703 @validates('group_parent_id')
2703 @validates('group_parent_id')
2704 def validate_group_parent_id(self, key, val):
2704 def validate_group_parent_id(self, key, val):
2705 """
2705 """
2706 Check cycle references for a parent group to self
2706 Check cycle references for a parent group to self
2707 """
2707 """
2708 if self.group_id and val:
2708 if self.group_id and val:
2709 assert val != self.group_id
2709 assert val != self.group_id
2710
2710
2711 return val
2711 return val
2712
2712
2713 @hybrid_property
2713 @hybrid_property
2714 def description_safe(self):
2714 def description_safe(self):
2715 from rhodecode.lib import helpers as h
2715 from rhodecode.lib import helpers as h
2716 return h.escape(self.group_description)
2716 return h.escape(self.group_description)
2717
2717
2718 @classmethod
2718 @classmethod
2719 def hash_repo_group_name(cls, repo_group_name):
2719 def hash_repo_group_name(cls, repo_group_name):
2720 val = remove_formatting(repo_group_name)
2720 val = remove_formatting(repo_group_name)
2721 val = safe_str(val).lower()
2721 val = safe_str(val).lower()
2722 chars = []
2722 chars = []
2723 for c in val:
2723 for c in val:
2724 if c not in string.ascii_letters:
2724 if c not in string.ascii_letters:
2725 c = str(ord(c))
2725 c = str(ord(c))
2726 chars.append(c)
2726 chars.append(c)
2727
2727
2728 return ''.join(chars)
2728 return ''.join(chars)
2729
2729
2730 @classmethod
2730 @classmethod
2731 def _generate_choice(cls, repo_group):
2731 def _generate_choice(cls, repo_group):
2732 from webhelpers2.html import literal as _literal
2732 from webhelpers2.html import literal as _literal
2733 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2733 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2734 return repo_group.group_id, _name(repo_group.full_path_splitted)
2734 return repo_group.group_id, _name(repo_group.full_path_splitted)
2735
2735
2736 @classmethod
2736 @classmethod
2737 def groups_choices(cls, groups=None, show_empty_group=True):
2737 def groups_choices(cls, groups=None, show_empty_group=True):
2738 if not groups:
2738 if not groups:
2739 groups = cls.query().all()
2739 groups = cls.query().all()
2740
2740
2741 repo_groups = []
2741 repo_groups = []
2742 if show_empty_group:
2742 if show_empty_group:
2743 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2743 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2744
2744
2745 repo_groups.extend([cls._generate_choice(x) for x in groups])
2745 repo_groups.extend([cls._generate_choice(x) for x in groups])
2746
2746
2747 repo_groups = sorted(
2747 repo_groups = sorted(
2748 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2748 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2749 return repo_groups
2749 return repo_groups
2750
2750
2751 @classmethod
2751 @classmethod
2752 def url_sep(cls):
2752 def url_sep(cls):
2753 return URL_SEP
2753 return URL_SEP
2754
2754
2755 @classmethod
2755 @classmethod
2756 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2756 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2757 if case_insensitive:
2757 if case_insensitive:
2758 gr = cls.query().filter(func.lower(cls.group_name)
2758 gr = cls.query().filter(func.lower(cls.group_name)
2759 == func.lower(group_name))
2759 == func.lower(group_name))
2760 else:
2760 else:
2761 gr = cls.query().filter(cls.group_name == group_name)
2761 gr = cls.query().filter(cls.group_name == group_name)
2762 if cache:
2762 if cache:
2763 name_key = _hash_key(group_name)
2763 name_key = _hash_key(group_name)
2764 gr = gr.options(
2764 gr = gr.options(
2765 FromCache("sql_cache_short", "get_group_%s" % name_key))
2765 FromCache("sql_cache_short", "get_group_%s" % name_key))
2766 return gr.scalar()
2766 return gr.scalar()
2767
2767
2768 @classmethod
2768 @classmethod
2769 def get_user_personal_repo_group(cls, user_id):
2769 def get_user_personal_repo_group(cls, user_id):
2770 user = User.get(user_id)
2770 user = User.get(user_id)
2771 if user.username == User.DEFAULT_USER:
2771 if user.username == User.DEFAULT_USER:
2772 return None
2772 return None
2773
2773
2774 return cls.query()\
2774 return cls.query()\
2775 .filter(cls.personal == true()) \
2775 .filter(cls.personal == true()) \
2776 .filter(cls.user == user) \
2776 .filter(cls.user == user) \
2777 .order_by(cls.group_id.asc()) \
2777 .order_by(cls.group_id.asc()) \
2778 .first()
2778 .first()
2779
2779
2780 @classmethod
2780 @classmethod
2781 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2781 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2782 case_insensitive=True):
2782 case_insensitive=True):
2783 q = RepoGroup.query()
2783 q = RepoGroup.query()
2784
2784
2785 if not isinstance(user_id, Optional):
2785 if not isinstance(user_id, Optional):
2786 q = q.filter(RepoGroup.user_id == user_id)
2786 q = q.filter(RepoGroup.user_id == user_id)
2787
2787
2788 if not isinstance(group_id, Optional):
2788 if not isinstance(group_id, Optional):
2789 q = q.filter(RepoGroup.group_parent_id == group_id)
2789 q = q.filter(RepoGroup.group_parent_id == group_id)
2790
2790
2791 if case_insensitive:
2791 if case_insensitive:
2792 q = q.order_by(func.lower(RepoGroup.group_name))
2792 q = q.order_by(func.lower(RepoGroup.group_name))
2793 else:
2793 else:
2794 q = q.order_by(RepoGroup.group_name)
2794 q = q.order_by(RepoGroup.group_name)
2795 return q.all()
2795 return q.all()
2796
2796
2797 @property
2797 @property
2798 def parents(self, parents_recursion_limit=10):
2798 def parents(self, parents_recursion_limit=10):
2799 groups = []
2799 groups = []
2800 if self.parent_group is None:
2800 if self.parent_group is None:
2801 return groups
2801 return groups
2802 cur_gr = self.parent_group
2802 cur_gr = self.parent_group
2803 groups.insert(0, cur_gr)
2803 groups.insert(0, cur_gr)
2804 cnt = 0
2804 cnt = 0
2805 while 1:
2805 while 1:
2806 cnt += 1
2806 cnt += 1
2807 gr = getattr(cur_gr, 'parent_group', None)
2807 gr = getattr(cur_gr, 'parent_group', None)
2808 cur_gr = cur_gr.parent_group
2808 cur_gr = cur_gr.parent_group
2809 if gr is None:
2809 if gr is None:
2810 break
2810 break
2811 if cnt == parents_recursion_limit:
2811 if cnt == parents_recursion_limit:
2812 # this will prevent accidental infinit loops
2812 # this will prevent accidental infinit loops
2813 log.error('more than %s parents found for group %s, stopping '
2813 log.error('more than %s parents found for group %s, stopping '
2814 'recursive parent fetching', parents_recursion_limit, self)
2814 'recursive parent fetching', parents_recursion_limit, self)
2815 break
2815 break
2816
2816
2817 groups.insert(0, gr)
2817 groups.insert(0, gr)
2818 return groups
2818 return groups
2819
2819
2820 @property
2820 @property
2821 def last_commit_cache_update_diff(self):
2821 def last_commit_cache_update_diff(self):
2822 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2822 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2823
2823
2824 @classmethod
2824 @classmethod
2825 def _load_commit_change(cls, last_commit_cache):
2825 def _load_commit_change(cls, last_commit_cache):
2826 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2826 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2827 empty_date = datetime.datetime.fromtimestamp(0)
2827 empty_date = datetime.datetime.fromtimestamp(0)
2828 date_latest = last_commit_cache.get('date', empty_date)
2828 date_latest = last_commit_cache.get('date', empty_date)
2829 try:
2829 try:
2830 return parse_datetime(date_latest)
2830 return parse_datetime(date_latest)
2831 except Exception:
2831 except Exception:
2832 return empty_date
2832 return empty_date
2833
2833
2834 @property
2834 @property
2835 def last_commit_change(self):
2835 def last_commit_change(self):
2836 return self._load_commit_change(self.changeset_cache)
2836 return self._load_commit_change(self.changeset_cache)
2837
2837
2838 @property
2838 @property
2839 def last_db_change(self):
2839 def last_db_change(self):
2840 return self.updated_on
2840 return self.updated_on
2841
2841
2842 @property
2842 @property
2843 def children(self):
2843 def children(self):
2844 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2844 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2845
2845
2846 @property
2846 @property
2847 def name(self):
2847 def name(self):
2848 return self.group_name.split(RepoGroup.url_sep())[-1]
2848 return self.group_name.split(RepoGroup.url_sep())[-1]
2849
2849
2850 @property
2850 @property
2851 def full_path(self):
2851 def full_path(self):
2852 return self.group_name
2852 return self.group_name
2853
2853
2854 @property
2854 @property
2855 def full_path_splitted(self):
2855 def full_path_splitted(self):
2856 return self.group_name.split(RepoGroup.url_sep())
2856 return self.group_name.split(RepoGroup.url_sep())
2857
2857
2858 @property
2858 @property
2859 def repositories(self):
2859 def repositories(self):
2860 return Repository.query()\
2860 return Repository.query()\
2861 .filter(Repository.group == self)\
2861 .filter(Repository.group == self)\
2862 .order_by(Repository.repo_name)
2862 .order_by(Repository.repo_name)
2863
2863
2864 @property
2864 @property
2865 def repositories_recursive_count(self):
2865 def repositories_recursive_count(self):
2866 cnt = self.repositories.count()
2866 cnt = self.repositories.count()
2867
2867
2868 def children_count(group):
2868 def children_count(group):
2869 cnt = 0
2869 cnt = 0
2870 for child in group.children:
2870 for child in group.children:
2871 cnt += child.repositories.count()
2871 cnt += child.repositories.count()
2872 cnt += children_count(child)
2872 cnt += children_count(child)
2873 return cnt
2873 return cnt
2874
2874
2875 return cnt + children_count(self)
2875 return cnt + children_count(self)
2876
2876
2877 def _recursive_objects(self, include_repos=True, include_groups=True):
2877 def _recursive_objects(self, include_repos=True, include_groups=True):
2878 all_ = []
2878 all_ = []
2879
2879
2880 def _get_members(root_gr):
2880 def _get_members(root_gr):
2881 if include_repos:
2881 if include_repos:
2882 for r in root_gr.repositories:
2882 for r in root_gr.repositories:
2883 all_.append(r)
2883 all_.append(r)
2884 childs = root_gr.children.all()
2884 childs = root_gr.children.all()
2885 if childs:
2885 if childs:
2886 for gr in childs:
2886 for gr in childs:
2887 if include_groups:
2887 if include_groups:
2888 all_.append(gr)
2888 all_.append(gr)
2889 _get_members(gr)
2889 _get_members(gr)
2890
2890
2891 root_group = []
2891 root_group = []
2892 if include_groups:
2892 if include_groups:
2893 root_group = [self]
2893 root_group = [self]
2894
2894
2895 _get_members(self)
2895 _get_members(self)
2896 return root_group + all_
2896 return root_group + all_
2897
2897
2898 def recursive_groups_and_repos(self):
2898 def recursive_groups_and_repos(self):
2899 """
2899 """
2900 Recursive return all groups, with repositories in those groups
2900 Recursive return all groups, with repositories in those groups
2901 """
2901 """
2902 return self._recursive_objects()
2902 return self._recursive_objects()
2903
2903
2904 def recursive_groups(self):
2904 def recursive_groups(self):
2905 """
2905 """
2906 Returns all children groups for this group including children of children
2906 Returns all children groups for this group including children of children
2907 """
2907 """
2908 return self._recursive_objects(include_repos=False)
2908 return self._recursive_objects(include_repos=False)
2909
2909
2910 def recursive_repos(self):
2910 def recursive_repos(self):
2911 """
2911 """
2912 Returns all children repositories for this group
2912 Returns all children repositories for this group
2913 """
2913 """
2914 return self._recursive_objects(include_groups=False)
2914 return self._recursive_objects(include_groups=False)
2915
2915
2916 def get_new_name(self, group_name):
2916 def get_new_name(self, group_name):
2917 """
2917 """
2918 returns new full group name based on parent and new name
2918 returns new full group name based on parent and new name
2919
2919
2920 :param group_name:
2920 :param group_name:
2921 """
2921 """
2922 path_prefix = (self.parent_group.full_path_splitted if
2922 path_prefix = (self.parent_group.full_path_splitted if
2923 self.parent_group else [])
2923 self.parent_group else [])
2924 return RepoGroup.url_sep().join(path_prefix + [group_name])
2924 return RepoGroup.url_sep().join(path_prefix + [group_name])
2925
2925
2926 def update_commit_cache(self, config=None):
2926 def update_commit_cache(self, config=None):
2927 """
2927 """
2928 Update cache of last commit for newest repository inside this repository group.
2928 Update cache of last commit for newest repository inside this repository group.
2929 cache_keys should be::
2929 cache_keys should be::
2930
2930
2931 source_repo_id
2931 source_repo_id
2932 short_id
2932 short_id
2933 raw_id
2933 raw_id
2934 revision
2934 revision
2935 parents
2935 parents
2936 message
2936 message
2937 date
2937 date
2938 author
2938 author
2939
2939
2940 """
2940 """
2941 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2941 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2942 empty_date = datetime.datetime.fromtimestamp(0)
2942 empty_date = datetime.datetime.fromtimestamp(0)
2943
2943
2944 def repo_groups_and_repos(root_gr):
2944 def repo_groups_and_repos(root_gr):
2945 for _repo in root_gr.repositories:
2945 for _repo in root_gr.repositories:
2946 yield _repo
2946 yield _repo
2947 for child_group in root_gr.children.all():
2947 for child_group in root_gr.children.all():
2948 yield child_group
2948 yield child_group
2949
2949
2950 latest_repo_cs_cache = {}
2950 latest_repo_cs_cache = {}
2951 for obj in repo_groups_and_repos(self):
2951 for obj in repo_groups_and_repos(self):
2952 repo_cs_cache = obj.changeset_cache
2952 repo_cs_cache = obj.changeset_cache
2953 date_latest = latest_repo_cs_cache.get('date', empty_date)
2953 date_latest = latest_repo_cs_cache.get('date', empty_date)
2954 date_current = repo_cs_cache.get('date', empty_date)
2954 date_current = repo_cs_cache.get('date', empty_date)
2955 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2955 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2956 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2956 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2957 latest_repo_cs_cache = repo_cs_cache
2957 latest_repo_cs_cache = repo_cs_cache
2958 if hasattr(obj, 'repo_id'):
2958 if hasattr(obj, 'repo_id'):
2959 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2959 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2960 else:
2960 else:
2961 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2961 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2962
2962
2963 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2963 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2964
2964
2965 latest_repo_cs_cache['updated_on'] = time.time()
2965 latest_repo_cs_cache['updated_on'] = time.time()
2966 self.changeset_cache = latest_repo_cs_cache
2966 self.changeset_cache = latest_repo_cs_cache
2967 self.updated_on = _date_latest
2967 self.updated_on = _date_latest
2968 Session().add(self)
2968 Session().add(self)
2969 Session().commit()
2969 Session().commit()
2970
2970
2971 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2971 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2972 self.group_name, latest_repo_cs_cache, _date_latest)
2972 self.group_name, latest_repo_cs_cache, _date_latest)
2973
2973
2974 def permissions(self, with_admins=True, with_owner=True,
2974 def permissions(self, with_admins=True, with_owner=True,
2975 expand_from_user_groups=False):
2975 expand_from_user_groups=False):
2976 """
2976 """
2977 Permissions for repository groups
2977 Permissions for repository groups
2978 """
2978 """
2979 _admin_perm = 'group.admin'
2979 _admin_perm = 'group.admin'
2980
2980
2981 owner_row = []
2981 owner_row = []
2982 if with_owner:
2982 if with_owner:
2983 usr = AttributeDict(self.user.get_dict())
2983 usr = AttributeDict(self.user.get_dict())
2984 usr.owner_row = True
2984 usr.owner_row = True
2985 usr.permission = _admin_perm
2985 usr.permission = _admin_perm
2986 owner_row.append(usr)
2986 owner_row.append(usr)
2987
2987
2988 super_admin_ids = []
2988 super_admin_ids = []
2989 super_admin_rows = []
2989 super_admin_rows = []
2990 if with_admins:
2990 if with_admins:
2991 for usr in User.get_all_super_admins():
2991 for usr in User.get_all_super_admins():
2992 super_admin_ids.append(usr.user_id)
2992 super_admin_ids.append(usr.user_id)
2993 # if this admin is also owner, don't double the record
2993 # if this admin is also owner, don't double the record
2994 if usr.user_id == owner_row[0].user_id:
2994 if usr.user_id == owner_row[0].user_id:
2995 owner_row[0].admin_row = True
2995 owner_row[0].admin_row = True
2996 else:
2996 else:
2997 usr = AttributeDict(usr.get_dict())
2997 usr = AttributeDict(usr.get_dict())
2998 usr.admin_row = True
2998 usr.admin_row = True
2999 usr.permission = _admin_perm
2999 usr.permission = _admin_perm
3000 super_admin_rows.append(usr)
3000 super_admin_rows.append(usr)
3001
3001
3002 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3002 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3003 q = q.options(joinedload(UserRepoGroupToPerm.group),
3003 q = q.options(joinedload(UserRepoGroupToPerm.group),
3004 joinedload(UserRepoGroupToPerm.user),
3004 joinedload(UserRepoGroupToPerm.user),
3005 joinedload(UserRepoGroupToPerm.permission),)
3005 joinedload(UserRepoGroupToPerm.permission),)
3006
3006
3007 # get owners and admins and permissions. We do a trick of re-writing
3007 # get owners and admins and permissions. We do a trick of re-writing
3008 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3008 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3009 # has a global reference and changing one object propagates to all
3009 # has a global reference and changing one object propagates to all
3010 # others. This means if admin is also an owner admin_row that change
3010 # others. This means if admin is also an owner admin_row that change
3011 # would propagate to both objects
3011 # would propagate to both objects
3012 perm_rows = []
3012 perm_rows = []
3013 for _usr in q.all():
3013 for _usr in q.all():
3014 usr = AttributeDict(_usr.user.get_dict())
3014 usr = AttributeDict(_usr.user.get_dict())
3015 # if this user is also owner/admin, mark as duplicate record
3015 # if this user is also owner/admin, mark as duplicate record
3016 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3016 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3017 usr.duplicate_perm = True
3017 usr.duplicate_perm = True
3018 usr.permission = _usr.permission.permission_name
3018 usr.permission = _usr.permission.permission_name
3019 perm_rows.append(usr)
3019 perm_rows.append(usr)
3020
3020
3021 # filter the perm rows by 'default' first and then sort them by
3021 # filter the perm rows by 'default' first and then sort them by
3022 # admin,write,read,none permissions sorted again alphabetically in
3022 # admin,write,read,none permissions sorted again alphabetically in
3023 # each group
3023 # each group
3024 perm_rows = sorted(perm_rows, key=display_user_sort)
3024 perm_rows = sorted(perm_rows, key=display_user_sort)
3025
3025
3026 user_groups_rows = []
3026 user_groups_rows = []
3027 if expand_from_user_groups:
3027 if expand_from_user_groups:
3028 for ug in self.permission_user_groups(with_members=True):
3028 for ug in self.permission_user_groups(with_members=True):
3029 for user_data in ug.members:
3029 for user_data in ug.members:
3030 user_groups_rows.append(user_data)
3030 user_groups_rows.append(user_data)
3031
3031
3032 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3032 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3033
3033
3034 def permission_user_groups(self, with_members=False):
3034 def permission_user_groups(self, with_members=False):
3035 q = UserGroupRepoGroupToPerm.query()\
3035 q = UserGroupRepoGroupToPerm.query()\
3036 .filter(UserGroupRepoGroupToPerm.group == self)
3036 .filter(UserGroupRepoGroupToPerm.group == self)
3037 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3037 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3038 joinedload(UserGroupRepoGroupToPerm.users_group),
3038 joinedload(UserGroupRepoGroupToPerm.users_group),
3039 joinedload(UserGroupRepoGroupToPerm.permission),)
3039 joinedload(UserGroupRepoGroupToPerm.permission),)
3040
3040
3041 perm_rows = []
3041 perm_rows = []
3042 for _user_group in q.all():
3042 for _user_group in q.all():
3043 entry = AttributeDict(_user_group.users_group.get_dict())
3043 entry = AttributeDict(_user_group.users_group.get_dict())
3044 entry.permission = _user_group.permission.permission_name
3044 entry.permission = _user_group.permission.permission_name
3045 if with_members:
3045 if with_members:
3046 entry.members = [x.user.get_dict()
3046 entry.members = [x.user.get_dict()
3047 for x in _user_group.users_group.members]
3047 for x in _user_group.users_group.members]
3048 perm_rows.append(entry)
3048 perm_rows.append(entry)
3049
3049
3050 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3050 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3051 return perm_rows
3051 return perm_rows
3052
3052
3053 def get_api_data(self):
3053 def get_api_data(self):
3054 """
3054 """
3055 Common function for generating api data
3055 Common function for generating api data
3056
3056
3057 """
3057 """
3058 group = self
3058 group = self
3059 data = {
3059 data = {
3060 'group_id': group.group_id,
3060 'group_id': group.group_id,
3061 'group_name': group.group_name,
3061 'group_name': group.group_name,
3062 'group_description': group.description_safe,
3062 'group_description': group.description_safe,
3063 'parent_group': group.parent_group.group_name if group.parent_group else None,
3063 'parent_group': group.parent_group.group_name if group.parent_group else None,
3064 'repositories': [x.repo_name for x in group.repositories],
3064 'repositories': [x.repo_name for x in group.repositories],
3065 'owner': group.user.username,
3065 'owner': group.user.username,
3066 }
3066 }
3067 return data
3067 return data
3068
3068
3069 def get_dict(self):
3069 def get_dict(self):
3070 # Since we transformed `group_name` to a hybrid property, we need to
3070 # Since we transformed `group_name` to a hybrid property, we need to
3071 # keep compatibility with the code which uses `group_name` field.
3071 # keep compatibility with the code which uses `group_name` field.
3072 result = super(RepoGroup, self).get_dict()
3072 result = super(RepoGroup, self).get_dict()
3073 result['group_name'] = result.pop('_group_name', None)
3073 result['group_name'] = result.pop('_group_name', None)
3074 return result
3074 return result
3075
3075
3076
3076
3077 class Permission(Base, BaseModel):
3077 class Permission(Base, BaseModel):
3078 __tablename__ = 'permissions'
3078 __tablename__ = 'permissions'
3079 __table_args__ = (
3079 __table_args__ = (
3080 Index('p_perm_name_idx', 'permission_name'),
3080 Index('p_perm_name_idx', 'permission_name'),
3081 base_table_args,
3081 base_table_args,
3082 )
3082 )
3083
3083
3084 PERMS = [
3084 PERMS = [
3085 ('hg.admin', _('RhodeCode Super Administrator')),
3085 ('hg.admin', _('RhodeCode Super Administrator')),
3086
3086
3087 ('repository.none', _('Repository no access')),
3087 ('repository.none', _('Repository no access')),
3088 ('repository.read', _('Repository read access')),
3088 ('repository.read', _('Repository read access')),
3089 ('repository.write', _('Repository write access')),
3089 ('repository.write', _('Repository write access')),
3090 ('repository.admin', _('Repository admin access')),
3090 ('repository.admin', _('Repository admin access')),
3091
3091
3092 ('group.none', _('Repository group no access')),
3092 ('group.none', _('Repository group no access')),
3093 ('group.read', _('Repository group read access')),
3093 ('group.read', _('Repository group read access')),
3094 ('group.write', _('Repository group write access')),
3094 ('group.write', _('Repository group write access')),
3095 ('group.admin', _('Repository group admin access')),
3095 ('group.admin', _('Repository group admin access')),
3096
3096
3097 ('usergroup.none', _('User group no access')),
3097 ('usergroup.none', _('User group no access')),
3098 ('usergroup.read', _('User group read access')),
3098 ('usergroup.read', _('User group read access')),
3099 ('usergroup.write', _('User group write access')),
3099 ('usergroup.write', _('User group write access')),
3100 ('usergroup.admin', _('User group admin access')),
3100 ('usergroup.admin', _('User group admin access')),
3101
3101
3102 ('branch.none', _('Branch no permissions')),
3102 ('branch.none', _('Branch no permissions')),
3103 ('branch.merge', _('Branch access by web merge')),
3103 ('branch.merge', _('Branch access by web merge')),
3104 ('branch.push', _('Branch access by push')),
3104 ('branch.push', _('Branch access by push')),
3105 ('branch.push_force', _('Branch access by push with force')),
3105 ('branch.push_force', _('Branch access by push with force')),
3106
3106
3107 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3107 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3108 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3108 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3109
3109
3110 ('hg.usergroup.create.false', _('User Group creation disabled')),
3110 ('hg.usergroup.create.false', _('User Group creation disabled')),
3111 ('hg.usergroup.create.true', _('User Group creation enabled')),
3111 ('hg.usergroup.create.true', _('User Group creation enabled')),
3112
3112
3113 ('hg.create.none', _('Repository creation disabled')),
3113 ('hg.create.none', _('Repository creation disabled')),
3114 ('hg.create.repository', _('Repository creation enabled')),
3114 ('hg.create.repository', _('Repository creation enabled')),
3115 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3115 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3116 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3116 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3117
3117
3118 ('hg.fork.none', _('Repository forking disabled')),
3118 ('hg.fork.none', _('Repository forking disabled')),
3119 ('hg.fork.repository', _('Repository forking enabled')),
3119 ('hg.fork.repository', _('Repository forking enabled')),
3120
3120
3121 ('hg.register.none', _('Registration disabled')),
3121 ('hg.register.none', _('Registration disabled')),
3122 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3122 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3123 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3123 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3124
3124
3125 ('hg.password_reset.enabled', _('Password reset enabled')),
3125 ('hg.password_reset.enabled', _('Password reset enabled')),
3126 ('hg.password_reset.hidden', _('Password reset hidden')),
3126 ('hg.password_reset.hidden', _('Password reset hidden')),
3127 ('hg.password_reset.disabled', _('Password reset disabled')),
3127 ('hg.password_reset.disabled', _('Password reset disabled')),
3128
3128
3129 ('hg.extern_activate.manual', _('Manual activation of external account')),
3129 ('hg.extern_activate.manual', _('Manual activation of external account')),
3130 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3130 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3131
3131
3132 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3132 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3133 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3133 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3134 ]
3134 ]
3135
3135
3136 # definition of system default permissions for DEFAULT user, created on
3136 # definition of system default permissions for DEFAULT user, created on
3137 # system setup
3137 # system setup
3138 DEFAULT_USER_PERMISSIONS = [
3138 DEFAULT_USER_PERMISSIONS = [
3139 # object perms
3139 # object perms
3140 'repository.read',
3140 'repository.read',
3141 'group.read',
3141 'group.read',
3142 'usergroup.read',
3142 'usergroup.read',
3143 # branch, for backward compat we need same value as before so forced pushed
3143 # branch, for backward compat we need same value as before so forced pushed
3144 'branch.push_force',
3144 'branch.push_force',
3145 # global
3145 # global
3146 'hg.create.repository',
3146 'hg.create.repository',
3147 'hg.repogroup.create.false',
3147 'hg.repogroup.create.false',
3148 'hg.usergroup.create.false',
3148 'hg.usergroup.create.false',
3149 'hg.create.write_on_repogroup.true',
3149 'hg.create.write_on_repogroup.true',
3150 'hg.fork.repository',
3150 'hg.fork.repository',
3151 'hg.register.manual_activate',
3151 'hg.register.manual_activate',
3152 'hg.password_reset.enabled',
3152 'hg.password_reset.enabled',
3153 'hg.extern_activate.auto',
3153 'hg.extern_activate.auto',
3154 'hg.inherit_default_perms.true',
3154 'hg.inherit_default_perms.true',
3155 ]
3155 ]
3156
3156
3157 # defines which permissions are more important higher the more important
3157 # defines which permissions are more important higher the more important
3158 # Weight defines which permissions are more important.
3158 # Weight defines which permissions are more important.
3159 # The higher number the more important.
3159 # The higher number the more important.
3160 PERM_WEIGHTS = {
3160 PERM_WEIGHTS = {
3161 'repository.none': 0,
3161 'repository.none': 0,
3162 'repository.read': 1,
3162 'repository.read': 1,
3163 'repository.write': 3,
3163 'repository.write': 3,
3164 'repository.admin': 4,
3164 'repository.admin': 4,
3165
3165
3166 'group.none': 0,
3166 'group.none': 0,
3167 'group.read': 1,
3167 'group.read': 1,
3168 'group.write': 3,
3168 'group.write': 3,
3169 'group.admin': 4,
3169 'group.admin': 4,
3170
3170
3171 'usergroup.none': 0,
3171 'usergroup.none': 0,
3172 'usergroup.read': 1,
3172 'usergroup.read': 1,
3173 'usergroup.write': 3,
3173 'usergroup.write': 3,
3174 'usergroup.admin': 4,
3174 'usergroup.admin': 4,
3175
3175
3176 'branch.none': 0,
3176 'branch.none': 0,
3177 'branch.merge': 1,
3177 'branch.merge': 1,
3178 'branch.push': 3,
3178 'branch.push': 3,
3179 'branch.push_force': 4,
3179 'branch.push_force': 4,
3180
3180
3181 'hg.repogroup.create.false': 0,
3181 'hg.repogroup.create.false': 0,
3182 'hg.repogroup.create.true': 1,
3182 'hg.repogroup.create.true': 1,
3183
3183
3184 'hg.usergroup.create.false': 0,
3184 'hg.usergroup.create.false': 0,
3185 'hg.usergroup.create.true': 1,
3185 'hg.usergroup.create.true': 1,
3186
3186
3187 'hg.fork.none': 0,
3187 'hg.fork.none': 0,
3188 'hg.fork.repository': 1,
3188 'hg.fork.repository': 1,
3189 'hg.create.none': 0,
3189 'hg.create.none': 0,
3190 'hg.create.repository': 1
3190 'hg.create.repository': 1
3191 }
3191 }
3192
3192
3193 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3193 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3194 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3194 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3195 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3195 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3196
3196
3197 def __unicode__(self):
3197 def __unicode__(self):
3198 return u"<%s('%s:%s')>" % (
3198 return u"<%s('%s:%s')>" % (
3199 self.__class__.__name__, self.permission_id, self.permission_name
3199 self.__class__.__name__, self.permission_id, self.permission_name
3200 )
3200 )
3201
3201
3202 @classmethod
3202 @classmethod
3203 def get_by_key(cls, key):
3203 def get_by_key(cls, key):
3204 return cls.query().filter(cls.permission_name == key).scalar()
3204 return cls.query().filter(cls.permission_name == key).scalar()
3205
3205
3206 @classmethod
3206 @classmethod
3207 def get_default_repo_perms(cls, user_id, repo_id=None):
3207 def get_default_repo_perms(cls, user_id, repo_id=None):
3208 q = Session().query(UserRepoToPerm, Repository, Permission)\
3208 q = Session().query(UserRepoToPerm, Repository, Permission)\
3209 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3209 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3210 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3210 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3211 .filter(UserRepoToPerm.user_id == user_id)
3211 .filter(UserRepoToPerm.user_id == user_id)
3212 if repo_id:
3212 if repo_id:
3213 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3213 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3214 return q.all()
3214 return q.all()
3215
3215
3216 @classmethod
3216 @classmethod
3217 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3217 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3218 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3218 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3219 .join(
3219 .join(
3220 Permission,
3220 Permission,
3221 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3221 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3222 .join(
3222 .join(
3223 UserRepoToPerm,
3223 UserRepoToPerm,
3224 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3224 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3225 .filter(UserRepoToPerm.user_id == user_id)
3225 .filter(UserRepoToPerm.user_id == user_id)
3226
3226
3227 if repo_id:
3227 if repo_id:
3228 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3228 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3229 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3229 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3230
3230
3231 @classmethod
3231 @classmethod
3232 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3232 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3233 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3233 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3234 .join(
3234 .join(
3235 Permission,
3235 Permission,
3236 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3236 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3237 .join(
3237 .join(
3238 Repository,
3238 Repository,
3239 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3239 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3240 .join(
3240 .join(
3241 UserGroup,
3241 UserGroup,
3242 UserGroupRepoToPerm.users_group_id ==
3242 UserGroupRepoToPerm.users_group_id ==
3243 UserGroup.users_group_id)\
3243 UserGroup.users_group_id)\
3244 .join(
3244 .join(
3245 UserGroupMember,
3245 UserGroupMember,
3246 UserGroupRepoToPerm.users_group_id ==
3246 UserGroupRepoToPerm.users_group_id ==
3247 UserGroupMember.users_group_id)\
3247 UserGroupMember.users_group_id)\
3248 .filter(
3248 .filter(
3249 UserGroupMember.user_id == user_id,
3249 UserGroupMember.user_id == user_id,
3250 UserGroup.users_group_active == true())
3250 UserGroup.users_group_active == true())
3251 if repo_id:
3251 if repo_id:
3252 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3252 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3253 return q.all()
3253 return q.all()
3254
3254
3255 @classmethod
3255 @classmethod
3256 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3256 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3257 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3257 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3258 .join(
3258 .join(
3259 Permission,
3259 Permission,
3260 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3260 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3261 .join(
3261 .join(
3262 UserGroupRepoToPerm,
3262 UserGroupRepoToPerm,
3263 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3263 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3264 .join(
3264 .join(
3265 UserGroup,
3265 UserGroup,
3266 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3266 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3267 .join(
3267 .join(
3268 UserGroupMember,
3268 UserGroupMember,
3269 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3269 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3270 .filter(
3270 .filter(
3271 UserGroupMember.user_id == user_id,
3271 UserGroupMember.user_id == user_id,
3272 UserGroup.users_group_active == true())
3272 UserGroup.users_group_active == true())
3273
3273
3274 if repo_id:
3274 if repo_id:
3275 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3275 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3276 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3276 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3277
3277
3278 @classmethod
3278 @classmethod
3279 def get_default_group_perms(cls, user_id, repo_group_id=None):
3279 def get_default_group_perms(cls, user_id, repo_group_id=None):
3280 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3280 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3281 .join(
3281 .join(
3282 Permission,
3282 Permission,
3283 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3283 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3284 .join(
3284 .join(
3285 RepoGroup,
3285 RepoGroup,
3286 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3286 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3287 .filter(UserRepoGroupToPerm.user_id == user_id)
3287 .filter(UserRepoGroupToPerm.user_id == user_id)
3288 if repo_group_id:
3288 if repo_group_id:
3289 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3289 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3290 return q.all()
3290 return q.all()
3291
3291
3292 @classmethod
3292 @classmethod
3293 def get_default_group_perms_from_user_group(
3293 def get_default_group_perms_from_user_group(
3294 cls, user_id, repo_group_id=None):
3294 cls, user_id, repo_group_id=None):
3295 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3295 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3296 .join(
3296 .join(
3297 Permission,
3297 Permission,
3298 UserGroupRepoGroupToPerm.permission_id ==
3298 UserGroupRepoGroupToPerm.permission_id ==
3299 Permission.permission_id)\
3299 Permission.permission_id)\
3300 .join(
3300 .join(
3301 RepoGroup,
3301 RepoGroup,
3302 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3302 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3303 .join(
3303 .join(
3304 UserGroup,
3304 UserGroup,
3305 UserGroupRepoGroupToPerm.users_group_id ==
3305 UserGroupRepoGroupToPerm.users_group_id ==
3306 UserGroup.users_group_id)\
3306 UserGroup.users_group_id)\
3307 .join(
3307 .join(
3308 UserGroupMember,
3308 UserGroupMember,
3309 UserGroupRepoGroupToPerm.users_group_id ==
3309 UserGroupRepoGroupToPerm.users_group_id ==
3310 UserGroupMember.users_group_id)\
3310 UserGroupMember.users_group_id)\
3311 .filter(
3311 .filter(
3312 UserGroupMember.user_id == user_id,
3312 UserGroupMember.user_id == user_id,
3313 UserGroup.users_group_active == true())
3313 UserGroup.users_group_active == true())
3314 if repo_group_id:
3314 if repo_group_id:
3315 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3315 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3316 return q.all()
3316 return q.all()
3317
3317
3318 @classmethod
3318 @classmethod
3319 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3319 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3320 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3320 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3321 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3321 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3322 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3322 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3323 .filter(UserUserGroupToPerm.user_id == user_id)
3323 .filter(UserUserGroupToPerm.user_id == user_id)
3324 if user_group_id:
3324 if user_group_id:
3325 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3325 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3326 return q.all()
3326 return q.all()
3327
3327
3328 @classmethod
3328 @classmethod
3329 def get_default_user_group_perms_from_user_group(
3329 def get_default_user_group_perms_from_user_group(
3330 cls, user_id, user_group_id=None):
3330 cls, user_id, user_group_id=None):
3331 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3331 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3332 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3332 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3333 .join(
3333 .join(
3334 Permission,
3334 Permission,
3335 UserGroupUserGroupToPerm.permission_id ==
3335 UserGroupUserGroupToPerm.permission_id ==
3336 Permission.permission_id)\
3336 Permission.permission_id)\
3337 .join(
3337 .join(
3338 TargetUserGroup,
3338 TargetUserGroup,
3339 UserGroupUserGroupToPerm.target_user_group_id ==
3339 UserGroupUserGroupToPerm.target_user_group_id ==
3340 TargetUserGroup.users_group_id)\
3340 TargetUserGroup.users_group_id)\
3341 .join(
3341 .join(
3342 UserGroup,
3342 UserGroup,
3343 UserGroupUserGroupToPerm.user_group_id ==
3343 UserGroupUserGroupToPerm.user_group_id ==
3344 UserGroup.users_group_id)\
3344 UserGroup.users_group_id)\
3345 .join(
3345 .join(
3346 UserGroupMember,
3346 UserGroupMember,
3347 UserGroupUserGroupToPerm.user_group_id ==
3347 UserGroupUserGroupToPerm.user_group_id ==
3348 UserGroupMember.users_group_id)\
3348 UserGroupMember.users_group_id)\
3349 .filter(
3349 .filter(
3350 UserGroupMember.user_id == user_id,
3350 UserGroupMember.user_id == user_id,
3351 UserGroup.users_group_active == true())
3351 UserGroup.users_group_active == true())
3352 if user_group_id:
3352 if user_group_id:
3353 q = q.filter(
3353 q = q.filter(
3354 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3354 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3355
3355
3356 return q.all()
3356 return q.all()
3357
3357
3358
3358
3359 class UserRepoToPerm(Base, BaseModel):
3359 class UserRepoToPerm(Base, BaseModel):
3360 __tablename__ = 'repo_to_perm'
3360 __tablename__ = 'repo_to_perm'
3361 __table_args__ = (
3361 __table_args__ = (
3362 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3362 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3363 base_table_args
3363 base_table_args
3364 )
3364 )
3365
3365
3366 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3366 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3367 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3367 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3368 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3368 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3369 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3369 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3370
3370
3371 user = relationship('User')
3371 user = relationship('User')
3372 repository = relationship('Repository')
3372 repository = relationship('Repository')
3373 permission = relationship('Permission')
3373 permission = relationship('Permission')
3374
3374
3375 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3375 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3376
3376
3377 @classmethod
3377 @classmethod
3378 def create(cls, user, repository, permission):
3378 def create(cls, user, repository, permission):
3379 n = cls()
3379 n = cls()
3380 n.user = user
3380 n.user = user
3381 n.repository = repository
3381 n.repository = repository
3382 n.permission = permission
3382 n.permission = permission
3383 Session().add(n)
3383 Session().add(n)
3384 return n
3384 return n
3385
3385
3386 def __unicode__(self):
3386 def __unicode__(self):
3387 return u'<%s => %s >' % (self.user, self.repository)
3387 return u'<%s => %s >' % (self.user, self.repository)
3388
3388
3389
3389
3390 class UserUserGroupToPerm(Base, BaseModel):
3390 class UserUserGroupToPerm(Base, BaseModel):
3391 __tablename__ = 'user_user_group_to_perm'
3391 __tablename__ = 'user_user_group_to_perm'
3392 __table_args__ = (
3392 __table_args__ = (
3393 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3393 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3394 base_table_args
3394 base_table_args
3395 )
3395 )
3396
3396
3397 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3397 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3398 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3398 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3399 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3399 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3400 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3400 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3401
3401
3402 user = relationship('User')
3402 user = relationship('User')
3403 user_group = relationship('UserGroup')
3403 user_group = relationship('UserGroup')
3404 permission = relationship('Permission')
3404 permission = relationship('Permission')
3405
3405
3406 @classmethod
3406 @classmethod
3407 def create(cls, user, user_group, permission):
3407 def create(cls, user, user_group, permission):
3408 n = cls()
3408 n = cls()
3409 n.user = user
3409 n.user = user
3410 n.user_group = user_group
3410 n.user_group = user_group
3411 n.permission = permission
3411 n.permission = permission
3412 Session().add(n)
3412 Session().add(n)
3413 return n
3413 return n
3414
3414
3415 def __unicode__(self):
3415 def __unicode__(self):
3416 return u'<%s => %s >' % (self.user, self.user_group)
3416 return u'<%s => %s >' % (self.user, self.user_group)
3417
3417
3418
3418
3419 class UserToPerm(Base, BaseModel):
3419 class UserToPerm(Base, BaseModel):
3420 __tablename__ = 'user_to_perm'
3420 __tablename__ = 'user_to_perm'
3421 __table_args__ = (
3421 __table_args__ = (
3422 UniqueConstraint('user_id', 'permission_id'),
3422 UniqueConstraint('user_id', 'permission_id'),
3423 base_table_args
3423 base_table_args
3424 )
3424 )
3425
3425
3426 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3426 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3427 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3427 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3428 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3428 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3429
3429
3430 user = relationship('User')
3430 user = relationship('User')
3431 permission = relationship('Permission', lazy='joined')
3431 permission = relationship('Permission', lazy='joined')
3432
3432
3433 def __unicode__(self):
3433 def __unicode__(self):
3434 return u'<%s => %s >' % (self.user, self.permission)
3434 return u'<%s => %s >' % (self.user, self.permission)
3435
3435
3436
3436
3437 class UserGroupRepoToPerm(Base, BaseModel):
3437 class UserGroupRepoToPerm(Base, BaseModel):
3438 __tablename__ = 'users_group_repo_to_perm'
3438 __tablename__ = 'users_group_repo_to_perm'
3439 __table_args__ = (
3439 __table_args__ = (
3440 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3440 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3441 base_table_args
3441 base_table_args
3442 )
3442 )
3443
3443
3444 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3444 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3445 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3445 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3446 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3446 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3447 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3447 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3448
3448
3449 users_group = relationship('UserGroup')
3449 users_group = relationship('UserGroup')
3450 permission = relationship('Permission')
3450 permission = relationship('Permission')
3451 repository = relationship('Repository')
3451 repository = relationship('Repository')
3452 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3452 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3453
3453
3454 @classmethod
3454 @classmethod
3455 def create(cls, users_group, repository, permission):
3455 def create(cls, users_group, repository, permission):
3456 n = cls()
3456 n = cls()
3457 n.users_group = users_group
3457 n.users_group = users_group
3458 n.repository = repository
3458 n.repository = repository
3459 n.permission = permission
3459 n.permission = permission
3460 Session().add(n)
3460 Session().add(n)
3461 return n
3461 return n
3462
3462
3463 def __unicode__(self):
3463 def __unicode__(self):
3464 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3464 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3465
3465
3466
3466
3467 class UserGroupUserGroupToPerm(Base, BaseModel):
3467 class UserGroupUserGroupToPerm(Base, BaseModel):
3468 __tablename__ = 'user_group_user_group_to_perm'
3468 __tablename__ = 'user_group_user_group_to_perm'
3469 __table_args__ = (
3469 __table_args__ = (
3470 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3470 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3471 CheckConstraint('target_user_group_id != user_group_id'),
3471 CheckConstraint('target_user_group_id != user_group_id'),
3472 base_table_args
3472 base_table_args
3473 )
3473 )
3474
3474
3475 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)
3475 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)
3476 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3476 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3477 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3477 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3478 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3478 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3479
3479
3480 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3480 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3481 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3481 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3482 permission = relationship('Permission')
3482 permission = relationship('Permission')
3483
3483
3484 @classmethod
3484 @classmethod
3485 def create(cls, target_user_group, user_group, permission):
3485 def create(cls, target_user_group, user_group, permission):
3486 n = cls()
3486 n = cls()
3487 n.target_user_group = target_user_group
3487 n.target_user_group = target_user_group
3488 n.user_group = user_group
3488 n.user_group = user_group
3489 n.permission = permission
3489 n.permission = permission
3490 Session().add(n)
3490 Session().add(n)
3491 return n
3491 return n
3492
3492
3493 def __unicode__(self):
3493 def __unicode__(self):
3494 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3494 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3495
3495
3496
3496
3497 class UserGroupToPerm(Base, BaseModel):
3497 class UserGroupToPerm(Base, BaseModel):
3498 __tablename__ = 'users_group_to_perm'
3498 __tablename__ = 'users_group_to_perm'
3499 __table_args__ = (
3499 __table_args__ = (
3500 UniqueConstraint('users_group_id', 'permission_id',),
3500 UniqueConstraint('users_group_id', 'permission_id',),
3501 base_table_args
3501 base_table_args
3502 )
3502 )
3503
3503
3504 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3504 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3505 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3505 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3506 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3506 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3507
3507
3508 users_group = relationship('UserGroup')
3508 users_group = relationship('UserGroup')
3509 permission = relationship('Permission')
3509 permission = relationship('Permission')
3510
3510
3511
3511
3512 class UserRepoGroupToPerm(Base, BaseModel):
3512 class UserRepoGroupToPerm(Base, BaseModel):
3513 __tablename__ = 'user_repo_group_to_perm'
3513 __tablename__ = 'user_repo_group_to_perm'
3514 __table_args__ = (
3514 __table_args__ = (
3515 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3515 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3516 base_table_args
3516 base_table_args
3517 )
3517 )
3518
3518
3519 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3519 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3520 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3520 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3521 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3521 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3522 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3522 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3523
3523
3524 user = relationship('User')
3524 user = relationship('User')
3525 group = relationship('RepoGroup')
3525 group = relationship('RepoGroup')
3526 permission = relationship('Permission')
3526 permission = relationship('Permission')
3527
3527
3528 @classmethod
3528 @classmethod
3529 def create(cls, user, repository_group, permission):
3529 def create(cls, user, repository_group, permission):
3530 n = cls()
3530 n = cls()
3531 n.user = user
3531 n.user = user
3532 n.group = repository_group
3532 n.group = repository_group
3533 n.permission = permission
3533 n.permission = permission
3534 Session().add(n)
3534 Session().add(n)
3535 return n
3535 return n
3536
3536
3537
3537
3538 class UserGroupRepoGroupToPerm(Base, BaseModel):
3538 class UserGroupRepoGroupToPerm(Base, BaseModel):
3539 __tablename__ = 'users_group_repo_group_to_perm'
3539 __tablename__ = 'users_group_repo_group_to_perm'
3540 __table_args__ = (
3540 __table_args__ = (
3541 UniqueConstraint('users_group_id', 'group_id'),
3541 UniqueConstraint('users_group_id', 'group_id'),
3542 base_table_args
3542 base_table_args
3543 )
3543 )
3544
3544
3545 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)
3545 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)
3546 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3546 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3547 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3547 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3548 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3548 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3549
3549
3550 users_group = relationship('UserGroup')
3550 users_group = relationship('UserGroup')
3551 permission = relationship('Permission')
3551 permission = relationship('Permission')
3552 group = relationship('RepoGroup')
3552 group = relationship('RepoGroup')
3553
3553
3554 @classmethod
3554 @classmethod
3555 def create(cls, user_group, repository_group, permission):
3555 def create(cls, user_group, repository_group, permission):
3556 n = cls()
3556 n = cls()
3557 n.users_group = user_group
3557 n.users_group = user_group
3558 n.group = repository_group
3558 n.group = repository_group
3559 n.permission = permission
3559 n.permission = permission
3560 Session().add(n)
3560 Session().add(n)
3561 return n
3561 return n
3562
3562
3563 def __unicode__(self):
3563 def __unicode__(self):
3564 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3564 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3565
3565
3566
3566
3567 class Statistics(Base, BaseModel):
3567 class Statistics(Base, BaseModel):
3568 __tablename__ = 'statistics'
3568 __tablename__ = 'statistics'
3569 __table_args__ = (
3569 __table_args__ = (
3570 base_table_args
3570 base_table_args
3571 )
3571 )
3572
3572
3573 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3573 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3574 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3574 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3575 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3575 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3576 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3576 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3577 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3577 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3578 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3578 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3579
3579
3580 repository = relationship('Repository', single_parent=True)
3580 repository = relationship('Repository', single_parent=True)
3581
3581
3582
3582
3583 class UserFollowing(Base, BaseModel):
3583 class UserFollowing(Base, BaseModel):
3584 __tablename__ = 'user_followings'
3584 __tablename__ = 'user_followings'
3585 __table_args__ = (
3585 __table_args__ = (
3586 UniqueConstraint('user_id', 'follows_repository_id'),
3586 UniqueConstraint('user_id', 'follows_repository_id'),
3587 UniqueConstraint('user_id', 'follows_user_id'),
3587 UniqueConstraint('user_id', 'follows_user_id'),
3588 base_table_args
3588 base_table_args
3589 )
3589 )
3590
3590
3591 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3591 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3593 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3593 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3594 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3594 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3595 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3595 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3596
3596
3597 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3597 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3598
3598
3599 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3599 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3600 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3600 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3601
3601
3602 @classmethod
3602 @classmethod
3603 def get_repo_followers(cls, repo_id):
3603 def get_repo_followers(cls, repo_id):
3604 return cls.query().filter(cls.follows_repo_id == repo_id)
3604 return cls.query().filter(cls.follows_repo_id == repo_id)
3605
3605
3606
3606
3607 class CacheKey(Base, BaseModel):
3607 class CacheKey(Base, BaseModel):
3608 __tablename__ = 'cache_invalidation'
3608 __tablename__ = 'cache_invalidation'
3609 __table_args__ = (
3609 __table_args__ = (
3610 UniqueConstraint('cache_key'),
3610 UniqueConstraint('cache_key'),
3611 Index('key_idx', 'cache_key'),
3611 Index('key_idx', 'cache_key'),
3612 base_table_args,
3612 base_table_args,
3613 )
3613 )
3614
3614
3615 CACHE_TYPE_FEED = 'FEED'
3615 CACHE_TYPE_FEED = 'FEED'
3616
3616
3617 # namespaces used to register process/thread aware caches
3617 # namespaces used to register process/thread aware caches
3618 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3618 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3619 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3619 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3620
3620
3621 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3621 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3622 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3622 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3623 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3623 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3624 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3624 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3625 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3625 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3626
3626
3627 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3627 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3628 self.cache_key = cache_key
3628 self.cache_key = cache_key
3629 self.cache_args = cache_args
3629 self.cache_args = cache_args
3630 self.cache_active = False
3630 self.cache_active = False
3631 # first key should be same for all entries, since all workers should share it
3631 # first key should be same for all entries, since all workers should share it
3632 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3632 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3633
3633
3634 def __unicode__(self):
3634 def __unicode__(self):
3635 return u"<%s('%s:%s[%s]')>" % (
3635 return u"<%s('%s:%s[%s]')>" % (
3636 self.__class__.__name__,
3636 self.__class__.__name__,
3637 self.cache_id, self.cache_key, self.cache_active)
3637 self.cache_id, self.cache_key, self.cache_active)
3638
3638
3639 def _cache_key_partition(self):
3639 def _cache_key_partition(self):
3640 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3640 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3641 return prefix, repo_name, suffix
3641 return prefix, repo_name, suffix
3642
3642
3643 def get_prefix(self):
3643 def get_prefix(self):
3644 """
3644 """
3645 Try to extract prefix from existing cache key. The key could consist
3645 Try to extract prefix from existing cache key. The key could consist
3646 of prefix, repo_name, suffix
3646 of prefix, repo_name, suffix
3647 """
3647 """
3648 # this returns prefix, repo_name, suffix
3648 # this returns prefix, repo_name, suffix
3649 return self._cache_key_partition()[0]
3649 return self._cache_key_partition()[0]
3650
3650
3651 def get_suffix(self):
3651 def get_suffix(self):
3652 """
3652 """
3653 get suffix that might have been used in _get_cache_key to
3653 get suffix that might have been used in _get_cache_key to
3654 generate self.cache_key. Only used for informational purposes
3654 generate self.cache_key. Only used for informational purposes
3655 in repo_edit.mako.
3655 in repo_edit.mako.
3656 """
3656 """
3657 # prefix, repo_name, suffix
3657 # prefix, repo_name, suffix
3658 return self._cache_key_partition()[2]
3658 return self._cache_key_partition()[2]
3659
3659
3660 @classmethod
3660 @classmethod
3661 def generate_new_state_uid(cls, based_on=None):
3661 def generate_new_state_uid(cls, based_on=None):
3662 if based_on:
3662 if based_on:
3663 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3663 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3664 else:
3664 else:
3665 return str(uuid.uuid4())
3665 return str(uuid.uuid4())
3666
3666
3667 @classmethod
3667 @classmethod
3668 def delete_all_cache(cls):
3668 def delete_all_cache(cls):
3669 """
3669 """
3670 Delete all cache keys from database.
3670 Delete all cache keys from database.
3671 Should only be run when all instances are down and all entries
3671 Should only be run when all instances are down and all entries
3672 thus stale.
3672 thus stale.
3673 """
3673 """
3674 cls.query().delete()
3674 cls.query().delete()
3675 Session().commit()
3675 Session().commit()
3676
3676
3677 @classmethod
3677 @classmethod
3678 def set_invalidate(cls, cache_uid, delete=False):
3678 def set_invalidate(cls, cache_uid, delete=False):
3679 """
3679 """
3680 Mark all caches of a repo as invalid in the database.
3680 Mark all caches of a repo as invalid in the database.
3681 """
3681 """
3682
3682
3683 try:
3683 try:
3684 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3684 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3685 if delete:
3685 if delete:
3686 qry.delete()
3686 qry.delete()
3687 log.debug('cache objects deleted for cache args %s',
3687 log.debug('cache objects deleted for cache args %s',
3688 safe_str(cache_uid))
3688 safe_str(cache_uid))
3689 else:
3689 else:
3690 qry.update({"cache_active": False,
3690 qry.update({"cache_active": False,
3691 "cache_state_uid": cls.generate_new_state_uid()})
3691 "cache_state_uid": cls.generate_new_state_uid()})
3692 log.debug('cache objects marked as invalid for cache args %s',
3692 log.debug('cache objects marked as invalid for cache args %s',
3693 safe_str(cache_uid))
3693 safe_str(cache_uid))
3694
3694
3695 Session().commit()
3695 Session().commit()
3696 except Exception:
3696 except Exception:
3697 log.exception(
3697 log.exception(
3698 'Cache key invalidation failed for cache args %s',
3698 'Cache key invalidation failed for cache args %s',
3699 safe_str(cache_uid))
3699 safe_str(cache_uid))
3700 Session().rollback()
3700 Session().rollback()
3701
3701
3702 @classmethod
3702 @classmethod
3703 def get_active_cache(cls, cache_key):
3703 def get_active_cache(cls, cache_key):
3704 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3704 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3705 if inv_obj:
3705 if inv_obj:
3706 return inv_obj
3706 return inv_obj
3707 return None
3707 return None
3708
3708
3709 @classmethod
3709 @classmethod
3710 def get_namespace_map(cls, namespace):
3710 def get_namespace_map(cls, namespace):
3711 return {
3711 return {
3712 x.cache_key: x
3712 x.cache_key: x
3713 for x in cls.query().filter(cls.cache_args == namespace)}
3713 for x in cls.query().filter(cls.cache_args == namespace)}
3714
3714
3715
3715
3716 class ChangesetComment(Base, BaseModel):
3716 class ChangesetComment(Base, BaseModel):
3717 __tablename__ = 'changeset_comments'
3717 __tablename__ = 'changeset_comments'
3718 __table_args__ = (
3718 __table_args__ = (
3719 Index('cc_revision_idx', 'revision'),
3719 Index('cc_revision_idx', 'revision'),
3720 base_table_args,
3720 base_table_args,
3721 )
3721 )
3722
3722
3723 COMMENT_OUTDATED = u'comment_outdated'
3723 COMMENT_OUTDATED = u'comment_outdated'
3724 COMMENT_TYPE_NOTE = u'note'
3724 COMMENT_TYPE_NOTE = u'note'
3725 COMMENT_TYPE_TODO = u'todo'
3725 COMMENT_TYPE_TODO = u'todo'
3726 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3726 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3727
3727
3728 OP_IMMUTABLE = u'immutable'
3728 OP_IMMUTABLE = u'immutable'
3729 OP_CHANGEABLE = u'changeable'
3729 OP_CHANGEABLE = u'changeable'
3730
3730
3731 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3731 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3732 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3732 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3733 revision = Column('revision', String(40), nullable=True)
3733 revision = Column('revision', String(40), nullable=True)
3734 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3734 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3735 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3735 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3736 line_no = Column('line_no', Unicode(10), nullable=True)
3736 line_no = Column('line_no', Unicode(10), nullable=True)
3737 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3737 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3738 f_path = Column('f_path', Unicode(1000), nullable=True)
3738 f_path = Column('f_path', Unicode(1000), nullable=True)
3739 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3739 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3740 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3740 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3741 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3741 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3742 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3742 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3743 renderer = Column('renderer', Unicode(64), nullable=True)
3743 renderer = Column('renderer', Unicode(64), nullable=True)
3744 display_state = Column('display_state', Unicode(128), nullable=True)
3744 display_state = Column('display_state', Unicode(128), nullable=True)
3745 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3745 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3746
3746
3747 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3747 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3748 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3748 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3749
3749
3750 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3750 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3751 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3751 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3752
3752
3753 author = relationship('User', lazy='joined')
3753 author = relationship('User', lazy='joined')
3754 repo = relationship('Repository')
3754 repo = relationship('Repository')
3755 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3755 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3756 pull_request = relationship('PullRequest', lazy='joined')
3756 pull_request = relationship('PullRequest', lazy='joined')
3757 pull_request_version = relationship('PullRequestVersion')
3757 pull_request_version = relationship('PullRequestVersion')
3758 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='joined', order_by='ChangesetCommentHistory.version')
3758 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='joined', order_by='ChangesetCommentHistory.version')
3759
3759
3760 @classmethod
3760 @classmethod
3761 def get_users(cls, revision=None, pull_request_id=None):
3761 def get_users(cls, revision=None, pull_request_id=None):
3762 """
3762 """
3763 Returns user associated with this ChangesetComment. ie those
3763 Returns user associated with this ChangesetComment. ie those
3764 who actually commented
3764 who actually commented
3765
3765
3766 :param cls:
3766 :param cls:
3767 :param revision:
3767 :param revision:
3768 """
3768 """
3769 q = Session().query(User)\
3769 q = Session().query(User)\
3770 .join(ChangesetComment.author)
3770 .join(ChangesetComment.author)
3771 if revision:
3771 if revision:
3772 q = q.filter(cls.revision == revision)
3772 q = q.filter(cls.revision == revision)
3773 elif pull_request_id:
3773 elif pull_request_id:
3774 q = q.filter(cls.pull_request_id == pull_request_id)
3774 q = q.filter(cls.pull_request_id == pull_request_id)
3775 return q.all()
3775 return q.all()
3776
3776
3777 @classmethod
3777 @classmethod
3778 def get_index_from_version(cls, pr_version, versions):
3778 def get_index_from_version(cls, pr_version, versions):
3779 num_versions = [x.pull_request_version_id for x in versions]
3779 num_versions = [x.pull_request_version_id for x in versions]
3780 try:
3780 try:
3781 return num_versions.index(pr_version) +1
3781 return num_versions.index(pr_version) +1
3782 except (IndexError, ValueError):
3782 except (IndexError, ValueError):
3783 return
3783 return
3784
3784
3785 @property
3785 @property
3786 def outdated(self):
3786 def outdated(self):
3787 return self.display_state == self.COMMENT_OUTDATED
3787 return self.display_state == self.COMMENT_OUTDATED
3788
3788
3789 @property
3789 @property
3790 def immutable(self):
3790 def immutable(self):
3791 return self.immutable_state == self.OP_IMMUTABLE
3791 return self.immutable_state == self.OP_IMMUTABLE
3792
3792
3793 def outdated_at_version(self, version):
3793 def outdated_at_version(self, version):
3794 """
3794 """
3795 Checks if comment is outdated for given pull request version
3795 Checks if comment is outdated for given pull request version
3796 """
3796 """
3797 return self.outdated and self.pull_request_version_id != version
3797 return self.outdated and self.pull_request_version_id != version
3798
3798
3799 def older_than_version(self, version):
3799 def older_than_version(self, version):
3800 """
3800 """
3801 Checks if comment is made from previous version than given
3801 Checks if comment is made from previous version than given
3802 """
3802 """
3803 if version is None:
3803 if version is None:
3804 return self.pull_request_version_id is not None
3804 return self.pull_request_version_id is not None
3805
3805
3806 return self.pull_request_version_id < version
3806 return self.pull_request_version_id < version
3807
3807
3808 @property
3808 @property
3809 def commit_id(self):
3810 """New style naming to stop using .revision"""
3811 return self.revision
3812
3813 @property
3809 def resolved(self):
3814 def resolved(self):
3810 return self.resolved_by[0] if self.resolved_by else None
3815 return self.resolved_by[0] if self.resolved_by else None
3811
3816
3812 @property
3817 @property
3813 def is_todo(self):
3818 def is_todo(self):
3814 return self.comment_type == self.COMMENT_TYPE_TODO
3819 return self.comment_type == self.COMMENT_TYPE_TODO
3815
3820
3816 @property
3821 @property
3817 def is_inline(self):
3822 def is_inline(self):
3818 return self.line_no and self.f_path
3823 return self.line_no and self.f_path
3819
3824
3820 def get_index_version(self, versions):
3825 def get_index_version(self, versions):
3821 return self.get_index_from_version(
3826 return self.get_index_from_version(
3822 self.pull_request_version_id, versions)
3827 self.pull_request_version_id, versions)
3823
3828
3824 def __repr__(self):
3829 def __repr__(self):
3825 if self.comment_id:
3830 if self.comment_id:
3826 return '<DB:Comment #%s>' % self.comment_id
3831 return '<DB:Comment #%s>' % self.comment_id
3827 else:
3832 else:
3828 return '<DB:Comment at %#x>' % id(self)
3833 return '<DB:Comment at %#x>' % id(self)
3829
3834
3830 def get_api_data(self):
3835 def get_api_data(self):
3831 comment = self
3836 comment = self
3832 data = {
3837 data = {
3833 'comment_id': comment.comment_id,
3838 'comment_id': comment.comment_id,
3834 'comment_type': comment.comment_type,
3839 'comment_type': comment.comment_type,
3835 'comment_text': comment.text,
3840 'comment_text': comment.text,
3836 'comment_status': comment.status_change,
3841 'comment_status': comment.status_change,
3837 'comment_f_path': comment.f_path,
3842 'comment_f_path': comment.f_path,
3838 'comment_lineno': comment.line_no,
3843 'comment_lineno': comment.line_no,
3839 'comment_author': comment.author,
3844 'comment_author': comment.author,
3840 'comment_created_on': comment.created_on,
3845 'comment_created_on': comment.created_on,
3841 'comment_resolved_by': self.resolved,
3846 'comment_resolved_by': self.resolved,
3842 'comment_commit_id': comment.revision,
3847 'comment_commit_id': comment.revision,
3843 'comment_pull_request_id': comment.pull_request_id,
3848 'comment_pull_request_id': comment.pull_request_id,
3844 }
3849 }
3845 return data
3850 return data
3846
3851
3847 def __json__(self):
3852 def __json__(self):
3848 data = dict()
3853 data = dict()
3849 data.update(self.get_api_data())
3854 data.update(self.get_api_data())
3850 return data
3855 return data
3851
3856
3852
3857
3853 class ChangesetCommentHistory(Base, BaseModel):
3858 class ChangesetCommentHistory(Base, BaseModel):
3854 __tablename__ = 'changeset_comments_history'
3859 __tablename__ = 'changeset_comments_history'
3855 __table_args__ = (
3860 __table_args__ = (
3856 Index('cch_comment_id_idx', 'comment_id'),
3861 Index('cch_comment_id_idx', 'comment_id'),
3857 base_table_args,
3862 base_table_args,
3858 )
3863 )
3859
3864
3860 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3865 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3861 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3866 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3862 version = Column("version", Integer(), nullable=False, default=0)
3867 version = Column("version", Integer(), nullable=False, default=0)
3863 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3868 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3864 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3869 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3865 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3870 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3866 deleted = Column('deleted', Boolean(), default=False)
3871 deleted = Column('deleted', Boolean(), default=False)
3867
3872
3868 author = relationship('User', lazy='joined')
3873 author = relationship('User', lazy='joined')
3869 comment = relationship('ChangesetComment', cascade="all, delete")
3874 comment = relationship('ChangesetComment', cascade="all, delete")
3870
3875
3871 @classmethod
3876 @classmethod
3872 def get_version(cls, comment_id):
3877 def get_version(cls, comment_id):
3873 q = Session().query(ChangesetCommentHistory).filter(
3878 q = Session().query(ChangesetCommentHistory).filter(
3874 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3879 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3875 if q.count() == 0:
3880 if q.count() == 0:
3876 return 1
3881 return 1
3877 elif q.count() >= q[0].version:
3882 elif q.count() >= q[0].version:
3878 return q.count() + 1
3883 return q.count() + 1
3879 else:
3884 else:
3880 return q[0].version + 1
3885 return q[0].version + 1
3881
3886
3882
3887
3883 class ChangesetStatus(Base, BaseModel):
3888 class ChangesetStatus(Base, BaseModel):
3884 __tablename__ = 'changeset_statuses'
3889 __tablename__ = 'changeset_statuses'
3885 __table_args__ = (
3890 __table_args__ = (
3886 Index('cs_revision_idx', 'revision'),
3891 Index('cs_revision_idx', 'revision'),
3887 Index('cs_version_idx', 'version'),
3892 Index('cs_version_idx', 'version'),
3888 UniqueConstraint('repo_id', 'revision', 'version'),
3893 UniqueConstraint('repo_id', 'revision', 'version'),
3889 base_table_args
3894 base_table_args
3890 )
3895 )
3891
3896
3892 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3897 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3893 STATUS_APPROVED = 'approved'
3898 STATUS_APPROVED = 'approved'
3894 STATUS_REJECTED = 'rejected'
3899 STATUS_REJECTED = 'rejected'
3895 STATUS_UNDER_REVIEW = 'under_review'
3900 STATUS_UNDER_REVIEW = 'under_review'
3896
3901
3897 STATUSES = [
3902 STATUSES = [
3898 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3903 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3899 (STATUS_APPROVED, _("Approved")),
3904 (STATUS_APPROVED, _("Approved")),
3900 (STATUS_REJECTED, _("Rejected")),
3905 (STATUS_REJECTED, _("Rejected")),
3901 (STATUS_UNDER_REVIEW, _("Under Review")),
3906 (STATUS_UNDER_REVIEW, _("Under Review")),
3902 ]
3907 ]
3903
3908
3904 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3909 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3905 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3910 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3906 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3911 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3907 revision = Column('revision', String(40), nullable=False)
3912 revision = Column('revision', String(40), nullable=False)
3908 status = Column('status', String(128), nullable=False, default=DEFAULT)
3913 status = Column('status', String(128), nullable=False, default=DEFAULT)
3909 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3914 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3910 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3915 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3911 version = Column('version', Integer(), nullable=False, default=0)
3916 version = Column('version', Integer(), nullable=False, default=0)
3912 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3917 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3913
3918
3914 author = relationship('User', lazy='joined')
3919 author = relationship('User', lazy='joined')
3915 repo = relationship('Repository')
3920 repo = relationship('Repository')
3916 comment = relationship('ChangesetComment', lazy='joined')
3921 comment = relationship('ChangesetComment', lazy='joined')
3917 pull_request = relationship('PullRequest', lazy='joined')
3922 pull_request = relationship('PullRequest', lazy='joined')
3918
3923
3919 def __unicode__(self):
3924 def __unicode__(self):
3920 return u"<%s('%s[v%s]:%s')>" % (
3925 return u"<%s('%s[v%s]:%s')>" % (
3921 self.__class__.__name__,
3926 self.__class__.__name__,
3922 self.status, self.version, self.author
3927 self.status, self.version, self.author
3923 )
3928 )
3924
3929
3925 @classmethod
3930 @classmethod
3926 def get_status_lbl(cls, value):
3931 def get_status_lbl(cls, value):
3927 return dict(cls.STATUSES).get(value)
3932 return dict(cls.STATUSES).get(value)
3928
3933
3929 @property
3934 @property
3930 def status_lbl(self):
3935 def status_lbl(self):
3931 return ChangesetStatus.get_status_lbl(self.status)
3936 return ChangesetStatus.get_status_lbl(self.status)
3932
3937
3933 def get_api_data(self):
3938 def get_api_data(self):
3934 status = self
3939 status = self
3935 data = {
3940 data = {
3936 'status_id': status.changeset_status_id,
3941 'status_id': status.changeset_status_id,
3937 'status': status.status,
3942 'status': status.status,
3938 }
3943 }
3939 return data
3944 return data
3940
3945
3941 def __json__(self):
3946 def __json__(self):
3942 data = dict()
3947 data = dict()
3943 data.update(self.get_api_data())
3948 data.update(self.get_api_data())
3944 return data
3949 return data
3945
3950
3946
3951
3947 class _SetState(object):
3952 class _SetState(object):
3948 """
3953 """
3949 Context processor allowing changing state for sensitive operation such as
3954 Context processor allowing changing state for sensitive operation such as
3950 pull request update or merge
3955 pull request update or merge
3951 """
3956 """
3952
3957
3953 def __init__(self, pull_request, pr_state, back_state=None):
3958 def __init__(self, pull_request, pr_state, back_state=None):
3954 self._pr = pull_request
3959 self._pr = pull_request
3955 self._org_state = back_state or pull_request.pull_request_state
3960 self._org_state = back_state or pull_request.pull_request_state
3956 self._pr_state = pr_state
3961 self._pr_state = pr_state
3957 self._current_state = None
3962 self._current_state = None
3958
3963
3959 def __enter__(self):
3964 def __enter__(self):
3960 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3965 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3961 self._pr, self._pr_state)
3966 self._pr, self._pr_state)
3962 self.set_pr_state(self._pr_state)
3967 self.set_pr_state(self._pr_state)
3963 return self
3968 return self
3964
3969
3965 def __exit__(self, exc_type, exc_val, exc_tb):
3970 def __exit__(self, exc_type, exc_val, exc_tb):
3966 if exc_val is not None:
3971 if exc_val is not None:
3967 log.error(traceback.format_exc(exc_tb))
3972 log.error(traceback.format_exc(exc_tb))
3968 return None
3973 return None
3969
3974
3970 self.set_pr_state(self._org_state)
3975 self.set_pr_state(self._org_state)
3971 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3976 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3972 self._pr, self._org_state)
3977 self._pr, self._org_state)
3973
3978
3974 @property
3979 @property
3975 def state(self):
3980 def state(self):
3976 return self._current_state
3981 return self._current_state
3977
3982
3978 def set_pr_state(self, pr_state):
3983 def set_pr_state(self, pr_state):
3979 try:
3984 try:
3980 self._pr.pull_request_state = pr_state
3985 self._pr.pull_request_state = pr_state
3981 Session().add(self._pr)
3986 Session().add(self._pr)
3982 Session().commit()
3987 Session().commit()
3983 self._current_state = pr_state
3988 self._current_state = pr_state
3984 except Exception:
3989 except Exception:
3985 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3990 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3986 raise
3991 raise
3987
3992
3988
3993
3989 class _PullRequestBase(BaseModel):
3994 class _PullRequestBase(BaseModel):
3990 """
3995 """
3991 Common attributes of pull request and version entries.
3996 Common attributes of pull request and version entries.
3992 """
3997 """
3993
3998
3994 # .status values
3999 # .status values
3995 STATUS_NEW = u'new'
4000 STATUS_NEW = u'new'
3996 STATUS_OPEN = u'open'
4001 STATUS_OPEN = u'open'
3997 STATUS_CLOSED = u'closed'
4002 STATUS_CLOSED = u'closed'
3998
4003
3999 # available states
4004 # available states
4000 STATE_CREATING = u'creating'
4005 STATE_CREATING = u'creating'
4001 STATE_UPDATING = u'updating'
4006 STATE_UPDATING = u'updating'
4002 STATE_MERGING = u'merging'
4007 STATE_MERGING = u'merging'
4003 STATE_CREATED = u'created'
4008 STATE_CREATED = u'created'
4004
4009
4005 title = Column('title', Unicode(255), nullable=True)
4010 title = Column('title', Unicode(255), nullable=True)
4006 description = Column(
4011 description = Column(
4007 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4012 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4008 nullable=True)
4013 nullable=True)
4009 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4014 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4010
4015
4011 # new/open/closed status of pull request (not approve/reject/etc)
4016 # new/open/closed status of pull request (not approve/reject/etc)
4012 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4017 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4013 created_on = Column(
4018 created_on = Column(
4014 'created_on', DateTime(timezone=False), nullable=False,
4019 'created_on', DateTime(timezone=False), nullable=False,
4015 default=datetime.datetime.now)
4020 default=datetime.datetime.now)
4016 updated_on = Column(
4021 updated_on = Column(
4017 'updated_on', DateTime(timezone=False), nullable=False,
4022 'updated_on', DateTime(timezone=False), nullable=False,
4018 default=datetime.datetime.now)
4023 default=datetime.datetime.now)
4019
4024
4020 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4025 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4021
4026
4022 @declared_attr
4027 @declared_attr
4023 def user_id(cls):
4028 def user_id(cls):
4024 return Column(
4029 return Column(
4025 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4030 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4026 unique=None)
4031 unique=None)
4027
4032
4028 # 500 revisions max
4033 # 500 revisions max
4029 _revisions = Column(
4034 _revisions = Column(
4030 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4035 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4031
4036
4032 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4037 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4033
4038
4034 @declared_attr
4039 @declared_attr
4035 def source_repo_id(cls):
4040 def source_repo_id(cls):
4036 # TODO: dan: rename column to source_repo_id
4041 # TODO: dan: rename column to source_repo_id
4037 return Column(
4042 return Column(
4038 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4043 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4039 nullable=False)
4044 nullable=False)
4040
4045
4041 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4046 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4042
4047
4043 @hybrid_property
4048 @hybrid_property
4044 def source_ref(self):
4049 def source_ref(self):
4045 return self._source_ref
4050 return self._source_ref
4046
4051
4047 @source_ref.setter
4052 @source_ref.setter
4048 def source_ref(self, val):
4053 def source_ref(self, val):
4049 parts = (val or '').split(':')
4054 parts = (val or '').split(':')
4050 if len(parts) != 3:
4055 if len(parts) != 3:
4051 raise ValueError(
4056 raise ValueError(
4052 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4057 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4053 self._source_ref = safe_unicode(val)
4058 self._source_ref = safe_unicode(val)
4054
4059
4055 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4060 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4056
4061
4057 @hybrid_property
4062 @hybrid_property
4058 def target_ref(self):
4063 def target_ref(self):
4059 return self._target_ref
4064 return self._target_ref
4060
4065
4061 @target_ref.setter
4066 @target_ref.setter
4062 def target_ref(self, val):
4067 def target_ref(self, val):
4063 parts = (val or '').split(':')
4068 parts = (val or '').split(':')
4064 if len(parts) != 3:
4069 if len(parts) != 3:
4065 raise ValueError(
4070 raise ValueError(
4066 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4071 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4067 self._target_ref = safe_unicode(val)
4072 self._target_ref = safe_unicode(val)
4068
4073
4069 @declared_attr
4074 @declared_attr
4070 def target_repo_id(cls):
4075 def target_repo_id(cls):
4071 # TODO: dan: rename column to target_repo_id
4076 # TODO: dan: rename column to target_repo_id
4072 return Column(
4077 return Column(
4073 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4078 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4074 nullable=False)
4079 nullable=False)
4075
4080
4076 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4081 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4077
4082
4078 # TODO: dan: rename column to last_merge_source_rev
4083 # TODO: dan: rename column to last_merge_source_rev
4079 _last_merge_source_rev = Column(
4084 _last_merge_source_rev = Column(
4080 'last_merge_org_rev', String(40), nullable=True)
4085 'last_merge_org_rev', String(40), nullable=True)
4081 # TODO: dan: rename column to last_merge_target_rev
4086 # TODO: dan: rename column to last_merge_target_rev
4082 _last_merge_target_rev = Column(
4087 _last_merge_target_rev = Column(
4083 'last_merge_other_rev', String(40), nullable=True)
4088 'last_merge_other_rev', String(40), nullable=True)
4084 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4089 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4085 last_merge_metadata = Column(
4090 last_merge_metadata = Column(
4086 'last_merge_metadata', MutationObj.as_mutable(
4091 'last_merge_metadata', MutationObj.as_mutable(
4087 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4092 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4088
4093
4089 merge_rev = Column('merge_rev', String(40), nullable=True)
4094 merge_rev = Column('merge_rev', String(40), nullable=True)
4090
4095
4091 reviewer_data = Column(
4096 reviewer_data = Column(
4092 'reviewer_data_json', MutationObj.as_mutable(
4097 'reviewer_data_json', MutationObj.as_mutable(
4093 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4098 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4094
4099
4095 @property
4100 @property
4096 def reviewer_data_json(self):
4101 def reviewer_data_json(self):
4097 return json.dumps(self.reviewer_data)
4102 return json.dumps(self.reviewer_data)
4098
4103
4099 @property
4104 @property
4100 def work_in_progress(self):
4105 def work_in_progress(self):
4101 """checks if pull request is work in progress by checking the title"""
4106 """checks if pull request is work in progress by checking the title"""
4102 title = self.title.upper()
4107 title = self.title.upper()
4103 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4108 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4104 return True
4109 return True
4105 return False
4110 return False
4106
4111
4107 @hybrid_property
4112 @hybrid_property
4108 def description_safe(self):
4113 def description_safe(self):
4109 from rhodecode.lib import helpers as h
4114 from rhodecode.lib import helpers as h
4110 return h.escape(self.description)
4115 return h.escape(self.description)
4111
4116
4112 @hybrid_property
4117 @hybrid_property
4113 def revisions(self):
4118 def revisions(self):
4114 return self._revisions.split(':') if self._revisions else []
4119 return self._revisions.split(':') if self._revisions else []
4115
4120
4116 @revisions.setter
4121 @revisions.setter
4117 def revisions(self, val):
4122 def revisions(self, val):
4118 self._revisions = u':'.join(val)
4123 self._revisions = u':'.join(val)
4119
4124
4120 @hybrid_property
4125 @hybrid_property
4121 def last_merge_status(self):
4126 def last_merge_status(self):
4122 return safe_int(self._last_merge_status)
4127 return safe_int(self._last_merge_status)
4123
4128
4124 @last_merge_status.setter
4129 @last_merge_status.setter
4125 def last_merge_status(self, val):
4130 def last_merge_status(self, val):
4126 self._last_merge_status = val
4131 self._last_merge_status = val
4127
4132
4128 @declared_attr
4133 @declared_attr
4129 def author(cls):
4134 def author(cls):
4130 return relationship('User', lazy='joined')
4135 return relationship('User', lazy='joined')
4131
4136
4132 @declared_attr
4137 @declared_attr
4133 def source_repo(cls):
4138 def source_repo(cls):
4134 return relationship(
4139 return relationship(
4135 'Repository',
4140 'Repository',
4136 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4141 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4137
4142
4138 @property
4143 @property
4139 def source_ref_parts(self):
4144 def source_ref_parts(self):
4140 return self.unicode_to_reference(self.source_ref)
4145 return self.unicode_to_reference(self.source_ref)
4141
4146
4142 @declared_attr
4147 @declared_attr
4143 def target_repo(cls):
4148 def target_repo(cls):
4144 return relationship(
4149 return relationship(
4145 'Repository',
4150 'Repository',
4146 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4151 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4147
4152
4148 @property
4153 @property
4149 def target_ref_parts(self):
4154 def target_ref_parts(self):
4150 return self.unicode_to_reference(self.target_ref)
4155 return self.unicode_to_reference(self.target_ref)
4151
4156
4152 @property
4157 @property
4153 def shadow_merge_ref(self):
4158 def shadow_merge_ref(self):
4154 return self.unicode_to_reference(self._shadow_merge_ref)
4159 return self.unicode_to_reference(self._shadow_merge_ref)
4155
4160
4156 @shadow_merge_ref.setter
4161 @shadow_merge_ref.setter
4157 def shadow_merge_ref(self, ref):
4162 def shadow_merge_ref(self, ref):
4158 self._shadow_merge_ref = self.reference_to_unicode(ref)
4163 self._shadow_merge_ref = self.reference_to_unicode(ref)
4159
4164
4160 @staticmethod
4165 @staticmethod
4161 def unicode_to_reference(raw):
4166 def unicode_to_reference(raw):
4162 """
4167 """
4163 Convert a unicode (or string) to a reference object.
4168 Convert a unicode (or string) to a reference object.
4164 If unicode evaluates to False it returns None.
4169 If unicode evaluates to False it returns None.
4165 """
4170 """
4166 if raw:
4171 if raw:
4167 refs = raw.split(':')
4172 refs = raw.split(':')
4168 return Reference(*refs)
4173 return Reference(*refs)
4169 else:
4174 else:
4170 return None
4175 return None
4171
4176
4172 @staticmethod
4177 @staticmethod
4173 def reference_to_unicode(ref):
4178 def reference_to_unicode(ref):
4174 """
4179 """
4175 Convert a reference object to unicode.
4180 Convert a reference object to unicode.
4176 If reference is None it returns None.
4181 If reference is None it returns None.
4177 """
4182 """
4178 if ref:
4183 if ref:
4179 return u':'.join(ref)
4184 return u':'.join(ref)
4180 else:
4185 else:
4181 return None
4186 return None
4182
4187
4183 def get_api_data(self, with_merge_state=True):
4188 def get_api_data(self, with_merge_state=True):
4184 from rhodecode.model.pull_request import PullRequestModel
4189 from rhodecode.model.pull_request import PullRequestModel
4185
4190
4186 pull_request = self
4191 pull_request = self
4187 if with_merge_state:
4192 if with_merge_state:
4188 merge_response, merge_status, msg = \
4193 merge_response, merge_status, msg = \
4189 PullRequestModel().merge_status(pull_request)
4194 PullRequestModel().merge_status(pull_request)
4190 merge_state = {
4195 merge_state = {
4191 'status': merge_status,
4196 'status': merge_status,
4192 'message': safe_unicode(msg),
4197 'message': safe_unicode(msg),
4193 }
4198 }
4194 else:
4199 else:
4195 merge_state = {'status': 'not_available',
4200 merge_state = {'status': 'not_available',
4196 'message': 'not_available'}
4201 'message': 'not_available'}
4197
4202
4198 merge_data = {
4203 merge_data = {
4199 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4204 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4200 'reference': (
4205 'reference': (
4201 pull_request.shadow_merge_ref._asdict()
4206 pull_request.shadow_merge_ref._asdict()
4202 if pull_request.shadow_merge_ref else None),
4207 if pull_request.shadow_merge_ref else None),
4203 }
4208 }
4204
4209
4205 data = {
4210 data = {
4206 'pull_request_id': pull_request.pull_request_id,
4211 'pull_request_id': pull_request.pull_request_id,
4207 'url': PullRequestModel().get_url(pull_request),
4212 'url': PullRequestModel().get_url(pull_request),
4208 'title': pull_request.title,
4213 'title': pull_request.title,
4209 'description': pull_request.description,
4214 'description': pull_request.description,
4210 'status': pull_request.status,
4215 'status': pull_request.status,
4211 'state': pull_request.pull_request_state,
4216 'state': pull_request.pull_request_state,
4212 'created_on': pull_request.created_on,
4217 'created_on': pull_request.created_on,
4213 'updated_on': pull_request.updated_on,
4218 'updated_on': pull_request.updated_on,
4214 'commit_ids': pull_request.revisions,
4219 'commit_ids': pull_request.revisions,
4215 'review_status': pull_request.calculated_review_status(),
4220 'review_status': pull_request.calculated_review_status(),
4216 'mergeable': merge_state,
4221 'mergeable': merge_state,
4217 'source': {
4222 'source': {
4218 'clone_url': pull_request.source_repo.clone_url(),
4223 'clone_url': pull_request.source_repo.clone_url(),
4219 'repository': pull_request.source_repo.repo_name,
4224 'repository': pull_request.source_repo.repo_name,
4220 'reference': {
4225 'reference': {
4221 'name': pull_request.source_ref_parts.name,
4226 'name': pull_request.source_ref_parts.name,
4222 'type': pull_request.source_ref_parts.type,
4227 'type': pull_request.source_ref_parts.type,
4223 'commit_id': pull_request.source_ref_parts.commit_id,
4228 'commit_id': pull_request.source_ref_parts.commit_id,
4224 },
4229 },
4225 },
4230 },
4226 'target': {
4231 'target': {
4227 'clone_url': pull_request.target_repo.clone_url(),
4232 'clone_url': pull_request.target_repo.clone_url(),
4228 'repository': pull_request.target_repo.repo_name,
4233 'repository': pull_request.target_repo.repo_name,
4229 'reference': {
4234 'reference': {
4230 'name': pull_request.target_ref_parts.name,
4235 'name': pull_request.target_ref_parts.name,
4231 'type': pull_request.target_ref_parts.type,
4236 'type': pull_request.target_ref_parts.type,
4232 'commit_id': pull_request.target_ref_parts.commit_id,
4237 'commit_id': pull_request.target_ref_parts.commit_id,
4233 },
4238 },
4234 },
4239 },
4235 'merge': merge_data,
4240 'merge': merge_data,
4236 'author': pull_request.author.get_api_data(include_secrets=False,
4241 'author': pull_request.author.get_api_data(include_secrets=False,
4237 details='basic'),
4242 details='basic'),
4238 'reviewers': [
4243 'reviewers': [
4239 {
4244 {
4240 'user': reviewer.get_api_data(include_secrets=False,
4245 'user': reviewer.get_api_data(include_secrets=False,
4241 details='basic'),
4246 details='basic'),
4242 'reasons': reasons,
4247 'reasons': reasons,
4243 'review_status': st[0][1].status if st else 'not_reviewed',
4248 'review_status': st[0][1].status if st else 'not_reviewed',
4244 }
4249 }
4245 for obj, reviewer, reasons, mandatory, st in
4250 for obj, reviewer, reasons, mandatory, st in
4246 pull_request.reviewers_statuses()
4251 pull_request.reviewers_statuses()
4247 ]
4252 ]
4248 }
4253 }
4249
4254
4250 return data
4255 return data
4251
4256
4252 def set_state(self, pull_request_state, final_state=None):
4257 def set_state(self, pull_request_state, final_state=None):
4253 """
4258 """
4254 # goes from initial state to updating to initial state.
4259 # goes from initial state to updating to initial state.
4255 # initial state can be changed by specifying back_state=
4260 # initial state can be changed by specifying back_state=
4256 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4261 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4257 pull_request.merge()
4262 pull_request.merge()
4258
4263
4259 :param pull_request_state:
4264 :param pull_request_state:
4260 :param final_state:
4265 :param final_state:
4261
4266
4262 """
4267 """
4263
4268
4264 return _SetState(self, pull_request_state, back_state=final_state)
4269 return _SetState(self, pull_request_state, back_state=final_state)
4265
4270
4266
4271
4267 class PullRequest(Base, _PullRequestBase):
4272 class PullRequest(Base, _PullRequestBase):
4268 __tablename__ = 'pull_requests'
4273 __tablename__ = 'pull_requests'
4269 __table_args__ = (
4274 __table_args__ = (
4270 base_table_args,
4275 base_table_args,
4271 )
4276 )
4272
4277
4273 pull_request_id = Column(
4278 pull_request_id = Column(
4274 'pull_request_id', Integer(), nullable=False, primary_key=True)
4279 'pull_request_id', Integer(), nullable=False, primary_key=True)
4275
4280
4276 def __repr__(self):
4281 def __repr__(self):
4277 if self.pull_request_id:
4282 if self.pull_request_id:
4278 return '<DB:PullRequest #%s>' % self.pull_request_id
4283 return '<DB:PullRequest #%s>' % self.pull_request_id
4279 else:
4284 else:
4280 return '<DB:PullRequest at %#x>' % id(self)
4285 return '<DB:PullRequest at %#x>' % id(self)
4281
4286
4282 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4287 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4283 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4288 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4284 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4289 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4285 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4290 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4286 lazy='dynamic')
4291 lazy='dynamic')
4287
4292
4288 @classmethod
4293 @classmethod
4289 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4294 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4290 internal_methods=None):
4295 internal_methods=None):
4291
4296
4292 class PullRequestDisplay(object):
4297 class PullRequestDisplay(object):
4293 """
4298 """
4294 Special object wrapper for showing PullRequest data via Versions
4299 Special object wrapper for showing PullRequest data via Versions
4295 It mimics PR object as close as possible. This is read only object
4300 It mimics PR object as close as possible. This is read only object
4296 just for display
4301 just for display
4297 """
4302 """
4298
4303
4299 def __init__(self, attrs, internal=None):
4304 def __init__(self, attrs, internal=None):
4300 self.attrs = attrs
4305 self.attrs = attrs
4301 # internal have priority over the given ones via attrs
4306 # internal have priority over the given ones via attrs
4302 self.internal = internal or ['versions']
4307 self.internal = internal or ['versions']
4303
4308
4304 def __getattr__(self, item):
4309 def __getattr__(self, item):
4305 if item in self.internal:
4310 if item in self.internal:
4306 return getattr(self, item)
4311 return getattr(self, item)
4307 try:
4312 try:
4308 return self.attrs[item]
4313 return self.attrs[item]
4309 except KeyError:
4314 except KeyError:
4310 raise AttributeError(
4315 raise AttributeError(
4311 '%s object has no attribute %s' % (self, item))
4316 '%s object has no attribute %s' % (self, item))
4312
4317
4313 def __repr__(self):
4318 def __repr__(self):
4314 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4319 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4315
4320
4316 def versions(self):
4321 def versions(self):
4317 return pull_request_obj.versions.order_by(
4322 return pull_request_obj.versions.order_by(
4318 PullRequestVersion.pull_request_version_id).all()
4323 PullRequestVersion.pull_request_version_id).all()
4319
4324
4320 def is_closed(self):
4325 def is_closed(self):
4321 return pull_request_obj.is_closed()
4326 return pull_request_obj.is_closed()
4322
4327
4323 def is_state_changing(self):
4328 def is_state_changing(self):
4324 return pull_request_obj.is_state_changing()
4329 return pull_request_obj.is_state_changing()
4325
4330
4326 @property
4331 @property
4327 def pull_request_version_id(self):
4332 def pull_request_version_id(self):
4328 return getattr(pull_request_obj, 'pull_request_version_id', None)
4333 return getattr(pull_request_obj, 'pull_request_version_id', None)
4329
4334
4330 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4335 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4331
4336
4332 attrs.author = StrictAttributeDict(
4337 attrs.author = StrictAttributeDict(
4333 pull_request_obj.author.get_api_data())
4338 pull_request_obj.author.get_api_data())
4334 if pull_request_obj.target_repo:
4339 if pull_request_obj.target_repo:
4335 attrs.target_repo = StrictAttributeDict(
4340 attrs.target_repo = StrictAttributeDict(
4336 pull_request_obj.target_repo.get_api_data())
4341 pull_request_obj.target_repo.get_api_data())
4337 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4342 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4338
4343
4339 if pull_request_obj.source_repo:
4344 if pull_request_obj.source_repo:
4340 attrs.source_repo = StrictAttributeDict(
4345 attrs.source_repo = StrictAttributeDict(
4341 pull_request_obj.source_repo.get_api_data())
4346 pull_request_obj.source_repo.get_api_data())
4342 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4347 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4343
4348
4344 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4349 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4345 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4350 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4346 attrs.revisions = pull_request_obj.revisions
4351 attrs.revisions = pull_request_obj.revisions
4347 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4352 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4348 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4353 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4349 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4354 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4350 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4355 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4351
4356
4352 return PullRequestDisplay(attrs, internal=internal_methods)
4357 return PullRequestDisplay(attrs, internal=internal_methods)
4353
4358
4354 def is_closed(self):
4359 def is_closed(self):
4355 return self.status == self.STATUS_CLOSED
4360 return self.status == self.STATUS_CLOSED
4356
4361
4357 def is_state_changing(self):
4362 def is_state_changing(self):
4358 return self.pull_request_state != PullRequest.STATE_CREATED
4363 return self.pull_request_state != PullRequest.STATE_CREATED
4359
4364
4360 def __json__(self):
4365 def __json__(self):
4361 return {
4366 return {
4362 'revisions': self.revisions,
4367 'revisions': self.revisions,
4363 'versions': self.versions_count
4368 'versions': self.versions_count
4364 }
4369 }
4365
4370
4366 def calculated_review_status(self):
4371 def calculated_review_status(self):
4367 from rhodecode.model.changeset_status import ChangesetStatusModel
4372 from rhodecode.model.changeset_status import ChangesetStatusModel
4368 return ChangesetStatusModel().calculated_review_status(self)
4373 return ChangesetStatusModel().calculated_review_status(self)
4369
4374
4370 def reviewers_statuses(self):
4375 def reviewers_statuses(self):
4371 from rhodecode.model.changeset_status import ChangesetStatusModel
4376 from rhodecode.model.changeset_status import ChangesetStatusModel
4372 return ChangesetStatusModel().reviewers_statuses(self)
4377 return ChangesetStatusModel().reviewers_statuses(self)
4373
4378
4374 @property
4379 @property
4375 def workspace_id(self):
4380 def workspace_id(self):
4376 from rhodecode.model.pull_request import PullRequestModel
4381 from rhodecode.model.pull_request import PullRequestModel
4377 return PullRequestModel()._workspace_id(self)
4382 return PullRequestModel()._workspace_id(self)
4378
4383
4379 def get_shadow_repo(self):
4384 def get_shadow_repo(self):
4380 workspace_id = self.workspace_id
4385 workspace_id = self.workspace_id
4381 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4386 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4382 if os.path.isdir(shadow_repository_path):
4387 if os.path.isdir(shadow_repository_path):
4383 vcs_obj = self.target_repo.scm_instance()
4388 vcs_obj = self.target_repo.scm_instance()
4384 return vcs_obj.get_shadow_instance(shadow_repository_path)
4389 return vcs_obj.get_shadow_instance(shadow_repository_path)
4385
4390
4386 @property
4391 @property
4387 def versions_count(self):
4392 def versions_count(self):
4388 """
4393 """
4389 return number of versions this PR have, e.g a PR that once been
4394 return number of versions this PR have, e.g a PR that once been
4390 updated will have 2 versions
4395 updated will have 2 versions
4391 """
4396 """
4392 return self.versions.count() + 1
4397 return self.versions.count() + 1
4393
4398
4394
4399
4395 class PullRequestVersion(Base, _PullRequestBase):
4400 class PullRequestVersion(Base, _PullRequestBase):
4396 __tablename__ = 'pull_request_versions'
4401 __tablename__ = 'pull_request_versions'
4397 __table_args__ = (
4402 __table_args__ = (
4398 base_table_args,
4403 base_table_args,
4399 )
4404 )
4400
4405
4401 pull_request_version_id = Column(
4406 pull_request_version_id = Column(
4402 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4407 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4403 pull_request_id = Column(
4408 pull_request_id = Column(
4404 'pull_request_id', Integer(),
4409 'pull_request_id', Integer(),
4405 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4410 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4406 pull_request = relationship('PullRequest')
4411 pull_request = relationship('PullRequest')
4407
4412
4408 def __repr__(self):
4413 def __repr__(self):
4409 if self.pull_request_version_id:
4414 if self.pull_request_version_id:
4410 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4415 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4411 else:
4416 else:
4412 return '<DB:PullRequestVersion at %#x>' % id(self)
4417 return '<DB:PullRequestVersion at %#x>' % id(self)
4413
4418
4414 @property
4419 @property
4415 def reviewers(self):
4420 def reviewers(self):
4416 return self.pull_request.reviewers
4421 return self.pull_request.reviewers
4417
4422
4418 @property
4423 @property
4419 def versions(self):
4424 def versions(self):
4420 return self.pull_request.versions
4425 return self.pull_request.versions
4421
4426
4422 def is_closed(self):
4427 def is_closed(self):
4423 # calculate from original
4428 # calculate from original
4424 return self.pull_request.status == self.STATUS_CLOSED
4429 return self.pull_request.status == self.STATUS_CLOSED
4425
4430
4426 def is_state_changing(self):
4431 def is_state_changing(self):
4427 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4432 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4428
4433
4429 def calculated_review_status(self):
4434 def calculated_review_status(self):
4430 return self.pull_request.calculated_review_status()
4435 return self.pull_request.calculated_review_status()
4431
4436
4432 def reviewers_statuses(self):
4437 def reviewers_statuses(self):
4433 return self.pull_request.reviewers_statuses()
4438 return self.pull_request.reviewers_statuses()
4434
4439
4435
4440
4436 class PullRequestReviewers(Base, BaseModel):
4441 class PullRequestReviewers(Base, BaseModel):
4437 __tablename__ = 'pull_request_reviewers'
4442 __tablename__ = 'pull_request_reviewers'
4438 __table_args__ = (
4443 __table_args__ = (
4439 base_table_args,
4444 base_table_args,
4440 )
4445 )
4441
4446
4442 @hybrid_property
4447 @hybrid_property
4443 def reasons(self):
4448 def reasons(self):
4444 if not self._reasons:
4449 if not self._reasons:
4445 return []
4450 return []
4446 return self._reasons
4451 return self._reasons
4447
4452
4448 @reasons.setter
4453 @reasons.setter
4449 def reasons(self, val):
4454 def reasons(self, val):
4450 val = val or []
4455 val = val or []
4451 if any(not isinstance(x, compat.string_types) for x in val):
4456 if any(not isinstance(x, compat.string_types) for x in val):
4452 raise Exception('invalid reasons type, must be list of strings')
4457 raise Exception('invalid reasons type, must be list of strings')
4453 self._reasons = val
4458 self._reasons = val
4454
4459
4455 pull_requests_reviewers_id = Column(
4460 pull_requests_reviewers_id = Column(
4456 'pull_requests_reviewers_id', Integer(), nullable=False,
4461 'pull_requests_reviewers_id', Integer(), nullable=False,
4457 primary_key=True)
4462 primary_key=True)
4458 pull_request_id = Column(
4463 pull_request_id = Column(
4459 "pull_request_id", Integer(),
4464 "pull_request_id", Integer(),
4460 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4465 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4461 user_id = Column(
4466 user_id = Column(
4462 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4467 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4463 _reasons = Column(
4468 _reasons = Column(
4464 'reason', MutationList.as_mutable(
4469 'reason', MutationList.as_mutable(
4465 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4470 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4466
4471
4467 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4472 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4468 user = relationship('User')
4473 user = relationship('User')
4469 pull_request = relationship('PullRequest')
4474 pull_request = relationship('PullRequest')
4470
4475
4471 rule_data = Column(
4476 rule_data = Column(
4472 'rule_data_json',
4477 'rule_data_json',
4473 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4478 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4474
4479
4475 def rule_user_group_data(self):
4480 def rule_user_group_data(self):
4476 """
4481 """
4477 Returns the voting user group rule data for this reviewer
4482 Returns the voting user group rule data for this reviewer
4478 """
4483 """
4479
4484
4480 if self.rule_data and 'vote_rule' in self.rule_data:
4485 if self.rule_data and 'vote_rule' in self.rule_data:
4481 user_group_data = {}
4486 user_group_data = {}
4482 if 'rule_user_group_entry_id' in self.rule_data:
4487 if 'rule_user_group_entry_id' in self.rule_data:
4483 # means a group with voting rules !
4488 # means a group with voting rules !
4484 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4489 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4485 user_group_data['name'] = self.rule_data['rule_name']
4490 user_group_data['name'] = self.rule_data['rule_name']
4486 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4491 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4487
4492
4488 return user_group_data
4493 return user_group_data
4489
4494
4490 def __unicode__(self):
4495 def __unicode__(self):
4491 return u"<%s('id:%s')>" % (self.__class__.__name__,
4496 return u"<%s('id:%s')>" % (self.__class__.__name__,
4492 self.pull_requests_reviewers_id)
4497 self.pull_requests_reviewers_id)
4493
4498
4494
4499
4495 class Notification(Base, BaseModel):
4500 class Notification(Base, BaseModel):
4496 __tablename__ = 'notifications'
4501 __tablename__ = 'notifications'
4497 __table_args__ = (
4502 __table_args__ = (
4498 Index('notification_type_idx', 'type'),
4503 Index('notification_type_idx', 'type'),
4499 base_table_args,
4504 base_table_args,
4500 )
4505 )
4501
4506
4502 TYPE_CHANGESET_COMMENT = u'cs_comment'
4507 TYPE_CHANGESET_COMMENT = u'cs_comment'
4503 TYPE_MESSAGE = u'message'
4508 TYPE_MESSAGE = u'message'
4504 TYPE_MENTION = u'mention'
4509 TYPE_MENTION = u'mention'
4505 TYPE_REGISTRATION = u'registration'
4510 TYPE_REGISTRATION = u'registration'
4506 TYPE_PULL_REQUEST = u'pull_request'
4511 TYPE_PULL_REQUEST = u'pull_request'
4507 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4512 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4508 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4513 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4509
4514
4510 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4515 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4511 subject = Column('subject', Unicode(512), nullable=True)
4516 subject = Column('subject', Unicode(512), nullable=True)
4512 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4517 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4513 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4518 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4519 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4515 type_ = Column('type', Unicode(255))
4520 type_ = Column('type', Unicode(255))
4516
4521
4517 created_by_user = relationship('User')
4522 created_by_user = relationship('User')
4518 notifications_to_users = relationship('UserNotification', lazy='joined',
4523 notifications_to_users = relationship('UserNotification', lazy='joined',
4519 cascade="all, delete-orphan")
4524 cascade="all, delete-orphan")
4520
4525
4521 @property
4526 @property
4522 def recipients(self):
4527 def recipients(self):
4523 return [x.user for x in UserNotification.query()\
4528 return [x.user for x in UserNotification.query()\
4524 .filter(UserNotification.notification == self)\
4529 .filter(UserNotification.notification == self)\
4525 .order_by(UserNotification.user_id.asc()).all()]
4530 .order_by(UserNotification.user_id.asc()).all()]
4526
4531
4527 @classmethod
4532 @classmethod
4528 def create(cls, created_by, subject, body, recipients, type_=None):
4533 def create(cls, created_by, subject, body, recipients, type_=None):
4529 if type_ is None:
4534 if type_ is None:
4530 type_ = Notification.TYPE_MESSAGE
4535 type_ = Notification.TYPE_MESSAGE
4531
4536
4532 notification = cls()
4537 notification = cls()
4533 notification.created_by_user = created_by
4538 notification.created_by_user = created_by
4534 notification.subject = subject
4539 notification.subject = subject
4535 notification.body = body
4540 notification.body = body
4536 notification.type_ = type_
4541 notification.type_ = type_
4537 notification.created_on = datetime.datetime.now()
4542 notification.created_on = datetime.datetime.now()
4538
4543
4539 # For each recipient link the created notification to his account
4544 # For each recipient link the created notification to his account
4540 for u in recipients:
4545 for u in recipients:
4541 assoc = UserNotification()
4546 assoc = UserNotification()
4542 assoc.user_id = u.user_id
4547 assoc.user_id = u.user_id
4543 assoc.notification = notification
4548 assoc.notification = notification
4544
4549
4545 # if created_by is inside recipients mark his notification
4550 # if created_by is inside recipients mark his notification
4546 # as read
4551 # as read
4547 if u.user_id == created_by.user_id:
4552 if u.user_id == created_by.user_id:
4548 assoc.read = True
4553 assoc.read = True
4549 Session().add(assoc)
4554 Session().add(assoc)
4550
4555
4551 Session().add(notification)
4556 Session().add(notification)
4552
4557
4553 return notification
4558 return notification
4554
4559
4555
4560
4556 class UserNotification(Base, BaseModel):
4561 class UserNotification(Base, BaseModel):
4557 __tablename__ = 'user_to_notification'
4562 __tablename__ = 'user_to_notification'
4558 __table_args__ = (
4563 __table_args__ = (
4559 UniqueConstraint('user_id', 'notification_id'),
4564 UniqueConstraint('user_id', 'notification_id'),
4560 base_table_args
4565 base_table_args
4561 )
4566 )
4562
4567
4563 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4568 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4564 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4569 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4565 read = Column('read', Boolean, default=False)
4570 read = Column('read', Boolean, default=False)
4566 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4571 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4567
4572
4568 user = relationship('User', lazy="joined")
4573 user = relationship('User', lazy="joined")
4569 notification = relationship('Notification', lazy="joined",
4574 notification = relationship('Notification', lazy="joined",
4570 order_by=lambda: Notification.created_on.desc(),)
4575 order_by=lambda: Notification.created_on.desc(),)
4571
4576
4572 def mark_as_read(self):
4577 def mark_as_read(self):
4573 self.read = True
4578 self.read = True
4574 Session().add(self)
4579 Session().add(self)
4575
4580
4576
4581
4577 class UserNotice(Base, BaseModel):
4582 class UserNotice(Base, BaseModel):
4578 __tablename__ = 'user_notices'
4583 __tablename__ = 'user_notices'
4579 __table_args__ = (
4584 __table_args__ = (
4580 base_table_args
4585 base_table_args
4581 )
4586 )
4582
4587
4583 NOTIFICATION_TYPE_MESSAGE = 'message'
4588 NOTIFICATION_TYPE_MESSAGE = 'message'
4584 NOTIFICATION_TYPE_NOTICE = 'notice'
4589 NOTIFICATION_TYPE_NOTICE = 'notice'
4585
4590
4586 NOTIFICATION_LEVEL_INFO = 'info'
4591 NOTIFICATION_LEVEL_INFO = 'info'
4587 NOTIFICATION_LEVEL_WARNING = 'warning'
4592 NOTIFICATION_LEVEL_WARNING = 'warning'
4588 NOTIFICATION_LEVEL_ERROR = 'error'
4593 NOTIFICATION_LEVEL_ERROR = 'error'
4589
4594
4590 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4595 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4591
4596
4592 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4597 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4593 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4598 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4594
4599
4595 notice_read = Column('notice_read', Boolean, default=False)
4600 notice_read = Column('notice_read', Boolean, default=False)
4596
4601
4597 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4602 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4598 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4603 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4599
4604
4600 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4605 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4601 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4606 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4602
4607
4603 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4608 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4604 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4609 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4605
4610
4606 @classmethod
4611 @classmethod
4607 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4612 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4608
4613
4609 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4614 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4610 cls.NOTIFICATION_LEVEL_WARNING,
4615 cls.NOTIFICATION_LEVEL_WARNING,
4611 cls.NOTIFICATION_LEVEL_INFO]:
4616 cls.NOTIFICATION_LEVEL_INFO]:
4612 return
4617 return
4613
4618
4614 from rhodecode.model.user import UserModel
4619 from rhodecode.model.user import UserModel
4615 user = UserModel().get_user(user)
4620 user = UserModel().get_user(user)
4616
4621
4617 new_notice = UserNotice()
4622 new_notice = UserNotice()
4618 if not allow_duplicate:
4623 if not allow_duplicate:
4619 existing_msg = UserNotice().query() \
4624 existing_msg = UserNotice().query() \
4620 .filter(UserNotice.user == user) \
4625 .filter(UserNotice.user == user) \
4621 .filter(UserNotice.notice_body == body) \
4626 .filter(UserNotice.notice_body == body) \
4622 .filter(UserNotice.notice_read == false()) \
4627 .filter(UserNotice.notice_read == false()) \
4623 .scalar()
4628 .scalar()
4624 if existing_msg:
4629 if existing_msg:
4625 log.warning('Ignoring duplicate notice for user %s', user)
4630 log.warning('Ignoring duplicate notice for user %s', user)
4626 return
4631 return
4627
4632
4628 new_notice.user = user
4633 new_notice.user = user
4629 new_notice.notice_subject = subject
4634 new_notice.notice_subject = subject
4630 new_notice.notice_body = body
4635 new_notice.notice_body = body
4631 new_notice.notification_level = notice_level
4636 new_notice.notification_level = notice_level
4632 Session().add(new_notice)
4637 Session().add(new_notice)
4633 Session().commit()
4638 Session().commit()
4634
4639
4635
4640
4636 class Gist(Base, BaseModel):
4641 class Gist(Base, BaseModel):
4637 __tablename__ = 'gists'
4642 __tablename__ = 'gists'
4638 __table_args__ = (
4643 __table_args__ = (
4639 Index('g_gist_access_id_idx', 'gist_access_id'),
4644 Index('g_gist_access_id_idx', 'gist_access_id'),
4640 Index('g_created_on_idx', 'created_on'),
4645 Index('g_created_on_idx', 'created_on'),
4641 base_table_args
4646 base_table_args
4642 )
4647 )
4643
4648
4644 GIST_PUBLIC = u'public'
4649 GIST_PUBLIC = u'public'
4645 GIST_PRIVATE = u'private'
4650 GIST_PRIVATE = u'private'
4646 DEFAULT_FILENAME = u'gistfile1.txt'
4651 DEFAULT_FILENAME = u'gistfile1.txt'
4647
4652
4648 ACL_LEVEL_PUBLIC = u'acl_public'
4653 ACL_LEVEL_PUBLIC = u'acl_public'
4649 ACL_LEVEL_PRIVATE = u'acl_private'
4654 ACL_LEVEL_PRIVATE = u'acl_private'
4650
4655
4651 gist_id = Column('gist_id', Integer(), primary_key=True)
4656 gist_id = Column('gist_id', Integer(), primary_key=True)
4652 gist_access_id = Column('gist_access_id', Unicode(250))
4657 gist_access_id = Column('gist_access_id', Unicode(250))
4653 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4658 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4654 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4659 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4655 gist_expires = Column('gist_expires', Float(53), nullable=False)
4660 gist_expires = Column('gist_expires', Float(53), nullable=False)
4656 gist_type = Column('gist_type', Unicode(128), nullable=False)
4661 gist_type = Column('gist_type', Unicode(128), nullable=False)
4657 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4662 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4658 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4663 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4659 acl_level = Column('acl_level', Unicode(128), nullable=True)
4664 acl_level = Column('acl_level', Unicode(128), nullable=True)
4660
4665
4661 owner = relationship('User')
4666 owner = relationship('User')
4662
4667
4663 def __repr__(self):
4668 def __repr__(self):
4664 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4669 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4665
4670
4666 @hybrid_property
4671 @hybrid_property
4667 def description_safe(self):
4672 def description_safe(self):
4668 from rhodecode.lib import helpers as h
4673 from rhodecode.lib import helpers as h
4669 return h.escape(self.gist_description)
4674 return h.escape(self.gist_description)
4670
4675
4671 @classmethod
4676 @classmethod
4672 def get_or_404(cls, id_):
4677 def get_or_404(cls, id_):
4673 from pyramid.httpexceptions import HTTPNotFound
4678 from pyramid.httpexceptions import HTTPNotFound
4674
4679
4675 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4680 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4676 if not res:
4681 if not res:
4677 raise HTTPNotFound()
4682 raise HTTPNotFound()
4678 return res
4683 return res
4679
4684
4680 @classmethod
4685 @classmethod
4681 def get_by_access_id(cls, gist_access_id):
4686 def get_by_access_id(cls, gist_access_id):
4682 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4687 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4683
4688
4684 def gist_url(self):
4689 def gist_url(self):
4685 from rhodecode.model.gist import GistModel
4690 from rhodecode.model.gist import GistModel
4686 return GistModel().get_url(self)
4691 return GistModel().get_url(self)
4687
4692
4688 @classmethod
4693 @classmethod
4689 def base_path(cls):
4694 def base_path(cls):
4690 """
4695 """
4691 Returns base path when all gists are stored
4696 Returns base path when all gists are stored
4692
4697
4693 :param cls:
4698 :param cls:
4694 """
4699 """
4695 from rhodecode.model.gist import GIST_STORE_LOC
4700 from rhodecode.model.gist import GIST_STORE_LOC
4696 q = Session().query(RhodeCodeUi)\
4701 q = Session().query(RhodeCodeUi)\
4697 .filter(RhodeCodeUi.ui_key == URL_SEP)
4702 .filter(RhodeCodeUi.ui_key == URL_SEP)
4698 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4703 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4699 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4704 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4700
4705
4701 def get_api_data(self):
4706 def get_api_data(self):
4702 """
4707 """
4703 Common function for generating gist related data for API
4708 Common function for generating gist related data for API
4704 """
4709 """
4705 gist = self
4710 gist = self
4706 data = {
4711 data = {
4707 'gist_id': gist.gist_id,
4712 'gist_id': gist.gist_id,
4708 'type': gist.gist_type,
4713 'type': gist.gist_type,
4709 'access_id': gist.gist_access_id,
4714 'access_id': gist.gist_access_id,
4710 'description': gist.gist_description,
4715 'description': gist.gist_description,
4711 'url': gist.gist_url(),
4716 'url': gist.gist_url(),
4712 'expires': gist.gist_expires,
4717 'expires': gist.gist_expires,
4713 'created_on': gist.created_on,
4718 'created_on': gist.created_on,
4714 'modified_at': gist.modified_at,
4719 'modified_at': gist.modified_at,
4715 'content': None,
4720 'content': None,
4716 'acl_level': gist.acl_level,
4721 'acl_level': gist.acl_level,
4717 }
4722 }
4718 return data
4723 return data
4719
4724
4720 def __json__(self):
4725 def __json__(self):
4721 data = dict(
4726 data = dict(
4722 )
4727 )
4723 data.update(self.get_api_data())
4728 data.update(self.get_api_data())
4724 return data
4729 return data
4725 # SCM functions
4730 # SCM functions
4726
4731
4727 def scm_instance(self, **kwargs):
4732 def scm_instance(self, **kwargs):
4728 """
4733 """
4729 Get an instance of VCS Repository
4734 Get an instance of VCS Repository
4730
4735
4731 :param kwargs:
4736 :param kwargs:
4732 """
4737 """
4733 from rhodecode.model.gist import GistModel
4738 from rhodecode.model.gist import GistModel
4734 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4739 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4735 return get_vcs_instance(
4740 return get_vcs_instance(
4736 repo_path=safe_str(full_repo_path), create=False,
4741 repo_path=safe_str(full_repo_path), create=False,
4737 _vcs_alias=GistModel.vcs_backend)
4742 _vcs_alias=GistModel.vcs_backend)
4738
4743
4739
4744
4740 class ExternalIdentity(Base, BaseModel):
4745 class ExternalIdentity(Base, BaseModel):
4741 __tablename__ = 'external_identities'
4746 __tablename__ = 'external_identities'
4742 __table_args__ = (
4747 __table_args__ = (
4743 Index('local_user_id_idx', 'local_user_id'),
4748 Index('local_user_id_idx', 'local_user_id'),
4744 Index('external_id_idx', 'external_id'),
4749 Index('external_id_idx', 'external_id'),
4745 base_table_args
4750 base_table_args
4746 )
4751 )
4747
4752
4748 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4753 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4749 external_username = Column('external_username', Unicode(1024), default=u'')
4754 external_username = Column('external_username', Unicode(1024), default=u'')
4750 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4755 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4751 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4756 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4752 access_token = Column('access_token', String(1024), default=u'')
4757 access_token = Column('access_token', String(1024), default=u'')
4753 alt_token = Column('alt_token', String(1024), default=u'')
4758 alt_token = Column('alt_token', String(1024), default=u'')
4754 token_secret = Column('token_secret', String(1024), default=u'')
4759 token_secret = Column('token_secret', String(1024), default=u'')
4755
4760
4756 @classmethod
4761 @classmethod
4757 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4762 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4758 """
4763 """
4759 Returns ExternalIdentity instance based on search params
4764 Returns ExternalIdentity instance based on search params
4760
4765
4761 :param external_id:
4766 :param external_id:
4762 :param provider_name:
4767 :param provider_name:
4763 :return: ExternalIdentity
4768 :return: ExternalIdentity
4764 """
4769 """
4765 query = cls.query()
4770 query = cls.query()
4766 query = query.filter(cls.external_id == external_id)
4771 query = query.filter(cls.external_id == external_id)
4767 query = query.filter(cls.provider_name == provider_name)
4772 query = query.filter(cls.provider_name == provider_name)
4768 if local_user_id:
4773 if local_user_id:
4769 query = query.filter(cls.local_user_id == local_user_id)
4774 query = query.filter(cls.local_user_id == local_user_id)
4770 return query.first()
4775 return query.first()
4771
4776
4772 @classmethod
4777 @classmethod
4773 def user_by_external_id_and_provider(cls, external_id, provider_name):
4778 def user_by_external_id_and_provider(cls, external_id, provider_name):
4774 """
4779 """
4775 Returns User instance based on search params
4780 Returns User instance based on search params
4776
4781
4777 :param external_id:
4782 :param external_id:
4778 :param provider_name:
4783 :param provider_name:
4779 :return: User
4784 :return: User
4780 """
4785 """
4781 query = User.query()
4786 query = User.query()
4782 query = query.filter(cls.external_id == external_id)
4787 query = query.filter(cls.external_id == external_id)
4783 query = query.filter(cls.provider_name == provider_name)
4788 query = query.filter(cls.provider_name == provider_name)
4784 query = query.filter(User.user_id == cls.local_user_id)
4789 query = query.filter(User.user_id == cls.local_user_id)
4785 return query.first()
4790 return query.first()
4786
4791
4787 @classmethod
4792 @classmethod
4788 def by_local_user_id(cls, local_user_id):
4793 def by_local_user_id(cls, local_user_id):
4789 """
4794 """
4790 Returns all tokens for user
4795 Returns all tokens for user
4791
4796
4792 :param local_user_id:
4797 :param local_user_id:
4793 :return: ExternalIdentity
4798 :return: ExternalIdentity
4794 """
4799 """
4795 query = cls.query()
4800 query = cls.query()
4796 query = query.filter(cls.local_user_id == local_user_id)
4801 query = query.filter(cls.local_user_id == local_user_id)
4797 return query
4802 return query
4798
4803
4799 @classmethod
4804 @classmethod
4800 def load_provider_plugin(cls, plugin_id):
4805 def load_provider_plugin(cls, plugin_id):
4801 from rhodecode.authentication.base import loadplugin
4806 from rhodecode.authentication.base import loadplugin
4802 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4807 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4803 auth_plugin = loadplugin(_plugin_id)
4808 auth_plugin = loadplugin(_plugin_id)
4804 return auth_plugin
4809 return auth_plugin
4805
4810
4806
4811
4807 class Integration(Base, BaseModel):
4812 class Integration(Base, BaseModel):
4808 __tablename__ = 'integrations'
4813 __tablename__ = 'integrations'
4809 __table_args__ = (
4814 __table_args__ = (
4810 base_table_args
4815 base_table_args
4811 )
4816 )
4812
4817
4813 integration_id = Column('integration_id', Integer(), primary_key=True)
4818 integration_id = Column('integration_id', Integer(), primary_key=True)
4814 integration_type = Column('integration_type', String(255))
4819 integration_type = Column('integration_type', String(255))
4815 enabled = Column('enabled', Boolean(), nullable=False)
4820 enabled = Column('enabled', Boolean(), nullable=False)
4816 name = Column('name', String(255), nullable=False)
4821 name = Column('name', String(255), nullable=False)
4817 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4822 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4818 default=False)
4823 default=False)
4819
4824
4820 settings = Column(
4825 settings = Column(
4821 'settings_json', MutationObj.as_mutable(
4826 'settings_json', MutationObj.as_mutable(
4822 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4827 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4823 repo_id = Column(
4828 repo_id = Column(
4824 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4829 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4825 nullable=True, unique=None, default=None)
4830 nullable=True, unique=None, default=None)
4826 repo = relationship('Repository', lazy='joined')
4831 repo = relationship('Repository', lazy='joined')
4827
4832
4828 repo_group_id = Column(
4833 repo_group_id = Column(
4829 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4834 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4830 nullable=True, unique=None, default=None)
4835 nullable=True, unique=None, default=None)
4831 repo_group = relationship('RepoGroup', lazy='joined')
4836 repo_group = relationship('RepoGroup', lazy='joined')
4832
4837
4833 @property
4838 @property
4834 def scope(self):
4839 def scope(self):
4835 if self.repo:
4840 if self.repo:
4836 return repr(self.repo)
4841 return repr(self.repo)
4837 if self.repo_group:
4842 if self.repo_group:
4838 if self.child_repos_only:
4843 if self.child_repos_only:
4839 return repr(self.repo_group) + ' (child repos only)'
4844 return repr(self.repo_group) + ' (child repos only)'
4840 else:
4845 else:
4841 return repr(self.repo_group) + ' (recursive)'
4846 return repr(self.repo_group) + ' (recursive)'
4842 if self.child_repos_only:
4847 if self.child_repos_only:
4843 return 'root_repos'
4848 return 'root_repos'
4844 return 'global'
4849 return 'global'
4845
4850
4846 def __repr__(self):
4851 def __repr__(self):
4847 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4852 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4848
4853
4849
4854
4850 class RepoReviewRuleUser(Base, BaseModel):
4855 class RepoReviewRuleUser(Base, BaseModel):
4851 __tablename__ = 'repo_review_rules_users'
4856 __tablename__ = 'repo_review_rules_users'
4852 __table_args__ = (
4857 __table_args__ = (
4853 base_table_args
4858 base_table_args
4854 )
4859 )
4855
4860
4856 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4861 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4857 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4862 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4858 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4863 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4859 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4864 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4860 user = relationship('User')
4865 user = relationship('User')
4861
4866
4862 def rule_data(self):
4867 def rule_data(self):
4863 return {
4868 return {
4864 'mandatory': self.mandatory
4869 'mandatory': self.mandatory
4865 }
4870 }
4866
4871
4867
4872
4868 class RepoReviewRuleUserGroup(Base, BaseModel):
4873 class RepoReviewRuleUserGroup(Base, BaseModel):
4869 __tablename__ = 'repo_review_rules_users_groups'
4874 __tablename__ = 'repo_review_rules_users_groups'
4870 __table_args__ = (
4875 __table_args__ = (
4871 base_table_args
4876 base_table_args
4872 )
4877 )
4873
4878
4874 VOTE_RULE_ALL = -1
4879 VOTE_RULE_ALL = -1
4875
4880
4876 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4881 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4877 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4882 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4878 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4883 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4879 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4884 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4880 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4885 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4881 users_group = relationship('UserGroup')
4886 users_group = relationship('UserGroup')
4882
4887
4883 def rule_data(self):
4888 def rule_data(self):
4884 return {
4889 return {
4885 'mandatory': self.mandatory,
4890 'mandatory': self.mandatory,
4886 'vote_rule': self.vote_rule
4891 'vote_rule': self.vote_rule
4887 }
4892 }
4888
4893
4889 @property
4894 @property
4890 def vote_rule_label(self):
4895 def vote_rule_label(self):
4891 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4896 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4892 return 'all must vote'
4897 return 'all must vote'
4893 else:
4898 else:
4894 return 'min. vote {}'.format(self.vote_rule)
4899 return 'min. vote {}'.format(self.vote_rule)
4895
4900
4896
4901
4897 class RepoReviewRule(Base, BaseModel):
4902 class RepoReviewRule(Base, BaseModel):
4898 __tablename__ = 'repo_review_rules'
4903 __tablename__ = 'repo_review_rules'
4899 __table_args__ = (
4904 __table_args__ = (
4900 base_table_args
4905 base_table_args
4901 )
4906 )
4902
4907
4903 repo_review_rule_id = Column(
4908 repo_review_rule_id = Column(
4904 'repo_review_rule_id', Integer(), primary_key=True)
4909 'repo_review_rule_id', Integer(), primary_key=True)
4905 repo_id = Column(
4910 repo_id = Column(
4906 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4911 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4907 repo = relationship('Repository', backref='review_rules')
4912 repo = relationship('Repository', backref='review_rules')
4908
4913
4909 review_rule_name = Column('review_rule_name', String(255))
4914 review_rule_name = Column('review_rule_name', String(255))
4910 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4915 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4911 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4916 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4912 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4917 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4913
4918
4914 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4919 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4915 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4920 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4916 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4921 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4917 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4922 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4918
4923
4919 rule_users = relationship('RepoReviewRuleUser')
4924 rule_users = relationship('RepoReviewRuleUser')
4920 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4925 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4921
4926
4922 def _validate_pattern(self, value):
4927 def _validate_pattern(self, value):
4923 re.compile('^' + glob2re(value) + '$')
4928 re.compile('^' + glob2re(value) + '$')
4924
4929
4925 @hybrid_property
4930 @hybrid_property
4926 def source_branch_pattern(self):
4931 def source_branch_pattern(self):
4927 return self._branch_pattern or '*'
4932 return self._branch_pattern or '*'
4928
4933
4929 @source_branch_pattern.setter
4934 @source_branch_pattern.setter
4930 def source_branch_pattern(self, value):
4935 def source_branch_pattern(self, value):
4931 self._validate_pattern(value)
4936 self._validate_pattern(value)
4932 self._branch_pattern = value or '*'
4937 self._branch_pattern = value or '*'
4933
4938
4934 @hybrid_property
4939 @hybrid_property
4935 def target_branch_pattern(self):
4940 def target_branch_pattern(self):
4936 return self._target_branch_pattern or '*'
4941 return self._target_branch_pattern or '*'
4937
4942
4938 @target_branch_pattern.setter
4943 @target_branch_pattern.setter
4939 def target_branch_pattern(self, value):
4944 def target_branch_pattern(self, value):
4940 self._validate_pattern(value)
4945 self._validate_pattern(value)
4941 self._target_branch_pattern = value or '*'
4946 self._target_branch_pattern = value or '*'
4942
4947
4943 @hybrid_property
4948 @hybrid_property
4944 def file_pattern(self):
4949 def file_pattern(self):
4945 return self._file_pattern or '*'
4950 return self._file_pattern or '*'
4946
4951
4947 @file_pattern.setter
4952 @file_pattern.setter
4948 def file_pattern(self, value):
4953 def file_pattern(self, value):
4949 self._validate_pattern(value)
4954 self._validate_pattern(value)
4950 self._file_pattern = value or '*'
4955 self._file_pattern = value or '*'
4951
4956
4952 def matches(self, source_branch, target_branch, files_changed):
4957 def matches(self, source_branch, target_branch, files_changed):
4953 """
4958 """
4954 Check if this review rule matches a branch/files in a pull request
4959 Check if this review rule matches a branch/files in a pull request
4955
4960
4956 :param source_branch: source branch name for the commit
4961 :param source_branch: source branch name for the commit
4957 :param target_branch: target branch name for the commit
4962 :param target_branch: target branch name for the commit
4958 :param files_changed: list of file paths changed in the pull request
4963 :param files_changed: list of file paths changed in the pull request
4959 """
4964 """
4960
4965
4961 source_branch = source_branch or ''
4966 source_branch = source_branch or ''
4962 target_branch = target_branch or ''
4967 target_branch = target_branch or ''
4963 files_changed = files_changed or []
4968 files_changed = files_changed or []
4964
4969
4965 branch_matches = True
4970 branch_matches = True
4966 if source_branch or target_branch:
4971 if source_branch or target_branch:
4967 if self.source_branch_pattern == '*':
4972 if self.source_branch_pattern == '*':
4968 source_branch_match = True
4973 source_branch_match = True
4969 else:
4974 else:
4970 if self.source_branch_pattern.startswith('re:'):
4975 if self.source_branch_pattern.startswith('re:'):
4971 source_pattern = self.source_branch_pattern[3:]
4976 source_pattern = self.source_branch_pattern[3:]
4972 else:
4977 else:
4973 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4978 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4974 source_branch_regex = re.compile(source_pattern)
4979 source_branch_regex = re.compile(source_pattern)
4975 source_branch_match = bool(source_branch_regex.search(source_branch))
4980 source_branch_match = bool(source_branch_regex.search(source_branch))
4976 if self.target_branch_pattern == '*':
4981 if self.target_branch_pattern == '*':
4977 target_branch_match = True
4982 target_branch_match = True
4978 else:
4983 else:
4979 if self.target_branch_pattern.startswith('re:'):
4984 if self.target_branch_pattern.startswith('re:'):
4980 target_pattern = self.target_branch_pattern[3:]
4985 target_pattern = self.target_branch_pattern[3:]
4981 else:
4986 else:
4982 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4987 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4983 target_branch_regex = re.compile(target_pattern)
4988 target_branch_regex = re.compile(target_pattern)
4984 target_branch_match = bool(target_branch_regex.search(target_branch))
4989 target_branch_match = bool(target_branch_regex.search(target_branch))
4985
4990
4986 branch_matches = source_branch_match and target_branch_match
4991 branch_matches = source_branch_match and target_branch_match
4987
4992
4988 files_matches = True
4993 files_matches = True
4989 if self.file_pattern != '*':
4994 if self.file_pattern != '*':
4990 files_matches = False
4995 files_matches = False
4991 if self.file_pattern.startswith('re:'):
4996 if self.file_pattern.startswith('re:'):
4992 file_pattern = self.file_pattern[3:]
4997 file_pattern = self.file_pattern[3:]
4993 else:
4998 else:
4994 file_pattern = glob2re(self.file_pattern)
4999 file_pattern = glob2re(self.file_pattern)
4995 file_regex = re.compile(file_pattern)
5000 file_regex = re.compile(file_pattern)
4996 for file_data in files_changed:
5001 for file_data in files_changed:
4997 filename = file_data.get('filename')
5002 filename = file_data.get('filename')
4998
5003
4999 if file_regex.search(filename):
5004 if file_regex.search(filename):
5000 files_matches = True
5005 files_matches = True
5001 break
5006 break
5002
5007
5003 return branch_matches and files_matches
5008 return branch_matches and files_matches
5004
5009
5005 @property
5010 @property
5006 def review_users(self):
5011 def review_users(self):
5007 """ Returns the users which this rule applies to """
5012 """ Returns the users which this rule applies to """
5008
5013
5009 users = collections.OrderedDict()
5014 users = collections.OrderedDict()
5010
5015
5011 for rule_user in self.rule_users:
5016 for rule_user in self.rule_users:
5012 if rule_user.user.active:
5017 if rule_user.user.active:
5013 if rule_user.user not in users:
5018 if rule_user.user not in users:
5014 users[rule_user.user.username] = {
5019 users[rule_user.user.username] = {
5015 'user': rule_user.user,
5020 'user': rule_user.user,
5016 'source': 'user',
5021 'source': 'user',
5017 'source_data': {},
5022 'source_data': {},
5018 'data': rule_user.rule_data()
5023 'data': rule_user.rule_data()
5019 }
5024 }
5020
5025
5021 for rule_user_group in self.rule_user_groups:
5026 for rule_user_group in self.rule_user_groups:
5022 source_data = {
5027 source_data = {
5023 'user_group_id': rule_user_group.users_group.users_group_id,
5028 'user_group_id': rule_user_group.users_group.users_group_id,
5024 'name': rule_user_group.users_group.users_group_name,
5029 'name': rule_user_group.users_group.users_group_name,
5025 'members': len(rule_user_group.users_group.members)
5030 'members': len(rule_user_group.users_group.members)
5026 }
5031 }
5027 for member in rule_user_group.users_group.members:
5032 for member in rule_user_group.users_group.members:
5028 if member.user.active:
5033 if member.user.active:
5029 key = member.user.username
5034 key = member.user.username
5030 if key in users:
5035 if key in users:
5031 # skip this member as we have him already
5036 # skip this member as we have him already
5032 # this prevents from override the "first" matched
5037 # this prevents from override the "first" matched
5033 # users with duplicates in multiple groups
5038 # users with duplicates in multiple groups
5034 continue
5039 continue
5035
5040
5036 users[key] = {
5041 users[key] = {
5037 'user': member.user,
5042 'user': member.user,
5038 'source': 'user_group',
5043 'source': 'user_group',
5039 'source_data': source_data,
5044 'source_data': source_data,
5040 'data': rule_user_group.rule_data()
5045 'data': rule_user_group.rule_data()
5041 }
5046 }
5042
5047
5043 return users
5048 return users
5044
5049
5045 def user_group_vote_rule(self, user_id):
5050 def user_group_vote_rule(self, user_id):
5046
5051
5047 rules = []
5052 rules = []
5048 if not self.rule_user_groups:
5053 if not self.rule_user_groups:
5049 return rules
5054 return rules
5050
5055
5051 for user_group in self.rule_user_groups:
5056 for user_group in self.rule_user_groups:
5052 user_group_members = [x.user_id for x in user_group.users_group.members]
5057 user_group_members = [x.user_id for x in user_group.users_group.members]
5053 if user_id in user_group_members:
5058 if user_id in user_group_members:
5054 rules.append(user_group)
5059 rules.append(user_group)
5055 return rules
5060 return rules
5056
5061
5057 def __repr__(self):
5062 def __repr__(self):
5058 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5063 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5059 self.repo_review_rule_id, self.repo)
5064 self.repo_review_rule_id, self.repo)
5060
5065
5061
5066
5062 class ScheduleEntry(Base, BaseModel):
5067 class ScheduleEntry(Base, BaseModel):
5063 __tablename__ = 'schedule_entries'
5068 __tablename__ = 'schedule_entries'
5064 __table_args__ = (
5069 __table_args__ = (
5065 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5070 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5066 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5071 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5067 base_table_args,
5072 base_table_args,
5068 )
5073 )
5069
5074
5070 schedule_types = ['crontab', 'timedelta', 'integer']
5075 schedule_types = ['crontab', 'timedelta', 'integer']
5071 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5076 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5072
5077
5073 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5078 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5074 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5079 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5075 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5080 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5076
5081
5077 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5082 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5078 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5083 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5079
5084
5080 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5085 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5081 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5086 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5082
5087
5083 # task
5088 # task
5084 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5089 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5085 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5090 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5086 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5091 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5087 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5092 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5088
5093
5089 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5094 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5090 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5095 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5091
5096
5092 @hybrid_property
5097 @hybrid_property
5093 def schedule_type(self):
5098 def schedule_type(self):
5094 return self._schedule_type
5099 return self._schedule_type
5095
5100
5096 @schedule_type.setter
5101 @schedule_type.setter
5097 def schedule_type(self, val):
5102 def schedule_type(self, val):
5098 if val not in self.schedule_types:
5103 if val not in self.schedule_types:
5099 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5104 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5100 val, self.schedule_type))
5105 val, self.schedule_type))
5101
5106
5102 self._schedule_type = val
5107 self._schedule_type = val
5103
5108
5104 @classmethod
5109 @classmethod
5105 def get_uid(cls, obj):
5110 def get_uid(cls, obj):
5106 args = obj.task_args
5111 args = obj.task_args
5107 kwargs = obj.task_kwargs
5112 kwargs = obj.task_kwargs
5108 if isinstance(args, JsonRaw):
5113 if isinstance(args, JsonRaw):
5109 try:
5114 try:
5110 args = json.loads(args)
5115 args = json.loads(args)
5111 except ValueError:
5116 except ValueError:
5112 args = tuple()
5117 args = tuple()
5113
5118
5114 if isinstance(kwargs, JsonRaw):
5119 if isinstance(kwargs, JsonRaw):
5115 try:
5120 try:
5116 kwargs = json.loads(kwargs)
5121 kwargs = json.loads(kwargs)
5117 except ValueError:
5122 except ValueError:
5118 kwargs = dict()
5123 kwargs = dict()
5119
5124
5120 dot_notation = obj.task_dot_notation
5125 dot_notation = obj.task_dot_notation
5121 val = '.'.join(map(safe_str, [
5126 val = '.'.join(map(safe_str, [
5122 sorted(dot_notation), args, sorted(kwargs.items())]))
5127 sorted(dot_notation), args, sorted(kwargs.items())]))
5123 return hashlib.sha1(val).hexdigest()
5128 return hashlib.sha1(val).hexdigest()
5124
5129
5125 @classmethod
5130 @classmethod
5126 def get_by_schedule_name(cls, schedule_name):
5131 def get_by_schedule_name(cls, schedule_name):
5127 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5132 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5128
5133
5129 @classmethod
5134 @classmethod
5130 def get_by_schedule_id(cls, schedule_id):
5135 def get_by_schedule_id(cls, schedule_id):
5131 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5136 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5132
5137
5133 @property
5138 @property
5134 def task(self):
5139 def task(self):
5135 return self.task_dot_notation
5140 return self.task_dot_notation
5136
5141
5137 @property
5142 @property
5138 def schedule(self):
5143 def schedule(self):
5139 from rhodecode.lib.celerylib.utils import raw_2_schedule
5144 from rhodecode.lib.celerylib.utils import raw_2_schedule
5140 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5145 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5141 return schedule
5146 return schedule
5142
5147
5143 @property
5148 @property
5144 def args(self):
5149 def args(self):
5145 try:
5150 try:
5146 return list(self.task_args or [])
5151 return list(self.task_args or [])
5147 except ValueError:
5152 except ValueError:
5148 return list()
5153 return list()
5149
5154
5150 @property
5155 @property
5151 def kwargs(self):
5156 def kwargs(self):
5152 try:
5157 try:
5153 return dict(self.task_kwargs or {})
5158 return dict(self.task_kwargs or {})
5154 except ValueError:
5159 except ValueError:
5155 return dict()
5160 return dict()
5156
5161
5157 def _as_raw(self, val):
5162 def _as_raw(self, val):
5158 if hasattr(val, 'de_coerce'):
5163 if hasattr(val, 'de_coerce'):
5159 val = val.de_coerce()
5164 val = val.de_coerce()
5160 if val:
5165 if val:
5161 val = json.dumps(val)
5166 val = json.dumps(val)
5162
5167
5163 return val
5168 return val
5164
5169
5165 @property
5170 @property
5166 def schedule_definition_raw(self):
5171 def schedule_definition_raw(self):
5167 return self._as_raw(self.schedule_definition)
5172 return self._as_raw(self.schedule_definition)
5168
5173
5169 @property
5174 @property
5170 def args_raw(self):
5175 def args_raw(self):
5171 return self._as_raw(self.task_args)
5176 return self._as_raw(self.task_args)
5172
5177
5173 @property
5178 @property
5174 def kwargs_raw(self):
5179 def kwargs_raw(self):
5175 return self._as_raw(self.task_kwargs)
5180 return self._as_raw(self.task_kwargs)
5176
5181
5177 def __repr__(self):
5182 def __repr__(self):
5178 return '<DB:ScheduleEntry({}:{})>'.format(
5183 return '<DB:ScheduleEntry({}:{})>'.format(
5179 self.schedule_entry_id, self.schedule_name)
5184 self.schedule_entry_id, self.schedule_name)
5180
5185
5181
5186
5182 @event.listens_for(ScheduleEntry, 'before_update')
5187 @event.listens_for(ScheduleEntry, 'before_update')
5183 def update_task_uid(mapper, connection, target):
5188 def update_task_uid(mapper, connection, target):
5184 target.task_uid = ScheduleEntry.get_uid(target)
5189 target.task_uid = ScheduleEntry.get_uid(target)
5185
5190
5186
5191
5187 @event.listens_for(ScheduleEntry, 'before_insert')
5192 @event.listens_for(ScheduleEntry, 'before_insert')
5188 def set_task_uid(mapper, connection, target):
5193 def set_task_uid(mapper, connection, target):
5189 target.task_uid = ScheduleEntry.get_uid(target)
5194 target.task_uid = ScheduleEntry.get_uid(target)
5190
5195
5191
5196
5192 class _BaseBranchPerms(BaseModel):
5197 class _BaseBranchPerms(BaseModel):
5193 @classmethod
5198 @classmethod
5194 def compute_hash(cls, value):
5199 def compute_hash(cls, value):
5195 return sha1_safe(value)
5200 return sha1_safe(value)
5196
5201
5197 @hybrid_property
5202 @hybrid_property
5198 def branch_pattern(self):
5203 def branch_pattern(self):
5199 return self._branch_pattern or '*'
5204 return self._branch_pattern or '*'
5200
5205
5201 @hybrid_property
5206 @hybrid_property
5202 def branch_hash(self):
5207 def branch_hash(self):
5203 return self._branch_hash
5208 return self._branch_hash
5204
5209
5205 def _validate_glob(self, value):
5210 def _validate_glob(self, value):
5206 re.compile('^' + glob2re(value) + '$')
5211 re.compile('^' + glob2re(value) + '$')
5207
5212
5208 @branch_pattern.setter
5213 @branch_pattern.setter
5209 def branch_pattern(self, value):
5214 def branch_pattern(self, value):
5210 self._validate_glob(value)
5215 self._validate_glob(value)
5211 self._branch_pattern = value or '*'
5216 self._branch_pattern = value or '*'
5212 # set the Hash when setting the branch pattern
5217 # set the Hash when setting the branch pattern
5213 self._branch_hash = self.compute_hash(self._branch_pattern)
5218 self._branch_hash = self.compute_hash(self._branch_pattern)
5214
5219
5215 def matches(self, branch):
5220 def matches(self, branch):
5216 """
5221 """
5217 Check if this the branch matches entry
5222 Check if this the branch matches entry
5218
5223
5219 :param branch: branch name for the commit
5224 :param branch: branch name for the commit
5220 """
5225 """
5221
5226
5222 branch = branch or ''
5227 branch = branch or ''
5223
5228
5224 branch_matches = True
5229 branch_matches = True
5225 if branch:
5230 if branch:
5226 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5231 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5227 branch_matches = bool(branch_regex.search(branch))
5232 branch_matches = bool(branch_regex.search(branch))
5228
5233
5229 return branch_matches
5234 return branch_matches
5230
5235
5231
5236
5232 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5237 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5233 __tablename__ = 'user_to_repo_branch_permissions'
5238 __tablename__ = 'user_to_repo_branch_permissions'
5234 __table_args__ = (
5239 __table_args__ = (
5235 base_table_args
5240 base_table_args
5236 )
5241 )
5237
5242
5238 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5243 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5239
5244
5240 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5245 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5241 repo = relationship('Repository', backref='user_branch_perms')
5246 repo = relationship('Repository', backref='user_branch_perms')
5242
5247
5243 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5248 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5244 permission = relationship('Permission')
5249 permission = relationship('Permission')
5245
5250
5246 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5251 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5247 user_repo_to_perm = relationship('UserRepoToPerm')
5252 user_repo_to_perm = relationship('UserRepoToPerm')
5248
5253
5249 rule_order = Column('rule_order', Integer(), nullable=False)
5254 rule_order = Column('rule_order', Integer(), nullable=False)
5250 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5255 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5251 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5256 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5252
5257
5253 def __unicode__(self):
5258 def __unicode__(self):
5254 return u'<UserBranchPermission(%s => %r)>' % (
5259 return u'<UserBranchPermission(%s => %r)>' % (
5255 self.user_repo_to_perm, self.branch_pattern)
5260 self.user_repo_to_perm, self.branch_pattern)
5256
5261
5257
5262
5258 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5263 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5259 __tablename__ = 'user_group_to_repo_branch_permissions'
5264 __tablename__ = 'user_group_to_repo_branch_permissions'
5260 __table_args__ = (
5265 __table_args__ = (
5261 base_table_args
5266 base_table_args
5262 )
5267 )
5263
5268
5264 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5269 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5265
5270
5266 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5271 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5267 repo = relationship('Repository', backref='user_group_branch_perms')
5272 repo = relationship('Repository', backref='user_group_branch_perms')
5268
5273
5269 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5274 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5270 permission = relationship('Permission')
5275 permission = relationship('Permission')
5271
5276
5272 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5277 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5273 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5278 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5274
5279
5275 rule_order = Column('rule_order', Integer(), nullable=False)
5280 rule_order = Column('rule_order', Integer(), nullable=False)
5276 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5281 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5277 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5282 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5278
5283
5279 def __unicode__(self):
5284 def __unicode__(self):
5280 return u'<UserBranchPermission(%s => %r)>' % (
5285 return u'<UserBranchPermission(%s => %r)>' % (
5281 self.user_group_repo_to_perm, self.branch_pattern)
5286 self.user_group_repo_to_perm, self.branch_pattern)
5282
5287
5283
5288
5284 class UserBookmark(Base, BaseModel):
5289 class UserBookmark(Base, BaseModel):
5285 __tablename__ = 'user_bookmarks'
5290 __tablename__ = 'user_bookmarks'
5286 __table_args__ = (
5291 __table_args__ = (
5287 UniqueConstraint('user_id', 'bookmark_repo_id'),
5292 UniqueConstraint('user_id', 'bookmark_repo_id'),
5288 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5293 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5289 UniqueConstraint('user_id', 'bookmark_position'),
5294 UniqueConstraint('user_id', 'bookmark_position'),
5290 base_table_args
5295 base_table_args
5291 )
5296 )
5292
5297
5293 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5298 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5294 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5299 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5295 position = Column("bookmark_position", Integer(), nullable=False)
5300 position = Column("bookmark_position", Integer(), nullable=False)
5296 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5301 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5297 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5302 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5298 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5303 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5299
5304
5300 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5305 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5301 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5306 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5302
5307
5303 user = relationship("User")
5308 user = relationship("User")
5304
5309
5305 repository = relationship("Repository")
5310 repository = relationship("Repository")
5306 repository_group = relationship("RepoGroup")
5311 repository_group = relationship("RepoGroup")
5307
5312
5308 @classmethod
5313 @classmethod
5309 def get_by_position_for_user(cls, position, user_id):
5314 def get_by_position_for_user(cls, position, user_id):
5310 return cls.query() \
5315 return cls.query() \
5311 .filter(UserBookmark.user_id == user_id) \
5316 .filter(UserBookmark.user_id == user_id) \
5312 .filter(UserBookmark.position == position).scalar()
5317 .filter(UserBookmark.position == position).scalar()
5313
5318
5314 @classmethod
5319 @classmethod
5315 def get_bookmarks_for_user(cls, user_id, cache=True):
5320 def get_bookmarks_for_user(cls, user_id, cache=True):
5316 bookmarks = cls.query() \
5321 bookmarks = cls.query() \
5317 .filter(UserBookmark.user_id == user_id) \
5322 .filter(UserBookmark.user_id == user_id) \
5318 .options(joinedload(UserBookmark.repository)) \
5323 .options(joinedload(UserBookmark.repository)) \
5319 .options(joinedload(UserBookmark.repository_group)) \
5324 .options(joinedload(UserBookmark.repository_group)) \
5320 .order_by(UserBookmark.position.asc())
5325 .order_by(UserBookmark.position.asc())
5321
5326
5322 if cache:
5327 if cache:
5323 bookmarks = bookmarks.options(
5328 bookmarks = bookmarks.options(
5324 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5329 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5325 )
5330 )
5326
5331
5327 return bookmarks.all()
5332 return bookmarks.all()
5328
5333
5329 def __unicode__(self):
5334 def __unicode__(self):
5330 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5335 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5331
5336
5332
5337
5333 class FileStore(Base, BaseModel):
5338 class FileStore(Base, BaseModel):
5334 __tablename__ = 'file_store'
5339 __tablename__ = 'file_store'
5335 __table_args__ = (
5340 __table_args__ = (
5336 base_table_args
5341 base_table_args
5337 )
5342 )
5338
5343
5339 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5344 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5340 file_uid = Column('file_uid', String(1024), nullable=False)
5345 file_uid = Column('file_uid', String(1024), nullable=False)
5341 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5346 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5342 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5347 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5343 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5348 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5344
5349
5345 # sha256 hash
5350 # sha256 hash
5346 file_hash = Column('file_hash', String(512), nullable=False)
5351 file_hash = Column('file_hash', String(512), nullable=False)
5347 file_size = Column('file_size', BigInteger(), nullable=False)
5352 file_size = Column('file_size', BigInteger(), nullable=False)
5348
5353
5349 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5354 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5350 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5355 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5351 accessed_count = Column('accessed_count', Integer(), default=0)
5356 accessed_count = Column('accessed_count', Integer(), default=0)
5352
5357
5353 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5358 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5354
5359
5355 # if repo/repo_group reference is set, check for permissions
5360 # if repo/repo_group reference is set, check for permissions
5356 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5361 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5357
5362
5358 # hidden defines an attachment that should be hidden from showing in artifact listing
5363 # hidden defines an attachment that should be hidden from showing in artifact listing
5359 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5364 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5360
5365
5361 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5366 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5362 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5367 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5363
5368
5364 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5369 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5365
5370
5366 # scope limited to user, which requester have access to
5371 # scope limited to user, which requester have access to
5367 scope_user_id = Column(
5372 scope_user_id = Column(
5368 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5373 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5369 nullable=True, unique=None, default=None)
5374 nullable=True, unique=None, default=None)
5370 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5375 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5371
5376
5372 # scope limited to user group, which requester have access to
5377 # scope limited to user group, which requester have access to
5373 scope_user_group_id = Column(
5378 scope_user_group_id = Column(
5374 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5379 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5375 nullable=True, unique=None, default=None)
5380 nullable=True, unique=None, default=None)
5376 user_group = relationship('UserGroup', lazy='joined')
5381 user_group = relationship('UserGroup', lazy='joined')
5377
5382
5378 # scope limited to repo, which requester have access to
5383 # scope limited to repo, which requester have access to
5379 scope_repo_id = Column(
5384 scope_repo_id = Column(
5380 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5385 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5381 nullable=True, unique=None, default=None)
5386 nullable=True, unique=None, default=None)
5382 repo = relationship('Repository', lazy='joined')
5387 repo = relationship('Repository', lazy='joined')
5383
5388
5384 # scope limited to repo group, which requester have access to
5389 # scope limited to repo group, which requester have access to
5385 scope_repo_group_id = Column(
5390 scope_repo_group_id = Column(
5386 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5391 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5387 nullable=True, unique=None, default=None)
5392 nullable=True, unique=None, default=None)
5388 repo_group = relationship('RepoGroup', lazy='joined')
5393 repo_group = relationship('RepoGroup', lazy='joined')
5389
5394
5390 @classmethod
5395 @classmethod
5391 def get_by_store_uid(cls, file_store_uid):
5396 def get_by_store_uid(cls, file_store_uid):
5392 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5397 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5393
5398
5394 @classmethod
5399 @classmethod
5395 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5400 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5396 file_description='', enabled=True, hidden=False, check_acl=True,
5401 file_description='', enabled=True, hidden=False, check_acl=True,
5397 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5402 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5398
5403
5399 store_entry = FileStore()
5404 store_entry = FileStore()
5400 store_entry.file_uid = file_uid
5405 store_entry.file_uid = file_uid
5401 store_entry.file_display_name = file_display_name
5406 store_entry.file_display_name = file_display_name
5402 store_entry.file_org_name = filename
5407 store_entry.file_org_name = filename
5403 store_entry.file_size = file_size
5408 store_entry.file_size = file_size
5404 store_entry.file_hash = file_hash
5409 store_entry.file_hash = file_hash
5405 store_entry.file_description = file_description
5410 store_entry.file_description = file_description
5406
5411
5407 store_entry.check_acl = check_acl
5412 store_entry.check_acl = check_acl
5408 store_entry.enabled = enabled
5413 store_entry.enabled = enabled
5409 store_entry.hidden = hidden
5414 store_entry.hidden = hidden
5410
5415
5411 store_entry.user_id = user_id
5416 store_entry.user_id = user_id
5412 store_entry.scope_user_id = scope_user_id
5417 store_entry.scope_user_id = scope_user_id
5413 store_entry.scope_repo_id = scope_repo_id
5418 store_entry.scope_repo_id = scope_repo_id
5414 store_entry.scope_repo_group_id = scope_repo_group_id
5419 store_entry.scope_repo_group_id = scope_repo_group_id
5415
5420
5416 return store_entry
5421 return store_entry
5417
5422
5418 @classmethod
5423 @classmethod
5419 def store_metadata(cls, file_store_id, args, commit=True):
5424 def store_metadata(cls, file_store_id, args, commit=True):
5420 file_store = FileStore.get(file_store_id)
5425 file_store = FileStore.get(file_store_id)
5421 if file_store is None:
5426 if file_store is None:
5422 return
5427 return
5423
5428
5424 for section, key, value, value_type in args:
5429 for section, key, value, value_type in args:
5425 has_key = FileStoreMetadata().query() \
5430 has_key = FileStoreMetadata().query() \
5426 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5431 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5427 .filter(FileStoreMetadata.file_store_meta_section == section) \
5432 .filter(FileStoreMetadata.file_store_meta_section == section) \
5428 .filter(FileStoreMetadata.file_store_meta_key == key) \
5433 .filter(FileStoreMetadata.file_store_meta_key == key) \
5429 .scalar()
5434 .scalar()
5430 if has_key:
5435 if has_key:
5431 msg = 'key `{}` already defined under section `{}` for this file.'\
5436 msg = 'key `{}` already defined under section `{}` for this file.'\
5432 .format(key, section)
5437 .format(key, section)
5433 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5438 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5434
5439
5435 # NOTE(marcink): raises ArtifactMetadataBadValueType
5440 # NOTE(marcink): raises ArtifactMetadataBadValueType
5436 FileStoreMetadata.valid_value_type(value_type)
5441 FileStoreMetadata.valid_value_type(value_type)
5437
5442
5438 meta_entry = FileStoreMetadata()
5443 meta_entry = FileStoreMetadata()
5439 meta_entry.file_store = file_store
5444 meta_entry.file_store = file_store
5440 meta_entry.file_store_meta_section = section
5445 meta_entry.file_store_meta_section = section
5441 meta_entry.file_store_meta_key = key
5446 meta_entry.file_store_meta_key = key
5442 meta_entry.file_store_meta_value_type = value_type
5447 meta_entry.file_store_meta_value_type = value_type
5443 meta_entry.file_store_meta_value = value
5448 meta_entry.file_store_meta_value = value
5444
5449
5445 Session().add(meta_entry)
5450 Session().add(meta_entry)
5446
5451
5447 try:
5452 try:
5448 if commit:
5453 if commit:
5449 Session().commit()
5454 Session().commit()
5450 except IntegrityError:
5455 except IntegrityError:
5451 Session().rollback()
5456 Session().rollback()
5452 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5457 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5453
5458
5454 @classmethod
5459 @classmethod
5455 def bump_access_counter(cls, file_uid, commit=True):
5460 def bump_access_counter(cls, file_uid, commit=True):
5456 FileStore().query()\
5461 FileStore().query()\
5457 .filter(FileStore.file_uid == file_uid)\
5462 .filter(FileStore.file_uid == file_uid)\
5458 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5463 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5459 FileStore.accessed_on: datetime.datetime.now()})
5464 FileStore.accessed_on: datetime.datetime.now()})
5460 if commit:
5465 if commit:
5461 Session().commit()
5466 Session().commit()
5462
5467
5463 def __json__(self):
5468 def __json__(self):
5464 data = {
5469 data = {
5465 'filename': self.file_display_name,
5470 'filename': self.file_display_name,
5466 'filename_org': self.file_org_name,
5471 'filename_org': self.file_org_name,
5467 'file_uid': self.file_uid,
5472 'file_uid': self.file_uid,
5468 'description': self.file_description,
5473 'description': self.file_description,
5469 'hidden': self.hidden,
5474 'hidden': self.hidden,
5470 'size': self.file_size,
5475 'size': self.file_size,
5471 'created_on': self.created_on,
5476 'created_on': self.created_on,
5472 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5477 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5473 'downloaded_times': self.accessed_count,
5478 'downloaded_times': self.accessed_count,
5474 'sha256': self.file_hash,
5479 'sha256': self.file_hash,
5475 'metadata': self.file_metadata,
5480 'metadata': self.file_metadata,
5476 }
5481 }
5477
5482
5478 return data
5483 return data
5479
5484
5480 def __repr__(self):
5485 def __repr__(self):
5481 return '<FileStore({})>'.format(self.file_store_id)
5486 return '<FileStore({})>'.format(self.file_store_id)
5482
5487
5483
5488
5484 class FileStoreMetadata(Base, BaseModel):
5489 class FileStoreMetadata(Base, BaseModel):
5485 __tablename__ = 'file_store_metadata'
5490 __tablename__ = 'file_store_metadata'
5486 __table_args__ = (
5491 __table_args__ = (
5487 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5492 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5488 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5493 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5489 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5494 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5490 base_table_args
5495 base_table_args
5491 )
5496 )
5492 SETTINGS_TYPES = {
5497 SETTINGS_TYPES = {
5493 'str': safe_str,
5498 'str': safe_str,
5494 'int': safe_int,
5499 'int': safe_int,
5495 'unicode': safe_unicode,
5500 'unicode': safe_unicode,
5496 'bool': str2bool,
5501 'bool': str2bool,
5497 'list': functools.partial(aslist, sep=',')
5502 'list': functools.partial(aslist, sep=',')
5498 }
5503 }
5499
5504
5500 file_store_meta_id = Column(
5505 file_store_meta_id = Column(
5501 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5506 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5502 primary_key=True)
5507 primary_key=True)
5503 _file_store_meta_section = Column(
5508 _file_store_meta_section = Column(
5504 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5509 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5505 nullable=True, unique=None, default=None)
5510 nullable=True, unique=None, default=None)
5506 _file_store_meta_section_hash = Column(
5511 _file_store_meta_section_hash = Column(
5507 "file_store_meta_section_hash", String(255),
5512 "file_store_meta_section_hash", String(255),
5508 nullable=True, unique=None, default=None)
5513 nullable=True, unique=None, default=None)
5509 _file_store_meta_key = Column(
5514 _file_store_meta_key = Column(
5510 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5515 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5511 nullable=True, unique=None, default=None)
5516 nullable=True, unique=None, default=None)
5512 _file_store_meta_key_hash = Column(
5517 _file_store_meta_key_hash = Column(
5513 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5518 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5514 _file_store_meta_value = Column(
5519 _file_store_meta_value = Column(
5515 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5520 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5516 nullable=True, unique=None, default=None)
5521 nullable=True, unique=None, default=None)
5517 _file_store_meta_value_type = Column(
5522 _file_store_meta_value_type = Column(
5518 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5523 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5519 default='unicode')
5524 default='unicode')
5520
5525
5521 file_store_id = Column(
5526 file_store_id = Column(
5522 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5527 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5523 nullable=True, unique=None, default=None)
5528 nullable=True, unique=None, default=None)
5524
5529
5525 file_store = relationship('FileStore', lazy='joined')
5530 file_store = relationship('FileStore', lazy='joined')
5526
5531
5527 @classmethod
5532 @classmethod
5528 def valid_value_type(cls, value):
5533 def valid_value_type(cls, value):
5529 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5534 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5530 raise ArtifactMetadataBadValueType(
5535 raise ArtifactMetadataBadValueType(
5531 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5536 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5532
5537
5533 @hybrid_property
5538 @hybrid_property
5534 def file_store_meta_section(self):
5539 def file_store_meta_section(self):
5535 return self._file_store_meta_section
5540 return self._file_store_meta_section
5536
5541
5537 @file_store_meta_section.setter
5542 @file_store_meta_section.setter
5538 def file_store_meta_section(self, value):
5543 def file_store_meta_section(self, value):
5539 self._file_store_meta_section = value
5544 self._file_store_meta_section = value
5540 self._file_store_meta_section_hash = _hash_key(value)
5545 self._file_store_meta_section_hash = _hash_key(value)
5541
5546
5542 @hybrid_property
5547 @hybrid_property
5543 def file_store_meta_key(self):
5548 def file_store_meta_key(self):
5544 return self._file_store_meta_key
5549 return self._file_store_meta_key
5545
5550
5546 @file_store_meta_key.setter
5551 @file_store_meta_key.setter
5547 def file_store_meta_key(self, value):
5552 def file_store_meta_key(self, value):
5548 self._file_store_meta_key = value
5553 self._file_store_meta_key = value
5549 self._file_store_meta_key_hash = _hash_key(value)
5554 self._file_store_meta_key_hash = _hash_key(value)
5550
5555
5551 @hybrid_property
5556 @hybrid_property
5552 def file_store_meta_value(self):
5557 def file_store_meta_value(self):
5553 val = self._file_store_meta_value
5558 val = self._file_store_meta_value
5554
5559
5555 if self._file_store_meta_value_type:
5560 if self._file_store_meta_value_type:
5556 # e.g unicode.encrypted == unicode
5561 # e.g unicode.encrypted == unicode
5557 _type = self._file_store_meta_value_type.split('.')[0]
5562 _type = self._file_store_meta_value_type.split('.')[0]
5558 # decode the encrypted value if it's encrypted field type
5563 # decode the encrypted value if it's encrypted field type
5559 if '.encrypted' in self._file_store_meta_value_type:
5564 if '.encrypted' in self._file_store_meta_value_type:
5560 cipher = EncryptedTextValue()
5565 cipher = EncryptedTextValue()
5561 val = safe_unicode(cipher.process_result_value(val, None))
5566 val = safe_unicode(cipher.process_result_value(val, None))
5562 # do final type conversion
5567 # do final type conversion
5563 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5568 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5564 val = converter(val)
5569 val = converter(val)
5565
5570
5566 return val
5571 return val
5567
5572
5568 @file_store_meta_value.setter
5573 @file_store_meta_value.setter
5569 def file_store_meta_value(self, val):
5574 def file_store_meta_value(self, val):
5570 val = safe_unicode(val)
5575 val = safe_unicode(val)
5571 # encode the encrypted value
5576 # encode the encrypted value
5572 if '.encrypted' in self.file_store_meta_value_type:
5577 if '.encrypted' in self.file_store_meta_value_type:
5573 cipher = EncryptedTextValue()
5578 cipher = EncryptedTextValue()
5574 val = safe_unicode(cipher.process_bind_param(val, None))
5579 val = safe_unicode(cipher.process_bind_param(val, None))
5575 self._file_store_meta_value = val
5580 self._file_store_meta_value = val
5576
5581
5577 @hybrid_property
5582 @hybrid_property
5578 def file_store_meta_value_type(self):
5583 def file_store_meta_value_type(self):
5579 return self._file_store_meta_value_type
5584 return self._file_store_meta_value_type
5580
5585
5581 @file_store_meta_value_type.setter
5586 @file_store_meta_value_type.setter
5582 def file_store_meta_value_type(self, val):
5587 def file_store_meta_value_type(self, val):
5583 # e.g unicode.encrypted
5588 # e.g unicode.encrypted
5584 self.valid_value_type(val)
5589 self.valid_value_type(val)
5585 self._file_store_meta_value_type = val
5590 self._file_store_meta_value_type = val
5586
5591
5587 def __json__(self):
5592 def __json__(self):
5588 data = {
5593 data = {
5589 'artifact': self.file_store.file_uid,
5594 'artifact': self.file_store.file_uid,
5590 'section': self.file_store_meta_section,
5595 'section': self.file_store_meta_section,
5591 'key': self.file_store_meta_key,
5596 'key': self.file_store_meta_key,
5592 'value': self.file_store_meta_value,
5597 'value': self.file_store_meta_value,
5593 }
5598 }
5594
5599
5595 return data
5600 return data
5596
5601
5597 def __repr__(self):
5602 def __repr__(self):
5598 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5603 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5599 self.file_store_meta_key, self.file_store_meta_value)
5604 self.file_store_meta_key, self.file_store_meta_value)
5600
5605
5601
5606
5602 class DbMigrateVersion(Base, BaseModel):
5607 class DbMigrateVersion(Base, BaseModel):
5603 __tablename__ = 'db_migrate_version'
5608 __tablename__ = 'db_migrate_version'
5604 __table_args__ = (
5609 __table_args__ = (
5605 base_table_args,
5610 base_table_args,
5606 )
5611 )
5607
5612
5608 repository_id = Column('repository_id', String(250), primary_key=True)
5613 repository_id = Column('repository_id', String(250), primary_key=True)
5609 repository_path = Column('repository_path', Text)
5614 repository_path = Column('repository_path', Text)
5610 version = Column('version', Integer)
5615 version = Column('version', Integer)
5611
5616
5612 @classmethod
5617 @classmethod
5613 def set_version(cls, version):
5618 def set_version(cls, version):
5614 """
5619 """
5615 Helper for forcing a different version, usually for debugging purposes via ishell.
5620 Helper for forcing a different version, usually for debugging purposes via ishell.
5616 """
5621 """
5617 ver = DbMigrateVersion.query().first()
5622 ver = DbMigrateVersion.query().first()
5618 ver.version = version
5623 ver.version = version
5619 Session().commit()
5624 Session().commit()
5620
5625
5621
5626
5622 class DbSession(Base, BaseModel):
5627 class DbSession(Base, BaseModel):
5623 __tablename__ = 'db_session'
5628 __tablename__ = 'db_session'
5624 __table_args__ = (
5629 __table_args__ = (
5625 base_table_args,
5630 base_table_args,
5626 )
5631 )
5627
5632
5628 def __repr__(self):
5633 def __repr__(self):
5629 return '<DB:DbSession({})>'.format(self.id)
5634 return '<DB:DbSession({})>'.format(self.id)
5630
5635
5631 id = Column('id', Integer())
5636 id = Column('id', Integer())
5632 namespace = Column('namespace', String(255), primary_key=True)
5637 namespace = Column('namespace', String(255), primary_key=True)
5633 accessed = Column('accessed', DateTime, nullable=False)
5638 accessed = Column('accessed', DateTime, nullable=False)
5634 created = Column('created', DateTime, nullable=False)
5639 created = Column('created', DateTime, nullable=False)
5635 data = Column('data', PickleType, nullable=False)
5640 data = Column('data', PickleType, nullable=False)
@@ -1,1322 +1,1326 b''
1 // Default styles
1 // Default styles
2
2
3 .diff-collapse {
3 .diff-collapse {
4 margin: @padding 0;
4 margin: @padding 0;
5 text-align: right;
5 text-align: right;
6 }
6 }
7
7
8 .diff-container {
8 .diff-container {
9 margin-bottom: @space;
9 margin-bottom: @space;
10
10
11 .diffblock {
11 .diffblock {
12 margin-bottom: @space;
12 margin-bottom: @space;
13 }
13 }
14
14
15 &.hidden {
15 &.hidden {
16 display: none;
16 display: none;
17 overflow: hidden;
17 overflow: hidden;
18 }
18 }
19 }
19 }
20
20
21
21
22 div.diffblock .sidebyside {
22 div.diffblock .sidebyside {
23 background: #ffffff;
23 background: #ffffff;
24 }
24 }
25
25
26 div.diffblock {
26 div.diffblock {
27 overflow-x: auto;
27 overflow-x: auto;
28 overflow-y: hidden;
28 overflow-y: hidden;
29 clear: both;
29 clear: both;
30 padding: 0px;
30 padding: 0px;
31 background: @grey6;
31 background: @grey6;
32 border: @border-thickness solid @grey5;
32 border: @border-thickness solid @grey5;
33 -webkit-border-radius: @border-radius @border-radius 0px 0px;
33 -webkit-border-radius: @border-radius @border-radius 0px 0px;
34 border-radius: @border-radius @border-radius 0px 0px;
34 border-radius: @border-radius @border-radius 0px 0px;
35
35
36
36
37 .comments-number {
37 .comments-number {
38 float: right;
38 float: right;
39 }
39 }
40
40
41 // BEGIN CODE-HEADER STYLES
41 // BEGIN CODE-HEADER STYLES
42
42
43 .code-header {
43 .code-header {
44 background: @grey6;
44 background: @grey6;
45 padding: 10px 0 10px 0;
45 padding: 10px 0 10px 0;
46 height: auto;
46 height: auto;
47 width: 100%;
47 width: 100%;
48
48
49 .hash {
49 .hash {
50 float: left;
50 float: left;
51 padding: 2px 0 0 2px;
51 padding: 2px 0 0 2px;
52 }
52 }
53
53
54 .date {
54 .date {
55 float: left;
55 float: left;
56 text-transform: uppercase;
56 text-transform: uppercase;
57 padding: 4px 0px 0px 2px;
57 padding: 4px 0px 0px 2px;
58 }
58 }
59
59
60 div {
60 div {
61 margin-left: 4px;
61 margin-left: 4px;
62 }
62 }
63
63
64 div.compare_header {
64 div.compare_header {
65 min-height: 40px;
65 min-height: 40px;
66 margin: 0;
66 margin: 0;
67 padding: 0 @padding;
67 padding: 0 @padding;
68
68
69 .drop-menu {
69 .drop-menu {
70 float:left;
70 float:left;
71 display: block;
71 display: block;
72 margin:0 0 @padding 0;
72 margin:0 0 @padding 0;
73 }
73 }
74
74
75 .compare-label {
75 .compare-label {
76 float: left;
76 float: left;
77 clear: both;
77 clear: both;
78 display: inline-block;
78 display: inline-block;
79 min-width: 5em;
79 min-width: 5em;
80 margin: 0;
80 margin: 0;
81 padding: @button-padding @button-padding @button-padding 0;
81 padding: @button-padding @button-padding @button-padding 0;
82 font-weight: @text-semibold-weight;
82 font-weight: @text-semibold-weight;
83 font-family: @text-semibold;
83 font-family: @text-semibold;
84 }
84 }
85
85
86 .compare-buttons {
86 .compare-buttons {
87 float: left;
87 float: left;
88 margin: 0;
88 margin: 0;
89 padding: 0 0 @padding;
89 padding: 0 0 @padding;
90
90
91 .btn {
91 .btn {
92 margin: 0 @padding 0 0;
92 margin: 0 @padding 0 0;
93 }
93 }
94 }
94 }
95 }
95 }
96
96
97 }
97 }
98
98
99 .parents {
99 .parents {
100 float: left;
100 float: left;
101 width: 100px;
101 width: 100px;
102 font-weight: 400;
102 font-weight: 400;
103 vertical-align: middle;
103 vertical-align: middle;
104 padding: 0px 2px 0px 2px;
104 padding: 0px 2px 0px 2px;
105 background-color: @grey6;
105 background-color: @grey6;
106
106
107 #parent_link {
107 #parent_link {
108 margin: 00px 2px;
108 margin: 00px 2px;
109
109
110 &.double {
110 &.double {
111 margin: 0px 2px;
111 margin: 0px 2px;
112 }
112 }
113
113
114 &.disabled{
114 &.disabled{
115 margin-right: @padding;
115 margin-right: @padding;
116 }
116 }
117 }
117 }
118 }
118 }
119
119
120 .children {
120 .children {
121 float: right;
121 float: right;
122 width: 100px;
122 width: 100px;
123 font-weight: 400;
123 font-weight: 400;
124 vertical-align: middle;
124 vertical-align: middle;
125 text-align: right;
125 text-align: right;
126 padding: 0px 2px 0px 2px;
126 padding: 0px 2px 0px 2px;
127 background-color: @grey6;
127 background-color: @grey6;
128
128
129 #child_link {
129 #child_link {
130 margin: 0px 2px;
130 margin: 0px 2px;
131
131
132 &.double {
132 &.double {
133 margin: 0px 2px;
133 margin: 0px 2px;
134 }
134 }
135
135
136 &.disabled{
136 &.disabled{
137 margin-right: @padding;
137 margin-right: @padding;
138 }
138 }
139 }
139 }
140 }
140 }
141
141
142 .changeset_header {
142 .changeset_header {
143 height: 16px;
143 height: 16px;
144
144
145 & > div{
145 & > div{
146 margin-right: @padding;
146 margin-right: @padding;
147 }
147 }
148 }
148 }
149
149
150 .changeset_file {
150 .changeset_file {
151 text-align: left;
151 text-align: left;
152 float: left;
152 float: left;
153 padding: 0;
153 padding: 0;
154
154
155 a{
155 a{
156 display: inline-block;
156 display: inline-block;
157 margin-right: 0.5em;
157 margin-right: 0.5em;
158 }
158 }
159
159
160 #selected_mode{
160 #selected_mode{
161 margin-left: 0;
161 margin-left: 0;
162 }
162 }
163 }
163 }
164
164
165 .diff-menu-wrapper {
165 .diff-menu-wrapper {
166 float: left;
166 float: left;
167 }
167 }
168
168
169 .diff-menu {
169 .diff-menu {
170 position: absolute;
170 position: absolute;
171 background: none repeat scroll 0 0 #FFFFFF;
171 background: none repeat scroll 0 0 #FFFFFF;
172 border-color: #003367 @grey3 @grey3;
172 border-color: #003367 @grey3 @grey3;
173 border-right: 1px solid @grey3;
173 border-right: 1px solid @grey3;
174 border-style: solid solid solid;
174 border-style: solid solid solid;
175 border-width: @border-thickness;
175 border-width: @border-thickness;
176 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
176 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
177 margin-top: 5px;
177 margin-top: 5px;
178 margin-left: 1px;
178 margin-left: 1px;
179 }
179 }
180
180
181 .diff-actions, .editor-actions {
181 .diff-actions, .editor-actions {
182 float: left;
182 float: left;
183
183
184 input{
184 input{
185 margin: 0 0.5em 0 0;
185 margin: 0 0.5em 0 0;
186 }
186 }
187 }
187 }
188
188
189 // END CODE-HEADER STYLES
189 // END CODE-HEADER STYLES
190
190
191 // BEGIN CODE-BODY STYLES
191 // BEGIN CODE-BODY STYLES
192
192
193 .code-body {
193 .code-body {
194 padding: 0;
194 padding: 0;
195 background-color: #ffffff;
195 background-color: #ffffff;
196 position: relative;
196 position: relative;
197 max-width: none;
197 max-width: none;
198 box-sizing: border-box;
198 box-sizing: border-box;
199 // TODO: johbo: Parent has overflow: auto, this forces the child here
199 // TODO: johbo: Parent has overflow: auto, this forces the child here
200 // to have the intended size and to scroll. Should be simplified.
200 // to have the intended size and to scroll. Should be simplified.
201 width: 100%;
201 width: 100%;
202 overflow-x: auto;
202 overflow-x: auto;
203 }
203 }
204
204
205 pre.raw {
205 pre.raw {
206 background: white;
206 background: white;
207 color: @grey1;
207 color: @grey1;
208 }
208 }
209 // END CODE-BODY STYLES
209 // END CODE-BODY STYLES
210
210
211 }
211 }
212
212
213
213
214 table.code-difftable {
214 table.code-difftable {
215 border-collapse: collapse;
215 border-collapse: collapse;
216 width: 99%;
216 width: 99%;
217 border-radius: 0px !important;
217 border-radius: 0px !important;
218
218
219 td {
219 td {
220 padding: 0 !important;
220 padding: 0 !important;
221 background: none !important;
221 background: none !important;
222 border: 0 !important;
222 border: 0 !important;
223 }
223 }
224
224
225 .context {
225 .context {
226 background: none repeat scroll 0 0 #DDE7EF;
226 background: none repeat scroll 0 0 #DDE7EF;
227 }
227 }
228
228
229 .add {
229 .add {
230 background: none repeat scroll 0 0 #DDFFDD;
230 background: none repeat scroll 0 0 #DDFFDD;
231
231
232 ins {
232 ins {
233 background: none repeat scroll 0 0 #AAFFAA;
233 background: none repeat scroll 0 0 #AAFFAA;
234 text-decoration: none;
234 text-decoration: none;
235 }
235 }
236 }
236 }
237
237
238 .del {
238 .del {
239 background: none repeat scroll 0 0 #FFDDDD;
239 background: none repeat scroll 0 0 #FFDDDD;
240
240
241 del {
241 del {
242 background: none repeat scroll 0 0 #FFAAAA;
242 background: none repeat scroll 0 0 #FFAAAA;
243 text-decoration: none;
243 text-decoration: none;
244 }
244 }
245 }
245 }
246
246
247 /** LINE NUMBERS **/
247 /** LINE NUMBERS **/
248 .lineno {
248 .lineno {
249 padding-left: 2px !important;
249 padding-left: 2px !important;
250 padding-right: 2px;
250 padding-right: 2px;
251 text-align: right;
251 text-align: right;
252 width: 32px;
252 width: 32px;
253 -moz-user-select: none;
253 -moz-user-select: none;
254 -webkit-user-select: none;
254 -webkit-user-select: none;
255 border-right: @border-thickness solid @grey5 !important;
255 border-right: @border-thickness solid @grey5 !important;
256 border-left: 0px solid #CCC !important;
256 border-left: 0px solid #CCC !important;
257 border-top: 0px solid #CCC !important;
257 border-top: 0px solid #CCC !important;
258 border-bottom: none !important;
258 border-bottom: none !important;
259
259
260 a {
260 a {
261 &:extend(pre);
261 &:extend(pre);
262 text-align: right;
262 text-align: right;
263 padding-right: 2px;
263 padding-right: 2px;
264 cursor: pointer;
264 cursor: pointer;
265 display: block;
265 display: block;
266 width: 32px;
266 width: 32px;
267 }
267 }
268 }
268 }
269
269
270 .context {
270 .context {
271 cursor: auto;
271 cursor: auto;
272 &:extend(pre);
272 &:extend(pre);
273 }
273 }
274
274
275 .lineno-inline {
275 .lineno-inline {
276 background: none repeat scroll 0 0 #FFF !important;
276 background: none repeat scroll 0 0 #FFF !important;
277 padding-left: 2px;
277 padding-left: 2px;
278 padding-right: 2px;
278 padding-right: 2px;
279 text-align: right;
279 text-align: right;
280 width: 30px;
280 width: 30px;
281 -moz-user-select: none;
281 -moz-user-select: none;
282 -webkit-user-select: none;
282 -webkit-user-select: none;
283 }
283 }
284
284
285 /** CODE **/
285 /** CODE **/
286 .code {
286 .code {
287 display: block;
287 display: block;
288 width: 100%;
288 width: 100%;
289
289
290 td {
290 td {
291 margin: 0;
291 margin: 0;
292 padding: 0;
292 padding: 0;
293 }
293 }
294
294
295 pre {
295 pre {
296 margin: 0;
296 margin: 0;
297 padding: 0;
297 padding: 0;
298 margin-left: .5em;
298 margin-left: .5em;
299 }
299 }
300 }
300 }
301 }
301 }
302
302
303
303
304 // Comments
304 // Comments
305 .comment-selected-hl {
305 .comment-selected-hl {
306 border-left: 6px solid @comment-highlight-color !important;
306 border-left: 6px solid @comment-highlight-color !important;
307 padding-left: 3px !important;
307 padding-left: 3px !important;
308 margin-left: -7px !important;
308 margin-left: -7px !important;
309 }
309 }
310
310
311 div.comment:target,
311 div.comment:target,
312 div.comment-outdated:target {
312 div.comment-outdated:target {
313 .comment-selected-hl;
313 .comment-selected-hl;
314 }
314 }
315
315
316 //TODO: anderson: can't get an absolute number out of anything, so had to put the
316 //TODO: anderson: can't get an absolute number out of anything, so had to put the
317 //current values that might change. But to make it clear I put as a calculation
317 //current values that might change. But to make it clear I put as a calculation
318 @comment-max-width: 1065px;
318 @comment-max-width: 1065px;
319 @pr-extra-margin: 34px;
319 @pr-extra-margin: 34px;
320 @pr-border-spacing: 4px;
320 @pr-border-spacing: 4px;
321 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
321 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
322
322
323 // Pull Request
323 // Pull Request
324 .cs_files .code-difftable {
324 .cs_files .code-difftable {
325 border: @border-thickness solid @grey5; //borders only on PRs
325 border: @border-thickness solid @grey5; //borders only on PRs
326
326
327 .comment-inline-form,
327 .comment-inline-form,
328 div.comment {
328 div.comment {
329 width: @pr-comment-width;
329 width: @pr-comment-width;
330 }
330 }
331 }
331 }
332
332
333 // Changeset
333 // Changeset
334 .code-difftable {
334 .code-difftable {
335 .comment-inline-form,
335 .comment-inline-form,
336 div.comment {
336 div.comment {
337 width: @comment-max-width;
337 width: @comment-max-width;
338 }
338 }
339 }
339 }
340
340
341 //Style page
341 //Style page
342 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
342 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
343 #style-page .code-difftable{
343 #style-page .code-difftable{
344 .comment-inline-form,
344 .comment-inline-form,
345 div.comment {
345 div.comment {
346 width: @comment-max-width - @style-extra-margin;
346 width: @comment-max-width - @style-extra-margin;
347 }
347 }
348 }
348 }
349
349
350 #context-bar > h2 {
350 #context-bar > h2 {
351 font-size: 20px;
351 font-size: 20px;
352 }
352 }
353
353
354 #context-bar > h2> a {
354 #context-bar > h2> a {
355 font-size: 20px;
355 font-size: 20px;
356 }
356 }
357 // end of defaults
357 // end of defaults
358
358
359 .file_diff_buttons {
359 .file_diff_buttons {
360 padding: 0 0 @padding;
360 padding: 0 0 @padding;
361
361
362 .drop-menu {
362 .drop-menu {
363 float: left;
363 float: left;
364 margin: 0 @padding 0 0;
364 margin: 0 @padding 0 0;
365 }
365 }
366 .btn {
366 .btn {
367 margin: 0 @padding 0 0;
367 margin: 0 @padding 0 0;
368 }
368 }
369 }
369 }
370
370
371 .code-body.textarea.editor {
371 .code-body.textarea.editor {
372 max-width: none;
372 max-width: none;
373 padding: 15px;
373 padding: 15px;
374 }
374 }
375
375
376 td.injected_diff{
376 td.injected_diff{
377 max-width: 1178px;
377 max-width: 1178px;
378 overflow-x: auto;
378 overflow-x: auto;
379 overflow-y: hidden;
379 overflow-y: hidden;
380
380
381 div.diff-container,
381 div.diff-container,
382 div.diffblock{
382 div.diffblock{
383 max-width: 100%;
383 max-width: 100%;
384 }
384 }
385
385
386 div.code-body {
386 div.code-body {
387 max-width: 1124px;
387 max-width: 1124px;
388 overflow-x: auto;
388 overflow-x: auto;
389 overflow-y: hidden;
389 overflow-y: hidden;
390 padding: 0;
390 padding: 0;
391 }
391 }
392 div.diffblock {
392 div.diffblock {
393 border: none;
393 border: none;
394 }
394 }
395
395
396 &.inline-form {
396 &.inline-form {
397 width: 99%
397 width: 99%
398 }
398 }
399 }
399 }
400
400
401
401
402 table.code-difftable {
402 table.code-difftable {
403 width: 100%;
403 width: 100%;
404 }
404 }
405
405
406 /** PYGMENTS COLORING **/
406 /** PYGMENTS COLORING **/
407 div.codeblock {
407 div.codeblock {
408
408
409 // TODO: johbo: Added interim to get rid of the margin around
409 // TODO: johbo: Added interim to get rid of the margin around
410 // Select2 widgets. This needs further cleanup.
410 // Select2 widgets. This needs further cleanup.
411 overflow: auto;
411 overflow: auto;
412 padding: 0px;
412 padding: 0px;
413 border: @border-thickness solid @grey6;
413 border: @border-thickness solid @grey6;
414 .border-radius(@border-radius);
414 .border-radius(@border-radius);
415
415
416 #remove_gist {
416 #remove_gist {
417 float: right;
417 float: right;
418 }
418 }
419
419
420 .gist_url {
420 .gist_url {
421 padding: 0px 0px 35px 0px;
421 padding: 0px 0px 35px 0px;
422 }
422 }
423
423
424 .gist-desc {
424 .gist-desc {
425 clear: both;
425 clear: both;
426 margin: 0 0 10px 0;
426 margin: 0 0 10px 0;
427 code {
427 code {
428 white-space: pre-line;
428 white-space: pre-line;
429 line-height: inherit
429 line-height: inherit
430 }
430 }
431 }
431 }
432
432
433 .author {
433 .author {
434 clear: both;
434 clear: both;
435 vertical-align: middle;
435 vertical-align: middle;
436 font-weight: @text-bold-weight;
436 font-weight: @text-bold-weight;
437 font-family: @text-bold;
437 font-family: @text-bold;
438 }
438 }
439
439
440 .btn-mini {
440 .btn-mini {
441 float: left;
441 float: left;
442 margin: 0 5px 0 0;
442 margin: 0 5px 0 0;
443 }
443 }
444
444
445 .code-header {
445 .code-header {
446 padding: @padding;
446 padding: @padding;
447 border-bottom: @border-thickness solid @grey5;
447 border-bottom: @border-thickness solid @grey5;
448
448
449 .rc-user {
449 .rc-user {
450 min-width: 0;
450 min-width: 0;
451 margin-right: .5em;
451 margin-right: .5em;
452 }
452 }
453
453
454 .stats {
454 .stats {
455 clear: both;
455 clear: both;
456 margin: 0 0 @padding 0;
456 margin: 0 0 @padding 0;
457 padding: 0;
457 padding: 0;
458 .left {
458 .left {
459 float: left;
459 float: left;
460 clear: left;
460 clear: left;
461 max-width: 75%;
461 max-width: 75%;
462 margin: 0 0 @padding 0;
462 margin: 0 0 @padding 0;
463
463
464 &.item {
464 &.item {
465 margin-right: @padding;
465 margin-right: @padding;
466 &.last { border-right: none; }
466 &.last { border-right: none; }
467 }
467 }
468 }
468 }
469 .buttons { float: right; }
469 .buttons { float: right; }
470 .author {
470 .author {
471 height: 25px; margin-left: 15px; font-weight: bold;
471 height: 25px; margin-left: 15px; font-weight: bold;
472 }
472 }
473 }
473 }
474
474
475 .commit {
475 .commit {
476 margin: 5px 0 0 26px;
476 margin: 5px 0 0 26px;
477 font-weight: normal;
477 font-weight: normal;
478 white-space: pre-wrap;
478 white-space: pre-wrap;
479 }
479 }
480 }
480 }
481
481
482 .message {
482 .message {
483 position: relative;
483 position: relative;
484 margin: @padding;
484 margin: @padding;
485
485
486 .codeblock-label {
486 .codeblock-label {
487 margin: 0 0 1em 0;
487 margin: 0 0 1em 0;
488 }
488 }
489 }
489 }
490
490
491 .code-body {
491 .code-body {
492 padding: 0.8em 1em;
492 padding: 0.8em 1em;
493 background-color: #ffffff;
493 background-color: #ffffff;
494 min-width: 100%;
494 min-width: 100%;
495 box-sizing: border-box;
495 box-sizing: border-box;
496 // TODO: johbo: Parent has overflow: auto, this forces the child here
496 // TODO: johbo: Parent has overflow: auto, this forces the child here
497 // to have the intended size and to scroll. Should be simplified.
497 // to have the intended size and to scroll. Should be simplified.
498 width: 100%;
498 width: 100%;
499 overflow-x: auto;
499 overflow-x: auto;
500
500
501 img.rendered-binary {
501 img.rendered-binary {
502 height: auto;
502 height: auto;
503 width: auto;
503 width: auto;
504 }
504 }
505
505
506 .markdown-block {
506 .markdown-block {
507 padding: 1em 0;
507 padding: 1em 0;
508 }
508 }
509 }
509 }
510
510
511 .codeblock-header {
511 .codeblock-header {
512 background: @grey7;
512 background: @grey7;
513 height: 36px;
513 height: 36px;
514 }
514 }
515
515
516 .path {
516 .path {
517 border-bottom: 1px solid @grey6;
517 border-bottom: 1px solid @grey6;
518 padding: .65em 1em;
518 padding: .65em 1em;
519 height: 18px;
519 height: 18px;
520 }
520 }
521 }
521 }
522
522
523 .code-highlighttable,
523 .code-highlighttable,
524 div.codeblock {
524 div.codeblock {
525
525
526 &.readme {
526 &.readme {
527 background-color: white;
527 background-color: white;
528 }
528 }
529
529
530 .markdown-block table {
530 .markdown-block table {
531 border-collapse: collapse;
531 border-collapse: collapse;
532
532
533 th,
533 th,
534 td {
534 td {
535 padding: .5em;
535 padding: .5em;
536 border: @border-thickness solid @border-default-color;
536 border: @border-thickness solid @border-default-color;
537 }
537 }
538 }
538 }
539
539
540 table {
540 table {
541 border: 0px;
541 border: 0px;
542 margin: 0;
542 margin: 0;
543 letter-spacing: normal;
543 letter-spacing: normal;
544
544
545
545
546 td {
546 td {
547 border: 0px;
547 border: 0px;
548 vertical-align: top;
548 vertical-align: top;
549 }
549 }
550 }
550 }
551 }
551 }
552
552
553 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
553 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
554 div.search-code-body {
554 div.search-code-body {
555 background-color: #ffffff; padding: 5px 0 5px 10px;
555 background-color: #ffffff; padding: 5px 0 5px 10px;
556 pre {
556 pre {
557 .match { background-color: #faffa6;}
557 .match { background-color: #faffa6;}
558 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
558 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
559 }
559 }
560 .code-highlighttable {
560 .code-highlighttable {
561 border-collapse: collapse;
561 border-collapse: collapse;
562
562
563 tr:hover {
563 tr:hover {
564 background: #fafafa;
564 background: #fafafa;
565 }
565 }
566 td.code {
566 td.code {
567 padding-left: 10px;
567 padding-left: 10px;
568 }
568 }
569 td.line {
569 td.line {
570 border-right: 1px solid #ccc !important;
570 border-right: 1px solid #ccc !important;
571 padding-right: 10px;
571 padding-right: 10px;
572 text-align: right;
572 text-align: right;
573 font-family: @text-monospace;
573 font-family: @text-monospace;
574 span {
574 span {
575 white-space: pre-wrap;
575 white-space: pre-wrap;
576 color: #666666;
576 color: #666666;
577 }
577 }
578 }
578 }
579 }
579 }
580 }
580 }
581
581
582 div.annotatediv { margin-left: 2px; margin-right: 4px; }
582 div.annotatediv { margin-left: 2px; margin-right: 4px; }
583 .code-highlight {
583 .code-highlight {
584 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
584 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
585 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
585 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
586 pre div:target {background-color: @comment-highlight-color !important;}
586 pre div:target {background-color: @comment-highlight-color !important;}
587 }
587 }
588
588
589 .linenos a { text-decoration: none; }
589 .linenos a { text-decoration: none; }
590
590
591 .CodeMirror-selected { background: @rchighlightblue; }
591 .CodeMirror-selected { background: @rchighlightblue; }
592 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
592 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
593 .CodeMirror ::selection { background: @rchighlightblue; }
593 .CodeMirror ::selection { background: @rchighlightblue; }
594 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
594 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
595
595
596 .code { display: block; border:0px !important; }
596 .code { display: block; border:0px !important; }
597
597
598 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
598 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
599 .codehilite {
599 .codehilite {
600 /*ElasticMatch is custom RhodeCode TAG*/
600 /*ElasticMatch is custom RhodeCode TAG*/
601
601
602 .c-ElasticMatch {
602 .c-ElasticMatch {
603 background-color: #faffa6;
603 background-color: #faffa6;
604 padding: 0.2em;
604 padding: 0.2em;
605 }
605 }
606 }
606 }
607
607
608 /* This can be generated with `pygmentize -S default -f html` */
608 /* This can be generated with `pygmentize -S default -f html` */
609 .code-highlight,
609 .code-highlight,
610 .codehilite {
610 .codehilite {
611 /*ElasticMatch is custom RhodeCode TAG*/
611 /*ElasticMatch is custom RhodeCode TAG*/
612 .c-ElasticMatch { background-color: #faffa6; padding: 0.2em;}
612 .c-ElasticMatch { background-color: #faffa6; padding: 0.2em;}
613 .hll { background-color: #ffffcc }
613 .hll { background-color: #ffffcc }
614 .c { color: #408080; font-style: italic } /* Comment */
614 .c { color: #408080; font-style: italic } /* Comment */
615 .err, .codehilite .err { border: none } /* Error */
615 .err, .codehilite .err { border: none } /* Error */
616 .k { color: #008000; font-weight: bold } /* Keyword */
616 .k { color: #008000; font-weight: bold } /* Keyword */
617 .o { color: #666666 } /* Operator */
617 .o { color: #666666 } /* Operator */
618 .ch { color: #408080; font-style: italic } /* Comment.Hashbang */
618 .ch { color: #408080; font-style: italic } /* Comment.Hashbang */
619 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
619 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
620 .cp { color: #BC7A00 } /* Comment.Preproc */
620 .cp { color: #BC7A00 } /* Comment.Preproc */
621 .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */
621 .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */
622 .c1 { color: #408080; font-style: italic } /* Comment.Single */
622 .c1 { color: #408080; font-style: italic } /* Comment.Single */
623 .cs { color: #408080; font-style: italic } /* Comment.Special */
623 .cs { color: #408080; font-style: italic } /* Comment.Special */
624 .gd { color: #A00000 } /* Generic.Deleted */
624 .gd { color: #A00000 } /* Generic.Deleted */
625 .ge { font-style: italic } /* Generic.Emph */
625 .ge { font-style: italic } /* Generic.Emph */
626 .gr { color: #FF0000 } /* Generic.Error */
626 .gr { color: #FF0000 } /* Generic.Error */
627 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
627 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
628 .gi { color: #00A000 } /* Generic.Inserted */
628 .gi { color: #00A000 } /* Generic.Inserted */
629 .go { color: #888888 } /* Generic.Output */
629 .go { color: #888888 } /* Generic.Output */
630 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
630 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
631 .gs { font-weight: bold } /* Generic.Strong */
631 .gs { font-weight: bold } /* Generic.Strong */
632 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
632 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
633 .gt { color: #0044DD } /* Generic.Traceback */
633 .gt { color: #0044DD } /* Generic.Traceback */
634 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
634 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
635 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
635 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
636 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
636 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
637 .kp { color: #008000 } /* Keyword.Pseudo */
637 .kp { color: #008000 } /* Keyword.Pseudo */
638 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
638 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
639 .kt { color: #B00040 } /* Keyword.Type */
639 .kt { color: #B00040 } /* Keyword.Type */
640 .m { color: #666666 } /* Literal.Number */
640 .m { color: #666666 } /* Literal.Number */
641 .s { color: #BA2121 } /* Literal.String */
641 .s { color: #BA2121 } /* Literal.String */
642 .na { color: #7D9029 } /* Name.Attribute */
642 .na { color: #7D9029 } /* Name.Attribute */
643 .nb { color: #008000 } /* Name.Builtin */
643 .nb { color: #008000 } /* Name.Builtin */
644 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
644 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
645 .no { color: #880000 } /* Name.Constant */
645 .no { color: #880000 } /* Name.Constant */
646 .nd { color: #AA22FF } /* Name.Decorator */
646 .nd { color: #AA22FF } /* Name.Decorator */
647 .ni { color: #999999; font-weight: bold } /* Name.Entity */
647 .ni { color: #999999; font-weight: bold } /* Name.Entity */
648 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
648 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
649 .nf { color: #0000FF } /* Name.Function */
649 .nf { color: #0000FF } /* Name.Function */
650 .nl { color: #A0A000 } /* Name.Label */
650 .nl { color: #A0A000 } /* Name.Label */
651 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
651 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
652 .nt { color: #008000; font-weight: bold } /* Name.Tag */
652 .nt { color: #008000; font-weight: bold } /* Name.Tag */
653 .nv { color: #19177C } /* Name.Variable */
653 .nv { color: #19177C } /* Name.Variable */
654 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
654 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
655 .w { color: #bbbbbb } /* Text.Whitespace */
655 .w { color: #bbbbbb } /* Text.Whitespace */
656 .mb { color: #666666 } /* Literal.Number.Bin */
656 .mb { color: #666666 } /* Literal.Number.Bin */
657 .mf { color: #666666 } /* Literal.Number.Float */
657 .mf { color: #666666 } /* Literal.Number.Float */
658 .mh { color: #666666 } /* Literal.Number.Hex */
658 .mh { color: #666666 } /* Literal.Number.Hex */
659 .mi { color: #666666 } /* Literal.Number.Integer */
659 .mi { color: #666666 } /* Literal.Number.Integer */
660 .mo { color: #666666 } /* Literal.Number.Oct */
660 .mo { color: #666666 } /* Literal.Number.Oct */
661 .sa { color: #BA2121 } /* Literal.String.Affix */
661 .sa { color: #BA2121 } /* Literal.String.Affix */
662 .sb { color: #BA2121 } /* Literal.String.Backtick */
662 .sb { color: #BA2121 } /* Literal.String.Backtick */
663 .sc { color: #BA2121 } /* Literal.String.Char */
663 .sc { color: #BA2121 } /* Literal.String.Char */
664 .dl { color: #BA2121 } /* Literal.String.Delimiter */
664 .dl { color: #BA2121 } /* Literal.String.Delimiter */
665 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
665 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
666 .s2 { color: #BA2121 } /* Literal.String.Double */
666 .s2 { color: #BA2121 } /* Literal.String.Double */
667 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
667 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
668 .sh { color: #BA2121 } /* Literal.String.Heredoc */
668 .sh { color: #BA2121 } /* Literal.String.Heredoc */
669 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
669 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
670 .sx { color: #008000 } /* Literal.String.Other */
670 .sx { color: #008000 } /* Literal.String.Other */
671 .sr { color: #BB6688 } /* Literal.String.Regex */
671 .sr { color: #BB6688 } /* Literal.String.Regex */
672 .s1 { color: #BA2121 } /* Literal.String.Single */
672 .s1 { color: #BA2121 } /* Literal.String.Single */
673 .ss { color: #19177C } /* Literal.String.Symbol */
673 .ss { color: #19177C } /* Literal.String.Symbol */
674 .bp { color: #008000 } /* Name.Builtin.Pseudo */
674 .bp { color: #008000 } /* Name.Builtin.Pseudo */
675 .fm { color: #0000FF } /* Name.Function.Magic */
675 .fm { color: #0000FF } /* Name.Function.Magic */
676 .vc { color: #19177C } /* Name.Variable.Class */
676 .vc { color: #19177C } /* Name.Variable.Class */
677 .vg { color: #19177C } /* Name.Variable.Global */
677 .vg { color: #19177C } /* Name.Variable.Global */
678 .vi { color: #19177C } /* Name.Variable.Instance */
678 .vi { color: #19177C } /* Name.Variable.Instance */
679 .vm { color: #19177C } /* Name.Variable.Magic */
679 .vm { color: #19177C } /* Name.Variable.Magic */
680 .il { color: #666666 } /* Literal.Number.Integer.Long */
680 .il { color: #666666 } /* Literal.Number.Integer.Long */
681
681
682 }
682 }
683
683
684 /* customized pre blocks for markdown/rst */
684 /* customized pre blocks for markdown/rst */
685 pre.literal-block, .codehilite pre{
685 pre.literal-block, .codehilite pre{
686 padding: @padding;
686 padding: @padding;
687 border: 1px solid @grey6;
687 border: 1px solid @grey6;
688 .border-radius(@border-radius);
688 .border-radius(@border-radius);
689 background-color: @grey7;
689 background-color: @grey7;
690 }
690 }
691
691
692
692
693 /* START NEW CODE BLOCK CSS */
693 /* START NEW CODE BLOCK CSS */
694
694
695 @cb-line-height: 18px;
695 @cb-line-height: 18px;
696 @cb-line-code-padding: 10px;
696 @cb-line-code-padding: 10px;
697 @cb-text-padding: 5px;
697 @cb-text-padding: 5px;
698
698
699 @pill-padding: 2px 7px;
699 @pill-padding: 2px 7px;
700 @pill-padding-small: 2px 2px 1px 2px;
700 @pill-padding-small: 2px 2px 1px 2px;
701
701
702 input.filediff-collapse-state {
702 input.filediff-collapse-state {
703 display: none;
703 display: none;
704
704
705 &:checked + .filediff { /* file diff is collapsed */
705 &:checked + .filediff { /* file diff is collapsed */
706 .cb {
706 .cb {
707 display: none
707 display: none
708 }
708 }
709 .filediff-collapse-indicator {
709 .filediff-collapse-indicator {
710 float: left;
710 float: left;
711 cursor: pointer;
711 cursor: pointer;
712 margin: 1px -5px;
712 margin: 1px -5px;
713 }
713 }
714 .filediff-collapse-indicator:before {
714 .filediff-collapse-indicator:before {
715 content: '\f105';
715 content: '\f105';
716 }
716 }
717
717
718 .filediff-menu {
718 .filediff-menu {
719 display: none;
719 display: none;
720 }
720 }
721
721
722 }
722 }
723
723
724 &+ .filediff { /* file diff is expanded */
724 &+ .filediff { /* file diff is expanded */
725
725
726 .filediff-collapse-indicator {
726 .filediff-collapse-indicator {
727 float: left;
727 float: left;
728 cursor: pointer;
728 cursor: pointer;
729 margin: 1px -5px;
729 margin: 1px -5px;
730 }
730 }
731 .filediff-collapse-indicator:before {
731 .filediff-collapse-indicator:before {
732 content: '\f107';
732 content: '\f107';
733 }
733 }
734
734
735 .filediff-menu {
735 .filediff-menu {
736 display: block;
736 display: block;
737 }
737 }
738
738
739 margin: 10px 0;
739 margin: 10px 0;
740 &:nth-child(2) {
740 &:nth-child(2) {
741 margin: 0;
741 margin: 0;
742 }
742 }
743 }
743 }
744 }
744 }
745
745
746 .filediffs .anchor {
746 .filediffs .anchor {
747 display: block;
747 display: block;
748 height: 40px;
748 height: 40px;
749 margin-top: -40px;
749 margin-top: -40px;
750 visibility: hidden;
750 visibility: hidden;
751 }
751 }
752
752
753 .filediffs .anchor:nth-of-type(1) {
753 .filediffs .anchor:nth-of-type(1) {
754 display: block;
754 display: block;
755 height: 80px;
755 height: 80px;
756 margin-top: -80px;
756 margin-top: -80px;
757 visibility: hidden;
757 visibility: hidden;
758 }
758 }
759
759
760 .cs_files {
760 .cs_files {
761 clear: both;
761 clear: both;
762 }
762 }
763
763
764 #diff-file-sticky{
764 #diff-file-sticky{
765 will-change: min-height;
765 will-change: min-height;
766 height: 80px;
766 height: 80px;
767 }
767 }
768
768
769 .sidebar__inner{
769 .sidebar__inner{
770 transform: translate(0, 0); /* For browsers don't support translate3d. */
770 transform: translate(0, 0); /* For browsers don't support translate3d. */
771 transform: translate3d(0, 0, 0);
771 transform: translate3d(0, 0, 0);
772 will-change: position, transform;
772 will-change: position, transform;
773 height: 65px;
773 height: 65px;
774 background-color: #fff;
774 background-color: #fff;
775 padding: 5px 0px;
775 padding: 5px 0px;
776 }
776 }
777
777
778 .sidebar__bar {
778 .sidebar__bar {
779 padding: 5px 0px 0px 0px
779 padding: 5px 0px 0px 0px
780 }
780 }
781
781
782 .fpath-placeholder {
782 .fpath-placeholder {
783 clear: both;
783 clear: both;
784 visibility: hidden
784 visibility: hidden
785 }
785 }
786
786
787 .is-affixed {
787 .is-affixed {
788
788
789 .sidebar__inner {
789 .sidebar__inner {
790 z-index: 30;
790 z-index: 30;
791 }
791 }
792
792
793 .sidebar_inner_shadow {
793 .sidebar_inner_shadow {
794 position: fixed;
794 position: fixed;
795 top: 75px;
795 top: 75px;
796 right: -100%;
796 right: -100%;
797 left: -100%;
797 left: -100%;
798 z-index: 30;
798 z-index: 30;
799 display: block;
799 display: block;
800 height: 5px;
800 height: 5px;
801 content: "";
801 content: "";
802 background: linear-gradient(rgba(0, 0, 0, 0.075), rgba(0, 0, 0, 0.001)) repeat-x 0 0;
802 background: linear-gradient(rgba(0, 0, 0, 0.075), rgba(0, 0, 0, 0.001)) repeat-x 0 0;
803 border-top: 1px solid rgba(0, 0, 0, 0.15);
803 border-top: 1px solid rgba(0, 0, 0, 0.15);
804 }
804 }
805
805
806 .fpath-placeholder {
806 .fpath-placeholder {
807 visibility: visible !important;
807 visibility: visible !important;
808 }
808 }
809 }
809 }
810
810
811 .diffset-menu {
811 .diffset-menu {
812
812
813 }
813 }
814
814
815 #todo-box {
815 #todo-box {
816 clear:both;
816 clear:both;
817 display: none;
817 display: none;
818 text-align: right
818 text-align: right
819 }
819 }
820
820
821 .diffset {
821 .diffset {
822 margin: 0px auto;
822 margin: 0px auto;
823 .diffset-heading {
823 .diffset-heading {
824 border: 1px solid @grey5;
824 border: 1px solid @grey5;
825 margin-bottom: -1px;
825 margin-bottom: -1px;
826 // margin-top: 20px;
826 // margin-top: 20px;
827 h2 {
827 h2 {
828 margin: 0;
828 margin: 0;
829 line-height: 38px;
829 line-height: 38px;
830 padding-left: 10px;
830 padding-left: 10px;
831 }
831 }
832 .btn {
832 .btn {
833 margin: 0;
833 margin: 0;
834 }
834 }
835 background: @grey6;
835 background: @grey6;
836 display: block;
836 display: block;
837 padding: 5px;
837 padding: 5px;
838 }
838 }
839 .diffset-heading-warning {
839 .diffset-heading-warning {
840 background: @alert3-inner;
840 background: @alert3-inner;
841 border: 1px solid @alert3;
841 border: 1px solid @alert3;
842 }
842 }
843 &.diffset-comments-disabled {
843 &.diffset-comments-disabled {
844 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
844 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
845 display: none !important;
845 display: none !important;
846 }
846 }
847 }
847 }
848 }
848 }
849
849
850 .filelist {
850 .filelist {
851 .pill {
851 .pill {
852 display: block;
852 display: block;
853 float: left;
853 float: left;
854 padding: @pill-padding-small;
854 padding: @pill-padding-small;
855 }
855 }
856 }
856 }
857
857
858 .pill {
858 .pill {
859 display: block;
859 display: block;
860 float: left;
860 float: left;
861 padding: @pill-padding;
861 padding: @pill-padding;
862 }
862 }
863
863
864 .pill-group {
864 .pill-group {
865 .pill {
865 .pill {
866 opacity: .8;
866 opacity: .8;
867 margin-right: 3px;
867 margin-right: 3px;
868 font-size: 12px;
868 font-size: 12px;
869 font-weight: normal;
869 font-weight: normal;
870 min-width: 30px;
870 min-width: 30px;
871 text-align: center;
871 text-align: center;
872
872
873 &:first-child {
873 &:first-child {
874 border-radius: @border-radius 0 0 @border-radius;
874 border-radius: @border-radius 0 0 @border-radius;
875 }
875 }
876 &:last-child {
876 &:last-child {
877 border-radius: 0 @border-radius @border-radius 0;
877 border-radius: 0 @border-radius @border-radius 0;
878 }
878 }
879 &:only-child {
879 &:only-child {
880 border-radius: @border-radius;
880 border-radius: @border-radius;
881 margin-right: 0;
881 margin-right: 0;
882 }
882 }
883 }
883 }
884 }
884 }
885
885
886 /* Main comments*/
886 /* Main comments*/
887 #comments {
887 #comments {
888 .comment-selected {
888 .comment-selected {
889 border-left: 6px solid @comment-highlight-color;
889 border-left: 6px solid @comment-highlight-color;
890 padding-left: 3px;
890 padding-left: 3px;
891 margin-left: -9px;
891 margin-left: -9px;
892 }
892 }
893 }
893 }
894
894
895 .filediff {
895 .filediff {
896 border: 1px solid @grey5;
896 border: 1px solid @grey5;
897
897
898 /* START OVERRIDES */
898 /* START OVERRIDES */
899 .code-highlight {
899 .code-highlight {
900 border: none; // TODO: remove this border from the global
900 border: none; // TODO: remove this border from the global
901 // .code-highlight, it doesn't belong there
901 // .code-highlight, it doesn't belong there
902 }
902 }
903 label {
903 label {
904 margin: 0; // TODO: remove this margin definition from global label
904 margin: 0; // TODO: remove this margin definition from global label
905 // it doesn't belong there - if margin on labels
905 // it doesn't belong there - if margin on labels
906 // are needed for a form they should be defined
906 // are needed for a form they should be defined
907 // in the form's class
907 // in the form's class
908 }
908 }
909 /* END OVERRIDES */
909 /* END OVERRIDES */
910
910
911 * {
911 * {
912 box-sizing: border-box;
912 box-sizing: border-box;
913 }
913 }
914
914
915 .on-hover-icon {
915 .on-hover-icon {
916 visibility: hidden;
916 visibility: hidden;
917 }
917 }
918
918
919 .filediff-anchor {
919 .filediff-anchor {
920 visibility: hidden;
920 visibility: hidden;
921 }
921 }
922 &:hover {
922 &:hover {
923 .filediff-anchor {
923 .filediff-anchor {
924 visibility: visible;
924 visibility: visible;
925 }
925 }
926 .on-hover-icon {
926 .on-hover-icon {
927 visibility: visible;
927 visibility: visible;
928 }
928 }
929 }
929 }
930
930
931 .filediff-heading {
931 .filediff-heading {
932 cursor: pointer;
932 cursor: pointer;
933 display: block;
933 display: block;
934 padding: 10px 10px;
934 padding: 10px 10px;
935 }
935 }
936 .filediff-heading:after {
936 .filediff-heading:after {
937 content: "";
937 content: "";
938 display: table;
938 display: table;
939 clear: both;
939 clear: both;
940 }
940 }
941 .filediff-heading:hover {
941 .filediff-heading:hover {
942 background: #e1e9f4 !important;
942 background: #e1e9f4 !important;
943 }
943 }
944
944
945 .filediff-menu {
945 .filediff-menu {
946 text-align: right;
946 text-align: right;
947 padding: 5px 5px 5px 0px;
947 padding: 5px 5px 5px 0px;
948 background: @grey7;
948 background: @grey7;
949
949
950 &> a,
950 &> a,
951 &> span {
951 &> span {
952 padding: 1px;
952 padding: 1px;
953 }
953 }
954 }
954 }
955
955
956 .filediff-collapse-button, .filediff-expand-button {
956 .filediff-collapse-button, .filediff-expand-button {
957 cursor: pointer;
957 cursor: pointer;
958 }
958 }
959 .filediff-collapse-button {
959 .filediff-collapse-button {
960 display: inline;
960 display: inline;
961 }
961 }
962 .filediff-expand-button {
962 .filediff-expand-button {
963 display: none;
963 display: none;
964 }
964 }
965 .filediff-collapsed .filediff-collapse-button {
965 .filediff-collapsed .filediff-collapse-button {
966 display: none;
966 display: none;
967 }
967 }
968 .filediff-collapsed .filediff-expand-button {
968 .filediff-collapsed .filediff-expand-button {
969 display: inline;
969 display: inline;
970 }
970 }
971
971
972 /**** COMMENTS ****/
972 /**** COMMENTS ****/
973
973
974 .filediff-menu {
974 .filediff-menu {
975 .show-comment-button {
975 .show-comment-button {
976 display: none;
976 display: none;
977 }
977 }
978 }
978 }
979 &.hide-comments {
979 &.hide-comments {
980 .inline-comments {
980 .inline-comments {
981 display: none;
981 display: none;
982 }
982 }
983 .filediff-menu {
983 .filediff-menu {
984 .show-comment-button {
984 .show-comment-button {
985 display: inline;
985 display: inline;
986 }
986 }
987 .hide-comment-button {
987 .hide-comment-button {
988 display: none;
988 display: none;
989 }
989 }
990 }
990 }
991 }
991 }
992
992
993 .hide-line-comments {
993 .hide-line-comments {
994 .inline-comments {
994 .inline-comments {
995 display: none;
995 display: none;
996 }
996 }
997 }
997 }
998
998
999 /**** END COMMENTS ****/
999 /**** END COMMENTS ****/
1000
1000
1001 }
1001 }
1002
1002
1003
1003
1004 .op-added {
1004 .op-added {
1005 color: @alert1;
1005 color: @alert1;
1006 }
1006 }
1007
1007
1008 .op-deleted {
1008 .op-deleted {
1009 color: @alert2;
1009 color: @alert2;
1010 }
1010 }
1011
1011
1012 .filediff, .filelist {
1012 .filediff, .filelist {
1013
1013
1014 .pill {
1014 .pill {
1015 &[op="name"] {
1015 &[op="name"] {
1016 background: none;
1016 background: none;
1017 opacity: 1;
1017 opacity: 1;
1018 color: white;
1018 color: white;
1019 }
1019 }
1020 &[op="limited"] {
1020 &[op="limited"] {
1021 background: @grey2;
1021 background: @grey2;
1022 color: white;
1022 color: white;
1023 }
1023 }
1024 &[op="binary"] {
1024 &[op="binary"] {
1025 background: @color7;
1025 background: @color7;
1026 color: white;
1026 color: white;
1027 }
1027 }
1028 &[op="modified"] {
1028 &[op="modified"] {
1029 background: @alert1;
1029 background: @alert1;
1030 color: white;
1030 color: white;
1031 }
1031 }
1032 &[op="renamed"] {
1032 &[op="renamed"] {
1033 background: @color4;
1033 background: @color4;
1034 color: white;
1034 color: white;
1035 }
1035 }
1036 &[op="copied"] {
1036 &[op="copied"] {
1037 background: @color4;
1037 background: @color4;
1038 color: white;
1038 color: white;
1039 }
1039 }
1040 &[op="mode"] {
1040 &[op="mode"] {
1041 background: @grey3;
1041 background: @grey3;
1042 color: white;
1042 color: white;
1043 }
1043 }
1044 &[op="symlink"] {
1044 &[op="symlink"] {
1045 background: @color8;
1045 background: @color8;
1046 color: white;
1046 color: white;
1047 }
1047 }
1048
1048
1049 &[op="added"] { /* added lines */
1049 &[op="added"] { /* added lines */
1050 background: @alert1;
1050 background: @alert1;
1051 color: white;
1051 color: white;
1052 }
1052 }
1053 &[op="deleted"] { /* deleted lines */
1053 &[op="deleted"] { /* deleted lines */
1054 background: @alert2;
1054 background: @alert2;
1055 color: white;
1055 color: white;
1056 }
1056 }
1057
1057
1058 &[op="created"] { /* created file */
1058 &[op="created"] { /* created file */
1059 background: @alert1;
1059 background: @alert1;
1060 color: white;
1060 color: white;
1061 }
1061 }
1062 &[op="removed"] { /* deleted file */
1062 &[op="removed"] { /* deleted file */
1063 background: @color5;
1063 background: @color5;
1064 color: white;
1064 color: white;
1065 }
1065 }
1066 &[op="comments"] { /* comments on file */
1067 background: @grey4;
1068 color: white;
1069 }
1066 }
1070 }
1067 }
1071 }
1068
1072
1069
1073
1070 .filediff-outdated {
1074 .filediff-outdated {
1071 padding: 8px 0;
1075 padding: 8px 0;
1072
1076
1073 .filediff-heading {
1077 .filediff-heading {
1074 opacity: .5;
1078 opacity: .5;
1075 }
1079 }
1076 }
1080 }
1077
1081
1078 table.cb {
1082 table.cb {
1079 width: 100%;
1083 width: 100%;
1080 border-collapse: collapse;
1084 border-collapse: collapse;
1081
1085
1082 .cb-text {
1086 .cb-text {
1083 padding: @cb-text-padding;
1087 padding: @cb-text-padding;
1084 }
1088 }
1085 .cb-hunk {
1089 .cb-hunk {
1086 padding: @cb-text-padding;
1090 padding: @cb-text-padding;
1087 }
1091 }
1088 .cb-expand {
1092 .cb-expand {
1089 display: none;
1093 display: none;
1090 }
1094 }
1091 .cb-collapse {
1095 .cb-collapse {
1092 display: inline;
1096 display: inline;
1093 }
1097 }
1094 &.cb-collapsed {
1098 &.cb-collapsed {
1095 .cb-line {
1099 .cb-line {
1096 display: none;
1100 display: none;
1097 }
1101 }
1098 .cb-expand {
1102 .cb-expand {
1099 display: inline;
1103 display: inline;
1100 }
1104 }
1101 .cb-collapse {
1105 .cb-collapse {
1102 display: none;
1106 display: none;
1103 }
1107 }
1104 .cb-hunk {
1108 .cb-hunk {
1105 display: none;
1109 display: none;
1106 }
1110 }
1107 }
1111 }
1108
1112
1109 /* intentionally general selector since .cb-line-selected must override it
1113 /* intentionally general selector since .cb-line-selected must override it
1110 and they both use !important since the td itself may have a random color
1114 and they both use !important since the td itself may have a random color
1111 generated by annotation blocks. TLDR: if you change it, make sure
1115 generated by annotation blocks. TLDR: if you change it, make sure
1112 annotated block selection and line selection in file view still work */
1116 annotated block selection and line selection in file view still work */
1113 .cb-line-fresh .cb-content {
1117 .cb-line-fresh .cb-content {
1114 background: white !important;
1118 background: white !important;
1115 }
1119 }
1116 .cb-warning {
1120 .cb-warning {
1117 background: #fff4dd;
1121 background: #fff4dd;
1118 }
1122 }
1119
1123
1120 &.cb-diff-sideside {
1124 &.cb-diff-sideside {
1121 td {
1125 td {
1122 &.cb-content {
1126 &.cb-content {
1123 width: 50%;
1127 width: 50%;
1124 }
1128 }
1125 }
1129 }
1126 }
1130 }
1127
1131
1128 tr {
1132 tr {
1129 &.cb-annotate {
1133 &.cb-annotate {
1130 border-top: 1px solid #eee;
1134 border-top: 1px solid #eee;
1131 }
1135 }
1132
1136
1133 &.cb-comment-info {
1137 &.cb-comment-info {
1134 border-top: 1px solid #eee;
1138 border-top: 1px solid #eee;
1135 color: rgba(0, 0, 0, 0.3);
1139 color: rgba(0, 0, 0, 0.3);
1136 background: #edf2f9;
1140 background: #edf2f9;
1137
1141
1138 td {
1142 td {
1139
1143
1140 }
1144 }
1141 }
1145 }
1142
1146
1143 &.cb-hunk {
1147 &.cb-hunk {
1144 font-family: @text-monospace;
1148 font-family: @text-monospace;
1145 color: rgba(0, 0, 0, 0.3);
1149 color: rgba(0, 0, 0, 0.3);
1146
1150
1147 td {
1151 td {
1148 &:first-child {
1152 &:first-child {
1149 background: #edf2f9;
1153 background: #edf2f9;
1150 }
1154 }
1151 &:last-child {
1155 &:last-child {
1152 background: #f4f7fb;
1156 background: #f4f7fb;
1153 }
1157 }
1154 }
1158 }
1155 }
1159 }
1156 }
1160 }
1157
1161
1158
1162
1159 td {
1163 td {
1160 vertical-align: top;
1164 vertical-align: top;
1161 padding: 0;
1165 padding: 0;
1162
1166
1163 &.cb-content {
1167 &.cb-content {
1164 font-size: 12.35px;
1168 font-size: 12.35px;
1165
1169
1166 &.cb-line-selected .cb-code {
1170 &.cb-line-selected .cb-code {
1167 background: @comment-highlight-color !important;
1171 background: @comment-highlight-color !important;
1168 }
1172 }
1169
1173
1170 span.cb-code {
1174 span.cb-code {
1171 line-height: @cb-line-height;
1175 line-height: @cb-line-height;
1172 padding-left: @cb-line-code-padding;
1176 padding-left: @cb-line-code-padding;
1173 padding-right: @cb-line-code-padding;
1177 padding-right: @cb-line-code-padding;
1174 display: block;
1178 display: block;
1175 white-space: pre-wrap;
1179 white-space: pre-wrap;
1176 font-family: @text-monospace;
1180 font-family: @text-monospace;
1177 word-break: break-all;
1181 word-break: break-all;
1178 .nonl {
1182 .nonl {
1179 color: @color5;
1183 color: @color5;
1180 }
1184 }
1181 .cb-action {
1185 .cb-action {
1182 &:before {
1186 &:before {
1183 content: " ";
1187 content: " ";
1184 }
1188 }
1185 &.cb-deletion:before {
1189 &.cb-deletion:before {
1186 content: "- ";
1190 content: "- ";
1187 }
1191 }
1188 &.cb-addition:before {
1192 &.cb-addition:before {
1189 content: "+ ";
1193 content: "+ ";
1190 }
1194 }
1191 }
1195 }
1192 }
1196 }
1193
1197
1194 &> button.cb-comment-box-opener {
1198 &> button.cb-comment-box-opener {
1195
1199
1196 padding: 2px 2px 1px 3px;
1200 padding: 2px 2px 1px 3px;
1197 margin-left: -6px;
1201 margin-left: -6px;
1198 margin-top: -1px;
1202 margin-top: -1px;
1199
1203
1200 border-radius: @border-radius;
1204 border-radius: @border-radius;
1201 position: absolute;
1205 position: absolute;
1202 display: none;
1206 display: none;
1203 }
1207 }
1204 .cb-comment {
1208 .cb-comment {
1205 margin-top: 10px;
1209 margin-top: 10px;
1206 white-space: normal;
1210 white-space: normal;
1207 }
1211 }
1208 }
1212 }
1209 &:hover {
1213 &:hover {
1210 button.cb-comment-box-opener {
1214 button.cb-comment-box-opener {
1211 display: block;
1215 display: block;
1212 }
1216 }
1213 &+ td button.cb-comment-box-opener {
1217 &+ td button.cb-comment-box-opener {
1214 display: block
1218 display: block
1215 }
1219 }
1216 }
1220 }
1217
1221
1218 &.cb-data {
1222 &.cb-data {
1219 text-align: right;
1223 text-align: right;
1220 width: 30px;
1224 width: 30px;
1221 font-family: @text-monospace;
1225 font-family: @text-monospace;
1222
1226
1223 .icon-comment {
1227 .icon-comment {
1224 cursor: pointer;
1228 cursor: pointer;
1225 }
1229 }
1226 &.cb-line-selected {
1230 &.cb-line-selected {
1227 background: @comment-highlight-color !important;
1231 background: @comment-highlight-color !important;
1228 }
1232 }
1229 &.cb-line-selected > div {
1233 &.cb-line-selected > div {
1230 display: block;
1234 display: block;
1231 background: @comment-highlight-color !important;
1235 background: @comment-highlight-color !important;
1232 line-height: @cb-line-height;
1236 line-height: @cb-line-height;
1233 color: rgba(0, 0, 0, 0.3);
1237 color: rgba(0, 0, 0, 0.3);
1234 }
1238 }
1235 }
1239 }
1236
1240
1237 &.cb-lineno {
1241 &.cb-lineno {
1238 padding: 0;
1242 padding: 0;
1239 width: 50px;
1243 width: 50px;
1240 color: rgba(0, 0, 0, 0.3);
1244 color: rgba(0, 0, 0, 0.3);
1241 text-align: right;
1245 text-align: right;
1242 border-right: 1px solid #eee;
1246 border-right: 1px solid #eee;
1243 font-family: @text-monospace;
1247 font-family: @text-monospace;
1244 -webkit-user-select: none;
1248 -webkit-user-select: none;
1245 -moz-user-select: none;
1249 -moz-user-select: none;
1246 user-select: none;
1250 user-select: none;
1247
1251
1248 a::before {
1252 a::before {
1249 content: attr(data-line-no);
1253 content: attr(data-line-no);
1250 }
1254 }
1251 &.cb-line-selected {
1255 &.cb-line-selected {
1252 background: @comment-highlight-color !important;
1256 background: @comment-highlight-color !important;
1253 }
1257 }
1254
1258
1255 a {
1259 a {
1256 display: block;
1260 display: block;
1257 padding-right: @cb-line-code-padding;
1261 padding-right: @cb-line-code-padding;
1258 padding-left: @cb-line-code-padding;
1262 padding-left: @cb-line-code-padding;
1259 line-height: @cb-line-height;
1263 line-height: @cb-line-height;
1260 color: rgba(0, 0, 0, 0.3);
1264 color: rgba(0, 0, 0, 0.3);
1261 }
1265 }
1262 }
1266 }
1263
1267
1264 &.cb-empty {
1268 &.cb-empty {
1265 background: @grey7;
1269 background: @grey7;
1266 }
1270 }
1267
1271
1268 ins {
1272 ins {
1269 color: black;
1273 color: black;
1270 background: #a6f3a6;
1274 background: #a6f3a6;
1271 text-decoration: none;
1275 text-decoration: none;
1272 }
1276 }
1273 del {
1277 del {
1274 color: black;
1278 color: black;
1275 background: #f8cbcb;
1279 background: #f8cbcb;
1276 text-decoration: none;
1280 text-decoration: none;
1277 }
1281 }
1278 &.cb-addition {
1282 &.cb-addition {
1279 background: #ecffec;
1283 background: #ecffec;
1280
1284
1281 &.blob-lineno {
1285 &.blob-lineno {
1282 background: #ddffdd;
1286 background: #ddffdd;
1283 }
1287 }
1284 }
1288 }
1285 &.cb-deletion {
1289 &.cb-deletion {
1286 background: #ffecec;
1290 background: #ffecec;
1287
1291
1288 &.blob-lineno {
1292 &.blob-lineno {
1289 background: #ffdddd;
1293 background: #ffdddd;
1290 }
1294 }
1291 }
1295 }
1292 &.cb-annotate-message-spacer {
1296 &.cb-annotate-message-spacer {
1293 width:8px;
1297 width:8px;
1294 padding: 1px 0px 0px 3px;
1298 padding: 1px 0px 0px 3px;
1295 }
1299 }
1296 &.cb-annotate-info {
1300 &.cb-annotate-info {
1297 width: 320px;
1301 width: 320px;
1298 min-width: 320px;
1302 min-width: 320px;
1299 max-width: 320px;
1303 max-width: 320px;
1300 padding: 5px 2px;
1304 padding: 5px 2px;
1301 font-size: 13px;
1305 font-size: 13px;
1302
1306
1303 .cb-annotate-message {
1307 .cb-annotate-message {
1304 padding: 2px 0px 0px 0px;
1308 padding: 2px 0px 0px 0px;
1305 white-space: pre-line;
1309 white-space: pre-line;
1306 overflow: hidden;
1310 overflow: hidden;
1307 }
1311 }
1308 .rc-user {
1312 .rc-user {
1309 float: none;
1313 float: none;
1310 padding: 0 6px 0 17px;
1314 padding: 0 6px 0 17px;
1311 min-width: unset;
1315 min-width: unset;
1312 min-height: unset;
1316 min-height: unset;
1313 }
1317 }
1314 }
1318 }
1315
1319
1316 &.cb-annotate-revision {
1320 &.cb-annotate-revision {
1317 cursor: pointer;
1321 cursor: pointer;
1318 text-align: right;
1322 text-align: right;
1319 padding: 1px 3px 0px 3px;
1323 padding: 1px 3px 0px 3px;
1320 }
1324 }
1321 }
1325 }
1322 }
1326 }
@@ -1,634 +1,635 b''
1 // comments.less
1 // comments.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // see style guide documentation for guidelines.
3 // see style guide documentation for guidelines.
4
4
5
5
6 // Comments
6 // Comments
7 @comment-outdated-opacity: 0.6;
7 @comment-outdated-opacity: 0.6;
8
8
9 .comments {
9 .comments {
10 width: 100%;
10 width: 100%;
11 }
11 }
12
12
13 .comments-heading {
13 .comments-heading {
14 margin-bottom: -1px;
14 margin-bottom: -1px;
15 background: @grey6;
15 background: @grey6;
16 display: block;
16 display: block;
17 padding: 10px 0px;
17 padding: 10px 0px;
18 font-size: 18px
18 font-size: 18px
19 }
19 }
20
20
21 #comment-tr-show {
21 #comment-tr-show {
22 padding: 5px 0;
22 padding: 5px 0;
23 }
23 }
24
24
25 tr.inline-comments div {
25 tr.inline-comments div {
26 max-width: 100%;
26 max-width: 100%;
27
27
28 p {
28 p {
29 white-space: normal;
29 white-space: normal;
30 }
30 }
31
31
32 code, pre, .code, dd {
32 code, pre, .code, dd {
33 overflow-x: auto;
33 overflow-x: auto;
34 width: 1062px;
34 width: 1062px;
35 }
35 }
36
36
37 dd {
37 dd {
38 width: auto;
38 width: auto;
39 }
39 }
40 }
40 }
41
41
42 #injected_page_comments {
42 #injected_page_comments {
43 .comment-previous-link,
43 .comment-previous-link,
44 .comment-next-link,
44 .comment-next-link,
45 .comment-links-divider {
45 .comment-links-divider {
46 display: none;
46 display: none;
47 }
47 }
48 }
48 }
49
49
50 .add-comment {
50 .add-comment {
51 margin-bottom: 10px;
51 margin-bottom: 10px;
52 }
52 }
53 .hide-comment-button .add-comment {
53 .hide-comment-button .add-comment {
54 display: none;
54 display: none;
55 }
55 }
56
56
57 .comment-bubble {
57 .comment-bubble {
58 color: @grey4;
58 color: @grey4;
59 margin-top: 4px;
59 margin-top: 4px;
60 margin-right: 30px;
60 margin-right: 30px;
61 visibility: hidden;
61 visibility: hidden;
62 }
62 }
63
63
64 .comment-label {
64 .comment-label {
65 float: left;
65 float: left;
66
66
67 padding: 0.4em 0.4em;
67 padding: 0.4em 0.4em;
68 margin: 3px 5px 0px -10px;
68 margin: 2px 4px 0px 0px;
69 display: inline-block;
69 display: inline-block;
70 min-height: 0;
70 min-height: 0;
71
71
72 text-align: center;
72 text-align: center;
73 font-size: 10px;
73 font-size: 10px;
74 line-height: .8em;
74 line-height: .8em;
75
75
76 font-family: @text-italic;
76 font-family: @text-italic;
77 font-style: italic;
77 font-style: italic;
78 background: #fff none;
78 background: #fff none;
79 color: @grey4;
79 color: @grey3;
80 border: 1px solid @grey4;
80 border: 1px solid @grey4;
81 white-space: nowrap;
81 white-space: nowrap;
82
82
83 text-transform: uppercase;
83 text-transform: uppercase;
84 min-width: 40px;
84 min-width: 50px;
85 border-radius: 4px;
85
86
86 &.todo {
87 &.todo {
87 color: @color5;
88 color: @color5;
88 font-style: italic;
89 font-style: italic;
89 font-weight: @text-bold-italic-weight;
90 font-weight: @text-bold-italic-weight;
90 font-family: @text-bold-italic;
91 font-family: @text-bold-italic;
91 }
92 }
92
93
93 .resolve {
94 .resolve {
94 cursor: pointer;
95 cursor: pointer;
95 text-decoration: underline;
96 text-decoration: underline;
96 }
97 }
97
98
98 .resolved {
99 .resolved {
99 text-decoration: line-through;
100 text-decoration: line-through;
100 color: @color1;
101 color: @color1;
101 }
102 }
102 .resolved a {
103 .resolved a {
103 text-decoration: line-through;
104 text-decoration: line-through;
104 color: @color1;
105 color: @color1;
105 }
106 }
106 .resolve-text {
107 .resolve-text {
107 color: @color1;
108 color: @color1;
108 margin: 2px 8px;
109 margin: 2px 8px;
109 font-family: @text-italic;
110 font-family: @text-italic;
110 font-style: italic;
111 font-style: italic;
111 }
112 }
112 }
113 }
113
114
114 .has-spacer-after {
115 .has-spacer-after {
115 &:after {
116 &:after {
116 content: ' | ';
117 content: ' | ';
117 color: @grey5;
118 color: @grey5;
118 }
119 }
119 }
120 }
120
121
121 .has-spacer-before {
122 .has-spacer-before {
122 &:before {
123 &:before {
123 content: ' | ';
124 content: ' | ';
124 color: @grey5;
125 color: @grey5;
125 }
126 }
126 }
127 }
127
128
128 .comment {
129 .comment {
129
130
130 &.comment-general {
131 &.comment-general {
131 border: 1px solid @grey5;
132 border: 1px solid @grey5;
132 padding: 5px 5px 5px 5px;
133 padding: 5px 5px 5px 5px;
133 }
134 }
134
135
135 margin: @padding 0;
136 margin: @padding 0;
136 padding: 4px 0 0 0;
137 padding: 4px 0 0 0;
137 line-height: 1em;
138 line-height: 1em;
138
139
139 .rc-user {
140 .rc-user {
140 min-width: 0;
141 min-width: 0;
141 margin: 0px .5em 0 0;
142 margin: 0px .5em 0 0;
142
143
143 .user {
144 .user {
144 display: inline;
145 display: inline;
145 }
146 }
146 }
147 }
147
148
148 .meta {
149 .meta {
149 position: relative;
150 position: relative;
150 width: 100%;
151 width: 100%;
151 border-bottom: 1px solid @grey5;
152 border-bottom: 1px solid @grey5;
152 margin: -5px 0px;
153 margin: -5px 0px;
153 line-height: 24px;
154 line-height: 24px;
154
155
155 &:hover .permalink {
156 &:hover .permalink {
156 visibility: visible;
157 visibility: visible;
157 color: @rcblue;
158 color: @rcblue;
158 }
159 }
159 }
160 }
160
161
161 .author,
162 .author,
162 .date {
163 .date {
163 display: inline;
164 display: inline;
164
165
165 &:after {
166 &:after {
166 content: ' | ';
167 content: ' | ';
167 color: @grey5;
168 color: @grey5;
168 }
169 }
169 }
170 }
170
171
171 .author-general img {
172 .author-general img {
172 top: 3px;
173 top: 3px;
173 }
174 }
174 .author-inline img {
175 .author-inline img {
175 top: 3px;
176 top: 3px;
176 }
177 }
177
178
178 .status-change,
179 .status-change,
179 .permalink,
180 .permalink,
180 .changeset-status-lbl {
181 .changeset-status-lbl {
181 display: inline;
182 display: inline;
182 }
183 }
183
184
184 .permalink {
185 .permalink {
185 visibility: hidden;
186 visibility: hidden;
186 }
187 }
187
188
188 .comment-links-divider {
189 .comment-links-divider {
189 display: inline;
190 display: inline;
190 }
191 }
191
192
192 .comment-links-block {
193 .comment-links-block {
193 float:right;
194 float:right;
194 text-align: right;
195 text-align: right;
195 min-width: 85px;
196 min-width: 85px;
196
197
197 [class^="icon-"]:before,
198 [class^="icon-"]:before,
198 [class*=" icon-"]:before {
199 [class*=" icon-"]:before {
199 margin-left: 0;
200 margin-left: 0;
200 margin-right: 0;
201 margin-right: 0;
201 }
202 }
202 }
203 }
203
204
204 .comment-previous-link {
205 .comment-previous-link {
205 display: inline-block;
206 display: inline-block;
206
207
207 .arrow_comment_link{
208 .arrow_comment_link{
208 cursor: pointer;
209 cursor: pointer;
209 i {
210 i {
210 font-size:10px;
211 font-size:10px;
211 }
212 }
212 }
213 }
213 .arrow_comment_link.disabled {
214 .arrow_comment_link.disabled {
214 cursor: default;
215 cursor: default;
215 color: @grey5;
216 color: @grey5;
216 }
217 }
217 }
218 }
218
219
219 .comment-next-link {
220 .comment-next-link {
220 display: inline-block;
221 display: inline-block;
221
222
222 .arrow_comment_link{
223 .arrow_comment_link{
223 cursor: pointer;
224 cursor: pointer;
224 i {
225 i {
225 font-size:10px;
226 font-size:10px;
226 }
227 }
227 }
228 }
228 .arrow_comment_link.disabled {
229 .arrow_comment_link.disabled {
229 cursor: default;
230 cursor: default;
230 color: @grey5;
231 color: @grey5;
231 }
232 }
232 }
233 }
233
234
234 .delete-comment {
235 .delete-comment {
235 display: inline-block;
236 display: inline-block;
236 color: @rcblue;
237 color: @rcblue;
237
238
238 &:hover {
239 &:hover {
239 cursor: pointer;
240 cursor: pointer;
240 }
241 }
241 }
242 }
242
243
243 .text {
244 .text {
244 clear: both;
245 clear: both;
245 .border-radius(@border-radius);
246 .border-radius(@border-radius);
246 .box-sizing(border-box);
247 .box-sizing(border-box);
247
248
248 .markdown-block p,
249 .markdown-block p,
249 .rst-block p {
250 .rst-block p {
250 margin: .5em 0 !important;
251 margin: .5em 0 !important;
251 // TODO: lisa: This is needed because of other rst !important rules :[
252 // TODO: lisa: This is needed because of other rst !important rules :[
252 }
253 }
253 }
254 }
254
255
255 .pr-version {
256 .pr-version {
256 display: inline-block;
257 display: inline-block;
257 }
258 }
258 .pr-version-inline {
259 .pr-version-inline {
259 display: inline-block;
260 display: inline-block;
260 }
261 }
261 .pr-version-num {
262 .pr-version-num {
262 font-size: 10px;
263 font-size: 10px;
263 }
264 }
264 }
265 }
265
266
266 @comment-padding: 5px;
267 @comment-padding: 5px;
267
268
268 .general-comments {
269 .general-comments {
269 .comment-outdated {
270 .comment-outdated {
270 opacity: @comment-outdated-opacity;
271 opacity: @comment-outdated-opacity;
271 }
272 }
272 }
273 }
273
274
274 .inline-comments {
275 .inline-comments {
275 border-radius: @border-radius;
276 border-radius: @border-radius;
276 .comment {
277 .comment {
277 margin: 0;
278 margin: 0;
278 border-radius: @border-radius;
279 border-radius: @border-radius;
279 }
280 }
280 .comment-outdated {
281 .comment-outdated {
281 opacity: @comment-outdated-opacity;
282 opacity: @comment-outdated-opacity;
282 }
283 }
283
284
284 .comment-inline {
285 .comment-inline {
285 background: white;
286 background: white;
286 padding: @comment-padding @comment-padding;
287 padding: @comment-padding @comment-padding;
287 border: @comment-padding solid @grey6;
288 border: @comment-padding solid @grey6;
288
289
289 .text {
290 .text {
290 border: none;
291 border: none;
291 }
292 }
292 .meta {
293 .meta {
293 border-bottom: 1px solid @grey6;
294 border-bottom: 1px solid @grey6;
294 margin: -5px 0px;
295 margin: -5px 0px;
295 line-height: 24px;
296 line-height: 24px;
296 }
297 }
297 }
298 }
298 .comment-selected {
299 .comment-selected {
299 border-left: 6px solid @comment-highlight-color;
300 border-left: 6px solid @comment-highlight-color;
300 }
301 }
301 .comment-inline-form {
302 .comment-inline-form {
302 padding: @comment-padding;
303 padding: @comment-padding;
303 display: none;
304 display: none;
304 }
305 }
305 .cb-comment-add-button {
306 .cb-comment-add-button {
306 margin: @comment-padding;
307 margin: @comment-padding;
307 }
308 }
308 /* hide add comment button when form is open */
309 /* hide add comment button when form is open */
309 .comment-inline-form-open ~ .cb-comment-add-button {
310 .comment-inline-form-open ~ .cb-comment-add-button {
310 display: none;
311 display: none;
311 }
312 }
312 .comment-inline-form-open {
313 .comment-inline-form-open {
313 display: block;
314 display: block;
314 }
315 }
315 /* hide add comment button when form but no comments */
316 /* hide add comment button when form but no comments */
316 .comment-inline-form:first-child + .cb-comment-add-button {
317 .comment-inline-form:first-child + .cb-comment-add-button {
317 display: none;
318 display: none;
318 }
319 }
319 /* hide add comment button when no comments or form */
320 /* hide add comment button when no comments or form */
320 .cb-comment-add-button:first-child {
321 .cb-comment-add-button:first-child {
321 display: none;
322 display: none;
322 }
323 }
323 /* hide add comment button when only comment is being deleted */
324 /* hide add comment button when only comment is being deleted */
324 .comment-deleting:first-child + .cb-comment-add-button {
325 .comment-deleting:first-child + .cb-comment-add-button {
325 display: none;
326 display: none;
326 }
327 }
327 }
328 }
328
329
329
330
330 .show-outdated-comments {
331 .show-outdated-comments {
331 display: inline;
332 display: inline;
332 color: @rcblue;
333 color: @rcblue;
333 }
334 }
334
335
335 // Comment Form
336 // Comment Form
336 div.comment-form {
337 div.comment-form {
337 margin-top: 20px;
338 margin-top: 20px;
338 }
339 }
339
340
340 .comment-form strong {
341 .comment-form strong {
341 display: block;
342 display: block;
342 margin-bottom: 15px;
343 margin-bottom: 15px;
343 }
344 }
344
345
345 .comment-form textarea {
346 .comment-form textarea {
346 width: 100%;
347 width: 100%;
347 height: 100px;
348 height: 100px;
348 font-family: @text-monospace;
349 font-family: @text-monospace;
349 }
350 }
350
351
351 form.comment-form {
352 form.comment-form {
352 margin-top: 10px;
353 margin-top: 10px;
353 margin-left: 10px;
354 margin-left: 10px;
354 }
355 }
355
356
356 .comment-inline-form .comment-block-ta,
357 .comment-inline-form .comment-block-ta,
357 .comment-form .comment-block-ta,
358 .comment-form .comment-block-ta,
358 .comment-form .preview-box {
359 .comment-form .preview-box {
359 .border-radius(@border-radius);
360 .border-radius(@border-radius);
360 .box-sizing(border-box);
361 .box-sizing(border-box);
361 background-color: white;
362 background-color: white;
362 }
363 }
363
364
364 .comment-form-submit {
365 .comment-form-submit {
365 margin-top: 5px;
366 margin-top: 5px;
366 margin-left: 525px;
367 margin-left: 525px;
367 }
368 }
368
369
369 .file-comments {
370 .file-comments {
370 display: none;
371 display: none;
371 }
372 }
372
373
373 .comment-form .preview-box.unloaded,
374 .comment-form .preview-box.unloaded,
374 .comment-inline-form .preview-box.unloaded {
375 .comment-inline-form .preview-box.unloaded {
375 height: 50px;
376 height: 50px;
376 text-align: center;
377 text-align: center;
377 padding: 20px;
378 padding: 20px;
378 background-color: white;
379 background-color: white;
379 }
380 }
380
381
381 .comment-footer {
382 .comment-footer {
382 position: relative;
383 position: relative;
383 width: 100%;
384 width: 100%;
384 min-height: 42px;
385 min-height: 42px;
385
386
386 .status_box,
387 .status_box,
387 .cancel-button {
388 .cancel-button {
388 float: left;
389 float: left;
389 display: inline-block;
390 display: inline-block;
390 }
391 }
391
392
392 .status_box {
393 .status_box {
393 margin-left: 10px;
394 margin-left: 10px;
394 }
395 }
395
396
396 .action-buttons {
397 .action-buttons {
397 float: left;
398 float: left;
398 display: inline-block;
399 display: inline-block;
399 }
400 }
400
401
401 .action-buttons-extra {
402 .action-buttons-extra {
402 display: inline-block;
403 display: inline-block;
403 }
404 }
404 }
405 }
405
406
406 .comment-form {
407 .comment-form {
407
408
408 .comment {
409 .comment {
409 margin-left: 10px;
410 margin-left: 10px;
410 }
411 }
411
412
412 .comment-help {
413 .comment-help {
413 color: @grey4;
414 color: @grey4;
414 padding: 5px 0 5px 0;
415 padding: 5px 0 5px 0;
415 }
416 }
416
417
417 .comment-title {
418 .comment-title {
418 padding: 5px 0 5px 0;
419 padding: 5px 0 5px 0;
419 }
420 }
420
421
421 .comment-button {
422 .comment-button {
422 display: inline-block;
423 display: inline-block;
423 }
424 }
424
425
425 .comment-button-input {
426 .comment-button-input {
426 margin-right: 0;
427 margin-right: 0;
427 }
428 }
428
429
429 .comment-footer {
430 .comment-footer {
430 margin-bottom: 50px;
431 margin-bottom: 50px;
431 margin-top: 10px;
432 margin-top: 10px;
432 }
433 }
433 }
434 }
434
435
435
436
436 .comment-form-login {
437 .comment-form-login {
437 .comment-help {
438 .comment-help {
438 padding: 0.7em; //same as the button
439 padding: 0.7em; //same as the button
439 }
440 }
440
441
441 div.clearfix {
442 div.clearfix {
442 clear: both;
443 clear: both;
443 width: 100%;
444 width: 100%;
444 display: block;
445 display: block;
445 }
446 }
446 }
447 }
447
448
448 .comment-version-select {
449 .comment-version-select {
449 margin: 0px;
450 margin: 0px;
450 border-radius: inherit;
451 border-radius: inherit;
451 border-color: @grey6;
452 border-color: @grey6;
452 height: 20px;
453 height: 20px;
453 }
454 }
454
455
455 .comment-type {
456 .comment-type {
456 margin: 0px;
457 margin: 0px;
457 border-radius: inherit;
458 border-radius: inherit;
458 border-color: @grey6;
459 border-color: @grey6;
459 }
460 }
460
461
461 .preview-box {
462 .preview-box {
462 min-height: 105px;
463 min-height: 105px;
463 margin-bottom: 15px;
464 margin-bottom: 15px;
464 background-color: white;
465 background-color: white;
465 .border-radius(@border-radius);
466 .border-radius(@border-radius);
466 .box-sizing(border-box);
467 .box-sizing(border-box);
467 }
468 }
468
469
469 .add-another-button {
470 .add-another-button {
470 margin-left: 10px;
471 margin-left: 10px;
471 margin-top: 10px;
472 margin-top: 10px;
472 margin-bottom: 10px;
473 margin-bottom: 10px;
473 }
474 }
474
475
475 .comment .buttons {
476 .comment .buttons {
476 float: right;
477 float: right;
477 margin: -1px 0px 0px 0px;
478 margin: -1px 0px 0px 0px;
478 }
479 }
479
480
480 // Inline Comment Form
481 // Inline Comment Form
481 .injected_diff .comment-inline-form,
482 .injected_diff .comment-inline-form,
482 .comment-inline-form {
483 .comment-inline-form {
483 background-color: white;
484 background-color: white;
484 margin-top: 10px;
485 margin-top: 10px;
485 margin-bottom: 20px;
486 margin-bottom: 20px;
486 }
487 }
487
488
488 .inline-form {
489 .inline-form {
489 padding: 10px 7px;
490 padding: 10px 7px;
490 }
491 }
491
492
492 .inline-form div {
493 .inline-form div {
493 max-width: 100%;
494 max-width: 100%;
494 }
495 }
495
496
496 .overlay {
497 .overlay {
497 display: none;
498 display: none;
498 position: absolute;
499 position: absolute;
499 width: 100%;
500 width: 100%;
500 text-align: center;
501 text-align: center;
501 vertical-align: middle;
502 vertical-align: middle;
502 font-size: 16px;
503 font-size: 16px;
503 background: none repeat scroll 0 0 white;
504 background: none repeat scroll 0 0 white;
504
505
505 &.submitting {
506 &.submitting {
506 display: block;
507 display: block;
507 opacity: 0.5;
508 opacity: 0.5;
508 z-index: 100;
509 z-index: 100;
509 }
510 }
510 }
511 }
511 .comment-inline-form .overlay.submitting .overlay-text {
512 .comment-inline-form .overlay.submitting .overlay-text {
512 margin-top: 5%;
513 margin-top: 5%;
513 }
514 }
514
515
515 .comment-inline-form .clearfix,
516 .comment-inline-form .clearfix,
516 .comment-form .clearfix {
517 .comment-form .clearfix {
517 .border-radius(@border-radius);
518 .border-radius(@border-radius);
518 margin: 0px;
519 margin: 0px;
519 }
520 }
520
521
521 .comment-inline-form .comment-footer {
522 .comment-inline-form .comment-footer {
522 margin: 10px 0px 0px 0px;
523 margin: 10px 0px 0px 0px;
523 }
524 }
524
525
525 .hide-inline-form-button {
526 .hide-inline-form-button {
526 margin-left: 5px;
527 margin-left: 5px;
527 }
528 }
528 .comment-button .hide-inline-form {
529 .comment-button .hide-inline-form {
529 background: white;
530 background: white;
530 }
531 }
531
532
532 .comment-area {
533 .comment-area {
533 padding: 6px 8px;
534 padding: 6px 8px;
534 border: 1px solid @grey5;
535 border: 1px solid @grey5;
535 .border-radius(@border-radius);
536 .border-radius(@border-radius);
536
537
537 .resolve-action {
538 .resolve-action {
538 padding: 1px 0px 0px 6px;
539 padding: 1px 0px 0px 6px;
539 }
540 }
540
541
541 }
542 }
542
543
543 comment-area-text {
544 comment-area-text {
544 color: @grey3;
545 color: @grey3;
545 }
546 }
546
547
547 .comment-area-header {
548 .comment-area-header {
548 height: 35px;
549 height: 35px;
549 }
550 }
550
551
551 .comment-area-header .nav-links {
552 .comment-area-header .nav-links {
552 display: flex;
553 display: flex;
553 flex-flow: row wrap;
554 flex-flow: row wrap;
554 -webkit-flex-flow: row wrap;
555 -webkit-flex-flow: row wrap;
555 width: 100%;
556 width: 100%;
556 }
557 }
557
558
558 .comment-area-footer {
559 .comment-area-footer {
559 min-height: 30px;
560 min-height: 30px;
560 }
561 }
561
562
562 .comment-footer .toolbar {
563 .comment-footer .toolbar {
563
564
564 }
565 }
565
566
566 .comment-attachment-uploader {
567 .comment-attachment-uploader {
567 border: 1px dashed white;
568 border: 1px dashed white;
568 border-radius: @border-radius;
569 border-radius: @border-radius;
569 margin-top: -10px;
570 margin-top: -10px;
570 line-height: 30px;
571 line-height: 30px;
571 &.dz-drag-hover {
572 &.dz-drag-hover {
572 border-color: @grey3;
573 border-color: @grey3;
573 }
574 }
574
575
575 .dz-error-message {
576 .dz-error-message {
576 padding-top: 0;
577 padding-top: 0;
577 }
578 }
578 }
579 }
579
580
580 .comment-attachment-text {
581 .comment-attachment-text {
581 clear: both;
582 clear: both;
582 font-size: 11px;
583 font-size: 11px;
583 color: #8F8F8F;
584 color: #8F8F8F;
584 width: 100%;
585 width: 100%;
585 .pick-attachment {
586 .pick-attachment {
586 color: #8F8F8F;
587 color: #8F8F8F;
587 }
588 }
588 .pick-attachment:hover {
589 .pick-attachment:hover {
589 color: @rcblue;
590 color: @rcblue;
590 }
591 }
591 }
592 }
592
593
593 .nav-links {
594 .nav-links {
594 padding: 0;
595 padding: 0;
595 margin: 0;
596 margin: 0;
596 list-style: none;
597 list-style: none;
597 height: auto;
598 height: auto;
598 border-bottom: 1px solid @grey5;
599 border-bottom: 1px solid @grey5;
599 }
600 }
600 .nav-links li {
601 .nav-links li {
601 display: inline-block;
602 display: inline-block;
602 list-style-type: none;
603 list-style-type: none;
603 }
604 }
604
605
605 .nav-links li a.disabled {
606 .nav-links li a.disabled {
606 cursor: not-allowed;
607 cursor: not-allowed;
607 }
608 }
608
609
609 .nav-links li.active a {
610 .nav-links li.active a {
610 border-bottom: 2px solid @rcblue;
611 border-bottom: 2px solid @rcblue;
611 color: #000;
612 color: #000;
612 font-weight: 600;
613 font-weight: 600;
613 }
614 }
614 .nav-links li a {
615 .nav-links li a {
615 display: inline-block;
616 display: inline-block;
616 padding: 0px 10px 5px 10px;
617 padding: 0px 10px 5px 10px;
617 margin-bottom: -1px;
618 margin-bottom: -1px;
618 font-size: 14px;
619 font-size: 14px;
619 line-height: 28px;
620 line-height: 28px;
620 color: #8f8f8f;
621 color: #8f8f8f;
621 border-bottom: 2px solid transparent;
622 border-bottom: 2px solid transparent;
622 }
623 }
623
624
624 .toolbar-text {
625 .toolbar-text {
625 float: right;
626 float: right;
626 font-size: 11px;
627 font-size: 11px;
627 color: @grey4;
628 color: @grey4;
628 text-align: right;
629 text-align: right;
629
630
630 a {
631 a {
631 color: @grey4;
632 color: @grey4;
632 }
633 }
633 }
634 }
634
635
@@ -1,481 +1,491 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ## usage:
2 ## usage:
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 ## ${comment.comment_block(comment)}
4 ## ${comment.comment_block(comment)}
5 ##
5 ##
6
6
7 <%!
7 <%!
8 from rhodecode.lib import html_filters
8 from rhodecode.lib import html_filters
9 %>
9 %>
10
10
11 <%namespace name="base" file="/base/base.mako"/>
11 <%namespace name="base" file="/base/base.mako"/>
12 <%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
12 <%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
13 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
13 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
14 <% latest_ver = len(getattr(c, 'versions', [])) %>
14 <% latest_ver = len(getattr(c, 'versions', [])) %>
15 % if inline:
15 % if inline:
16 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
16 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
17 % else:
17 % else:
18 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
18 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
19 % endif
19 % endif
20
20
21 <div class="comment
21 <div class="comment
22 ${'comment-inline' if inline else 'comment-general'}
22 ${'comment-inline' if inline else 'comment-general'}
23 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
23 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
24 id="comment-${comment.comment_id}"
24 id="comment-${comment.comment_id}"
25 line="${comment.line_no}"
25 line="${comment.line_no}"
26 data-comment-id="${comment.comment_id}"
26 data-comment-id="${comment.comment_id}"
27 data-comment-type="${comment.comment_type}"
27 data-comment-type="${comment.comment_type}"
28 data-comment-renderer="${comment.renderer}"
28 data-comment-renderer="${comment.renderer}"
29 data-comment-text="${comment.text | html_filters.base64,n}"
29 data-comment-text="${comment.text | html_filters.base64,n}"
30 data-comment-line-no="${comment.line_no}"
30 data-comment-line-no="${comment.line_no}"
31 data-comment-inline=${h.json.dumps(inline)}
31 data-comment-inline=${h.json.dumps(inline)}
32 style="${'display: none;' if outdated_at_ver else ''}">
32 style="${'display: none;' if outdated_at_ver else ''}">
33
33
34 <div class="meta">
34 <div class="meta">
35 <div class="comment-type-label">
35 <div class="comment-type-label">
36 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
36 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
37
38 ## TODO COMMENT
37 % if comment.comment_type == 'todo':
39 % if comment.comment_type == 'todo':
38 % if comment.resolved:
40 % if comment.resolved:
39 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
41 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
42 <i class="icon-flag-filled"></i>
40 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
43 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
41 </div>
44 </div>
42 % else:
45 % else:
43 <div class="resolved tooltip" style="display: none">
46 <div class="resolved tooltip" style="display: none">
44 <span>${comment.comment_type}</span>
47 <span>${comment.comment_type}</span>
45 </div>
48 </div>
46 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
49 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to create resolution comment.')}">
50 <i class="icon-flag-filled"></i>
47 ${comment.comment_type}
51 ${comment.comment_type}
48 </div>
52 </div>
49 % endif
53 % endif
54 ## NOTE COMMENT
50 % else:
55 % else:
56 ## RESOLVED NOTE
51 % if comment.resolved_comment:
57 % if comment.resolved_comment:
58 <div class="tooltip" title="${_('This comment resolves TODO #{}').format(comment.resolved_comment.comment_id)}">
52 fix
59 fix
53 <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
60 <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
54 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
61 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
55 </a>
62 </a>
63 </div>
64 ## STATUS CHANGE NOTE
65 % elif not comment.is_inline and comment.status_change:
66 <%
67 if comment.pull_request:
68 status_change_title = 'Status of review for pull request !{}'.format(comment.pull_request.pull_request_id)
69 else:
70 status_change_title = 'Status of review for commit {}'.format(h.short_id(comment.commit_id))
71 %>
72
73 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
74 <div class="changeset-status-lbl tooltip" title="${status_change_title}">
75 ${comment.status_change[0].status_lbl}
76 </div>
56 % else:
77 % else:
57 ${comment.comment_type or 'note'}
78 <div>
79 <i class="icon-comment"></i>
80 ${(comment.comment_type or 'note')}
81 </div>
58 % endif
82 % endif
59 % endif
83 % endif
84
60 </div>
85 </div>
61 </div>
86 </div>
62
87
88 % if 0 and comment.status_change:
89 <div class="pull-left">
90 <span class="tag authortag tooltip" title="${_('Status from pull request.')}">
91 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
92 ${'!{}'.format(comment.pull_request.pull_request_id)}
93 </a>
94 </span>
95 </div>
96 % endif
97
63 <div class="author ${'author-inline' if inline else 'author-general'}">
98 <div class="author ${'author-inline' if inline else 'author-general'}">
64 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
99 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
100 </div>
101
102 <div class="date">
103 ${h.age_component(comment.modified_at, time_is_local=True)}
104 </div>
65
105
66 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
106 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
67 <span class="tag authortag tooltip" title="${_('Pull request author')}">
107 <span class="tag authortag tooltip" title="${_('Pull request author')}">
68 ${_('author')}
108 ${_('author')}
69 </span>
109 </span>
70 % endif
110 % endif
71
111
72 </div>
73 <div class="date">
74 ${h.age_component(comment.modified_at, time_is_local=True)}
75 </div>
76 <%
112 <%
77 comment_version_selector = 'comment_versions_{}'.format(comment.comment_id)
113 comment_version_selector = 'comment_versions_{}'.format(comment.comment_id)
78 %>
114 %>
79
115
80 % if comment.history:
116 % if comment.history:
81 <div class="date">
117 <div class="date">
82
118
83 <input id="${comment_version_selector}" name="${comment_version_selector}"
119 <input id="${comment_version_selector}" name="${comment_version_selector}"
84 type="hidden"
120 type="hidden"
85 data-last-version="${comment.history[-1].version}">
121 data-last-version="${comment.history[-1].version}">
86
122
87 <script type="text/javascript">
123 <script type="text/javascript">
88
124
89 var preLoadVersionData = [
125 var preLoadVersionData = [
90 % for comment_history in comment.history:
126 % for comment_history in comment.history:
91 {
127 {
92 id: ${comment_history.comment_history_id},
128 id: ${comment_history.comment_history_id},
93 text: 'v${comment_history.version}',
129 text: 'v${comment_history.version}',
94 action: function () {
130 action: function () {
95 Rhodecode.comments.showVersion(
131 Rhodecode.comments.showVersion(
96 "${comment.comment_id}",
132 "${comment.comment_id}",
97 "${comment_history.comment_history_id}"
133 "${comment_history.comment_history_id}"
98 )
134 )
99 },
135 },
100 comment_version: "${comment_history.version}",
136 comment_version: "${comment_history.version}",
101 comment_author_username: "${comment_history.author.username}",
137 comment_author_username: "${comment_history.author.username}",
102 comment_author_gravatar: "${h.gravatar_url(comment_history.author.email, 16)}",
138 comment_author_gravatar: "${h.gravatar_url(comment_history.author.email, 16)}",
103 comment_created_on: '${h.age_component(comment_history.created_on, time_is_local=True)}',
139 comment_created_on: '${h.age_component(comment_history.created_on, time_is_local=True)}',
104 },
140 },
105 % endfor
141 % endfor
106 ]
142 ]
107 initVersionSelector("#${comment_version_selector}", {results: preLoadVersionData});
143 initVersionSelector("#${comment_version_selector}", {results: preLoadVersionData});
108
144
109 </script>
145 </script>
110
146
111 </div>
147 </div>
112 % else:
148 % else:
113 <div class="date" style="display: none">
149 <div class="date" style="display: none">
114 <input id="${comment_version_selector}" name="${comment_version_selector}"
150 <input id="${comment_version_selector}" name="${comment_version_selector}"
115 type="hidden"
151 type="hidden"
116 data-last-version="0">
152 data-last-version="0">
117 </div>
153 </div>
118 %endif
154 %endif
119 % if inline:
120 <span></span>
121 % else:
122 <div class="status-change">
123 % if comment.pull_request:
124 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
125 % if comment.status_change:
126 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}:
127 % else:
128 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}
129 % endif
130 </a>
131 % else:
132 % if comment.status_change:
133 ${_('Status change on commit')}:
134 % endif
135 % endif
136 </div>
137 % endif
138
139 % if comment.status_change:
140 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
141 <div title="${_('Commit status')}" class="changeset-status-lbl">
142 ${comment.status_change[0].status_lbl}
143 </div>
144 % endif
145
155
146 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
156 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
147
157
148 <div class="comment-links-block">
158 <div class="comment-links-block">
149
159
150 % if inline:
160 % if inline:
151 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
161 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
152 % if outdated_at_ver:
162 % if outdated_at_ver:
153 <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
163 <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
154 outdated ${'v{}'.format(pr_index_ver)} |
164 outdated ${'v{}'.format(pr_index_ver)} |
155 </code>
165 </code>
156 % elif pr_index_ver:
166 % elif pr_index_ver:
157 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
167 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
158 ${'v{}'.format(pr_index_ver)} |
168 ${'v{}'.format(pr_index_ver)} |
159 </code>
169 </code>
160 % endif
170 % endif
161 </a>
171 </a>
162 % else:
172 % else:
163 % if comment.pull_request_version_id and pr_index_ver:
173 % if pr_index_ver:
164
174
165 % if comment.outdated:
175 % if comment.outdated:
166 <a class="pr-version"
176 <a class="pr-version"
167 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
177 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
168 >
178 >
169 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
179 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
170 </a> |
180 </a> |
171 % else:
181 % else:
172 <a class="pr-version"
182 <a class="tooltip pr-version"
173 title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}"
183 title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}"
174 href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}"
184 href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}"
175 >
185 >
176 <code class="pr-version-num">
186 <code class="pr-version-num">
177 ${'v{}'.format(pr_index_ver)}
187 ${'v{}'.format(pr_index_ver)}
178 </code>
188 </code>
179 </a> |
189 </a> |
180 % endif
190 % endif
181
191
182 % endif
192 % endif
183 % endif
193 % endif
184
194
185 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
195 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
186 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
196 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
187 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
197 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
188 ## permissions to delete
198 ## permissions to delete
189 %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
199 %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
190 <a onclick="return Rhodecode.comments.editComment(this);"
200 <a onclick="return Rhodecode.comments.editComment(this);"
191 class="edit-comment">${_('Edit')}</a>
201 class="edit-comment">${_('Edit')}</a>
192 | <a onclick="return Rhodecode.comments.deleteComment(this);"
202 | <a onclick="return Rhodecode.comments.deleteComment(this);"
193 class="delete-comment">${_('Delete')}</a>
203 class="delete-comment">${_('Delete')}</a>
194 %else:
204 %else:
195 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
205 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
196 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
206 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
197 %endif
207 %endif
198 %else:
208 %else:
199 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
209 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
200 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
210 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
201 %endif
211 %endif
202
212
203 % if outdated_at_ver:
213 % if outdated_at_ver:
204 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a>
214 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a>
205 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a>
215 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a>
206 % else:
216 % else:
207 | <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
217 | <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
208 | <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
218 | <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
209 % endif
219 % endif
210
220
211 </div>
221 </div>
212 </div>
222 </div>
213 <div class="text">
223 <div class="text">
214 ${h.render(comment.text, renderer=comment.renderer, mentions=True, repo_name=getattr(c, 'repo_name', None), active_pattern_entries=active_pattern_entries)}
224 ${h.render(comment.text, renderer=comment.renderer, mentions=True, repo_name=getattr(c, 'repo_name', None), active_pattern_entries=active_pattern_entries)}
215 </div>
225 </div>
216
226
217 </div>
227 </div>
218 </%def>
228 </%def>
219
229
220 ## generate main comments
230 ## generate main comments
221 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
231 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
222 <%
232 <%
223 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
233 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
224 %>
234 %>
225
235
226 <div class="general-comments" id="comments">
236 <div class="general-comments" id="comments">
227 %for comment in comments:
237 %for comment in comments:
228 <div id="comment-tr-${comment.comment_id}">
238 <div id="comment-tr-${comment.comment_id}">
229 ## only render comments that are not from pull request, or from
239 ## only render comments that are not from pull request, or from
230 ## pull request and a status change
240 ## pull request and a status change
231 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
241 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
232 ${comment_block(comment, active_pattern_entries=active_pattern_entries)}
242 ${comment_block(comment, active_pattern_entries=active_pattern_entries)}
233 %endif
243 %endif
234 </div>
244 </div>
235 %endfor
245 %endfor
236 ## to anchor ajax comments
246 ## to anchor ajax comments
237 <div id="injected_page_comments"></div>
247 <div id="injected_page_comments"></div>
238 </div>
248 </div>
239 </%def>
249 </%def>
240
250
241
251
242 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
252 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
243
253
244 <div class="comments">
254 <div class="comments">
245 <%
255 <%
246 if is_pull_request:
256 if is_pull_request:
247 placeholder = _('Leave a comment on this Pull Request.')
257 placeholder = _('Leave a comment on this Pull Request.')
248 elif is_compare:
258 elif is_compare:
249 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
259 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
250 else:
260 else:
251 placeholder = _('Leave a comment on this Commit.')
261 placeholder = _('Leave a comment on this Commit.')
252 %>
262 %>
253
263
254 % if c.rhodecode_user.username != h.DEFAULT_USER:
264 % if c.rhodecode_user.username != h.DEFAULT_USER:
255 <div class="js-template" id="cb-comment-general-form-template">
265 <div class="js-template" id="cb-comment-general-form-template">
256 ## template generated for injection
266 ## template generated for injection
257 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
267 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
258 </div>
268 </div>
259
269
260 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
270 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
261 ## inject form here
271 ## inject form here
262 </div>
272 </div>
263 <script type="text/javascript">
273 <script type="text/javascript">
264 var lineNo = 'general';
274 var lineNo = 'general';
265 var resolvesCommentId = null;
275 var resolvesCommentId = null;
266 var generalCommentForm = Rhodecode.comments.createGeneralComment(
276 var generalCommentForm = Rhodecode.comments.createGeneralComment(
267 lineNo, "${placeholder}", resolvesCommentId);
277 lineNo, "${placeholder}", resolvesCommentId);
268
278
269 // set custom success callback on rangeCommit
279 // set custom success callback on rangeCommit
270 % if is_compare:
280 % if is_compare:
271 generalCommentForm.setHandleFormSubmit(function(o) {
281 generalCommentForm.setHandleFormSubmit(function(o) {
272 var self = generalCommentForm;
282 var self = generalCommentForm;
273
283
274 var text = self.cm.getValue();
284 var text = self.cm.getValue();
275 var status = self.getCommentStatus();
285 var status = self.getCommentStatus();
276 var commentType = self.getCommentType();
286 var commentType = self.getCommentType();
277
287
278 if (text === "" && !status) {
288 if (text === "" && !status) {
279 return;
289 return;
280 }
290 }
281
291
282 // we can pick which commits we want to make the comment by
292 // we can pick which commits we want to make the comment by
283 // selecting them via click on preview pane, this will alter the hidden inputs
293 // selecting them via click on preview pane, this will alter the hidden inputs
284 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
294 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
285
295
286 var commitIds = [];
296 var commitIds = [];
287 $('#changeset_compare_view_content .compare_select').each(function(el) {
297 $('#changeset_compare_view_content .compare_select').each(function(el) {
288 var commitId = this.id.replace('row-', '');
298 var commitId = this.id.replace('row-', '');
289 if ($(this).hasClass('hl') || !cherryPicked) {
299 if ($(this).hasClass('hl') || !cherryPicked) {
290 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
300 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
291 commitIds.push(commitId);
301 commitIds.push(commitId);
292 } else {
302 } else {
293 $("input[data-commit-id='{0}']".format(commitId)).val('')
303 $("input[data-commit-id='{0}']".format(commitId)).val('')
294 }
304 }
295 });
305 });
296
306
297 self.setActionButtonsDisabled(true);
307 self.setActionButtonsDisabled(true);
298 self.cm.setOption("readOnly", true);
308 self.cm.setOption("readOnly", true);
299 var postData = {
309 var postData = {
300 'text': text,
310 'text': text,
301 'changeset_status': status,
311 'changeset_status': status,
302 'comment_type': commentType,
312 'comment_type': commentType,
303 'commit_ids': commitIds,
313 'commit_ids': commitIds,
304 'csrf_token': CSRF_TOKEN
314 'csrf_token': CSRF_TOKEN
305 };
315 };
306
316
307 var submitSuccessCallback = function(o) {
317 var submitSuccessCallback = function(o) {
308 location.reload(true);
318 location.reload(true);
309 };
319 };
310 var submitFailCallback = function(){
320 var submitFailCallback = function(){
311 self.resetCommentFormState(text)
321 self.resetCommentFormState(text)
312 };
322 };
313 self.submitAjaxPOST(
323 self.submitAjaxPOST(
314 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
324 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
315 });
325 });
316 % endif
326 % endif
317
327
318 </script>
328 </script>
319 % else:
329 % else:
320 ## form state when not logged in
330 ## form state when not logged in
321 <div class="comment-form ac">
331 <div class="comment-form ac">
322
332
323 <div class="comment-area">
333 <div class="comment-area">
324 <div class="comment-area-header">
334 <div class="comment-area-header">
325 <ul class="nav-links clearfix">
335 <ul class="nav-links clearfix">
326 <li class="active">
336 <li class="active">
327 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
337 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
328 </li>
338 </li>
329 <li class="">
339 <li class="">
330 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
340 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
331 </li>
341 </li>
332 </ul>
342 </ul>
333 </div>
343 </div>
334
344
335 <div class="comment-area-write" style="display: block;">
345 <div class="comment-area-write" style="display: block;">
336 <div id="edit-container">
346 <div id="edit-container">
337 <div style="padding: 40px 0">
347 <div style="padding: 40px 0">
338 ${_('You need to be logged in to leave comments.')}
348 ${_('You need to be logged in to leave comments.')}
339 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
349 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
340 </div>
350 </div>
341 </div>
351 </div>
342 <div id="preview-container" class="clearfix" style="display: none;">
352 <div id="preview-container" class="clearfix" style="display: none;">
343 <div id="preview-box" class="preview-box"></div>
353 <div id="preview-box" class="preview-box"></div>
344 </div>
354 </div>
345 </div>
355 </div>
346
356
347 <div class="comment-area-footer">
357 <div class="comment-area-footer">
348 <div class="toolbar">
358 <div class="toolbar">
349 <div class="toolbar-text">
359 <div class="toolbar-text">
350 </div>
360 </div>
351 </div>
361 </div>
352 </div>
362 </div>
353 </div>
363 </div>
354
364
355 <div class="comment-footer">
365 <div class="comment-footer">
356 </div>
366 </div>
357
367
358 </div>
368 </div>
359 % endif
369 % endif
360
370
361 <script type="text/javascript">
371 <script type="text/javascript">
362 bindToggleButtons();
372 bindToggleButtons();
363 </script>
373 </script>
364 </div>
374 </div>
365 </%def>
375 </%def>
366
376
367
377
368 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
378 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
369
379
370 ## comment injected based on assumption that user is logged in
380 ## comment injected based on assumption that user is logged in
371 <form ${('id="{}"'.format(form_id) if form_id else '') |n} action="#" method="GET">
381 <form ${('id="{}"'.format(form_id) if form_id else '') |n} action="#" method="GET">
372
382
373 <div class="comment-area">
383 <div class="comment-area">
374 <div class="comment-area-header">
384 <div class="comment-area-header">
375 <div class="pull-left">
385 <div class="pull-left">
376 <ul class="nav-links clearfix">
386 <ul class="nav-links clearfix">
377 <li class="active">
387 <li class="active">
378 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
388 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
379 </li>
389 </li>
380 <li class="">
390 <li class="">
381 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
391 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
382 </li>
392 </li>
383 </ul>
393 </ul>
384 </div>
394 </div>
385 <div class="pull-right">
395 <div class="pull-right">
386 <span class="comment-area-text">${_('Mark as')}:</span>
396 <span class="comment-area-text">${_('Mark as')}:</span>
387 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
397 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
388 % for val in c.visual.comment_types:
398 % for val in c.visual.comment_types:
389 <option value="${val}">${val.upper()}</option>
399 <option value="${val}">${val.upper()}</option>
390 % endfor
400 % endfor
391 </select>
401 </select>
392 </div>
402 </div>
393 </div>
403 </div>
394
404
395 <div class="comment-area-write" style="display: block;">
405 <div class="comment-area-write" style="display: block;">
396 <div id="edit-container_${lineno_id}">
406 <div id="edit-container_${lineno_id}">
397 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
407 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
398 </div>
408 </div>
399 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
409 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
400 <div id="preview-box_${lineno_id}" class="preview-box"></div>
410 <div id="preview-box_${lineno_id}" class="preview-box"></div>
401 </div>
411 </div>
402 </div>
412 </div>
403
413
404 <div class="comment-area-footer comment-attachment-uploader">
414 <div class="comment-area-footer comment-attachment-uploader">
405 <div class="toolbar">
415 <div class="toolbar">
406
416
407 <div class="comment-attachment-text">
417 <div class="comment-attachment-text">
408 <div class="dropzone-text">
418 <div class="dropzone-text">
409 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
419 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
410 </div>
420 </div>
411 <div class="dropzone-upload" style="display:none">
421 <div class="dropzone-upload" style="display:none">
412 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
422 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
413 </div>
423 </div>
414 </div>
424 </div>
415
425
416 ## comments dropzone template, empty on purpose
426 ## comments dropzone template, empty on purpose
417 <div style="display: none" class="comment-attachment-uploader-template">
427 <div style="display: none" class="comment-attachment-uploader-template">
418 <div class="dz-file-preview" style="margin: 0">
428 <div class="dz-file-preview" style="margin: 0">
419 <div class="dz-error-message"></div>
429 <div class="dz-error-message"></div>
420 </div>
430 </div>
421 </div>
431 </div>
422
432
423 </div>
433 </div>
424 </div>
434 </div>
425 </div>
435 </div>
426
436
427 <div class="comment-footer">
437 <div class="comment-footer">
428
438
429 ## inject extra inputs into the form
439 ## inject extra inputs into the form
430 % if form_extras and isinstance(form_extras, (list, tuple)):
440 % if form_extras and isinstance(form_extras, (list, tuple)):
431 <div id="comment_form_extras">
441 <div id="comment_form_extras">
432 % for form_ex_el in form_extras:
442 % for form_ex_el in form_extras:
433 ${form_ex_el|n}
443 ${form_ex_el|n}
434 % endfor
444 % endfor
435 </div>
445 </div>
436 % endif
446 % endif
437
447
438 <div class="action-buttons">
448 <div class="action-buttons">
439 % if form_type != 'inline':
449 % if form_type != 'inline':
440 <div class="action-buttons-extra"></div>
450 <div class="action-buttons-extra"></div>
441 % endif
451 % endif
442
452
443 <input class="btn btn-success comment-button-input" id="save_${lineno_id}" name="save" type="submit" value="${_('Comment')}">
453 <input class="btn btn-success comment-button-input" id="save_${lineno_id}" name="save" type="submit" value="${_('Comment')}">
444
454
445 ## inline for has a file, and line-number together with cancel hide button.
455 ## inline for has a file, and line-number together with cancel hide button.
446 % if form_type == 'inline':
456 % if form_type == 'inline':
447 <input type="hidden" name="f_path" value="{0}">
457 <input type="hidden" name="f_path" value="{0}">
448 <input type="hidden" name="line" value="${lineno_id}">
458 <input type="hidden" name="line" value="${lineno_id}">
449 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
459 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
450 ${_('Cancel')}
460 ${_('Cancel')}
451 </button>
461 </button>
452 % endif
462 % endif
453 </div>
463 </div>
454
464
455 % if review_statuses:
465 % if review_statuses:
456 <div class="status_box">
466 <div class="status_box">
457 <select id="change_status_${lineno_id}" name="changeset_status">
467 <select id="change_status_${lineno_id}" name="changeset_status">
458 <option></option> ## Placeholder
468 <option></option> ## Placeholder
459 % for status, lbl in review_statuses:
469 % for status, lbl in review_statuses:
460 <option value="${status}" data-status="${status}">${lbl}</option>
470 <option value="${status}" data-status="${status}">${lbl}</option>
461 %if is_pull_request and change_status and status in ('approved', 'rejected'):
471 %if is_pull_request and change_status and status in ('approved', 'rejected'):
462 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
472 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
463 %endif
473 %endif
464 % endfor
474 % endfor
465 </select>
475 </select>
466 </div>
476 </div>
467 % endif
477 % endif
468
478
469 <div class="toolbar-text">
479 <div class="toolbar-text">
470 <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %>
480 <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %>
471 ${_('Comments parsed using {} syntax.').format(renderer_url)|n} <br/>
481 ${_('Comments parsed using {} syntax.').format(renderer_url)|n} <br/>
472 <span class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span>
482 <span class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span>
473 ${_('and')}
483 ${_('and')}
474 <span class="tooltip" title="${_('Start typing with / for certain actions to be triggered via text box.')}">`/` autocomplete</span>
484 <span class="tooltip" title="${_('Start typing with / for certain actions to be triggered via text box.')}">`/` autocomplete</span>
475 ${_('actions supported.')}
485 ${_('actions supported.')}
476 </div>
486 </div>
477 </div>
487 </div>
478
488
479 </form>
489 </form>
480
490
481 </%def> No newline at end of file
491 </%def>
@@ -1,1215 +1,1218 b''
1 <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/>
1 <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/>
2
2
3 <%def name="diff_line_anchor(commit, filename, line, type)"><%
3 <%def name="diff_line_anchor(commit, filename, line, type)"><%
4 return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
4 return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
5 %></%def>
5 %></%def>
6
6
7 <%def name="action_class(action)">
7 <%def name="action_class(action)">
8 <%
8 <%
9 return {
9 return {
10 '-': 'cb-deletion',
10 '-': 'cb-deletion',
11 '+': 'cb-addition',
11 '+': 'cb-addition',
12 ' ': 'cb-context',
12 ' ': 'cb-context',
13 }.get(action, 'cb-empty')
13 }.get(action, 'cb-empty')
14 %>
14 %>
15 </%def>
15 </%def>
16
16
17 <%def name="op_class(op_id)">
17 <%def name="op_class(op_id)">
18 <%
18 <%
19 return {
19 return {
20 DEL_FILENODE: 'deletion', # file deleted
20 DEL_FILENODE: 'deletion', # file deleted
21 BIN_FILENODE: 'warning' # binary diff hidden
21 BIN_FILENODE: 'warning' # binary diff hidden
22 }.get(op_id, 'addition')
22 }.get(op_id, 'addition')
23 %>
23 %>
24 </%def>
24 </%def>
25
25
26
26
27
27
28 <%def name="render_diffset(diffset, commit=None,
28 <%def name="render_diffset(diffset, commit=None,
29
29
30 # collapse all file diff entries when there are more than this amount of files in the diff
30 # collapse all file diff entries when there are more than this amount of files in the diff
31 collapse_when_files_over=20,
31 collapse_when_files_over=20,
32
32
33 # collapse lines in the diff when more than this amount of lines changed in the file diff
33 # collapse lines in the diff when more than this amount of lines changed in the file diff
34 lines_changed_limit=500,
34 lines_changed_limit=500,
35
35
36 # add a ruler at to the output
36 # add a ruler at to the output
37 ruler_at_chars=0,
37 ruler_at_chars=0,
38
38
39 # show inline comments
39 # show inline comments
40 use_comments=False,
40 use_comments=False,
41
41
42 # disable new comments
42 # disable new comments
43 disable_new_comments=False,
43 disable_new_comments=False,
44
44
45 # special file-comments that were deleted in previous versions
45 # special file-comments that were deleted in previous versions
46 # it's used for showing outdated comments for deleted files in a PR
46 # it's used for showing outdated comments for deleted files in a PR
47 deleted_files_comments=None,
47 deleted_files_comments=None,
48
48
49 # for cache purpose
49 # for cache purpose
50 inline_comments=None,
50 inline_comments=None,
51
51
52 # additional menu for PRs
52 # additional menu for PRs
53 pull_request_menu=None,
53 pull_request_menu=None,
54
54
55 # show/hide todo next to comments
55 # show/hide todo next to comments
56 show_todos=True,
56 show_todos=True,
57
57
58 )">
58 )">
59
59
60 <%
60 <%
61 diffset_container_id = h.md5(diffset.target_ref)
61 diffset_container_id = h.md5(diffset.target_ref)
62 collapse_all = len(diffset.files) > collapse_when_files_over
62 collapse_all = len(diffset.files) > collapse_when_files_over
63 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
63 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
64 %>
64 %>
65
65
66 %if use_comments:
66 %if use_comments:
67
67
68 ## Template for injecting comments
68 ## Template for injecting comments
69 <div id="cb-comments-inline-container-template" class="js-template">
69 <div id="cb-comments-inline-container-template" class="js-template">
70 ${inline_comments_container([])}
70 ${inline_comments_container([])}
71 </div>
71 </div>
72
72
73 <div class="js-template" id="cb-comment-inline-form-template">
73 <div class="js-template" id="cb-comment-inline-form-template">
74 <div class="comment-inline-form ac">
74 <div class="comment-inline-form ac">
75
75
76 %if c.rhodecode_user.username != h.DEFAULT_USER:
76 %if c.rhodecode_user.username != h.DEFAULT_USER:
77 ## render template for inline comments
77 ## render template for inline comments
78 ${commentblock.comment_form(form_type='inline')}
78 ${commentblock.comment_form(form_type='inline')}
79 %else:
79 %else:
80 ${h.form('', class_='inline-form comment-form-login', method='get')}
80 ${h.form('', class_='inline-form comment-form-login', method='get')}
81 <div class="pull-left">
81 <div class="pull-left">
82 <div class="comment-help pull-right">
82 <div class="comment-help pull-right">
83 ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
83 ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
84 </div>
84 </div>
85 </div>
85 </div>
86 <div class="comment-button pull-right">
86 <div class="comment-button pull-right">
87 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
87 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
88 ${_('Cancel')}
88 ${_('Cancel')}
89 </button>
89 </button>
90 </div>
90 </div>
91 <div class="clearfix"></div>
91 <div class="clearfix"></div>
92 ${h.end_form()}
92 ${h.end_form()}
93 %endif
93 %endif
94 </div>
94 </div>
95 </div>
95 </div>
96
96
97 %endif
97 %endif
98
98
99 %if c.user_session_attrs["diffmode"] == 'sideside':
99 %if c.user_session_attrs["diffmode"] == 'sideside':
100 <style>
100 <style>
101 .wrapper {
101 .wrapper {
102 max-width: 1600px !important;
102 max-width: 1600px !important;
103 }
103 }
104 </style>
104 </style>
105 %endif
105 %endif
106
106
107 %if ruler_at_chars:
107 %if ruler_at_chars:
108 <style>
108 <style>
109 .diff table.cb .cb-content:after {
109 .diff table.cb .cb-content:after {
110 content: "";
110 content: "";
111 border-left: 1px solid blue;
111 border-left: 1px solid blue;
112 position: absolute;
112 position: absolute;
113 top: 0;
113 top: 0;
114 height: 18px;
114 height: 18px;
115 opacity: .2;
115 opacity: .2;
116 z-index: 10;
116 z-index: 10;
117 //## +5 to account for diff action (+/-)
117 //## +5 to account for diff action (+/-)
118 left: ${ruler_at_chars + 5}ch;
118 left: ${ruler_at_chars + 5}ch;
119 </style>
119 </style>
120 %endif
120 %endif
121
121
122 <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}">
122 <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}">
123
123
124 <div style="height: 20px; line-height: 20px">
124 <div style="height: 20px; line-height: 20px">
125 ## expand/collapse action
125 ## expand/collapse action
126 <div class="pull-left">
126 <div class="pull-left">
127 <a class="${'collapsed' if collapse_all else ''}" href="#expand-files" onclick="toggleExpand(this, '${diffset_container_id}'); return false">
127 <a class="${'collapsed' if collapse_all else ''}" href="#expand-files" onclick="toggleExpand(this, '${diffset_container_id}'); return false">
128 % if collapse_all:
128 % if collapse_all:
129 <i class="icon-plus-squared-alt icon-no-margin"></i>${_('Expand all files')}
129 <i class="icon-plus-squared-alt icon-no-margin"></i>${_('Expand all files')}
130 % else:
130 % else:
131 <i class="icon-minus-squared-alt icon-no-margin"></i>${_('Collapse all files')}
131 <i class="icon-minus-squared-alt icon-no-margin"></i>${_('Collapse all files')}
132 % endif
132 % endif
133 </a>
133 </a>
134
134
135 </div>
135 </div>
136
136
137 ## todos
137 ## todos
138 % if show_todos and getattr(c, 'at_version', None):
138 % if show_todos and getattr(c, 'at_version', None):
139 <div class="pull-right">
139 <div class="pull-right">
140 <i class="icon-flag-filled" style="color: #949494">TODOs:</i>
140 <i class="icon-flag-filled" style="color: #949494">TODOs:</i>
141 ${_('not available in this view')}
141 ${_('not available in this view')}
142 </div>
142 </div>
143 % elif show_todos:
143 % elif show_todos:
144 <div class="pull-right">
144 <div class="pull-right">
145 <div class="comments-number" style="padding-left: 10px">
145 <div class="comments-number" style="padding-left: 10px">
146 % if hasattr(c, 'unresolved_comments') and hasattr(c, 'resolved_comments'):
146 % if hasattr(c, 'unresolved_comments') and hasattr(c, 'resolved_comments'):
147 <i class="icon-flag-filled" style="color: #949494">TODOs:</i>
147 <i class="icon-flag-filled" style="color: #949494">TODOs:</i>
148 % if c.unresolved_comments:
148 % if c.unresolved_comments:
149 <a href="#show-todos" onclick="$('#todo-box').toggle(); return false">
149 <a href="#show-todos" onclick="$('#todo-box').toggle(); return false">
150 ${_('{} unresolved').format(len(c.unresolved_comments))}
150 ${_('{} unresolved').format(len(c.unresolved_comments))}
151 </a>
151 </a>
152 % else:
152 % else:
153 ${_('0 unresolved')}
153 ${_('0 unresolved')}
154 % endif
154 % endif
155
155
156 ${_('{} Resolved').format(len(c.resolved_comments))}
156 ${_('{} Resolved').format(len(c.resolved_comments))}
157 % endif
157 % endif
158 </div>
158 </div>
159 </div>
159 </div>
160 % endif
160 % endif
161
161
162 ## comments
162 ## comments
163 <div class="pull-right">
163 <div class="pull-right">
164 <div class="comments-number" style="padding-left: 10px">
164 <div class="comments-number" style="padding-left: 10px">
165 % if hasattr(c, 'comments') and hasattr(c, 'inline_cnt'):
165 % if hasattr(c, 'comments') and hasattr(c, 'inline_cnt'):
166 <i class="icon-comment" style="color: #949494">COMMENTS:</i>
166 <i class="icon-comment" style="color: #949494">COMMENTS:</i>
167 % if c.comments:
167 % if c.comments:
168 <a href="#comments">${_ungettext("{} General", "{} General", len(c.comments)).format(len(c.comments))}</a>,
168 <a href="#comments">${_ungettext("{} General", "{} General", len(c.comments)).format(len(c.comments))}</a>,
169 % else:
169 % else:
170 ${_('0 General')}
170 ${_('0 General')}
171 % endif
171 % endif
172
172
173 % if c.inline_cnt:
173 % if c.inline_cnt:
174 <a href="#" onclick="return Rhodecode.comments.nextComment();"
174 <a href="#" onclick="return Rhodecode.comments.nextComment();"
175 id="inline-comments-counter">${_ungettext("{} Inline", "{} Inline", c.inline_cnt).format(c.inline_cnt)}
175 id="inline-comments-counter">${_ungettext("{} Inline", "{} Inline", c.inline_cnt).format(c.inline_cnt)}
176 </a>
176 </a>
177 % else:
177 % else:
178 ${_('0 Inline')}
178 ${_('0 Inline')}
179 % endif
179 % endif
180 % endif
180 % endif
181
181
182 % if pull_request_menu:
182 % if pull_request_menu:
183 <%
183 <%
184 outdated_comm_count_ver = pull_request_menu['outdated_comm_count_ver']
184 outdated_comm_count_ver = pull_request_menu['outdated_comm_count_ver']
185 %>
185 %>
186
186
187 % if outdated_comm_count_ver:
187 % if outdated_comm_count_ver:
188 <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
188 <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
189 (${_("{} Outdated").format(outdated_comm_count_ver)})
189 (${_("{} Outdated").format(outdated_comm_count_ver)})
190 </a>
190 </a>
191 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
191 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
192 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
192 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
193 % else:
193 % else:
194 (${_("{} Outdated").format(outdated_comm_count_ver)})
194 (${_("{} Outdated").format(outdated_comm_count_ver)})
195 % endif
195 % endif
196
196
197 % endif
197 % endif
198
198
199 </div>
199 </div>
200 </div>
200 </div>
201
201
202 </div>
202 </div>
203
203
204 % if diffset.limited_diff:
204 % if diffset.limited_diff:
205 <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
205 <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
206 <h2 class="clearinner">
206 <h2 class="clearinner">
207 ${_('The requested changes are too big and content was truncated.')}
207 ${_('The requested changes are too big and content was truncated.')}
208 <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
208 <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
209 </h2>
209 </h2>
210 </div>
210 </div>
211 ## commit range header for each individual diff
211 ## commit range header for each individual diff
212 % elif commit and hasattr(c, 'commit_ranges') and len(c.commit_ranges) > 1:
212 % elif commit and hasattr(c, 'commit_ranges') and len(c.commit_ranges) > 1:
213 <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
213 <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
214 <div class="clearinner">
214 <div class="clearinner">
215 <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=diffset.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a>
215 <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=diffset.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a>
216 </div>
216 </div>
217 </div>
217 </div>
218 % endif
218 % endif
219
219
220 <div id="todo-box">
220 <div id="todo-box">
221 % if hasattr(c, 'unresolved_comments') and c.unresolved_comments:
221 % if hasattr(c, 'unresolved_comments') and c.unresolved_comments:
222 % for co in c.unresolved_comments:
222 % for co in c.unresolved_comments:
223 <a class="permalink" href="#comment-${co.comment_id}"
223 <a class="permalink" href="#comment-${co.comment_id}"
224 onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))">
224 onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))">
225 <i class="icon-flag-filled-red"></i>
225 <i class="icon-flag-filled-red"></i>
226 ${co.comment_id}</a>${('' if loop.last else ',')}
226 ${co.comment_id}</a>${('' if loop.last else ',')}
227 % endfor
227 % endfor
228 % endif
228 % endif
229 </div>
229 </div>
230 %if diffset.has_hidden_changes:
230 %if diffset.has_hidden_changes:
231 <p class="empty_data">${_('Some changes may be hidden')}</p>
231 <p class="empty_data">${_('Some changes may be hidden')}</p>
232 %elif not diffset.files:
232 %elif not diffset.files:
233 <p class="empty_data">${_('No files')}</p>
233 <p class="empty_data">${_('No files')}</p>
234 %endif
234 %endif
235
235
236 <div class="filediffs">
236 <div class="filediffs">
237
237
238 ## initial value could be marked as False later on
238 ## initial value could be marked as False later on
239 <% over_lines_changed_limit = False %>
239 <% over_lines_changed_limit = False %>
240 %for i, filediff in enumerate(diffset.files):
240 %for i, filediff in enumerate(diffset.files):
241
241
242 <%
242 <%
243 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
243 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
244 over_lines_changed_limit = lines_changed > lines_changed_limit
244 over_lines_changed_limit = lines_changed > lines_changed_limit
245 %>
245 %>
246 ## anchor with support of sticky header
246 ## anchor with support of sticky header
247 <div class="anchor" id="a_${h.FID(filediff.raw_id, filediff.patch['filename'])}"></div>
247 <div class="anchor" id="a_${h.FID(filediff.raw_id, filediff.patch['filename'])}"></div>
248
248
249 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filediff)}" type="checkbox" onchange="updateSticky();">
249 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filediff)}" type="checkbox" onchange="updateSticky();">
250 <div
250 <div
251 class="filediff"
251 class="filediff"
252 data-f-path="${filediff.patch['filename']}"
252 data-f-path="${filediff.patch['filename']}"
253 data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}"
253 data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}"
254 >
254 >
255 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
255 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
256 <%
256 <%
257 file_comments = (get_inline_comments(inline_comments, filediff.patch['filename']) or {}).values()
257 file_comments = (get_inline_comments(inline_comments, filediff.patch['filename']) or {}).values()
258 total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not _c.outdated]
258 total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not _c.outdated]
259 %>
259 %>
260 <div class="filediff-collapse-indicator icon-"></div>
260 <div class="filediff-collapse-indicator icon-"></div>
261 <span class="pill-group pull-right" >
261 <span class="pill-group pull-right" >
262 <span class="pill"><i class="icon-comment"></i> ${len(total_file_comments)}</span>
262 <span class="pill" op="comments">
263
264 <i class="icon-comment"></i> ${len(total_file_comments)}
265 </span>
263 </span>
266 </span>
264 ${diff_ops(filediff)}
267 ${diff_ops(filediff)}
265
268
266 </label>
269 </label>
267
270
268 ${diff_menu(filediff, use_comments=use_comments)}
271 ${diff_menu(filediff, use_comments=use_comments)}
269 <table data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
272 <table data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
270
273
271 ## new/deleted/empty content case
274 ## new/deleted/empty content case
272 % if not filediff.hunks:
275 % if not filediff.hunks:
273 ## Comment container, on "fakes" hunk that contains all data to render comments
276 ## Comment container, on "fakes" hunk that contains all data to render comments
274 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], filediff.hunk_ops, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
277 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], filediff.hunk_ops, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
275 % endif
278 % endif
276
279
277 %if filediff.limited_diff:
280 %if filediff.limited_diff:
278 <tr class="cb-warning cb-collapser">
281 <tr class="cb-warning cb-collapser">
279 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
282 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
280 ${_('The requested commit or file is too big and content was truncated.')} <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
283 ${_('The requested commit or file is too big and content was truncated.')} <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
281 </td>
284 </td>
282 </tr>
285 </tr>
283 %else:
286 %else:
284 %if over_lines_changed_limit:
287 %if over_lines_changed_limit:
285 <tr class="cb-warning cb-collapser">
288 <tr class="cb-warning cb-collapser">
286 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
289 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
287 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
290 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
288 <a href="#" class="cb-expand"
291 <a href="#" class="cb-expand"
289 onclick="$(this).closest('table').removeClass('cb-collapsed'); updateSticky(); return false;">${_('Show them')}
292 onclick="$(this).closest('table').removeClass('cb-collapsed'); updateSticky(); return false;">${_('Show them')}
290 </a>
293 </a>
291 <a href="#" class="cb-collapse"
294 <a href="#" class="cb-collapse"
292 onclick="$(this).closest('table').addClass('cb-collapsed'); updateSticky(); return false;">${_('Hide them')}
295 onclick="$(this).closest('table').addClass('cb-collapsed'); updateSticky(); return false;">${_('Hide them')}
293 </a>
296 </a>
294 </td>
297 </td>
295 </tr>
298 </tr>
296 %endif
299 %endif
297 %endif
300 %endif
298
301
299 % for hunk in filediff.hunks:
302 % for hunk in filediff.hunks:
300 <tr class="cb-hunk">
303 <tr class="cb-hunk">
301 <td ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=3' or '')}>
304 <td ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=3' or '')}>
302 ## TODO: dan: add ajax loading of more context here
305 ## TODO: dan: add ajax loading of more context here
303 ## <a href="#">
306 ## <a href="#">
304 <i class="icon-more"></i>
307 <i class="icon-more"></i>
305 ## </a>
308 ## </a>
306 </td>
309 </td>
307 <td ${(c.user_session_attrs["diffmode"] == 'sideside' and 'colspan=5' or '')}>
310 <td ${(c.user_session_attrs["diffmode"] == 'sideside' and 'colspan=5' or '')}>
308 @@
311 @@
309 -${hunk.source_start},${hunk.source_length}
312 -${hunk.source_start},${hunk.source_length}
310 +${hunk.target_start},${hunk.target_length}
313 +${hunk.target_start},${hunk.target_length}
311 ${hunk.section_header}
314 ${hunk.section_header}
312 </td>
315 </td>
313 </tr>
316 </tr>
314 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
317 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
315 % endfor
318 % endfor
316
319
317 <% unmatched_comments = (inline_comments or {}).get(filediff.patch['filename'], {}) %>
320 <% unmatched_comments = (inline_comments or {}).get(filediff.patch['filename'], {}) %>
318
321
319 ## outdated comments that do not fit into currently displayed lines
322 ## outdated comments that do not fit into currently displayed lines
320 % for lineno, comments in unmatched_comments.items():
323 % for lineno, comments in unmatched_comments.items():
321
324
322 %if c.user_session_attrs["diffmode"] == 'unified':
325 %if c.user_session_attrs["diffmode"] == 'unified':
323 % if loop.index == 0:
326 % if loop.index == 0:
324 <tr class="cb-hunk">
327 <tr class="cb-hunk">
325 <td colspan="3"></td>
328 <td colspan="3"></td>
326 <td>
329 <td>
327 <div>
330 <div>
328 ${_('Unmatched/outdated inline comments below')}
331 ${_('Unmatched/outdated inline comments below')}
329 </div>
332 </div>
330 </td>
333 </td>
331 </tr>
334 </tr>
332 % endif
335 % endif
333 <tr class="cb-line">
336 <tr class="cb-line">
334 <td class="cb-data cb-context"></td>
337 <td class="cb-data cb-context"></td>
335 <td class="cb-lineno cb-context"></td>
338 <td class="cb-lineno cb-context"></td>
336 <td class="cb-lineno cb-context"></td>
339 <td class="cb-lineno cb-context"></td>
337 <td class="cb-content cb-context">
340 <td class="cb-content cb-context">
338 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
341 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
339 </td>
342 </td>
340 </tr>
343 </tr>
341 %elif c.user_session_attrs["diffmode"] == 'sideside':
344 %elif c.user_session_attrs["diffmode"] == 'sideside':
342 % if loop.index == 0:
345 % if loop.index == 0:
343 <tr class="cb-comment-info">
346 <tr class="cb-comment-info">
344 <td colspan="2"></td>
347 <td colspan="2"></td>
345 <td class="cb-line">
348 <td class="cb-line">
346 <div>
349 <div>
347 ${_('Unmatched/outdated inline comments below')}
350 ${_('Unmatched/outdated inline comments below')}
348 </div>
351 </div>
349 </td>
352 </td>
350 <td colspan="2"></td>
353 <td colspan="2"></td>
351 <td class="cb-line">
354 <td class="cb-line">
352 <div>
355 <div>
353 ${_('Unmatched/outdated comments below')}
356 ${_('Unmatched/outdated comments below')}
354 </div>
357 </div>
355 </td>
358 </td>
356 </tr>
359 </tr>
357 % endif
360 % endif
358 <tr class="cb-line">
361 <tr class="cb-line">
359 <td class="cb-data cb-context"></td>
362 <td class="cb-data cb-context"></td>
360 <td class="cb-lineno cb-context"></td>
363 <td class="cb-lineno cb-context"></td>
361 <td class="cb-content cb-context">
364 <td class="cb-content cb-context">
362 % if lineno.startswith('o'):
365 % if lineno.startswith('o'):
363 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
366 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
364 % endif
367 % endif
365 </td>
368 </td>
366
369
367 <td class="cb-data cb-context"></td>
370 <td class="cb-data cb-context"></td>
368 <td class="cb-lineno cb-context"></td>
371 <td class="cb-lineno cb-context"></td>
369 <td class="cb-content cb-context">
372 <td class="cb-content cb-context">
370 % if lineno.startswith('n'):
373 % if lineno.startswith('n'):
371 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
374 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
372 % endif
375 % endif
373 </td>
376 </td>
374 </tr>
377 </tr>
375 %endif
378 %endif
376
379
377 % endfor
380 % endfor
378
381
379 </table>
382 </table>
380 </div>
383 </div>
381 %endfor
384 %endfor
382
385
383 ## outdated comments that are made for a file that has been deleted
386 ## outdated comments that are made for a file that has been deleted
384 % for filename, comments_dict in (deleted_files_comments or {}).items():
387 % for filename, comments_dict in (deleted_files_comments or {}).items():
385
388
386 <%
389 <%
387 display_state = 'display: none'
390 display_state = 'display: none'
388 open_comments_in_file = [x for x in comments_dict['comments'] if x.outdated is False]
391 open_comments_in_file = [x for x in comments_dict['comments'] if x.outdated is False]
389 if open_comments_in_file:
392 if open_comments_in_file:
390 display_state = ''
393 display_state = ''
391 fid = str(id(filename))
394 fid = str(id(filename))
392 %>
395 %>
393 <div class="filediffs filediff-outdated" style="${display_state}">
396 <div class="filediffs filediff-outdated" style="${display_state}">
394 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filename)}" type="checkbox" onchange="updateSticky();">
397 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filename)}" type="checkbox" onchange="updateSticky();">
395 <div class="filediff" data-f-path="${filename}" id="a_${h.FID(fid, filename)}">
398 <div class="filediff" data-f-path="${filename}" id="a_${h.FID(fid, filename)}">
396 <label for="filediff-collapse-${id(filename)}" class="filediff-heading">
399 <label for="filediff-collapse-${id(filename)}" class="filediff-heading">
397 <div class="filediff-collapse-indicator icon-"></div>
400 <div class="filediff-collapse-indicator icon-"></div>
398
401
399 <span class="pill">
402 <span class="pill">
400 ## file was deleted
403 ## file was deleted
401 ${filename}
404 ${filename}
402 </span>
405 </span>
403 <span class="pill-group pull-left" >
406 <span class="pill-group pull-left" >
404 ## file op, doesn't need translation
407 ## file op, doesn't need translation
405 <span class="pill" op="removed">unresolved comments</span>
408 <span class="pill" op="removed">unresolved comments</span>
406 </span>
409 </span>
407 <a class="pill filediff-anchor" href="#a_${h.FID(fid, filename)}">ΒΆ</a>
410 <a class="pill filediff-anchor" href="#a_${h.FID(fid, filename)}">ΒΆ</a>
408 <span class="pill-group pull-right">
411 <span class="pill-group pull-right">
409 <span class="pill" op="deleted">
412 <span class="pill" op="deleted">
410 % if comments_dict['stats'] >0:
413 % if comments_dict['stats'] >0:
411 -${comments_dict['stats']}
414 -${comments_dict['stats']}
412 % else:
415 % else:
413 ${comments_dict['stats']}
416 ${comments_dict['stats']}
414 % endif
417 % endif
415 </span>
418 </span>
416 </span>
419 </span>
417 </label>
420 </label>
418
421
419 <table class="cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
422 <table class="cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
420 <tr>
423 <tr>
421 % if c.user_session_attrs["diffmode"] == 'unified':
424 % if c.user_session_attrs["diffmode"] == 'unified':
422 <td></td>
425 <td></td>
423 %endif
426 %endif
424
427
425 <td></td>
428 <td></td>
426 <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=5')}>
429 <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=5')}>
427 <strong>${_('This file was removed from diff during updates to this pull-request.')}</strong><br/>
430 <strong>${_('This file was removed from diff during updates to this pull-request.')}</strong><br/>
428 ${_('There are still outdated/unresolved comments attached to it.')}
431 ${_('There are still outdated/unresolved comments attached to it.')}
429 </td>
432 </td>
430 </tr>
433 </tr>
431 %if c.user_session_attrs["diffmode"] == 'unified':
434 %if c.user_session_attrs["diffmode"] == 'unified':
432 <tr class="cb-line">
435 <tr class="cb-line">
433 <td class="cb-data cb-context"></td>
436 <td class="cb-data cb-context"></td>
434 <td class="cb-lineno cb-context"></td>
437 <td class="cb-lineno cb-context"></td>
435 <td class="cb-lineno cb-context"></td>
438 <td class="cb-lineno cb-context"></td>
436 <td class="cb-content cb-context">
439 <td class="cb-content cb-context">
437 ${inline_comments_container(comments_dict['comments'], active_pattern_entries=active_pattern_entries)}
440 ${inline_comments_container(comments_dict['comments'], active_pattern_entries=active_pattern_entries)}
438 </td>
441 </td>
439 </tr>
442 </tr>
440 %elif c.user_session_attrs["diffmode"] == 'sideside':
443 %elif c.user_session_attrs["diffmode"] == 'sideside':
441 <tr class="cb-line">
444 <tr class="cb-line">
442 <td class="cb-data cb-context"></td>
445 <td class="cb-data cb-context"></td>
443 <td class="cb-lineno cb-context"></td>
446 <td class="cb-lineno cb-context"></td>
444 <td class="cb-content cb-context"></td>
447 <td class="cb-content cb-context"></td>
445
448
446 <td class="cb-data cb-context"></td>
449 <td class="cb-data cb-context"></td>
447 <td class="cb-lineno cb-context"></td>
450 <td class="cb-lineno cb-context"></td>
448 <td class="cb-content cb-context">
451 <td class="cb-content cb-context">
449 ${inline_comments_container(comments_dict['comments'], active_pattern_entries=active_pattern_entries)}
452 ${inline_comments_container(comments_dict['comments'], active_pattern_entries=active_pattern_entries)}
450 </td>
453 </td>
451 </tr>
454 </tr>
452 %endif
455 %endif
453 </table>
456 </table>
454 </div>
457 </div>
455 </div>
458 </div>
456 % endfor
459 % endfor
457
460
458 </div>
461 </div>
459 </div>
462 </div>
460 </%def>
463 </%def>
461
464
462 <%def name="diff_ops(filediff)">
465 <%def name="diff_ops(filediff)">
463 <%
466 <%
464 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
467 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
465 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
468 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
466 %>
469 %>
467 <span class="pill">
470 <span class="pill">
468 <i class="icon-file-text"></i>
471 <i class="icon-file-text"></i>
469 %if filediff.source_file_path and filediff.target_file_path:
472 %if filediff.source_file_path and filediff.target_file_path:
470 %if filediff.source_file_path != filediff.target_file_path:
473 %if filediff.source_file_path != filediff.target_file_path:
471 ## file was renamed, or copied
474 ## file was renamed, or copied
472 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
475 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
473 ${filediff.target_file_path} β¬… <del>${filediff.source_file_path}</del>
476 ${filediff.target_file_path} β¬… <del>${filediff.source_file_path}</del>
474 <% final_path = filediff.target_file_path %>
477 <% final_path = filediff.target_file_path %>
475 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
478 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
476 ${filediff.target_file_path} β¬… ${filediff.source_file_path}
479 ${filediff.target_file_path} β¬… ${filediff.source_file_path}
477 <% final_path = filediff.target_file_path %>
480 <% final_path = filediff.target_file_path %>
478 %endif
481 %endif
479 %else:
482 %else:
480 ## file was modified
483 ## file was modified
481 ${filediff.source_file_path}
484 ${filediff.source_file_path}
482 <% final_path = filediff.source_file_path %>
485 <% final_path = filediff.source_file_path %>
483 %endif
486 %endif
484 %else:
487 %else:
485 %if filediff.source_file_path:
488 %if filediff.source_file_path:
486 ## file was deleted
489 ## file was deleted
487 ${filediff.source_file_path}
490 ${filediff.source_file_path}
488 <% final_path = filediff.source_file_path %>
491 <% final_path = filediff.source_file_path %>
489 %else:
492 %else:
490 ## file was added
493 ## file was added
491 ${filediff.target_file_path}
494 ${filediff.target_file_path}
492 <% final_path = filediff.target_file_path %>
495 <% final_path = filediff.target_file_path %>
493 %endif
496 %endif
494 %endif
497 %endif
495 <i style="color: #aaa" class="on-hover-icon icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="${_('Copy file path')}" onclick="return false;"></i>
498 <i style="color: #aaa" class="on-hover-icon icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="${_('Copy file path')}" onclick="return false;"></i>
496 </span>
499 </span>
497 ## anchor link
500 ## anchor link
498 <a class="pill filediff-anchor" href="#a_${h.FID(filediff.raw_id, filediff.patch['filename'])}">ΒΆ</a>
501 <a class="pill filediff-anchor" href="#a_${h.FID(filediff.raw_id, filediff.patch['filename'])}">ΒΆ</a>
499
502
500 <span class="pill-group pull-right">
503 <span class="pill-group pull-right">
501
504
502 ## ops pills
505 ## ops pills
503 %if filediff.limited_diff:
506 %if filediff.limited_diff:
504 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
507 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
505 %endif
508 %endif
506
509
507 %if NEW_FILENODE in filediff.patch['stats']['ops']:
510 %if NEW_FILENODE in filediff.patch['stats']['ops']:
508 <span class="pill" op="created">created</span>
511 <span class="pill" op="created">created</span>
509 %if filediff['target_mode'].startswith('120'):
512 %if filediff['target_mode'].startswith('120'):
510 <span class="pill" op="symlink">symlink</span>
513 <span class="pill" op="symlink">symlink</span>
511 %else:
514 %else:
512 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
515 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
513 %endif
516 %endif
514 %endif
517 %endif
515
518
516 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
519 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
517 <span class="pill" op="renamed">renamed</span>
520 <span class="pill" op="renamed">renamed</span>
518 %endif
521 %endif
519
522
520 %if COPIED_FILENODE in filediff.patch['stats']['ops']:
523 %if COPIED_FILENODE in filediff.patch['stats']['ops']:
521 <span class="pill" op="copied">copied</span>
524 <span class="pill" op="copied">copied</span>
522 %endif
525 %endif
523
526
524 %if DEL_FILENODE in filediff.patch['stats']['ops']:
527 %if DEL_FILENODE in filediff.patch['stats']['ops']:
525 <span class="pill" op="removed">removed</span>
528 <span class="pill" op="removed">removed</span>
526 %endif
529 %endif
527
530
528 %if CHMOD_FILENODE in filediff.patch['stats']['ops']:
531 %if CHMOD_FILENODE in filediff.patch['stats']['ops']:
529 <span class="pill" op="mode">
532 <span class="pill" op="mode">
530 ${nice_mode(filediff['source_mode'])} ➑ ${nice_mode(filediff['target_mode'])}
533 ${nice_mode(filediff['source_mode'])} ➑ ${nice_mode(filediff['target_mode'])}
531 </span>
534 </span>
532 %endif
535 %endif
533
536
534 %if BIN_FILENODE in filediff.patch['stats']['ops']:
537 %if BIN_FILENODE in filediff.patch['stats']['ops']:
535 <span class="pill" op="binary">binary</span>
538 <span class="pill" op="binary">binary</span>
536 %if MOD_FILENODE in filediff.patch['stats']['ops']:
539 %if MOD_FILENODE in filediff.patch['stats']['ops']:
537 <span class="pill" op="modified">modified</span>
540 <span class="pill" op="modified">modified</span>
538 %endif
541 %endif
539 %endif
542 %endif
540
543
541 <span class="pill" op="added">${('+' if filediff.patch['stats']['added'] else '')}${filediff.patch['stats']['added']}</span>
544 <span class="pill" op="added">${('+' if filediff.patch['stats']['added'] else '')}${filediff.patch['stats']['added']}</span>
542 <span class="pill" op="deleted">${((h.safe_int(filediff.patch['stats']['deleted']) or 0) * -1)}</span>
545 <span class="pill" op="deleted">${((h.safe_int(filediff.patch['stats']['deleted']) or 0) * -1)}</span>
543
546
544 </span>
547 </span>
545
548
546 </%def>
549 </%def>
547
550
548 <%def name="nice_mode(filemode)">
551 <%def name="nice_mode(filemode)">
549 ${(filemode.startswith('100') and filemode[3:] or filemode)}
552 ${(filemode.startswith('100') and filemode[3:] or filemode)}
550 </%def>
553 </%def>
551
554
552 <%def name="diff_menu(filediff, use_comments=False)">
555 <%def name="diff_menu(filediff, use_comments=False)">
553 <div class="filediff-menu">
556 <div class="filediff-menu">
554
557
555 %if filediff.diffset.source_ref:
558 %if filediff.diffset.source_ref:
556
559
557 ## FILE BEFORE CHANGES
560 ## FILE BEFORE CHANGES
558 %if filediff.operation in ['D', 'M']:
561 %if filediff.operation in ['D', 'M']:
559 <a
562 <a
560 class="tooltip"
563 class="tooltip"
561 href="${h.route_path('repo_files',repo_name=filediff.diffset.target_repo_name,commit_id=filediff.diffset.source_ref,f_path=filediff.source_file_path)}"
564 href="${h.route_path('repo_files',repo_name=filediff.diffset.target_repo_name,commit_id=filediff.diffset.source_ref,f_path=filediff.source_file_path)}"
562 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
565 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
563 >
566 >
564 ${_('Show file before')}
567 ${_('Show file before')}
565 </a> |
568 </a> |
566 %else:
569 %else:
567 <span
570 <span
568 class="tooltip"
571 class="tooltip"
569 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
572 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
570 >
573 >
571 ${_('Show file before')}
574 ${_('Show file before')}
572 </span> |
575 </span> |
573 %endif
576 %endif
574
577
575 ## FILE AFTER CHANGES
578 ## FILE AFTER CHANGES
576 %if filediff.operation in ['A', 'M']:
579 %if filediff.operation in ['A', 'M']:
577 <a
580 <a
578 class="tooltip"
581 class="tooltip"
579 href="${h.route_path('repo_files',repo_name=filediff.diffset.source_repo_name,commit_id=filediff.diffset.target_ref,f_path=filediff.target_file_path)}"
582 href="${h.route_path('repo_files',repo_name=filediff.diffset.source_repo_name,commit_id=filediff.diffset.target_ref,f_path=filediff.target_file_path)}"
580 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
583 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
581 >
584 >
582 ${_('Show file after')}
585 ${_('Show file after')}
583 </a>
586 </a>
584 %else:
587 %else:
585 <span
588 <span
586 class="tooltip"
589 class="tooltip"
587 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
590 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
588 >
591 >
589 ${_('Show file after')}
592 ${_('Show file after')}
590 </span>
593 </span>
591 %endif
594 %endif
592
595
593 % if use_comments:
596 % if use_comments:
594 |
597 |
595 <a href="#" onclick="return Rhodecode.comments.toggleComments(this);">
598 <a href="#" onclick="return Rhodecode.comments.toggleComments(this);">
596 <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
599 <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
597 </a>
600 </a>
598 % endif
601 % endif
599
602
600 %endif
603 %endif
601
604
602 </div>
605 </div>
603 </%def>
606 </%def>
604
607
605
608
606 <%def name="inline_comments_container(comments, active_pattern_entries=None)">
609 <%def name="inline_comments_container(comments, active_pattern_entries=None)">
607
610
608 <div class="inline-comments">
611 <div class="inline-comments">
609 %for comment in comments:
612 %for comment in comments:
610 ${commentblock.comment_block(comment, inline=True, active_pattern_entries=active_pattern_entries)}
613 ${commentblock.comment_block(comment, inline=True, active_pattern_entries=active_pattern_entries)}
611 %endfor
614 %endfor
612 % if comments and comments[-1].outdated:
615 % if comments and comments[-1].outdated:
613 <span class="btn btn-secondary cb-comment-add-button comment-outdated}" style="display: none;}">
616 <span class="btn btn-secondary cb-comment-add-button comment-outdated}" style="display: none;}">
614 ${_('Add another comment')}
617 ${_('Add another comment')}
615 </span>
618 </span>
616 % else:
619 % else:
617 <span onclick="return Rhodecode.comments.createComment(this)" class="btn btn-secondary cb-comment-add-button">
620 <span onclick="return Rhodecode.comments.createComment(this)" class="btn btn-secondary cb-comment-add-button">
618 ${_('Add another comment')}
621 ${_('Add another comment')}
619 </span>
622 </span>
620 % endif
623 % endif
621
624
622 </div>
625 </div>
623 </%def>
626 </%def>
624
627
625 <%!
628 <%!
626
629
627 def get_inline_comments(comments, filename):
630 def get_inline_comments(comments, filename):
628 if hasattr(filename, 'unicode_path'):
631 if hasattr(filename, 'unicode_path'):
629 filename = filename.unicode_path
632 filename = filename.unicode_path
630
633
631 if not isinstance(filename, (unicode, str)):
634 if not isinstance(filename, (unicode, str)):
632 return None
635 return None
633
636
634 if comments and filename in comments:
637 if comments and filename in comments:
635 return comments[filename]
638 return comments[filename]
636
639
637 return None
640 return None
638
641
639 def get_comments_for(diff_type, comments, filename, line_version, line_number):
642 def get_comments_for(diff_type, comments, filename, line_version, line_number):
640 if hasattr(filename, 'unicode_path'):
643 if hasattr(filename, 'unicode_path'):
641 filename = filename.unicode_path
644 filename = filename.unicode_path
642
645
643 if not isinstance(filename, (unicode, str)):
646 if not isinstance(filename, (unicode, str)):
644 return None
647 return None
645
648
646 file_comments = get_inline_comments(comments, filename)
649 file_comments = get_inline_comments(comments, filename)
647 if file_comments is None:
650 if file_comments is None:
648 return None
651 return None
649
652
650 line_key = '{}{}'.format(line_version, line_number) ## e.g o37, n12
653 line_key = '{}{}'.format(line_version, line_number) ## e.g o37, n12
651 if line_key in file_comments:
654 if line_key in file_comments:
652 data = file_comments.pop(line_key)
655 data = file_comments.pop(line_key)
653 return data
656 return data
654 %>
657 %>
655
658
656 <%def name="render_hunk_lines_sideside(filediff, hunk, use_comments=False, inline_comments=None, active_pattern_entries=None)">
659 <%def name="render_hunk_lines_sideside(filediff, hunk, use_comments=False, inline_comments=None, active_pattern_entries=None)">
657 %for i, line in enumerate(hunk.sideside):
660 %for i, line in enumerate(hunk.sideside):
658 <%
661 <%
659 old_line_anchor, new_line_anchor = None, None
662 old_line_anchor, new_line_anchor = None, None
660
663
661 if line.original.lineno:
664 if line.original.lineno:
662 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, line.original.lineno, 'o')
665 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, line.original.lineno, 'o')
663 if line.modified.lineno:
666 if line.modified.lineno:
664 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, line.modified.lineno, 'n')
667 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, line.modified.lineno, 'n')
665 %>
668 %>
666
669
667 <tr class="cb-line">
670 <tr class="cb-line">
668 <td class="cb-data ${action_class(line.original.action)}"
671 <td class="cb-data ${action_class(line.original.action)}"
669 data-line-no="${line.original.lineno}"
672 data-line-no="${line.original.lineno}"
670 >
673 >
671 <div>
674 <div>
672
675
673 <% line_old_comments = None %>
676 <% line_old_comments = None %>
674 %if line.original.get_comment_args:
677 %if line.original.get_comment_args:
675 <% line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args) %>
678 <% line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args) %>
676 %endif
679 %endif
677 %if line_old_comments:
680 %if line_old_comments:
678 <% has_outdated = any([x.outdated for x in line_old_comments]) %>
681 <% has_outdated = any([x.outdated for x in line_old_comments]) %>
679 % if has_outdated:
682 % if has_outdated:
680 <i class="tooltip" title="${_('comments including outdated, click to show them')}:${len(line_old_comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
683 <i class="tooltip" title="${_('comments including outdated, click to show them')}:${len(line_old_comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
681 % else:
684 % else:
682 <i class="tooltip" title="${_('comments')}: ${len(line_old_comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
685 <i class="tooltip" title="${_('comments')}: ${len(line_old_comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
683 % endif
686 % endif
684 %endif
687 %endif
685 </div>
688 </div>
686 </td>
689 </td>
687 <td class="cb-lineno ${action_class(line.original.action)}"
690 <td class="cb-lineno ${action_class(line.original.action)}"
688 data-line-no="${line.original.lineno}"
691 data-line-no="${line.original.lineno}"
689 %if old_line_anchor:
692 %if old_line_anchor:
690 id="${old_line_anchor}"
693 id="${old_line_anchor}"
691 %endif
694 %endif
692 >
695 >
693 %if line.original.lineno:
696 %if line.original.lineno:
694 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
697 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
695 %endif
698 %endif
696 </td>
699 </td>
697 <td class="cb-content ${action_class(line.original.action)}"
700 <td class="cb-content ${action_class(line.original.action)}"
698 data-line-no="o${line.original.lineno}"
701 data-line-no="o${line.original.lineno}"
699 >
702 >
700 %if use_comments and line.original.lineno:
703 %if use_comments and line.original.lineno:
701 ${render_add_comment_button()}
704 ${render_add_comment_button()}
702 %endif
705 %endif
703 <span class="cb-code"><span class="cb-action ${action_class(line.original.action)}"></span>${line.original.content or '' | n}</span>
706 <span class="cb-code"><span class="cb-action ${action_class(line.original.action)}"></span>${line.original.content or '' | n}</span>
704
707
705 %if use_comments and line.original.lineno and line_old_comments:
708 %if use_comments and line.original.lineno and line_old_comments:
706 ${inline_comments_container(line_old_comments, active_pattern_entries=active_pattern_entries)}
709 ${inline_comments_container(line_old_comments, active_pattern_entries=active_pattern_entries)}
707 %endif
710 %endif
708
711
709 </td>
712 </td>
710 <td class="cb-data ${action_class(line.modified.action)}"
713 <td class="cb-data ${action_class(line.modified.action)}"
711 data-line-no="${line.modified.lineno}"
714 data-line-no="${line.modified.lineno}"
712 >
715 >
713 <div>
716 <div>
714
717
715 %if line.modified.get_comment_args:
718 %if line.modified.get_comment_args:
716 <% line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args) %>
719 <% line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args) %>
717 %else:
720 %else:
718 <% line_new_comments = None%>
721 <% line_new_comments = None%>
719 %endif
722 %endif
720 %if line_new_comments:
723 %if line_new_comments:
721 <% has_outdated = any([x.outdated for x in line_new_comments]) %>
724 <% has_outdated = any([x.outdated for x in line_new_comments]) %>
722 % if has_outdated:
725 % if has_outdated:
723 <i class="tooltip" title="${_('comments including outdated, click to show them')}:${len(line_new_comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
726 <i class="tooltip" title="${_('comments including outdated, click to show them')}:${len(line_new_comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
724 % else:
727 % else:
725 <i class="tooltip" title="${_('comments')}: ${len(line_new_comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
728 <i class="tooltip" title="${_('comments')}: ${len(line_new_comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
726 % endif
729 % endif
727 %endif
730 %endif
728 </div>
731 </div>
729 </td>
732 </td>
730 <td class="cb-lineno ${action_class(line.modified.action)}"
733 <td class="cb-lineno ${action_class(line.modified.action)}"
731 data-line-no="${line.modified.lineno}"
734 data-line-no="${line.modified.lineno}"
732 %if new_line_anchor:
735 %if new_line_anchor:
733 id="${new_line_anchor}"
736 id="${new_line_anchor}"
734 %endif
737 %endif
735 >
738 >
736 %if line.modified.lineno:
739 %if line.modified.lineno:
737 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
740 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
738 %endif
741 %endif
739 </td>
742 </td>
740 <td class="cb-content ${action_class(line.modified.action)}"
743 <td class="cb-content ${action_class(line.modified.action)}"
741 data-line-no="n${line.modified.lineno}"
744 data-line-no="n${line.modified.lineno}"
742 >
745 >
743 %if use_comments and line.modified.lineno:
746 %if use_comments and line.modified.lineno:
744 ${render_add_comment_button()}
747 ${render_add_comment_button()}
745 %endif
748 %endif
746 <span class="cb-code"><span class="cb-action ${action_class(line.modified.action)}"></span>${line.modified.content or '' | n}</span>
749 <span class="cb-code"><span class="cb-action ${action_class(line.modified.action)}"></span>${line.modified.content or '' | n}</span>
747 %if use_comments and line.modified.lineno and line_new_comments:
750 %if use_comments and line.modified.lineno and line_new_comments:
748 ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries)}
751 ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries)}
749 %endif
752 %endif
750 </td>
753 </td>
751 </tr>
754 </tr>
752 %endfor
755 %endfor
753 </%def>
756 </%def>
754
757
755
758
756 <%def name="render_hunk_lines_unified(filediff, hunk, use_comments=False, inline_comments=None, active_pattern_entries=None)">
759 <%def name="render_hunk_lines_unified(filediff, hunk, use_comments=False, inline_comments=None, active_pattern_entries=None)">
757 %for old_line_no, new_line_no, action, content, comments_args in hunk.unified:
760 %for old_line_no, new_line_no, action, content, comments_args in hunk.unified:
758
761
759 <%
762 <%
760 old_line_anchor, new_line_anchor = None, None
763 old_line_anchor, new_line_anchor = None, None
761 if old_line_no:
764 if old_line_no:
762 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, old_line_no, 'o')
765 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, old_line_no, 'o')
763 if new_line_no:
766 if new_line_no:
764 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, new_line_no, 'n')
767 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, new_line_no, 'n')
765 %>
768 %>
766 <tr class="cb-line">
769 <tr class="cb-line">
767 <td class="cb-data ${action_class(action)}">
770 <td class="cb-data ${action_class(action)}">
768 <div>
771 <div>
769
772
770 %if comments_args:
773 %if comments_args:
771 <% comments = get_comments_for('unified', inline_comments, *comments_args) %>
774 <% comments = get_comments_for('unified', inline_comments, *comments_args) %>
772 %else:
775 %else:
773 <% comments = None %>
776 <% comments = None %>
774 %endif
777 %endif
775
778
776 % if comments:
779 % if comments:
777 <% has_outdated = any([x.outdated for x in comments]) %>
780 <% has_outdated = any([x.outdated for x in comments]) %>
778 % if has_outdated:
781 % if has_outdated:
779 <i class="tooltip" title="${_('comments including outdated, click to show them')}:${len(comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
782 <i class="tooltip" title="${_('comments including outdated, click to show them')}:${len(comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
780 % else:
783 % else:
781 <i class="tooltip" title="${_('comments')}: ${len(comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
784 <i class="tooltip" title="${_('comments')}: ${len(comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
782 % endif
785 % endif
783 % endif
786 % endif
784 </div>
787 </div>
785 </td>
788 </td>
786 <td class="cb-lineno ${action_class(action)}"
789 <td class="cb-lineno ${action_class(action)}"
787 data-line-no="${old_line_no}"
790 data-line-no="${old_line_no}"
788 %if old_line_anchor:
791 %if old_line_anchor:
789 id="${old_line_anchor}"
792 id="${old_line_anchor}"
790 %endif
793 %endif
791 >
794 >
792 %if old_line_anchor:
795 %if old_line_anchor:
793 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
796 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
794 %endif
797 %endif
795 </td>
798 </td>
796 <td class="cb-lineno ${action_class(action)}"
799 <td class="cb-lineno ${action_class(action)}"
797 data-line-no="${new_line_no}"
800 data-line-no="${new_line_no}"
798 %if new_line_anchor:
801 %if new_line_anchor:
799 id="${new_line_anchor}"
802 id="${new_line_anchor}"
800 %endif
803 %endif
801 >
804 >
802 %if new_line_anchor:
805 %if new_line_anchor:
803 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
806 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
804 %endif
807 %endif
805 </td>
808 </td>
806 <td class="cb-content ${action_class(action)}"
809 <td class="cb-content ${action_class(action)}"
807 data-line-no="${(new_line_no and 'n' or 'o')}${(new_line_no or old_line_no)}"
810 data-line-no="${(new_line_no and 'n' or 'o')}${(new_line_no or old_line_no)}"
808 >
811 >
809 %if use_comments:
812 %if use_comments:
810 ${render_add_comment_button()}
813 ${render_add_comment_button()}
811 %endif
814 %endif
812 <span class="cb-code"><span class="cb-action ${action_class(action)}"></span> ${content or '' | n}</span>
815 <span class="cb-code"><span class="cb-action ${action_class(action)}"></span> ${content or '' | n}</span>
813 %if use_comments and comments:
816 %if use_comments and comments:
814 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
817 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
815 %endif
818 %endif
816 </td>
819 </td>
817 </tr>
820 </tr>
818 %endfor
821 %endfor
819 </%def>
822 </%def>
820
823
821
824
822 <%def name="render_hunk_lines(filediff, diff_mode, hunk, use_comments, inline_comments, active_pattern_entries)">
825 <%def name="render_hunk_lines(filediff, diff_mode, hunk, use_comments, inline_comments, active_pattern_entries)">
823 % if diff_mode == 'unified':
826 % if diff_mode == 'unified':
824 ${render_hunk_lines_unified(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
827 ${render_hunk_lines_unified(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
825 % elif diff_mode == 'sideside':
828 % elif diff_mode == 'sideside':
826 ${render_hunk_lines_sideside(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
829 ${render_hunk_lines_sideside(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
827 % else:
830 % else:
828 <tr class="cb-line">
831 <tr class="cb-line">
829 <td>unknown diff mode</td>
832 <td>unknown diff mode</td>
830 </tr>
833 </tr>
831 % endif
834 % endif
832 </%def>file changes
835 </%def>file changes
833
836
834
837
835 <%def name="render_add_comment_button()">
838 <%def name="render_add_comment_button()">
836 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)">
839 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)">
837 <span><i class="icon-comment"></i></span>
840 <span><i class="icon-comment"></i></span>
838 </button>
841 </button>
839 </%def>
842 </%def>
840
843
841 <%def name="render_diffset_menu(diffset, range_diff_on=None)">
844 <%def name="render_diffset_menu(diffset, range_diff_on=None)">
842 <% diffset_container_id = h.md5(diffset.target_ref) %>
845 <% diffset_container_id = h.md5(diffset.target_ref) %>
843
846
844 <div id="diff-file-sticky" class="diffset-menu clearinner">
847 <div id="diff-file-sticky" class="diffset-menu clearinner">
845 ## auto adjustable
848 ## auto adjustable
846 <div class="sidebar__inner">
849 <div class="sidebar__inner">
847 <div class="sidebar__bar">
850 <div class="sidebar__bar">
848 <div class="pull-right">
851 <div class="pull-right">
849 <div class="btn-group">
852 <div class="btn-group">
850 <a class="btn tooltip toggle-wide-diff" href="#toggle-wide-diff" onclick="toggleWideDiff(this); return false" title="${h.tooltip(_('Toggle wide diff'))}">
853 <a class="btn tooltip toggle-wide-diff" href="#toggle-wide-diff" onclick="toggleWideDiff(this); return false" title="${h.tooltip(_('Toggle wide diff'))}">
851 <i class="icon-wide-mode"></i>
854 <i class="icon-wide-mode"></i>
852 </a>
855 </a>
853 </div>
856 </div>
854 <div class="btn-group">
857 <div class="btn-group">
855
858
856 <a
859 <a
857 class="btn ${(c.user_session_attrs["diffmode"] == 'sideside' and 'btn-active')} tooltip"
860 class="btn ${(c.user_session_attrs["diffmode"] == 'sideside' and 'btn-active')} tooltip"
858 title="${h.tooltip(_('View diff as side by side'))}"
861 title="${h.tooltip(_('View diff as side by side'))}"
859 href="${h.current_route_path(request, diffmode='sideside')}">
862 href="${h.current_route_path(request, diffmode='sideside')}">
860 <span>${_('Side by Side')}</span>
863 <span>${_('Side by Side')}</span>
861 </a>
864 </a>
862
865
863 <a
866 <a
864 class="btn ${(c.user_session_attrs["diffmode"] == 'unified' and 'btn-active')} tooltip"
867 class="btn ${(c.user_session_attrs["diffmode"] == 'unified' and 'btn-active')} tooltip"
865 title="${h.tooltip(_('View diff as unified'))}" href="${h.current_route_path(request, diffmode='unified')}">
868 title="${h.tooltip(_('View diff as unified'))}" href="${h.current_route_path(request, diffmode='unified')}">
866 <span>${_('Unified')}</span>
869 <span>${_('Unified')}</span>
867 </a>
870 </a>
868
871
869 % if range_diff_on is True:
872 % if range_diff_on is True:
870 <a
873 <a
871 title="${_('Turn off: Show the diff as commit range')}"
874 title="${_('Turn off: Show the diff as commit range')}"
872 class="btn btn-primary"
875 class="btn btn-primary"
873 href="${h.current_route_path(request, **{"range-diff":"0"})}">
876 href="${h.current_route_path(request, **{"range-diff":"0"})}">
874 <span>${_('Range Diff')}</span>
877 <span>${_('Range Diff')}</span>
875 </a>
878 </a>
876 % elif range_diff_on is False:
879 % elif range_diff_on is False:
877 <a
880 <a
878 title="${_('Show the diff as commit range')}"
881 title="${_('Show the diff as commit range')}"
879 class="btn"
882 class="btn"
880 href="${h.current_route_path(request, **{"range-diff":"1"})}">
883 href="${h.current_route_path(request, **{"range-diff":"1"})}">
881 <span>${_('Range Diff')}</span>
884 <span>${_('Range Diff')}</span>
882 </a>
885 </a>
883 % endif
886 % endif
884 </div>
887 </div>
885 <div class="btn-group">
888 <div class="btn-group">
886
889
887 <div class="pull-left">
890 <div class="pull-left">
888 ${h.hidden('diff_menu_{}'.format(diffset_container_id))}
891 ${h.hidden('diff_menu_{}'.format(diffset_container_id))}
889 </div>
892 </div>
890
893
891 </div>
894 </div>
892 </div>
895 </div>
893 <div class="pull-left">
896 <div class="pull-left">
894 <div class="btn-group">
897 <div class="btn-group">
895 <div class="pull-left">
898 <div class="pull-left">
896 ${h.hidden('file_filter_{}'.format(diffset_container_id))}
899 ${h.hidden('file_filter_{}'.format(diffset_container_id))}
897 </div>
900 </div>
898
901
899 </div>
902 </div>
900 </div>
903 </div>
901 </div>
904 </div>
902 <div class="fpath-placeholder">
905 <div class="fpath-placeholder">
903 <i class="icon-file-text"></i>
906 <i class="icon-file-text"></i>
904 <strong class="fpath-placeholder-text">
907 <strong class="fpath-placeholder-text">
905 Context file:
908 Context file:
906 </strong>
909 </strong>
907 </div>
910 </div>
908 <div class="sidebar_inner_shadow"></div>
911 <div class="sidebar_inner_shadow"></div>
909 </div>
912 </div>
910 </div>
913 </div>
911
914
912 % if diffset:
915 % if diffset:
913 %if diffset.limited_diff:
916 %if diffset.limited_diff:
914 <% file_placeholder = _ungettext('%(num)s file changed', '%(num)s files changed', diffset.changed_files) % {'num': diffset.changed_files} %>
917 <% file_placeholder = _ungettext('%(num)s file changed', '%(num)s files changed', diffset.changed_files) % {'num': diffset.changed_files} %>
915 %else:
918 %else:
916 <% file_placeholder = h.literal(_ungettext('%(num)s file changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>', '%(num)s files changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>',
919 <% file_placeholder = h.literal(_ungettext('%(num)s file changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>', '%(num)s files changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>',
917 diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}) %>
920 diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}) %>
918
921
919 %endif
922 %endif
920 ## case on range-diff placeholder needs to be updated
923 ## case on range-diff placeholder needs to be updated
921 % if range_diff_on is True:
924 % if range_diff_on is True:
922 <% file_placeholder = _('Disabled on range diff') %>
925 <% file_placeholder = _('Disabled on range diff') %>
923 % endif
926 % endif
924
927
925 <script type="text/javascript">
928 <script type="text/javascript">
926 var feedFilesOptions = function (query, initialData) {
929 var feedFilesOptions = function (query, initialData) {
927 var data = {results: []};
930 var data = {results: []};
928 var isQuery = typeof query.term !== 'undefined';
931 var isQuery = typeof query.term !== 'undefined';
929
932
930 var section = _gettext('Changed files');
933 var section = _gettext('Changed files');
931 var filteredData = [];
934 var filteredData = [];
932
935
933 //filter results
936 //filter results
934 $.each(initialData.results, function (idx, value) {
937 $.each(initialData.results, function (idx, value) {
935
938
936 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
939 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
937 filteredData.push({
940 filteredData.push({
938 'id': this.id,
941 'id': this.id,
939 'text': this.text,
942 'text': this.text,
940 "ops": this.ops,
943 "ops": this.ops,
941 })
944 })
942 }
945 }
943
946
944 });
947 });
945
948
946 data.results = filteredData;
949 data.results = filteredData;
947
950
948 query.callback(data);
951 query.callback(data);
949 };
952 };
950
953
951 var selectionFormatter = function(data, escapeMarkup) {
954 var selectionFormatter = function(data, escapeMarkup) {
952 var container = '<div class="filelist" style="padding-right:100px">{0}</div>';
955 var container = '<div class="filelist" style="padding-right:100px">{0}</div>';
953 var tmpl = '<div><strong>{0}</strong></div>'.format(escapeMarkup(data['text']));
956 var tmpl = '<div><strong>{0}</strong></div>'.format(escapeMarkup(data['text']));
954 var pill = '<div class="pill-group" style="position: absolute; top:7px; right: 0">' +
957 var pill = '<div class="pill-group" style="position: absolute; top:7px; right: 0">' +
955 '<span class="pill" op="added">{0}</span>' +
958 '<span class="pill" op="added">{0}</span>' +
956 '<span class="pill" op="deleted">{1}</span>' +
959 '<span class="pill" op="deleted">{1}</span>' +
957 '</div>'
960 '</div>'
958 ;
961 ;
959 var added = data['ops']['added'];
962 var added = data['ops']['added'];
960 if (added === 0) {
963 if (added === 0) {
961 // don't show +0
964 // don't show +0
962 added = 0;
965 added = 0;
963 } else {
966 } else {
964 added = '+' + added;
967 added = '+' + added;
965 }
968 }
966
969
967 var deleted = -1*data['ops']['deleted'];
970 var deleted = -1*data['ops']['deleted'];
968
971
969 tmpl += pill.format(added, deleted);
972 tmpl += pill.format(added, deleted);
970 return container.format(tmpl);
973 return container.format(tmpl);
971 };
974 };
972 var formatFileResult = function(result, container, query, escapeMarkup) {
975 var formatFileResult = function(result, container, query, escapeMarkup) {
973 return selectionFormatter(result, escapeMarkup);
976 return selectionFormatter(result, escapeMarkup);
974 };
977 };
975
978
976 var formatSelection = function (data, container) {
979 var formatSelection = function (data, container) {
977 return '${file_placeholder}'
980 return '${file_placeholder}'
978 };
981 };
979
982
980 if (window.preloadFileFilterData === undefined) {
983 if (window.preloadFileFilterData === undefined) {
981 window.preloadFileFilterData = {}
984 window.preloadFileFilterData = {}
982 }
985 }
983
986
984 preloadFileFilterData["${diffset_container_id}"] = {
987 preloadFileFilterData["${diffset_container_id}"] = {
985 results: [
988 results: [
986 % for filediff in diffset.files:
989 % for filediff in diffset.files:
987 {id:"a_${h.FID(filediff.raw_id, filediff.patch['filename'])}",
990 {id:"a_${h.FID(filediff.raw_id, filediff.patch['filename'])}",
988 text:"${filediff.patch['filename']}",
991 text:"${filediff.patch['filename']}",
989 ops:${h.json.dumps(filediff.patch['stats'])|n}}${('' if loop.last else ',')}
992 ops:${h.json.dumps(filediff.patch['stats'])|n}}${('' if loop.last else ',')}
990 % endfor
993 % endfor
991 ]
994 ]
992 };
995 };
993
996
994 var diffFileFilterId = "#file_filter_" + "${diffset_container_id}";
997 var diffFileFilterId = "#file_filter_" + "${diffset_container_id}";
995 var diffFileFilter = $(diffFileFilterId).select2({
998 var diffFileFilter = $(diffFileFilterId).select2({
996 'dropdownAutoWidth': true,
999 'dropdownAutoWidth': true,
997 'width': 'auto',
1000 'width': 'auto',
998
1001
999 containerCssClass: "drop-menu",
1002 containerCssClass: "drop-menu",
1000 dropdownCssClass: "drop-menu-dropdown",
1003 dropdownCssClass: "drop-menu-dropdown",
1001 data: preloadFileFilterData["${diffset_container_id}"],
1004 data: preloadFileFilterData["${diffset_container_id}"],
1002 query: function(query) {
1005 query: function(query) {
1003 feedFilesOptions(query, preloadFileFilterData["${diffset_container_id}"]);
1006 feedFilesOptions(query, preloadFileFilterData["${diffset_container_id}"]);
1004 },
1007 },
1005 initSelection: function(element, callback) {
1008 initSelection: function(element, callback) {
1006 callback({'init': true});
1009 callback({'init': true});
1007 },
1010 },
1008 formatResult: formatFileResult,
1011 formatResult: formatFileResult,
1009 formatSelection: formatSelection
1012 formatSelection: formatSelection
1010 });
1013 });
1011
1014
1012 % if range_diff_on is True:
1015 % if range_diff_on is True:
1013 diffFileFilter.select2("enable", false);
1016 diffFileFilter.select2("enable", false);
1014 % endif
1017 % endif
1015
1018
1016 $(diffFileFilterId).on('select2-selecting', function (e) {
1019 $(diffFileFilterId).on('select2-selecting', function (e) {
1017 var idSelector = e.choice.id;
1020 var idSelector = e.choice.id;
1018
1021
1019 // expand the container if we quick-select the field
1022 // expand the container if we quick-select the field
1020 $('#'+idSelector).next().prop('checked', false);
1023 $('#'+idSelector).next().prop('checked', false);
1021 // hide the mast as we later do preventDefault()
1024 // hide the mast as we later do preventDefault()
1022 $("#select2-drop-mask").click();
1025 $("#select2-drop-mask").click();
1023
1026
1024 window.location.hash = '#'+idSelector;
1027 window.location.hash = '#'+idSelector;
1025 updateSticky();
1028 updateSticky();
1026
1029
1027 e.preventDefault();
1030 e.preventDefault();
1028 });
1031 });
1029
1032
1030 </script>
1033 </script>
1031 % endif
1034 % endif
1032
1035
1033 <script type="text/javascript">
1036 <script type="text/javascript">
1034 $(document).ready(function () {
1037 $(document).ready(function () {
1035
1038
1036 var contextPrefix = _gettext('Context file: ');
1039 var contextPrefix = _gettext('Context file: ');
1037 ## sticky sidebar
1040 ## sticky sidebar
1038 var sidebarElement = document.getElementById('diff-file-sticky');
1041 var sidebarElement = document.getElementById('diff-file-sticky');
1039 sidebar = new StickySidebar(sidebarElement, {
1042 sidebar = new StickySidebar(sidebarElement, {
1040 topSpacing: 0,
1043 topSpacing: 0,
1041 bottomSpacing: 0,
1044 bottomSpacing: 0,
1042 innerWrapperSelector: '.sidebar__inner'
1045 innerWrapperSelector: '.sidebar__inner'
1043 });
1046 });
1044 sidebarElement.addEventListener('affixed.static.stickySidebar', function () {
1047 sidebarElement.addEventListener('affixed.static.stickySidebar', function () {
1045 // reset our file so it's not holding new value
1048 // reset our file so it's not holding new value
1046 $('.fpath-placeholder-text').html(contextPrefix + ' - ')
1049 $('.fpath-placeholder-text').html(contextPrefix + ' - ')
1047 });
1050 });
1048
1051
1049 updateSticky = function () {
1052 updateSticky = function () {
1050 sidebar.updateSticky();
1053 sidebar.updateSticky();
1051 Waypoint.refreshAll();
1054 Waypoint.refreshAll();
1052 };
1055 };
1053
1056
1054 var animateText = function (fPath, anchorId) {
1057 var animateText = function (fPath, anchorId) {
1055 fPath = Select2.util.escapeMarkup(fPath);
1058 fPath = Select2.util.escapeMarkup(fPath);
1056 $('.fpath-placeholder-text').html(contextPrefix + '<a href="#a_' + anchorId + '">' + fPath + '</a>')
1059 $('.fpath-placeholder-text').html(contextPrefix + '<a href="#a_' + anchorId + '">' + fPath + '</a>')
1057 };
1060 };
1058
1061
1059 ## dynamic file waypoints
1062 ## dynamic file waypoints
1060 var setFPathInfo = function(fPath, anchorId){
1063 var setFPathInfo = function(fPath, anchorId){
1061 animateText(fPath, anchorId)
1064 animateText(fPath, anchorId)
1062 };
1065 };
1063
1066
1064 var codeBlock = $('.filediff');
1067 var codeBlock = $('.filediff');
1065
1068
1066 // forward waypoint
1069 // forward waypoint
1067 codeBlock.waypoint(
1070 codeBlock.waypoint(
1068 function(direction) {
1071 function(direction) {
1069 if (direction === "down"){
1072 if (direction === "down"){
1070 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1073 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1071 }
1074 }
1072 }, {
1075 }, {
1073 offset: function () {
1076 offset: function () {
1074 return 70;
1077 return 70;
1075 },
1078 },
1076 context: '.fpath-placeholder'
1079 context: '.fpath-placeholder'
1077 }
1080 }
1078 );
1081 );
1079
1082
1080 // backward waypoint
1083 // backward waypoint
1081 codeBlock.waypoint(
1084 codeBlock.waypoint(
1082 function(direction) {
1085 function(direction) {
1083 if (direction === "up"){
1086 if (direction === "up"){
1084 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1087 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1085 }
1088 }
1086 }, {
1089 }, {
1087 offset: function () {
1090 offset: function () {
1088 return -this.element.clientHeight + 90;
1091 return -this.element.clientHeight + 90;
1089 },
1092 },
1090 context: '.fpath-placeholder'
1093 context: '.fpath-placeholder'
1091 }
1094 }
1092 );
1095 );
1093
1096
1094 toggleWideDiff = function (el) {
1097 toggleWideDiff = function (el) {
1095 updateSticky();
1098 updateSticky();
1096 var wide = Rhodecode.comments.toggleWideMode(this);
1099 var wide = Rhodecode.comments.toggleWideMode(this);
1097 storeUserSessionAttr('rc_user_session_attr.wide_diff_mode', wide);
1100 storeUserSessionAttr('rc_user_session_attr.wide_diff_mode', wide);
1098 if (wide === true) {
1101 if (wide === true) {
1099 $(el).addClass('btn-active');
1102 $(el).addClass('btn-active');
1100 } else {
1103 } else {
1101 $(el).removeClass('btn-active');
1104 $(el).removeClass('btn-active');
1102 }
1105 }
1103 return null;
1106 return null;
1104 };
1107 };
1105
1108
1106 var preloadDiffMenuData = {
1109 var preloadDiffMenuData = {
1107 results: [
1110 results: [
1108
1111
1109 ## Whitespace change
1112 ## Whitespace change
1110 % if request.GET.get('ignorews', '') == '1':
1113 % if request.GET.get('ignorews', '') == '1':
1111 {
1114 {
1112 id: 2,
1115 id: 2,
1113 text: _gettext('Show whitespace changes'),
1116 text: _gettext('Show whitespace changes'),
1114 action: function () {},
1117 action: function () {},
1115 url: "${h.current_route_path(request, ignorews=0)|n}"
1118 url: "${h.current_route_path(request, ignorews=0)|n}"
1116 },
1119 },
1117 % else:
1120 % else:
1118 {
1121 {
1119 id: 2,
1122 id: 2,
1120 text: _gettext('Hide whitespace changes'),
1123 text: _gettext('Hide whitespace changes'),
1121 action: function () {},
1124 action: function () {},
1122 url: "${h.current_route_path(request, ignorews=1)|n}"
1125 url: "${h.current_route_path(request, ignorews=1)|n}"
1123 },
1126 },
1124 % endif
1127 % endif
1125
1128
1126 ## FULL CONTEXT
1129 ## FULL CONTEXT
1127 % if request.GET.get('fullcontext', '') == '1':
1130 % if request.GET.get('fullcontext', '') == '1':
1128 {
1131 {
1129 id: 3,
1132 id: 3,
1130 text: _gettext('Hide full context diff'),
1133 text: _gettext('Hide full context diff'),
1131 action: function () {},
1134 action: function () {},
1132 url: "${h.current_route_path(request, fullcontext=0)|n}"
1135 url: "${h.current_route_path(request, fullcontext=0)|n}"
1133 },
1136 },
1134 % else:
1137 % else:
1135 {
1138 {
1136 id: 3,
1139 id: 3,
1137 text: _gettext('Show full context diff'),
1140 text: _gettext('Show full context diff'),
1138 action: function () {},
1141 action: function () {},
1139 url: "${h.current_route_path(request, fullcontext=1)|n}"
1142 url: "${h.current_route_path(request, fullcontext=1)|n}"
1140 },
1143 },
1141 % endif
1144 % endif
1142
1145
1143 ]
1146 ]
1144 };
1147 };
1145
1148
1146 var diffMenuId = "#diff_menu_" + "${diffset_container_id}";
1149 var diffMenuId = "#diff_menu_" + "${diffset_container_id}";
1147 $(diffMenuId).select2({
1150 $(diffMenuId).select2({
1148 minimumResultsForSearch: -1,
1151 minimumResultsForSearch: -1,
1149 containerCssClass: "drop-menu-no-width",
1152 containerCssClass: "drop-menu-no-width",
1150 dropdownCssClass: "drop-menu-dropdown",
1153 dropdownCssClass: "drop-menu-dropdown",
1151 dropdownAutoWidth: true,
1154 dropdownAutoWidth: true,
1152 data: preloadDiffMenuData,
1155 data: preloadDiffMenuData,
1153 placeholder: "${_('...')}",
1156 placeholder: "${_('...')}",
1154 });
1157 });
1155 $(diffMenuId).on('select2-selecting', function (e) {
1158 $(diffMenuId).on('select2-selecting', function (e) {
1156 e.choice.action();
1159 e.choice.action();
1157 if (e.choice.url !== null) {
1160 if (e.choice.url !== null) {
1158 window.location = e.choice.url
1161 window.location = e.choice.url
1159 }
1162 }
1160 });
1163 });
1161 toggleExpand = function (el, diffsetEl) {
1164 toggleExpand = function (el, diffsetEl) {
1162 var el = $(el);
1165 var el = $(el);
1163 if (el.hasClass('collapsed')) {
1166 if (el.hasClass('collapsed')) {
1164 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', false);
1167 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', false);
1165 el.removeClass('collapsed');
1168 el.removeClass('collapsed');
1166 el.html(
1169 el.html(
1167 '<i class="icon-minus-squared-alt icon-no-margin"></i>' +
1170 '<i class="icon-minus-squared-alt icon-no-margin"></i>' +
1168 _gettext('Collapse all files'));
1171 _gettext('Collapse all files'));
1169 }
1172 }
1170 else {
1173 else {
1171 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', true);
1174 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', true);
1172 el.addClass('collapsed');
1175 el.addClass('collapsed');
1173 el.html(
1176 el.html(
1174 '<i class="icon-plus-squared-alt icon-no-margin"></i>' +
1177 '<i class="icon-plus-squared-alt icon-no-margin"></i>' +
1175 _gettext('Expand all files'));
1178 _gettext('Expand all files'));
1176 }
1179 }
1177 updateSticky()
1180 updateSticky()
1178 };
1181 };
1179
1182
1180 toggleCommitExpand = function (el) {
1183 toggleCommitExpand = function (el) {
1181 var $el = $(el);
1184 var $el = $(el);
1182 var commits = $el.data('toggleCommitsCnt');
1185 var commits = $el.data('toggleCommitsCnt');
1183 var collapseMsg = _ngettext('Collapse {0} commit', 'Collapse {0} commits', commits).format(commits);
1186 var collapseMsg = _ngettext('Collapse {0} commit', 'Collapse {0} commits', commits).format(commits);
1184 var expandMsg = _ngettext('Expand {0} commit', 'Expand {0} commits', commits).format(commits);
1187 var expandMsg = _ngettext('Expand {0} commit', 'Expand {0} commits', commits).format(commits);
1185
1188
1186 if ($el.hasClass('collapsed')) {
1189 if ($el.hasClass('collapsed')) {
1187 $('.compare_select').show();
1190 $('.compare_select').show();
1188 $('.compare_select_hidden').hide();
1191 $('.compare_select_hidden').hide();
1189
1192
1190 $el.removeClass('collapsed');
1193 $el.removeClass('collapsed');
1191 $el.html(
1194 $el.html(
1192 '<i class="icon-minus-squared-alt icon-no-margin"></i>' +
1195 '<i class="icon-minus-squared-alt icon-no-margin"></i>' +
1193 collapseMsg);
1196 collapseMsg);
1194 }
1197 }
1195 else {
1198 else {
1196 $('.compare_select').hide();
1199 $('.compare_select').hide();
1197 $('.compare_select_hidden').show();
1200 $('.compare_select_hidden').show();
1198 $el.addClass('collapsed');
1201 $el.addClass('collapsed');
1199 $el.html(
1202 $el.html(
1200 '<i class="icon-plus-squared-alt icon-no-margin"></i>' +
1203 '<i class="icon-plus-squared-alt icon-no-margin"></i>' +
1201 expandMsg);
1204 expandMsg);
1202 }
1205 }
1203 updateSticky();
1206 updateSticky();
1204 };
1207 };
1205
1208
1206 // get stored diff mode and pre-enable it
1209 // get stored diff mode and pre-enable it
1207 if (templateContext.session_attrs.wide_diff_mode === "true") {
1210 if (templateContext.session_attrs.wide_diff_mode === "true") {
1208 Rhodecode.comments.toggleWideMode(null);
1211 Rhodecode.comments.toggleWideMode(null);
1209 $('.toggle-wide-diff').addClass('btn-active');
1212 $('.toggle-wide-diff').addClass('btn-active');
1210 updateSticky();
1213 updateSticky();
1211 }
1214 }
1212 });
1215 });
1213 </script>
1216 </script>
1214
1217
1215 </%def>
1218 </%def>
General Comments 0
You need to be logged in to leave comments. Login now