##// END OF EJS Templates
svn: fix checkout SVN ssh url....
marcink -
r4133:23b8627d default
parent child Browse files
Show More
@@ -1,1095 +1,1100 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Some simple helper functions
23 Some simple helper functions
24 """
24 """
25
25
26 import collections
26 import collections
27 import datetime
27 import datetime
28 import dateutil.relativedelta
28 import dateutil.relativedelta
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import re
31 import re
32 import sys
32 import sys
33 import time
33 import time
34 import urllib
34 import urllib
35 import urlobject
35 import urlobject
36 import uuid
36 import uuid
37 import getpass
37 import getpass
38 from functools import update_wrapper, partial
38 from functools import update_wrapper, partial
39
39
40 import pygments.lexers
40 import pygments.lexers
41 import sqlalchemy
41 import sqlalchemy
42 import sqlalchemy.engine.url
42 import sqlalchemy.engine.url
43 import sqlalchemy.exc
43 import sqlalchemy.exc
44 import sqlalchemy.sql
44 import sqlalchemy.sql
45 import webob
45 import webob
46 import pyramid.threadlocal
46 import pyramid.threadlocal
47 from pyramid import compat
47 from pyramid import compat
48 from pyramid.settings import asbool
48 from pyramid.settings import asbool
49
49
50 import rhodecode
50 import rhodecode
51 from rhodecode.translation import _, _pluralize
51 from rhodecode.translation import _, _pluralize
52
52
53
53
54 def md5(s):
54 def md5(s):
55 return hashlib.md5(s).hexdigest()
55 return hashlib.md5(s).hexdigest()
56
56
57
57
58 def md5_safe(s):
58 def md5_safe(s):
59 return md5(safe_str(s))
59 return md5(safe_str(s))
60
60
61
61
62 def sha1(s):
62 def sha1(s):
63 return hashlib.sha1(s).hexdigest()
63 return hashlib.sha1(s).hexdigest()
64
64
65
65
66 def sha1_safe(s):
66 def sha1_safe(s):
67 return sha1(safe_str(s))
67 return sha1(safe_str(s))
68
68
69
69
70 def __get_lem(extra_mapping=None):
70 def __get_lem(extra_mapping=None):
71 """
71 """
72 Get language extension map based on what's inside pygments lexers
72 Get language extension map based on what's inside pygments lexers
73 """
73 """
74 d = collections.defaultdict(lambda: [])
74 d = collections.defaultdict(lambda: [])
75
75
76 def __clean(s):
76 def __clean(s):
77 s = s.lstrip('*')
77 s = s.lstrip('*')
78 s = s.lstrip('.')
78 s = s.lstrip('.')
79
79
80 if s.find('[') != -1:
80 if s.find('[') != -1:
81 exts = []
81 exts = []
82 start, stop = s.find('['), s.find(']')
82 start, stop = s.find('['), s.find(']')
83
83
84 for suffix in s[start + 1:stop]:
84 for suffix in s[start + 1:stop]:
85 exts.append(s[:s.find('[')] + suffix)
85 exts.append(s[:s.find('[')] + suffix)
86 return [e.lower() for e in exts]
86 return [e.lower() for e in exts]
87 else:
87 else:
88 return [s.lower()]
88 return [s.lower()]
89
89
90 for lx, t in sorted(pygments.lexers.LEXERS.items()):
90 for lx, t in sorted(pygments.lexers.LEXERS.items()):
91 m = map(__clean, t[-2])
91 m = map(__clean, t[-2])
92 if m:
92 if m:
93 m = reduce(lambda x, y: x + y, m)
93 m = reduce(lambda x, y: x + y, m)
94 for ext in m:
94 for ext in m:
95 desc = lx.replace('Lexer', '')
95 desc = lx.replace('Lexer', '')
96 d[ext].append(desc)
96 d[ext].append(desc)
97
97
98 data = dict(d)
98 data = dict(d)
99
99
100 extra_mapping = extra_mapping or {}
100 extra_mapping = extra_mapping or {}
101 if extra_mapping:
101 if extra_mapping:
102 for k, v in extra_mapping.items():
102 for k, v in extra_mapping.items():
103 if k not in data:
103 if k not in data:
104 # register new mapping2lexer
104 # register new mapping2lexer
105 data[k] = [v]
105 data[k] = [v]
106
106
107 return data
107 return data
108
108
109
109
110 def str2bool(_str):
110 def str2bool(_str):
111 """
111 """
112 returns True/False value from given string, it tries to translate the
112 returns True/False value from given string, it tries to translate the
113 string into boolean
113 string into boolean
114
114
115 :param _str: string value to translate into boolean
115 :param _str: string value to translate into boolean
116 :rtype: boolean
116 :rtype: boolean
117 :returns: boolean from given string
117 :returns: boolean from given string
118 """
118 """
119 if _str is None:
119 if _str is None:
120 return False
120 return False
121 if _str in (True, False):
121 if _str in (True, False):
122 return _str
122 return _str
123 _str = str(_str).strip().lower()
123 _str = str(_str).strip().lower()
124 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
124 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
125
125
126
126
127 def aslist(obj, sep=None, strip=True):
127 def aslist(obj, sep=None, strip=True):
128 """
128 """
129 Returns given string separated by sep as list
129 Returns given string separated by sep as list
130
130
131 :param obj:
131 :param obj:
132 :param sep:
132 :param sep:
133 :param strip:
133 :param strip:
134 """
134 """
135 if isinstance(obj, (basestring,)):
135 if isinstance(obj, (basestring,)):
136 lst = obj.split(sep)
136 lst = obj.split(sep)
137 if strip:
137 if strip:
138 lst = [v.strip() for v in lst]
138 lst = [v.strip() for v in lst]
139 return lst
139 return lst
140 elif isinstance(obj, (list, tuple)):
140 elif isinstance(obj, (list, tuple)):
141 return obj
141 return obj
142 elif obj is None:
142 elif obj is None:
143 return []
143 return []
144 else:
144 else:
145 return [obj]
145 return [obj]
146
146
147
147
148 def convert_line_endings(line, mode):
148 def convert_line_endings(line, mode):
149 """
149 """
150 Converts a given line "line end" accordingly to given mode
150 Converts a given line "line end" accordingly to given mode
151
151
152 Available modes are::
152 Available modes are::
153 0 - Unix
153 0 - Unix
154 1 - Mac
154 1 - Mac
155 2 - DOS
155 2 - DOS
156
156
157 :param line: given line to convert
157 :param line: given line to convert
158 :param mode: mode to convert to
158 :param mode: mode to convert to
159 :rtype: str
159 :rtype: str
160 :return: converted line according to mode
160 :return: converted line according to mode
161 """
161 """
162 if mode == 0:
162 if mode == 0:
163 line = line.replace('\r\n', '\n')
163 line = line.replace('\r\n', '\n')
164 line = line.replace('\r', '\n')
164 line = line.replace('\r', '\n')
165 elif mode == 1:
165 elif mode == 1:
166 line = line.replace('\r\n', '\r')
166 line = line.replace('\r\n', '\r')
167 line = line.replace('\n', '\r')
167 line = line.replace('\n', '\r')
168 elif mode == 2:
168 elif mode == 2:
169 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
169 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
170 return line
170 return line
171
171
172
172
173 def detect_mode(line, default):
173 def detect_mode(line, default):
174 """
174 """
175 Detects line break for given line, if line break couldn't be found
175 Detects line break for given line, if line break couldn't be found
176 given default value is returned
176 given default value is returned
177
177
178 :param line: str line
178 :param line: str line
179 :param default: default
179 :param default: default
180 :rtype: int
180 :rtype: int
181 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
181 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
182 """
182 """
183 if line.endswith('\r\n'):
183 if line.endswith('\r\n'):
184 return 2
184 return 2
185 elif line.endswith('\n'):
185 elif line.endswith('\n'):
186 return 0
186 return 0
187 elif line.endswith('\r'):
187 elif line.endswith('\r'):
188 return 1
188 return 1
189 else:
189 else:
190 return default
190 return default
191
191
192
192
193 def safe_int(val, default=None):
193 def safe_int(val, default=None):
194 """
194 """
195 Returns int() of val if val is not convertable to int use default
195 Returns int() of val if val is not convertable to int use default
196 instead
196 instead
197
197
198 :param val:
198 :param val:
199 :param default:
199 :param default:
200 """
200 """
201
201
202 try:
202 try:
203 val = int(val)
203 val = int(val)
204 except (ValueError, TypeError):
204 except (ValueError, TypeError):
205 val = default
205 val = default
206
206
207 return val
207 return val
208
208
209
209
210 def safe_unicode(str_, from_encoding=None, use_chardet=False):
210 def safe_unicode(str_, from_encoding=None, use_chardet=False):
211 """
211 """
212 safe unicode function. Does few trick to turn str_ into unicode
212 safe unicode function. Does few trick to turn str_ into unicode
213
213
214 In case of UnicodeDecode error, we try to return it with encoding detected
214 In case of UnicodeDecode error, we try to return it with encoding detected
215 by chardet library if it fails fallback to unicode with errors replaced
215 by chardet library if it fails fallback to unicode with errors replaced
216
216
217 :param str_: string to decode
217 :param str_: string to decode
218 :rtype: unicode
218 :rtype: unicode
219 :returns: unicode object
219 :returns: unicode object
220 """
220 """
221 if isinstance(str_, unicode):
221 if isinstance(str_, unicode):
222 return str_
222 return str_
223
223
224 if not from_encoding:
224 if not from_encoding:
225 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
225 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
226 'utf8'), sep=',')
226 'utf8'), sep=',')
227 from_encoding = DEFAULT_ENCODINGS
227 from_encoding = DEFAULT_ENCODINGS
228
228
229 if not isinstance(from_encoding, (list, tuple)):
229 if not isinstance(from_encoding, (list, tuple)):
230 from_encoding = [from_encoding]
230 from_encoding = [from_encoding]
231
231
232 try:
232 try:
233 return unicode(str_)
233 return unicode(str_)
234 except UnicodeDecodeError:
234 except UnicodeDecodeError:
235 pass
235 pass
236
236
237 for enc in from_encoding:
237 for enc in from_encoding:
238 try:
238 try:
239 return unicode(str_, enc)
239 return unicode(str_, enc)
240 except UnicodeDecodeError:
240 except UnicodeDecodeError:
241 pass
241 pass
242
242
243 if use_chardet:
243 if use_chardet:
244 try:
244 try:
245 import chardet
245 import chardet
246 encoding = chardet.detect(str_)['encoding']
246 encoding = chardet.detect(str_)['encoding']
247 if encoding is None:
247 if encoding is None:
248 raise Exception()
248 raise Exception()
249 return str_.decode(encoding)
249 return str_.decode(encoding)
250 except (ImportError, UnicodeDecodeError, Exception):
250 except (ImportError, UnicodeDecodeError, Exception):
251 return unicode(str_, from_encoding[0], 'replace')
251 return unicode(str_, from_encoding[0], 'replace')
252 else:
252 else:
253 return unicode(str_, from_encoding[0], 'replace')
253 return unicode(str_, from_encoding[0], 'replace')
254
254
255 def safe_str(unicode_, to_encoding=None, use_chardet=False):
255 def safe_str(unicode_, to_encoding=None, use_chardet=False):
256 """
256 """
257 safe str function. Does few trick to turn unicode_ into string
257 safe str function. Does few trick to turn unicode_ into string
258
258
259 In case of UnicodeEncodeError, we try to return it with encoding detected
259 In case of UnicodeEncodeError, we try to return it with encoding detected
260 by chardet library if it fails fallback to string with errors replaced
260 by chardet library if it fails fallback to string with errors replaced
261
261
262 :param unicode_: unicode to encode
262 :param unicode_: unicode to encode
263 :rtype: str
263 :rtype: str
264 :returns: str object
264 :returns: str object
265 """
265 """
266
266
267 # if it's not basestr cast to str
267 # if it's not basestr cast to str
268 if not isinstance(unicode_, compat.string_types):
268 if not isinstance(unicode_, compat.string_types):
269 return str(unicode_)
269 return str(unicode_)
270
270
271 if isinstance(unicode_, str):
271 if isinstance(unicode_, str):
272 return unicode_
272 return unicode_
273
273
274 if not to_encoding:
274 if not to_encoding:
275 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
275 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
276 'utf8'), sep=',')
276 'utf8'), sep=',')
277 to_encoding = DEFAULT_ENCODINGS
277 to_encoding = DEFAULT_ENCODINGS
278
278
279 if not isinstance(to_encoding, (list, tuple)):
279 if not isinstance(to_encoding, (list, tuple)):
280 to_encoding = [to_encoding]
280 to_encoding = [to_encoding]
281
281
282 for enc in to_encoding:
282 for enc in to_encoding:
283 try:
283 try:
284 return unicode_.encode(enc)
284 return unicode_.encode(enc)
285 except UnicodeEncodeError:
285 except UnicodeEncodeError:
286 pass
286 pass
287
287
288 if use_chardet:
288 if use_chardet:
289 try:
289 try:
290 import chardet
290 import chardet
291 encoding = chardet.detect(unicode_)['encoding']
291 encoding = chardet.detect(unicode_)['encoding']
292 if encoding is None:
292 if encoding is None:
293 raise UnicodeEncodeError()
293 raise UnicodeEncodeError()
294
294
295 return unicode_.encode(encoding)
295 return unicode_.encode(encoding)
296 except (ImportError, UnicodeEncodeError):
296 except (ImportError, UnicodeEncodeError):
297 return unicode_.encode(to_encoding[0], 'replace')
297 return unicode_.encode(to_encoding[0], 'replace')
298 else:
298 else:
299 return unicode_.encode(to_encoding[0], 'replace')
299 return unicode_.encode(to_encoding[0], 'replace')
300
300
301
301
302 def remove_suffix(s, suffix):
302 def remove_suffix(s, suffix):
303 if s.endswith(suffix):
303 if s.endswith(suffix):
304 s = s[:-1 * len(suffix)]
304 s = s[:-1 * len(suffix)]
305 return s
305 return s
306
306
307
307
308 def remove_prefix(s, prefix):
308 def remove_prefix(s, prefix):
309 if s.startswith(prefix):
309 if s.startswith(prefix):
310 s = s[len(prefix):]
310 s = s[len(prefix):]
311 return s
311 return s
312
312
313
313
314 def find_calling_context(ignore_modules=None):
314 def find_calling_context(ignore_modules=None):
315 """
315 """
316 Look through the calling stack and return the frame which called
316 Look through the calling stack and return the frame which called
317 this function and is part of core module ( ie. rhodecode.* )
317 this function and is part of core module ( ie. rhodecode.* )
318
318
319 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
319 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
320 """
320 """
321
321
322 ignore_modules = ignore_modules or []
322 ignore_modules = ignore_modules or []
323
323
324 f = sys._getframe(2)
324 f = sys._getframe(2)
325 while f.f_back is not None:
325 while f.f_back is not None:
326 name = f.f_globals.get('__name__')
326 name = f.f_globals.get('__name__')
327 if name and name.startswith(__name__.split('.')[0]):
327 if name and name.startswith(__name__.split('.')[0]):
328 if name not in ignore_modules:
328 if name not in ignore_modules:
329 return f
329 return f
330 f = f.f_back
330 f = f.f_back
331 return None
331 return None
332
332
333
333
334 def ping_connection(connection, branch):
334 def ping_connection(connection, branch):
335 if branch:
335 if branch:
336 # "branch" refers to a sub-connection of a connection,
336 # "branch" refers to a sub-connection of a connection,
337 # we don't want to bother pinging on these.
337 # we don't want to bother pinging on these.
338 return
338 return
339
339
340 # turn off "close with result". This flag is only used with
340 # turn off "close with result". This flag is only used with
341 # "connectionless" execution, otherwise will be False in any case
341 # "connectionless" execution, otherwise will be False in any case
342 save_should_close_with_result = connection.should_close_with_result
342 save_should_close_with_result = connection.should_close_with_result
343 connection.should_close_with_result = False
343 connection.should_close_with_result = False
344
344
345 try:
345 try:
346 # run a SELECT 1. use a core select() so that
346 # run a SELECT 1. use a core select() so that
347 # the SELECT of a scalar value without a table is
347 # the SELECT of a scalar value without a table is
348 # appropriately formatted for the backend
348 # appropriately formatted for the backend
349 connection.scalar(sqlalchemy.sql.select([1]))
349 connection.scalar(sqlalchemy.sql.select([1]))
350 except sqlalchemy.exc.DBAPIError as err:
350 except sqlalchemy.exc.DBAPIError as err:
351 # catch SQLAlchemy's DBAPIError, which is a wrapper
351 # catch SQLAlchemy's DBAPIError, which is a wrapper
352 # for the DBAPI's exception. It includes a .connection_invalidated
352 # for the DBAPI's exception. It includes a .connection_invalidated
353 # attribute which specifies if this connection is a "disconnect"
353 # attribute which specifies if this connection is a "disconnect"
354 # condition, which is based on inspection of the original exception
354 # condition, which is based on inspection of the original exception
355 # by the dialect in use.
355 # by the dialect in use.
356 if err.connection_invalidated:
356 if err.connection_invalidated:
357 # run the same SELECT again - the connection will re-validate
357 # run the same SELECT again - the connection will re-validate
358 # itself and establish a new connection. The disconnect detection
358 # itself and establish a new connection. The disconnect detection
359 # here also causes the whole connection pool to be invalidated
359 # here also causes the whole connection pool to be invalidated
360 # so that all stale connections are discarded.
360 # so that all stale connections are discarded.
361 connection.scalar(sqlalchemy.sql.select([1]))
361 connection.scalar(sqlalchemy.sql.select([1]))
362 else:
362 else:
363 raise
363 raise
364 finally:
364 finally:
365 # restore "close with result"
365 # restore "close with result"
366 connection.should_close_with_result = save_should_close_with_result
366 connection.should_close_with_result = save_should_close_with_result
367
367
368
368
369 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
369 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
370 """Custom engine_from_config functions."""
370 """Custom engine_from_config functions."""
371 log = logging.getLogger('sqlalchemy.engine')
371 log = logging.getLogger('sqlalchemy.engine')
372 use_ping_connection = asbool(configuration.pop('sqlalchemy.db1.ping_connection', None))
372 use_ping_connection = asbool(configuration.pop('sqlalchemy.db1.ping_connection', None))
373 debug = asbool(configuration.get('debug'))
373 debug = asbool(configuration.get('debug'))
374
374
375 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
375 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
376
376
377 def color_sql(sql):
377 def color_sql(sql):
378 color_seq = '\033[1;33m' # This is yellow: code 33
378 color_seq = '\033[1;33m' # This is yellow: code 33
379 normal = '\x1b[0m'
379 normal = '\x1b[0m'
380 return ''.join([color_seq, sql, normal])
380 return ''.join([color_seq, sql, normal])
381
381
382 if use_ping_connection:
382 if use_ping_connection:
383 log.debug('Adding ping_connection on the engine config.')
383 log.debug('Adding ping_connection on the engine config.')
384 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
384 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
385
385
386 if debug:
386 if debug:
387 # attach events only for debug configuration
387 # attach events only for debug configuration
388 def before_cursor_execute(conn, cursor, statement,
388 def before_cursor_execute(conn, cursor, statement,
389 parameters, context, executemany):
389 parameters, context, executemany):
390 setattr(conn, 'query_start_time', time.time())
390 setattr(conn, 'query_start_time', time.time())
391 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
391 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
392 calling_context = find_calling_context(ignore_modules=[
392 calling_context = find_calling_context(ignore_modules=[
393 'rhodecode.lib.caching_query',
393 'rhodecode.lib.caching_query',
394 'rhodecode.model.settings',
394 'rhodecode.model.settings',
395 ])
395 ])
396 if calling_context:
396 if calling_context:
397 log.info(color_sql('call context %s:%s' % (
397 log.info(color_sql('call context %s:%s' % (
398 calling_context.f_code.co_filename,
398 calling_context.f_code.co_filename,
399 calling_context.f_lineno,
399 calling_context.f_lineno,
400 )))
400 )))
401
401
402 def after_cursor_execute(conn, cursor, statement,
402 def after_cursor_execute(conn, cursor, statement,
403 parameters, context, executemany):
403 parameters, context, executemany):
404 delattr(conn, 'query_start_time')
404 delattr(conn, 'query_start_time')
405
405
406 sqlalchemy.event.listen(engine, "before_cursor_execute", before_cursor_execute)
406 sqlalchemy.event.listen(engine, "before_cursor_execute", before_cursor_execute)
407 sqlalchemy.event.listen(engine, "after_cursor_execute", after_cursor_execute)
407 sqlalchemy.event.listen(engine, "after_cursor_execute", after_cursor_execute)
408
408
409 return engine
409 return engine
410
410
411
411
412 def get_encryption_key(config):
412 def get_encryption_key(config):
413 secret = config.get('rhodecode.encrypted_values.secret')
413 secret = config.get('rhodecode.encrypted_values.secret')
414 default = config['beaker.session.secret']
414 default = config['beaker.session.secret']
415 return secret or default
415 return secret or default
416
416
417
417
418 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
418 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
419 short_format=False):
419 short_format=False):
420 """
420 """
421 Turns a datetime into an age string.
421 Turns a datetime into an age string.
422 If show_short_version is True, this generates a shorter string with
422 If show_short_version is True, this generates a shorter string with
423 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
423 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
424
424
425 * IMPORTANT*
425 * IMPORTANT*
426 Code of this function is written in special way so it's easier to
426 Code of this function is written in special way so it's easier to
427 backport it to javascript. If you mean to update it, please also update
427 backport it to javascript. If you mean to update it, please also update
428 `jquery.timeago-extension.js` file
428 `jquery.timeago-extension.js` file
429
429
430 :param prevdate: datetime object
430 :param prevdate: datetime object
431 :param now: get current time, if not define we use
431 :param now: get current time, if not define we use
432 `datetime.datetime.now()`
432 `datetime.datetime.now()`
433 :param show_short_version: if it should approximate the date and
433 :param show_short_version: if it should approximate the date and
434 return a shorter string
434 return a shorter string
435 :param show_suffix:
435 :param show_suffix:
436 :param short_format: show short format, eg 2D instead of 2 days
436 :param short_format: show short format, eg 2D instead of 2 days
437 :rtype: unicode
437 :rtype: unicode
438 :returns: unicode words describing age
438 :returns: unicode words describing age
439 """
439 """
440
440
441 def _get_relative_delta(now, prevdate):
441 def _get_relative_delta(now, prevdate):
442 base = dateutil.relativedelta.relativedelta(now, prevdate)
442 base = dateutil.relativedelta.relativedelta(now, prevdate)
443 return {
443 return {
444 'year': base.years,
444 'year': base.years,
445 'month': base.months,
445 'month': base.months,
446 'day': base.days,
446 'day': base.days,
447 'hour': base.hours,
447 'hour': base.hours,
448 'minute': base.minutes,
448 'minute': base.minutes,
449 'second': base.seconds,
449 'second': base.seconds,
450 }
450 }
451
451
452 def _is_leap_year(year):
452 def _is_leap_year(year):
453 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
453 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
454
454
455 def get_month(prevdate):
455 def get_month(prevdate):
456 return prevdate.month
456 return prevdate.month
457
457
458 def get_year(prevdate):
458 def get_year(prevdate):
459 return prevdate.year
459 return prevdate.year
460
460
461 now = now or datetime.datetime.now()
461 now = now or datetime.datetime.now()
462 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
462 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
463 deltas = {}
463 deltas = {}
464 future = False
464 future = False
465
465
466 if prevdate > now:
466 if prevdate > now:
467 now_old = now
467 now_old = now
468 now = prevdate
468 now = prevdate
469 prevdate = now_old
469 prevdate = now_old
470 future = True
470 future = True
471 if future:
471 if future:
472 prevdate = prevdate.replace(microsecond=0)
472 prevdate = prevdate.replace(microsecond=0)
473 # Get date parts deltas
473 # Get date parts deltas
474 for part in order:
474 for part in order:
475 rel_delta = _get_relative_delta(now, prevdate)
475 rel_delta = _get_relative_delta(now, prevdate)
476 deltas[part] = rel_delta[part]
476 deltas[part] = rel_delta[part]
477
477
478 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
478 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
479 # not 1 hour, -59 minutes and -59 seconds)
479 # not 1 hour, -59 minutes and -59 seconds)
480 offsets = [[5, 60], [4, 60], [3, 24]]
480 offsets = [[5, 60], [4, 60], [3, 24]]
481 for element in offsets: # seconds, minutes, hours
481 for element in offsets: # seconds, minutes, hours
482 num = element[0]
482 num = element[0]
483 length = element[1]
483 length = element[1]
484
484
485 part = order[num]
485 part = order[num]
486 carry_part = order[num - 1]
486 carry_part = order[num - 1]
487
487
488 if deltas[part] < 0:
488 if deltas[part] < 0:
489 deltas[part] += length
489 deltas[part] += length
490 deltas[carry_part] -= 1
490 deltas[carry_part] -= 1
491
491
492 # Same thing for days except that the increment depends on the (variable)
492 # Same thing for days except that the increment depends on the (variable)
493 # number of days in the month
493 # number of days in the month
494 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
494 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
495 if deltas['day'] < 0:
495 if deltas['day'] < 0:
496 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
496 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
497 deltas['day'] += 29
497 deltas['day'] += 29
498 else:
498 else:
499 deltas['day'] += month_lengths[get_month(prevdate) - 1]
499 deltas['day'] += month_lengths[get_month(prevdate) - 1]
500
500
501 deltas['month'] -= 1
501 deltas['month'] -= 1
502
502
503 if deltas['month'] < 0:
503 if deltas['month'] < 0:
504 deltas['month'] += 12
504 deltas['month'] += 12
505 deltas['year'] -= 1
505 deltas['year'] -= 1
506
506
507 # Format the result
507 # Format the result
508 if short_format:
508 if short_format:
509 fmt_funcs = {
509 fmt_funcs = {
510 'year': lambda d: u'%dy' % d,
510 'year': lambda d: u'%dy' % d,
511 'month': lambda d: u'%dm' % d,
511 'month': lambda d: u'%dm' % d,
512 'day': lambda d: u'%dd' % d,
512 'day': lambda d: u'%dd' % d,
513 'hour': lambda d: u'%dh' % d,
513 'hour': lambda d: u'%dh' % d,
514 'minute': lambda d: u'%dmin' % d,
514 'minute': lambda d: u'%dmin' % d,
515 'second': lambda d: u'%dsec' % d,
515 'second': lambda d: u'%dsec' % d,
516 }
516 }
517 else:
517 else:
518 fmt_funcs = {
518 fmt_funcs = {
519 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
519 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
520 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
520 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
521 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
521 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
522 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
522 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
523 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
523 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
524 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
524 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
525 }
525 }
526
526
527 i = 0
527 i = 0
528 for part in order:
528 for part in order:
529 value = deltas[part]
529 value = deltas[part]
530 if value != 0:
530 if value != 0:
531
531
532 if i < 5:
532 if i < 5:
533 sub_part = order[i + 1]
533 sub_part = order[i + 1]
534 sub_value = deltas[sub_part]
534 sub_value = deltas[sub_part]
535 else:
535 else:
536 sub_value = 0
536 sub_value = 0
537
537
538 if sub_value == 0 or show_short_version:
538 if sub_value == 0 or show_short_version:
539 _val = fmt_funcs[part](value)
539 _val = fmt_funcs[part](value)
540 if future:
540 if future:
541 if show_suffix:
541 if show_suffix:
542 return _(u'in ${ago}', mapping={'ago': _val})
542 return _(u'in ${ago}', mapping={'ago': _val})
543 else:
543 else:
544 return _(_val)
544 return _(_val)
545
545
546 else:
546 else:
547 if show_suffix:
547 if show_suffix:
548 return _(u'${ago} ago', mapping={'ago': _val})
548 return _(u'${ago} ago', mapping={'ago': _val})
549 else:
549 else:
550 return _(_val)
550 return _(_val)
551
551
552 val = fmt_funcs[part](value)
552 val = fmt_funcs[part](value)
553 val_detail = fmt_funcs[sub_part](sub_value)
553 val_detail = fmt_funcs[sub_part](sub_value)
554 mapping = {'val': val, 'detail': val_detail}
554 mapping = {'val': val, 'detail': val_detail}
555
555
556 if short_format:
556 if short_format:
557 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
557 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
558 if show_suffix:
558 if show_suffix:
559 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
559 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
560 if future:
560 if future:
561 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
561 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
562 else:
562 else:
563 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
563 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
564 if show_suffix:
564 if show_suffix:
565 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
565 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
566 if future:
566 if future:
567 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
567 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
568
568
569 return datetime_tmpl
569 return datetime_tmpl
570 i += 1
570 i += 1
571 return _(u'just now')
571 return _(u'just now')
572
572
573
573
574 def age_from_seconds(seconds):
574 def age_from_seconds(seconds):
575 seconds = safe_int(seconds) or 0
575 seconds = safe_int(seconds) or 0
576 prevdate = time_to_datetime(time.time() + seconds)
576 prevdate = time_to_datetime(time.time() + seconds)
577 return age(prevdate, show_suffix=False, show_short_version=True)
577 return age(prevdate, show_suffix=False, show_short_version=True)
578
578
579
579
580 def cleaned_uri(uri):
580 def cleaned_uri(uri):
581 """
581 """
582 Quotes '[' and ']' from uri if there is only one of them.
582 Quotes '[' and ']' from uri if there is only one of them.
583 according to RFC3986 we cannot use such chars in uri
583 according to RFC3986 we cannot use such chars in uri
584 :param uri:
584 :param uri:
585 :return: uri without this chars
585 :return: uri without this chars
586 """
586 """
587 return urllib.quote(uri, safe='@$:/')
587 return urllib.quote(uri, safe='@$:/')
588
588
589
589
590 def uri_filter(uri):
590 def uri_filter(uri):
591 """
591 """
592 Removes user:password from given url string
592 Removes user:password from given url string
593
593
594 :param uri:
594 :param uri:
595 :rtype: unicode
595 :rtype: unicode
596 :returns: filtered list of strings
596 :returns: filtered list of strings
597 """
597 """
598 if not uri:
598 if not uri:
599 return ''
599 return ''
600
600
601 proto = ''
601 proto = ''
602
602
603 for pat in ('https://', 'http://'):
603 for pat in ('https://', 'http://'):
604 if uri.startswith(pat):
604 if uri.startswith(pat):
605 uri = uri[len(pat):]
605 uri = uri[len(pat):]
606 proto = pat
606 proto = pat
607 break
607 break
608
608
609 # remove passwords and username
609 # remove passwords and username
610 uri = uri[uri.find('@') + 1:]
610 uri = uri[uri.find('@') + 1:]
611
611
612 # get the port
612 # get the port
613 cred_pos = uri.find(':')
613 cred_pos = uri.find(':')
614 if cred_pos == -1:
614 if cred_pos == -1:
615 host, port = uri, None
615 host, port = uri, None
616 else:
616 else:
617 host, port = uri[:cred_pos], uri[cred_pos + 1:]
617 host, port = uri[:cred_pos], uri[cred_pos + 1:]
618
618
619 return filter(None, [proto, host, port])
619 return filter(None, [proto, host, port])
620
620
621
621
622 def credentials_filter(uri):
622 def credentials_filter(uri):
623 """
623 """
624 Returns a url with removed credentials
624 Returns a url with removed credentials
625
625
626 :param uri:
626 :param uri:
627 """
627 """
628
628
629 uri = uri_filter(uri)
629 uri = uri_filter(uri)
630 # check if we have port
630 # check if we have port
631 if len(uri) > 2 and uri[2]:
631 if len(uri) > 2 and uri[2]:
632 uri[2] = ':' + uri[2]
632 uri[2] = ':' + uri[2]
633
633
634 return ''.join(uri)
634 return ''.join(uri)
635
635
636
636
637 def get_host_info(request):
637 def get_host_info(request):
638 """
638 """
639 Generate host info, to obtain full url e.g https://server.com
639 Generate host info, to obtain full url e.g https://server.com
640 use this
640 use this
641 `{scheme}://{netloc}`
641 `{scheme}://{netloc}`
642 """
642 """
643 if not request:
643 if not request:
644 return {}
644 return {}
645
645
646 qualified_home_url = request.route_url('home')
646 qualified_home_url = request.route_url('home')
647 parsed_url = urlobject.URLObject(qualified_home_url)
647 parsed_url = urlobject.URLObject(qualified_home_url)
648 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
648 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
649
649
650 return {
650 return {
651 'scheme': parsed_url.scheme,
651 'scheme': parsed_url.scheme,
652 'netloc': parsed_url.netloc+decoded_path,
652 'netloc': parsed_url.netloc+decoded_path,
653 'hostname': parsed_url.hostname,
653 'hostname': parsed_url.hostname,
654 }
654 }
655
655
656
656
657 def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override):
657 def get_clone_url(request, uri_tmpl, repo_name, repo_id, repo_type, **override):
658 qualified_home_url = request.route_url('home')
658 qualified_home_url = request.route_url('home')
659 parsed_url = urlobject.URLObject(qualified_home_url)
659 parsed_url = urlobject.URLObject(qualified_home_url)
660 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
660 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
661
661
662 args = {
662 args = {
663 'scheme': parsed_url.scheme,
663 'scheme': parsed_url.scheme,
664 'user': '',
664 'user': '',
665 'sys_user': getpass.getuser(),
665 'sys_user': getpass.getuser(),
666 # path if we use proxy-prefix
666 # path if we use proxy-prefix
667 'netloc': parsed_url.netloc+decoded_path,
667 'netloc': parsed_url.netloc+decoded_path,
668 'hostname': parsed_url.hostname,
668 'hostname': parsed_url.hostname,
669 'prefix': decoded_path,
669 'prefix': decoded_path,
670 'repo': repo_name,
670 'repo': repo_name,
671 'repoid': str(repo_id)
671 'repoid': str(repo_id),
672 'repo_type': repo_type
672 }
673 }
673 args.update(override)
674 args.update(override)
674 args['user'] = urllib.quote(safe_str(args['user']))
675 args['user'] = urllib.quote(safe_str(args['user']))
675
676
676 for k, v in args.items():
677 for k, v in args.items():
677 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
678 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
678
679
680 # special case for SVN clone url
681 if repo_type == 'svn':
682 uri_tmpl = uri_tmpl.replace('ssh://', 'svn+ssh://')
683
679 # remove leading @ sign if it's present. Case of empty user
684 # remove leading @ sign if it's present. Case of empty user
680 url_obj = urlobject.URLObject(uri_tmpl)
685 url_obj = urlobject.URLObject(uri_tmpl)
681 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
686 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
682
687
683 return safe_unicode(url)
688 return safe_unicode(url)
684
689
685
690
686 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
691 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
687 """
692 """
688 Safe version of get_commit if this commit doesn't exists for a
693 Safe version of get_commit if this commit doesn't exists for a
689 repository it returns a Dummy one instead
694 repository it returns a Dummy one instead
690
695
691 :param repo: repository instance
696 :param repo: repository instance
692 :param commit_id: commit id as str
697 :param commit_id: commit id as str
693 :param pre_load: optional list of commit attributes to load
698 :param pre_load: optional list of commit attributes to load
694 """
699 """
695 # TODO(skreft): remove these circular imports
700 # TODO(skreft): remove these circular imports
696 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
701 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
697 from rhodecode.lib.vcs.exceptions import RepositoryError
702 from rhodecode.lib.vcs.exceptions import RepositoryError
698 if not isinstance(repo, BaseRepository):
703 if not isinstance(repo, BaseRepository):
699 raise Exception('You must pass an Repository '
704 raise Exception('You must pass an Repository '
700 'object as first argument got %s', type(repo))
705 'object as first argument got %s', type(repo))
701
706
702 try:
707 try:
703 commit = repo.get_commit(
708 commit = repo.get_commit(
704 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
709 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
705 except (RepositoryError, LookupError):
710 except (RepositoryError, LookupError):
706 commit = EmptyCommit()
711 commit = EmptyCommit()
707 return commit
712 return commit
708
713
709
714
710 def datetime_to_time(dt):
715 def datetime_to_time(dt):
711 if dt:
716 if dt:
712 return time.mktime(dt.timetuple())
717 return time.mktime(dt.timetuple())
713
718
714
719
715 def time_to_datetime(tm):
720 def time_to_datetime(tm):
716 if tm:
721 if tm:
717 if isinstance(tm, compat.string_types):
722 if isinstance(tm, compat.string_types):
718 try:
723 try:
719 tm = float(tm)
724 tm = float(tm)
720 except ValueError:
725 except ValueError:
721 return
726 return
722 return datetime.datetime.fromtimestamp(tm)
727 return datetime.datetime.fromtimestamp(tm)
723
728
724
729
725 def time_to_utcdatetime(tm):
730 def time_to_utcdatetime(tm):
726 if tm:
731 if tm:
727 if isinstance(tm, compat.string_types):
732 if isinstance(tm, compat.string_types):
728 try:
733 try:
729 tm = float(tm)
734 tm = float(tm)
730 except ValueError:
735 except ValueError:
731 return
736 return
732 return datetime.datetime.utcfromtimestamp(tm)
737 return datetime.datetime.utcfromtimestamp(tm)
733
738
734
739
735 MENTIONS_REGEX = re.compile(
740 MENTIONS_REGEX = re.compile(
736 # ^@ or @ without any special chars in front
741 # ^@ or @ without any special chars in front
737 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
742 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
738 # main body starts with letter, then can be . - _
743 # main body starts with letter, then can be . - _
739 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
744 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
740 re.VERBOSE | re.MULTILINE)
745 re.VERBOSE | re.MULTILINE)
741
746
742
747
743 def extract_mentioned_users(s):
748 def extract_mentioned_users(s):
744 """
749 """
745 Returns unique usernames from given string s that have @mention
750 Returns unique usernames from given string s that have @mention
746
751
747 :param s: string to get mentions
752 :param s: string to get mentions
748 """
753 """
749 usrs = set()
754 usrs = set()
750 for username in MENTIONS_REGEX.findall(s):
755 for username in MENTIONS_REGEX.findall(s):
751 usrs.add(username)
756 usrs.add(username)
752
757
753 return sorted(list(usrs), key=lambda k: k.lower())
758 return sorted(list(usrs), key=lambda k: k.lower())
754
759
755
760
756 class AttributeDictBase(dict):
761 class AttributeDictBase(dict):
757 def __getstate__(self):
762 def __getstate__(self):
758 odict = self.__dict__ # get attribute dictionary
763 odict = self.__dict__ # get attribute dictionary
759 return odict
764 return odict
760
765
761 def __setstate__(self, dict):
766 def __setstate__(self, dict):
762 self.__dict__ = dict
767 self.__dict__ = dict
763
768
764 __setattr__ = dict.__setitem__
769 __setattr__ = dict.__setitem__
765 __delattr__ = dict.__delitem__
770 __delattr__ = dict.__delitem__
766
771
767
772
768 class StrictAttributeDict(AttributeDictBase):
773 class StrictAttributeDict(AttributeDictBase):
769 """
774 """
770 Strict Version of Attribute dict which raises an Attribute error when
775 Strict Version of Attribute dict which raises an Attribute error when
771 requested attribute is not set
776 requested attribute is not set
772 """
777 """
773 def __getattr__(self, attr):
778 def __getattr__(self, attr):
774 try:
779 try:
775 return self[attr]
780 return self[attr]
776 except KeyError:
781 except KeyError:
777 raise AttributeError('%s object has no attribute %s' % (
782 raise AttributeError('%s object has no attribute %s' % (
778 self.__class__, attr))
783 self.__class__, attr))
779
784
780
785
781 class AttributeDict(AttributeDictBase):
786 class AttributeDict(AttributeDictBase):
782 def __getattr__(self, attr):
787 def __getattr__(self, attr):
783 return self.get(attr, None)
788 return self.get(attr, None)
784
789
785
790
786
791
787 class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict):
792 class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict):
788 def __init__(self, default_factory=None, *args, **kwargs):
793 def __init__(self, default_factory=None, *args, **kwargs):
789 # in python3 you can omit the args to super
794 # in python3 you can omit the args to super
790 super(OrderedDefaultDict, self).__init__(*args, **kwargs)
795 super(OrderedDefaultDict, self).__init__(*args, **kwargs)
791 self.default_factory = default_factory
796 self.default_factory = default_factory
792
797
793
798
794 def fix_PATH(os_=None):
799 def fix_PATH(os_=None):
795 """
800 """
796 Get current active python path, and append it to PATH variable to fix
801 Get current active python path, and append it to PATH variable to fix
797 issues of subprocess calls and different python versions
802 issues of subprocess calls and different python versions
798 """
803 """
799 if os_ is None:
804 if os_ is None:
800 import os
805 import os
801 else:
806 else:
802 os = os_
807 os = os_
803
808
804 cur_path = os.path.split(sys.executable)[0]
809 cur_path = os.path.split(sys.executable)[0]
805 if not os.environ['PATH'].startswith(cur_path):
810 if not os.environ['PATH'].startswith(cur_path):
806 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
811 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
807
812
808
813
809 def obfuscate_url_pw(engine):
814 def obfuscate_url_pw(engine):
810 _url = engine or ''
815 _url = engine or ''
811 try:
816 try:
812 _url = sqlalchemy.engine.url.make_url(engine)
817 _url = sqlalchemy.engine.url.make_url(engine)
813 if _url.password:
818 if _url.password:
814 _url.password = 'XXXXX'
819 _url.password = 'XXXXX'
815 except Exception:
820 except Exception:
816 pass
821 pass
817 return unicode(_url)
822 return unicode(_url)
818
823
819
824
820 def get_server_url(environ):
825 def get_server_url(environ):
821 req = webob.Request(environ)
826 req = webob.Request(environ)
822 return req.host_url + req.script_name
827 return req.host_url + req.script_name
823
828
824
829
825 def unique_id(hexlen=32):
830 def unique_id(hexlen=32):
826 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
831 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
827 return suuid(truncate_to=hexlen, alphabet=alphabet)
832 return suuid(truncate_to=hexlen, alphabet=alphabet)
828
833
829
834
830 def suuid(url=None, truncate_to=22, alphabet=None):
835 def suuid(url=None, truncate_to=22, alphabet=None):
831 """
836 """
832 Generate and return a short URL safe UUID.
837 Generate and return a short URL safe UUID.
833
838
834 If the url parameter is provided, set the namespace to the provided
839 If the url parameter is provided, set the namespace to the provided
835 URL and generate a UUID.
840 URL and generate a UUID.
836
841
837 :param url to get the uuid for
842 :param url to get the uuid for
838 :truncate_to: truncate the basic 22 UUID to shorter version
843 :truncate_to: truncate the basic 22 UUID to shorter version
839
844
840 The IDs won't be universally unique any longer, but the probability of
845 The IDs won't be universally unique any longer, but the probability of
841 a collision will still be very low.
846 a collision will still be very low.
842 """
847 """
843 # Define our alphabet.
848 # Define our alphabet.
844 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
849 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
845
850
846 # If no URL is given, generate a random UUID.
851 # If no URL is given, generate a random UUID.
847 if url is None:
852 if url is None:
848 unique_id = uuid.uuid4().int
853 unique_id = uuid.uuid4().int
849 else:
854 else:
850 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
855 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
851
856
852 alphabet_length = len(_ALPHABET)
857 alphabet_length = len(_ALPHABET)
853 output = []
858 output = []
854 while unique_id > 0:
859 while unique_id > 0:
855 digit = unique_id % alphabet_length
860 digit = unique_id % alphabet_length
856 output.append(_ALPHABET[digit])
861 output.append(_ALPHABET[digit])
857 unique_id = int(unique_id / alphabet_length)
862 unique_id = int(unique_id / alphabet_length)
858 return "".join(output)[:truncate_to]
863 return "".join(output)[:truncate_to]
859
864
860
865
861 def get_current_rhodecode_user(request=None):
866 def get_current_rhodecode_user(request=None):
862 """
867 """
863 Gets rhodecode user from request
868 Gets rhodecode user from request
864 """
869 """
865 pyramid_request = request or pyramid.threadlocal.get_current_request()
870 pyramid_request = request or pyramid.threadlocal.get_current_request()
866
871
867 # web case
872 # web case
868 if pyramid_request and hasattr(pyramid_request, 'user'):
873 if pyramid_request and hasattr(pyramid_request, 'user'):
869 return pyramid_request.user
874 return pyramid_request.user
870
875
871 # api case
876 # api case
872 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
877 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
873 return pyramid_request.rpc_user
878 return pyramid_request.rpc_user
874
879
875 return None
880 return None
876
881
877
882
878 def action_logger_generic(action, namespace=''):
883 def action_logger_generic(action, namespace=''):
879 """
884 """
880 A generic logger for actions useful to the system overview, tries to find
885 A generic logger for actions useful to the system overview, tries to find
881 an acting user for the context of the call otherwise reports unknown user
886 an acting user for the context of the call otherwise reports unknown user
882
887
883 :param action: logging message eg 'comment 5 deleted'
888 :param action: logging message eg 'comment 5 deleted'
884 :param type: string
889 :param type: string
885
890
886 :param namespace: namespace of the logging message eg. 'repo.comments'
891 :param namespace: namespace of the logging message eg. 'repo.comments'
887 :param type: string
892 :param type: string
888
893
889 """
894 """
890
895
891 logger_name = 'rhodecode.actions'
896 logger_name = 'rhodecode.actions'
892
897
893 if namespace:
898 if namespace:
894 logger_name += '.' + namespace
899 logger_name += '.' + namespace
895
900
896 log = logging.getLogger(logger_name)
901 log = logging.getLogger(logger_name)
897
902
898 # get a user if we can
903 # get a user if we can
899 user = get_current_rhodecode_user()
904 user = get_current_rhodecode_user()
900
905
901 logfunc = log.info
906 logfunc = log.info
902
907
903 if not user:
908 if not user:
904 user = '<unknown user>'
909 user = '<unknown user>'
905 logfunc = log.warning
910 logfunc = log.warning
906
911
907 logfunc('Logging action by {}: {}'.format(user, action))
912 logfunc('Logging action by {}: {}'.format(user, action))
908
913
909
914
910 def escape_split(text, sep=',', maxsplit=-1):
915 def escape_split(text, sep=',', maxsplit=-1):
911 r"""
916 r"""
912 Allows for escaping of the separator: e.g. arg='foo\, bar'
917 Allows for escaping of the separator: e.g. arg='foo\, bar'
913
918
914 It should be noted that the way bash et. al. do command line parsing, those
919 It should be noted that the way bash et. al. do command line parsing, those
915 single quotes are required.
920 single quotes are required.
916 """
921 """
917 escaped_sep = r'\%s' % sep
922 escaped_sep = r'\%s' % sep
918
923
919 if escaped_sep not in text:
924 if escaped_sep not in text:
920 return text.split(sep, maxsplit)
925 return text.split(sep, maxsplit)
921
926
922 before, _mid, after = text.partition(escaped_sep)
927 before, _mid, after = text.partition(escaped_sep)
923 startlist = before.split(sep, maxsplit) # a regular split is fine here
928 startlist = before.split(sep, maxsplit) # a regular split is fine here
924 unfinished = startlist[-1]
929 unfinished = startlist[-1]
925 startlist = startlist[:-1]
930 startlist = startlist[:-1]
926
931
927 # recurse because there may be more escaped separators
932 # recurse because there may be more escaped separators
928 endlist = escape_split(after, sep, maxsplit)
933 endlist = escape_split(after, sep, maxsplit)
929
934
930 # finish building the escaped value. we use endlist[0] becaue the first
935 # finish building the escaped value. we use endlist[0] becaue the first
931 # part of the string sent in recursion is the rest of the escaped value.
936 # part of the string sent in recursion is the rest of the escaped value.
932 unfinished += sep + endlist[0]
937 unfinished += sep + endlist[0]
933
938
934 return startlist + [unfinished] + endlist[1:] # put together all the parts
939 return startlist + [unfinished] + endlist[1:] # put together all the parts
935
940
936
941
937 class OptionalAttr(object):
942 class OptionalAttr(object):
938 """
943 """
939 Special Optional Option that defines other attribute. Example::
944 Special Optional Option that defines other attribute. Example::
940
945
941 def test(apiuser, userid=Optional(OAttr('apiuser')):
946 def test(apiuser, userid=Optional(OAttr('apiuser')):
942 user = Optional.extract(userid)
947 user = Optional.extract(userid)
943 # calls
948 # calls
944
949
945 """
950 """
946
951
947 def __init__(self, attr_name):
952 def __init__(self, attr_name):
948 self.attr_name = attr_name
953 self.attr_name = attr_name
949
954
950 def __repr__(self):
955 def __repr__(self):
951 return '<OptionalAttr:%s>' % self.attr_name
956 return '<OptionalAttr:%s>' % self.attr_name
952
957
953 def __call__(self):
958 def __call__(self):
954 return self
959 return self
955
960
956
961
957 # alias
962 # alias
958 OAttr = OptionalAttr
963 OAttr = OptionalAttr
959
964
960
965
961 class Optional(object):
966 class Optional(object):
962 """
967 """
963 Defines an optional parameter::
968 Defines an optional parameter::
964
969
965 param = param.getval() if isinstance(param, Optional) else param
970 param = param.getval() if isinstance(param, Optional) else param
966 param = param() if isinstance(param, Optional) else param
971 param = param() if isinstance(param, Optional) else param
967
972
968 is equivalent of::
973 is equivalent of::
969
974
970 param = Optional.extract(param)
975 param = Optional.extract(param)
971
976
972 """
977 """
973
978
974 def __init__(self, type_):
979 def __init__(self, type_):
975 self.type_ = type_
980 self.type_ = type_
976
981
977 def __repr__(self):
982 def __repr__(self):
978 return '<Optional:%s>' % self.type_.__repr__()
983 return '<Optional:%s>' % self.type_.__repr__()
979
984
980 def __call__(self):
985 def __call__(self):
981 return self.getval()
986 return self.getval()
982
987
983 def getval(self):
988 def getval(self):
984 """
989 """
985 returns value from this Optional instance
990 returns value from this Optional instance
986 """
991 """
987 if isinstance(self.type_, OAttr):
992 if isinstance(self.type_, OAttr):
988 # use params name
993 # use params name
989 return self.type_.attr_name
994 return self.type_.attr_name
990 return self.type_
995 return self.type_
991
996
992 @classmethod
997 @classmethod
993 def extract(cls, val):
998 def extract(cls, val):
994 """
999 """
995 Extracts value from Optional() instance
1000 Extracts value from Optional() instance
996
1001
997 :param val:
1002 :param val:
998 :return: original value if it's not Optional instance else
1003 :return: original value if it's not Optional instance else
999 value of instance
1004 value of instance
1000 """
1005 """
1001 if isinstance(val, cls):
1006 if isinstance(val, cls):
1002 return val.getval()
1007 return val.getval()
1003 return val
1008 return val
1004
1009
1005
1010
1006 def glob2re(pat):
1011 def glob2re(pat):
1007 """
1012 """
1008 Translate a shell PATTERN to a regular expression.
1013 Translate a shell PATTERN to a regular expression.
1009
1014
1010 There is no way to quote meta-characters.
1015 There is no way to quote meta-characters.
1011 """
1016 """
1012
1017
1013 i, n = 0, len(pat)
1018 i, n = 0, len(pat)
1014 res = ''
1019 res = ''
1015 while i < n:
1020 while i < n:
1016 c = pat[i]
1021 c = pat[i]
1017 i = i+1
1022 i = i+1
1018 if c == '*':
1023 if c == '*':
1019 #res = res + '.*'
1024 #res = res + '.*'
1020 res = res + '[^/]*'
1025 res = res + '[^/]*'
1021 elif c == '?':
1026 elif c == '?':
1022 #res = res + '.'
1027 #res = res + '.'
1023 res = res + '[^/]'
1028 res = res + '[^/]'
1024 elif c == '[':
1029 elif c == '[':
1025 j = i
1030 j = i
1026 if j < n and pat[j] == '!':
1031 if j < n and pat[j] == '!':
1027 j = j+1
1032 j = j+1
1028 if j < n and pat[j] == ']':
1033 if j < n and pat[j] == ']':
1029 j = j+1
1034 j = j+1
1030 while j < n and pat[j] != ']':
1035 while j < n and pat[j] != ']':
1031 j = j+1
1036 j = j+1
1032 if j >= n:
1037 if j >= n:
1033 res = res + '\\['
1038 res = res + '\\['
1034 else:
1039 else:
1035 stuff = pat[i:j].replace('\\','\\\\')
1040 stuff = pat[i:j].replace('\\','\\\\')
1036 i = j+1
1041 i = j+1
1037 if stuff[0] == '!':
1042 if stuff[0] == '!':
1038 stuff = '^' + stuff[1:]
1043 stuff = '^' + stuff[1:]
1039 elif stuff[0] == '^':
1044 elif stuff[0] == '^':
1040 stuff = '\\' + stuff
1045 stuff = '\\' + stuff
1041 res = '%s[%s]' % (res, stuff)
1046 res = '%s[%s]' % (res, stuff)
1042 else:
1047 else:
1043 res = res + re.escape(c)
1048 res = res + re.escape(c)
1044 return res + '\Z(?ms)'
1049 return res + '\Z(?ms)'
1045
1050
1046
1051
1047 def parse_byte_string(size_str):
1052 def parse_byte_string(size_str):
1048 match = re.match(r'(\d+)(MB|KB)', size_str, re.IGNORECASE)
1053 match = re.match(r'(\d+)(MB|KB)', size_str, re.IGNORECASE)
1049 if not match:
1054 if not match:
1050 raise ValueError('Given size:%s is invalid, please make sure '
1055 raise ValueError('Given size:%s is invalid, please make sure '
1051 'to use format of <num>(MB|KB)' % size_str)
1056 'to use format of <num>(MB|KB)' % size_str)
1052
1057
1053 _parts = match.groups()
1058 _parts = match.groups()
1054 num, type_ = _parts
1059 num, type_ = _parts
1055 return long(num) * {'mb': 1024*1024, 'kb': 1024}[type_.lower()]
1060 return long(num) * {'mb': 1024*1024, 'kb': 1024}[type_.lower()]
1056
1061
1057
1062
1058 class CachedProperty(object):
1063 class CachedProperty(object):
1059 """
1064 """
1060 Lazy Attributes. With option to invalidate the cache by running a method
1065 Lazy Attributes. With option to invalidate the cache by running a method
1061
1066
1062 class Foo():
1067 class Foo():
1063
1068
1064 @CachedProperty
1069 @CachedProperty
1065 def heavy_func():
1070 def heavy_func():
1066 return 'super-calculation'
1071 return 'super-calculation'
1067
1072
1068 foo = Foo()
1073 foo = Foo()
1069 foo.heavy_func() # first computions
1074 foo.heavy_func() # first computions
1070 foo.heavy_func() # fetch from cache
1075 foo.heavy_func() # fetch from cache
1071 foo._invalidate_prop_cache('heavy_func')
1076 foo._invalidate_prop_cache('heavy_func')
1072 # at this point calling foo.heavy_func() will be re-computed
1077 # at this point calling foo.heavy_func() will be re-computed
1073 """
1078 """
1074
1079
1075 def __init__(self, func, func_name=None):
1080 def __init__(self, func, func_name=None):
1076
1081
1077 if func_name is None:
1082 if func_name is None:
1078 func_name = func.__name__
1083 func_name = func.__name__
1079 self.data = (func, func_name)
1084 self.data = (func, func_name)
1080 update_wrapper(self, func)
1085 update_wrapper(self, func)
1081
1086
1082 def __get__(self, inst, class_):
1087 def __get__(self, inst, class_):
1083 if inst is None:
1088 if inst is None:
1084 return self
1089 return self
1085
1090
1086 func, func_name = self.data
1091 func, func_name = self.data
1087 value = func(inst)
1092 value = func(inst)
1088 inst.__dict__[func_name] = value
1093 inst.__dict__[func_name] = value
1089 if '_invalidate_prop_cache' not in inst.__dict__:
1094 if '_invalidate_prop_cache' not in inst.__dict__:
1090 inst.__dict__['_invalidate_prop_cache'] = partial(
1095 inst.__dict__['_invalidate_prop_cache'] = partial(
1091 self._invalidate_prop_cache, inst)
1096 self._invalidate_prop_cache, inst)
1092 return value
1097 return value
1093
1098
1094 def _invalidate_prop_cache(self, inst, name):
1099 def _invalidate_prop_cache(self, inst, name):
1095 inst.__dict__.pop(name, None)
1100 inst.__dict__.pop(name, None)
@@ -1,5464 +1,5468 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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
58 from rhodecode.lib.vcs import get_vcs_instance
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 # external identities
620 # external identities
621 external_identities = relationship(
621 external_identities = relationship(
622 'ExternalIdentity',
622 'ExternalIdentity',
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
624 cascade='all')
624 cascade='all')
625 # review rules
625 # review rules
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
627
627
628 # artifacts owned
628 # artifacts owned
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
630
630
631 # no cascade, set NULL
631 # no cascade, set NULL
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
633
633
634 def __unicode__(self):
634 def __unicode__(self):
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
636 self.user_id, self.username)
636 self.user_id, self.username)
637
637
638 @hybrid_property
638 @hybrid_property
639 def email(self):
639 def email(self):
640 return self._email
640 return self._email
641
641
642 @email.setter
642 @email.setter
643 def email(self, val):
643 def email(self, val):
644 self._email = val.lower() if val else None
644 self._email = val.lower() if val else None
645
645
646 @hybrid_property
646 @hybrid_property
647 def first_name(self):
647 def first_name(self):
648 from rhodecode.lib import helpers as h
648 from rhodecode.lib import helpers as h
649 if self.name:
649 if self.name:
650 return h.escape(self.name)
650 return h.escape(self.name)
651 return self.name
651 return self.name
652
652
653 @hybrid_property
653 @hybrid_property
654 def last_name(self):
654 def last_name(self):
655 from rhodecode.lib import helpers as h
655 from rhodecode.lib import helpers as h
656 if self.lastname:
656 if self.lastname:
657 return h.escape(self.lastname)
657 return h.escape(self.lastname)
658 return self.lastname
658 return self.lastname
659
659
660 @hybrid_property
660 @hybrid_property
661 def api_key(self):
661 def api_key(self):
662 """
662 """
663 Fetch if exist an auth-token with role ALL connected to this user
663 Fetch if exist an auth-token with role ALL connected to this user
664 """
664 """
665 user_auth_token = UserApiKeys.query()\
665 user_auth_token = UserApiKeys.query()\
666 .filter(UserApiKeys.user_id == self.user_id)\
666 .filter(UserApiKeys.user_id == self.user_id)\
667 .filter(or_(UserApiKeys.expires == -1,
667 .filter(or_(UserApiKeys.expires == -1,
668 UserApiKeys.expires >= time.time()))\
668 UserApiKeys.expires >= time.time()))\
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
670 if user_auth_token:
670 if user_auth_token:
671 user_auth_token = user_auth_token.api_key
671 user_auth_token = user_auth_token.api_key
672
672
673 return user_auth_token
673 return user_auth_token
674
674
675 @api_key.setter
675 @api_key.setter
676 def api_key(self, val):
676 def api_key(self, val):
677 # don't allow to set API key this is deprecated for now
677 # don't allow to set API key this is deprecated for now
678 self._api_key = None
678 self._api_key = None
679
679
680 @property
680 @property
681 def reviewer_pull_requests(self):
681 def reviewer_pull_requests(self):
682 return PullRequestReviewers.query() \
682 return PullRequestReviewers.query() \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
685 .all()
685 .all()
686
686
687 @property
687 @property
688 def firstname(self):
688 def firstname(self):
689 # alias for future
689 # alias for future
690 return self.name
690 return self.name
691
691
692 @property
692 @property
693 def emails(self):
693 def emails(self):
694 other = UserEmailMap.query()\
694 other = UserEmailMap.query()\
695 .filter(UserEmailMap.user == self) \
695 .filter(UserEmailMap.user == self) \
696 .order_by(UserEmailMap.email_id.asc()) \
696 .order_by(UserEmailMap.email_id.asc()) \
697 .all()
697 .all()
698 return [self.email] + [x.email for x in other]
698 return [self.email] + [x.email for x in other]
699
699
700 def emails_cached(self):
700 def emails_cached(self):
701 emails = UserEmailMap.query()\
701 emails = UserEmailMap.query()\
702 .filter(UserEmailMap.user == self) \
702 .filter(UserEmailMap.user == self) \
703 .order_by(UserEmailMap.email_id.asc())
703 .order_by(UserEmailMap.email_id.asc())
704
704
705 emails = emails.options(
705 emails = emails.options(
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
707 )
707 )
708
708
709 return [self.email] + [x.email for x in emails]
709 return [self.email] + [x.email for x in emails]
710
710
711 @property
711 @property
712 def auth_tokens(self):
712 def auth_tokens(self):
713 auth_tokens = self.get_auth_tokens()
713 auth_tokens = self.get_auth_tokens()
714 return [x.api_key for x in auth_tokens]
714 return [x.api_key for x in auth_tokens]
715
715
716 def get_auth_tokens(self):
716 def get_auth_tokens(self):
717 return UserApiKeys.query()\
717 return UserApiKeys.query()\
718 .filter(UserApiKeys.user == self)\
718 .filter(UserApiKeys.user == self)\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
720 .all()
720 .all()
721
721
722 @LazyProperty
722 @LazyProperty
723 def feed_token(self):
723 def feed_token(self):
724 return self.get_feed_token()
724 return self.get_feed_token()
725
725
726 def get_feed_token(self, cache=True):
726 def get_feed_token(self, cache=True):
727 feed_tokens = UserApiKeys.query()\
727 feed_tokens = UserApiKeys.query()\
728 .filter(UserApiKeys.user == self)\
728 .filter(UserApiKeys.user == self)\
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
730 if cache:
730 if cache:
731 feed_tokens = feed_tokens.options(
731 feed_tokens = feed_tokens.options(
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
733
733
734 feed_tokens = feed_tokens.all()
734 feed_tokens = feed_tokens.all()
735 if feed_tokens:
735 if feed_tokens:
736 return feed_tokens[0].api_key
736 return feed_tokens[0].api_key
737 return 'NO_FEED_TOKEN_AVAILABLE'
737 return 'NO_FEED_TOKEN_AVAILABLE'
738
738
739 @LazyProperty
739 @LazyProperty
740 def artifact_token(self):
740 def artifact_token(self):
741 return self.get_artifact_token()
741 return self.get_artifact_token()
742
742
743 def get_artifact_token(self, cache=True):
743 def get_artifact_token(self, cache=True):
744 artifacts_tokens = UserApiKeys.query()\
744 artifacts_tokens = UserApiKeys.query()\
745 .filter(UserApiKeys.user == self)\
745 .filter(UserApiKeys.user == self)\
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
747 if cache:
747 if cache:
748 artifacts_tokens = artifacts_tokens.options(
748 artifacts_tokens = artifacts_tokens.options(
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
750
750
751 artifacts_tokens = artifacts_tokens.all()
751 artifacts_tokens = artifacts_tokens.all()
752 if artifacts_tokens:
752 if artifacts_tokens:
753 return artifacts_tokens[0].api_key
753 return artifacts_tokens[0].api_key
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
755
755
756 @classmethod
756 @classmethod
757 def get(cls, user_id, cache=False):
757 def get(cls, user_id, cache=False):
758 if not user_id:
758 if not user_id:
759 return
759 return
760
760
761 user = cls.query()
761 user = cls.query()
762 if cache:
762 if cache:
763 user = user.options(
763 user = user.options(
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
765 return user.get(user_id)
765 return user.get(user_id)
766
766
767 @classmethod
767 @classmethod
768 def extra_valid_auth_tokens(cls, user, role=None):
768 def extra_valid_auth_tokens(cls, user, role=None):
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
770 .filter(or_(UserApiKeys.expires == -1,
770 .filter(or_(UserApiKeys.expires == -1,
771 UserApiKeys.expires >= time.time()))
771 UserApiKeys.expires >= time.time()))
772 if role:
772 if role:
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
775 return tokens.all()
775 return tokens.all()
776
776
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
778 from rhodecode.lib import auth
778 from rhodecode.lib import auth
779
779
780 log.debug('Trying to authenticate user: %s via auth-token, '
780 log.debug('Trying to authenticate user: %s via auth-token, '
781 'and roles: %s', self, roles)
781 'and roles: %s', self, roles)
782
782
783 if not auth_token:
783 if not auth_token:
784 return False
784 return False
785
785
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
787 tokens_q = UserApiKeys.query()\
787 tokens_q = UserApiKeys.query()\
788 .filter(UserApiKeys.user_id == self.user_id)\
788 .filter(UserApiKeys.user_id == self.user_id)\
789 .filter(or_(UserApiKeys.expires == -1,
789 .filter(or_(UserApiKeys.expires == -1,
790 UserApiKeys.expires >= time.time()))
790 UserApiKeys.expires >= time.time()))
791
791
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
793
793
794 crypto_backend = auth.crypto_backend()
794 crypto_backend = auth.crypto_backend()
795 enc_token_map = {}
795 enc_token_map = {}
796 plain_token_map = {}
796 plain_token_map = {}
797 for token in tokens_q:
797 for token in tokens_q:
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
799 enc_token_map[token.api_key] = token
799 enc_token_map[token.api_key] = token
800 else:
800 else:
801 plain_token_map[token.api_key] = token
801 plain_token_map[token.api_key] = token
802 log.debug(
802 log.debug(
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
804 len(plain_token_map), len(enc_token_map))
804 len(plain_token_map), len(enc_token_map))
805
805
806 # plain token match comes first
806 # plain token match comes first
807 match = plain_token_map.get(auth_token)
807 match = plain_token_map.get(auth_token)
808
808
809 # check encrypted tokens now
809 # check encrypted tokens now
810 if not match:
810 if not match:
811 for token_hash, token in enc_token_map.items():
811 for token_hash, token in enc_token_map.items():
812 # NOTE(marcink): this is expensive to calculate, but most secure
812 # NOTE(marcink): this is expensive to calculate, but most secure
813 if crypto_backend.hash_check(auth_token, token_hash):
813 if crypto_backend.hash_check(auth_token, token_hash):
814 match = token
814 match = token
815 break
815 break
816
816
817 if match:
817 if match:
818 log.debug('Found matching token %s', match)
818 log.debug('Found matching token %s', match)
819 if match.repo_id:
819 if match.repo_id:
820 log.debug('Found scope, checking for scope match of token %s', match)
820 log.debug('Found scope, checking for scope match of token %s', match)
821 if match.repo_id == scope_repo_id:
821 if match.repo_id == scope_repo_id:
822 return True
822 return True
823 else:
823 else:
824 log.debug(
824 log.debug(
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
826 'and calling scope is:%s, skipping further checks',
826 'and calling scope is:%s, skipping further checks',
827 match.repo, scope_repo_id)
827 match.repo, scope_repo_id)
828 return False
828 return False
829 else:
829 else:
830 return True
830 return True
831
831
832 return False
832 return False
833
833
834 @property
834 @property
835 def ip_addresses(self):
835 def ip_addresses(self):
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
837 return [x.ip_addr for x in ret]
837 return [x.ip_addr for x in ret]
838
838
839 @property
839 @property
840 def username_and_name(self):
840 def username_and_name(self):
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
842
842
843 @property
843 @property
844 def username_or_name_or_email(self):
844 def username_or_name_or_email(self):
845 full_name = self.full_name if self.full_name is not ' ' else None
845 full_name = self.full_name if self.full_name is not ' ' else None
846 return self.username or full_name or self.email
846 return self.username or full_name or self.email
847
847
848 @property
848 @property
849 def full_name(self):
849 def full_name(self):
850 return '%s %s' % (self.first_name, self.last_name)
850 return '%s %s' % (self.first_name, self.last_name)
851
851
852 @property
852 @property
853 def full_name_or_username(self):
853 def full_name_or_username(self):
854 return ('%s %s' % (self.first_name, self.last_name)
854 return ('%s %s' % (self.first_name, self.last_name)
855 if (self.first_name and self.last_name) else self.username)
855 if (self.first_name and self.last_name) else self.username)
856
856
857 @property
857 @property
858 def full_contact(self):
858 def full_contact(self):
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
860
860
861 @property
861 @property
862 def short_contact(self):
862 def short_contact(self):
863 return '%s %s' % (self.first_name, self.last_name)
863 return '%s %s' % (self.first_name, self.last_name)
864
864
865 @property
865 @property
866 def is_admin(self):
866 def is_admin(self):
867 return self.admin
867 return self.admin
868
868
869 @property
869 @property
870 def language(self):
870 def language(self):
871 return self.user_data.get('language')
871 return self.user_data.get('language')
872
872
873 def AuthUser(self, **kwargs):
873 def AuthUser(self, **kwargs):
874 """
874 """
875 Returns instance of AuthUser for this user
875 Returns instance of AuthUser for this user
876 """
876 """
877 from rhodecode.lib.auth import AuthUser
877 from rhodecode.lib.auth import AuthUser
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
879
879
880 @hybrid_property
880 @hybrid_property
881 def user_data(self):
881 def user_data(self):
882 if not self._user_data:
882 if not self._user_data:
883 return {}
883 return {}
884
884
885 try:
885 try:
886 return json.loads(self._user_data)
886 return json.loads(self._user_data)
887 except TypeError:
887 except TypeError:
888 return {}
888 return {}
889
889
890 @user_data.setter
890 @user_data.setter
891 def user_data(self, val):
891 def user_data(self, val):
892 if not isinstance(val, dict):
892 if not isinstance(val, dict):
893 raise Exception('user_data must be dict, got %s' % type(val))
893 raise Exception('user_data must be dict, got %s' % type(val))
894 try:
894 try:
895 self._user_data = json.dumps(val)
895 self._user_data = json.dumps(val)
896 except Exception:
896 except Exception:
897 log.error(traceback.format_exc())
897 log.error(traceback.format_exc())
898
898
899 @classmethod
899 @classmethod
900 def get_by_username(cls, username, case_insensitive=False,
900 def get_by_username(cls, username, case_insensitive=False,
901 cache=False, identity_cache=False):
901 cache=False, identity_cache=False):
902 session = Session()
902 session = Session()
903
903
904 if case_insensitive:
904 if case_insensitive:
905 q = cls.query().filter(
905 q = cls.query().filter(
906 func.lower(cls.username) == func.lower(username))
906 func.lower(cls.username) == func.lower(username))
907 else:
907 else:
908 q = cls.query().filter(cls.username == username)
908 q = cls.query().filter(cls.username == username)
909
909
910 if cache:
910 if cache:
911 if identity_cache:
911 if identity_cache:
912 val = cls.identity_cache(session, 'username', username)
912 val = cls.identity_cache(session, 'username', username)
913 if val:
913 if val:
914 return val
914 return val
915 else:
915 else:
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
917 q = q.options(
917 q = q.options(
918 FromCache("sql_cache_short", cache_key))
918 FromCache("sql_cache_short", cache_key))
919
919
920 return q.scalar()
920 return q.scalar()
921
921
922 @classmethod
922 @classmethod
923 def get_by_auth_token(cls, auth_token, cache=False):
923 def get_by_auth_token(cls, auth_token, cache=False):
924 q = UserApiKeys.query()\
924 q = UserApiKeys.query()\
925 .filter(UserApiKeys.api_key == auth_token)\
925 .filter(UserApiKeys.api_key == auth_token)\
926 .filter(or_(UserApiKeys.expires == -1,
926 .filter(or_(UserApiKeys.expires == -1,
927 UserApiKeys.expires >= time.time()))
927 UserApiKeys.expires >= time.time()))
928 if cache:
928 if cache:
929 q = q.options(
929 q = q.options(
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
931
931
932 match = q.first()
932 match = q.first()
933 if match:
933 if match:
934 return match.user
934 return match.user
935
935
936 @classmethod
936 @classmethod
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
938
938
939 if case_insensitive:
939 if case_insensitive:
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
941
941
942 else:
942 else:
943 q = cls.query().filter(cls.email == email)
943 q = cls.query().filter(cls.email == email)
944
944
945 email_key = _hash_key(email)
945 email_key = _hash_key(email)
946 if cache:
946 if cache:
947 q = q.options(
947 q = q.options(
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
949
949
950 ret = q.scalar()
950 ret = q.scalar()
951 if ret is None:
951 if ret is None:
952 q = UserEmailMap.query()
952 q = UserEmailMap.query()
953 # try fetching in alternate email map
953 # try fetching in alternate email map
954 if case_insensitive:
954 if case_insensitive:
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
956 else:
956 else:
957 q = q.filter(UserEmailMap.email == email)
957 q = q.filter(UserEmailMap.email == email)
958 q = q.options(joinedload(UserEmailMap.user))
958 q = q.options(joinedload(UserEmailMap.user))
959 if cache:
959 if cache:
960 q = q.options(
960 q = q.options(
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
962 ret = getattr(q.scalar(), 'user', None)
962 ret = getattr(q.scalar(), 'user', None)
963
963
964 return ret
964 return ret
965
965
966 @classmethod
966 @classmethod
967 def get_from_cs_author(cls, author):
967 def get_from_cs_author(cls, author):
968 """
968 """
969 Tries to get User objects out of commit author string
969 Tries to get User objects out of commit author string
970
970
971 :param author:
971 :param author:
972 """
972 """
973 from rhodecode.lib.helpers import email, author_name
973 from rhodecode.lib.helpers import email, author_name
974 # Valid email in the attribute passed, see if they're in the system
974 # Valid email in the attribute passed, see if they're in the system
975 _email = email(author)
975 _email = email(author)
976 if _email:
976 if _email:
977 user = cls.get_by_email(_email, case_insensitive=True)
977 user = cls.get_by_email(_email, case_insensitive=True)
978 if user:
978 if user:
979 return user
979 return user
980 # Maybe we can match by username?
980 # Maybe we can match by username?
981 _author = author_name(author)
981 _author = author_name(author)
982 user = cls.get_by_username(_author, case_insensitive=True)
982 user = cls.get_by_username(_author, case_insensitive=True)
983 if user:
983 if user:
984 return user
984 return user
985
985
986 def update_userdata(self, **kwargs):
986 def update_userdata(self, **kwargs):
987 usr = self
987 usr = self
988 old = usr.user_data
988 old = usr.user_data
989 old.update(**kwargs)
989 old.update(**kwargs)
990 usr.user_data = old
990 usr.user_data = old
991 Session().add(usr)
991 Session().add(usr)
992 log.debug('updated userdata with %s', kwargs)
992 log.debug('updated userdata with %s', kwargs)
993
993
994 def update_lastlogin(self):
994 def update_lastlogin(self):
995 """Update user lastlogin"""
995 """Update user lastlogin"""
996 self.last_login = datetime.datetime.now()
996 self.last_login = datetime.datetime.now()
997 Session().add(self)
997 Session().add(self)
998 log.debug('updated user %s lastlogin', self.username)
998 log.debug('updated user %s lastlogin', self.username)
999
999
1000 def update_password(self, new_password):
1000 def update_password(self, new_password):
1001 from rhodecode.lib.auth import get_crypt_password
1001 from rhodecode.lib.auth import get_crypt_password
1002
1002
1003 self.password = get_crypt_password(new_password)
1003 self.password = get_crypt_password(new_password)
1004 Session().add(self)
1004 Session().add(self)
1005
1005
1006 @classmethod
1006 @classmethod
1007 def get_first_super_admin(cls):
1007 def get_first_super_admin(cls):
1008 user = User.query()\
1008 user = User.query()\
1009 .filter(User.admin == true()) \
1009 .filter(User.admin == true()) \
1010 .order_by(User.user_id.asc()) \
1010 .order_by(User.user_id.asc()) \
1011 .first()
1011 .first()
1012
1012
1013 if user is None:
1013 if user is None:
1014 raise Exception('FATAL: Missing administrative account!')
1014 raise Exception('FATAL: Missing administrative account!')
1015 return user
1015 return user
1016
1016
1017 @classmethod
1017 @classmethod
1018 def get_all_super_admins(cls, only_active=False):
1018 def get_all_super_admins(cls, only_active=False):
1019 """
1019 """
1020 Returns all admin accounts sorted by username
1020 Returns all admin accounts sorted by username
1021 """
1021 """
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1023 if only_active:
1023 if only_active:
1024 qry = qry.filter(User.active == true())
1024 qry = qry.filter(User.active == true())
1025 return qry.all()
1025 return qry.all()
1026
1026
1027 @classmethod
1027 @classmethod
1028 def get_default_user(cls, cache=False, refresh=False):
1028 def get_default_user(cls, cache=False, refresh=False):
1029 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1029 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1030 if user is None:
1030 if user is None:
1031 raise Exception('FATAL: Missing default account!')
1031 raise Exception('FATAL: Missing default account!')
1032 if refresh:
1032 if refresh:
1033 # The default user might be based on outdated state which
1033 # The default user might be based on outdated state which
1034 # has been loaded from the cache.
1034 # has been loaded from the cache.
1035 # A call to refresh() ensures that the
1035 # A call to refresh() ensures that the
1036 # latest state from the database is used.
1036 # latest state from the database is used.
1037 Session().refresh(user)
1037 Session().refresh(user)
1038 return user
1038 return user
1039
1039
1040 def _get_default_perms(self, user, suffix=''):
1040 def _get_default_perms(self, user, suffix=''):
1041 from rhodecode.model.permission import PermissionModel
1041 from rhodecode.model.permission import PermissionModel
1042 return PermissionModel().get_default_perms(user.user_perms, suffix)
1042 return PermissionModel().get_default_perms(user.user_perms, suffix)
1043
1043
1044 def get_default_perms(self, suffix=''):
1044 def get_default_perms(self, suffix=''):
1045 return self._get_default_perms(self, suffix)
1045 return self._get_default_perms(self, suffix)
1046
1046
1047 def get_api_data(self, include_secrets=False, details='full'):
1047 def get_api_data(self, include_secrets=False, details='full'):
1048 """
1048 """
1049 Common function for generating user related data for API
1049 Common function for generating user related data for API
1050
1050
1051 :param include_secrets: By default secrets in the API data will be replaced
1051 :param include_secrets: By default secrets in the API data will be replaced
1052 by a placeholder value to prevent exposing this data by accident. In case
1052 by a placeholder value to prevent exposing this data by accident. In case
1053 this data shall be exposed, set this flag to ``True``.
1053 this data shall be exposed, set this flag to ``True``.
1054
1054
1055 :param details: details can be 'basic|full' basic gives only a subset of
1055 :param details: details can be 'basic|full' basic gives only a subset of
1056 the available user information that includes user_id, name and emails.
1056 the available user information that includes user_id, name and emails.
1057 """
1057 """
1058 user = self
1058 user = self
1059 user_data = self.user_data
1059 user_data = self.user_data
1060 data = {
1060 data = {
1061 'user_id': user.user_id,
1061 'user_id': user.user_id,
1062 'username': user.username,
1062 'username': user.username,
1063 'firstname': user.name,
1063 'firstname': user.name,
1064 'lastname': user.lastname,
1064 'lastname': user.lastname,
1065 'description': user.description,
1065 'description': user.description,
1066 'email': user.email,
1066 'email': user.email,
1067 'emails': user.emails,
1067 'emails': user.emails,
1068 }
1068 }
1069 if details == 'basic':
1069 if details == 'basic':
1070 return data
1070 return data
1071
1071
1072 auth_token_length = 40
1072 auth_token_length = 40
1073 auth_token_replacement = '*' * auth_token_length
1073 auth_token_replacement = '*' * auth_token_length
1074
1074
1075 extras = {
1075 extras = {
1076 'auth_tokens': [auth_token_replacement],
1076 'auth_tokens': [auth_token_replacement],
1077 'active': user.active,
1077 'active': user.active,
1078 'admin': user.admin,
1078 'admin': user.admin,
1079 'extern_type': user.extern_type,
1079 'extern_type': user.extern_type,
1080 'extern_name': user.extern_name,
1080 'extern_name': user.extern_name,
1081 'last_login': user.last_login,
1081 'last_login': user.last_login,
1082 'last_activity': user.last_activity,
1082 'last_activity': user.last_activity,
1083 'ip_addresses': user.ip_addresses,
1083 'ip_addresses': user.ip_addresses,
1084 'language': user_data.get('language')
1084 'language': user_data.get('language')
1085 }
1085 }
1086 data.update(extras)
1086 data.update(extras)
1087
1087
1088 if include_secrets:
1088 if include_secrets:
1089 data['auth_tokens'] = user.auth_tokens
1089 data['auth_tokens'] = user.auth_tokens
1090 return data
1090 return data
1091
1091
1092 def __json__(self):
1092 def __json__(self):
1093 data = {
1093 data = {
1094 'full_name': self.full_name,
1094 'full_name': self.full_name,
1095 'full_name_or_username': self.full_name_or_username,
1095 'full_name_or_username': self.full_name_or_username,
1096 'short_contact': self.short_contact,
1096 'short_contact': self.short_contact,
1097 'full_contact': self.full_contact,
1097 'full_contact': self.full_contact,
1098 }
1098 }
1099 data.update(self.get_api_data())
1099 data.update(self.get_api_data())
1100 return data
1100 return data
1101
1101
1102
1102
1103 class UserApiKeys(Base, BaseModel):
1103 class UserApiKeys(Base, BaseModel):
1104 __tablename__ = 'user_api_keys'
1104 __tablename__ = 'user_api_keys'
1105 __table_args__ = (
1105 __table_args__ = (
1106 Index('uak_api_key_idx', 'api_key'),
1106 Index('uak_api_key_idx', 'api_key'),
1107 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1107 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1108 base_table_args
1108 base_table_args
1109 )
1109 )
1110 __mapper_args__ = {}
1110 __mapper_args__ = {}
1111
1111
1112 # ApiKey role
1112 # ApiKey role
1113 ROLE_ALL = 'token_role_all'
1113 ROLE_ALL = 'token_role_all'
1114 ROLE_HTTP = 'token_role_http'
1114 ROLE_HTTP = 'token_role_http'
1115 ROLE_VCS = 'token_role_vcs'
1115 ROLE_VCS = 'token_role_vcs'
1116 ROLE_API = 'token_role_api'
1116 ROLE_API = 'token_role_api'
1117 ROLE_FEED = 'token_role_feed'
1117 ROLE_FEED = 'token_role_feed'
1118 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1118 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1119 ROLE_PASSWORD_RESET = 'token_password_reset'
1119 ROLE_PASSWORD_RESET = 'token_password_reset'
1120
1120
1121 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1121 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1122
1122
1123 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1123 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1125 api_key = Column("api_key", String(255), nullable=False, unique=True)
1125 api_key = Column("api_key", String(255), nullable=False, unique=True)
1126 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1126 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1127 expires = Column('expires', Float(53), nullable=False)
1127 expires = Column('expires', Float(53), nullable=False)
1128 role = Column('role', String(255), nullable=True)
1128 role = Column('role', String(255), nullable=True)
1129 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1129 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1130
1130
1131 # scope columns
1131 # scope columns
1132 repo_id = Column(
1132 repo_id = Column(
1133 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1133 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1134 nullable=True, unique=None, default=None)
1134 nullable=True, unique=None, default=None)
1135 repo = relationship('Repository', lazy='joined')
1135 repo = relationship('Repository', lazy='joined')
1136
1136
1137 repo_group_id = Column(
1137 repo_group_id = Column(
1138 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1138 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1139 nullable=True, unique=None, default=None)
1139 nullable=True, unique=None, default=None)
1140 repo_group = relationship('RepoGroup', lazy='joined')
1140 repo_group = relationship('RepoGroup', lazy='joined')
1141
1141
1142 user = relationship('User', lazy='joined')
1142 user = relationship('User', lazy='joined')
1143
1143
1144 def __unicode__(self):
1144 def __unicode__(self):
1145 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1145 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1146
1146
1147 def __json__(self):
1147 def __json__(self):
1148 data = {
1148 data = {
1149 'auth_token': self.api_key,
1149 'auth_token': self.api_key,
1150 'role': self.role,
1150 'role': self.role,
1151 'scope': self.scope_humanized,
1151 'scope': self.scope_humanized,
1152 'expired': self.expired
1152 'expired': self.expired
1153 }
1153 }
1154 return data
1154 return data
1155
1155
1156 def get_api_data(self, include_secrets=False):
1156 def get_api_data(self, include_secrets=False):
1157 data = self.__json__()
1157 data = self.__json__()
1158 if include_secrets:
1158 if include_secrets:
1159 return data
1159 return data
1160 else:
1160 else:
1161 data['auth_token'] = self.token_obfuscated
1161 data['auth_token'] = self.token_obfuscated
1162 return data
1162 return data
1163
1163
1164 @hybrid_property
1164 @hybrid_property
1165 def description_safe(self):
1165 def description_safe(self):
1166 from rhodecode.lib import helpers as h
1166 from rhodecode.lib import helpers as h
1167 return h.escape(self.description)
1167 return h.escape(self.description)
1168
1168
1169 @property
1169 @property
1170 def expired(self):
1170 def expired(self):
1171 if self.expires == -1:
1171 if self.expires == -1:
1172 return False
1172 return False
1173 return time.time() > self.expires
1173 return time.time() > self.expires
1174
1174
1175 @classmethod
1175 @classmethod
1176 def _get_role_name(cls, role):
1176 def _get_role_name(cls, role):
1177 return {
1177 return {
1178 cls.ROLE_ALL: _('all'),
1178 cls.ROLE_ALL: _('all'),
1179 cls.ROLE_HTTP: _('http/web interface'),
1179 cls.ROLE_HTTP: _('http/web interface'),
1180 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1180 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1181 cls.ROLE_API: _('api calls'),
1181 cls.ROLE_API: _('api calls'),
1182 cls.ROLE_FEED: _('feed access'),
1182 cls.ROLE_FEED: _('feed access'),
1183 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1183 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1184 }.get(role, role)
1184 }.get(role, role)
1185
1185
1186 @property
1186 @property
1187 def role_humanized(self):
1187 def role_humanized(self):
1188 return self._get_role_name(self.role)
1188 return self._get_role_name(self.role)
1189
1189
1190 def _get_scope(self):
1190 def _get_scope(self):
1191 if self.repo:
1191 if self.repo:
1192 return 'Repository: {}'.format(self.repo.repo_name)
1192 return 'Repository: {}'.format(self.repo.repo_name)
1193 if self.repo_group:
1193 if self.repo_group:
1194 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1194 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1195 return 'Global'
1195 return 'Global'
1196
1196
1197 @property
1197 @property
1198 def scope_humanized(self):
1198 def scope_humanized(self):
1199 return self._get_scope()
1199 return self._get_scope()
1200
1200
1201 @property
1201 @property
1202 def token_obfuscated(self):
1202 def token_obfuscated(self):
1203 if self.api_key:
1203 if self.api_key:
1204 return self.api_key[:4] + "****"
1204 return self.api_key[:4] + "****"
1205
1205
1206
1206
1207 class UserEmailMap(Base, BaseModel):
1207 class UserEmailMap(Base, BaseModel):
1208 __tablename__ = 'user_email_map'
1208 __tablename__ = 'user_email_map'
1209 __table_args__ = (
1209 __table_args__ = (
1210 Index('uem_email_idx', 'email'),
1210 Index('uem_email_idx', 'email'),
1211 UniqueConstraint('email'),
1211 UniqueConstraint('email'),
1212 base_table_args
1212 base_table_args
1213 )
1213 )
1214 __mapper_args__ = {}
1214 __mapper_args__ = {}
1215
1215
1216 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1216 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1218 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1218 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1219 user = relationship('User', lazy='joined')
1219 user = relationship('User', lazy='joined')
1220
1220
1221 @validates('_email')
1221 @validates('_email')
1222 def validate_email(self, key, email):
1222 def validate_email(self, key, email):
1223 # check if this email is not main one
1223 # check if this email is not main one
1224 main_email = Session().query(User).filter(User.email == email).scalar()
1224 main_email = Session().query(User).filter(User.email == email).scalar()
1225 if main_email is not None:
1225 if main_email is not None:
1226 raise AttributeError('email %s is present is user table' % email)
1226 raise AttributeError('email %s is present is user table' % email)
1227 return email
1227 return email
1228
1228
1229 @hybrid_property
1229 @hybrid_property
1230 def email(self):
1230 def email(self):
1231 return self._email
1231 return self._email
1232
1232
1233 @email.setter
1233 @email.setter
1234 def email(self, val):
1234 def email(self, val):
1235 self._email = val.lower() if val else None
1235 self._email = val.lower() if val else None
1236
1236
1237
1237
1238 class UserIpMap(Base, BaseModel):
1238 class UserIpMap(Base, BaseModel):
1239 __tablename__ = 'user_ip_map'
1239 __tablename__ = 'user_ip_map'
1240 __table_args__ = (
1240 __table_args__ = (
1241 UniqueConstraint('user_id', 'ip_addr'),
1241 UniqueConstraint('user_id', 'ip_addr'),
1242 base_table_args
1242 base_table_args
1243 )
1243 )
1244 __mapper_args__ = {}
1244 __mapper_args__ = {}
1245
1245
1246 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1246 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1247 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1247 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1248 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1248 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1249 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1249 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1250 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1250 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1251 user = relationship('User', lazy='joined')
1251 user = relationship('User', lazy='joined')
1252
1252
1253 @hybrid_property
1253 @hybrid_property
1254 def description_safe(self):
1254 def description_safe(self):
1255 from rhodecode.lib import helpers as h
1255 from rhodecode.lib import helpers as h
1256 return h.escape(self.description)
1256 return h.escape(self.description)
1257
1257
1258 @classmethod
1258 @classmethod
1259 def _get_ip_range(cls, ip_addr):
1259 def _get_ip_range(cls, ip_addr):
1260 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1260 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1261 return [str(net.network_address), str(net.broadcast_address)]
1261 return [str(net.network_address), str(net.broadcast_address)]
1262
1262
1263 def __json__(self):
1263 def __json__(self):
1264 return {
1264 return {
1265 'ip_addr': self.ip_addr,
1265 'ip_addr': self.ip_addr,
1266 'ip_range': self._get_ip_range(self.ip_addr),
1266 'ip_range': self._get_ip_range(self.ip_addr),
1267 }
1267 }
1268
1268
1269 def __unicode__(self):
1269 def __unicode__(self):
1270 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1270 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1271 self.user_id, self.ip_addr)
1271 self.user_id, self.ip_addr)
1272
1272
1273
1273
1274 class UserSshKeys(Base, BaseModel):
1274 class UserSshKeys(Base, BaseModel):
1275 __tablename__ = 'user_ssh_keys'
1275 __tablename__ = 'user_ssh_keys'
1276 __table_args__ = (
1276 __table_args__ = (
1277 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1277 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1278
1278
1279 UniqueConstraint('ssh_key_fingerprint'),
1279 UniqueConstraint('ssh_key_fingerprint'),
1280
1280
1281 base_table_args
1281 base_table_args
1282 )
1282 )
1283 __mapper_args__ = {}
1283 __mapper_args__ = {}
1284
1284
1285 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1285 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1286 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1286 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1287 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1287 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1288
1288
1289 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1289 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1290
1290
1291 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1291 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1292 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1292 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1293 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1293 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1294
1294
1295 user = relationship('User', lazy='joined')
1295 user = relationship('User', lazy='joined')
1296
1296
1297 def __json__(self):
1297 def __json__(self):
1298 data = {
1298 data = {
1299 'ssh_fingerprint': self.ssh_key_fingerprint,
1299 'ssh_fingerprint': self.ssh_key_fingerprint,
1300 'description': self.description,
1300 'description': self.description,
1301 'created_on': self.created_on
1301 'created_on': self.created_on
1302 }
1302 }
1303 return data
1303 return data
1304
1304
1305 def get_api_data(self):
1305 def get_api_data(self):
1306 data = self.__json__()
1306 data = self.__json__()
1307 return data
1307 return data
1308
1308
1309
1309
1310 class UserLog(Base, BaseModel):
1310 class UserLog(Base, BaseModel):
1311 __tablename__ = 'user_logs'
1311 __tablename__ = 'user_logs'
1312 __table_args__ = (
1312 __table_args__ = (
1313 base_table_args,
1313 base_table_args,
1314 )
1314 )
1315
1315
1316 VERSION_1 = 'v1'
1316 VERSION_1 = 'v1'
1317 VERSION_2 = 'v2'
1317 VERSION_2 = 'v2'
1318 VERSIONS = [VERSION_1, VERSION_2]
1318 VERSIONS = [VERSION_1, VERSION_2]
1319
1319
1320 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1320 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1322 username = Column("username", String(255), nullable=True, unique=None, default=None)
1322 username = Column("username", String(255), nullable=True, unique=None, default=None)
1323 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1323 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1324 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1324 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1325 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1325 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1326 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1326 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1327 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1327 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1328
1328
1329 version = Column("version", String(255), nullable=True, default=VERSION_1)
1329 version = Column("version", String(255), nullable=True, default=VERSION_1)
1330 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1330 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1331 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1331 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1332
1332
1333 def __unicode__(self):
1333 def __unicode__(self):
1334 return u"<%s('id:%s:%s')>" % (
1334 return u"<%s('id:%s:%s')>" % (
1335 self.__class__.__name__, self.repository_name, self.action)
1335 self.__class__.__name__, self.repository_name, self.action)
1336
1336
1337 def __json__(self):
1337 def __json__(self):
1338 return {
1338 return {
1339 'user_id': self.user_id,
1339 'user_id': self.user_id,
1340 'username': self.username,
1340 'username': self.username,
1341 'repository_id': self.repository_id,
1341 'repository_id': self.repository_id,
1342 'repository_name': self.repository_name,
1342 'repository_name': self.repository_name,
1343 'user_ip': self.user_ip,
1343 'user_ip': self.user_ip,
1344 'action_date': self.action_date,
1344 'action_date': self.action_date,
1345 'action': self.action,
1345 'action': self.action,
1346 }
1346 }
1347
1347
1348 @hybrid_property
1348 @hybrid_property
1349 def entry_id(self):
1349 def entry_id(self):
1350 return self.user_log_id
1350 return self.user_log_id
1351
1351
1352 @property
1352 @property
1353 def action_as_day(self):
1353 def action_as_day(self):
1354 return datetime.date(*self.action_date.timetuple()[:3])
1354 return datetime.date(*self.action_date.timetuple()[:3])
1355
1355
1356 user = relationship('User')
1356 user = relationship('User')
1357 repository = relationship('Repository', cascade='')
1357 repository = relationship('Repository', cascade='')
1358
1358
1359
1359
1360 class UserGroup(Base, BaseModel):
1360 class UserGroup(Base, BaseModel):
1361 __tablename__ = 'users_groups'
1361 __tablename__ = 'users_groups'
1362 __table_args__ = (
1362 __table_args__ = (
1363 base_table_args,
1363 base_table_args,
1364 )
1364 )
1365
1365
1366 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1366 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1367 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1367 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1368 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1368 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1369 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1369 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1370 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1370 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1372 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1372 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1373 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1373 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1374
1374
1375 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1375 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1376 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1376 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1377 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1377 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1378 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1378 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1379 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1379 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1380 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1380 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1381
1381
1382 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1382 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1383 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1383 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1384
1384
1385 @classmethod
1385 @classmethod
1386 def _load_group_data(cls, column):
1386 def _load_group_data(cls, column):
1387 if not column:
1387 if not column:
1388 return {}
1388 return {}
1389
1389
1390 try:
1390 try:
1391 return json.loads(column) or {}
1391 return json.loads(column) or {}
1392 except TypeError:
1392 except TypeError:
1393 return {}
1393 return {}
1394
1394
1395 @hybrid_property
1395 @hybrid_property
1396 def description_safe(self):
1396 def description_safe(self):
1397 from rhodecode.lib import helpers as h
1397 from rhodecode.lib import helpers as h
1398 return h.escape(self.user_group_description)
1398 return h.escape(self.user_group_description)
1399
1399
1400 @hybrid_property
1400 @hybrid_property
1401 def group_data(self):
1401 def group_data(self):
1402 return self._load_group_data(self._group_data)
1402 return self._load_group_data(self._group_data)
1403
1403
1404 @group_data.expression
1404 @group_data.expression
1405 def group_data(self, **kwargs):
1405 def group_data(self, **kwargs):
1406 return self._group_data
1406 return self._group_data
1407
1407
1408 @group_data.setter
1408 @group_data.setter
1409 def group_data(self, val):
1409 def group_data(self, val):
1410 try:
1410 try:
1411 self._group_data = json.dumps(val)
1411 self._group_data = json.dumps(val)
1412 except Exception:
1412 except Exception:
1413 log.error(traceback.format_exc())
1413 log.error(traceback.format_exc())
1414
1414
1415 @classmethod
1415 @classmethod
1416 def _load_sync(cls, group_data):
1416 def _load_sync(cls, group_data):
1417 if group_data:
1417 if group_data:
1418 return group_data.get('extern_type')
1418 return group_data.get('extern_type')
1419
1419
1420 @property
1420 @property
1421 def sync(self):
1421 def sync(self):
1422 return self._load_sync(self.group_data)
1422 return self._load_sync(self.group_data)
1423
1423
1424 def __unicode__(self):
1424 def __unicode__(self):
1425 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1425 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1426 self.users_group_id,
1426 self.users_group_id,
1427 self.users_group_name)
1427 self.users_group_name)
1428
1428
1429 @classmethod
1429 @classmethod
1430 def get_by_group_name(cls, group_name, cache=False,
1430 def get_by_group_name(cls, group_name, cache=False,
1431 case_insensitive=False):
1431 case_insensitive=False):
1432 if case_insensitive:
1432 if case_insensitive:
1433 q = cls.query().filter(func.lower(cls.users_group_name) ==
1433 q = cls.query().filter(func.lower(cls.users_group_name) ==
1434 func.lower(group_name))
1434 func.lower(group_name))
1435
1435
1436 else:
1436 else:
1437 q = cls.query().filter(cls.users_group_name == group_name)
1437 q = cls.query().filter(cls.users_group_name == group_name)
1438 if cache:
1438 if cache:
1439 q = q.options(
1439 q = q.options(
1440 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1440 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1441 return q.scalar()
1441 return q.scalar()
1442
1442
1443 @classmethod
1443 @classmethod
1444 def get(cls, user_group_id, cache=False):
1444 def get(cls, user_group_id, cache=False):
1445 if not user_group_id:
1445 if not user_group_id:
1446 return
1446 return
1447
1447
1448 user_group = cls.query()
1448 user_group = cls.query()
1449 if cache:
1449 if cache:
1450 user_group = user_group.options(
1450 user_group = user_group.options(
1451 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1451 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1452 return user_group.get(user_group_id)
1452 return user_group.get(user_group_id)
1453
1453
1454 def permissions(self, with_admins=True, with_owner=True,
1454 def permissions(self, with_admins=True, with_owner=True,
1455 expand_from_user_groups=False):
1455 expand_from_user_groups=False):
1456 """
1456 """
1457 Permissions for user groups
1457 Permissions for user groups
1458 """
1458 """
1459 _admin_perm = 'usergroup.admin'
1459 _admin_perm = 'usergroup.admin'
1460
1460
1461 owner_row = []
1461 owner_row = []
1462 if with_owner:
1462 if with_owner:
1463 usr = AttributeDict(self.user.get_dict())
1463 usr = AttributeDict(self.user.get_dict())
1464 usr.owner_row = True
1464 usr.owner_row = True
1465 usr.permission = _admin_perm
1465 usr.permission = _admin_perm
1466 owner_row.append(usr)
1466 owner_row.append(usr)
1467
1467
1468 super_admin_ids = []
1468 super_admin_ids = []
1469 super_admin_rows = []
1469 super_admin_rows = []
1470 if with_admins:
1470 if with_admins:
1471 for usr in User.get_all_super_admins():
1471 for usr in User.get_all_super_admins():
1472 super_admin_ids.append(usr.user_id)
1472 super_admin_ids.append(usr.user_id)
1473 # if this admin is also owner, don't double the record
1473 # if this admin is also owner, don't double the record
1474 if usr.user_id == owner_row[0].user_id:
1474 if usr.user_id == owner_row[0].user_id:
1475 owner_row[0].admin_row = True
1475 owner_row[0].admin_row = True
1476 else:
1476 else:
1477 usr = AttributeDict(usr.get_dict())
1477 usr = AttributeDict(usr.get_dict())
1478 usr.admin_row = True
1478 usr.admin_row = True
1479 usr.permission = _admin_perm
1479 usr.permission = _admin_perm
1480 super_admin_rows.append(usr)
1480 super_admin_rows.append(usr)
1481
1481
1482 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1482 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1483 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1483 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1484 joinedload(UserUserGroupToPerm.user),
1484 joinedload(UserUserGroupToPerm.user),
1485 joinedload(UserUserGroupToPerm.permission),)
1485 joinedload(UserUserGroupToPerm.permission),)
1486
1486
1487 # get owners and admins and permissions. We do a trick of re-writing
1487 # get owners and admins and permissions. We do a trick of re-writing
1488 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1488 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1489 # has a global reference and changing one object propagates to all
1489 # has a global reference and changing one object propagates to all
1490 # others. This means if admin is also an owner admin_row that change
1490 # others. This means if admin is also an owner admin_row that change
1491 # would propagate to both objects
1491 # would propagate to both objects
1492 perm_rows = []
1492 perm_rows = []
1493 for _usr in q.all():
1493 for _usr in q.all():
1494 usr = AttributeDict(_usr.user.get_dict())
1494 usr = AttributeDict(_usr.user.get_dict())
1495 # if this user is also owner/admin, mark as duplicate record
1495 # if this user is also owner/admin, mark as duplicate record
1496 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1496 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1497 usr.duplicate_perm = True
1497 usr.duplicate_perm = True
1498 usr.permission = _usr.permission.permission_name
1498 usr.permission = _usr.permission.permission_name
1499 perm_rows.append(usr)
1499 perm_rows.append(usr)
1500
1500
1501 # filter the perm rows by 'default' first and then sort them by
1501 # filter the perm rows by 'default' first and then sort them by
1502 # admin,write,read,none permissions sorted again alphabetically in
1502 # admin,write,read,none permissions sorted again alphabetically in
1503 # each group
1503 # each group
1504 perm_rows = sorted(perm_rows, key=display_user_sort)
1504 perm_rows = sorted(perm_rows, key=display_user_sort)
1505
1505
1506 user_groups_rows = []
1506 user_groups_rows = []
1507 if expand_from_user_groups:
1507 if expand_from_user_groups:
1508 for ug in self.permission_user_groups(with_members=True):
1508 for ug in self.permission_user_groups(with_members=True):
1509 for user_data in ug.members:
1509 for user_data in ug.members:
1510 user_groups_rows.append(user_data)
1510 user_groups_rows.append(user_data)
1511
1511
1512 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1512 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1513
1513
1514 def permission_user_groups(self, with_members=False):
1514 def permission_user_groups(self, with_members=False):
1515 q = UserGroupUserGroupToPerm.query()\
1515 q = UserGroupUserGroupToPerm.query()\
1516 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1516 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1517 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1517 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1518 joinedload(UserGroupUserGroupToPerm.target_user_group),
1518 joinedload(UserGroupUserGroupToPerm.target_user_group),
1519 joinedload(UserGroupUserGroupToPerm.permission),)
1519 joinedload(UserGroupUserGroupToPerm.permission),)
1520
1520
1521 perm_rows = []
1521 perm_rows = []
1522 for _user_group in q.all():
1522 for _user_group in q.all():
1523 entry = AttributeDict(_user_group.user_group.get_dict())
1523 entry = AttributeDict(_user_group.user_group.get_dict())
1524 entry.permission = _user_group.permission.permission_name
1524 entry.permission = _user_group.permission.permission_name
1525 if with_members:
1525 if with_members:
1526 entry.members = [x.user.get_dict()
1526 entry.members = [x.user.get_dict()
1527 for x in _user_group.user_group.members]
1527 for x in _user_group.user_group.members]
1528 perm_rows.append(entry)
1528 perm_rows.append(entry)
1529
1529
1530 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1530 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1531 return perm_rows
1531 return perm_rows
1532
1532
1533 def _get_default_perms(self, user_group, suffix=''):
1533 def _get_default_perms(self, user_group, suffix=''):
1534 from rhodecode.model.permission import PermissionModel
1534 from rhodecode.model.permission import PermissionModel
1535 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1535 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1536
1536
1537 def get_default_perms(self, suffix=''):
1537 def get_default_perms(self, suffix=''):
1538 return self._get_default_perms(self, suffix)
1538 return self._get_default_perms(self, suffix)
1539
1539
1540 def get_api_data(self, with_group_members=True, include_secrets=False):
1540 def get_api_data(self, with_group_members=True, include_secrets=False):
1541 """
1541 """
1542 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1542 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1543 basically forwarded.
1543 basically forwarded.
1544
1544
1545 """
1545 """
1546 user_group = self
1546 user_group = self
1547 data = {
1547 data = {
1548 'users_group_id': user_group.users_group_id,
1548 'users_group_id': user_group.users_group_id,
1549 'group_name': user_group.users_group_name,
1549 'group_name': user_group.users_group_name,
1550 'group_description': user_group.user_group_description,
1550 'group_description': user_group.user_group_description,
1551 'active': user_group.users_group_active,
1551 'active': user_group.users_group_active,
1552 'owner': user_group.user.username,
1552 'owner': user_group.user.username,
1553 'sync': user_group.sync,
1553 'sync': user_group.sync,
1554 'owner_email': user_group.user.email,
1554 'owner_email': user_group.user.email,
1555 }
1555 }
1556
1556
1557 if with_group_members:
1557 if with_group_members:
1558 users = []
1558 users = []
1559 for user in user_group.members:
1559 for user in user_group.members:
1560 user = user.user
1560 user = user.user
1561 users.append(user.get_api_data(include_secrets=include_secrets))
1561 users.append(user.get_api_data(include_secrets=include_secrets))
1562 data['users'] = users
1562 data['users'] = users
1563
1563
1564 return data
1564 return data
1565
1565
1566
1566
1567 class UserGroupMember(Base, BaseModel):
1567 class UserGroupMember(Base, BaseModel):
1568 __tablename__ = 'users_groups_members'
1568 __tablename__ = 'users_groups_members'
1569 __table_args__ = (
1569 __table_args__ = (
1570 base_table_args,
1570 base_table_args,
1571 )
1571 )
1572
1572
1573 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1573 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1575 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1575 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1576
1576
1577 user = relationship('User', lazy='joined')
1577 user = relationship('User', lazy='joined')
1578 users_group = relationship('UserGroup')
1578 users_group = relationship('UserGroup')
1579
1579
1580 def __init__(self, gr_id='', u_id=''):
1580 def __init__(self, gr_id='', u_id=''):
1581 self.users_group_id = gr_id
1581 self.users_group_id = gr_id
1582 self.user_id = u_id
1582 self.user_id = u_id
1583
1583
1584
1584
1585 class RepositoryField(Base, BaseModel):
1585 class RepositoryField(Base, BaseModel):
1586 __tablename__ = 'repositories_fields'
1586 __tablename__ = 'repositories_fields'
1587 __table_args__ = (
1587 __table_args__ = (
1588 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1588 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1589 base_table_args,
1589 base_table_args,
1590 )
1590 )
1591
1591
1592 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1592 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1593
1593
1594 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1594 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1595 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1595 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1596 field_key = Column("field_key", String(250))
1596 field_key = Column("field_key", String(250))
1597 field_label = Column("field_label", String(1024), nullable=False)
1597 field_label = Column("field_label", String(1024), nullable=False)
1598 field_value = Column("field_value", String(10000), nullable=False)
1598 field_value = Column("field_value", String(10000), nullable=False)
1599 field_desc = Column("field_desc", String(1024), nullable=False)
1599 field_desc = Column("field_desc", String(1024), nullable=False)
1600 field_type = Column("field_type", String(255), nullable=False, unique=None)
1600 field_type = Column("field_type", String(255), nullable=False, unique=None)
1601 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1601 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1602
1602
1603 repository = relationship('Repository')
1603 repository = relationship('Repository')
1604
1604
1605 @property
1605 @property
1606 def field_key_prefixed(self):
1606 def field_key_prefixed(self):
1607 return 'ex_%s' % self.field_key
1607 return 'ex_%s' % self.field_key
1608
1608
1609 @classmethod
1609 @classmethod
1610 def un_prefix_key(cls, key):
1610 def un_prefix_key(cls, key):
1611 if key.startswith(cls.PREFIX):
1611 if key.startswith(cls.PREFIX):
1612 return key[len(cls.PREFIX):]
1612 return key[len(cls.PREFIX):]
1613 return key
1613 return key
1614
1614
1615 @classmethod
1615 @classmethod
1616 def get_by_key_name(cls, key, repo):
1616 def get_by_key_name(cls, key, repo):
1617 row = cls.query()\
1617 row = cls.query()\
1618 .filter(cls.repository == repo)\
1618 .filter(cls.repository == repo)\
1619 .filter(cls.field_key == key).scalar()
1619 .filter(cls.field_key == key).scalar()
1620 return row
1620 return row
1621
1621
1622
1622
1623 class Repository(Base, BaseModel):
1623 class Repository(Base, BaseModel):
1624 __tablename__ = 'repositories'
1624 __tablename__ = 'repositories'
1625 __table_args__ = (
1625 __table_args__ = (
1626 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1626 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1627 base_table_args,
1627 base_table_args,
1628 )
1628 )
1629 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1629 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1630 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1630 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1631 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1631 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1632
1632
1633 STATE_CREATED = 'repo_state_created'
1633 STATE_CREATED = 'repo_state_created'
1634 STATE_PENDING = 'repo_state_pending'
1634 STATE_PENDING = 'repo_state_pending'
1635 STATE_ERROR = 'repo_state_error'
1635 STATE_ERROR = 'repo_state_error'
1636
1636
1637 LOCK_AUTOMATIC = 'lock_auto'
1637 LOCK_AUTOMATIC = 'lock_auto'
1638 LOCK_API = 'lock_api'
1638 LOCK_API = 'lock_api'
1639 LOCK_WEB = 'lock_web'
1639 LOCK_WEB = 'lock_web'
1640 LOCK_PULL = 'lock_pull'
1640 LOCK_PULL = 'lock_pull'
1641
1641
1642 NAME_SEP = URL_SEP
1642 NAME_SEP = URL_SEP
1643
1643
1644 repo_id = Column(
1644 repo_id = Column(
1645 "repo_id", Integer(), nullable=False, unique=True, default=None,
1645 "repo_id", Integer(), nullable=False, unique=True, default=None,
1646 primary_key=True)
1646 primary_key=True)
1647 _repo_name = Column(
1647 _repo_name = Column(
1648 "repo_name", Text(), nullable=False, default=None)
1648 "repo_name", Text(), nullable=False, default=None)
1649 _repo_name_hash = Column(
1649 _repo_name_hash = Column(
1650 "repo_name_hash", String(255), nullable=False, unique=True)
1650 "repo_name_hash", String(255), nullable=False, unique=True)
1651 repo_state = Column("repo_state", String(255), nullable=True)
1651 repo_state = Column("repo_state", String(255), nullable=True)
1652
1652
1653 clone_uri = Column(
1653 clone_uri = Column(
1654 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1654 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1655 default=None)
1655 default=None)
1656 push_uri = Column(
1656 push_uri = Column(
1657 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1657 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1658 default=None)
1658 default=None)
1659 repo_type = Column(
1659 repo_type = Column(
1660 "repo_type", String(255), nullable=False, unique=False, default=None)
1660 "repo_type", String(255), nullable=False, unique=False, default=None)
1661 user_id = Column(
1661 user_id = Column(
1662 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1662 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1663 unique=False, default=None)
1663 unique=False, default=None)
1664 private = Column(
1664 private = Column(
1665 "private", Boolean(), nullable=True, unique=None, default=None)
1665 "private", Boolean(), nullable=True, unique=None, default=None)
1666 archived = Column(
1666 archived = Column(
1667 "archived", Boolean(), nullable=True, unique=None, default=None)
1667 "archived", Boolean(), nullable=True, unique=None, default=None)
1668 enable_statistics = Column(
1668 enable_statistics = Column(
1669 "statistics", Boolean(), nullable=True, unique=None, default=True)
1669 "statistics", Boolean(), nullable=True, unique=None, default=True)
1670 enable_downloads = Column(
1670 enable_downloads = Column(
1671 "downloads", Boolean(), nullable=True, unique=None, default=True)
1671 "downloads", Boolean(), nullable=True, unique=None, default=True)
1672 description = Column(
1672 description = Column(
1673 "description", String(10000), nullable=True, unique=None, default=None)
1673 "description", String(10000), nullable=True, unique=None, default=None)
1674 created_on = Column(
1674 created_on = Column(
1675 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1675 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1676 default=datetime.datetime.now)
1676 default=datetime.datetime.now)
1677 updated_on = Column(
1677 updated_on = Column(
1678 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1678 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1679 default=datetime.datetime.now)
1679 default=datetime.datetime.now)
1680 _landing_revision = Column(
1680 _landing_revision = Column(
1681 "landing_revision", String(255), nullable=False, unique=False,
1681 "landing_revision", String(255), nullable=False, unique=False,
1682 default=None)
1682 default=None)
1683 enable_locking = Column(
1683 enable_locking = Column(
1684 "enable_locking", Boolean(), nullable=False, unique=None,
1684 "enable_locking", Boolean(), nullable=False, unique=None,
1685 default=False)
1685 default=False)
1686 _locked = Column(
1686 _locked = Column(
1687 "locked", String(255), nullable=True, unique=False, default=None)
1687 "locked", String(255), nullable=True, unique=False, default=None)
1688 _changeset_cache = Column(
1688 _changeset_cache = Column(
1689 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1689 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1690
1690
1691 fork_id = Column(
1691 fork_id = Column(
1692 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1692 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1693 nullable=True, unique=False, default=None)
1693 nullable=True, unique=False, default=None)
1694 group_id = Column(
1694 group_id = Column(
1695 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1695 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1696 unique=False, default=None)
1696 unique=False, default=None)
1697
1697
1698 user = relationship('User', lazy='joined')
1698 user = relationship('User', lazy='joined')
1699 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1699 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1700 group = relationship('RepoGroup', lazy='joined')
1700 group = relationship('RepoGroup', lazy='joined')
1701 repo_to_perm = relationship(
1701 repo_to_perm = relationship(
1702 'UserRepoToPerm', cascade='all',
1702 'UserRepoToPerm', cascade='all',
1703 order_by='UserRepoToPerm.repo_to_perm_id')
1703 order_by='UserRepoToPerm.repo_to_perm_id')
1704 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1704 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1705 stats = relationship('Statistics', cascade='all', uselist=False)
1705 stats = relationship('Statistics', cascade='all', uselist=False)
1706
1706
1707 followers = relationship(
1707 followers = relationship(
1708 'UserFollowing',
1708 'UserFollowing',
1709 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1709 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1710 cascade='all')
1710 cascade='all')
1711 extra_fields = relationship(
1711 extra_fields = relationship(
1712 'RepositoryField', cascade="all, delete-orphan")
1712 'RepositoryField', cascade="all, delete-orphan")
1713 logs = relationship('UserLog')
1713 logs = relationship('UserLog')
1714 comments = relationship(
1714 comments = relationship(
1715 'ChangesetComment', cascade="all, delete-orphan")
1715 'ChangesetComment', cascade="all, delete-orphan")
1716 pull_requests_source = relationship(
1716 pull_requests_source = relationship(
1717 'PullRequest',
1717 'PullRequest',
1718 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1718 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1719 cascade="all, delete-orphan")
1719 cascade="all, delete-orphan")
1720 pull_requests_target = relationship(
1720 pull_requests_target = relationship(
1721 'PullRequest',
1721 'PullRequest',
1722 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1722 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1723 cascade="all, delete-orphan")
1723 cascade="all, delete-orphan")
1724 ui = relationship('RepoRhodeCodeUi', cascade="all")
1724 ui = relationship('RepoRhodeCodeUi', cascade="all")
1725 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1725 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1726 integrations = relationship('Integration', cascade="all, delete-orphan")
1726 integrations = relationship('Integration', cascade="all, delete-orphan")
1727
1727
1728 scoped_tokens = relationship('UserApiKeys', cascade="all")
1728 scoped_tokens = relationship('UserApiKeys', cascade="all")
1729
1729
1730 # no cascade, set NULL
1730 # no cascade, set NULL
1731 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1731 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1732
1732
1733 def __unicode__(self):
1733 def __unicode__(self):
1734 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1734 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1735 safe_unicode(self.repo_name))
1735 safe_unicode(self.repo_name))
1736
1736
1737 @hybrid_property
1737 @hybrid_property
1738 def description_safe(self):
1738 def description_safe(self):
1739 from rhodecode.lib import helpers as h
1739 from rhodecode.lib import helpers as h
1740 return h.escape(self.description)
1740 return h.escape(self.description)
1741
1741
1742 @hybrid_property
1742 @hybrid_property
1743 def landing_rev(self):
1743 def landing_rev(self):
1744 # always should return [rev_type, rev]
1744 # always should return [rev_type, rev]
1745 if self._landing_revision:
1745 if self._landing_revision:
1746 _rev_info = self._landing_revision.split(':')
1746 _rev_info = self._landing_revision.split(':')
1747 if len(_rev_info) < 2:
1747 if len(_rev_info) < 2:
1748 _rev_info.insert(0, 'rev')
1748 _rev_info.insert(0, 'rev')
1749 return [_rev_info[0], _rev_info[1]]
1749 return [_rev_info[0], _rev_info[1]]
1750 return [None, None]
1750 return [None, None]
1751
1751
1752 @landing_rev.setter
1752 @landing_rev.setter
1753 def landing_rev(self, val):
1753 def landing_rev(self, val):
1754 if ':' not in val:
1754 if ':' not in val:
1755 raise ValueError('value must be delimited with `:` and consist '
1755 raise ValueError('value must be delimited with `:` and consist '
1756 'of <rev_type>:<rev>, got %s instead' % val)
1756 'of <rev_type>:<rev>, got %s instead' % val)
1757 self._landing_revision = val
1757 self._landing_revision = val
1758
1758
1759 @hybrid_property
1759 @hybrid_property
1760 def locked(self):
1760 def locked(self):
1761 if self._locked:
1761 if self._locked:
1762 user_id, timelocked, reason = self._locked.split(':')
1762 user_id, timelocked, reason = self._locked.split(':')
1763 lock_values = int(user_id), timelocked, reason
1763 lock_values = int(user_id), timelocked, reason
1764 else:
1764 else:
1765 lock_values = [None, None, None]
1765 lock_values = [None, None, None]
1766 return lock_values
1766 return lock_values
1767
1767
1768 @locked.setter
1768 @locked.setter
1769 def locked(self, val):
1769 def locked(self, val):
1770 if val and isinstance(val, (list, tuple)):
1770 if val and isinstance(val, (list, tuple)):
1771 self._locked = ':'.join(map(str, val))
1771 self._locked = ':'.join(map(str, val))
1772 else:
1772 else:
1773 self._locked = None
1773 self._locked = None
1774
1774
1775 @hybrid_property
1775 @hybrid_property
1776 def changeset_cache(self):
1776 def changeset_cache(self):
1777 from rhodecode.lib.vcs.backends.base import EmptyCommit
1777 from rhodecode.lib.vcs.backends.base import EmptyCommit
1778 dummy = EmptyCommit().__json__()
1778 dummy = EmptyCommit().__json__()
1779 if not self._changeset_cache:
1779 if not self._changeset_cache:
1780 dummy['source_repo_id'] = self.repo_id
1780 dummy['source_repo_id'] = self.repo_id
1781 return json.loads(json.dumps(dummy))
1781 return json.loads(json.dumps(dummy))
1782
1782
1783 try:
1783 try:
1784 return json.loads(self._changeset_cache)
1784 return json.loads(self._changeset_cache)
1785 except TypeError:
1785 except TypeError:
1786 return dummy
1786 return dummy
1787 except Exception:
1787 except Exception:
1788 log.error(traceback.format_exc())
1788 log.error(traceback.format_exc())
1789 return dummy
1789 return dummy
1790
1790
1791 @changeset_cache.setter
1791 @changeset_cache.setter
1792 def changeset_cache(self, val):
1792 def changeset_cache(self, val):
1793 try:
1793 try:
1794 self._changeset_cache = json.dumps(val)
1794 self._changeset_cache = json.dumps(val)
1795 except Exception:
1795 except Exception:
1796 log.error(traceback.format_exc())
1796 log.error(traceback.format_exc())
1797
1797
1798 @hybrid_property
1798 @hybrid_property
1799 def repo_name(self):
1799 def repo_name(self):
1800 return self._repo_name
1800 return self._repo_name
1801
1801
1802 @repo_name.setter
1802 @repo_name.setter
1803 def repo_name(self, value):
1803 def repo_name(self, value):
1804 self._repo_name = value
1804 self._repo_name = value
1805 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1805 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1806
1806
1807 @classmethod
1807 @classmethod
1808 def normalize_repo_name(cls, repo_name):
1808 def normalize_repo_name(cls, repo_name):
1809 """
1809 """
1810 Normalizes os specific repo_name to the format internally stored inside
1810 Normalizes os specific repo_name to the format internally stored inside
1811 database using URL_SEP
1811 database using URL_SEP
1812
1812
1813 :param cls:
1813 :param cls:
1814 :param repo_name:
1814 :param repo_name:
1815 """
1815 """
1816 return cls.NAME_SEP.join(repo_name.split(os.sep))
1816 return cls.NAME_SEP.join(repo_name.split(os.sep))
1817
1817
1818 @classmethod
1818 @classmethod
1819 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1819 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1820 session = Session()
1820 session = Session()
1821 q = session.query(cls).filter(cls.repo_name == repo_name)
1821 q = session.query(cls).filter(cls.repo_name == repo_name)
1822
1822
1823 if cache:
1823 if cache:
1824 if identity_cache:
1824 if identity_cache:
1825 val = cls.identity_cache(session, 'repo_name', repo_name)
1825 val = cls.identity_cache(session, 'repo_name', repo_name)
1826 if val:
1826 if val:
1827 return val
1827 return val
1828 else:
1828 else:
1829 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1829 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1830 q = q.options(
1830 q = q.options(
1831 FromCache("sql_cache_short", cache_key))
1831 FromCache("sql_cache_short", cache_key))
1832
1832
1833 return q.scalar()
1833 return q.scalar()
1834
1834
1835 @classmethod
1835 @classmethod
1836 def get_by_id_or_repo_name(cls, repoid):
1836 def get_by_id_or_repo_name(cls, repoid):
1837 if isinstance(repoid, (int, long)):
1837 if isinstance(repoid, (int, long)):
1838 try:
1838 try:
1839 repo = cls.get(repoid)
1839 repo = cls.get(repoid)
1840 except ValueError:
1840 except ValueError:
1841 repo = None
1841 repo = None
1842 else:
1842 else:
1843 repo = cls.get_by_repo_name(repoid)
1843 repo = cls.get_by_repo_name(repoid)
1844 return repo
1844 return repo
1845
1845
1846 @classmethod
1846 @classmethod
1847 def get_by_full_path(cls, repo_full_path):
1847 def get_by_full_path(cls, repo_full_path):
1848 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1848 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1849 repo_name = cls.normalize_repo_name(repo_name)
1849 repo_name = cls.normalize_repo_name(repo_name)
1850 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1850 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1851
1851
1852 @classmethod
1852 @classmethod
1853 def get_repo_forks(cls, repo_id):
1853 def get_repo_forks(cls, repo_id):
1854 return cls.query().filter(Repository.fork_id == repo_id)
1854 return cls.query().filter(Repository.fork_id == repo_id)
1855
1855
1856 @classmethod
1856 @classmethod
1857 def base_path(cls):
1857 def base_path(cls):
1858 """
1858 """
1859 Returns base path when all repos are stored
1859 Returns base path when all repos are stored
1860
1860
1861 :param cls:
1861 :param cls:
1862 """
1862 """
1863 q = Session().query(RhodeCodeUi)\
1863 q = Session().query(RhodeCodeUi)\
1864 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1864 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1865 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1865 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1866 return q.one().ui_value
1866 return q.one().ui_value
1867
1867
1868 @classmethod
1868 @classmethod
1869 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1869 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1870 case_insensitive=True, archived=False):
1870 case_insensitive=True, archived=False):
1871 q = Repository.query()
1871 q = Repository.query()
1872
1872
1873 if not archived:
1873 if not archived:
1874 q = q.filter(Repository.archived.isnot(true()))
1874 q = q.filter(Repository.archived.isnot(true()))
1875
1875
1876 if not isinstance(user_id, Optional):
1876 if not isinstance(user_id, Optional):
1877 q = q.filter(Repository.user_id == user_id)
1877 q = q.filter(Repository.user_id == user_id)
1878
1878
1879 if not isinstance(group_id, Optional):
1879 if not isinstance(group_id, Optional):
1880 q = q.filter(Repository.group_id == group_id)
1880 q = q.filter(Repository.group_id == group_id)
1881
1881
1882 if case_insensitive:
1882 if case_insensitive:
1883 q = q.order_by(func.lower(Repository.repo_name))
1883 q = q.order_by(func.lower(Repository.repo_name))
1884 else:
1884 else:
1885 q = q.order_by(Repository.repo_name)
1885 q = q.order_by(Repository.repo_name)
1886
1886
1887 return q.all()
1887 return q.all()
1888
1888
1889 @property
1889 @property
1890 def repo_uid(self):
1890 def repo_uid(self):
1891 return '_{}'.format(self.repo_id)
1891 return '_{}'.format(self.repo_id)
1892
1892
1893 @property
1893 @property
1894 def forks(self):
1894 def forks(self):
1895 """
1895 """
1896 Return forks of this repo
1896 Return forks of this repo
1897 """
1897 """
1898 return Repository.get_repo_forks(self.repo_id)
1898 return Repository.get_repo_forks(self.repo_id)
1899
1899
1900 @property
1900 @property
1901 def parent(self):
1901 def parent(self):
1902 """
1902 """
1903 Returns fork parent
1903 Returns fork parent
1904 """
1904 """
1905 return self.fork
1905 return self.fork
1906
1906
1907 @property
1907 @property
1908 def just_name(self):
1908 def just_name(self):
1909 return self.repo_name.split(self.NAME_SEP)[-1]
1909 return self.repo_name.split(self.NAME_SEP)[-1]
1910
1910
1911 @property
1911 @property
1912 def groups_with_parents(self):
1912 def groups_with_parents(self):
1913 groups = []
1913 groups = []
1914 if self.group is None:
1914 if self.group is None:
1915 return groups
1915 return groups
1916
1916
1917 cur_gr = self.group
1917 cur_gr = self.group
1918 groups.insert(0, cur_gr)
1918 groups.insert(0, cur_gr)
1919 while 1:
1919 while 1:
1920 gr = getattr(cur_gr, 'parent_group', None)
1920 gr = getattr(cur_gr, 'parent_group', None)
1921 cur_gr = cur_gr.parent_group
1921 cur_gr = cur_gr.parent_group
1922 if gr is None:
1922 if gr is None:
1923 break
1923 break
1924 groups.insert(0, gr)
1924 groups.insert(0, gr)
1925
1925
1926 return groups
1926 return groups
1927
1927
1928 @property
1928 @property
1929 def groups_and_repo(self):
1929 def groups_and_repo(self):
1930 return self.groups_with_parents, self
1930 return self.groups_with_parents, self
1931
1931
1932 @LazyProperty
1932 @LazyProperty
1933 def repo_path(self):
1933 def repo_path(self):
1934 """
1934 """
1935 Returns base full path for that repository means where it actually
1935 Returns base full path for that repository means where it actually
1936 exists on a filesystem
1936 exists on a filesystem
1937 """
1937 """
1938 q = Session().query(RhodeCodeUi).filter(
1938 q = Session().query(RhodeCodeUi).filter(
1939 RhodeCodeUi.ui_key == self.NAME_SEP)
1939 RhodeCodeUi.ui_key == self.NAME_SEP)
1940 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1940 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1941 return q.one().ui_value
1941 return q.one().ui_value
1942
1942
1943 @property
1943 @property
1944 def repo_full_path(self):
1944 def repo_full_path(self):
1945 p = [self.repo_path]
1945 p = [self.repo_path]
1946 # we need to split the name by / since this is how we store the
1946 # we need to split the name by / since this is how we store the
1947 # names in the database, but that eventually needs to be converted
1947 # names in the database, but that eventually needs to be converted
1948 # into a valid system path
1948 # into a valid system path
1949 p += self.repo_name.split(self.NAME_SEP)
1949 p += self.repo_name.split(self.NAME_SEP)
1950 return os.path.join(*map(safe_unicode, p))
1950 return os.path.join(*map(safe_unicode, p))
1951
1951
1952 @property
1952 @property
1953 def cache_keys(self):
1953 def cache_keys(self):
1954 """
1954 """
1955 Returns associated cache keys for that repo
1955 Returns associated cache keys for that repo
1956 """
1956 """
1957 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1957 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1958 repo_id=self.repo_id)
1958 repo_id=self.repo_id)
1959 return CacheKey.query()\
1959 return CacheKey.query()\
1960 .filter(CacheKey.cache_args == invalidation_namespace)\
1960 .filter(CacheKey.cache_args == invalidation_namespace)\
1961 .order_by(CacheKey.cache_key)\
1961 .order_by(CacheKey.cache_key)\
1962 .all()
1962 .all()
1963
1963
1964 @property
1964 @property
1965 def cached_diffs_relative_dir(self):
1965 def cached_diffs_relative_dir(self):
1966 """
1966 """
1967 Return a relative to the repository store path of cached diffs
1967 Return a relative to the repository store path of cached diffs
1968 used for safe display for users, who shouldn't know the absolute store
1968 used for safe display for users, who shouldn't know the absolute store
1969 path
1969 path
1970 """
1970 """
1971 return os.path.join(
1971 return os.path.join(
1972 os.path.dirname(self.repo_name),
1972 os.path.dirname(self.repo_name),
1973 self.cached_diffs_dir.split(os.path.sep)[-1])
1973 self.cached_diffs_dir.split(os.path.sep)[-1])
1974
1974
1975 @property
1975 @property
1976 def cached_diffs_dir(self):
1976 def cached_diffs_dir(self):
1977 path = self.repo_full_path
1977 path = self.repo_full_path
1978 return os.path.join(
1978 return os.path.join(
1979 os.path.dirname(path),
1979 os.path.dirname(path),
1980 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1980 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1981
1981
1982 def cached_diffs(self):
1982 def cached_diffs(self):
1983 diff_cache_dir = self.cached_diffs_dir
1983 diff_cache_dir = self.cached_diffs_dir
1984 if os.path.isdir(diff_cache_dir):
1984 if os.path.isdir(diff_cache_dir):
1985 return os.listdir(diff_cache_dir)
1985 return os.listdir(diff_cache_dir)
1986 return []
1986 return []
1987
1987
1988 def shadow_repos(self):
1988 def shadow_repos(self):
1989 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1989 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1990 return [
1990 return [
1991 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1991 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1992 if x.startswith(shadow_repos_pattern)]
1992 if x.startswith(shadow_repos_pattern)]
1993
1993
1994 def get_new_name(self, repo_name):
1994 def get_new_name(self, repo_name):
1995 """
1995 """
1996 returns new full repository name based on assigned group and new new
1996 returns new full repository name based on assigned group and new new
1997
1997
1998 :param group_name:
1998 :param group_name:
1999 """
1999 """
2000 path_prefix = self.group.full_path_splitted if self.group else []
2000 path_prefix = self.group.full_path_splitted if self.group else []
2001 return self.NAME_SEP.join(path_prefix + [repo_name])
2001 return self.NAME_SEP.join(path_prefix + [repo_name])
2002
2002
2003 @property
2003 @property
2004 def _config(self):
2004 def _config(self):
2005 """
2005 """
2006 Returns db based config object.
2006 Returns db based config object.
2007 """
2007 """
2008 from rhodecode.lib.utils import make_db_config
2008 from rhodecode.lib.utils import make_db_config
2009 return make_db_config(clear_session=False, repo=self)
2009 return make_db_config(clear_session=False, repo=self)
2010
2010
2011 def permissions(self, with_admins=True, with_owner=True,
2011 def permissions(self, with_admins=True, with_owner=True,
2012 expand_from_user_groups=False):
2012 expand_from_user_groups=False):
2013 """
2013 """
2014 Permissions for repositories
2014 Permissions for repositories
2015 """
2015 """
2016 _admin_perm = 'repository.admin'
2016 _admin_perm = 'repository.admin'
2017
2017
2018 owner_row = []
2018 owner_row = []
2019 if with_owner:
2019 if with_owner:
2020 usr = AttributeDict(self.user.get_dict())
2020 usr = AttributeDict(self.user.get_dict())
2021 usr.owner_row = True
2021 usr.owner_row = True
2022 usr.permission = _admin_perm
2022 usr.permission = _admin_perm
2023 usr.permission_id = None
2023 usr.permission_id = None
2024 owner_row.append(usr)
2024 owner_row.append(usr)
2025
2025
2026 super_admin_ids = []
2026 super_admin_ids = []
2027 super_admin_rows = []
2027 super_admin_rows = []
2028 if with_admins:
2028 if with_admins:
2029 for usr in User.get_all_super_admins():
2029 for usr in User.get_all_super_admins():
2030 super_admin_ids.append(usr.user_id)
2030 super_admin_ids.append(usr.user_id)
2031 # if this admin is also owner, don't double the record
2031 # if this admin is also owner, don't double the record
2032 if usr.user_id == owner_row[0].user_id:
2032 if usr.user_id == owner_row[0].user_id:
2033 owner_row[0].admin_row = True
2033 owner_row[0].admin_row = True
2034 else:
2034 else:
2035 usr = AttributeDict(usr.get_dict())
2035 usr = AttributeDict(usr.get_dict())
2036 usr.admin_row = True
2036 usr.admin_row = True
2037 usr.permission = _admin_perm
2037 usr.permission = _admin_perm
2038 usr.permission_id = None
2038 usr.permission_id = None
2039 super_admin_rows.append(usr)
2039 super_admin_rows.append(usr)
2040
2040
2041 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2041 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2042 q = q.options(joinedload(UserRepoToPerm.repository),
2042 q = q.options(joinedload(UserRepoToPerm.repository),
2043 joinedload(UserRepoToPerm.user),
2043 joinedload(UserRepoToPerm.user),
2044 joinedload(UserRepoToPerm.permission),)
2044 joinedload(UserRepoToPerm.permission),)
2045
2045
2046 # get owners and admins and permissions. We do a trick of re-writing
2046 # get owners and admins and permissions. We do a trick of re-writing
2047 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2047 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2048 # has a global reference and changing one object propagates to all
2048 # has a global reference and changing one object propagates to all
2049 # others. This means if admin is also an owner admin_row that change
2049 # others. This means if admin is also an owner admin_row that change
2050 # would propagate to both objects
2050 # would propagate to both objects
2051 perm_rows = []
2051 perm_rows = []
2052 for _usr in q.all():
2052 for _usr in q.all():
2053 usr = AttributeDict(_usr.user.get_dict())
2053 usr = AttributeDict(_usr.user.get_dict())
2054 # if this user is also owner/admin, mark as duplicate record
2054 # if this user is also owner/admin, mark as duplicate record
2055 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2055 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2056 usr.duplicate_perm = True
2056 usr.duplicate_perm = True
2057 # also check if this permission is maybe used by branch_permissions
2057 # also check if this permission is maybe used by branch_permissions
2058 if _usr.branch_perm_entry:
2058 if _usr.branch_perm_entry:
2059 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2059 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2060
2060
2061 usr.permission = _usr.permission.permission_name
2061 usr.permission = _usr.permission.permission_name
2062 usr.permission_id = _usr.repo_to_perm_id
2062 usr.permission_id = _usr.repo_to_perm_id
2063 perm_rows.append(usr)
2063 perm_rows.append(usr)
2064
2064
2065 # filter the perm rows by 'default' first and then sort them by
2065 # filter the perm rows by 'default' first and then sort them by
2066 # admin,write,read,none permissions sorted again alphabetically in
2066 # admin,write,read,none permissions sorted again alphabetically in
2067 # each group
2067 # each group
2068 perm_rows = sorted(perm_rows, key=display_user_sort)
2068 perm_rows = sorted(perm_rows, key=display_user_sort)
2069
2069
2070 user_groups_rows = []
2070 user_groups_rows = []
2071 if expand_from_user_groups:
2071 if expand_from_user_groups:
2072 for ug in self.permission_user_groups(with_members=True):
2072 for ug in self.permission_user_groups(with_members=True):
2073 for user_data in ug.members:
2073 for user_data in ug.members:
2074 user_groups_rows.append(user_data)
2074 user_groups_rows.append(user_data)
2075
2075
2076 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2076 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2077
2077
2078 def permission_user_groups(self, with_members=True):
2078 def permission_user_groups(self, with_members=True):
2079 q = UserGroupRepoToPerm.query()\
2079 q = UserGroupRepoToPerm.query()\
2080 .filter(UserGroupRepoToPerm.repository == self)
2080 .filter(UserGroupRepoToPerm.repository == self)
2081 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2081 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2082 joinedload(UserGroupRepoToPerm.users_group),
2082 joinedload(UserGroupRepoToPerm.users_group),
2083 joinedload(UserGroupRepoToPerm.permission),)
2083 joinedload(UserGroupRepoToPerm.permission),)
2084
2084
2085 perm_rows = []
2085 perm_rows = []
2086 for _user_group in q.all():
2086 for _user_group in q.all():
2087 entry = AttributeDict(_user_group.users_group.get_dict())
2087 entry = AttributeDict(_user_group.users_group.get_dict())
2088 entry.permission = _user_group.permission.permission_name
2088 entry.permission = _user_group.permission.permission_name
2089 if with_members:
2089 if with_members:
2090 entry.members = [x.user.get_dict()
2090 entry.members = [x.user.get_dict()
2091 for x in _user_group.users_group.members]
2091 for x in _user_group.users_group.members]
2092 perm_rows.append(entry)
2092 perm_rows.append(entry)
2093
2093
2094 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2094 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2095 return perm_rows
2095 return perm_rows
2096
2096
2097 def get_api_data(self, include_secrets=False):
2097 def get_api_data(self, include_secrets=False):
2098 """
2098 """
2099 Common function for generating repo api data
2099 Common function for generating repo api data
2100
2100
2101 :param include_secrets: See :meth:`User.get_api_data`.
2101 :param include_secrets: See :meth:`User.get_api_data`.
2102
2102
2103 """
2103 """
2104 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2104 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2105 # move this methods on models level.
2105 # move this methods on models level.
2106 from rhodecode.model.settings import SettingsModel
2106 from rhodecode.model.settings import SettingsModel
2107 from rhodecode.model.repo import RepoModel
2107 from rhodecode.model.repo import RepoModel
2108
2108
2109 repo = self
2109 repo = self
2110 _user_id, _time, _reason = self.locked
2110 _user_id, _time, _reason = self.locked
2111
2111
2112 data = {
2112 data = {
2113 'repo_id': repo.repo_id,
2113 'repo_id': repo.repo_id,
2114 'repo_name': repo.repo_name,
2114 'repo_name': repo.repo_name,
2115 'repo_type': repo.repo_type,
2115 'repo_type': repo.repo_type,
2116 'clone_uri': repo.clone_uri or '',
2116 'clone_uri': repo.clone_uri or '',
2117 'push_uri': repo.push_uri or '',
2117 'push_uri': repo.push_uri or '',
2118 'url': RepoModel().get_url(self),
2118 'url': RepoModel().get_url(self),
2119 'private': repo.private,
2119 'private': repo.private,
2120 'created_on': repo.created_on,
2120 'created_on': repo.created_on,
2121 'description': repo.description_safe,
2121 'description': repo.description_safe,
2122 'landing_rev': repo.landing_rev,
2122 'landing_rev': repo.landing_rev,
2123 'owner': repo.user.username,
2123 'owner': repo.user.username,
2124 'fork_of': repo.fork.repo_name if repo.fork else None,
2124 'fork_of': repo.fork.repo_name if repo.fork else None,
2125 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2125 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2126 'enable_statistics': repo.enable_statistics,
2126 'enable_statistics': repo.enable_statistics,
2127 'enable_locking': repo.enable_locking,
2127 'enable_locking': repo.enable_locking,
2128 'enable_downloads': repo.enable_downloads,
2128 'enable_downloads': repo.enable_downloads,
2129 'last_changeset': repo.changeset_cache,
2129 'last_changeset': repo.changeset_cache,
2130 'locked_by': User.get(_user_id).get_api_data(
2130 'locked_by': User.get(_user_id).get_api_data(
2131 include_secrets=include_secrets) if _user_id else None,
2131 include_secrets=include_secrets) if _user_id else None,
2132 'locked_date': time_to_datetime(_time) if _time else None,
2132 'locked_date': time_to_datetime(_time) if _time else None,
2133 'lock_reason': _reason if _reason else None,
2133 'lock_reason': _reason if _reason else None,
2134 }
2134 }
2135
2135
2136 # TODO: mikhail: should be per-repo settings here
2136 # TODO: mikhail: should be per-repo settings here
2137 rc_config = SettingsModel().get_all_settings()
2137 rc_config = SettingsModel().get_all_settings()
2138 repository_fields = str2bool(
2138 repository_fields = str2bool(
2139 rc_config.get('rhodecode_repository_fields'))
2139 rc_config.get('rhodecode_repository_fields'))
2140 if repository_fields:
2140 if repository_fields:
2141 for f in self.extra_fields:
2141 for f in self.extra_fields:
2142 data[f.field_key_prefixed] = f.field_value
2142 data[f.field_key_prefixed] = f.field_value
2143
2143
2144 return data
2144 return data
2145
2145
2146 @classmethod
2146 @classmethod
2147 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2147 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2148 if not lock_time:
2148 if not lock_time:
2149 lock_time = time.time()
2149 lock_time = time.time()
2150 if not lock_reason:
2150 if not lock_reason:
2151 lock_reason = cls.LOCK_AUTOMATIC
2151 lock_reason = cls.LOCK_AUTOMATIC
2152 repo.locked = [user_id, lock_time, lock_reason]
2152 repo.locked = [user_id, lock_time, lock_reason]
2153 Session().add(repo)
2153 Session().add(repo)
2154 Session().commit()
2154 Session().commit()
2155
2155
2156 @classmethod
2156 @classmethod
2157 def unlock(cls, repo):
2157 def unlock(cls, repo):
2158 repo.locked = None
2158 repo.locked = None
2159 Session().add(repo)
2159 Session().add(repo)
2160 Session().commit()
2160 Session().commit()
2161
2161
2162 @classmethod
2162 @classmethod
2163 def getlock(cls, repo):
2163 def getlock(cls, repo):
2164 return repo.locked
2164 return repo.locked
2165
2165
2166 def is_user_lock(self, user_id):
2166 def is_user_lock(self, user_id):
2167 if self.lock[0]:
2167 if self.lock[0]:
2168 lock_user_id = safe_int(self.lock[0])
2168 lock_user_id = safe_int(self.lock[0])
2169 user_id = safe_int(user_id)
2169 user_id = safe_int(user_id)
2170 # both are ints, and they are equal
2170 # both are ints, and they are equal
2171 return all([lock_user_id, user_id]) and lock_user_id == user_id
2171 return all([lock_user_id, user_id]) and lock_user_id == user_id
2172
2172
2173 return False
2173 return False
2174
2174
2175 def get_locking_state(self, action, user_id, only_when_enabled=True):
2175 def get_locking_state(self, action, user_id, only_when_enabled=True):
2176 """
2176 """
2177 Checks locking on this repository, if locking is enabled and lock is
2177 Checks locking on this repository, if locking is enabled and lock is
2178 present returns a tuple of make_lock, locked, locked_by.
2178 present returns a tuple of make_lock, locked, locked_by.
2179 make_lock can have 3 states None (do nothing) True, make lock
2179 make_lock can have 3 states None (do nothing) True, make lock
2180 False release lock, This value is later propagated to hooks, which
2180 False release lock, This value is later propagated to hooks, which
2181 do the locking. Think about this as signals passed to hooks what to do.
2181 do the locking. Think about this as signals passed to hooks what to do.
2182
2182
2183 """
2183 """
2184 # TODO: johbo: This is part of the business logic and should be moved
2184 # TODO: johbo: This is part of the business logic and should be moved
2185 # into the RepositoryModel.
2185 # into the RepositoryModel.
2186
2186
2187 if action not in ('push', 'pull'):
2187 if action not in ('push', 'pull'):
2188 raise ValueError("Invalid action value: %s" % repr(action))
2188 raise ValueError("Invalid action value: %s" % repr(action))
2189
2189
2190 # defines if locked error should be thrown to user
2190 # defines if locked error should be thrown to user
2191 currently_locked = False
2191 currently_locked = False
2192 # defines if new lock should be made, tri-state
2192 # defines if new lock should be made, tri-state
2193 make_lock = None
2193 make_lock = None
2194 repo = self
2194 repo = self
2195 user = User.get(user_id)
2195 user = User.get(user_id)
2196
2196
2197 lock_info = repo.locked
2197 lock_info = repo.locked
2198
2198
2199 if repo and (repo.enable_locking or not only_when_enabled):
2199 if repo and (repo.enable_locking or not only_when_enabled):
2200 if action == 'push':
2200 if action == 'push':
2201 # check if it's already locked !, if it is compare users
2201 # check if it's already locked !, if it is compare users
2202 locked_by_user_id = lock_info[0]
2202 locked_by_user_id = lock_info[0]
2203 if user.user_id == locked_by_user_id:
2203 if user.user_id == locked_by_user_id:
2204 log.debug(
2204 log.debug(
2205 'Got `push` action from user %s, now unlocking', user)
2205 'Got `push` action from user %s, now unlocking', user)
2206 # unlock if we have push from user who locked
2206 # unlock if we have push from user who locked
2207 make_lock = False
2207 make_lock = False
2208 else:
2208 else:
2209 # we're not the same user who locked, ban with
2209 # we're not the same user who locked, ban with
2210 # code defined in settings (default is 423 HTTP Locked) !
2210 # code defined in settings (default is 423 HTTP Locked) !
2211 log.debug('Repo %s is currently locked by %s', repo, user)
2211 log.debug('Repo %s is currently locked by %s', repo, user)
2212 currently_locked = True
2212 currently_locked = True
2213 elif action == 'pull':
2213 elif action == 'pull':
2214 # [0] user [1] date
2214 # [0] user [1] date
2215 if lock_info[0] and lock_info[1]:
2215 if lock_info[0] and lock_info[1]:
2216 log.debug('Repo %s is currently locked by %s', repo, user)
2216 log.debug('Repo %s is currently locked by %s', repo, user)
2217 currently_locked = True
2217 currently_locked = True
2218 else:
2218 else:
2219 log.debug('Setting lock on repo %s by %s', repo, user)
2219 log.debug('Setting lock on repo %s by %s', repo, user)
2220 make_lock = True
2220 make_lock = True
2221
2221
2222 else:
2222 else:
2223 log.debug('Repository %s do not have locking enabled', repo)
2223 log.debug('Repository %s do not have locking enabled', repo)
2224
2224
2225 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2225 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2226 make_lock, currently_locked, lock_info)
2226 make_lock, currently_locked, lock_info)
2227
2227
2228 from rhodecode.lib.auth import HasRepoPermissionAny
2228 from rhodecode.lib.auth import HasRepoPermissionAny
2229 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2229 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2230 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2230 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2231 # if we don't have at least write permission we cannot make a lock
2231 # if we don't have at least write permission we cannot make a lock
2232 log.debug('lock state reset back to FALSE due to lack '
2232 log.debug('lock state reset back to FALSE due to lack '
2233 'of at least read permission')
2233 'of at least read permission')
2234 make_lock = False
2234 make_lock = False
2235
2235
2236 return make_lock, currently_locked, lock_info
2236 return make_lock, currently_locked, lock_info
2237
2237
2238 @property
2238 @property
2239 def last_commit_cache_update_diff(self):
2239 def last_commit_cache_update_diff(self):
2240 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2240 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2241
2241
2242 @property
2242 @property
2243 def last_commit_change(self):
2243 def last_commit_change(self):
2244 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2244 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2245 empty_date = datetime.datetime.fromtimestamp(0)
2245 empty_date = datetime.datetime.fromtimestamp(0)
2246 date_latest = self.changeset_cache.get('date', empty_date)
2246 date_latest = self.changeset_cache.get('date', empty_date)
2247 try:
2247 try:
2248 return parse_datetime(date_latest)
2248 return parse_datetime(date_latest)
2249 except Exception:
2249 except Exception:
2250 return empty_date
2250 return empty_date
2251
2251
2252 @property
2252 @property
2253 def last_db_change(self):
2253 def last_db_change(self):
2254 return self.updated_on
2254 return self.updated_on
2255
2255
2256 @property
2256 @property
2257 def clone_uri_hidden(self):
2257 def clone_uri_hidden(self):
2258 clone_uri = self.clone_uri
2258 clone_uri = self.clone_uri
2259 if clone_uri:
2259 if clone_uri:
2260 import urlobject
2260 import urlobject
2261 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2261 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2262 if url_obj.password:
2262 if url_obj.password:
2263 clone_uri = url_obj.with_password('*****')
2263 clone_uri = url_obj.with_password('*****')
2264 return clone_uri
2264 return clone_uri
2265
2265
2266 @property
2266 @property
2267 def push_uri_hidden(self):
2267 def push_uri_hidden(self):
2268 push_uri = self.push_uri
2268 push_uri = self.push_uri
2269 if push_uri:
2269 if push_uri:
2270 import urlobject
2270 import urlobject
2271 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2271 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2272 if url_obj.password:
2272 if url_obj.password:
2273 push_uri = url_obj.with_password('*****')
2273 push_uri = url_obj.with_password('*****')
2274 return push_uri
2274 return push_uri
2275
2275
2276 def clone_url(self, **override):
2276 def clone_url(self, **override):
2277 from rhodecode.model.settings import SettingsModel
2277 from rhodecode.model.settings import SettingsModel
2278
2278
2279 uri_tmpl = None
2279 uri_tmpl = None
2280 if 'with_id' in override:
2280 if 'with_id' in override:
2281 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2281 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2282 del override['with_id']
2282 del override['with_id']
2283
2283
2284 if 'uri_tmpl' in override:
2284 if 'uri_tmpl' in override:
2285 uri_tmpl = override['uri_tmpl']
2285 uri_tmpl = override['uri_tmpl']
2286 del override['uri_tmpl']
2286 del override['uri_tmpl']
2287
2287
2288 ssh = False
2288 ssh = False
2289 if 'ssh' in override:
2289 if 'ssh' in override:
2290 ssh = True
2290 ssh = True
2291 del override['ssh']
2291 del override['ssh']
2292
2292
2293 # we didn't override our tmpl from **overrides
2293 # we didn't override our tmpl from **overrides
2294 request = get_current_request()
2294 request = get_current_request()
2295 if not uri_tmpl:
2295 if not uri_tmpl:
2296 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2296 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2297 rc_config = request.call_context.rc_config
2297 rc_config = request.call_context.rc_config
2298 else:
2298 else:
2299 rc_config = SettingsModel().get_all_settings(cache=True)
2299 rc_config = SettingsModel().get_all_settings(cache=True)
2300
2300 if ssh:
2301 if ssh:
2301 uri_tmpl = rc_config.get(
2302 uri_tmpl = rc_config.get(
2302 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2303 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2304
2303 else:
2305 else:
2304 uri_tmpl = rc_config.get(
2306 uri_tmpl = rc_config.get(
2305 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2307 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2306
2308
2307 return get_clone_url(request=request,
2309 return get_clone_url(request=request,
2308 uri_tmpl=uri_tmpl,
2310 uri_tmpl=uri_tmpl,
2309 repo_name=self.repo_name,
2311 repo_name=self.repo_name,
2310 repo_id=self.repo_id, **override)
2312 repo_id=self.repo_id,
2313 repo_type=self.repo_type,
2314 **override)
2311
2315
2312 def set_state(self, state):
2316 def set_state(self, state):
2313 self.repo_state = state
2317 self.repo_state = state
2314 Session().add(self)
2318 Session().add(self)
2315 #==========================================================================
2319 #==========================================================================
2316 # SCM PROPERTIES
2320 # SCM PROPERTIES
2317 #==========================================================================
2321 #==========================================================================
2318
2322
2319 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2323 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2320 return get_commit_safe(
2324 return get_commit_safe(
2321 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2325 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2322
2326
2323 def get_changeset(self, rev=None, pre_load=None):
2327 def get_changeset(self, rev=None, pre_load=None):
2324 warnings.warn("Use get_commit", DeprecationWarning)
2328 warnings.warn("Use get_commit", DeprecationWarning)
2325 commit_id = None
2329 commit_id = None
2326 commit_idx = None
2330 commit_idx = None
2327 if isinstance(rev, compat.string_types):
2331 if isinstance(rev, compat.string_types):
2328 commit_id = rev
2332 commit_id = rev
2329 else:
2333 else:
2330 commit_idx = rev
2334 commit_idx = rev
2331 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2335 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2332 pre_load=pre_load)
2336 pre_load=pre_load)
2333
2337
2334 def get_landing_commit(self):
2338 def get_landing_commit(self):
2335 """
2339 """
2336 Returns landing commit, or if that doesn't exist returns the tip
2340 Returns landing commit, or if that doesn't exist returns the tip
2337 """
2341 """
2338 _rev_type, _rev = self.landing_rev
2342 _rev_type, _rev = self.landing_rev
2339 commit = self.get_commit(_rev)
2343 commit = self.get_commit(_rev)
2340 if isinstance(commit, EmptyCommit):
2344 if isinstance(commit, EmptyCommit):
2341 return self.get_commit()
2345 return self.get_commit()
2342 return commit
2346 return commit
2343
2347
2344 def flush_commit_cache(self):
2348 def flush_commit_cache(self):
2345 self.update_commit_cache(cs_cache={'raw_id':'0'})
2349 self.update_commit_cache(cs_cache={'raw_id':'0'})
2346 self.update_commit_cache()
2350 self.update_commit_cache()
2347
2351
2348 def update_commit_cache(self, cs_cache=None, config=None):
2352 def update_commit_cache(self, cs_cache=None, config=None):
2349 """
2353 """
2350 Update cache of last commit for repository, keys should be::
2354 Update cache of last commit for repository, keys should be::
2351
2355
2352 source_repo_id
2356 source_repo_id
2353 short_id
2357 short_id
2354 raw_id
2358 raw_id
2355 revision
2359 revision
2356 parents
2360 parents
2357 message
2361 message
2358 date
2362 date
2359 author
2363 author
2360 updated_on
2364 updated_on
2361
2365
2362 """
2366 """
2363 from rhodecode.lib.vcs.backends.base import BaseChangeset
2367 from rhodecode.lib.vcs.backends.base import BaseChangeset
2364 if cs_cache is None:
2368 if cs_cache is None:
2365 # use no-cache version here
2369 # use no-cache version here
2366 scm_repo = self.scm_instance(cache=False, config=config)
2370 scm_repo = self.scm_instance(cache=False, config=config)
2367
2371
2368 empty = scm_repo is None or scm_repo.is_empty()
2372 empty = scm_repo is None or scm_repo.is_empty()
2369 if not empty:
2373 if not empty:
2370 cs_cache = scm_repo.get_commit(
2374 cs_cache = scm_repo.get_commit(
2371 pre_load=["author", "date", "message", "parents", "branch"])
2375 pre_load=["author", "date", "message", "parents", "branch"])
2372 else:
2376 else:
2373 cs_cache = EmptyCommit()
2377 cs_cache = EmptyCommit()
2374
2378
2375 if isinstance(cs_cache, BaseChangeset):
2379 if isinstance(cs_cache, BaseChangeset):
2376 cs_cache = cs_cache.__json__()
2380 cs_cache = cs_cache.__json__()
2377
2381
2378 def is_outdated(new_cs_cache):
2382 def is_outdated(new_cs_cache):
2379 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2383 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2380 new_cs_cache['revision'] != self.changeset_cache['revision']):
2384 new_cs_cache['revision'] != self.changeset_cache['revision']):
2381 return True
2385 return True
2382 return False
2386 return False
2383
2387
2384 # check if we have maybe already latest cached revision
2388 # check if we have maybe already latest cached revision
2385 if is_outdated(cs_cache) or not self.changeset_cache:
2389 if is_outdated(cs_cache) or not self.changeset_cache:
2386 _default = datetime.datetime.utcnow()
2390 _default = datetime.datetime.utcnow()
2387 last_change = cs_cache.get('date') or _default
2391 last_change = cs_cache.get('date') or _default
2388 # we check if last update is newer than the new value
2392 # we check if last update is newer than the new value
2389 # if yes, we use the current timestamp instead. Imagine you get
2393 # if yes, we use the current timestamp instead. Imagine you get
2390 # old commit pushed 1y ago, we'd set last update 1y to ago.
2394 # old commit pushed 1y ago, we'd set last update 1y to ago.
2391 last_change_timestamp = datetime_to_time(last_change)
2395 last_change_timestamp = datetime_to_time(last_change)
2392 current_timestamp = datetime_to_time(last_change)
2396 current_timestamp = datetime_to_time(last_change)
2393 if last_change_timestamp > current_timestamp:
2397 if last_change_timestamp > current_timestamp:
2394 cs_cache['date'] = _default
2398 cs_cache['date'] = _default
2395
2399
2396 cs_cache['updated_on'] = time.time()
2400 cs_cache['updated_on'] = time.time()
2397 self.changeset_cache = cs_cache
2401 self.changeset_cache = cs_cache
2398 self.updated_on = last_change
2402 self.updated_on = last_change
2399 Session().add(self)
2403 Session().add(self)
2400 Session().commit()
2404 Session().commit()
2401
2405
2402 log.debug('updated repo `%s` with new commit cache %s',
2406 log.debug('updated repo `%s` with new commit cache %s',
2403 self.repo_name, cs_cache)
2407 self.repo_name, cs_cache)
2404 else:
2408 else:
2405 cs_cache = self.changeset_cache
2409 cs_cache = self.changeset_cache
2406 cs_cache['updated_on'] = time.time()
2410 cs_cache['updated_on'] = time.time()
2407 self.changeset_cache = cs_cache
2411 self.changeset_cache = cs_cache
2408 Session().add(self)
2412 Session().add(self)
2409 Session().commit()
2413 Session().commit()
2410
2414
2411 log.debug('Skipping update_commit_cache for repo:`%s` '
2415 log.debug('Skipping update_commit_cache for repo:`%s` '
2412 'commit already with latest changes', self.repo_name)
2416 'commit already with latest changes', self.repo_name)
2413
2417
2414 @property
2418 @property
2415 def tip(self):
2419 def tip(self):
2416 return self.get_commit('tip')
2420 return self.get_commit('tip')
2417
2421
2418 @property
2422 @property
2419 def author(self):
2423 def author(self):
2420 return self.tip.author
2424 return self.tip.author
2421
2425
2422 @property
2426 @property
2423 def last_change(self):
2427 def last_change(self):
2424 return self.scm_instance().last_change
2428 return self.scm_instance().last_change
2425
2429
2426 def get_comments(self, revisions=None):
2430 def get_comments(self, revisions=None):
2427 """
2431 """
2428 Returns comments for this repository grouped by revisions
2432 Returns comments for this repository grouped by revisions
2429
2433
2430 :param revisions: filter query by revisions only
2434 :param revisions: filter query by revisions only
2431 """
2435 """
2432 cmts = ChangesetComment.query()\
2436 cmts = ChangesetComment.query()\
2433 .filter(ChangesetComment.repo == self)
2437 .filter(ChangesetComment.repo == self)
2434 if revisions:
2438 if revisions:
2435 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2439 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2436 grouped = collections.defaultdict(list)
2440 grouped = collections.defaultdict(list)
2437 for cmt in cmts.all():
2441 for cmt in cmts.all():
2438 grouped[cmt.revision].append(cmt)
2442 grouped[cmt.revision].append(cmt)
2439 return grouped
2443 return grouped
2440
2444
2441 def statuses(self, revisions=None):
2445 def statuses(self, revisions=None):
2442 """
2446 """
2443 Returns statuses for this repository
2447 Returns statuses for this repository
2444
2448
2445 :param revisions: list of revisions to get statuses for
2449 :param revisions: list of revisions to get statuses for
2446 """
2450 """
2447 statuses = ChangesetStatus.query()\
2451 statuses = ChangesetStatus.query()\
2448 .filter(ChangesetStatus.repo == self)\
2452 .filter(ChangesetStatus.repo == self)\
2449 .filter(ChangesetStatus.version == 0)
2453 .filter(ChangesetStatus.version == 0)
2450
2454
2451 if revisions:
2455 if revisions:
2452 # Try doing the filtering in chunks to avoid hitting limits
2456 # Try doing the filtering in chunks to avoid hitting limits
2453 size = 500
2457 size = 500
2454 status_results = []
2458 status_results = []
2455 for chunk in xrange(0, len(revisions), size):
2459 for chunk in xrange(0, len(revisions), size):
2456 status_results += statuses.filter(
2460 status_results += statuses.filter(
2457 ChangesetStatus.revision.in_(
2461 ChangesetStatus.revision.in_(
2458 revisions[chunk: chunk+size])
2462 revisions[chunk: chunk+size])
2459 ).all()
2463 ).all()
2460 else:
2464 else:
2461 status_results = statuses.all()
2465 status_results = statuses.all()
2462
2466
2463 grouped = {}
2467 grouped = {}
2464
2468
2465 # maybe we have open new pullrequest without a status?
2469 # maybe we have open new pullrequest without a status?
2466 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2470 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2467 status_lbl = ChangesetStatus.get_status_lbl(stat)
2471 status_lbl = ChangesetStatus.get_status_lbl(stat)
2468 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2472 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2469 for rev in pr.revisions:
2473 for rev in pr.revisions:
2470 pr_id = pr.pull_request_id
2474 pr_id = pr.pull_request_id
2471 pr_repo = pr.target_repo.repo_name
2475 pr_repo = pr.target_repo.repo_name
2472 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2476 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2473
2477
2474 for stat in status_results:
2478 for stat in status_results:
2475 pr_id = pr_repo = None
2479 pr_id = pr_repo = None
2476 if stat.pull_request:
2480 if stat.pull_request:
2477 pr_id = stat.pull_request.pull_request_id
2481 pr_id = stat.pull_request.pull_request_id
2478 pr_repo = stat.pull_request.target_repo.repo_name
2482 pr_repo = stat.pull_request.target_repo.repo_name
2479 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2483 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2480 pr_id, pr_repo]
2484 pr_id, pr_repo]
2481 return grouped
2485 return grouped
2482
2486
2483 # ==========================================================================
2487 # ==========================================================================
2484 # SCM CACHE INSTANCE
2488 # SCM CACHE INSTANCE
2485 # ==========================================================================
2489 # ==========================================================================
2486
2490
2487 def scm_instance(self, **kwargs):
2491 def scm_instance(self, **kwargs):
2488 import rhodecode
2492 import rhodecode
2489
2493
2490 # Passing a config will not hit the cache currently only used
2494 # Passing a config will not hit the cache currently only used
2491 # for repo2dbmapper
2495 # for repo2dbmapper
2492 config = kwargs.pop('config', None)
2496 config = kwargs.pop('config', None)
2493 cache = kwargs.pop('cache', None)
2497 cache = kwargs.pop('cache', None)
2494 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2498 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2495 if vcs_full_cache is not None:
2499 if vcs_full_cache is not None:
2496 # allows override global config
2500 # allows override global config
2497 full_cache = vcs_full_cache
2501 full_cache = vcs_full_cache
2498 else:
2502 else:
2499 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2503 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2500 # if cache is NOT defined use default global, else we have a full
2504 # if cache is NOT defined use default global, else we have a full
2501 # control over cache behaviour
2505 # control over cache behaviour
2502 if cache is None and full_cache and not config:
2506 if cache is None and full_cache and not config:
2503 log.debug('Initializing pure cached instance for %s', self.repo_path)
2507 log.debug('Initializing pure cached instance for %s', self.repo_path)
2504 return self._get_instance_cached()
2508 return self._get_instance_cached()
2505
2509
2506 # cache here is sent to the "vcs server"
2510 # cache here is sent to the "vcs server"
2507 return self._get_instance(cache=bool(cache), config=config)
2511 return self._get_instance(cache=bool(cache), config=config)
2508
2512
2509 def _get_instance_cached(self):
2513 def _get_instance_cached(self):
2510 from rhodecode.lib import rc_cache
2514 from rhodecode.lib import rc_cache
2511
2515
2512 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2516 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2513 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2517 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2514 repo_id=self.repo_id)
2518 repo_id=self.repo_id)
2515 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2519 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2516
2520
2517 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2521 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2518 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2522 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2519 return self._get_instance(repo_state_uid=_cache_state_uid)
2523 return self._get_instance(repo_state_uid=_cache_state_uid)
2520
2524
2521 # we must use thread scoped cache here,
2525 # we must use thread scoped cache here,
2522 # because each thread of gevent needs it's own not shared connection and cache
2526 # because each thread of gevent needs it's own not shared connection and cache
2523 # we also alter `args` so the cache key is individual for every green thread.
2527 # we also alter `args` so the cache key is individual for every green thread.
2524 inv_context_manager = rc_cache.InvalidationContext(
2528 inv_context_manager = rc_cache.InvalidationContext(
2525 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2529 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2526 thread_scoped=True)
2530 thread_scoped=True)
2527 with inv_context_manager as invalidation_context:
2531 with inv_context_manager as invalidation_context:
2528 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2532 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2529 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2533 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2530
2534
2531 # re-compute and store cache if we get invalidate signal
2535 # re-compute and store cache if we get invalidate signal
2532 if invalidation_context.should_invalidate():
2536 if invalidation_context.should_invalidate():
2533 instance = get_instance_cached.refresh(*args)
2537 instance = get_instance_cached.refresh(*args)
2534 else:
2538 else:
2535 instance = get_instance_cached(*args)
2539 instance = get_instance_cached(*args)
2536
2540
2537 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2541 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2538 return instance
2542 return instance
2539
2543
2540 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2544 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2541 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2545 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2542 self.repo_type, self.repo_path, cache)
2546 self.repo_type, self.repo_path, cache)
2543 config = config or self._config
2547 config = config or self._config
2544 custom_wire = {
2548 custom_wire = {
2545 'cache': cache, # controls the vcs.remote cache
2549 'cache': cache, # controls the vcs.remote cache
2546 'repo_state_uid': repo_state_uid
2550 'repo_state_uid': repo_state_uid
2547 }
2551 }
2548 repo = get_vcs_instance(
2552 repo = get_vcs_instance(
2549 repo_path=safe_str(self.repo_full_path),
2553 repo_path=safe_str(self.repo_full_path),
2550 config=config,
2554 config=config,
2551 with_wire=custom_wire,
2555 with_wire=custom_wire,
2552 create=False,
2556 create=False,
2553 _vcs_alias=self.repo_type)
2557 _vcs_alias=self.repo_type)
2554 if repo is not None:
2558 if repo is not None:
2555 repo.count() # cache rebuild
2559 repo.count() # cache rebuild
2556 return repo
2560 return repo
2557
2561
2558 def get_shadow_repository_path(self, workspace_id):
2562 def get_shadow_repository_path(self, workspace_id):
2559 from rhodecode.lib.vcs.backends.base import BaseRepository
2563 from rhodecode.lib.vcs.backends.base import BaseRepository
2560 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2564 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2561 self.repo_full_path, self.repo_id, workspace_id)
2565 self.repo_full_path, self.repo_id, workspace_id)
2562 return shadow_repo_path
2566 return shadow_repo_path
2563
2567
2564 def __json__(self):
2568 def __json__(self):
2565 return {'landing_rev': self.landing_rev}
2569 return {'landing_rev': self.landing_rev}
2566
2570
2567 def get_dict(self):
2571 def get_dict(self):
2568
2572
2569 # Since we transformed `repo_name` to a hybrid property, we need to
2573 # Since we transformed `repo_name` to a hybrid property, we need to
2570 # keep compatibility with the code which uses `repo_name` field.
2574 # keep compatibility with the code which uses `repo_name` field.
2571
2575
2572 result = super(Repository, self).get_dict()
2576 result = super(Repository, self).get_dict()
2573 result['repo_name'] = result.pop('_repo_name', None)
2577 result['repo_name'] = result.pop('_repo_name', None)
2574 return result
2578 return result
2575
2579
2576
2580
2577 class RepoGroup(Base, BaseModel):
2581 class RepoGroup(Base, BaseModel):
2578 __tablename__ = 'groups'
2582 __tablename__ = 'groups'
2579 __table_args__ = (
2583 __table_args__ = (
2580 UniqueConstraint('group_name', 'group_parent_id'),
2584 UniqueConstraint('group_name', 'group_parent_id'),
2581 base_table_args,
2585 base_table_args,
2582 )
2586 )
2583 __mapper_args__ = {'order_by': 'group_name'}
2587 __mapper_args__ = {'order_by': 'group_name'}
2584
2588
2585 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2589 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2586
2590
2587 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2591 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2588 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2592 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2589 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2593 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2590 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2594 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2591 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2595 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2592 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2596 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2593 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2594 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2598 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2595 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2599 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2596 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2600 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2597 _changeset_cache = Column(
2601 _changeset_cache = Column(
2598 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2602 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2599
2603
2600 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2604 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2601 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2605 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2602 parent_group = relationship('RepoGroup', remote_side=group_id)
2606 parent_group = relationship('RepoGroup', remote_side=group_id)
2603 user = relationship('User')
2607 user = relationship('User')
2604 integrations = relationship('Integration', cascade="all, delete-orphan")
2608 integrations = relationship('Integration', cascade="all, delete-orphan")
2605
2609
2606 # no cascade, set NULL
2610 # no cascade, set NULL
2607 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2611 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2608
2612
2609 def __init__(self, group_name='', parent_group=None):
2613 def __init__(self, group_name='', parent_group=None):
2610 self.group_name = group_name
2614 self.group_name = group_name
2611 self.parent_group = parent_group
2615 self.parent_group = parent_group
2612
2616
2613 def __unicode__(self):
2617 def __unicode__(self):
2614 return u"<%s('id:%s:%s')>" % (
2618 return u"<%s('id:%s:%s')>" % (
2615 self.__class__.__name__, self.group_id, self.group_name)
2619 self.__class__.__name__, self.group_id, self.group_name)
2616
2620
2617 @hybrid_property
2621 @hybrid_property
2618 def group_name(self):
2622 def group_name(self):
2619 return self._group_name
2623 return self._group_name
2620
2624
2621 @group_name.setter
2625 @group_name.setter
2622 def group_name(self, value):
2626 def group_name(self, value):
2623 self._group_name = value
2627 self._group_name = value
2624 self.group_name_hash = self.hash_repo_group_name(value)
2628 self.group_name_hash = self.hash_repo_group_name(value)
2625
2629
2626 @hybrid_property
2630 @hybrid_property
2627 def changeset_cache(self):
2631 def changeset_cache(self):
2628 from rhodecode.lib.vcs.backends.base import EmptyCommit
2632 from rhodecode.lib.vcs.backends.base import EmptyCommit
2629 dummy = EmptyCommit().__json__()
2633 dummy = EmptyCommit().__json__()
2630 if not self._changeset_cache:
2634 if not self._changeset_cache:
2631 dummy['source_repo_id'] = ''
2635 dummy['source_repo_id'] = ''
2632 return json.loads(json.dumps(dummy))
2636 return json.loads(json.dumps(dummy))
2633
2637
2634 try:
2638 try:
2635 return json.loads(self._changeset_cache)
2639 return json.loads(self._changeset_cache)
2636 except TypeError:
2640 except TypeError:
2637 return dummy
2641 return dummy
2638 except Exception:
2642 except Exception:
2639 log.error(traceback.format_exc())
2643 log.error(traceback.format_exc())
2640 return dummy
2644 return dummy
2641
2645
2642 @changeset_cache.setter
2646 @changeset_cache.setter
2643 def changeset_cache(self, val):
2647 def changeset_cache(self, val):
2644 try:
2648 try:
2645 self._changeset_cache = json.dumps(val)
2649 self._changeset_cache = json.dumps(val)
2646 except Exception:
2650 except Exception:
2647 log.error(traceback.format_exc())
2651 log.error(traceback.format_exc())
2648
2652
2649 @validates('group_parent_id')
2653 @validates('group_parent_id')
2650 def validate_group_parent_id(self, key, val):
2654 def validate_group_parent_id(self, key, val):
2651 """
2655 """
2652 Check cycle references for a parent group to self
2656 Check cycle references for a parent group to self
2653 """
2657 """
2654 if self.group_id and val:
2658 if self.group_id and val:
2655 assert val != self.group_id
2659 assert val != self.group_id
2656
2660
2657 return val
2661 return val
2658
2662
2659 @hybrid_property
2663 @hybrid_property
2660 def description_safe(self):
2664 def description_safe(self):
2661 from rhodecode.lib import helpers as h
2665 from rhodecode.lib import helpers as h
2662 return h.escape(self.group_description)
2666 return h.escape(self.group_description)
2663
2667
2664 @classmethod
2668 @classmethod
2665 def hash_repo_group_name(cls, repo_group_name):
2669 def hash_repo_group_name(cls, repo_group_name):
2666 val = remove_formatting(repo_group_name)
2670 val = remove_formatting(repo_group_name)
2667 val = safe_str(val).lower()
2671 val = safe_str(val).lower()
2668 chars = []
2672 chars = []
2669 for c in val:
2673 for c in val:
2670 if c not in string.ascii_letters:
2674 if c not in string.ascii_letters:
2671 c = str(ord(c))
2675 c = str(ord(c))
2672 chars.append(c)
2676 chars.append(c)
2673
2677
2674 return ''.join(chars)
2678 return ''.join(chars)
2675
2679
2676 @classmethod
2680 @classmethod
2677 def _generate_choice(cls, repo_group):
2681 def _generate_choice(cls, repo_group):
2678 from webhelpers2.html import literal as _literal
2682 from webhelpers2.html import literal as _literal
2679 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2683 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2680 return repo_group.group_id, _name(repo_group.full_path_splitted)
2684 return repo_group.group_id, _name(repo_group.full_path_splitted)
2681
2685
2682 @classmethod
2686 @classmethod
2683 def groups_choices(cls, groups=None, show_empty_group=True):
2687 def groups_choices(cls, groups=None, show_empty_group=True):
2684 if not groups:
2688 if not groups:
2685 groups = cls.query().all()
2689 groups = cls.query().all()
2686
2690
2687 repo_groups = []
2691 repo_groups = []
2688 if show_empty_group:
2692 if show_empty_group:
2689 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2693 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2690
2694
2691 repo_groups.extend([cls._generate_choice(x) for x in groups])
2695 repo_groups.extend([cls._generate_choice(x) for x in groups])
2692
2696
2693 repo_groups = sorted(
2697 repo_groups = sorted(
2694 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2698 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2695 return repo_groups
2699 return repo_groups
2696
2700
2697 @classmethod
2701 @classmethod
2698 def url_sep(cls):
2702 def url_sep(cls):
2699 return URL_SEP
2703 return URL_SEP
2700
2704
2701 @classmethod
2705 @classmethod
2702 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2706 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2703 if case_insensitive:
2707 if case_insensitive:
2704 gr = cls.query().filter(func.lower(cls.group_name)
2708 gr = cls.query().filter(func.lower(cls.group_name)
2705 == func.lower(group_name))
2709 == func.lower(group_name))
2706 else:
2710 else:
2707 gr = cls.query().filter(cls.group_name == group_name)
2711 gr = cls.query().filter(cls.group_name == group_name)
2708 if cache:
2712 if cache:
2709 name_key = _hash_key(group_name)
2713 name_key = _hash_key(group_name)
2710 gr = gr.options(
2714 gr = gr.options(
2711 FromCache("sql_cache_short", "get_group_%s" % name_key))
2715 FromCache("sql_cache_short", "get_group_%s" % name_key))
2712 return gr.scalar()
2716 return gr.scalar()
2713
2717
2714 @classmethod
2718 @classmethod
2715 def get_user_personal_repo_group(cls, user_id):
2719 def get_user_personal_repo_group(cls, user_id):
2716 user = User.get(user_id)
2720 user = User.get(user_id)
2717 if user.username == User.DEFAULT_USER:
2721 if user.username == User.DEFAULT_USER:
2718 return None
2722 return None
2719
2723
2720 return cls.query()\
2724 return cls.query()\
2721 .filter(cls.personal == true()) \
2725 .filter(cls.personal == true()) \
2722 .filter(cls.user == user) \
2726 .filter(cls.user == user) \
2723 .order_by(cls.group_id.asc()) \
2727 .order_by(cls.group_id.asc()) \
2724 .first()
2728 .first()
2725
2729
2726 @classmethod
2730 @classmethod
2727 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2731 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2728 case_insensitive=True):
2732 case_insensitive=True):
2729 q = RepoGroup.query()
2733 q = RepoGroup.query()
2730
2734
2731 if not isinstance(user_id, Optional):
2735 if not isinstance(user_id, Optional):
2732 q = q.filter(RepoGroup.user_id == user_id)
2736 q = q.filter(RepoGroup.user_id == user_id)
2733
2737
2734 if not isinstance(group_id, Optional):
2738 if not isinstance(group_id, Optional):
2735 q = q.filter(RepoGroup.group_parent_id == group_id)
2739 q = q.filter(RepoGroup.group_parent_id == group_id)
2736
2740
2737 if case_insensitive:
2741 if case_insensitive:
2738 q = q.order_by(func.lower(RepoGroup.group_name))
2742 q = q.order_by(func.lower(RepoGroup.group_name))
2739 else:
2743 else:
2740 q = q.order_by(RepoGroup.group_name)
2744 q = q.order_by(RepoGroup.group_name)
2741 return q.all()
2745 return q.all()
2742
2746
2743 @property
2747 @property
2744 def parents(self, parents_recursion_limit = 10):
2748 def parents(self, parents_recursion_limit = 10):
2745 groups = []
2749 groups = []
2746 if self.parent_group is None:
2750 if self.parent_group is None:
2747 return groups
2751 return groups
2748 cur_gr = self.parent_group
2752 cur_gr = self.parent_group
2749 groups.insert(0, cur_gr)
2753 groups.insert(0, cur_gr)
2750 cnt = 0
2754 cnt = 0
2751 while 1:
2755 while 1:
2752 cnt += 1
2756 cnt += 1
2753 gr = getattr(cur_gr, 'parent_group', None)
2757 gr = getattr(cur_gr, 'parent_group', None)
2754 cur_gr = cur_gr.parent_group
2758 cur_gr = cur_gr.parent_group
2755 if gr is None:
2759 if gr is None:
2756 break
2760 break
2757 if cnt == parents_recursion_limit:
2761 if cnt == parents_recursion_limit:
2758 # this will prevent accidental infinit loops
2762 # this will prevent accidental infinit loops
2759 log.error('more than %s parents found for group %s, stopping '
2763 log.error('more than %s parents found for group %s, stopping '
2760 'recursive parent fetching', parents_recursion_limit, self)
2764 'recursive parent fetching', parents_recursion_limit, self)
2761 break
2765 break
2762
2766
2763 groups.insert(0, gr)
2767 groups.insert(0, gr)
2764 return groups
2768 return groups
2765
2769
2766 @property
2770 @property
2767 def last_commit_cache_update_diff(self):
2771 def last_commit_cache_update_diff(self):
2768 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2772 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2769
2773
2770 @property
2774 @property
2771 def last_commit_change(self):
2775 def last_commit_change(self):
2772 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2776 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2773 empty_date = datetime.datetime.fromtimestamp(0)
2777 empty_date = datetime.datetime.fromtimestamp(0)
2774 date_latest = self.changeset_cache.get('date', empty_date)
2778 date_latest = self.changeset_cache.get('date', empty_date)
2775 try:
2779 try:
2776 return parse_datetime(date_latest)
2780 return parse_datetime(date_latest)
2777 except Exception:
2781 except Exception:
2778 return empty_date
2782 return empty_date
2779
2783
2780 @property
2784 @property
2781 def last_db_change(self):
2785 def last_db_change(self):
2782 return self.updated_on
2786 return self.updated_on
2783
2787
2784 @property
2788 @property
2785 def children(self):
2789 def children(self):
2786 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2790 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2787
2791
2788 @property
2792 @property
2789 def name(self):
2793 def name(self):
2790 return self.group_name.split(RepoGroup.url_sep())[-1]
2794 return self.group_name.split(RepoGroup.url_sep())[-1]
2791
2795
2792 @property
2796 @property
2793 def full_path(self):
2797 def full_path(self):
2794 return self.group_name
2798 return self.group_name
2795
2799
2796 @property
2800 @property
2797 def full_path_splitted(self):
2801 def full_path_splitted(self):
2798 return self.group_name.split(RepoGroup.url_sep())
2802 return self.group_name.split(RepoGroup.url_sep())
2799
2803
2800 @property
2804 @property
2801 def repositories(self):
2805 def repositories(self):
2802 return Repository.query()\
2806 return Repository.query()\
2803 .filter(Repository.group == self)\
2807 .filter(Repository.group == self)\
2804 .order_by(Repository.repo_name)
2808 .order_by(Repository.repo_name)
2805
2809
2806 @property
2810 @property
2807 def repositories_recursive_count(self):
2811 def repositories_recursive_count(self):
2808 cnt = self.repositories.count()
2812 cnt = self.repositories.count()
2809
2813
2810 def children_count(group):
2814 def children_count(group):
2811 cnt = 0
2815 cnt = 0
2812 for child in group.children:
2816 for child in group.children:
2813 cnt += child.repositories.count()
2817 cnt += child.repositories.count()
2814 cnt += children_count(child)
2818 cnt += children_count(child)
2815 return cnt
2819 return cnt
2816
2820
2817 return cnt + children_count(self)
2821 return cnt + children_count(self)
2818
2822
2819 def _recursive_objects(self, include_repos=True, include_groups=True):
2823 def _recursive_objects(self, include_repos=True, include_groups=True):
2820 all_ = []
2824 all_ = []
2821
2825
2822 def _get_members(root_gr):
2826 def _get_members(root_gr):
2823 if include_repos:
2827 if include_repos:
2824 for r in root_gr.repositories:
2828 for r in root_gr.repositories:
2825 all_.append(r)
2829 all_.append(r)
2826 childs = root_gr.children.all()
2830 childs = root_gr.children.all()
2827 if childs:
2831 if childs:
2828 for gr in childs:
2832 for gr in childs:
2829 if include_groups:
2833 if include_groups:
2830 all_.append(gr)
2834 all_.append(gr)
2831 _get_members(gr)
2835 _get_members(gr)
2832
2836
2833 root_group = []
2837 root_group = []
2834 if include_groups:
2838 if include_groups:
2835 root_group = [self]
2839 root_group = [self]
2836
2840
2837 _get_members(self)
2841 _get_members(self)
2838 return root_group + all_
2842 return root_group + all_
2839
2843
2840 def recursive_groups_and_repos(self):
2844 def recursive_groups_and_repos(self):
2841 """
2845 """
2842 Recursive return all groups, with repositories in those groups
2846 Recursive return all groups, with repositories in those groups
2843 """
2847 """
2844 return self._recursive_objects()
2848 return self._recursive_objects()
2845
2849
2846 def recursive_groups(self):
2850 def recursive_groups(self):
2847 """
2851 """
2848 Returns all children groups for this group including children of children
2852 Returns all children groups for this group including children of children
2849 """
2853 """
2850 return self._recursive_objects(include_repos=False)
2854 return self._recursive_objects(include_repos=False)
2851
2855
2852 def recursive_repos(self):
2856 def recursive_repos(self):
2853 """
2857 """
2854 Returns all children repositories for this group
2858 Returns all children repositories for this group
2855 """
2859 """
2856 return self._recursive_objects(include_groups=False)
2860 return self._recursive_objects(include_groups=False)
2857
2861
2858 def get_new_name(self, group_name):
2862 def get_new_name(self, group_name):
2859 """
2863 """
2860 returns new full group name based on parent and new name
2864 returns new full group name based on parent and new name
2861
2865
2862 :param group_name:
2866 :param group_name:
2863 """
2867 """
2864 path_prefix = (self.parent_group.full_path_splitted if
2868 path_prefix = (self.parent_group.full_path_splitted if
2865 self.parent_group else [])
2869 self.parent_group else [])
2866 return RepoGroup.url_sep().join(path_prefix + [group_name])
2870 return RepoGroup.url_sep().join(path_prefix + [group_name])
2867
2871
2868 def update_commit_cache(self, config=None):
2872 def update_commit_cache(self, config=None):
2869 """
2873 """
2870 Update cache of last changeset for newest repository inside this group, keys should be::
2874 Update cache of last changeset for newest repository inside this group, keys should be::
2871
2875
2872 source_repo_id
2876 source_repo_id
2873 short_id
2877 short_id
2874 raw_id
2878 raw_id
2875 revision
2879 revision
2876 parents
2880 parents
2877 message
2881 message
2878 date
2882 date
2879 author
2883 author
2880
2884
2881 """
2885 """
2882 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2886 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2883
2887
2884 def repo_groups_and_repos():
2888 def repo_groups_and_repos():
2885 all_entries = OrderedDefaultDict(list)
2889 all_entries = OrderedDefaultDict(list)
2886
2890
2887 def _get_members(root_gr, pos=0):
2891 def _get_members(root_gr, pos=0):
2888
2892
2889 for repo in root_gr.repositories:
2893 for repo in root_gr.repositories:
2890 all_entries[root_gr].append(repo)
2894 all_entries[root_gr].append(repo)
2891
2895
2892 # fill in all parent positions
2896 # fill in all parent positions
2893 for parent_group in root_gr.parents:
2897 for parent_group in root_gr.parents:
2894 all_entries[parent_group].extend(all_entries[root_gr])
2898 all_entries[parent_group].extend(all_entries[root_gr])
2895
2899
2896 children_groups = root_gr.children.all()
2900 children_groups = root_gr.children.all()
2897 if children_groups:
2901 if children_groups:
2898 for cnt, gr in enumerate(children_groups, 1):
2902 for cnt, gr in enumerate(children_groups, 1):
2899 _get_members(gr, pos=pos+cnt)
2903 _get_members(gr, pos=pos+cnt)
2900
2904
2901 _get_members(root_gr=self)
2905 _get_members(root_gr=self)
2902 return all_entries
2906 return all_entries
2903
2907
2904 empty_date = datetime.datetime.fromtimestamp(0)
2908 empty_date = datetime.datetime.fromtimestamp(0)
2905 for repo_group, repos in repo_groups_and_repos().items():
2909 for repo_group, repos in repo_groups_and_repos().items():
2906
2910
2907 latest_repo_cs_cache = {}
2911 latest_repo_cs_cache = {}
2908 _date_latest = empty_date
2912 _date_latest = empty_date
2909 for repo in repos:
2913 for repo in repos:
2910 repo_cs_cache = repo.changeset_cache
2914 repo_cs_cache = repo.changeset_cache
2911 date_latest = latest_repo_cs_cache.get('date', empty_date)
2915 date_latest = latest_repo_cs_cache.get('date', empty_date)
2912 date_current = repo_cs_cache.get('date', empty_date)
2916 date_current = repo_cs_cache.get('date', empty_date)
2913 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2917 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2914 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2918 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2915 latest_repo_cs_cache = repo_cs_cache
2919 latest_repo_cs_cache = repo_cs_cache
2916 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2920 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2917 _date_latest = parse_datetime(latest_repo_cs_cache['date'])
2921 _date_latest = parse_datetime(latest_repo_cs_cache['date'])
2918
2922
2919 latest_repo_cs_cache['updated_on'] = time.time()
2923 latest_repo_cs_cache['updated_on'] = time.time()
2920 repo_group.changeset_cache = latest_repo_cs_cache
2924 repo_group.changeset_cache = latest_repo_cs_cache
2921 repo_group.updated_on = _date_latest
2925 repo_group.updated_on = _date_latest
2922 Session().add(repo_group)
2926 Session().add(repo_group)
2923 Session().commit()
2927 Session().commit()
2924
2928
2925 log.debug('updated repo group `%s` with new commit cache %s',
2929 log.debug('updated repo group `%s` with new commit cache %s',
2926 repo_group.group_name, latest_repo_cs_cache)
2930 repo_group.group_name, latest_repo_cs_cache)
2927
2931
2928 def permissions(self, with_admins=True, with_owner=True,
2932 def permissions(self, with_admins=True, with_owner=True,
2929 expand_from_user_groups=False):
2933 expand_from_user_groups=False):
2930 """
2934 """
2931 Permissions for repository groups
2935 Permissions for repository groups
2932 """
2936 """
2933 _admin_perm = 'group.admin'
2937 _admin_perm = 'group.admin'
2934
2938
2935 owner_row = []
2939 owner_row = []
2936 if with_owner:
2940 if with_owner:
2937 usr = AttributeDict(self.user.get_dict())
2941 usr = AttributeDict(self.user.get_dict())
2938 usr.owner_row = True
2942 usr.owner_row = True
2939 usr.permission = _admin_perm
2943 usr.permission = _admin_perm
2940 owner_row.append(usr)
2944 owner_row.append(usr)
2941
2945
2942 super_admin_ids = []
2946 super_admin_ids = []
2943 super_admin_rows = []
2947 super_admin_rows = []
2944 if with_admins:
2948 if with_admins:
2945 for usr in User.get_all_super_admins():
2949 for usr in User.get_all_super_admins():
2946 super_admin_ids.append(usr.user_id)
2950 super_admin_ids.append(usr.user_id)
2947 # if this admin is also owner, don't double the record
2951 # if this admin is also owner, don't double the record
2948 if usr.user_id == owner_row[0].user_id:
2952 if usr.user_id == owner_row[0].user_id:
2949 owner_row[0].admin_row = True
2953 owner_row[0].admin_row = True
2950 else:
2954 else:
2951 usr = AttributeDict(usr.get_dict())
2955 usr = AttributeDict(usr.get_dict())
2952 usr.admin_row = True
2956 usr.admin_row = True
2953 usr.permission = _admin_perm
2957 usr.permission = _admin_perm
2954 super_admin_rows.append(usr)
2958 super_admin_rows.append(usr)
2955
2959
2956 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2960 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2957 q = q.options(joinedload(UserRepoGroupToPerm.group),
2961 q = q.options(joinedload(UserRepoGroupToPerm.group),
2958 joinedload(UserRepoGroupToPerm.user),
2962 joinedload(UserRepoGroupToPerm.user),
2959 joinedload(UserRepoGroupToPerm.permission),)
2963 joinedload(UserRepoGroupToPerm.permission),)
2960
2964
2961 # get owners and admins and permissions. We do a trick of re-writing
2965 # get owners and admins and permissions. We do a trick of re-writing
2962 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2966 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2963 # has a global reference and changing one object propagates to all
2967 # has a global reference and changing one object propagates to all
2964 # others. This means if admin is also an owner admin_row that change
2968 # others. This means if admin is also an owner admin_row that change
2965 # would propagate to both objects
2969 # would propagate to both objects
2966 perm_rows = []
2970 perm_rows = []
2967 for _usr in q.all():
2971 for _usr in q.all():
2968 usr = AttributeDict(_usr.user.get_dict())
2972 usr = AttributeDict(_usr.user.get_dict())
2969 # if this user is also owner/admin, mark as duplicate record
2973 # if this user is also owner/admin, mark as duplicate record
2970 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2974 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2971 usr.duplicate_perm = True
2975 usr.duplicate_perm = True
2972 usr.permission = _usr.permission.permission_name
2976 usr.permission = _usr.permission.permission_name
2973 perm_rows.append(usr)
2977 perm_rows.append(usr)
2974
2978
2975 # filter the perm rows by 'default' first and then sort them by
2979 # filter the perm rows by 'default' first and then sort them by
2976 # admin,write,read,none permissions sorted again alphabetically in
2980 # admin,write,read,none permissions sorted again alphabetically in
2977 # each group
2981 # each group
2978 perm_rows = sorted(perm_rows, key=display_user_sort)
2982 perm_rows = sorted(perm_rows, key=display_user_sort)
2979
2983
2980 user_groups_rows = []
2984 user_groups_rows = []
2981 if expand_from_user_groups:
2985 if expand_from_user_groups:
2982 for ug in self.permission_user_groups(with_members=True):
2986 for ug in self.permission_user_groups(with_members=True):
2983 for user_data in ug.members:
2987 for user_data in ug.members:
2984 user_groups_rows.append(user_data)
2988 user_groups_rows.append(user_data)
2985
2989
2986 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2990 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2987
2991
2988 def permission_user_groups(self, with_members=False):
2992 def permission_user_groups(self, with_members=False):
2989 q = UserGroupRepoGroupToPerm.query()\
2993 q = UserGroupRepoGroupToPerm.query()\
2990 .filter(UserGroupRepoGroupToPerm.group == self)
2994 .filter(UserGroupRepoGroupToPerm.group == self)
2991 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2995 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2992 joinedload(UserGroupRepoGroupToPerm.users_group),
2996 joinedload(UserGroupRepoGroupToPerm.users_group),
2993 joinedload(UserGroupRepoGroupToPerm.permission),)
2997 joinedload(UserGroupRepoGroupToPerm.permission),)
2994
2998
2995 perm_rows = []
2999 perm_rows = []
2996 for _user_group in q.all():
3000 for _user_group in q.all():
2997 entry = AttributeDict(_user_group.users_group.get_dict())
3001 entry = AttributeDict(_user_group.users_group.get_dict())
2998 entry.permission = _user_group.permission.permission_name
3002 entry.permission = _user_group.permission.permission_name
2999 if with_members:
3003 if with_members:
3000 entry.members = [x.user.get_dict()
3004 entry.members = [x.user.get_dict()
3001 for x in _user_group.users_group.members]
3005 for x in _user_group.users_group.members]
3002 perm_rows.append(entry)
3006 perm_rows.append(entry)
3003
3007
3004 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3008 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3005 return perm_rows
3009 return perm_rows
3006
3010
3007 def get_api_data(self):
3011 def get_api_data(self):
3008 """
3012 """
3009 Common function for generating api data
3013 Common function for generating api data
3010
3014
3011 """
3015 """
3012 group = self
3016 group = self
3013 data = {
3017 data = {
3014 'group_id': group.group_id,
3018 'group_id': group.group_id,
3015 'group_name': group.group_name,
3019 'group_name': group.group_name,
3016 'group_description': group.description_safe,
3020 'group_description': group.description_safe,
3017 'parent_group': group.parent_group.group_name if group.parent_group else None,
3021 'parent_group': group.parent_group.group_name if group.parent_group else None,
3018 'repositories': [x.repo_name for x in group.repositories],
3022 'repositories': [x.repo_name for x in group.repositories],
3019 'owner': group.user.username,
3023 'owner': group.user.username,
3020 }
3024 }
3021 return data
3025 return data
3022
3026
3023 def get_dict(self):
3027 def get_dict(self):
3024 # Since we transformed `group_name` to a hybrid property, we need to
3028 # Since we transformed `group_name` to a hybrid property, we need to
3025 # keep compatibility with the code which uses `group_name` field.
3029 # keep compatibility with the code which uses `group_name` field.
3026 result = super(RepoGroup, self).get_dict()
3030 result = super(RepoGroup, self).get_dict()
3027 result['group_name'] = result.pop('_group_name', None)
3031 result['group_name'] = result.pop('_group_name', None)
3028 return result
3032 return result
3029
3033
3030
3034
3031 class Permission(Base, BaseModel):
3035 class Permission(Base, BaseModel):
3032 __tablename__ = 'permissions'
3036 __tablename__ = 'permissions'
3033 __table_args__ = (
3037 __table_args__ = (
3034 Index('p_perm_name_idx', 'permission_name'),
3038 Index('p_perm_name_idx', 'permission_name'),
3035 base_table_args,
3039 base_table_args,
3036 )
3040 )
3037
3041
3038 PERMS = [
3042 PERMS = [
3039 ('hg.admin', _('RhodeCode Super Administrator')),
3043 ('hg.admin', _('RhodeCode Super Administrator')),
3040
3044
3041 ('repository.none', _('Repository no access')),
3045 ('repository.none', _('Repository no access')),
3042 ('repository.read', _('Repository read access')),
3046 ('repository.read', _('Repository read access')),
3043 ('repository.write', _('Repository write access')),
3047 ('repository.write', _('Repository write access')),
3044 ('repository.admin', _('Repository admin access')),
3048 ('repository.admin', _('Repository admin access')),
3045
3049
3046 ('group.none', _('Repository group no access')),
3050 ('group.none', _('Repository group no access')),
3047 ('group.read', _('Repository group read access')),
3051 ('group.read', _('Repository group read access')),
3048 ('group.write', _('Repository group write access')),
3052 ('group.write', _('Repository group write access')),
3049 ('group.admin', _('Repository group admin access')),
3053 ('group.admin', _('Repository group admin access')),
3050
3054
3051 ('usergroup.none', _('User group no access')),
3055 ('usergroup.none', _('User group no access')),
3052 ('usergroup.read', _('User group read access')),
3056 ('usergroup.read', _('User group read access')),
3053 ('usergroup.write', _('User group write access')),
3057 ('usergroup.write', _('User group write access')),
3054 ('usergroup.admin', _('User group admin access')),
3058 ('usergroup.admin', _('User group admin access')),
3055
3059
3056 ('branch.none', _('Branch no permissions')),
3060 ('branch.none', _('Branch no permissions')),
3057 ('branch.merge', _('Branch access by web merge')),
3061 ('branch.merge', _('Branch access by web merge')),
3058 ('branch.push', _('Branch access by push')),
3062 ('branch.push', _('Branch access by push')),
3059 ('branch.push_force', _('Branch access by push with force')),
3063 ('branch.push_force', _('Branch access by push with force')),
3060
3064
3061 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3065 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3062 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3066 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3063
3067
3064 ('hg.usergroup.create.false', _('User Group creation disabled')),
3068 ('hg.usergroup.create.false', _('User Group creation disabled')),
3065 ('hg.usergroup.create.true', _('User Group creation enabled')),
3069 ('hg.usergroup.create.true', _('User Group creation enabled')),
3066
3070
3067 ('hg.create.none', _('Repository creation disabled')),
3071 ('hg.create.none', _('Repository creation disabled')),
3068 ('hg.create.repository', _('Repository creation enabled')),
3072 ('hg.create.repository', _('Repository creation enabled')),
3069 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3073 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3070 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3074 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3071
3075
3072 ('hg.fork.none', _('Repository forking disabled')),
3076 ('hg.fork.none', _('Repository forking disabled')),
3073 ('hg.fork.repository', _('Repository forking enabled')),
3077 ('hg.fork.repository', _('Repository forking enabled')),
3074
3078
3075 ('hg.register.none', _('Registration disabled')),
3079 ('hg.register.none', _('Registration disabled')),
3076 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3080 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3077 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3081 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3078
3082
3079 ('hg.password_reset.enabled', _('Password reset enabled')),
3083 ('hg.password_reset.enabled', _('Password reset enabled')),
3080 ('hg.password_reset.hidden', _('Password reset hidden')),
3084 ('hg.password_reset.hidden', _('Password reset hidden')),
3081 ('hg.password_reset.disabled', _('Password reset disabled')),
3085 ('hg.password_reset.disabled', _('Password reset disabled')),
3082
3086
3083 ('hg.extern_activate.manual', _('Manual activation of external account')),
3087 ('hg.extern_activate.manual', _('Manual activation of external account')),
3084 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3088 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3085
3089
3086 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3090 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3087 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3091 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3088 ]
3092 ]
3089
3093
3090 # definition of system default permissions for DEFAULT user, created on
3094 # definition of system default permissions for DEFAULT user, created on
3091 # system setup
3095 # system setup
3092 DEFAULT_USER_PERMISSIONS = [
3096 DEFAULT_USER_PERMISSIONS = [
3093 # object perms
3097 # object perms
3094 'repository.read',
3098 'repository.read',
3095 'group.read',
3099 'group.read',
3096 'usergroup.read',
3100 'usergroup.read',
3097 # branch, for backward compat we need same value as before so forced pushed
3101 # branch, for backward compat we need same value as before so forced pushed
3098 'branch.push_force',
3102 'branch.push_force',
3099 # global
3103 # global
3100 'hg.create.repository',
3104 'hg.create.repository',
3101 'hg.repogroup.create.false',
3105 'hg.repogroup.create.false',
3102 'hg.usergroup.create.false',
3106 'hg.usergroup.create.false',
3103 'hg.create.write_on_repogroup.true',
3107 'hg.create.write_on_repogroup.true',
3104 'hg.fork.repository',
3108 'hg.fork.repository',
3105 'hg.register.manual_activate',
3109 'hg.register.manual_activate',
3106 'hg.password_reset.enabled',
3110 'hg.password_reset.enabled',
3107 'hg.extern_activate.auto',
3111 'hg.extern_activate.auto',
3108 'hg.inherit_default_perms.true',
3112 'hg.inherit_default_perms.true',
3109 ]
3113 ]
3110
3114
3111 # defines which permissions are more important higher the more important
3115 # defines which permissions are more important higher the more important
3112 # Weight defines which permissions are more important.
3116 # Weight defines which permissions are more important.
3113 # The higher number the more important.
3117 # The higher number the more important.
3114 PERM_WEIGHTS = {
3118 PERM_WEIGHTS = {
3115 'repository.none': 0,
3119 'repository.none': 0,
3116 'repository.read': 1,
3120 'repository.read': 1,
3117 'repository.write': 3,
3121 'repository.write': 3,
3118 'repository.admin': 4,
3122 'repository.admin': 4,
3119
3123
3120 'group.none': 0,
3124 'group.none': 0,
3121 'group.read': 1,
3125 'group.read': 1,
3122 'group.write': 3,
3126 'group.write': 3,
3123 'group.admin': 4,
3127 'group.admin': 4,
3124
3128
3125 'usergroup.none': 0,
3129 'usergroup.none': 0,
3126 'usergroup.read': 1,
3130 'usergroup.read': 1,
3127 'usergroup.write': 3,
3131 'usergroup.write': 3,
3128 'usergroup.admin': 4,
3132 'usergroup.admin': 4,
3129
3133
3130 'branch.none': 0,
3134 'branch.none': 0,
3131 'branch.merge': 1,
3135 'branch.merge': 1,
3132 'branch.push': 3,
3136 'branch.push': 3,
3133 'branch.push_force': 4,
3137 'branch.push_force': 4,
3134
3138
3135 'hg.repogroup.create.false': 0,
3139 'hg.repogroup.create.false': 0,
3136 'hg.repogroup.create.true': 1,
3140 'hg.repogroup.create.true': 1,
3137
3141
3138 'hg.usergroup.create.false': 0,
3142 'hg.usergroup.create.false': 0,
3139 'hg.usergroup.create.true': 1,
3143 'hg.usergroup.create.true': 1,
3140
3144
3141 'hg.fork.none': 0,
3145 'hg.fork.none': 0,
3142 'hg.fork.repository': 1,
3146 'hg.fork.repository': 1,
3143 'hg.create.none': 0,
3147 'hg.create.none': 0,
3144 'hg.create.repository': 1
3148 'hg.create.repository': 1
3145 }
3149 }
3146
3150
3147 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3151 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3148 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3152 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3149 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3153 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3150
3154
3151 def __unicode__(self):
3155 def __unicode__(self):
3152 return u"<%s('%s:%s')>" % (
3156 return u"<%s('%s:%s')>" % (
3153 self.__class__.__name__, self.permission_id, self.permission_name
3157 self.__class__.__name__, self.permission_id, self.permission_name
3154 )
3158 )
3155
3159
3156 @classmethod
3160 @classmethod
3157 def get_by_key(cls, key):
3161 def get_by_key(cls, key):
3158 return cls.query().filter(cls.permission_name == key).scalar()
3162 return cls.query().filter(cls.permission_name == key).scalar()
3159
3163
3160 @classmethod
3164 @classmethod
3161 def get_default_repo_perms(cls, user_id, repo_id=None):
3165 def get_default_repo_perms(cls, user_id, repo_id=None):
3162 q = Session().query(UserRepoToPerm, Repository, Permission)\
3166 q = Session().query(UserRepoToPerm, Repository, Permission)\
3163 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3167 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3164 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3168 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3165 .filter(UserRepoToPerm.user_id == user_id)
3169 .filter(UserRepoToPerm.user_id == user_id)
3166 if repo_id:
3170 if repo_id:
3167 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3171 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3168 return q.all()
3172 return q.all()
3169
3173
3170 @classmethod
3174 @classmethod
3171 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3175 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3172 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3176 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3173 .join(
3177 .join(
3174 Permission,
3178 Permission,
3175 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3179 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3176 .join(
3180 .join(
3177 UserRepoToPerm,
3181 UserRepoToPerm,
3178 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3182 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3179 .filter(UserRepoToPerm.user_id == user_id)
3183 .filter(UserRepoToPerm.user_id == user_id)
3180
3184
3181 if repo_id:
3185 if repo_id:
3182 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3186 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3183 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3187 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3184
3188
3185 @classmethod
3189 @classmethod
3186 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3190 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3187 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3191 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3188 .join(
3192 .join(
3189 Permission,
3193 Permission,
3190 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3194 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3191 .join(
3195 .join(
3192 Repository,
3196 Repository,
3193 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3197 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3194 .join(
3198 .join(
3195 UserGroup,
3199 UserGroup,
3196 UserGroupRepoToPerm.users_group_id ==
3200 UserGroupRepoToPerm.users_group_id ==
3197 UserGroup.users_group_id)\
3201 UserGroup.users_group_id)\
3198 .join(
3202 .join(
3199 UserGroupMember,
3203 UserGroupMember,
3200 UserGroupRepoToPerm.users_group_id ==
3204 UserGroupRepoToPerm.users_group_id ==
3201 UserGroupMember.users_group_id)\
3205 UserGroupMember.users_group_id)\
3202 .filter(
3206 .filter(
3203 UserGroupMember.user_id == user_id,
3207 UserGroupMember.user_id == user_id,
3204 UserGroup.users_group_active == true())
3208 UserGroup.users_group_active == true())
3205 if repo_id:
3209 if repo_id:
3206 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3210 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3207 return q.all()
3211 return q.all()
3208
3212
3209 @classmethod
3213 @classmethod
3210 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3214 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3211 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3215 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3212 .join(
3216 .join(
3213 Permission,
3217 Permission,
3214 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3218 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3215 .join(
3219 .join(
3216 UserGroupRepoToPerm,
3220 UserGroupRepoToPerm,
3217 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3221 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3218 .join(
3222 .join(
3219 UserGroup,
3223 UserGroup,
3220 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3224 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3221 .join(
3225 .join(
3222 UserGroupMember,
3226 UserGroupMember,
3223 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3227 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3224 .filter(
3228 .filter(
3225 UserGroupMember.user_id == user_id,
3229 UserGroupMember.user_id == user_id,
3226 UserGroup.users_group_active == true())
3230 UserGroup.users_group_active == true())
3227
3231
3228 if repo_id:
3232 if repo_id:
3229 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3233 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3230 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3234 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3231
3235
3232 @classmethod
3236 @classmethod
3233 def get_default_group_perms(cls, user_id, repo_group_id=None):
3237 def get_default_group_perms(cls, user_id, repo_group_id=None):
3234 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3238 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3235 .join(
3239 .join(
3236 Permission,
3240 Permission,
3237 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3241 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3238 .join(
3242 .join(
3239 RepoGroup,
3243 RepoGroup,
3240 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3244 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3241 .filter(UserRepoGroupToPerm.user_id == user_id)
3245 .filter(UserRepoGroupToPerm.user_id == user_id)
3242 if repo_group_id:
3246 if repo_group_id:
3243 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3247 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3244 return q.all()
3248 return q.all()
3245
3249
3246 @classmethod
3250 @classmethod
3247 def get_default_group_perms_from_user_group(
3251 def get_default_group_perms_from_user_group(
3248 cls, user_id, repo_group_id=None):
3252 cls, user_id, repo_group_id=None):
3249 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3253 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3250 .join(
3254 .join(
3251 Permission,
3255 Permission,
3252 UserGroupRepoGroupToPerm.permission_id ==
3256 UserGroupRepoGroupToPerm.permission_id ==
3253 Permission.permission_id)\
3257 Permission.permission_id)\
3254 .join(
3258 .join(
3255 RepoGroup,
3259 RepoGroup,
3256 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3260 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3257 .join(
3261 .join(
3258 UserGroup,
3262 UserGroup,
3259 UserGroupRepoGroupToPerm.users_group_id ==
3263 UserGroupRepoGroupToPerm.users_group_id ==
3260 UserGroup.users_group_id)\
3264 UserGroup.users_group_id)\
3261 .join(
3265 .join(
3262 UserGroupMember,
3266 UserGroupMember,
3263 UserGroupRepoGroupToPerm.users_group_id ==
3267 UserGroupRepoGroupToPerm.users_group_id ==
3264 UserGroupMember.users_group_id)\
3268 UserGroupMember.users_group_id)\
3265 .filter(
3269 .filter(
3266 UserGroupMember.user_id == user_id,
3270 UserGroupMember.user_id == user_id,
3267 UserGroup.users_group_active == true())
3271 UserGroup.users_group_active == true())
3268 if repo_group_id:
3272 if repo_group_id:
3269 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3273 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3270 return q.all()
3274 return q.all()
3271
3275
3272 @classmethod
3276 @classmethod
3273 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3277 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3274 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3278 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3275 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3279 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3276 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3280 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3277 .filter(UserUserGroupToPerm.user_id == user_id)
3281 .filter(UserUserGroupToPerm.user_id == user_id)
3278 if user_group_id:
3282 if user_group_id:
3279 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3283 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3280 return q.all()
3284 return q.all()
3281
3285
3282 @classmethod
3286 @classmethod
3283 def get_default_user_group_perms_from_user_group(
3287 def get_default_user_group_perms_from_user_group(
3284 cls, user_id, user_group_id=None):
3288 cls, user_id, user_group_id=None):
3285 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3289 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3286 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3290 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3287 .join(
3291 .join(
3288 Permission,
3292 Permission,
3289 UserGroupUserGroupToPerm.permission_id ==
3293 UserGroupUserGroupToPerm.permission_id ==
3290 Permission.permission_id)\
3294 Permission.permission_id)\
3291 .join(
3295 .join(
3292 TargetUserGroup,
3296 TargetUserGroup,
3293 UserGroupUserGroupToPerm.target_user_group_id ==
3297 UserGroupUserGroupToPerm.target_user_group_id ==
3294 TargetUserGroup.users_group_id)\
3298 TargetUserGroup.users_group_id)\
3295 .join(
3299 .join(
3296 UserGroup,
3300 UserGroup,
3297 UserGroupUserGroupToPerm.user_group_id ==
3301 UserGroupUserGroupToPerm.user_group_id ==
3298 UserGroup.users_group_id)\
3302 UserGroup.users_group_id)\
3299 .join(
3303 .join(
3300 UserGroupMember,
3304 UserGroupMember,
3301 UserGroupUserGroupToPerm.user_group_id ==
3305 UserGroupUserGroupToPerm.user_group_id ==
3302 UserGroupMember.users_group_id)\
3306 UserGroupMember.users_group_id)\
3303 .filter(
3307 .filter(
3304 UserGroupMember.user_id == user_id,
3308 UserGroupMember.user_id == user_id,
3305 UserGroup.users_group_active == true())
3309 UserGroup.users_group_active == true())
3306 if user_group_id:
3310 if user_group_id:
3307 q = q.filter(
3311 q = q.filter(
3308 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3312 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3309
3313
3310 return q.all()
3314 return q.all()
3311
3315
3312
3316
3313 class UserRepoToPerm(Base, BaseModel):
3317 class UserRepoToPerm(Base, BaseModel):
3314 __tablename__ = 'repo_to_perm'
3318 __tablename__ = 'repo_to_perm'
3315 __table_args__ = (
3319 __table_args__ = (
3316 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3320 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3317 base_table_args
3321 base_table_args
3318 )
3322 )
3319
3323
3320 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3324 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3325 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3322 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3326 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3323 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3327 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3324
3328
3325 user = relationship('User')
3329 user = relationship('User')
3326 repository = relationship('Repository')
3330 repository = relationship('Repository')
3327 permission = relationship('Permission')
3331 permission = relationship('Permission')
3328
3332
3329 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3333 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3330
3334
3331 @classmethod
3335 @classmethod
3332 def create(cls, user, repository, permission):
3336 def create(cls, user, repository, permission):
3333 n = cls()
3337 n = cls()
3334 n.user = user
3338 n.user = user
3335 n.repository = repository
3339 n.repository = repository
3336 n.permission = permission
3340 n.permission = permission
3337 Session().add(n)
3341 Session().add(n)
3338 return n
3342 return n
3339
3343
3340 def __unicode__(self):
3344 def __unicode__(self):
3341 return u'<%s => %s >' % (self.user, self.repository)
3345 return u'<%s => %s >' % (self.user, self.repository)
3342
3346
3343
3347
3344 class UserUserGroupToPerm(Base, BaseModel):
3348 class UserUserGroupToPerm(Base, BaseModel):
3345 __tablename__ = 'user_user_group_to_perm'
3349 __tablename__ = 'user_user_group_to_perm'
3346 __table_args__ = (
3350 __table_args__ = (
3347 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3351 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3348 base_table_args
3352 base_table_args
3349 )
3353 )
3350
3354
3351 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3355 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3352 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3356 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3353 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3357 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3354 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3358 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3355
3359
3356 user = relationship('User')
3360 user = relationship('User')
3357 user_group = relationship('UserGroup')
3361 user_group = relationship('UserGroup')
3358 permission = relationship('Permission')
3362 permission = relationship('Permission')
3359
3363
3360 @classmethod
3364 @classmethod
3361 def create(cls, user, user_group, permission):
3365 def create(cls, user, user_group, permission):
3362 n = cls()
3366 n = cls()
3363 n.user = user
3367 n.user = user
3364 n.user_group = user_group
3368 n.user_group = user_group
3365 n.permission = permission
3369 n.permission = permission
3366 Session().add(n)
3370 Session().add(n)
3367 return n
3371 return n
3368
3372
3369 def __unicode__(self):
3373 def __unicode__(self):
3370 return u'<%s => %s >' % (self.user, self.user_group)
3374 return u'<%s => %s >' % (self.user, self.user_group)
3371
3375
3372
3376
3373 class UserToPerm(Base, BaseModel):
3377 class UserToPerm(Base, BaseModel):
3374 __tablename__ = 'user_to_perm'
3378 __tablename__ = 'user_to_perm'
3375 __table_args__ = (
3379 __table_args__ = (
3376 UniqueConstraint('user_id', 'permission_id'),
3380 UniqueConstraint('user_id', 'permission_id'),
3377 base_table_args
3381 base_table_args
3378 )
3382 )
3379
3383
3380 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3384 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3381 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3382 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3386 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3383
3387
3384 user = relationship('User')
3388 user = relationship('User')
3385 permission = relationship('Permission', lazy='joined')
3389 permission = relationship('Permission', lazy='joined')
3386
3390
3387 def __unicode__(self):
3391 def __unicode__(self):
3388 return u'<%s => %s >' % (self.user, self.permission)
3392 return u'<%s => %s >' % (self.user, self.permission)
3389
3393
3390
3394
3391 class UserGroupRepoToPerm(Base, BaseModel):
3395 class UserGroupRepoToPerm(Base, BaseModel):
3392 __tablename__ = 'users_group_repo_to_perm'
3396 __tablename__ = 'users_group_repo_to_perm'
3393 __table_args__ = (
3397 __table_args__ = (
3394 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3398 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3395 base_table_args
3399 base_table_args
3396 )
3400 )
3397
3401
3398 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3402 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3399 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3403 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3400 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3401 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3405 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3402
3406
3403 users_group = relationship('UserGroup')
3407 users_group = relationship('UserGroup')
3404 permission = relationship('Permission')
3408 permission = relationship('Permission')
3405 repository = relationship('Repository')
3409 repository = relationship('Repository')
3406 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3410 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3407
3411
3408 @classmethod
3412 @classmethod
3409 def create(cls, users_group, repository, permission):
3413 def create(cls, users_group, repository, permission):
3410 n = cls()
3414 n = cls()
3411 n.users_group = users_group
3415 n.users_group = users_group
3412 n.repository = repository
3416 n.repository = repository
3413 n.permission = permission
3417 n.permission = permission
3414 Session().add(n)
3418 Session().add(n)
3415 return n
3419 return n
3416
3420
3417 def __unicode__(self):
3421 def __unicode__(self):
3418 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3422 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3419
3423
3420
3424
3421 class UserGroupUserGroupToPerm(Base, BaseModel):
3425 class UserGroupUserGroupToPerm(Base, BaseModel):
3422 __tablename__ = 'user_group_user_group_to_perm'
3426 __tablename__ = 'user_group_user_group_to_perm'
3423 __table_args__ = (
3427 __table_args__ = (
3424 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3428 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3425 CheckConstraint('target_user_group_id != user_group_id'),
3429 CheckConstraint('target_user_group_id != user_group_id'),
3426 base_table_args
3430 base_table_args
3427 )
3431 )
3428
3432
3429 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)
3433 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)
3430 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3434 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3431 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3435 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3432 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3436 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3433
3437
3434 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3438 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3435 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3439 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3436 permission = relationship('Permission')
3440 permission = relationship('Permission')
3437
3441
3438 @classmethod
3442 @classmethod
3439 def create(cls, target_user_group, user_group, permission):
3443 def create(cls, target_user_group, user_group, permission):
3440 n = cls()
3444 n = cls()
3441 n.target_user_group = target_user_group
3445 n.target_user_group = target_user_group
3442 n.user_group = user_group
3446 n.user_group = user_group
3443 n.permission = permission
3447 n.permission = permission
3444 Session().add(n)
3448 Session().add(n)
3445 return n
3449 return n
3446
3450
3447 def __unicode__(self):
3451 def __unicode__(self):
3448 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3452 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3449
3453
3450
3454
3451 class UserGroupToPerm(Base, BaseModel):
3455 class UserGroupToPerm(Base, BaseModel):
3452 __tablename__ = 'users_group_to_perm'
3456 __tablename__ = 'users_group_to_perm'
3453 __table_args__ = (
3457 __table_args__ = (
3454 UniqueConstraint('users_group_id', 'permission_id',),
3458 UniqueConstraint('users_group_id', 'permission_id',),
3455 base_table_args
3459 base_table_args
3456 )
3460 )
3457
3461
3458 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3462 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3459 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3463 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3460 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3464 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3461
3465
3462 users_group = relationship('UserGroup')
3466 users_group = relationship('UserGroup')
3463 permission = relationship('Permission')
3467 permission = relationship('Permission')
3464
3468
3465
3469
3466 class UserRepoGroupToPerm(Base, BaseModel):
3470 class UserRepoGroupToPerm(Base, BaseModel):
3467 __tablename__ = 'user_repo_group_to_perm'
3471 __tablename__ = 'user_repo_group_to_perm'
3468 __table_args__ = (
3472 __table_args__ = (
3469 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3473 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3470 base_table_args
3474 base_table_args
3471 )
3475 )
3472
3476
3473 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3477 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3474 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3475 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3479 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3476 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3480 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3477
3481
3478 user = relationship('User')
3482 user = relationship('User')
3479 group = relationship('RepoGroup')
3483 group = relationship('RepoGroup')
3480 permission = relationship('Permission')
3484 permission = relationship('Permission')
3481
3485
3482 @classmethod
3486 @classmethod
3483 def create(cls, user, repository_group, permission):
3487 def create(cls, user, repository_group, permission):
3484 n = cls()
3488 n = cls()
3485 n.user = user
3489 n.user = user
3486 n.group = repository_group
3490 n.group = repository_group
3487 n.permission = permission
3491 n.permission = permission
3488 Session().add(n)
3492 Session().add(n)
3489 return n
3493 return n
3490
3494
3491
3495
3492 class UserGroupRepoGroupToPerm(Base, BaseModel):
3496 class UserGroupRepoGroupToPerm(Base, BaseModel):
3493 __tablename__ = 'users_group_repo_group_to_perm'
3497 __tablename__ = 'users_group_repo_group_to_perm'
3494 __table_args__ = (
3498 __table_args__ = (
3495 UniqueConstraint('users_group_id', 'group_id'),
3499 UniqueConstraint('users_group_id', 'group_id'),
3496 base_table_args
3500 base_table_args
3497 )
3501 )
3498
3502
3499 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)
3503 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)
3500 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3504 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3501 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3505 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3502 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)
3503
3507
3504 users_group = relationship('UserGroup')
3508 users_group = relationship('UserGroup')
3505 permission = relationship('Permission')
3509 permission = relationship('Permission')
3506 group = relationship('RepoGroup')
3510 group = relationship('RepoGroup')
3507
3511
3508 @classmethod
3512 @classmethod
3509 def create(cls, user_group, repository_group, permission):
3513 def create(cls, user_group, repository_group, permission):
3510 n = cls()
3514 n = cls()
3511 n.users_group = user_group
3515 n.users_group = user_group
3512 n.group = repository_group
3516 n.group = repository_group
3513 n.permission = permission
3517 n.permission = permission
3514 Session().add(n)
3518 Session().add(n)
3515 return n
3519 return n
3516
3520
3517 def __unicode__(self):
3521 def __unicode__(self):
3518 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3522 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3519
3523
3520
3524
3521 class Statistics(Base, BaseModel):
3525 class Statistics(Base, BaseModel):
3522 __tablename__ = 'statistics'
3526 __tablename__ = 'statistics'
3523 __table_args__ = (
3527 __table_args__ = (
3524 base_table_args
3528 base_table_args
3525 )
3529 )
3526
3530
3527 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3531 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3528 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3532 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3529 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3533 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3530 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3534 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3531 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3535 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3532 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3536 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3533
3537
3534 repository = relationship('Repository', single_parent=True)
3538 repository = relationship('Repository', single_parent=True)
3535
3539
3536
3540
3537 class UserFollowing(Base, BaseModel):
3541 class UserFollowing(Base, BaseModel):
3538 __tablename__ = 'user_followings'
3542 __tablename__ = 'user_followings'
3539 __table_args__ = (
3543 __table_args__ = (
3540 UniqueConstraint('user_id', 'follows_repository_id'),
3544 UniqueConstraint('user_id', 'follows_repository_id'),
3541 UniqueConstraint('user_id', 'follows_user_id'),
3545 UniqueConstraint('user_id', 'follows_user_id'),
3542 base_table_args
3546 base_table_args
3543 )
3547 )
3544
3548
3545 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3549 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3546 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3550 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3547 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3551 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3548 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3552 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3549 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3553 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3550
3554
3551 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3555 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3552
3556
3553 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3557 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3554 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3558 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3555
3559
3556 @classmethod
3560 @classmethod
3557 def get_repo_followers(cls, repo_id):
3561 def get_repo_followers(cls, repo_id):
3558 return cls.query().filter(cls.follows_repo_id == repo_id)
3562 return cls.query().filter(cls.follows_repo_id == repo_id)
3559
3563
3560
3564
3561 class CacheKey(Base, BaseModel):
3565 class CacheKey(Base, BaseModel):
3562 __tablename__ = 'cache_invalidation'
3566 __tablename__ = 'cache_invalidation'
3563 __table_args__ = (
3567 __table_args__ = (
3564 UniqueConstraint('cache_key'),
3568 UniqueConstraint('cache_key'),
3565 Index('key_idx', 'cache_key'),
3569 Index('key_idx', 'cache_key'),
3566 base_table_args,
3570 base_table_args,
3567 )
3571 )
3568
3572
3569 CACHE_TYPE_FEED = 'FEED'
3573 CACHE_TYPE_FEED = 'FEED'
3570
3574
3571 # namespaces used to register process/thread aware caches
3575 # namespaces used to register process/thread aware caches
3572 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3576 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3573 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3577 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3574
3578
3575 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3579 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3576 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3580 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3577 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3581 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3578 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3582 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3579 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3583 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3580
3584
3581 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3585 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3582 self.cache_key = cache_key
3586 self.cache_key = cache_key
3583 self.cache_args = cache_args
3587 self.cache_args = cache_args
3584 self.cache_active = False
3588 self.cache_active = False
3585 # first key should be same for all entries, since all workers should share it
3589 # first key should be same for all entries, since all workers should share it
3586 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3590 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3587
3591
3588 def __unicode__(self):
3592 def __unicode__(self):
3589 return u"<%s('%s:%s[%s]')>" % (
3593 return u"<%s('%s:%s[%s]')>" % (
3590 self.__class__.__name__,
3594 self.__class__.__name__,
3591 self.cache_id, self.cache_key, self.cache_active)
3595 self.cache_id, self.cache_key, self.cache_active)
3592
3596
3593 def _cache_key_partition(self):
3597 def _cache_key_partition(self):
3594 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3598 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3595 return prefix, repo_name, suffix
3599 return prefix, repo_name, suffix
3596
3600
3597 def get_prefix(self):
3601 def get_prefix(self):
3598 """
3602 """
3599 Try to extract prefix from existing cache key. The key could consist
3603 Try to extract prefix from existing cache key. The key could consist
3600 of prefix, repo_name, suffix
3604 of prefix, repo_name, suffix
3601 """
3605 """
3602 # this returns prefix, repo_name, suffix
3606 # this returns prefix, repo_name, suffix
3603 return self._cache_key_partition()[0]
3607 return self._cache_key_partition()[0]
3604
3608
3605 def get_suffix(self):
3609 def get_suffix(self):
3606 """
3610 """
3607 get suffix that might have been used in _get_cache_key to
3611 get suffix that might have been used in _get_cache_key to
3608 generate self.cache_key. Only used for informational purposes
3612 generate self.cache_key. Only used for informational purposes
3609 in repo_edit.mako.
3613 in repo_edit.mako.
3610 """
3614 """
3611 # prefix, repo_name, suffix
3615 # prefix, repo_name, suffix
3612 return self._cache_key_partition()[2]
3616 return self._cache_key_partition()[2]
3613
3617
3614 @classmethod
3618 @classmethod
3615 def generate_new_state_uid(cls, based_on=None):
3619 def generate_new_state_uid(cls, based_on=None):
3616 if based_on:
3620 if based_on:
3617 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3621 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3618 else:
3622 else:
3619 return str(uuid.uuid4())
3623 return str(uuid.uuid4())
3620
3624
3621 @classmethod
3625 @classmethod
3622 def delete_all_cache(cls):
3626 def delete_all_cache(cls):
3623 """
3627 """
3624 Delete all cache keys from database.
3628 Delete all cache keys from database.
3625 Should only be run when all instances are down and all entries
3629 Should only be run when all instances are down and all entries
3626 thus stale.
3630 thus stale.
3627 """
3631 """
3628 cls.query().delete()
3632 cls.query().delete()
3629 Session().commit()
3633 Session().commit()
3630
3634
3631 @classmethod
3635 @classmethod
3632 def set_invalidate(cls, cache_uid, delete=False):
3636 def set_invalidate(cls, cache_uid, delete=False):
3633 """
3637 """
3634 Mark all caches of a repo as invalid in the database.
3638 Mark all caches of a repo as invalid in the database.
3635 """
3639 """
3636
3640
3637 try:
3641 try:
3638 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3642 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3639 if delete:
3643 if delete:
3640 qry.delete()
3644 qry.delete()
3641 log.debug('cache objects deleted for cache args %s',
3645 log.debug('cache objects deleted for cache args %s',
3642 safe_str(cache_uid))
3646 safe_str(cache_uid))
3643 else:
3647 else:
3644 qry.update({"cache_active": False,
3648 qry.update({"cache_active": False,
3645 "cache_state_uid": cls.generate_new_state_uid()})
3649 "cache_state_uid": cls.generate_new_state_uid()})
3646 log.debug('cache objects marked as invalid for cache args %s',
3650 log.debug('cache objects marked as invalid for cache args %s',
3647 safe_str(cache_uid))
3651 safe_str(cache_uid))
3648
3652
3649 Session().commit()
3653 Session().commit()
3650 except Exception:
3654 except Exception:
3651 log.exception(
3655 log.exception(
3652 'Cache key invalidation failed for cache args %s',
3656 'Cache key invalidation failed for cache args %s',
3653 safe_str(cache_uid))
3657 safe_str(cache_uid))
3654 Session().rollback()
3658 Session().rollback()
3655
3659
3656 @classmethod
3660 @classmethod
3657 def get_active_cache(cls, cache_key):
3661 def get_active_cache(cls, cache_key):
3658 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3662 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3659 if inv_obj:
3663 if inv_obj:
3660 return inv_obj
3664 return inv_obj
3661 return None
3665 return None
3662
3666
3663 @classmethod
3667 @classmethod
3664 def get_namespace_map(cls, namespace):
3668 def get_namespace_map(cls, namespace):
3665 return {
3669 return {
3666 x.cache_key: x
3670 x.cache_key: x
3667 for x in cls.query().filter(cls.cache_args == namespace)}
3671 for x in cls.query().filter(cls.cache_args == namespace)}
3668
3672
3669
3673
3670 class ChangesetComment(Base, BaseModel):
3674 class ChangesetComment(Base, BaseModel):
3671 __tablename__ = 'changeset_comments'
3675 __tablename__ = 'changeset_comments'
3672 __table_args__ = (
3676 __table_args__ = (
3673 Index('cc_revision_idx', 'revision'),
3677 Index('cc_revision_idx', 'revision'),
3674 base_table_args,
3678 base_table_args,
3675 )
3679 )
3676
3680
3677 COMMENT_OUTDATED = u'comment_outdated'
3681 COMMENT_OUTDATED = u'comment_outdated'
3678 COMMENT_TYPE_NOTE = u'note'
3682 COMMENT_TYPE_NOTE = u'note'
3679 COMMENT_TYPE_TODO = u'todo'
3683 COMMENT_TYPE_TODO = u'todo'
3680 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3684 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3681
3685
3682 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3686 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3683 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3687 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3684 revision = Column('revision', String(40), nullable=True)
3688 revision = Column('revision', String(40), nullable=True)
3685 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3689 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3686 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3690 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3687 line_no = Column('line_no', Unicode(10), nullable=True)
3691 line_no = Column('line_no', Unicode(10), nullable=True)
3688 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3692 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3689 f_path = Column('f_path', Unicode(1000), nullable=True)
3693 f_path = Column('f_path', Unicode(1000), nullable=True)
3690 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3694 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3691 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3695 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3692 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3696 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3693 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3697 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3694 renderer = Column('renderer', Unicode(64), nullable=True)
3698 renderer = Column('renderer', Unicode(64), nullable=True)
3695 display_state = Column('display_state', Unicode(128), nullable=True)
3699 display_state = Column('display_state', Unicode(128), nullable=True)
3696
3700
3697 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3701 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3698 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3702 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3699
3703
3700 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3704 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3701 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3705 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3702
3706
3703 author = relationship('User', lazy='joined')
3707 author = relationship('User', lazy='joined')
3704 repo = relationship('Repository')
3708 repo = relationship('Repository')
3705 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3709 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3706 pull_request = relationship('PullRequest', lazy='joined')
3710 pull_request = relationship('PullRequest', lazy='joined')
3707 pull_request_version = relationship('PullRequestVersion')
3711 pull_request_version = relationship('PullRequestVersion')
3708
3712
3709 @classmethod
3713 @classmethod
3710 def get_users(cls, revision=None, pull_request_id=None):
3714 def get_users(cls, revision=None, pull_request_id=None):
3711 """
3715 """
3712 Returns user associated with this ChangesetComment. ie those
3716 Returns user associated with this ChangesetComment. ie those
3713 who actually commented
3717 who actually commented
3714
3718
3715 :param cls:
3719 :param cls:
3716 :param revision:
3720 :param revision:
3717 """
3721 """
3718 q = Session().query(User)\
3722 q = Session().query(User)\
3719 .join(ChangesetComment.author)
3723 .join(ChangesetComment.author)
3720 if revision:
3724 if revision:
3721 q = q.filter(cls.revision == revision)
3725 q = q.filter(cls.revision == revision)
3722 elif pull_request_id:
3726 elif pull_request_id:
3723 q = q.filter(cls.pull_request_id == pull_request_id)
3727 q = q.filter(cls.pull_request_id == pull_request_id)
3724 return q.all()
3728 return q.all()
3725
3729
3726 @classmethod
3730 @classmethod
3727 def get_index_from_version(cls, pr_version, versions):
3731 def get_index_from_version(cls, pr_version, versions):
3728 num_versions = [x.pull_request_version_id for x in versions]
3732 num_versions = [x.pull_request_version_id for x in versions]
3729 try:
3733 try:
3730 return num_versions.index(pr_version) +1
3734 return num_versions.index(pr_version) +1
3731 except (IndexError, ValueError):
3735 except (IndexError, ValueError):
3732 return
3736 return
3733
3737
3734 @property
3738 @property
3735 def outdated(self):
3739 def outdated(self):
3736 return self.display_state == self.COMMENT_OUTDATED
3740 return self.display_state == self.COMMENT_OUTDATED
3737
3741
3738 def outdated_at_version(self, version):
3742 def outdated_at_version(self, version):
3739 """
3743 """
3740 Checks if comment is outdated for given pull request version
3744 Checks if comment is outdated for given pull request version
3741 """
3745 """
3742 return self.outdated and self.pull_request_version_id != version
3746 return self.outdated and self.pull_request_version_id != version
3743
3747
3744 def older_than_version(self, version):
3748 def older_than_version(self, version):
3745 """
3749 """
3746 Checks if comment is made from previous version than given
3750 Checks if comment is made from previous version than given
3747 """
3751 """
3748 if version is None:
3752 if version is None:
3749 return self.pull_request_version_id is not None
3753 return self.pull_request_version_id is not None
3750
3754
3751 return self.pull_request_version_id < version
3755 return self.pull_request_version_id < version
3752
3756
3753 @property
3757 @property
3754 def resolved(self):
3758 def resolved(self):
3755 return self.resolved_by[0] if self.resolved_by else None
3759 return self.resolved_by[0] if self.resolved_by else None
3756
3760
3757 @property
3761 @property
3758 def is_todo(self):
3762 def is_todo(self):
3759 return self.comment_type == self.COMMENT_TYPE_TODO
3763 return self.comment_type == self.COMMENT_TYPE_TODO
3760
3764
3761 @property
3765 @property
3762 def is_inline(self):
3766 def is_inline(self):
3763 return self.line_no and self.f_path
3767 return self.line_no and self.f_path
3764
3768
3765 def get_index_version(self, versions):
3769 def get_index_version(self, versions):
3766 return self.get_index_from_version(
3770 return self.get_index_from_version(
3767 self.pull_request_version_id, versions)
3771 self.pull_request_version_id, versions)
3768
3772
3769 def __repr__(self):
3773 def __repr__(self):
3770 if self.comment_id:
3774 if self.comment_id:
3771 return '<DB:Comment #%s>' % self.comment_id
3775 return '<DB:Comment #%s>' % self.comment_id
3772 else:
3776 else:
3773 return '<DB:Comment at %#x>' % id(self)
3777 return '<DB:Comment at %#x>' % id(self)
3774
3778
3775 def get_api_data(self):
3779 def get_api_data(self):
3776 comment = self
3780 comment = self
3777 data = {
3781 data = {
3778 'comment_id': comment.comment_id,
3782 'comment_id': comment.comment_id,
3779 'comment_type': comment.comment_type,
3783 'comment_type': comment.comment_type,
3780 'comment_text': comment.text,
3784 'comment_text': comment.text,
3781 'comment_status': comment.status_change,
3785 'comment_status': comment.status_change,
3782 'comment_f_path': comment.f_path,
3786 'comment_f_path': comment.f_path,
3783 'comment_lineno': comment.line_no,
3787 'comment_lineno': comment.line_no,
3784 'comment_author': comment.author,
3788 'comment_author': comment.author,
3785 'comment_created_on': comment.created_on,
3789 'comment_created_on': comment.created_on,
3786 'comment_resolved_by': self.resolved
3790 'comment_resolved_by': self.resolved
3787 }
3791 }
3788 return data
3792 return data
3789
3793
3790 def __json__(self):
3794 def __json__(self):
3791 data = dict()
3795 data = dict()
3792 data.update(self.get_api_data())
3796 data.update(self.get_api_data())
3793 return data
3797 return data
3794
3798
3795
3799
3796 class ChangesetStatus(Base, BaseModel):
3800 class ChangesetStatus(Base, BaseModel):
3797 __tablename__ = 'changeset_statuses'
3801 __tablename__ = 'changeset_statuses'
3798 __table_args__ = (
3802 __table_args__ = (
3799 Index('cs_revision_idx', 'revision'),
3803 Index('cs_revision_idx', 'revision'),
3800 Index('cs_version_idx', 'version'),
3804 Index('cs_version_idx', 'version'),
3801 UniqueConstraint('repo_id', 'revision', 'version'),
3805 UniqueConstraint('repo_id', 'revision', 'version'),
3802 base_table_args
3806 base_table_args
3803 )
3807 )
3804
3808
3805 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3809 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3806 STATUS_APPROVED = 'approved'
3810 STATUS_APPROVED = 'approved'
3807 STATUS_REJECTED = 'rejected'
3811 STATUS_REJECTED = 'rejected'
3808 STATUS_UNDER_REVIEW = 'under_review'
3812 STATUS_UNDER_REVIEW = 'under_review'
3809
3813
3810 STATUSES = [
3814 STATUSES = [
3811 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3815 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3812 (STATUS_APPROVED, _("Approved")),
3816 (STATUS_APPROVED, _("Approved")),
3813 (STATUS_REJECTED, _("Rejected")),
3817 (STATUS_REJECTED, _("Rejected")),
3814 (STATUS_UNDER_REVIEW, _("Under Review")),
3818 (STATUS_UNDER_REVIEW, _("Under Review")),
3815 ]
3819 ]
3816
3820
3817 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3821 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3818 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3822 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3819 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3823 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3820 revision = Column('revision', String(40), nullable=False)
3824 revision = Column('revision', String(40), nullable=False)
3821 status = Column('status', String(128), nullable=False, default=DEFAULT)
3825 status = Column('status', String(128), nullable=False, default=DEFAULT)
3822 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3826 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3823 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3827 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3824 version = Column('version', Integer(), nullable=False, default=0)
3828 version = Column('version', Integer(), nullable=False, default=0)
3825 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3829 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3826
3830
3827 author = relationship('User', lazy='joined')
3831 author = relationship('User', lazy='joined')
3828 repo = relationship('Repository')
3832 repo = relationship('Repository')
3829 comment = relationship('ChangesetComment', lazy='joined')
3833 comment = relationship('ChangesetComment', lazy='joined')
3830 pull_request = relationship('PullRequest', lazy='joined')
3834 pull_request = relationship('PullRequest', lazy='joined')
3831
3835
3832 def __unicode__(self):
3836 def __unicode__(self):
3833 return u"<%s('%s[v%s]:%s')>" % (
3837 return u"<%s('%s[v%s]:%s')>" % (
3834 self.__class__.__name__,
3838 self.__class__.__name__,
3835 self.status, self.version, self.author
3839 self.status, self.version, self.author
3836 )
3840 )
3837
3841
3838 @classmethod
3842 @classmethod
3839 def get_status_lbl(cls, value):
3843 def get_status_lbl(cls, value):
3840 return dict(cls.STATUSES).get(value)
3844 return dict(cls.STATUSES).get(value)
3841
3845
3842 @property
3846 @property
3843 def status_lbl(self):
3847 def status_lbl(self):
3844 return ChangesetStatus.get_status_lbl(self.status)
3848 return ChangesetStatus.get_status_lbl(self.status)
3845
3849
3846 def get_api_data(self):
3850 def get_api_data(self):
3847 status = self
3851 status = self
3848 data = {
3852 data = {
3849 'status_id': status.changeset_status_id,
3853 'status_id': status.changeset_status_id,
3850 'status': status.status,
3854 'status': status.status,
3851 }
3855 }
3852 return data
3856 return data
3853
3857
3854 def __json__(self):
3858 def __json__(self):
3855 data = dict()
3859 data = dict()
3856 data.update(self.get_api_data())
3860 data.update(self.get_api_data())
3857 return data
3861 return data
3858
3862
3859
3863
3860 class _SetState(object):
3864 class _SetState(object):
3861 """
3865 """
3862 Context processor allowing changing state for sensitive operation such as
3866 Context processor allowing changing state for sensitive operation such as
3863 pull request update or merge
3867 pull request update or merge
3864 """
3868 """
3865
3869
3866 def __init__(self, pull_request, pr_state, back_state=None):
3870 def __init__(self, pull_request, pr_state, back_state=None):
3867 self._pr = pull_request
3871 self._pr = pull_request
3868 self._org_state = back_state or pull_request.pull_request_state
3872 self._org_state = back_state or pull_request.pull_request_state
3869 self._pr_state = pr_state
3873 self._pr_state = pr_state
3870 self._current_state = None
3874 self._current_state = None
3871
3875
3872 def __enter__(self):
3876 def __enter__(self):
3873 log.debug('StateLock: entering set state context, setting state to: `%s`',
3877 log.debug('StateLock: entering set state context, setting state to: `%s`',
3874 self._pr_state)
3878 self._pr_state)
3875 self.set_pr_state(self._pr_state)
3879 self.set_pr_state(self._pr_state)
3876 return self
3880 return self
3877
3881
3878 def __exit__(self, exc_type, exc_val, exc_tb):
3882 def __exit__(self, exc_type, exc_val, exc_tb):
3879 if exc_val is not None:
3883 if exc_val is not None:
3880 log.error(traceback.format_exc(exc_tb))
3884 log.error(traceback.format_exc(exc_tb))
3881 return None
3885 return None
3882
3886
3883 self.set_pr_state(self._org_state)
3887 self.set_pr_state(self._org_state)
3884 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3888 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3885 self._org_state)
3889 self._org_state)
3886 @property
3890 @property
3887 def state(self):
3891 def state(self):
3888 return self._current_state
3892 return self._current_state
3889
3893
3890 def set_pr_state(self, pr_state):
3894 def set_pr_state(self, pr_state):
3891 try:
3895 try:
3892 self._pr.pull_request_state = pr_state
3896 self._pr.pull_request_state = pr_state
3893 Session().add(self._pr)
3897 Session().add(self._pr)
3894 Session().commit()
3898 Session().commit()
3895 self._current_state = pr_state
3899 self._current_state = pr_state
3896 except Exception:
3900 except Exception:
3897 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3901 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3898 raise
3902 raise
3899
3903
3900
3904
3901 class _PullRequestBase(BaseModel):
3905 class _PullRequestBase(BaseModel):
3902 """
3906 """
3903 Common attributes of pull request and version entries.
3907 Common attributes of pull request and version entries.
3904 """
3908 """
3905
3909
3906 # .status values
3910 # .status values
3907 STATUS_NEW = u'new'
3911 STATUS_NEW = u'new'
3908 STATUS_OPEN = u'open'
3912 STATUS_OPEN = u'open'
3909 STATUS_CLOSED = u'closed'
3913 STATUS_CLOSED = u'closed'
3910
3914
3911 # available states
3915 # available states
3912 STATE_CREATING = u'creating'
3916 STATE_CREATING = u'creating'
3913 STATE_UPDATING = u'updating'
3917 STATE_UPDATING = u'updating'
3914 STATE_MERGING = u'merging'
3918 STATE_MERGING = u'merging'
3915 STATE_CREATED = u'created'
3919 STATE_CREATED = u'created'
3916
3920
3917 title = Column('title', Unicode(255), nullable=True)
3921 title = Column('title', Unicode(255), nullable=True)
3918 description = Column(
3922 description = Column(
3919 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3923 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3920 nullable=True)
3924 nullable=True)
3921 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3925 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3922
3926
3923 # new/open/closed status of pull request (not approve/reject/etc)
3927 # new/open/closed status of pull request (not approve/reject/etc)
3924 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3928 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3925 created_on = Column(
3929 created_on = Column(
3926 'created_on', DateTime(timezone=False), nullable=False,
3930 'created_on', DateTime(timezone=False), nullable=False,
3927 default=datetime.datetime.now)
3931 default=datetime.datetime.now)
3928 updated_on = Column(
3932 updated_on = Column(
3929 'updated_on', DateTime(timezone=False), nullable=False,
3933 'updated_on', DateTime(timezone=False), nullable=False,
3930 default=datetime.datetime.now)
3934 default=datetime.datetime.now)
3931
3935
3932 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3936 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3933
3937
3934 @declared_attr
3938 @declared_attr
3935 def user_id(cls):
3939 def user_id(cls):
3936 return Column(
3940 return Column(
3937 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3941 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3938 unique=None)
3942 unique=None)
3939
3943
3940 # 500 revisions max
3944 # 500 revisions max
3941 _revisions = Column(
3945 _revisions = Column(
3942 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3946 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3943
3947
3944 @declared_attr
3948 @declared_attr
3945 def source_repo_id(cls):
3949 def source_repo_id(cls):
3946 # TODO: dan: rename column to source_repo_id
3950 # TODO: dan: rename column to source_repo_id
3947 return Column(
3951 return Column(
3948 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3952 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3949 nullable=False)
3953 nullable=False)
3950
3954
3951 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3955 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3952
3956
3953 @hybrid_property
3957 @hybrid_property
3954 def source_ref(self):
3958 def source_ref(self):
3955 return self._source_ref
3959 return self._source_ref
3956
3960
3957 @source_ref.setter
3961 @source_ref.setter
3958 def source_ref(self, val):
3962 def source_ref(self, val):
3959 parts = (val or '').split(':')
3963 parts = (val or '').split(':')
3960 if len(parts) != 3:
3964 if len(parts) != 3:
3961 raise ValueError(
3965 raise ValueError(
3962 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3966 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3963 self._source_ref = safe_unicode(val)
3967 self._source_ref = safe_unicode(val)
3964
3968
3965 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3969 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3966
3970
3967 @hybrid_property
3971 @hybrid_property
3968 def target_ref(self):
3972 def target_ref(self):
3969 return self._target_ref
3973 return self._target_ref
3970
3974
3971 @target_ref.setter
3975 @target_ref.setter
3972 def target_ref(self, val):
3976 def target_ref(self, val):
3973 parts = (val or '').split(':')
3977 parts = (val or '').split(':')
3974 if len(parts) != 3:
3978 if len(parts) != 3:
3975 raise ValueError(
3979 raise ValueError(
3976 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3980 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3977 self._target_ref = safe_unicode(val)
3981 self._target_ref = safe_unicode(val)
3978
3982
3979 @declared_attr
3983 @declared_attr
3980 def target_repo_id(cls):
3984 def target_repo_id(cls):
3981 # TODO: dan: rename column to target_repo_id
3985 # TODO: dan: rename column to target_repo_id
3982 return Column(
3986 return Column(
3983 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3987 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3984 nullable=False)
3988 nullable=False)
3985
3989
3986 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3990 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3987
3991
3988 # TODO: dan: rename column to last_merge_source_rev
3992 # TODO: dan: rename column to last_merge_source_rev
3989 _last_merge_source_rev = Column(
3993 _last_merge_source_rev = Column(
3990 'last_merge_org_rev', String(40), nullable=True)
3994 'last_merge_org_rev', String(40), nullable=True)
3991 # TODO: dan: rename column to last_merge_target_rev
3995 # TODO: dan: rename column to last_merge_target_rev
3992 _last_merge_target_rev = Column(
3996 _last_merge_target_rev = Column(
3993 'last_merge_other_rev', String(40), nullable=True)
3997 'last_merge_other_rev', String(40), nullable=True)
3994 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3998 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3995 merge_rev = Column('merge_rev', String(40), nullable=True)
3999 merge_rev = Column('merge_rev', String(40), nullable=True)
3996
4000
3997 reviewer_data = Column(
4001 reviewer_data = Column(
3998 'reviewer_data_json', MutationObj.as_mutable(
4002 'reviewer_data_json', MutationObj.as_mutable(
3999 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4003 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4000
4004
4001 @property
4005 @property
4002 def reviewer_data_json(self):
4006 def reviewer_data_json(self):
4003 return json.dumps(self.reviewer_data)
4007 return json.dumps(self.reviewer_data)
4004
4008
4005 @property
4009 @property
4006 def work_in_progress(self):
4010 def work_in_progress(self):
4007 """checks if pull request is work in progress by checking the title"""
4011 """checks if pull request is work in progress by checking the title"""
4008 title = self.title.upper()
4012 title = self.title.upper()
4009 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4013 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4010 return True
4014 return True
4011 return False
4015 return False
4012
4016
4013 @hybrid_property
4017 @hybrid_property
4014 def description_safe(self):
4018 def description_safe(self):
4015 from rhodecode.lib import helpers as h
4019 from rhodecode.lib import helpers as h
4016 return h.escape(self.description)
4020 return h.escape(self.description)
4017
4021
4018 @hybrid_property
4022 @hybrid_property
4019 def revisions(self):
4023 def revisions(self):
4020 return self._revisions.split(':') if self._revisions else []
4024 return self._revisions.split(':') if self._revisions else []
4021
4025
4022 @revisions.setter
4026 @revisions.setter
4023 def revisions(self, val):
4027 def revisions(self, val):
4024 self._revisions = u':'.join(val)
4028 self._revisions = u':'.join(val)
4025
4029
4026 @hybrid_property
4030 @hybrid_property
4027 def last_merge_status(self):
4031 def last_merge_status(self):
4028 return safe_int(self._last_merge_status)
4032 return safe_int(self._last_merge_status)
4029
4033
4030 @last_merge_status.setter
4034 @last_merge_status.setter
4031 def last_merge_status(self, val):
4035 def last_merge_status(self, val):
4032 self._last_merge_status = val
4036 self._last_merge_status = val
4033
4037
4034 @declared_attr
4038 @declared_attr
4035 def author(cls):
4039 def author(cls):
4036 return relationship('User', lazy='joined')
4040 return relationship('User', lazy='joined')
4037
4041
4038 @declared_attr
4042 @declared_attr
4039 def source_repo(cls):
4043 def source_repo(cls):
4040 return relationship(
4044 return relationship(
4041 'Repository',
4045 'Repository',
4042 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4046 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4043
4047
4044 @property
4048 @property
4045 def source_ref_parts(self):
4049 def source_ref_parts(self):
4046 return self.unicode_to_reference(self.source_ref)
4050 return self.unicode_to_reference(self.source_ref)
4047
4051
4048 @declared_attr
4052 @declared_attr
4049 def target_repo(cls):
4053 def target_repo(cls):
4050 return relationship(
4054 return relationship(
4051 'Repository',
4055 'Repository',
4052 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4056 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4053
4057
4054 @property
4058 @property
4055 def target_ref_parts(self):
4059 def target_ref_parts(self):
4056 return self.unicode_to_reference(self.target_ref)
4060 return self.unicode_to_reference(self.target_ref)
4057
4061
4058 @property
4062 @property
4059 def shadow_merge_ref(self):
4063 def shadow_merge_ref(self):
4060 return self.unicode_to_reference(self._shadow_merge_ref)
4064 return self.unicode_to_reference(self._shadow_merge_ref)
4061
4065
4062 @shadow_merge_ref.setter
4066 @shadow_merge_ref.setter
4063 def shadow_merge_ref(self, ref):
4067 def shadow_merge_ref(self, ref):
4064 self._shadow_merge_ref = self.reference_to_unicode(ref)
4068 self._shadow_merge_ref = self.reference_to_unicode(ref)
4065
4069
4066 @staticmethod
4070 @staticmethod
4067 def unicode_to_reference(raw):
4071 def unicode_to_reference(raw):
4068 """
4072 """
4069 Convert a unicode (or string) to a reference object.
4073 Convert a unicode (or string) to a reference object.
4070 If unicode evaluates to False it returns None.
4074 If unicode evaluates to False it returns None.
4071 """
4075 """
4072 if raw:
4076 if raw:
4073 refs = raw.split(':')
4077 refs = raw.split(':')
4074 return Reference(*refs)
4078 return Reference(*refs)
4075 else:
4079 else:
4076 return None
4080 return None
4077
4081
4078 @staticmethod
4082 @staticmethod
4079 def reference_to_unicode(ref):
4083 def reference_to_unicode(ref):
4080 """
4084 """
4081 Convert a reference object to unicode.
4085 Convert a reference object to unicode.
4082 If reference is None it returns None.
4086 If reference is None it returns None.
4083 """
4087 """
4084 if ref:
4088 if ref:
4085 return u':'.join(ref)
4089 return u':'.join(ref)
4086 else:
4090 else:
4087 return None
4091 return None
4088
4092
4089 def get_api_data(self, with_merge_state=True):
4093 def get_api_data(self, with_merge_state=True):
4090 from rhodecode.model.pull_request import PullRequestModel
4094 from rhodecode.model.pull_request import PullRequestModel
4091
4095
4092 pull_request = self
4096 pull_request = self
4093 if with_merge_state:
4097 if with_merge_state:
4094 merge_status = PullRequestModel().merge_status(pull_request)
4098 merge_status = PullRequestModel().merge_status(pull_request)
4095 merge_state = {
4099 merge_state = {
4096 'status': merge_status[0],
4100 'status': merge_status[0],
4097 'message': safe_unicode(merge_status[1]),
4101 'message': safe_unicode(merge_status[1]),
4098 }
4102 }
4099 else:
4103 else:
4100 merge_state = {'status': 'not_available',
4104 merge_state = {'status': 'not_available',
4101 'message': 'not_available'}
4105 'message': 'not_available'}
4102
4106
4103 merge_data = {
4107 merge_data = {
4104 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4108 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4105 'reference': (
4109 'reference': (
4106 pull_request.shadow_merge_ref._asdict()
4110 pull_request.shadow_merge_ref._asdict()
4107 if pull_request.shadow_merge_ref else None),
4111 if pull_request.shadow_merge_ref else None),
4108 }
4112 }
4109
4113
4110 data = {
4114 data = {
4111 'pull_request_id': pull_request.pull_request_id,
4115 'pull_request_id': pull_request.pull_request_id,
4112 'url': PullRequestModel().get_url(pull_request),
4116 'url': PullRequestModel().get_url(pull_request),
4113 'title': pull_request.title,
4117 'title': pull_request.title,
4114 'description': pull_request.description,
4118 'description': pull_request.description,
4115 'status': pull_request.status,
4119 'status': pull_request.status,
4116 'state': pull_request.pull_request_state,
4120 'state': pull_request.pull_request_state,
4117 'created_on': pull_request.created_on,
4121 'created_on': pull_request.created_on,
4118 'updated_on': pull_request.updated_on,
4122 'updated_on': pull_request.updated_on,
4119 'commit_ids': pull_request.revisions,
4123 'commit_ids': pull_request.revisions,
4120 'review_status': pull_request.calculated_review_status(),
4124 'review_status': pull_request.calculated_review_status(),
4121 'mergeable': merge_state,
4125 'mergeable': merge_state,
4122 'source': {
4126 'source': {
4123 'clone_url': pull_request.source_repo.clone_url(),
4127 'clone_url': pull_request.source_repo.clone_url(),
4124 'repository': pull_request.source_repo.repo_name,
4128 'repository': pull_request.source_repo.repo_name,
4125 'reference': {
4129 'reference': {
4126 'name': pull_request.source_ref_parts.name,
4130 'name': pull_request.source_ref_parts.name,
4127 'type': pull_request.source_ref_parts.type,
4131 'type': pull_request.source_ref_parts.type,
4128 'commit_id': pull_request.source_ref_parts.commit_id,
4132 'commit_id': pull_request.source_ref_parts.commit_id,
4129 },
4133 },
4130 },
4134 },
4131 'target': {
4135 'target': {
4132 'clone_url': pull_request.target_repo.clone_url(),
4136 'clone_url': pull_request.target_repo.clone_url(),
4133 'repository': pull_request.target_repo.repo_name,
4137 'repository': pull_request.target_repo.repo_name,
4134 'reference': {
4138 'reference': {
4135 'name': pull_request.target_ref_parts.name,
4139 'name': pull_request.target_ref_parts.name,
4136 'type': pull_request.target_ref_parts.type,
4140 'type': pull_request.target_ref_parts.type,
4137 'commit_id': pull_request.target_ref_parts.commit_id,
4141 'commit_id': pull_request.target_ref_parts.commit_id,
4138 },
4142 },
4139 },
4143 },
4140 'merge': merge_data,
4144 'merge': merge_data,
4141 'author': pull_request.author.get_api_data(include_secrets=False,
4145 'author': pull_request.author.get_api_data(include_secrets=False,
4142 details='basic'),
4146 details='basic'),
4143 'reviewers': [
4147 'reviewers': [
4144 {
4148 {
4145 'user': reviewer.get_api_data(include_secrets=False,
4149 'user': reviewer.get_api_data(include_secrets=False,
4146 details='basic'),
4150 details='basic'),
4147 'reasons': reasons,
4151 'reasons': reasons,
4148 'review_status': st[0][1].status if st else 'not_reviewed',
4152 'review_status': st[0][1].status if st else 'not_reviewed',
4149 }
4153 }
4150 for obj, reviewer, reasons, mandatory, st in
4154 for obj, reviewer, reasons, mandatory, st in
4151 pull_request.reviewers_statuses()
4155 pull_request.reviewers_statuses()
4152 ]
4156 ]
4153 }
4157 }
4154
4158
4155 return data
4159 return data
4156
4160
4157 def set_state(self, pull_request_state, final_state=None):
4161 def set_state(self, pull_request_state, final_state=None):
4158 """
4162 """
4159 # goes from initial state to updating to initial state.
4163 # goes from initial state to updating to initial state.
4160 # initial state can be changed by specifying back_state=
4164 # initial state can be changed by specifying back_state=
4161 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4165 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4162 pull_request.merge()
4166 pull_request.merge()
4163
4167
4164 :param pull_request_state:
4168 :param pull_request_state:
4165 :param final_state:
4169 :param final_state:
4166
4170
4167 """
4171 """
4168
4172
4169 return _SetState(self, pull_request_state, back_state=final_state)
4173 return _SetState(self, pull_request_state, back_state=final_state)
4170
4174
4171
4175
4172 class PullRequest(Base, _PullRequestBase):
4176 class PullRequest(Base, _PullRequestBase):
4173 __tablename__ = 'pull_requests'
4177 __tablename__ = 'pull_requests'
4174 __table_args__ = (
4178 __table_args__ = (
4175 base_table_args,
4179 base_table_args,
4176 )
4180 )
4177
4181
4178 pull_request_id = Column(
4182 pull_request_id = Column(
4179 'pull_request_id', Integer(), nullable=False, primary_key=True)
4183 'pull_request_id', Integer(), nullable=False, primary_key=True)
4180
4184
4181 def __repr__(self):
4185 def __repr__(self):
4182 if self.pull_request_id:
4186 if self.pull_request_id:
4183 return '<DB:PullRequest #%s>' % self.pull_request_id
4187 return '<DB:PullRequest #%s>' % self.pull_request_id
4184 else:
4188 else:
4185 return '<DB:PullRequest at %#x>' % id(self)
4189 return '<DB:PullRequest at %#x>' % id(self)
4186
4190
4187 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4191 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4188 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4192 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4189 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4193 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4190 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4194 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4191 lazy='dynamic')
4195 lazy='dynamic')
4192
4196
4193 @classmethod
4197 @classmethod
4194 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4198 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4195 internal_methods=None):
4199 internal_methods=None):
4196
4200
4197 class PullRequestDisplay(object):
4201 class PullRequestDisplay(object):
4198 """
4202 """
4199 Special object wrapper for showing PullRequest data via Versions
4203 Special object wrapper for showing PullRequest data via Versions
4200 It mimics PR object as close as possible. This is read only object
4204 It mimics PR object as close as possible. This is read only object
4201 just for display
4205 just for display
4202 """
4206 """
4203
4207
4204 def __init__(self, attrs, internal=None):
4208 def __init__(self, attrs, internal=None):
4205 self.attrs = attrs
4209 self.attrs = attrs
4206 # internal have priority over the given ones via attrs
4210 # internal have priority over the given ones via attrs
4207 self.internal = internal or ['versions']
4211 self.internal = internal or ['versions']
4208
4212
4209 def __getattr__(self, item):
4213 def __getattr__(self, item):
4210 if item in self.internal:
4214 if item in self.internal:
4211 return getattr(self, item)
4215 return getattr(self, item)
4212 try:
4216 try:
4213 return self.attrs[item]
4217 return self.attrs[item]
4214 except KeyError:
4218 except KeyError:
4215 raise AttributeError(
4219 raise AttributeError(
4216 '%s object has no attribute %s' % (self, item))
4220 '%s object has no attribute %s' % (self, item))
4217
4221
4218 def __repr__(self):
4222 def __repr__(self):
4219 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4223 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4220
4224
4221 def versions(self):
4225 def versions(self):
4222 return pull_request_obj.versions.order_by(
4226 return pull_request_obj.versions.order_by(
4223 PullRequestVersion.pull_request_version_id).all()
4227 PullRequestVersion.pull_request_version_id).all()
4224
4228
4225 def is_closed(self):
4229 def is_closed(self):
4226 return pull_request_obj.is_closed()
4230 return pull_request_obj.is_closed()
4227
4231
4228 def is_state_changing(self):
4232 def is_state_changing(self):
4229 return pull_request_obj.is_state_changing()
4233 return pull_request_obj.is_state_changing()
4230
4234
4231 @property
4235 @property
4232 def pull_request_version_id(self):
4236 def pull_request_version_id(self):
4233 return getattr(pull_request_obj, 'pull_request_version_id', None)
4237 return getattr(pull_request_obj, 'pull_request_version_id', None)
4234
4238
4235 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4239 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4236
4240
4237 attrs.author = StrictAttributeDict(
4241 attrs.author = StrictAttributeDict(
4238 pull_request_obj.author.get_api_data())
4242 pull_request_obj.author.get_api_data())
4239 if pull_request_obj.target_repo:
4243 if pull_request_obj.target_repo:
4240 attrs.target_repo = StrictAttributeDict(
4244 attrs.target_repo = StrictAttributeDict(
4241 pull_request_obj.target_repo.get_api_data())
4245 pull_request_obj.target_repo.get_api_data())
4242 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4246 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4243
4247
4244 if pull_request_obj.source_repo:
4248 if pull_request_obj.source_repo:
4245 attrs.source_repo = StrictAttributeDict(
4249 attrs.source_repo = StrictAttributeDict(
4246 pull_request_obj.source_repo.get_api_data())
4250 pull_request_obj.source_repo.get_api_data())
4247 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4251 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4248
4252
4249 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4253 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4250 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4254 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4251 attrs.revisions = pull_request_obj.revisions
4255 attrs.revisions = pull_request_obj.revisions
4252
4256
4253 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4257 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4254 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4258 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4255 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4259 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4256
4260
4257 return PullRequestDisplay(attrs, internal=internal_methods)
4261 return PullRequestDisplay(attrs, internal=internal_methods)
4258
4262
4259 def is_closed(self):
4263 def is_closed(self):
4260 return self.status == self.STATUS_CLOSED
4264 return self.status == self.STATUS_CLOSED
4261
4265
4262 def is_state_changing(self):
4266 def is_state_changing(self):
4263 return self.pull_request_state != PullRequest.STATE_CREATED
4267 return self.pull_request_state != PullRequest.STATE_CREATED
4264
4268
4265 def __json__(self):
4269 def __json__(self):
4266 return {
4270 return {
4267 'revisions': self.revisions,
4271 'revisions': self.revisions,
4268 }
4272 }
4269
4273
4270 def calculated_review_status(self):
4274 def calculated_review_status(self):
4271 from rhodecode.model.changeset_status import ChangesetStatusModel
4275 from rhodecode.model.changeset_status import ChangesetStatusModel
4272 return ChangesetStatusModel().calculated_review_status(self)
4276 return ChangesetStatusModel().calculated_review_status(self)
4273
4277
4274 def reviewers_statuses(self):
4278 def reviewers_statuses(self):
4275 from rhodecode.model.changeset_status import ChangesetStatusModel
4279 from rhodecode.model.changeset_status import ChangesetStatusModel
4276 return ChangesetStatusModel().reviewers_statuses(self)
4280 return ChangesetStatusModel().reviewers_statuses(self)
4277
4281
4278 @property
4282 @property
4279 def workspace_id(self):
4283 def workspace_id(self):
4280 from rhodecode.model.pull_request import PullRequestModel
4284 from rhodecode.model.pull_request import PullRequestModel
4281 return PullRequestModel()._workspace_id(self)
4285 return PullRequestModel()._workspace_id(self)
4282
4286
4283 def get_shadow_repo(self):
4287 def get_shadow_repo(self):
4284 workspace_id = self.workspace_id
4288 workspace_id = self.workspace_id
4285 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4289 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4286 if os.path.isdir(shadow_repository_path):
4290 if os.path.isdir(shadow_repository_path):
4287 vcs_obj = self.target_repo.scm_instance()
4291 vcs_obj = self.target_repo.scm_instance()
4288 return vcs_obj.get_shadow_instance(shadow_repository_path)
4292 return vcs_obj.get_shadow_instance(shadow_repository_path)
4289
4293
4290
4294
4291 class PullRequestVersion(Base, _PullRequestBase):
4295 class PullRequestVersion(Base, _PullRequestBase):
4292 __tablename__ = 'pull_request_versions'
4296 __tablename__ = 'pull_request_versions'
4293 __table_args__ = (
4297 __table_args__ = (
4294 base_table_args,
4298 base_table_args,
4295 )
4299 )
4296
4300
4297 pull_request_version_id = Column(
4301 pull_request_version_id = Column(
4298 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4302 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4299 pull_request_id = Column(
4303 pull_request_id = Column(
4300 'pull_request_id', Integer(),
4304 'pull_request_id', Integer(),
4301 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4305 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4302 pull_request = relationship('PullRequest')
4306 pull_request = relationship('PullRequest')
4303
4307
4304 def __repr__(self):
4308 def __repr__(self):
4305 if self.pull_request_version_id:
4309 if self.pull_request_version_id:
4306 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4310 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4307 else:
4311 else:
4308 return '<DB:PullRequestVersion at %#x>' % id(self)
4312 return '<DB:PullRequestVersion at %#x>' % id(self)
4309
4313
4310 @property
4314 @property
4311 def reviewers(self):
4315 def reviewers(self):
4312 return self.pull_request.reviewers
4316 return self.pull_request.reviewers
4313
4317
4314 @property
4318 @property
4315 def versions(self):
4319 def versions(self):
4316 return self.pull_request.versions
4320 return self.pull_request.versions
4317
4321
4318 def is_closed(self):
4322 def is_closed(self):
4319 # calculate from original
4323 # calculate from original
4320 return self.pull_request.status == self.STATUS_CLOSED
4324 return self.pull_request.status == self.STATUS_CLOSED
4321
4325
4322 def is_state_changing(self):
4326 def is_state_changing(self):
4323 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4327 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4324
4328
4325 def calculated_review_status(self):
4329 def calculated_review_status(self):
4326 return self.pull_request.calculated_review_status()
4330 return self.pull_request.calculated_review_status()
4327
4331
4328 def reviewers_statuses(self):
4332 def reviewers_statuses(self):
4329 return self.pull_request.reviewers_statuses()
4333 return self.pull_request.reviewers_statuses()
4330
4334
4331
4335
4332 class PullRequestReviewers(Base, BaseModel):
4336 class PullRequestReviewers(Base, BaseModel):
4333 __tablename__ = 'pull_request_reviewers'
4337 __tablename__ = 'pull_request_reviewers'
4334 __table_args__ = (
4338 __table_args__ = (
4335 base_table_args,
4339 base_table_args,
4336 )
4340 )
4337
4341
4338 @hybrid_property
4342 @hybrid_property
4339 def reasons(self):
4343 def reasons(self):
4340 if not self._reasons:
4344 if not self._reasons:
4341 return []
4345 return []
4342 return self._reasons
4346 return self._reasons
4343
4347
4344 @reasons.setter
4348 @reasons.setter
4345 def reasons(self, val):
4349 def reasons(self, val):
4346 val = val or []
4350 val = val or []
4347 if any(not isinstance(x, compat.string_types) for x in val):
4351 if any(not isinstance(x, compat.string_types) for x in val):
4348 raise Exception('invalid reasons type, must be list of strings')
4352 raise Exception('invalid reasons type, must be list of strings')
4349 self._reasons = val
4353 self._reasons = val
4350
4354
4351 pull_requests_reviewers_id = Column(
4355 pull_requests_reviewers_id = Column(
4352 'pull_requests_reviewers_id', Integer(), nullable=False,
4356 'pull_requests_reviewers_id', Integer(), nullable=False,
4353 primary_key=True)
4357 primary_key=True)
4354 pull_request_id = Column(
4358 pull_request_id = Column(
4355 "pull_request_id", Integer(),
4359 "pull_request_id", Integer(),
4356 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4360 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4357 user_id = Column(
4361 user_id = Column(
4358 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4362 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4359 _reasons = Column(
4363 _reasons = Column(
4360 'reason', MutationList.as_mutable(
4364 'reason', MutationList.as_mutable(
4361 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4365 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4362
4366
4363 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4367 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4364 user = relationship('User')
4368 user = relationship('User')
4365 pull_request = relationship('PullRequest')
4369 pull_request = relationship('PullRequest')
4366
4370
4367 rule_data = Column(
4371 rule_data = Column(
4368 'rule_data_json',
4372 'rule_data_json',
4369 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4373 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4370
4374
4371 def rule_user_group_data(self):
4375 def rule_user_group_data(self):
4372 """
4376 """
4373 Returns the voting user group rule data for this reviewer
4377 Returns the voting user group rule data for this reviewer
4374 """
4378 """
4375
4379
4376 if self.rule_data and 'vote_rule' in self.rule_data:
4380 if self.rule_data and 'vote_rule' in self.rule_data:
4377 user_group_data = {}
4381 user_group_data = {}
4378 if 'rule_user_group_entry_id' in self.rule_data:
4382 if 'rule_user_group_entry_id' in self.rule_data:
4379 # means a group with voting rules !
4383 # means a group with voting rules !
4380 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4384 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4381 user_group_data['name'] = self.rule_data['rule_name']
4385 user_group_data['name'] = self.rule_data['rule_name']
4382 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4386 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4383
4387
4384 return user_group_data
4388 return user_group_data
4385
4389
4386 def __unicode__(self):
4390 def __unicode__(self):
4387 return u"<%s('id:%s')>" % (self.__class__.__name__,
4391 return u"<%s('id:%s')>" % (self.__class__.__name__,
4388 self.pull_requests_reviewers_id)
4392 self.pull_requests_reviewers_id)
4389
4393
4390
4394
4391 class Notification(Base, BaseModel):
4395 class Notification(Base, BaseModel):
4392 __tablename__ = 'notifications'
4396 __tablename__ = 'notifications'
4393 __table_args__ = (
4397 __table_args__ = (
4394 Index('notification_type_idx', 'type'),
4398 Index('notification_type_idx', 'type'),
4395 base_table_args,
4399 base_table_args,
4396 )
4400 )
4397
4401
4398 TYPE_CHANGESET_COMMENT = u'cs_comment'
4402 TYPE_CHANGESET_COMMENT = u'cs_comment'
4399 TYPE_MESSAGE = u'message'
4403 TYPE_MESSAGE = u'message'
4400 TYPE_MENTION = u'mention'
4404 TYPE_MENTION = u'mention'
4401 TYPE_REGISTRATION = u'registration'
4405 TYPE_REGISTRATION = u'registration'
4402 TYPE_PULL_REQUEST = u'pull_request'
4406 TYPE_PULL_REQUEST = u'pull_request'
4403 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4407 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4404 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4408 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4405
4409
4406 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4410 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4407 subject = Column('subject', Unicode(512), nullable=True)
4411 subject = Column('subject', Unicode(512), nullable=True)
4408 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4412 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4409 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4413 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4410 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4414 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4411 type_ = Column('type', Unicode(255))
4415 type_ = Column('type', Unicode(255))
4412
4416
4413 created_by_user = relationship('User')
4417 created_by_user = relationship('User')
4414 notifications_to_users = relationship('UserNotification', lazy='joined',
4418 notifications_to_users = relationship('UserNotification', lazy='joined',
4415 cascade="all, delete-orphan")
4419 cascade="all, delete-orphan")
4416
4420
4417 @property
4421 @property
4418 def recipients(self):
4422 def recipients(self):
4419 return [x.user for x in UserNotification.query()\
4423 return [x.user for x in UserNotification.query()\
4420 .filter(UserNotification.notification == self)\
4424 .filter(UserNotification.notification == self)\
4421 .order_by(UserNotification.user_id.asc()).all()]
4425 .order_by(UserNotification.user_id.asc()).all()]
4422
4426
4423 @classmethod
4427 @classmethod
4424 def create(cls, created_by, subject, body, recipients, type_=None):
4428 def create(cls, created_by, subject, body, recipients, type_=None):
4425 if type_ is None:
4429 if type_ is None:
4426 type_ = Notification.TYPE_MESSAGE
4430 type_ = Notification.TYPE_MESSAGE
4427
4431
4428 notification = cls()
4432 notification = cls()
4429 notification.created_by_user = created_by
4433 notification.created_by_user = created_by
4430 notification.subject = subject
4434 notification.subject = subject
4431 notification.body = body
4435 notification.body = body
4432 notification.type_ = type_
4436 notification.type_ = type_
4433 notification.created_on = datetime.datetime.now()
4437 notification.created_on = datetime.datetime.now()
4434
4438
4435 # For each recipient link the created notification to his account
4439 # For each recipient link the created notification to his account
4436 for u in recipients:
4440 for u in recipients:
4437 assoc = UserNotification()
4441 assoc = UserNotification()
4438 assoc.user_id = u.user_id
4442 assoc.user_id = u.user_id
4439 assoc.notification = notification
4443 assoc.notification = notification
4440
4444
4441 # if created_by is inside recipients mark his notification
4445 # if created_by is inside recipients mark his notification
4442 # as read
4446 # as read
4443 if u.user_id == created_by.user_id:
4447 if u.user_id == created_by.user_id:
4444 assoc.read = True
4448 assoc.read = True
4445 Session().add(assoc)
4449 Session().add(assoc)
4446
4450
4447 Session().add(notification)
4451 Session().add(notification)
4448
4452
4449 return notification
4453 return notification
4450
4454
4451
4455
4452 class UserNotification(Base, BaseModel):
4456 class UserNotification(Base, BaseModel):
4453 __tablename__ = 'user_to_notification'
4457 __tablename__ = 'user_to_notification'
4454 __table_args__ = (
4458 __table_args__ = (
4455 UniqueConstraint('user_id', 'notification_id'),
4459 UniqueConstraint('user_id', 'notification_id'),
4456 base_table_args
4460 base_table_args
4457 )
4461 )
4458
4462
4459 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4463 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4460 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4464 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4461 read = Column('read', Boolean, default=False)
4465 read = Column('read', Boolean, default=False)
4462 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4466 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4463
4467
4464 user = relationship('User', lazy="joined")
4468 user = relationship('User', lazy="joined")
4465 notification = relationship('Notification', lazy="joined",
4469 notification = relationship('Notification', lazy="joined",
4466 order_by=lambda: Notification.created_on.desc(),)
4470 order_by=lambda: Notification.created_on.desc(),)
4467
4471
4468 def mark_as_read(self):
4472 def mark_as_read(self):
4469 self.read = True
4473 self.read = True
4470 Session().add(self)
4474 Session().add(self)
4471
4475
4472
4476
4473 class Gist(Base, BaseModel):
4477 class Gist(Base, BaseModel):
4474 __tablename__ = 'gists'
4478 __tablename__ = 'gists'
4475 __table_args__ = (
4479 __table_args__ = (
4476 Index('g_gist_access_id_idx', 'gist_access_id'),
4480 Index('g_gist_access_id_idx', 'gist_access_id'),
4477 Index('g_created_on_idx', 'created_on'),
4481 Index('g_created_on_idx', 'created_on'),
4478 base_table_args
4482 base_table_args
4479 )
4483 )
4480
4484
4481 GIST_PUBLIC = u'public'
4485 GIST_PUBLIC = u'public'
4482 GIST_PRIVATE = u'private'
4486 GIST_PRIVATE = u'private'
4483 DEFAULT_FILENAME = u'gistfile1.txt'
4487 DEFAULT_FILENAME = u'gistfile1.txt'
4484
4488
4485 ACL_LEVEL_PUBLIC = u'acl_public'
4489 ACL_LEVEL_PUBLIC = u'acl_public'
4486 ACL_LEVEL_PRIVATE = u'acl_private'
4490 ACL_LEVEL_PRIVATE = u'acl_private'
4487
4491
4488 gist_id = Column('gist_id', Integer(), primary_key=True)
4492 gist_id = Column('gist_id', Integer(), primary_key=True)
4489 gist_access_id = Column('gist_access_id', Unicode(250))
4493 gist_access_id = Column('gist_access_id', Unicode(250))
4490 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4494 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4491 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4495 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4492 gist_expires = Column('gist_expires', Float(53), nullable=False)
4496 gist_expires = Column('gist_expires', Float(53), nullable=False)
4493 gist_type = Column('gist_type', Unicode(128), nullable=False)
4497 gist_type = Column('gist_type', Unicode(128), nullable=False)
4494 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4498 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4495 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4499 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4496 acl_level = Column('acl_level', Unicode(128), nullable=True)
4500 acl_level = Column('acl_level', Unicode(128), nullable=True)
4497
4501
4498 owner = relationship('User')
4502 owner = relationship('User')
4499
4503
4500 def __repr__(self):
4504 def __repr__(self):
4501 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4505 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4502
4506
4503 @hybrid_property
4507 @hybrid_property
4504 def description_safe(self):
4508 def description_safe(self):
4505 from rhodecode.lib import helpers as h
4509 from rhodecode.lib import helpers as h
4506 return h.escape(self.gist_description)
4510 return h.escape(self.gist_description)
4507
4511
4508 @classmethod
4512 @classmethod
4509 def get_or_404(cls, id_):
4513 def get_or_404(cls, id_):
4510 from pyramid.httpexceptions import HTTPNotFound
4514 from pyramid.httpexceptions import HTTPNotFound
4511
4515
4512 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4516 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4513 if not res:
4517 if not res:
4514 raise HTTPNotFound()
4518 raise HTTPNotFound()
4515 return res
4519 return res
4516
4520
4517 @classmethod
4521 @classmethod
4518 def get_by_access_id(cls, gist_access_id):
4522 def get_by_access_id(cls, gist_access_id):
4519 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4523 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4520
4524
4521 def gist_url(self):
4525 def gist_url(self):
4522 from rhodecode.model.gist import GistModel
4526 from rhodecode.model.gist import GistModel
4523 return GistModel().get_url(self)
4527 return GistModel().get_url(self)
4524
4528
4525 @classmethod
4529 @classmethod
4526 def base_path(cls):
4530 def base_path(cls):
4527 """
4531 """
4528 Returns base path when all gists are stored
4532 Returns base path when all gists are stored
4529
4533
4530 :param cls:
4534 :param cls:
4531 """
4535 """
4532 from rhodecode.model.gist import GIST_STORE_LOC
4536 from rhodecode.model.gist import GIST_STORE_LOC
4533 q = Session().query(RhodeCodeUi)\
4537 q = Session().query(RhodeCodeUi)\
4534 .filter(RhodeCodeUi.ui_key == URL_SEP)
4538 .filter(RhodeCodeUi.ui_key == URL_SEP)
4535 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4539 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4536 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4540 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4537
4541
4538 def get_api_data(self):
4542 def get_api_data(self):
4539 """
4543 """
4540 Common function for generating gist related data for API
4544 Common function for generating gist related data for API
4541 """
4545 """
4542 gist = self
4546 gist = self
4543 data = {
4547 data = {
4544 'gist_id': gist.gist_id,
4548 'gist_id': gist.gist_id,
4545 'type': gist.gist_type,
4549 'type': gist.gist_type,
4546 'access_id': gist.gist_access_id,
4550 'access_id': gist.gist_access_id,
4547 'description': gist.gist_description,
4551 'description': gist.gist_description,
4548 'url': gist.gist_url(),
4552 'url': gist.gist_url(),
4549 'expires': gist.gist_expires,
4553 'expires': gist.gist_expires,
4550 'created_on': gist.created_on,
4554 'created_on': gist.created_on,
4551 'modified_at': gist.modified_at,
4555 'modified_at': gist.modified_at,
4552 'content': None,
4556 'content': None,
4553 'acl_level': gist.acl_level,
4557 'acl_level': gist.acl_level,
4554 }
4558 }
4555 return data
4559 return data
4556
4560
4557 def __json__(self):
4561 def __json__(self):
4558 data = dict(
4562 data = dict(
4559 )
4563 )
4560 data.update(self.get_api_data())
4564 data.update(self.get_api_data())
4561 return data
4565 return data
4562 # SCM functions
4566 # SCM functions
4563
4567
4564 def scm_instance(self, **kwargs):
4568 def scm_instance(self, **kwargs):
4565 """
4569 """
4566 Get an instance of VCS Repository
4570 Get an instance of VCS Repository
4567
4571
4568 :param kwargs:
4572 :param kwargs:
4569 """
4573 """
4570 from rhodecode.model.gist import GistModel
4574 from rhodecode.model.gist import GistModel
4571 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4575 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4572 return get_vcs_instance(
4576 return get_vcs_instance(
4573 repo_path=safe_str(full_repo_path), create=False,
4577 repo_path=safe_str(full_repo_path), create=False,
4574 _vcs_alias=GistModel.vcs_backend)
4578 _vcs_alias=GistModel.vcs_backend)
4575
4579
4576
4580
4577 class ExternalIdentity(Base, BaseModel):
4581 class ExternalIdentity(Base, BaseModel):
4578 __tablename__ = 'external_identities'
4582 __tablename__ = 'external_identities'
4579 __table_args__ = (
4583 __table_args__ = (
4580 Index('local_user_id_idx', 'local_user_id'),
4584 Index('local_user_id_idx', 'local_user_id'),
4581 Index('external_id_idx', 'external_id'),
4585 Index('external_id_idx', 'external_id'),
4582 base_table_args
4586 base_table_args
4583 )
4587 )
4584
4588
4585 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4589 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4586 external_username = Column('external_username', Unicode(1024), default=u'')
4590 external_username = Column('external_username', Unicode(1024), default=u'')
4587 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4591 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4588 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4592 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4589 access_token = Column('access_token', String(1024), default=u'')
4593 access_token = Column('access_token', String(1024), default=u'')
4590 alt_token = Column('alt_token', String(1024), default=u'')
4594 alt_token = Column('alt_token', String(1024), default=u'')
4591 token_secret = Column('token_secret', String(1024), default=u'')
4595 token_secret = Column('token_secret', String(1024), default=u'')
4592
4596
4593 @classmethod
4597 @classmethod
4594 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4598 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4595 """
4599 """
4596 Returns ExternalIdentity instance based on search params
4600 Returns ExternalIdentity instance based on search params
4597
4601
4598 :param external_id:
4602 :param external_id:
4599 :param provider_name:
4603 :param provider_name:
4600 :return: ExternalIdentity
4604 :return: ExternalIdentity
4601 """
4605 """
4602 query = cls.query()
4606 query = cls.query()
4603 query = query.filter(cls.external_id == external_id)
4607 query = query.filter(cls.external_id == external_id)
4604 query = query.filter(cls.provider_name == provider_name)
4608 query = query.filter(cls.provider_name == provider_name)
4605 if local_user_id:
4609 if local_user_id:
4606 query = query.filter(cls.local_user_id == local_user_id)
4610 query = query.filter(cls.local_user_id == local_user_id)
4607 return query.first()
4611 return query.first()
4608
4612
4609 @classmethod
4613 @classmethod
4610 def user_by_external_id_and_provider(cls, external_id, provider_name):
4614 def user_by_external_id_and_provider(cls, external_id, provider_name):
4611 """
4615 """
4612 Returns User instance based on search params
4616 Returns User instance based on search params
4613
4617
4614 :param external_id:
4618 :param external_id:
4615 :param provider_name:
4619 :param provider_name:
4616 :return: User
4620 :return: User
4617 """
4621 """
4618 query = User.query()
4622 query = User.query()
4619 query = query.filter(cls.external_id == external_id)
4623 query = query.filter(cls.external_id == external_id)
4620 query = query.filter(cls.provider_name == provider_name)
4624 query = query.filter(cls.provider_name == provider_name)
4621 query = query.filter(User.user_id == cls.local_user_id)
4625 query = query.filter(User.user_id == cls.local_user_id)
4622 return query.first()
4626 return query.first()
4623
4627
4624 @classmethod
4628 @classmethod
4625 def by_local_user_id(cls, local_user_id):
4629 def by_local_user_id(cls, local_user_id):
4626 """
4630 """
4627 Returns all tokens for user
4631 Returns all tokens for user
4628
4632
4629 :param local_user_id:
4633 :param local_user_id:
4630 :return: ExternalIdentity
4634 :return: ExternalIdentity
4631 """
4635 """
4632 query = cls.query()
4636 query = cls.query()
4633 query = query.filter(cls.local_user_id == local_user_id)
4637 query = query.filter(cls.local_user_id == local_user_id)
4634 return query
4638 return query
4635
4639
4636 @classmethod
4640 @classmethod
4637 def load_provider_plugin(cls, plugin_id):
4641 def load_provider_plugin(cls, plugin_id):
4638 from rhodecode.authentication.base import loadplugin
4642 from rhodecode.authentication.base import loadplugin
4639 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4643 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4640 auth_plugin = loadplugin(_plugin_id)
4644 auth_plugin = loadplugin(_plugin_id)
4641 return auth_plugin
4645 return auth_plugin
4642
4646
4643
4647
4644 class Integration(Base, BaseModel):
4648 class Integration(Base, BaseModel):
4645 __tablename__ = 'integrations'
4649 __tablename__ = 'integrations'
4646 __table_args__ = (
4650 __table_args__ = (
4647 base_table_args
4651 base_table_args
4648 )
4652 )
4649
4653
4650 integration_id = Column('integration_id', Integer(), primary_key=True)
4654 integration_id = Column('integration_id', Integer(), primary_key=True)
4651 integration_type = Column('integration_type', String(255))
4655 integration_type = Column('integration_type', String(255))
4652 enabled = Column('enabled', Boolean(), nullable=False)
4656 enabled = Column('enabled', Boolean(), nullable=False)
4653 name = Column('name', String(255), nullable=False)
4657 name = Column('name', String(255), nullable=False)
4654 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4658 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4655 default=False)
4659 default=False)
4656
4660
4657 settings = Column(
4661 settings = Column(
4658 'settings_json', MutationObj.as_mutable(
4662 'settings_json', MutationObj.as_mutable(
4659 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4663 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4660 repo_id = Column(
4664 repo_id = Column(
4661 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4665 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4662 nullable=True, unique=None, default=None)
4666 nullable=True, unique=None, default=None)
4663 repo = relationship('Repository', lazy='joined')
4667 repo = relationship('Repository', lazy='joined')
4664
4668
4665 repo_group_id = Column(
4669 repo_group_id = Column(
4666 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4670 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4667 nullable=True, unique=None, default=None)
4671 nullable=True, unique=None, default=None)
4668 repo_group = relationship('RepoGroup', lazy='joined')
4672 repo_group = relationship('RepoGroup', lazy='joined')
4669
4673
4670 @property
4674 @property
4671 def scope(self):
4675 def scope(self):
4672 if self.repo:
4676 if self.repo:
4673 return repr(self.repo)
4677 return repr(self.repo)
4674 if self.repo_group:
4678 if self.repo_group:
4675 if self.child_repos_only:
4679 if self.child_repos_only:
4676 return repr(self.repo_group) + ' (child repos only)'
4680 return repr(self.repo_group) + ' (child repos only)'
4677 else:
4681 else:
4678 return repr(self.repo_group) + ' (recursive)'
4682 return repr(self.repo_group) + ' (recursive)'
4679 if self.child_repos_only:
4683 if self.child_repos_only:
4680 return 'root_repos'
4684 return 'root_repos'
4681 return 'global'
4685 return 'global'
4682
4686
4683 def __repr__(self):
4687 def __repr__(self):
4684 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4688 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4685
4689
4686
4690
4687 class RepoReviewRuleUser(Base, BaseModel):
4691 class RepoReviewRuleUser(Base, BaseModel):
4688 __tablename__ = 'repo_review_rules_users'
4692 __tablename__ = 'repo_review_rules_users'
4689 __table_args__ = (
4693 __table_args__ = (
4690 base_table_args
4694 base_table_args
4691 )
4695 )
4692
4696
4693 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4697 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4694 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4698 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4695 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4699 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4696 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4700 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4697 user = relationship('User')
4701 user = relationship('User')
4698
4702
4699 def rule_data(self):
4703 def rule_data(self):
4700 return {
4704 return {
4701 'mandatory': self.mandatory
4705 'mandatory': self.mandatory
4702 }
4706 }
4703
4707
4704
4708
4705 class RepoReviewRuleUserGroup(Base, BaseModel):
4709 class RepoReviewRuleUserGroup(Base, BaseModel):
4706 __tablename__ = 'repo_review_rules_users_groups'
4710 __tablename__ = 'repo_review_rules_users_groups'
4707 __table_args__ = (
4711 __table_args__ = (
4708 base_table_args
4712 base_table_args
4709 )
4713 )
4710
4714
4711 VOTE_RULE_ALL = -1
4715 VOTE_RULE_ALL = -1
4712
4716
4713 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4717 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4714 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4718 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4715 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4719 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4716 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4720 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4717 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4721 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4718 users_group = relationship('UserGroup')
4722 users_group = relationship('UserGroup')
4719
4723
4720 def rule_data(self):
4724 def rule_data(self):
4721 return {
4725 return {
4722 'mandatory': self.mandatory,
4726 'mandatory': self.mandatory,
4723 'vote_rule': self.vote_rule
4727 'vote_rule': self.vote_rule
4724 }
4728 }
4725
4729
4726 @property
4730 @property
4727 def vote_rule_label(self):
4731 def vote_rule_label(self):
4728 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4732 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4729 return 'all must vote'
4733 return 'all must vote'
4730 else:
4734 else:
4731 return 'min. vote {}'.format(self.vote_rule)
4735 return 'min. vote {}'.format(self.vote_rule)
4732
4736
4733
4737
4734 class RepoReviewRule(Base, BaseModel):
4738 class RepoReviewRule(Base, BaseModel):
4735 __tablename__ = 'repo_review_rules'
4739 __tablename__ = 'repo_review_rules'
4736 __table_args__ = (
4740 __table_args__ = (
4737 base_table_args
4741 base_table_args
4738 )
4742 )
4739
4743
4740 repo_review_rule_id = Column(
4744 repo_review_rule_id = Column(
4741 'repo_review_rule_id', Integer(), primary_key=True)
4745 'repo_review_rule_id', Integer(), primary_key=True)
4742 repo_id = Column(
4746 repo_id = Column(
4743 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4747 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4744 repo = relationship('Repository', backref='review_rules')
4748 repo = relationship('Repository', backref='review_rules')
4745
4749
4746 review_rule_name = Column('review_rule_name', String(255))
4750 review_rule_name = Column('review_rule_name', String(255))
4747 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4751 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4748 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4752 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4749 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4753 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4750
4754
4751 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4755 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4752 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4756 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4753 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4757 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4754 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4758 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4755
4759
4756 rule_users = relationship('RepoReviewRuleUser')
4760 rule_users = relationship('RepoReviewRuleUser')
4757 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4761 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4758
4762
4759 def _validate_pattern(self, value):
4763 def _validate_pattern(self, value):
4760 re.compile('^' + glob2re(value) + '$')
4764 re.compile('^' + glob2re(value) + '$')
4761
4765
4762 @hybrid_property
4766 @hybrid_property
4763 def source_branch_pattern(self):
4767 def source_branch_pattern(self):
4764 return self._branch_pattern or '*'
4768 return self._branch_pattern or '*'
4765
4769
4766 @source_branch_pattern.setter
4770 @source_branch_pattern.setter
4767 def source_branch_pattern(self, value):
4771 def source_branch_pattern(self, value):
4768 self._validate_pattern(value)
4772 self._validate_pattern(value)
4769 self._branch_pattern = value or '*'
4773 self._branch_pattern = value or '*'
4770
4774
4771 @hybrid_property
4775 @hybrid_property
4772 def target_branch_pattern(self):
4776 def target_branch_pattern(self):
4773 return self._target_branch_pattern or '*'
4777 return self._target_branch_pattern or '*'
4774
4778
4775 @target_branch_pattern.setter
4779 @target_branch_pattern.setter
4776 def target_branch_pattern(self, value):
4780 def target_branch_pattern(self, value):
4777 self._validate_pattern(value)
4781 self._validate_pattern(value)
4778 self._target_branch_pattern = value or '*'
4782 self._target_branch_pattern = value or '*'
4779
4783
4780 @hybrid_property
4784 @hybrid_property
4781 def file_pattern(self):
4785 def file_pattern(self):
4782 return self._file_pattern or '*'
4786 return self._file_pattern or '*'
4783
4787
4784 @file_pattern.setter
4788 @file_pattern.setter
4785 def file_pattern(self, value):
4789 def file_pattern(self, value):
4786 self._validate_pattern(value)
4790 self._validate_pattern(value)
4787 self._file_pattern = value or '*'
4791 self._file_pattern = value or '*'
4788
4792
4789 def matches(self, source_branch, target_branch, files_changed):
4793 def matches(self, source_branch, target_branch, files_changed):
4790 """
4794 """
4791 Check if this review rule matches a branch/files in a pull request
4795 Check if this review rule matches a branch/files in a pull request
4792
4796
4793 :param source_branch: source branch name for the commit
4797 :param source_branch: source branch name for the commit
4794 :param target_branch: target branch name for the commit
4798 :param target_branch: target branch name for the commit
4795 :param files_changed: list of file paths changed in the pull request
4799 :param files_changed: list of file paths changed in the pull request
4796 """
4800 """
4797
4801
4798 source_branch = source_branch or ''
4802 source_branch = source_branch or ''
4799 target_branch = target_branch or ''
4803 target_branch = target_branch or ''
4800 files_changed = files_changed or []
4804 files_changed = files_changed or []
4801
4805
4802 branch_matches = True
4806 branch_matches = True
4803 if source_branch or target_branch:
4807 if source_branch or target_branch:
4804 if self.source_branch_pattern == '*':
4808 if self.source_branch_pattern == '*':
4805 source_branch_match = True
4809 source_branch_match = True
4806 else:
4810 else:
4807 if self.source_branch_pattern.startswith('re:'):
4811 if self.source_branch_pattern.startswith('re:'):
4808 source_pattern = self.source_branch_pattern[3:]
4812 source_pattern = self.source_branch_pattern[3:]
4809 else:
4813 else:
4810 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4814 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4811 source_branch_regex = re.compile(source_pattern)
4815 source_branch_regex = re.compile(source_pattern)
4812 source_branch_match = bool(source_branch_regex.search(source_branch))
4816 source_branch_match = bool(source_branch_regex.search(source_branch))
4813 if self.target_branch_pattern == '*':
4817 if self.target_branch_pattern == '*':
4814 target_branch_match = True
4818 target_branch_match = True
4815 else:
4819 else:
4816 if self.target_branch_pattern.startswith('re:'):
4820 if self.target_branch_pattern.startswith('re:'):
4817 target_pattern = self.target_branch_pattern[3:]
4821 target_pattern = self.target_branch_pattern[3:]
4818 else:
4822 else:
4819 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4823 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4820 target_branch_regex = re.compile(target_pattern)
4824 target_branch_regex = re.compile(target_pattern)
4821 target_branch_match = bool(target_branch_regex.search(target_branch))
4825 target_branch_match = bool(target_branch_regex.search(target_branch))
4822
4826
4823 branch_matches = source_branch_match and target_branch_match
4827 branch_matches = source_branch_match and target_branch_match
4824
4828
4825 files_matches = True
4829 files_matches = True
4826 if self.file_pattern != '*':
4830 if self.file_pattern != '*':
4827 files_matches = False
4831 files_matches = False
4828 if self.file_pattern.startswith('re:'):
4832 if self.file_pattern.startswith('re:'):
4829 file_pattern = self.file_pattern[3:]
4833 file_pattern = self.file_pattern[3:]
4830 else:
4834 else:
4831 file_pattern = glob2re(self.file_pattern)
4835 file_pattern = glob2re(self.file_pattern)
4832 file_regex = re.compile(file_pattern)
4836 file_regex = re.compile(file_pattern)
4833 for filename in files_changed:
4837 for filename in files_changed:
4834 if file_regex.search(filename):
4838 if file_regex.search(filename):
4835 files_matches = True
4839 files_matches = True
4836 break
4840 break
4837
4841
4838 return branch_matches and files_matches
4842 return branch_matches and files_matches
4839
4843
4840 @property
4844 @property
4841 def review_users(self):
4845 def review_users(self):
4842 """ Returns the users which this rule applies to """
4846 """ Returns the users which this rule applies to """
4843
4847
4844 users = collections.OrderedDict()
4848 users = collections.OrderedDict()
4845
4849
4846 for rule_user in self.rule_users:
4850 for rule_user in self.rule_users:
4847 if rule_user.user.active:
4851 if rule_user.user.active:
4848 if rule_user.user not in users:
4852 if rule_user.user not in users:
4849 users[rule_user.user.username] = {
4853 users[rule_user.user.username] = {
4850 'user': rule_user.user,
4854 'user': rule_user.user,
4851 'source': 'user',
4855 'source': 'user',
4852 'source_data': {},
4856 'source_data': {},
4853 'data': rule_user.rule_data()
4857 'data': rule_user.rule_data()
4854 }
4858 }
4855
4859
4856 for rule_user_group in self.rule_user_groups:
4860 for rule_user_group in self.rule_user_groups:
4857 source_data = {
4861 source_data = {
4858 'user_group_id': rule_user_group.users_group.users_group_id,
4862 'user_group_id': rule_user_group.users_group.users_group_id,
4859 'name': rule_user_group.users_group.users_group_name,
4863 'name': rule_user_group.users_group.users_group_name,
4860 'members': len(rule_user_group.users_group.members)
4864 'members': len(rule_user_group.users_group.members)
4861 }
4865 }
4862 for member in rule_user_group.users_group.members:
4866 for member in rule_user_group.users_group.members:
4863 if member.user.active:
4867 if member.user.active:
4864 key = member.user.username
4868 key = member.user.username
4865 if key in users:
4869 if key in users:
4866 # skip this member as we have him already
4870 # skip this member as we have him already
4867 # this prevents from override the "first" matched
4871 # this prevents from override the "first" matched
4868 # users with duplicates in multiple groups
4872 # users with duplicates in multiple groups
4869 continue
4873 continue
4870
4874
4871 users[key] = {
4875 users[key] = {
4872 'user': member.user,
4876 'user': member.user,
4873 'source': 'user_group',
4877 'source': 'user_group',
4874 'source_data': source_data,
4878 'source_data': source_data,
4875 'data': rule_user_group.rule_data()
4879 'data': rule_user_group.rule_data()
4876 }
4880 }
4877
4881
4878 return users
4882 return users
4879
4883
4880 def user_group_vote_rule(self, user_id):
4884 def user_group_vote_rule(self, user_id):
4881
4885
4882 rules = []
4886 rules = []
4883 if not self.rule_user_groups:
4887 if not self.rule_user_groups:
4884 return rules
4888 return rules
4885
4889
4886 for user_group in self.rule_user_groups:
4890 for user_group in self.rule_user_groups:
4887 user_group_members = [x.user_id for x in user_group.users_group.members]
4891 user_group_members = [x.user_id for x in user_group.users_group.members]
4888 if user_id in user_group_members:
4892 if user_id in user_group_members:
4889 rules.append(user_group)
4893 rules.append(user_group)
4890 return rules
4894 return rules
4891
4895
4892 def __repr__(self):
4896 def __repr__(self):
4893 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4897 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4894 self.repo_review_rule_id, self.repo)
4898 self.repo_review_rule_id, self.repo)
4895
4899
4896
4900
4897 class ScheduleEntry(Base, BaseModel):
4901 class ScheduleEntry(Base, BaseModel):
4898 __tablename__ = 'schedule_entries'
4902 __tablename__ = 'schedule_entries'
4899 __table_args__ = (
4903 __table_args__ = (
4900 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4904 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4901 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4905 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4902 base_table_args,
4906 base_table_args,
4903 )
4907 )
4904
4908
4905 schedule_types = ['crontab', 'timedelta', 'integer']
4909 schedule_types = ['crontab', 'timedelta', 'integer']
4906 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4910 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4907
4911
4908 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4912 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4909 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4913 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4910 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4914 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4911
4915
4912 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4916 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4913 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4917 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4914
4918
4915 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4919 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4916 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4920 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4917
4921
4918 # task
4922 # task
4919 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4923 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4920 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4924 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4921 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4925 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4922 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4926 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4923
4927
4924 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4928 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4925 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4929 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4926
4930
4927 @hybrid_property
4931 @hybrid_property
4928 def schedule_type(self):
4932 def schedule_type(self):
4929 return self._schedule_type
4933 return self._schedule_type
4930
4934
4931 @schedule_type.setter
4935 @schedule_type.setter
4932 def schedule_type(self, val):
4936 def schedule_type(self, val):
4933 if val not in self.schedule_types:
4937 if val not in self.schedule_types:
4934 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4938 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4935 val, self.schedule_type))
4939 val, self.schedule_type))
4936
4940
4937 self._schedule_type = val
4941 self._schedule_type = val
4938
4942
4939 @classmethod
4943 @classmethod
4940 def get_uid(cls, obj):
4944 def get_uid(cls, obj):
4941 args = obj.task_args
4945 args = obj.task_args
4942 kwargs = obj.task_kwargs
4946 kwargs = obj.task_kwargs
4943 if isinstance(args, JsonRaw):
4947 if isinstance(args, JsonRaw):
4944 try:
4948 try:
4945 args = json.loads(args)
4949 args = json.loads(args)
4946 except ValueError:
4950 except ValueError:
4947 args = tuple()
4951 args = tuple()
4948
4952
4949 if isinstance(kwargs, JsonRaw):
4953 if isinstance(kwargs, JsonRaw):
4950 try:
4954 try:
4951 kwargs = json.loads(kwargs)
4955 kwargs = json.loads(kwargs)
4952 except ValueError:
4956 except ValueError:
4953 kwargs = dict()
4957 kwargs = dict()
4954
4958
4955 dot_notation = obj.task_dot_notation
4959 dot_notation = obj.task_dot_notation
4956 val = '.'.join(map(safe_str, [
4960 val = '.'.join(map(safe_str, [
4957 sorted(dot_notation), args, sorted(kwargs.items())]))
4961 sorted(dot_notation), args, sorted(kwargs.items())]))
4958 return hashlib.sha1(val).hexdigest()
4962 return hashlib.sha1(val).hexdigest()
4959
4963
4960 @classmethod
4964 @classmethod
4961 def get_by_schedule_name(cls, schedule_name):
4965 def get_by_schedule_name(cls, schedule_name):
4962 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4966 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4963
4967
4964 @classmethod
4968 @classmethod
4965 def get_by_schedule_id(cls, schedule_id):
4969 def get_by_schedule_id(cls, schedule_id):
4966 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4970 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4967
4971
4968 @property
4972 @property
4969 def task(self):
4973 def task(self):
4970 return self.task_dot_notation
4974 return self.task_dot_notation
4971
4975
4972 @property
4976 @property
4973 def schedule(self):
4977 def schedule(self):
4974 from rhodecode.lib.celerylib.utils import raw_2_schedule
4978 from rhodecode.lib.celerylib.utils import raw_2_schedule
4975 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4979 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4976 return schedule
4980 return schedule
4977
4981
4978 @property
4982 @property
4979 def args(self):
4983 def args(self):
4980 try:
4984 try:
4981 return list(self.task_args or [])
4985 return list(self.task_args or [])
4982 except ValueError:
4986 except ValueError:
4983 return list()
4987 return list()
4984
4988
4985 @property
4989 @property
4986 def kwargs(self):
4990 def kwargs(self):
4987 try:
4991 try:
4988 return dict(self.task_kwargs or {})
4992 return dict(self.task_kwargs or {})
4989 except ValueError:
4993 except ValueError:
4990 return dict()
4994 return dict()
4991
4995
4992 def _as_raw(self, val):
4996 def _as_raw(self, val):
4993 if hasattr(val, 'de_coerce'):
4997 if hasattr(val, 'de_coerce'):
4994 val = val.de_coerce()
4998 val = val.de_coerce()
4995 if val:
4999 if val:
4996 val = json.dumps(val)
5000 val = json.dumps(val)
4997
5001
4998 return val
5002 return val
4999
5003
5000 @property
5004 @property
5001 def schedule_definition_raw(self):
5005 def schedule_definition_raw(self):
5002 return self._as_raw(self.schedule_definition)
5006 return self._as_raw(self.schedule_definition)
5003
5007
5004 @property
5008 @property
5005 def args_raw(self):
5009 def args_raw(self):
5006 return self._as_raw(self.task_args)
5010 return self._as_raw(self.task_args)
5007
5011
5008 @property
5012 @property
5009 def kwargs_raw(self):
5013 def kwargs_raw(self):
5010 return self._as_raw(self.task_kwargs)
5014 return self._as_raw(self.task_kwargs)
5011
5015
5012 def __repr__(self):
5016 def __repr__(self):
5013 return '<DB:ScheduleEntry({}:{})>'.format(
5017 return '<DB:ScheduleEntry({}:{})>'.format(
5014 self.schedule_entry_id, self.schedule_name)
5018 self.schedule_entry_id, self.schedule_name)
5015
5019
5016
5020
5017 @event.listens_for(ScheduleEntry, 'before_update')
5021 @event.listens_for(ScheduleEntry, 'before_update')
5018 def update_task_uid(mapper, connection, target):
5022 def update_task_uid(mapper, connection, target):
5019 target.task_uid = ScheduleEntry.get_uid(target)
5023 target.task_uid = ScheduleEntry.get_uid(target)
5020
5024
5021
5025
5022 @event.listens_for(ScheduleEntry, 'before_insert')
5026 @event.listens_for(ScheduleEntry, 'before_insert')
5023 def set_task_uid(mapper, connection, target):
5027 def set_task_uid(mapper, connection, target):
5024 target.task_uid = ScheduleEntry.get_uid(target)
5028 target.task_uid = ScheduleEntry.get_uid(target)
5025
5029
5026
5030
5027 class _BaseBranchPerms(BaseModel):
5031 class _BaseBranchPerms(BaseModel):
5028 @classmethod
5032 @classmethod
5029 def compute_hash(cls, value):
5033 def compute_hash(cls, value):
5030 return sha1_safe(value)
5034 return sha1_safe(value)
5031
5035
5032 @hybrid_property
5036 @hybrid_property
5033 def branch_pattern(self):
5037 def branch_pattern(self):
5034 return self._branch_pattern or '*'
5038 return self._branch_pattern or '*'
5035
5039
5036 @hybrid_property
5040 @hybrid_property
5037 def branch_hash(self):
5041 def branch_hash(self):
5038 return self._branch_hash
5042 return self._branch_hash
5039
5043
5040 def _validate_glob(self, value):
5044 def _validate_glob(self, value):
5041 re.compile('^' + glob2re(value) + '$')
5045 re.compile('^' + glob2re(value) + '$')
5042
5046
5043 @branch_pattern.setter
5047 @branch_pattern.setter
5044 def branch_pattern(self, value):
5048 def branch_pattern(self, value):
5045 self._validate_glob(value)
5049 self._validate_glob(value)
5046 self._branch_pattern = value or '*'
5050 self._branch_pattern = value or '*'
5047 # set the Hash when setting the branch pattern
5051 # set the Hash when setting the branch pattern
5048 self._branch_hash = self.compute_hash(self._branch_pattern)
5052 self._branch_hash = self.compute_hash(self._branch_pattern)
5049
5053
5050 def matches(self, branch):
5054 def matches(self, branch):
5051 """
5055 """
5052 Check if this the branch matches entry
5056 Check if this the branch matches entry
5053
5057
5054 :param branch: branch name for the commit
5058 :param branch: branch name for the commit
5055 """
5059 """
5056
5060
5057 branch = branch or ''
5061 branch = branch or ''
5058
5062
5059 branch_matches = True
5063 branch_matches = True
5060 if branch:
5064 if branch:
5061 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5065 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5062 branch_matches = bool(branch_regex.search(branch))
5066 branch_matches = bool(branch_regex.search(branch))
5063
5067
5064 return branch_matches
5068 return branch_matches
5065
5069
5066
5070
5067 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5071 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5068 __tablename__ = 'user_to_repo_branch_permissions'
5072 __tablename__ = 'user_to_repo_branch_permissions'
5069 __table_args__ = (
5073 __table_args__ = (
5070 base_table_args
5074 base_table_args
5071 )
5075 )
5072
5076
5073 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5077 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5074
5078
5075 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5079 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5076 repo = relationship('Repository', backref='user_branch_perms')
5080 repo = relationship('Repository', backref='user_branch_perms')
5077
5081
5078 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5082 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5079 permission = relationship('Permission')
5083 permission = relationship('Permission')
5080
5084
5081 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5085 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5082 user_repo_to_perm = relationship('UserRepoToPerm')
5086 user_repo_to_perm = relationship('UserRepoToPerm')
5083
5087
5084 rule_order = Column('rule_order', Integer(), nullable=False)
5088 rule_order = Column('rule_order', Integer(), nullable=False)
5085 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5089 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5086 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5090 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5087
5091
5088 def __unicode__(self):
5092 def __unicode__(self):
5089 return u'<UserBranchPermission(%s => %r)>' % (
5093 return u'<UserBranchPermission(%s => %r)>' % (
5090 self.user_repo_to_perm, self.branch_pattern)
5094 self.user_repo_to_perm, self.branch_pattern)
5091
5095
5092
5096
5093 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5097 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5094 __tablename__ = 'user_group_to_repo_branch_permissions'
5098 __tablename__ = 'user_group_to_repo_branch_permissions'
5095 __table_args__ = (
5099 __table_args__ = (
5096 base_table_args
5100 base_table_args
5097 )
5101 )
5098
5102
5099 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5103 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5100
5104
5101 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5105 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5102 repo = relationship('Repository', backref='user_group_branch_perms')
5106 repo = relationship('Repository', backref='user_group_branch_perms')
5103
5107
5104 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5108 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5105 permission = relationship('Permission')
5109 permission = relationship('Permission')
5106
5110
5107 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)
5111 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)
5108 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5112 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5109
5113
5110 rule_order = Column('rule_order', Integer(), nullable=False)
5114 rule_order = Column('rule_order', Integer(), nullable=False)
5111 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5115 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5112 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5116 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5113
5117
5114 def __unicode__(self):
5118 def __unicode__(self):
5115 return u'<UserBranchPermission(%s => %r)>' % (
5119 return u'<UserBranchPermission(%s => %r)>' % (
5116 self.user_group_repo_to_perm, self.branch_pattern)
5120 self.user_group_repo_to_perm, self.branch_pattern)
5117
5121
5118
5122
5119 class UserBookmark(Base, BaseModel):
5123 class UserBookmark(Base, BaseModel):
5120 __tablename__ = 'user_bookmarks'
5124 __tablename__ = 'user_bookmarks'
5121 __table_args__ = (
5125 __table_args__ = (
5122 UniqueConstraint('user_id', 'bookmark_repo_id'),
5126 UniqueConstraint('user_id', 'bookmark_repo_id'),
5123 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5127 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5124 UniqueConstraint('user_id', 'bookmark_position'),
5128 UniqueConstraint('user_id', 'bookmark_position'),
5125 base_table_args
5129 base_table_args
5126 )
5130 )
5127
5131
5128 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5132 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5129 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5133 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5130 position = Column("bookmark_position", Integer(), nullable=False)
5134 position = Column("bookmark_position", Integer(), nullable=False)
5131 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5135 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5132 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5136 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5133 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5137 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5134
5138
5135 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5139 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5136 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5140 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5137
5141
5138 user = relationship("User")
5142 user = relationship("User")
5139
5143
5140 repository = relationship("Repository")
5144 repository = relationship("Repository")
5141 repository_group = relationship("RepoGroup")
5145 repository_group = relationship("RepoGroup")
5142
5146
5143 @classmethod
5147 @classmethod
5144 def get_by_position_for_user(cls, position, user_id):
5148 def get_by_position_for_user(cls, position, user_id):
5145 return cls.query() \
5149 return cls.query() \
5146 .filter(UserBookmark.user_id == user_id) \
5150 .filter(UserBookmark.user_id == user_id) \
5147 .filter(UserBookmark.position == position).scalar()
5151 .filter(UserBookmark.position == position).scalar()
5148
5152
5149 @classmethod
5153 @classmethod
5150 def get_bookmarks_for_user(cls, user_id):
5154 def get_bookmarks_for_user(cls, user_id):
5151 return cls.query() \
5155 return cls.query() \
5152 .filter(UserBookmark.user_id == user_id) \
5156 .filter(UserBookmark.user_id == user_id) \
5153 .options(joinedload(UserBookmark.repository)) \
5157 .options(joinedload(UserBookmark.repository)) \
5154 .options(joinedload(UserBookmark.repository_group)) \
5158 .options(joinedload(UserBookmark.repository_group)) \
5155 .order_by(UserBookmark.position.asc()) \
5159 .order_by(UserBookmark.position.asc()) \
5156 .all()
5160 .all()
5157
5161
5158 def __unicode__(self):
5162 def __unicode__(self):
5159 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5163 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5160
5164
5161
5165
5162 class FileStore(Base, BaseModel):
5166 class FileStore(Base, BaseModel):
5163 __tablename__ = 'file_store'
5167 __tablename__ = 'file_store'
5164 __table_args__ = (
5168 __table_args__ = (
5165 base_table_args
5169 base_table_args
5166 )
5170 )
5167
5171
5168 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5172 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5169 file_uid = Column('file_uid', String(1024), nullable=False)
5173 file_uid = Column('file_uid', String(1024), nullable=False)
5170 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5174 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5171 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5175 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5172 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5176 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5173
5177
5174 # sha256 hash
5178 # sha256 hash
5175 file_hash = Column('file_hash', String(512), nullable=False)
5179 file_hash = Column('file_hash', String(512), nullable=False)
5176 file_size = Column('file_size', BigInteger(), nullable=False)
5180 file_size = Column('file_size', BigInteger(), nullable=False)
5177
5181
5178 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5182 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5179 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5183 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5180 accessed_count = Column('accessed_count', Integer(), default=0)
5184 accessed_count = Column('accessed_count', Integer(), default=0)
5181
5185
5182 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5186 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5183
5187
5184 # if repo/repo_group reference is set, check for permissions
5188 # if repo/repo_group reference is set, check for permissions
5185 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5189 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5186
5190
5187 # hidden defines an attachment that should be hidden from showing in artifact listing
5191 # hidden defines an attachment that should be hidden from showing in artifact listing
5188 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5192 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5189
5193
5190 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5194 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5191 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5195 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5192
5196
5193 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5197 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5194
5198
5195 # scope limited to user, which requester have access to
5199 # scope limited to user, which requester have access to
5196 scope_user_id = Column(
5200 scope_user_id = Column(
5197 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5201 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5198 nullable=True, unique=None, default=None)
5202 nullable=True, unique=None, default=None)
5199 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5203 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5200
5204
5201 # scope limited to user group, which requester have access to
5205 # scope limited to user group, which requester have access to
5202 scope_user_group_id = Column(
5206 scope_user_group_id = Column(
5203 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5207 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5204 nullable=True, unique=None, default=None)
5208 nullable=True, unique=None, default=None)
5205 user_group = relationship('UserGroup', lazy='joined')
5209 user_group = relationship('UserGroup', lazy='joined')
5206
5210
5207 # scope limited to repo, which requester have access to
5211 # scope limited to repo, which requester have access to
5208 scope_repo_id = Column(
5212 scope_repo_id = Column(
5209 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5213 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5210 nullable=True, unique=None, default=None)
5214 nullable=True, unique=None, default=None)
5211 repo = relationship('Repository', lazy='joined')
5215 repo = relationship('Repository', lazy='joined')
5212
5216
5213 # scope limited to repo group, which requester have access to
5217 # scope limited to repo group, which requester have access to
5214 scope_repo_group_id = Column(
5218 scope_repo_group_id = Column(
5215 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5219 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5216 nullable=True, unique=None, default=None)
5220 nullable=True, unique=None, default=None)
5217 repo_group = relationship('RepoGroup', lazy='joined')
5221 repo_group = relationship('RepoGroup', lazy='joined')
5218
5222
5219 @classmethod
5223 @classmethod
5220 def get_by_store_uid(cls, file_store_uid):
5224 def get_by_store_uid(cls, file_store_uid):
5221 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5225 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5222
5226
5223 @classmethod
5227 @classmethod
5224 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5228 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5225 file_description='', enabled=True, hidden=False, check_acl=True,
5229 file_description='', enabled=True, hidden=False, check_acl=True,
5226 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5230 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5227
5231
5228 store_entry = FileStore()
5232 store_entry = FileStore()
5229 store_entry.file_uid = file_uid
5233 store_entry.file_uid = file_uid
5230 store_entry.file_display_name = file_display_name
5234 store_entry.file_display_name = file_display_name
5231 store_entry.file_org_name = filename
5235 store_entry.file_org_name = filename
5232 store_entry.file_size = file_size
5236 store_entry.file_size = file_size
5233 store_entry.file_hash = file_hash
5237 store_entry.file_hash = file_hash
5234 store_entry.file_description = file_description
5238 store_entry.file_description = file_description
5235
5239
5236 store_entry.check_acl = check_acl
5240 store_entry.check_acl = check_acl
5237 store_entry.enabled = enabled
5241 store_entry.enabled = enabled
5238 store_entry.hidden = hidden
5242 store_entry.hidden = hidden
5239
5243
5240 store_entry.user_id = user_id
5244 store_entry.user_id = user_id
5241 store_entry.scope_user_id = scope_user_id
5245 store_entry.scope_user_id = scope_user_id
5242 store_entry.scope_repo_id = scope_repo_id
5246 store_entry.scope_repo_id = scope_repo_id
5243 store_entry.scope_repo_group_id = scope_repo_group_id
5247 store_entry.scope_repo_group_id = scope_repo_group_id
5244
5248
5245 return store_entry
5249 return store_entry
5246
5250
5247 @classmethod
5251 @classmethod
5248 def store_metadata(cls, file_store_id, args, commit=True):
5252 def store_metadata(cls, file_store_id, args, commit=True):
5249 file_store = FileStore.get(file_store_id)
5253 file_store = FileStore.get(file_store_id)
5250 if file_store is None:
5254 if file_store is None:
5251 return
5255 return
5252
5256
5253 for section, key, value, value_type in args:
5257 for section, key, value, value_type in args:
5254 has_key = FileStoreMetadata().query() \
5258 has_key = FileStoreMetadata().query() \
5255 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5259 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5256 .filter(FileStoreMetadata.file_store_meta_section == section) \
5260 .filter(FileStoreMetadata.file_store_meta_section == section) \
5257 .filter(FileStoreMetadata.file_store_meta_key == key) \
5261 .filter(FileStoreMetadata.file_store_meta_key == key) \
5258 .scalar()
5262 .scalar()
5259 if has_key:
5263 if has_key:
5260 msg = 'key `{}` already defined under section `{}` for this file.'\
5264 msg = 'key `{}` already defined under section `{}` for this file.'\
5261 .format(key, section)
5265 .format(key, section)
5262 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5266 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5263
5267
5264 # NOTE(marcink): raises ArtifactMetadataBadValueType
5268 # NOTE(marcink): raises ArtifactMetadataBadValueType
5265 FileStoreMetadata.valid_value_type(value_type)
5269 FileStoreMetadata.valid_value_type(value_type)
5266
5270
5267 meta_entry = FileStoreMetadata()
5271 meta_entry = FileStoreMetadata()
5268 meta_entry.file_store = file_store
5272 meta_entry.file_store = file_store
5269 meta_entry.file_store_meta_section = section
5273 meta_entry.file_store_meta_section = section
5270 meta_entry.file_store_meta_key = key
5274 meta_entry.file_store_meta_key = key
5271 meta_entry.file_store_meta_value_type = value_type
5275 meta_entry.file_store_meta_value_type = value_type
5272 meta_entry.file_store_meta_value = value
5276 meta_entry.file_store_meta_value = value
5273
5277
5274 Session().add(meta_entry)
5278 Session().add(meta_entry)
5275
5279
5276 try:
5280 try:
5277 if commit:
5281 if commit:
5278 Session().commit()
5282 Session().commit()
5279 except IntegrityError:
5283 except IntegrityError:
5280 Session().rollback()
5284 Session().rollback()
5281 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5285 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5282
5286
5283 @classmethod
5287 @classmethod
5284 def bump_access_counter(cls, file_uid, commit=True):
5288 def bump_access_counter(cls, file_uid, commit=True):
5285 FileStore().query()\
5289 FileStore().query()\
5286 .filter(FileStore.file_uid == file_uid)\
5290 .filter(FileStore.file_uid == file_uid)\
5287 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5291 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5288 FileStore.accessed_on: datetime.datetime.now()})
5292 FileStore.accessed_on: datetime.datetime.now()})
5289 if commit:
5293 if commit:
5290 Session().commit()
5294 Session().commit()
5291
5295
5292 def __json__(self):
5296 def __json__(self):
5293 data = {
5297 data = {
5294 'filename': self.file_display_name,
5298 'filename': self.file_display_name,
5295 'filename_org': self.file_org_name,
5299 'filename_org': self.file_org_name,
5296 'file_uid': self.file_uid,
5300 'file_uid': self.file_uid,
5297 'description': self.file_description,
5301 'description': self.file_description,
5298 'hidden': self.hidden,
5302 'hidden': self.hidden,
5299 'size': self.file_size,
5303 'size': self.file_size,
5300 'created_on': self.created_on,
5304 'created_on': self.created_on,
5301 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5305 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5302 'downloaded_times': self.accessed_count,
5306 'downloaded_times': self.accessed_count,
5303 'sha256': self.file_hash,
5307 'sha256': self.file_hash,
5304 'metadata': self.file_metadata,
5308 'metadata': self.file_metadata,
5305 }
5309 }
5306
5310
5307 return data
5311 return data
5308
5312
5309 def __repr__(self):
5313 def __repr__(self):
5310 return '<FileStore({})>'.format(self.file_store_id)
5314 return '<FileStore({})>'.format(self.file_store_id)
5311
5315
5312
5316
5313 class FileStoreMetadata(Base, BaseModel):
5317 class FileStoreMetadata(Base, BaseModel):
5314 __tablename__ = 'file_store_metadata'
5318 __tablename__ = 'file_store_metadata'
5315 __table_args__ = (
5319 __table_args__ = (
5316 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5320 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5317 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5321 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5318 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5322 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5319 base_table_args
5323 base_table_args
5320 )
5324 )
5321 SETTINGS_TYPES = {
5325 SETTINGS_TYPES = {
5322 'str': safe_str,
5326 'str': safe_str,
5323 'int': safe_int,
5327 'int': safe_int,
5324 'unicode': safe_unicode,
5328 'unicode': safe_unicode,
5325 'bool': str2bool,
5329 'bool': str2bool,
5326 'list': functools.partial(aslist, sep=',')
5330 'list': functools.partial(aslist, sep=',')
5327 }
5331 }
5328
5332
5329 file_store_meta_id = Column(
5333 file_store_meta_id = Column(
5330 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5334 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5331 primary_key=True)
5335 primary_key=True)
5332 _file_store_meta_section = Column(
5336 _file_store_meta_section = Column(
5333 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5337 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5334 nullable=True, unique=None, default=None)
5338 nullable=True, unique=None, default=None)
5335 _file_store_meta_section_hash = Column(
5339 _file_store_meta_section_hash = Column(
5336 "file_store_meta_section_hash", String(255),
5340 "file_store_meta_section_hash", String(255),
5337 nullable=True, unique=None, default=None)
5341 nullable=True, unique=None, default=None)
5338 _file_store_meta_key = Column(
5342 _file_store_meta_key = Column(
5339 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5343 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5340 nullable=True, unique=None, default=None)
5344 nullable=True, unique=None, default=None)
5341 _file_store_meta_key_hash = Column(
5345 _file_store_meta_key_hash = Column(
5342 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5346 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5343 _file_store_meta_value = Column(
5347 _file_store_meta_value = Column(
5344 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5348 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5345 nullable=True, unique=None, default=None)
5349 nullable=True, unique=None, default=None)
5346 _file_store_meta_value_type = Column(
5350 _file_store_meta_value_type = Column(
5347 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5351 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5348 default='unicode')
5352 default='unicode')
5349
5353
5350 file_store_id = Column(
5354 file_store_id = Column(
5351 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5355 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5352 nullable=True, unique=None, default=None)
5356 nullable=True, unique=None, default=None)
5353
5357
5354 file_store = relationship('FileStore', lazy='joined')
5358 file_store = relationship('FileStore', lazy='joined')
5355
5359
5356 @classmethod
5360 @classmethod
5357 def valid_value_type(cls, value):
5361 def valid_value_type(cls, value):
5358 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5362 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5359 raise ArtifactMetadataBadValueType(
5363 raise ArtifactMetadataBadValueType(
5360 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5364 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5361
5365
5362 @hybrid_property
5366 @hybrid_property
5363 def file_store_meta_section(self):
5367 def file_store_meta_section(self):
5364 return self._file_store_meta_section
5368 return self._file_store_meta_section
5365
5369
5366 @file_store_meta_section.setter
5370 @file_store_meta_section.setter
5367 def file_store_meta_section(self, value):
5371 def file_store_meta_section(self, value):
5368 self._file_store_meta_section = value
5372 self._file_store_meta_section = value
5369 self._file_store_meta_section_hash = _hash_key(value)
5373 self._file_store_meta_section_hash = _hash_key(value)
5370
5374
5371 @hybrid_property
5375 @hybrid_property
5372 def file_store_meta_key(self):
5376 def file_store_meta_key(self):
5373 return self._file_store_meta_key
5377 return self._file_store_meta_key
5374
5378
5375 @file_store_meta_key.setter
5379 @file_store_meta_key.setter
5376 def file_store_meta_key(self, value):
5380 def file_store_meta_key(self, value):
5377 self._file_store_meta_key = value
5381 self._file_store_meta_key = value
5378 self._file_store_meta_key_hash = _hash_key(value)
5382 self._file_store_meta_key_hash = _hash_key(value)
5379
5383
5380 @hybrid_property
5384 @hybrid_property
5381 def file_store_meta_value(self):
5385 def file_store_meta_value(self):
5382 val = self._file_store_meta_value
5386 val = self._file_store_meta_value
5383
5387
5384 if self._file_store_meta_value_type:
5388 if self._file_store_meta_value_type:
5385 # e.g unicode.encrypted == unicode
5389 # e.g unicode.encrypted == unicode
5386 _type = self._file_store_meta_value_type.split('.')[0]
5390 _type = self._file_store_meta_value_type.split('.')[0]
5387 # decode the encrypted value if it's encrypted field type
5391 # decode the encrypted value if it's encrypted field type
5388 if '.encrypted' in self._file_store_meta_value_type:
5392 if '.encrypted' in self._file_store_meta_value_type:
5389 cipher = EncryptedTextValue()
5393 cipher = EncryptedTextValue()
5390 val = safe_unicode(cipher.process_result_value(val, None))
5394 val = safe_unicode(cipher.process_result_value(val, None))
5391 # do final type conversion
5395 # do final type conversion
5392 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5396 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5393 val = converter(val)
5397 val = converter(val)
5394
5398
5395 return val
5399 return val
5396
5400
5397 @file_store_meta_value.setter
5401 @file_store_meta_value.setter
5398 def file_store_meta_value(self, val):
5402 def file_store_meta_value(self, val):
5399 val = safe_unicode(val)
5403 val = safe_unicode(val)
5400 # encode the encrypted value
5404 # encode the encrypted value
5401 if '.encrypted' in self.file_store_meta_value_type:
5405 if '.encrypted' in self.file_store_meta_value_type:
5402 cipher = EncryptedTextValue()
5406 cipher = EncryptedTextValue()
5403 val = safe_unicode(cipher.process_bind_param(val, None))
5407 val = safe_unicode(cipher.process_bind_param(val, None))
5404 self._file_store_meta_value = val
5408 self._file_store_meta_value = val
5405
5409
5406 @hybrid_property
5410 @hybrid_property
5407 def file_store_meta_value_type(self):
5411 def file_store_meta_value_type(self):
5408 return self._file_store_meta_value_type
5412 return self._file_store_meta_value_type
5409
5413
5410 @file_store_meta_value_type.setter
5414 @file_store_meta_value_type.setter
5411 def file_store_meta_value_type(self, val):
5415 def file_store_meta_value_type(self, val):
5412 # e.g unicode.encrypted
5416 # e.g unicode.encrypted
5413 self.valid_value_type(val)
5417 self.valid_value_type(val)
5414 self._file_store_meta_value_type = val
5418 self._file_store_meta_value_type = val
5415
5419
5416 def __json__(self):
5420 def __json__(self):
5417 data = {
5421 data = {
5418 'artifact': self.file_store.file_uid,
5422 'artifact': self.file_store.file_uid,
5419 'section': self.file_store_meta_section,
5423 'section': self.file_store_meta_section,
5420 'key': self.file_store_meta_key,
5424 'key': self.file_store_meta_key,
5421 'value': self.file_store_meta_value,
5425 'value': self.file_store_meta_value,
5422 }
5426 }
5423
5427
5424 return data
5428 return data
5425
5429
5426 def __repr__(self):
5430 def __repr__(self):
5427 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5431 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5428 self.file_store_meta_key, self.file_store_meta_value)
5432 self.file_store_meta_key, self.file_store_meta_value)
5429
5433
5430
5434
5431 class DbMigrateVersion(Base, BaseModel):
5435 class DbMigrateVersion(Base, BaseModel):
5432 __tablename__ = 'db_migrate_version'
5436 __tablename__ = 'db_migrate_version'
5433 __table_args__ = (
5437 __table_args__ = (
5434 base_table_args,
5438 base_table_args,
5435 )
5439 )
5436
5440
5437 repository_id = Column('repository_id', String(250), primary_key=True)
5441 repository_id = Column('repository_id', String(250), primary_key=True)
5438 repository_path = Column('repository_path', Text)
5442 repository_path = Column('repository_path', Text)
5439 version = Column('version', Integer)
5443 version = Column('version', Integer)
5440
5444
5441 @classmethod
5445 @classmethod
5442 def set_version(cls, version):
5446 def set_version(cls, version):
5443 """
5447 """
5444 Helper for forcing a different version, usually for debugging purposes via ishell.
5448 Helper for forcing a different version, usually for debugging purposes via ishell.
5445 """
5449 """
5446 ver = DbMigrateVersion.query().first()
5450 ver = DbMigrateVersion.query().first()
5447 ver.version = version
5451 ver.version = version
5448 Session().commit()
5452 Session().commit()
5449
5453
5450
5454
5451 class DbSession(Base, BaseModel):
5455 class DbSession(Base, BaseModel):
5452 __tablename__ = 'db_session'
5456 __tablename__ = 'db_session'
5453 __table_args__ = (
5457 __table_args__ = (
5454 base_table_args,
5458 base_table_args,
5455 )
5459 )
5456
5460
5457 def __repr__(self):
5461 def __repr__(self):
5458 return '<DB:DbSession({})>'.format(self.id)
5462 return '<DB:DbSession({})>'.format(self.id)
5459
5463
5460 id = Column('id', Integer())
5464 id = Column('id', Integer())
5461 namespace = Column('namespace', String(255), primary_key=True)
5465 namespace = Column('namespace', String(255), primary_key=True)
5462 accessed = Column('accessed', DateTime, nullable=False)
5466 accessed = Column('accessed', DateTime, nullable=False)
5463 created = Column('created', DateTime, nullable=False)
5467 created = Column('created', DateTime, nullable=False)
5464 data = Column('data', PickleType, nullable=False)
5468 data = Column('data', PickleType, nullable=False)
@@ -1,729 +1,746 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Package for testing various lib/helper functions in rhodecode
23 Package for testing various lib/helper functions in rhodecode
24 """
24 """
25
25
26 import datetime
26 import datetime
27 import string
27 import string
28 import mock
28 import mock
29 import pytest
29 import pytest
30
30
31 from rhodecode.tests import no_newline_id_generator
31 from rhodecode.tests import no_newline_id_generator
32 from rhodecode.tests.utils import run_test_concurrently
32 from rhodecode.tests.utils import run_test_concurrently
33
33
34 from rhodecode.lib import rc_cache
34 from rhodecode.lib import rc_cache
35 from rhodecode.lib.helpers import InitialsGravatar
35 from rhodecode.lib.helpers import InitialsGravatar
36 from rhodecode.lib.utils2 import AttributeDict
36 from rhodecode.lib.utils2 import AttributeDict
37
37
38 from rhodecode.model.db import Repository, CacheKey
38 from rhodecode.model.db import Repository, CacheKey
39
39
40
40
41 def _urls_for_proto(proto):
41 def _urls_for_proto(proto):
42 return [
42 return [
43 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
43 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
44 '%s://127.0.0.1' % proto),
44 '%s://127.0.0.1' % proto),
45 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
45 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
46 '%s://127.0.0.1' % proto),
46 '%s://127.0.0.1' % proto),
47 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
47 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
48 '%s://127.0.0.1' % proto),
48 '%s://127.0.0.1' % proto),
49 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
49 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
50 '%s://127.0.0.1:8080' % proto),
50 '%s://127.0.0.1:8080' % proto),
51 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
51 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
52 '%s://domain.org' % proto),
52 '%s://domain.org' % proto),
53 ('%s://user:pass@domain.org:8080' % proto,
53 ('%s://user:pass@domain.org:8080' % proto,
54 ['%s://' % proto, 'domain.org', '8080'],
54 ['%s://' % proto, 'domain.org', '8080'],
55 '%s://domain.org:8080' % proto),
55 '%s://domain.org:8080' % proto),
56 ]
56 ]
57
57
58 TEST_URLS = _urls_for_proto('http') + _urls_for_proto('https')
58 TEST_URLS = _urls_for_proto('http') + _urls_for_proto('https')
59
59
60
60
61 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
61 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
62 def test_uri_filter(test_url, expected, expected_creds):
62 def test_uri_filter(test_url, expected, expected_creds):
63 from rhodecode.lib.utils2 import uri_filter
63 from rhodecode.lib.utils2 import uri_filter
64 assert uri_filter(test_url) == expected
64 assert uri_filter(test_url) == expected
65
65
66
66
67 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
67 @pytest.mark.parametrize("test_url, expected, expected_creds", TEST_URLS)
68 def test_credentials_filter(test_url, expected, expected_creds):
68 def test_credentials_filter(test_url, expected, expected_creds):
69 from rhodecode.lib.utils2 import credentials_filter
69 from rhodecode.lib.utils2 import credentials_filter
70 assert credentials_filter(test_url) == expected_creds
70 assert credentials_filter(test_url) == expected_creds
71
71
72
72
73 @pytest.mark.parametrize("str_bool, expected", [
73 @pytest.mark.parametrize("str_bool, expected", [
74 ('t', True),
74 ('t', True),
75 ('true', True),
75 ('true', True),
76 ('y', True),
76 ('y', True),
77 ('yes', True),
77 ('yes', True),
78 ('on', True),
78 ('on', True),
79 ('1', True),
79 ('1', True),
80 ('Y', True),
80 ('Y', True),
81 ('yeS', True),
81 ('yeS', True),
82 ('Y', True),
82 ('Y', True),
83 ('TRUE', True),
83 ('TRUE', True),
84 ('T', True),
84 ('T', True),
85 ('False', False),
85 ('False', False),
86 ('F', False),
86 ('F', False),
87 ('FALSE', False),
87 ('FALSE', False),
88 ('0', False),
88 ('0', False),
89 ('-1', False),
89 ('-1', False),
90 ('', False)
90 ('', False)
91 ])
91 ])
92 def test_str2bool(str_bool, expected):
92 def test_str2bool(str_bool, expected):
93 from rhodecode.lib.utils2 import str2bool
93 from rhodecode.lib.utils2 import str2bool
94 assert str2bool(str_bool) == expected
94 assert str2bool(str_bool) == expected
95
95
96
96
97 @pytest.mark.parametrize("text, expected", reduce(lambda a1,a2:a1+a2, [
97 @pytest.mark.parametrize("text, expected", reduce(lambda a1,a2:a1+a2, [
98 [
98 [
99 (pref+"", []),
99 (pref+"", []),
100 (pref+"Hi there @marcink", ['marcink']),
100 (pref+"Hi there @marcink", ['marcink']),
101 (pref+"Hi there @marcink and @bob", ['bob', 'marcink']),
101 (pref+"Hi there @marcink and @bob", ['bob', 'marcink']),
102 (pref+"Hi there @marcink\n", ['marcink']),
102 (pref+"Hi there @marcink\n", ['marcink']),
103 (pref+"Hi there @marcink and @bob\n", ['bob', 'marcink']),
103 (pref+"Hi there @marcink and @bob\n", ['bob', 'marcink']),
104 (pref+"Hi there marcin@rhodecode.com", []),
104 (pref+"Hi there marcin@rhodecode.com", []),
105 (pref+"Hi there @john.malcovic and @bob\n", ['bob', 'john.malcovic']),
105 (pref+"Hi there @john.malcovic and @bob\n", ['bob', 'john.malcovic']),
106 (pref+"This needs to be reviewed: (@marcink,@john)", ["john", "marcink"]),
106 (pref+"This needs to be reviewed: (@marcink,@john)", ["john", "marcink"]),
107 (pref+"This needs to be reviewed: (@marcink, @john)", ["john", "marcink"]),
107 (pref+"This needs to be reviewed: (@marcink, @john)", ["john", "marcink"]),
108 (pref+"This needs to be reviewed: [@marcink,@john]", ["john", "marcink"]),
108 (pref+"This needs to be reviewed: [@marcink,@john]", ["john", "marcink"]),
109 (pref+"This needs to be reviewed: (@marcink @john)", ["john", "marcink"]),
109 (pref+"This needs to be reviewed: (@marcink @john)", ["john", "marcink"]),
110 (pref+"@john @mary, please review", ["john", "mary"]),
110 (pref+"@john @mary, please review", ["john", "mary"]),
111 (pref+"@john,@mary, please review", ["john", "mary"]),
111 (pref+"@john,@mary, please review", ["john", "mary"]),
112 (pref+"Hej @123, @22john,@mary, please review", ['123', '22john', 'mary']),
112 (pref+"Hej @123, @22john,@mary, please review", ['123', '22john', 'mary']),
113 (pref+"@first hi there @marcink here's my email marcin@email.com "
113 (pref+"@first hi there @marcink here's my email marcin@email.com "
114 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three ", ['first', 'lukaszb', 'marcink', 'one', 'one_more22']),
114 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three ", ['first', 'lukaszb', 'marcink', 'one', 'one_more22']),
115 (pref+"@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl", ['2one_more22', 'john', 'MARCIN', 'maRCiN']),
115 (pref+"@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl", ['2one_more22', 'john', 'MARCIN', 'maRCiN']),
116 (pref+"@marian.user just do it @marco-polo and next extract @marco_polo", ['marco-polo', 'marco_polo', 'marian.user']),
116 (pref+"@marian.user just do it @marco-polo and next extract @marco_polo", ['marco-polo', 'marco_polo', 'marian.user']),
117 (pref+"user.dot hej ! not-needed maril@domain.org", []),
117 (pref+"user.dot hej ! not-needed maril@domain.org", []),
118 (pref+"\n@marcin", ['marcin']),
118 (pref+"\n@marcin", ['marcin']),
119 ]
119 ]
120 for pref in ['', '\n', 'hi !', '\t', '\n\n']]), ids=no_newline_id_generator)
120 for pref in ['', '\n', 'hi !', '\t', '\n\n']]), ids=no_newline_id_generator)
121 def test_mention_extractor(text, expected):
121 def test_mention_extractor(text, expected):
122 from rhodecode.lib.utils2 import extract_mentioned_users
122 from rhodecode.lib.utils2 import extract_mentioned_users
123 got = extract_mentioned_users(text)
123 got = extract_mentioned_users(text)
124 assert sorted(got, key=lambda x: x.lower()) == got
124 assert sorted(got, key=lambda x: x.lower()) == got
125 assert set(expected) == set(got)
125 assert set(expected) == set(got)
126
126
127 @pytest.mark.parametrize("age_args, expected, kw", [
127 @pytest.mark.parametrize("age_args, expected, kw", [
128 ({}, u'just now', {}),
128 ({}, u'just now', {}),
129 ({'seconds': -1}, u'1 second ago', {}),
129 ({'seconds': -1}, u'1 second ago', {}),
130 ({'seconds': -60 * 2}, u'2 minutes ago', {}),
130 ({'seconds': -60 * 2}, u'2 minutes ago', {}),
131 ({'hours': -1}, u'1 hour ago', {}),
131 ({'hours': -1}, u'1 hour ago', {}),
132 ({'hours': -24}, u'1 day ago', {}),
132 ({'hours': -24}, u'1 day ago', {}),
133 ({'hours': -24 * 5}, u'5 days ago', {}),
133 ({'hours': -24 * 5}, u'5 days ago', {}),
134 ({'months': -1}, u'1 month ago', {}),
134 ({'months': -1}, u'1 month ago', {}),
135 ({'months': -1, 'days': -2}, u'1 month and 2 days ago', {}),
135 ({'months': -1, 'days': -2}, u'1 month and 2 days ago', {}),
136 ({'years': -1, 'months': -1}, u'1 year and 1 month ago', {}),
136 ({'years': -1, 'months': -1}, u'1 year and 1 month ago', {}),
137 ({}, u'just now', {'short_format': True}),
137 ({}, u'just now', {'short_format': True}),
138 ({'seconds': -1}, u'1sec ago', {'short_format': True}),
138 ({'seconds': -1}, u'1sec ago', {'short_format': True}),
139 ({'seconds': -60 * 2}, u'2min ago', {'short_format': True}),
139 ({'seconds': -60 * 2}, u'2min ago', {'short_format': True}),
140 ({'hours': -1}, u'1h ago', {'short_format': True}),
140 ({'hours': -1}, u'1h ago', {'short_format': True}),
141 ({'hours': -24}, u'1d ago', {'short_format': True}),
141 ({'hours': -24}, u'1d ago', {'short_format': True}),
142 ({'hours': -24 * 5}, u'5d ago', {'short_format': True}),
142 ({'hours': -24 * 5}, u'5d ago', {'short_format': True}),
143 ({'months': -1}, u'1m ago', {'short_format': True}),
143 ({'months': -1}, u'1m ago', {'short_format': True}),
144 ({'months': -1, 'days': -2}, u'1m, 2d ago', {'short_format': True}),
144 ({'months': -1, 'days': -2}, u'1m, 2d ago', {'short_format': True}),
145 ({'years': -1, 'months': -1}, u'1y, 1m ago', {'short_format': True}),
145 ({'years': -1, 'months': -1}, u'1y, 1m ago', {'short_format': True}),
146 ])
146 ])
147 def test_age(age_args, expected, kw, baseapp):
147 def test_age(age_args, expected, kw, baseapp):
148 from rhodecode.lib.utils2 import age
148 from rhodecode.lib.utils2 import age
149 from dateutil import relativedelta
149 from dateutil import relativedelta
150 n = datetime.datetime(year=2012, month=5, day=17)
150 n = datetime.datetime(year=2012, month=5, day=17)
151 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
151 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
152
152
153 def translate(elem):
153 def translate(elem):
154 return elem.interpolate()
154 return elem.interpolate()
155
155
156 assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
156 assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
157
157
158
158
159 @pytest.mark.parametrize("age_args, expected, kw", [
159 @pytest.mark.parametrize("age_args, expected, kw", [
160 ({}, u'just now', {}),
160 ({}, u'just now', {}),
161 ({'seconds': 1}, u'in 1 second', {}),
161 ({'seconds': 1}, u'in 1 second', {}),
162 ({'seconds': 60 * 2}, u'in 2 minutes', {}),
162 ({'seconds': 60 * 2}, u'in 2 minutes', {}),
163 ({'hours': 1}, u'in 1 hour', {}),
163 ({'hours': 1}, u'in 1 hour', {}),
164 ({'hours': 24}, u'in 1 day', {}),
164 ({'hours': 24}, u'in 1 day', {}),
165 ({'hours': 24 * 5}, u'in 5 days', {}),
165 ({'hours': 24 * 5}, u'in 5 days', {}),
166 ({'months': 1}, u'in 1 month', {}),
166 ({'months': 1}, u'in 1 month', {}),
167 ({'months': 1, 'days': 1}, u'in 1 month and 1 day', {}),
167 ({'months': 1, 'days': 1}, u'in 1 month and 1 day', {}),
168 ({'years': 1, 'months': 1}, u'in 1 year and 1 month', {}),
168 ({'years': 1, 'months': 1}, u'in 1 year and 1 month', {}),
169 ({}, u'just now', {'short_format': True}),
169 ({}, u'just now', {'short_format': True}),
170 ({'seconds': 1}, u'in 1sec', {'short_format': True}),
170 ({'seconds': 1}, u'in 1sec', {'short_format': True}),
171 ({'seconds': 60 * 2}, u'in 2min', {'short_format': True}),
171 ({'seconds': 60 * 2}, u'in 2min', {'short_format': True}),
172 ({'hours': 1}, u'in 1h', {'short_format': True}),
172 ({'hours': 1}, u'in 1h', {'short_format': True}),
173 ({'hours': 24}, u'in 1d', {'short_format': True}),
173 ({'hours': 24}, u'in 1d', {'short_format': True}),
174 ({'hours': 24 * 5}, u'in 5d', {'short_format': True}),
174 ({'hours': 24 * 5}, u'in 5d', {'short_format': True}),
175 ({'months': 1}, u'in 1m', {'short_format': True}),
175 ({'months': 1}, u'in 1m', {'short_format': True}),
176 ({'months': 1, 'days': 1}, u'in 1m, 1d', {'short_format': True}),
176 ({'months': 1, 'days': 1}, u'in 1m, 1d', {'short_format': True}),
177 ({'years': 1, 'months': 1}, u'in 1y, 1m', {'short_format': True}),
177 ({'years': 1, 'months': 1}, u'in 1y, 1m', {'short_format': True}),
178 ])
178 ])
179 def test_age_in_future(age_args, expected, kw, baseapp):
179 def test_age_in_future(age_args, expected, kw, baseapp):
180 from rhodecode.lib.utils2 import age
180 from rhodecode.lib.utils2 import age
181 from dateutil import relativedelta
181 from dateutil import relativedelta
182 n = datetime.datetime(year=2012, month=5, day=17)
182 n = datetime.datetime(year=2012, month=5, day=17)
183 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
183 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
184
184
185 def translate(elem):
185 def translate(elem):
186 return elem.interpolate()
186 return elem.interpolate()
187
187
188 assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
188 assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
189
189
190
190
191 @pytest.mark.parametrize("sample, expected_tags", [
191 @pytest.mark.parametrize("sample, expected_tags", [
192 # entry
192 # entry
193 ((
193 ((
194 ""
194 ""
195 ),
195 ),
196 [
196 [
197
197
198 ]),
198 ]),
199 # entry
199 # entry
200 ((
200 ((
201 "hello world [stale]"
201 "hello world [stale]"
202 ),
202 ),
203 [
203 [
204 ('state', '[stale]'),
204 ('state', '[stale]'),
205 ]),
205 ]),
206 # entry
206 # entry
207 ((
207 ((
208 "hello world [v2.0.0] [v1.0.0]"
208 "hello world [v2.0.0] [v1.0.0]"
209 ),
209 ),
210 [
210 [
211 ('generic', '[v2.0.0]'),
211 ('generic', '[v2.0.0]'),
212 ('generic', '[v1.0.0]'),
212 ('generic', '[v1.0.0]'),
213 ]),
213 ]),
214 # entry
214 # entry
215 ((
215 ((
216 "he[ll]o wo[rl]d"
216 "he[ll]o wo[rl]d"
217 ),
217 ),
218 [
218 [
219 ('label', '[ll]'),
219 ('label', '[ll]'),
220 ('label', '[rl]'),
220 ('label', '[rl]'),
221 ]),
221 ]),
222 # entry
222 # entry
223 ((
223 ((
224 "hello world [stale]\n[featured]\n[stale] [dead] [dev]"
224 "hello world [stale]\n[featured]\n[stale] [dead] [dev]"
225 ),
225 ),
226 [
226 [
227 ('state', '[stale]'),
227 ('state', '[stale]'),
228 ('state', '[featured]'),
228 ('state', '[featured]'),
229 ('state', '[stale]'),
229 ('state', '[stale]'),
230 ('state', '[dead]'),
230 ('state', '[dead]'),
231 ('state', '[dev]'),
231 ('state', '[dev]'),
232 ]),
232 ]),
233 # entry
233 # entry
234 ((
234 ((
235 "hello world \n\n [stale] \n [url =&gt; [name](http://rc.com)]"
235 "hello world \n\n [stale] \n [url =&gt; [name](http://rc.com)]"
236 ),
236 ),
237 [
237 [
238 ('state', '[stale]'),
238 ('state', '[stale]'),
239 ('url', '[url =&gt; [name](http://rc.com)]'),
239 ('url', '[url =&gt; [name](http://rc.com)]'),
240 ]),
240 ]),
241 # entry
241 # entry
242 ((
242 ((
243 "[url =&gt; [linkNameJS](javascript:alert(document.domain))]\n"
243 "[url =&gt; [linkNameJS](javascript:alert(document.domain))]\n"
244 "[url =&gt; [linkNameHTTP](http://rhodecode.com)]\n"
244 "[url =&gt; [linkNameHTTP](http://rhodecode.com)]\n"
245 "[url =&gt; [linkNameHTTPS](https://rhodecode.com)]\n"
245 "[url =&gt; [linkNameHTTPS](https://rhodecode.com)]\n"
246 "[url =&gt; [linkNamePath](/repo_group)]\n"
246 "[url =&gt; [linkNamePath](/repo_group)]\n"
247 ),
247 ),
248 [
248 [
249 ('generic', '[linkNameJS]'),
249 ('generic', '[linkNameJS]'),
250 ('url', '[url =&gt; [linkNameHTTP](http://rhodecode.com)]'),
250 ('url', '[url =&gt; [linkNameHTTP](http://rhodecode.com)]'),
251 ('url', '[url =&gt; [linkNameHTTPS](https://rhodecode.com)]'),
251 ('url', '[url =&gt; [linkNameHTTPS](https://rhodecode.com)]'),
252 ('url', '[url =&gt; [linkNamePath](/repo_group)]'),
252 ('url', '[url =&gt; [linkNamePath](/repo_group)]'),
253 ]),
253 ]),
254 # entry
254 # entry
255 ((
255 ((
256 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =&gt;>< sa]"
256 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =&gt;>< sa]"
257 "[requires] [stale] [see<>=&gt;] [see =&gt; http://url.com]"
257 "[requires] [stale] [see<>=&gt;] [see =&gt; http://url.com]"
258 "[requires =&gt; url] [lang =&gt; python] [just a tag] "
258 "[requires =&gt; url] [lang =&gt; python] [just a tag] "
259 "<html_tag first='abc' attr=\"my.url?attr=&another=\"></html_tag>"
259 "<html_tag first='abc' attr=\"my.url?attr=&another=\"></html_tag>"
260 "[,d] [ =&gt; ULR ] [obsolete] [desc]]"
260 "[,d] [ =&gt; ULR ] [obsolete] [desc]]"
261 ),
261 ),
262 [
262 [
263 ('label', '[desc]'),
263 ('label', '[desc]'),
264 ('label', '[obsolete]'),
264 ('label', '[obsolete]'),
265 ('label', '[or]'),
265 ('label', '[or]'),
266 ('label', '[requires]'),
266 ('label', '[requires]'),
267 ('label', '[tag]'),
267 ('label', '[tag]'),
268 ('state', '[stale]'),
268 ('state', '[stale]'),
269 ('lang', '[lang =&gt; python]'),
269 ('lang', '[lang =&gt; python]'),
270 ('ref', '[requires =&gt; url]'),
270 ('ref', '[requires =&gt; url]'),
271 ('see', '[see =&gt; http://url.com]'),
271 ('see', '[see =&gt; http://url.com]'),
272
272
273 ]),
273 ]),
274
274
275 ], ids=no_newline_id_generator)
275 ], ids=no_newline_id_generator)
276 def test_metatag_extraction(sample, expected_tags):
276 def test_metatag_extraction(sample, expected_tags):
277 from rhodecode.lib.helpers import extract_metatags
277 from rhodecode.lib.helpers import extract_metatags
278 tags, value = extract_metatags(sample)
278 tags, value = extract_metatags(sample)
279 assert sorted(tags) == sorted(expected_tags)
279 assert sorted(tags) == sorted(expected_tags)
280
280
281
281
282 @pytest.mark.parametrize("tag_data, expected_html", [
282 @pytest.mark.parametrize("tag_data, expected_html", [
283
283
284 (('state', '[stable]'), '<div class="metatag" tag="state stable">stable</div>'),
284 (('state', '[stable]'), '<div class="metatag" tag="state stable">stable</div>'),
285 (('state', '[stale]'), '<div class="metatag" tag="state stale">stale</div>'),
285 (('state', '[stale]'), '<div class="metatag" tag="state stale">stale</div>'),
286 (('state', '[featured]'), '<div class="metatag" tag="state featured">featured</div>'),
286 (('state', '[featured]'), '<div class="metatag" tag="state featured">featured</div>'),
287 (('state', '[dev]'), '<div class="metatag" tag="state dev">dev</div>'),
287 (('state', '[dev]'), '<div class="metatag" tag="state dev">dev</div>'),
288 (('state', '[dead]'), '<div class="metatag" tag="state dead">dead</div>'),
288 (('state', '[dead]'), '<div class="metatag" tag="state dead">dead</div>'),
289
289
290 (('label', '[personal]'), '<div class="metatag" tag="label">personal</div>'),
290 (('label', '[personal]'), '<div class="metatag" tag="label">personal</div>'),
291 (('generic', '[v2.0.0]'), '<div class="metatag" tag="generic">v2.0.0</div>'),
291 (('generic', '[v2.0.0]'), '<div class="metatag" tag="generic">v2.0.0</div>'),
292
292
293 (('lang', '[lang =&gt; JavaScript]'), '<div class="metatag" tag="lang">JavaScript</div>'),
293 (('lang', '[lang =&gt; JavaScript]'), '<div class="metatag" tag="lang">JavaScript</div>'),
294 (('lang', '[lang =&gt; C++]'), '<div class="metatag" tag="lang">C++</div>'),
294 (('lang', '[lang =&gt; C++]'), '<div class="metatag" tag="lang">C++</div>'),
295 (('lang', '[lang =&gt; C#]'), '<div class="metatag" tag="lang">C#</div>'),
295 (('lang', '[lang =&gt; C#]'), '<div class="metatag" tag="lang">C#</div>'),
296 (('lang', '[lang =&gt; Delphi/Object]'), '<div class="metatag" tag="lang">Delphi/Object</div>'),
296 (('lang', '[lang =&gt; Delphi/Object]'), '<div class="metatag" tag="lang">Delphi/Object</div>'),
297 (('lang', '[lang =&gt; Objective-C]'), '<div class="metatag" tag="lang">Objective-C</div>'),
297 (('lang', '[lang =&gt; Objective-C]'), '<div class="metatag" tag="lang">Objective-C</div>'),
298 (('lang', '[lang =&gt; .NET]'), '<div class="metatag" tag="lang">.NET</div>'),
298 (('lang', '[lang =&gt; .NET]'), '<div class="metatag" tag="lang">.NET</div>'),
299
299
300 (('license', '[license =&gt; BSD 3-clause]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/BSD 3-clause">BSD 3-clause</a></div>'),
300 (('license', '[license =&gt; BSD 3-clause]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/BSD 3-clause">BSD 3-clause</a></div>'),
301 (('license', '[license =&gt; GPLv3]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/GPLv3">GPLv3</a></div>'),
301 (('license', '[license =&gt; GPLv3]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/GPLv3">GPLv3</a></div>'),
302 (('license', '[license =&gt; MIT]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/MIT">MIT</a></div>'),
302 (('license', '[license =&gt; MIT]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/MIT">MIT</a></div>'),
303 (('license', '[license =&gt; AGPLv3]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/AGPLv3">AGPLv3</a></div>'),
303 (('license', '[license =&gt; AGPLv3]'), '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/AGPLv3">AGPLv3</a></div>'),
304
304
305 (('ref', '[requires =&gt; RepoName]'), '<div class="metatag" tag="ref requires">requires: <a href="/RepoName">RepoName</a></div>'),
305 (('ref', '[requires =&gt; RepoName]'), '<div class="metatag" tag="ref requires">requires: <a href="/RepoName">RepoName</a></div>'),
306 (('ref', '[recommends =&gt; GroupName]'), '<div class="metatag" tag="ref recommends">recommends: <a href="/GroupName">GroupName</a></div>'),
306 (('ref', '[recommends =&gt; GroupName]'), '<div class="metatag" tag="ref recommends">recommends: <a href="/GroupName">GroupName</a></div>'),
307 (('ref', '[conflicts =&gt; SomeName]'), '<div class="metatag" tag="ref conflicts">conflicts: <a href="/SomeName">SomeName</a></div>'),
307 (('ref', '[conflicts =&gt; SomeName]'), '<div class="metatag" tag="ref conflicts">conflicts: <a href="/SomeName">SomeName</a></div>'),
308 (('ref', '[base =&gt; SomeName]'), '<div class="metatag" tag="ref base">base: <a href="/SomeName">SomeName</a></div>'),
308 (('ref', '[base =&gt; SomeName]'), '<div class="metatag" tag="ref base">base: <a href="/SomeName">SomeName</a></div>'),
309
309
310 (('see', '[see =&gt; http://rhodecode.com]'), '<div class="metatag" tag="see">see: http://rhodecode.com </div>'),
310 (('see', '[see =&gt; http://rhodecode.com]'), '<div class="metatag" tag="see">see: http://rhodecode.com </div>'),
311
311
312 (('url', '[url =&gt; [linkName](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">linkName</a> </div>'),
312 (('url', '[url =&gt; [linkName](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">linkName</a> </div>'),
313 (('url', '[url =&gt; [example link](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">example link</a> </div>'),
313 (('url', '[url =&gt; [example link](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">example link</a> </div>'),
314 (('url', '[url =&gt; [v1.0.0](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">v1.0.0</a> </div>'),
314 (('url', '[url =&gt; [v1.0.0](https://rhodecode.com)]'), '<div class="metatag" tag="url"> <a href="https://rhodecode.com">v1.0.0</a> </div>'),
315
315
316 ])
316 ])
317 def test_metatags_stylize(tag_data, expected_html):
317 def test_metatags_stylize(tag_data, expected_html):
318 from rhodecode.lib.helpers import style_metatag
318 from rhodecode.lib.helpers import style_metatag
319 tag_type,value = tag_data
319 tag_type,value = tag_data
320 assert style_metatag(tag_type, value) == expected_html
320 assert style_metatag(tag_type, value) == expected_html
321
321
322
322
323 @pytest.mark.parametrize("tmpl_url, email, expected", [
323 @pytest.mark.parametrize("tmpl_url, email, expected", [
324 ('http://test.com/{email}', 'test@foo.com', 'http://test.com/test@foo.com'),
324 ('http://test.com/{email}', 'test@foo.com', 'http://test.com/test@foo.com'),
325
325
326 ('http://test.com/{md5email}', 'test@foo.com', 'http://test.com/3cb7232fcc48743000cb86d0d5022bd9'),
326 ('http://test.com/{md5email}', 'test@foo.com', 'http://test.com/3cb7232fcc48743000cb86d0d5022bd9'),
327 ('http://test.com/{md5email}', 'testΔ…Δ‡@foo.com', 'http://test.com/978debb907a3c55cd741872ab293ef30'),
327 ('http://test.com/{md5email}', 'testΔ…Δ‡@foo.com', 'http://test.com/978debb907a3c55cd741872ab293ef30'),
328
328
329 ('http://testX.com/{md5email}?s={size}', 'test@foo.com', 'http://testX.com/3cb7232fcc48743000cb86d0d5022bd9?s=24'),
329 ('http://testX.com/{md5email}?s={size}', 'test@foo.com', 'http://testX.com/3cb7232fcc48743000cb86d0d5022bd9?s=24'),
330 ('http://testX.com/{md5email}?s={size}', 'testΔ…Δ‡@foo.com', 'http://testX.com/978debb907a3c55cd741872ab293ef30?s=24'),
330 ('http://testX.com/{md5email}?s={size}', 'testΔ…Δ‡@foo.com', 'http://testX.com/978debb907a3c55cd741872ab293ef30?s=24'),
331
331
332 ('{scheme}://{netloc}/{md5email}/{size}', 'test@foo.com', 'https://server.com/3cb7232fcc48743000cb86d0d5022bd9/24'),
332 ('{scheme}://{netloc}/{md5email}/{size}', 'test@foo.com', 'https://server.com/3cb7232fcc48743000cb86d0d5022bd9/24'),
333 ('{scheme}://{netloc}/{md5email}/{size}', 'testΔ…Δ‡@foo.com', 'https://server.com/978debb907a3c55cd741872ab293ef30/24'),
333 ('{scheme}://{netloc}/{md5email}/{size}', 'testΔ…Δ‡@foo.com', 'https://server.com/978debb907a3c55cd741872ab293ef30/24'),
334
334
335 ('http://test.com/{email}', 'testΔ…Δ‡@foo.com', 'http://test.com/testΔ…Δ‡@foo.com'),
335 ('http://test.com/{email}', 'testΔ…Δ‡@foo.com', 'http://test.com/testΔ…Δ‡@foo.com'),
336 ('http://test.com/{email}?size={size}', 'test@foo.com', 'http://test.com/test@foo.com?size=24'),
336 ('http://test.com/{email}?size={size}', 'test@foo.com', 'http://test.com/test@foo.com?size=24'),
337 ('http://test.com/{email}?size={size}', 'testΔ…Δ‡@foo.com', 'http://test.com/testΔ…Δ‡@foo.com?size=24'),
337 ('http://test.com/{email}?size={size}', 'testΔ…Δ‡@foo.com', 'http://test.com/testΔ…Δ‡@foo.com?size=24'),
338 ])
338 ])
339 def test_gravatar_url_builder(tmpl_url, email, expected, request_stub):
339 def test_gravatar_url_builder(tmpl_url, email, expected, request_stub):
340 from rhodecode.lib.helpers import gravatar_url
340 from rhodecode.lib.helpers import gravatar_url
341
341
342 def fake_tmpl_context(_url):
342 def fake_tmpl_context(_url):
343 _c = AttributeDict()
343 _c = AttributeDict()
344 _c.visual = AttributeDict()
344 _c.visual = AttributeDict()
345 _c.visual.use_gravatar = True
345 _c.visual.use_gravatar = True
346 _c.visual.gravatar_url = _url
346 _c.visual.gravatar_url = _url
347 return _c
347 return _c
348
348
349 # mock pyramid.threadlocals
349 # mock pyramid.threadlocals
350 def fake_get_current_request():
350 def fake_get_current_request():
351 request_stub.scheme = 'https'
351 request_stub.scheme = 'https'
352 request_stub.host = 'server.com'
352 request_stub.host = 'server.com'
353
353
354 request_stub._call_context = fake_tmpl_context(tmpl_url)
354 request_stub._call_context = fake_tmpl_context(tmpl_url)
355 return request_stub
355 return request_stub
356
356
357 with mock.patch('rhodecode.lib.helpers.get_current_request',
357 with mock.patch('rhodecode.lib.helpers.get_current_request',
358 fake_get_current_request):
358 fake_get_current_request):
359
359
360 grav = gravatar_url(email_address=email, size=24)
360 grav = gravatar_url(email_address=email, size=24)
361 assert grav == expected
361 assert grav == expected
362
362
363
363
364 @pytest.mark.parametrize(
364 @pytest.mark.parametrize(
365 "email, first_name, last_name, expected_initials, expected_color", [
365 "email, first_name, last_name, expected_initials, expected_color", [
366
366
367 ('test@rhodecode.com', '', '', 'TR', '#8a994d'),
367 ('test@rhodecode.com', '', '', 'TR', '#8a994d'),
368 ('marcin.kuzminski@rhodecode.com', '', '', 'MK', '#6559b3'),
368 ('marcin.kuzminski@rhodecode.com', '', '', 'MK', '#6559b3'),
369 # special cases of email
369 # special cases of email
370 ('john.van.dam@rhodecode.com', '', '', 'JD', '#526600'),
370 ('john.van.dam@rhodecode.com', '', '', 'JD', '#526600'),
371 ('Guido.van.Rossum@rhodecode.com', '', '', 'GR', '#990052'),
371 ('Guido.van.Rossum@rhodecode.com', '', '', 'GR', '#990052'),
372 ('Guido.van.Rossum@rhodecode.com', 'Guido', 'Van Rossum', 'GR', '#990052'),
372 ('Guido.van.Rossum@rhodecode.com', 'Guido', 'Van Rossum', 'GR', '#990052'),
373
373
374 ('rhodecode+Guido.van.Rossum@rhodecode.com', '', '', 'RR', '#46598c'),
374 ('rhodecode+Guido.van.Rossum@rhodecode.com', '', '', 'RR', '#46598c'),
375 ('pclouds@rhodecode.com', 'Nguyα»…n ThΓ‘i', 'Tgọc Duy', 'ND', '#665200'),
375 ('pclouds@rhodecode.com', 'Nguyα»…n ThΓ‘i', 'Tgọc Duy', 'ND', '#665200'),
376
376
377 ('john-brown@foo.com', '', '', 'JF', '#73006b'),
377 ('john-brown@foo.com', '', '', 'JF', '#73006b'),
378 ('admin@rhodecode.com', 'Marcin', 'Kuzminski', 'MK', '#104036'),
378 ('admin@rhodecode.com', 'Marcin', 'Kuzminski', 'MK', '#104036'),
379 # partials
379 # partials
380 ('admin@rhodecode.com', 'Marcin', '', 'MR', '#104036'), # fn+email
380 ('admin@rhodecode.com', 'Marcin', '', 'MR', '#104036'), # fn+email
381 ('admin@rhodecode.com', '', 'Kuzminski', 'AK', '#104036'), # em+ln
381 ('admin@rhodecode.com', '', 'Kuzminski', 'AK', '#104036'), # em+ln
382 # non-ascii
382 # non-ascii
383 ('admin@rhodecode.com', 'Marcin', 'Śuzminski', 'MS', '#104036'),
383 ('admin@rhodecode.com', 'Marcin', 'Śuzminski', 'MS', '#104036'),
384 ('marcin.Ε›uzminski@rhodecode.com', '', '', 'MS', '#73000f'),
384 ('marcin.Ε›uzminski@rhodecode.com', '', '', 'MS', '#73000f'),
385
385
386 # special cases, LDAP can provide those...
386 # special cases, LDAP can provide those...
387 ('admin@', 'Marcin', 'Śuzminski', 'MS', '#aa00ff'),
387 ('admin@', 'Marcin', 'Śuzminski', 'MS', '#aa00ff'),
388 ('marcin.Ε›uzminski', '', '', 'MS', '#402020'),
388 ('marcin.Ε›uzminski', '', '', 'MS', '#402020'),
389 ('null', '', '', 'NL', '#8c4646'),
389 ('null', '', '', 'NL', '#8c4646'),
390 ('some.@abc.com', 'some', '', 'SA', '#664e33')
390 ('some.@abc.com', 'some', '', 'SA', '#664e33')
391 ])
391 ])
392 def test_initials_gravatar_pick_of_initials_and_color_algo(
392 def test_initials_gravatar_pick_of_initials_and_color_algo(
393 email, first_name, last_name, expected_initials, expected_color):
393 email, first_name, last_name, expected_initials, expected_color):
394 instance = InitialsGravatar(email, first_name, last_name)
394 instance = InitialsGravatar(email, first_name, last_name)
395 assert instance.get_initials() == expected_initials
395 assert instance.get_initials() == expected_initials
396 assert instance.str2color(email) == expected_color
396 assert instance.str2color(email) == expected_color
397
397
398
398
399 def test_initials_gravatar_mapping_algo():
399 def test_initials_gravatar_mapping_algo():
400 pos = set()
400 pos = set()
401 instance = InitialsGravatar('', '', '')
401 instance = InitialsGravatar('', '', '')
402 iterations = 0
402 iterations = 0
403
403
404 variations = []
404 variations = []
405 for letter1 in string.ascii_letters:
405 for letter1 in string.ascii_letters:
406 for letter2 in string.ascii_letters[::-1][:10]:
406 for letter2 in string.ascii_letters[::-1][:10]:
407 for letter3 in string.ascii_letters[:10]:
407 for letter3 in string.ascii_letters[:10]:
408 variations.append(
408 variations.append(
409 '%s@rhodecode.com' % (letter1+letter2+letter3))
409 '%s@rhodecode.com' % (letter1+letter2+letter3))
410
410
411 max_variations = 4096
411 max_variations = 4096
412 for email in variations[:max_variations]:
412 for email in variations[:max_variations]:
413 iterations += 1
413 iterations += 1
414 pos.add(
414 pos.add(
415 instance.pick_color_bank_index(email,
415 instance.pick_color_bank_index(email,
416 instance.get_color_bank()))
416 instance.get_color_bank()))
417
417
418 # we assume that we have match all 256 possible positions,
418 # we assume that we have match all 256 possible positions,
419 # in reasonable amount of different email addresses
419 # in reasonable amount of different email addresses
420 assert len(pos) == 256
420 assert len(pos) == 256
421 assert iterations == max_variations
421 assert iterations == max_variations
422
422
423
423
424 @pytest.mark.parametrize("tmpl, repo_name, overrides, prefix, expected", [
424 @pytest.mark.parametrize("tmpl, repo_name, overrides, prefix, expected", [
425 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '', 'http://vps1:8000/group/repo1'),
425 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '', 'http://vps1:8000/group/repo1'),
426 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/group/repo1'),
426 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/group/repo1'),
427 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '/rc', 'http://vps1:8000/rc/group/repo1'),
427 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '/rc', 'http://vps1:8000/rc/group/repo1'),
428 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc', 'http://user@vps1:8000/rc/group/repo1'),
428 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc', 'http://user@vps1:8000/rc/group/repo1'),
429 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc', 'http://marcink@vps1:8000/rc/group/repo1'),
429 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc', 'http://marcink@vps1:8000/rc/group/repo1'),
430 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc/', 'http://user@vps1:8000/rc/group/repo1'),
430 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'user'}, '/rc/', 'http://user@vps1:8000/rc/group/repo1'),
431 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc/', 'http://marcink@vps1:8000/rc/group/repo1'),
431 (Repository.DEFAULT_CLONE_URI, 'group/repo1', {'user': 'marcink'}, '/rc/', 'http://marcink@vps1:8000/rc/group/repo1'),
432 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {}, '', 'http://vps1:8000/_23'),
432 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {}, '', 'http://vps1:8000/_23'),
433 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
433 ('{scheme}://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
434 ('http://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
434 ('http://{user}@{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://marcink@vps1:8000/_23'),
435 ('http://{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://vps1:8000/_23'),
435 ('http://{netloc}/_{repoid}', 'group/repo1', {'user': 'marcink'}, '', 'http://vps1:8000/_23'),
436 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://marcink@proxy1.server.com/group/repo1'),
436 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://marcink@proxy1.server.com/group/repo1'),
437 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {}, '', 'https://proxy1.server.com/group/repo1'),
437 ('https://{user}@proxy1.server.com/{repo}', 'group/repo1', {}, '', 'https://proxy1.server.com/group/repo1'),
438 ('https://proxy1.server.com/{user}/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://proxy1.server.com/marcink/group/repo1'),
438 ('https://proxy1.server.com/{user}/{repo}', 'group/repo1', {'user': 'marcink'}, '', 'https://proxy1.server.com/marcink/group/repo1'),
439 ])
439 ])
440 def test_clone_url_generator(tmpl, repo_name, overrides, prefix, expected):
440 def test_clone_url_generator(tmpl, repo_name, overrides, prefix, expected):
441 from rhodecode.lib.utils2 import get_clone_url
441 from rhodecode.lib.utils2 import get_clone_url
442
442
443 class RequestStub(object):
443 class RequestStub(object):
444 def request_url(self, name):
444 def request_url(self, name):
445 return 'http://vps1:8000' + prefix
445 return 'http://vps1:8000' + prefix
446
446
447 def route_url(self, name):
447 def route_url(self, name):
448 return self.request_url(name)
448 return self.request_url(name)
449
449
450 clone_url = get_clone_url(
450 clone_url = get_clone_url(
451 request=RequestStub(),
451 request=RequestStub(),
452 uri_tmpl=tmpl,
452 uri_tmpl=tmpl,
453 repo_name=repo_name, repo_id=23, **overrides)
453 repo_name=repo_name, repo_id=23, repo_type='hg', **overrides)
454 assert clone_url == expected
454 assert clone_url == expected
455
455
456
456
457 def test_clone_url_svn_ssh_generator():
458 from rhodecode.lib.utils2 import get_clone_url
459
460 class RequestStub(object):
461 def request_url(self, name):
462 return 'http://vps1:8000'
463
464 def route_url(self, name):
465 return self.request_url(name)
466
467 clone_url = get_clone_url(
468 request=RequestStub(),
469 uri_tmpl=Repository.DEFAULT_CLONE_URI_SSH,
470 repo_name='svn-test', repo_id=23, repo_type='svn', **{'sys_user': 'rcdev'})
471 assert clone_url == 'svn+ssh://rcdev@vps1/svn-test'
472
473
457 idx = 0
474 idx = 0
458
475
459
476
460 def _quick_url(text, tmpl="""<a class="tooltip-hovercard revision-link" href="%s" data-hovercard-alt="Commit: %s" data-hovercard-url="/some-url">%s</a>""", url_=None, commits=''):
477 def _quick_url(text, tmpl="""<a class="tooltip-hovercard revision-link" href="%s" data-hovercard-alt="Commit: %s" data-hovercard-url="/some-url">%s</a>""", url_=None, commits=''):
461 """
478 """
462 Changes `some text url[foo]` => `some text <a href="/">foo</a>
479 Changes `some text url[foo]` => `some text <a href="/">foo</a>
463
480
464 :param text:
481 :param text:
465 """
482 """
466 import re
483 import re
467 # quickly change expected url[] into a link
484 # quickly change expected url[] into a link
468 url_pat = re.compile(r'(?:url\[)(.+?)(?:\])')
485 url_pat = re.compile(r'(?:url\[)(.+?)(?:\])')
469 commits = commits or []
486 commits = commits or []
470
487
471 global idx
488 global idx
472 idx = 0
489 idx = 0
473
490
474 def url_func(match_obj):
491 def url_func(match_obj):
475 global idx
492 global idx
476 _url = match_obj.groups()[0]
493 _url = match_obj.groups()[0]
477 if commits:
494 if commits:
478 commit = commits[idx]
495 commit = commits[idx]
479 idx += 1
496 idx += 1
480 return tmpl % (url_ or '/some-url', _url, commit)
497 return tmpl % (url_ or '/some-url', _url, commit)
481 else:
498 else:
482 return tmpl % (url_ or '/some-url', _url)
499 return tmpl % (url_ or '/some-url', _url)
483
500
484 return url_pat.sub(url_func, text)
501 return url_pat.sub(url_func, text)
485
502
486
503
487 @pytest.mark.parametrize("sample, expected, commits", [
504 @pytest.mark.parametrize("sample, expected, commits", [
488 (
505 (
489 "",
506 "",
490 "",
507 "",
491 [""]
508 [""]
492 ),
509 ),
493 (
510 (
494 "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
511 "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
495 "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
512 "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
496 [""]
513 [""]
497 ),
514 ),
498 (
515 (
499 "from rev 000000000000",
516 "from rev 000000000000",
500 "from rev url[000000000000]",
517 "from rev url[000000000000]",
501 ["000000000000"]
518 ["000000000000"]
502 ),
519 ),
503
520
504 (
521 (
505 "from rev 000000000000123123 also rev 000000000000",
522 "from rev 000000000000123123 also rev 000000000000",
506 "from rev url[000000000000123123] also rev url[000000000000]",
523 "from rev url[000000000000123123] also rev url[000000000000]",
507 ["000000000000123123", "000000000000"]
524 ["000000000000123123", "000000000000"]
508 ),
525 ),
509 (
526 (
510 "this should-000 00",
527 "this should-000 00",
511 "this should-000 00",
528 "this should-000 00",
512 [""]
529 [""]
513 ),
530 ),
514 (
531 (
515 "longtextffffffffff rev 123123123123",
532 "longtextffffffffff rev 123123123123",
516 "longtextffffffffff rev url[123123123123]",
533 "longtextffffffffff rev url[123123123123]",
517 ["123123123123"]
534 ["123123123123"]
518 ),
535 ),
519 (
536 (
520 "rev ffffffffffffffffffffffffffffffffffffffffffffffffff",
537 "rev ffffffffffffffffffffffffffffffffffffffffffffffffff",
521 "rev ffffffffffffffffffffffffffffffffffffffffffffffffff",
538 "rev ffffffffffffffffffffffffffffffffffffffffffffffffff",
522 ["ffffffffffffffffffffffffffffffffffffffffffffffffff"]
539 ["ffffffffffffffffffffffffffffffffffffffffffffffffff"]
523 ),
540 ),
524 (
541 (
525 "ffffffffffff some text traalaa",
542 "ffffffffffff some text traalaa",
526 "url[ffffffffffff] some text traalaa",
543 "url[ffffffffffff] some text traalaa",
527 ["ffffffffffff"]
544 ["ffffffffffff"]
528 ),
545 ),
529 (
546 (
530 """Multi line
547 """Multi line
531 123123123123
548 123123123123
532 some text 000000000000
549 some text 000000000000
533 sometimes !
550 sometimes !
534 """,
551 """,
535 """Multi line
552 """Multi line
536 url[123123123123]
553 url[123123123123]
537 some text url[000000000000]
554 some text url[000000000000]
538 sometimes !
555 sometimes !
539 """,
556 """,
540 ["123123123123", "000000000000"]
557 ["123123123123", "000000000000"]
541 )
558 )
542 ], ids=no_newline_id_generator)
559 ], ids=no_newline_id_generator)
543 def test_urlify_commits(sample, expected, commits):
560 def test_urlify_commits(sample, expected, commits):
544 def fake_url(self, *args, **kwargs):
561 def fake_url(self, *args, **kwargs):
545 return '/some-url'
562 return '/some-url'
546
563
547 expected = _quick_url(expected, commits=commits)
564 expected = _quick_url(expected, commits=commits)
548
565
549 with mock.patch('rhodecode.lib.helpers.route_url', fake_url):
566 with mock.patch('rhodecode.lib.helpers.route_url', fake_url):
550 from rhodecode.lib.helpers import urlify_commits
567 from rhodecode.lib.helpers import urlify_commits
551 assert urlify_commits(sample, 'repo_name') == expected
568 assert urlify_commits(sample, 'repo_name') == expected
552
569
553
570
554 @pytest.mark.parametrize("sample, expected, url_", [
571 @pytest.mark.parametrize("sample, expected, url_", [
555 ("",
572 ("",
556 "",
573 "",
557 ""),
574 ""),
558 ("https://svn.apache.org/repos",
575 ("https://svn.apache.org/repos",
559 "url[https://svn.apache.org/repos]",
576 "url[https://svn.apache.org/repos]",
560 "https://svn.apache.org/repos"),
577 "https://svn.apache.org/repos"),
561 ("http://svn.apache.org/repos",
578 ("http://svn.apache.org/repos",
562 "url[http://svn.apache.org/repos]",
579 "url[http://svn.apache.org/repos]",
563 "http://svn.apache.org/repos"),
580 "http://svn.apache.org/repos"),
564 ("from rev a also rev http://google.com",
581 ("from rev a also rev http://google.com",
565 "from rev a also rev url[http://google.com]",
582 "from rev a also rev url[http://google.com]",
566 "http://google.com"),
583 "http://google.com"),
567 ("""Multi line
584 ("""Multi line
568 https://foo.bar.com
585 https://foo.bar.com
569 some text lalala""",
586 some text lalala""",
570 """Multi line
587 """Multi line
571 url[https://foo.bar.com]
588 url[https://foo.bar.com]
572 some text lalala""",
589 some text lalala""",
573 "https://foo.bar.com")
590 "https://foo.bar.com")
574 ], ids=no_newline_id_generator)
591 ], ids=no_newline_id_generator)
575 def test_urlify_test(sample, expected, url_):
592 def test_urlify_test(sample, expected, url_):
576 from rhodecode.lib.helpers import urlify_text
593 from rhodecode.lib.helpers import urlify_text
577 expected = _quick_url(expected, tmpl="""<a href="%s">%s</a>""", url_=url_)
594 expected = _quick_url(expected, tmpl="""<a href="%s">%s</a>""", url_=url_)
578 assert urlify_text(sample) == expected
595 assert urlify_text(sample) == expected
579
596
580
597
581 @pytest.mark.parametrize("test, expected", [
598 @pytest.mark.parametrize("test, expected", [
582 ("", None),
599 ("", None),
583 ("/_2", '2'),
600 ("/_2", '2'),
584 ("_2", '2'),
601 ("_2", '2'),
585 ("/_2/", '2'),
602 ("/_2/", '2'),
586 ("_2/", '2'),
603 ("_2/", '2'),
587
604
588 ("/_21", '21'),
605 ("/_21", '21'),
589 ("_21", '21'),
606 ("_21", '21'),
590 ("/_21/", '21'),
607 ("/_21/", '21'),
591 ("_21/", '21'),
608 ("_21/", '21'),
592
609
593 ("/_21/foobar", '21'),
610 ("/_21/foobar", '21'),
594 ("_21/121", '21'),
611 ("_21/121", '21'),
595 ("/_21/_12", '21'),
612 ("/_21/_12", '21'),
596 ("_21/rc/foo", '21'),
613 ("_21/rc/foo", '21'),
597
614
598 ])
615 ])
599 def test_get_repo_by_id(test, expected):
616 def test_get_repo_by_id(test, expected):
600 from rhodecode.model.repo import RepoModel
617 from rhodecode.model.repo import RepoModel
601 _test = RepoModel()._extract_id_from_repo_name(test)
618 _test = RepoModel()._extract_id_from_repo_name(test)
602 assert _test == expected
619 assert _test == expected
603
620
604
621
605 def test_invalidation_context(baseapp):
622 def test_invalidation_context(baseapp):
606 repo_id = 9999
623 repo_id = 9999
607
624
608 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
625 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
609 repo_id, CacheKey.CACHE_TYPE_FEED)
626 repo_id, CacheKey.CACHE_TYPE_FEED)
610 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
627 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
611 repo_id=repo_id)
628 repo_id=repo_id)
612 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
629 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
613
630
614 calls = [1, 2]
631 calls = [1, 2]
615
632
616 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
633 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
617 def _dummy_func(cache_key):
634 def _dummy_func(cache_key):
618 val = calls.pop(0)
635 val = calls.pop(0)
619 return 'result:{}'.format(val)
636 return 'result:{}'.format(val)
620
637
621 inv_context_manager = rc_cache.InvalidationContext(
638 inv_context_manager = rc_cache.InvalidationContext(
622 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
639 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
623
640
624 # 1st call, fresh caches
641 # 1st call, fresh caches
625 with inv_context_manager as invalidation_context:
642 with inv_context_manager as invalidation_context:
626 should_invalidate = invalidation_context.should_invalidate()
643 should_invalidate = invalidation_context.should_invalidate()
627 if should_invalidate:
644 if should_invalidate:
628 result = _dummy_func.refresh('some-key')
645 result = _dummy_func.refresh('some-key')
629 else:
646 else:
630 result = _dummy_func('some-key')
647 result = _dummy_func('some-key')
631
648
632 assert isinstance(invalidation_context, rc_cache.FreshRegionCache)
649 assert isinstance(invalidation_context, rc_cache.FreshRegionCache)
633 assert should_invalidate is True
650 assert should_invalidate is True
634
651
635 assert 'result:1' == result
652 assert 'result:1' == result
636 # should be cached so calling it twice will give the same result !
653 # should be cached so calling it twice will give the same result !
637 result = _dummy_func('some-key')
654 result = _dummy_func('some-key')
638 assert 'result:1' == result
655 assert 'result:1' == result
639
656
640 # 2nd call, we create a new context manager, this should be now key aware, and
657 # 2nd call, we create a new context manager, this should be now key aware, and
641 # return an active cache region
658 # return an active cache region
642 with inv_context_manager as invalidation_context:
659 with inv_context_manager as invalidation_context:
643 should_invalidate = invalidation_context.should_invalidate()
660 should_invalidate = invalidation_context.should_invalidate()
644 assert isinstance(invalidation_context, rc_cache.ActiveRegionCache)
661 assert isinstance(invalidation_context, rc_cache.ActiveRegionCache)
645 assert should_invalidate is False
662 assert should_invalidate is False
646
663
647 # Mark invalidation
664 # Mark invalidation
648 CacheKey.set_invalidate(invalidation_namespace)
665 CacheKey.set_invalidate(invalidation_namespace)
649
666
650 # 3nd call, fresh caches
667 # 3nd call, fresh caches
651 with inv_context_manager as invalidation_context:
668 with inv_context_manager as invalidation_context:
652 should_invalidate = invalidation_context.should_invalidate()
669 should_invalidate = invalidation_context.should_invalidate()
653 if should_invalidate:
670 if should_invalidate:
654 result = _dummy_func.refresh('some-key')
671 result = _dummy_func.refresh('some-key')
655 else:
672 else:
656 result = _dummy_func('some-key')
673 result = _dummy_func('some-key')
657
674
658 assert isinstance(invalidation_context, rc_cache.FreshRegionCache)
675 assert isinstance(invalidation_context, rc_cache.FreshRegionCache)
659 assert should_invalidate is True
676 assert should_invalidate is True
660
677
661 assert 'result:2' == result
678 assert 'result:2' == result
662
679
663 # cached again, same result
680 # cached again, same result
664 result = _dummy_func('some-key')
681 result = _dummy_func('some-key')
665 assert 'result:2' == result
682 assert 'result:2' == result
666
683
667
684
668 def test_invalidation_context_exception_in_compute(baseapp):
685 def test_invalidation_context_exception_in_compute(baseapp):
669 repo_id = 888
686 repo_id = 888
670
687
671 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
688 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
672 repo_id, CacheKey.CACHE_TYPE_FEED)
689 repo_id, CacheKey.CACHE_TYPE_FEED)
673 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
690 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
674 repo_id=repo_id)
691 repo_id=repo_id)
675 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
692 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
676
693
677 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
694 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
678 def _dummy_func(cache_key):
695 def _dummy_func(cache_key):
679 raise Exception('Error in cache func')
696 raise Exception('Error in cache func')
680
697
681 with pytest.raises(Exception):
698 with pytest.raises(Exception):
682 inv_context_manager = rc_cache.InvalidationContext(
699 inv_context_manager = rc_cache.InvalidationContext(
683 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
700 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
684
701
685 # 1st call, fresh caches
702 # 1st call, fresh caches
686 with inv_context_manager as invalidation_context:
703 with inv_context_manager as invalidation_context:
687 should_invalidate = invalidation_context.should_invalidate()
704 should_invalidate = invalidation_context.should_invalidate()
688 if should_invalidate:
705 if should_invalidate:
689 _dummy_func.refresh('some-key-2')
706 _dummy_func.refresh('some-key-2')
690 else:
707 else:
691 _dummy_func('some-key-2')
708 _dummy_func('some-key-2')
692
709
693
710
694 @pytest.mark.parametrize('execution_number', range(5))
711 @pytest.mark.parametrize('execution_number', range(5))
695 def test_cache_invalidation_race_condition(execution_number, baseapp):
712 def test_cache_invalidation_race_condition(execution_number, baseapp):
696 import time
713 import time
697
714
698 repo_id = 777
715 repo_id = 777
699
716
700 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
717 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
701 repo_id, CacheKey.CACHE_TYPE_FEED)
718 repo_id, CacheKey.CACHE_TYPE_FEED)
702 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
719 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
703 repo_id=repo_id)
720 repo_id=repo_id)
704 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
721 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
705
722
706 @run_test_concurrently(25)
723 @run_test_concurrently(25)
707 def test_create_and_delete_cache_keys():
724 def test_create_and_delete_cache_keys():
708 time.sleep(0.2)
725 time.sleep(0.2)
709
726
710 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
727 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
711 def _dummy_func(cache_key):
728 def _dummy_func(cache_key):
712 val = 'async'
729 val = 'async'
713 return 'result:{}'.format(val)
730 return 'result:{}'.format(val)
714
731
715 inv_context_manager = rc_cache.InvalidationContext(
732 inv_context_manager = rc_cache.InvalidationContext(
716 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
733 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
717
734
718 # 1st call, fresh caches
735 # 1st call, fresh caches
719 with inv_context_manager as invalidation_context:
736 with inv_context_manager as invalidation_context:
720 should_invalidate = invalidation_context.should_invalidate()
737 should_invalidate = invalidation_context.should_invalidate()
721 if should_invalidate:
738 if should_invalidate:
722 _dummy_func.refresh('some-key-3')
739 _dummy_func.refresh('some-key-3')
723 else:
740 else:
724 _dummy_func('some-key-3')
741 _dummy_func('some-key-3')
725
742
726 # Mark invalidation
743 # Mark invalidation
727 CacheKey.set_invalidate(invalidation_namespace)
744 CacheKey.set_invalidate(invalidation_namespace)
728
745
729 test_create_and_delete_cache_keys()
746 test_create_and_delete_cache_keys()
General Comments 0
You need to be logged in to leave comments. Login now