##// END OF EJS Templates
core: break down some utils for better imports
super-admin -
r4915:f87c218e default
parent child Browse files
Show More
@@ -0,0 +1,38 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import hashlib
22 from rhodecode.lib.str_utils import safe_bytes
23
24
25 def md5(s):
26 return hashlib.md5(s).hexdigest()
27
28
29 def md5_safe(s):
30 return md5(safe_bytes(s))
31
32
33 def sha1(s):
34 return hashlib.sha1(s).hexdigest()
35
36
37 def sha1_safe(s):
38 return sha1(safe_bytes(s))
@@ -0,0 +1,135 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import rhodecode
23 from rhodecode.lib.type_utils import aslist
24
25 log = logging.getLogger(__name__)
26
27
28 def safe_int(val, default=None) -> int:
29 """
30 Returns int() of val if val is not convertable to int use default
31 instead
32
33 :param val:
34 :param default:
35 """
36
37 try:
38 val = int(val)
39 except (ValueError, TypeError):
40 val = default
41
42 return val
43
44
45 def get_default_encodings():
46 return aslist(rhodecode.CONFIG.get('default_encoding', 'utf8'), sep=',')
47
48
49 def safe_str(str_, to_encoding=None) -> str:
50 """
51 safe str function. Does few trick to turn unicode_ into string
52
53 :param str_: str to encode
54 :param to_encoding: encode to this type UTF8 default
55 :rtype: str
56 :returns: str object
57 """
58 if isinstance(str_, str):
59 return str_
60
61 # if it's bytes cast to str
62 if not isinstance(str_, bytes):
63 return str(str_)
64
65 to_encoding = to_encoding or get_default_encodings()
66 if not isinstance(to_encoding, (list, tuple)):
67 to_encoding = [to_encoding]
68
69 for enc in to_encoding:
70 try:
71 return str(str_, enc)
72 except UnicodeDecodeError:
73 pass
74
75 return str(str_, to_encoding[0], 'replace')
76
77
78 def safe_bytes(str_, from_encoding=None) -> bytes:
79 """
80 safe bytes function. Does few trick to turn str_ into bytes string:
81
82 :param str_: string to decode
83 :param from_encoding: encode from this type UTF8 default
84 :rtype: unicode
85 :returns: unicode object
86 """
87 if isinstance(str_, bytes):
88 return str_
89
90 if not isinstance(str_, str):
91 raise ValueError('safe_bytes cannot convert other types than str: got: {}'.format(type(str_)))
92
93 from_encoding = from_encoding or get_default_encodings()
94 if not isinstance(from_encoding, (list, tuple)):
95 from_encoding = [from_encoding]
96
97 for enc in from_encoding:
98 try:
99 return str_.encode(enc)
100 except UnicodeDecodeError:
101 pass
102
103 return str_.encode(from_encoding[0], 'replace')
104
105
106 def ascii_bytes(str_, allow_bytes=False) -> bytes:
107 """
108 Simple conversion from str to bytes, with assumption that str_ is pure ASCII.
109 Fails with UnicodeError on invalid input.
110 This should be used where encoding and "safe" ambiguity should be avoided.
111 Where strings already have been encoded in other ways but still are unicode
112 string - for example to hex, base64, json, urlencoding, or are known to be
113 identifiers.
114 """
115 if allow_bytes and isinstance(str_, bytes):
116 return str_
117
118 if not isinstance(str_, str):
119 raise ValueError('ascii_bytes cannot convert other types than str: got: {}'.format(type(str_)))
120 return str_.encode('ascii')
121
122
123 def ascii_str(str_):
124 """
125 Simple conversion from bytes to str, with assumption that str_ is pure ASCII.
126 Fails with UnicodeError on invalid input.
127 This should be used where encoding and "safe" ambiguity should be avoided.
128 Where strings are encoded but also in other ways are known to be ASCII, and
129 where a unicode string is wanted without caring about encoding. For example
130 to hex, base64, urlencoding, or are known to be identifiers.
131 """
132
133 if not isinstance(str_, bytes):
134 raise ValueError('ascii_str cannot convert other types than bytes: got: {}'.format(type(str_)))
135 return str_.decode('ascii')
@@ -0,0 +1,61 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 log = logging.getLogger(__name__)
24
25
26 def str2bool(str_):
27 """
28 returns True/False value from given string, it tries to translate the
29 string into boolean
30
31 :param str_: string value to translate into boolean
32 :rtype: boolean
33 :returns: boolean from given string
34 """
35 if str_ is None:
36 return False
37 if str_ in (True, False):
38 return str_
39 str_ = str(str_).strip().lower()
40 return str_ in ('t', 'true', 'y', 'yes', 'on', '1')
41
42
43 def aslist(obj, sep=None, strip=True):
44 """
45 Returns given string separated by sep as list
46
47 :param obj:
48 :param sep:
49 :param strip:
50 """
51 if isinstance(obj, str):
52 lst = obj.split(sep)
53 if strip:
54 lst = [v.strip() for v in lst]
55 return lst
56 elif isinstance(obj, (list, tuple)):
57 return obj
58 elif obj is None:
59 return []
60 else:
61 return [obj]
@@ -1,1202 +1,1044 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
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
30 import logging
29 import logging
31 import re
30 import re
32 import sys
31 import sys
33 import time
32 import time
34 import urllib.request, urllib.parse, urllib.error
33 import urllib.request, urllib.parse, urllib.error
35 import urlobject
34 import urlobject
36 import uuid
35 import uuid
37 import getpass
36 import getpass
38 import socket
37 import socket
39 import errno
38 import errno
40 import random
39 import random
41 from functools import update_wrapper, partial, wraps
40 from functools import update_wrapper, partial, wraps
42 from contextlib import closing
41 from contextlib import closing
43
42
44 import pygments.lexers
43 import pygments.lexers
45 import sqlalchemy
44 import sqlalchemy
46 import sqlalchemy.engine.url
45 import sqlalchemy.engine.url
47 import sqlalchemy.exc
46 import sqlalchemy.exc
48 import sqlalchemy.sql
47 import sqlalchemy.sql
49 import webob
48 import webob
50 import pyramid.threadlocal
49 import pyramid.threadlocal
51 from pyramid.settings import asbool
50 from pyramid.settings import asbool
52
51
53 import rhodecode
52 import rhodecode
54 from rhodecode.translation import _, _pluralize
53 from rhodecode.translation import _, _pluralize
55
54 from rhodecode.lib.str_utils import safe_str, safe_int, safe_bytes
56
55 from rhodecode.lib.hash_utils import md5, md5_safe, sha1, sha1_safe
57 def md5(s):
56 from rhodecode.lib.type_utils import aslist, str2bool
58 return hashlib.md5(s).hexdigest()
59
60
57
61 def md5_safe(s):
58 #TODO: there's no longer safe_unicode, we mock it now, but should remove it
62 return md5(safe_str(s))
59 safe_unicode = safe_str
63
64
65 def sha1(s):
66 return hashlib.sha1(s).hexdigest()
67
68
69 def sha1_safe(s):
70 return sha1(safe_str(s))
71
60
72
61
73 def __get_lem(extra_mapping=None):
62 def __get_lem(extra_mapping=None):
74 """
63 """
75 Get language extension map based on what's inside pygments lexers
64 Get language extension map based on what's inside pygments lexers
76 """
65 """
77 d = collections.defaultdict(lambda: [])
66 d = collections.defaultdict(lambda: [])
78
67
79 def __clean(s):
68 def __clean(s):
80 s = s.lstrip('*')
69 s = s.lstrip('*')
81 s = s.lstrip('.')
70 s = s.lstrip('.')
82
71
83 if s.find('[') != -1:
72 if s.find('[') != -1:
84 exts = []
73 exts = []
85 start, stop = s.find('['), s.find(']')
74 start, stop = s.find('['), s.find(']')
86
75
87 for suffix in s[start + 1:stop]:
76 for suffix in s[start + 1:stop]:
88 exts.append(s[:s.find('[')] + suffix)
77 exts.append(s[:s.find('[')] + suffix)
89 return [e.lower() for e in exts]
78 return [e.lower() for e in exts]
90 else:
79 else:
91 return [s.lower()]
80 return [s.lower()]
92
81
93 for lx, t in sorted(pygments.lexers.LEXERS.items()):
82 for lx, t in sorted(pygments.lexers.LEXERS.items()):
94 m = map(__clean, t[-2])
83 m = map(__clean, t[-2])
95 if m:
84 if m:
96 m = reduce(lambda x, y: x + y, m)
85 m = reduce(lambda x, y: x + y, m)
97 for ext in m:
86 for ext in m:
98 desc = lx.replace('Lexer', '')
87 desc = lx.replace('Lexer', '')
99 d[ext].append(desc)
88 d[ext].append(desc)
100
89
101 data = dict(d)
90 data = dict(d)
102
91
103 extra_mapping = extra_mapping or {}
92 extra_mapping = extra_mapping or {}
104 if extra_mapping:
93 if extra_mapping:
105 for k, v in extra_mapping.items():
94 for k, v in extra_mapping.items():
106 if k not in data:
95 if k not in data:
107 # register new mapping2lexer
96 # register new mapping2lexer
108 data[k] = [v]
97 data[k] = [v]
109
98
110 return data
99 return data
111
100
112
101
113 def str2bool(_str):
114 """
115 returns True/False value from given string, it tries to translate the
116 string into boolean
117
118 :param _str: string value to translate into boolean
119 :rtype: boolean
120 :returns: boolean from given string
121 """
122 if _str is None:
123 return False
124 if _str in (True, False):
125 return _str
126 _str = str(_str).strip().lower()
127 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
128
129
130 def aslist(obj, sep=None, strip=True):
131 """
132 Returns given string separated by sep as list
133
134 :param obj:
135 :param sep:
136 :param strip:
137 """
138 if isinstance(obj, (basestring,)):
139 lst = obj.split(sep)
140 if strip:
141 lst = [v.strip() for v in lst]
142 return lst
143 elif isinstance(obj, (list, tuple)):
144 return obj
145 elif obj is None:
146 return []
147 else:
148 return [obj]
149
150
151 def convert_line_endings(line, mode):
102 def convert_line_endings(line, mode):
152 """
103 """
153 Converts a given line "line end" accordingly to given mode
104 Converts a given line "line end" accordingly to given mode
154
105
155 Available modes are::
106 Available modes are::
156 0 - Unix
107 0 - Unix
157 1 - Mac
108 1 - Mac
158 2 - DOS
109 2 - DOS
159
110
160 :param line: given line to convert
111 :param line: given line to convert
161 :param mode: mode to convert to
112 :param mode: mode to convert to
162 :rtype: str
113 :rtype: str
163 :return: converted line according to mode
114 :return: converted line according to mode
164 """
115 """
165 if mode == 0:
116 if mode == 0:
166 line = line.replace('\r\n', '\n')
117 line = line.replace('\r\n', '\n')
167 line = line.replace('\r', '\n')
118 line = line.replace('\r', '\n')
168 elif mode == 1:
119 elif mode == 1:
169 line = line.replace('\r\n', '\r')
120 line = line.replace('\r\n', '\r')
170 line = line.replace('\n', '\r')
121 line = line.replace('\n', '\r')
171 elif mode == 2:
122 elif mode == 2:
172 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
123 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
173 return line
124 return line
174
125
175
126
176 def detect_mode(line, default):
127 def detect_mode(line, default):
177 """
128 """
178 Detects line break for given line, if line break couldn't be found
129 Detects line break for given line, if line break couldn't be found
179 given default value is returned
130 given default value is returned
180
131
181 :param line: str line
132 :param line: str line
182 :param default: default
133 :param default: default
183 :rtype: int
134 :rtype: int
184 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
135 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
185 """
136 """
186 if line.endswith('\r\n'):
137 if line.endswith('\r\n'):
187 return 2
138 return 2
188 elif line.endswith('\n'):
139 elif line.endswith('\n'):
189 return 0
140 return 0
190 elif line.endswith('\r'):
141 elif line.endswith('\r'):
191 return 1
142 return 1
192 else:
143 else:
193 return default
144 return default
194
145
195
146
196 def safe_int(val, default=None):
197 """
198 Returns int() of val if val is not convertable to int use default
199 instead
200
201 :param val:
202 :param default:
203 """
204
205 try:
206 val = int(val)
207 except (ValueError, TypeError):
208 val = default
209
210 return val
211
212
213 def safe_unicode(str_, from_encoding=None, use_chardet=False):
214 """
215 safe unicode function. Does few trick to turn str_ into unicode
216
217 In case of UnicodeDecode error, we try to return it with encoding detected
218 by chardet library if it fails fallback to unicode with errors replaced
219
220 :param str_: string to decode
221 :rtype: unicode
222 :returns: unicode object
223 """
224 if isinstance(str_, unicode):
225 return str_
226
227 if not from_encoding:
228 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
229 'utf8'), sep=',')
230 from_encoding = DEFAULT_ENCODINGS
231
232 if not isinstance(from_encoding, (list, tuple)):
233 from_encoding = [from_encoding]
234
235 try:
236 return unicode(str_)
237 except UnicodeDecodeError:
238 pass
239
240 for enc in from_encoding:
241 try:
242 return unicode(str_, enc)
243 except UnicodeDecodeError:
244 pass
245
246 if use_chardet:
247 try:
248 import chardet
249 encoding = chardet.detect(str_)['encoding']
250 if encoding is None:
251 raise Exception()
252 return str_.decode(encoding)
253 except (ImportError, UnicodeDecodeError, Exception):
254 return unicode(str_, from_encoding[0], 'replace')
255 else:
256 return unicode(str_, from_encoding[0], 'replace')
257
258 def safe_str(unicode_, to_encoding=None, use_chardet=False):
259 """
260 safe str function. Does few trick to turn unicode_ into string
261
262 In case of UnicodeEncodeError, we try to return it with encoding detected
263 by chardet library if it fails fallback to string with errors replaced
264
265 :param unicode_: unicode to encode
266 :rtype: str
267 :returns: str object
268 """
269
270 # if it's not basestr cast to str
271 if not isinstance(unicode_, str):
272 return str(unicode_)
273
274 if isinstance(unicode_, str):
275 return unicode_
276
277 if not to_encoding:
278 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
279 'utf8'), sep=',')
280 to_encoding = DEFAULT_ENCODINGS
281
282 if not isinstance(to_encoding, (list, tuple)):
283 to_encoding = [to_encoding]
284
285 for enc in to_encoding:
286 try:
287 return unicode_.encode(enc)
288 except UnicodeEncodeError:
289 pass
290
291 if use_chardet:
292 try:
293 import chardet
294 encoding = chardet.detect(unicode_)['encoding']
295 if encoding is None:
296 raise UnicodeEncodeError()
297
298 return unicode_.encode(encoding)
299 except (ImportError, UnicodeEncodeError):
300 return unicode_.encode(to_encoding[0], 'replace')
301 else:
302 return unicode_.encode(to_encoding[0], 'replace')
303
304
305 def remove_suffix(s, suffix):
147 def remove_suffix(s, suffix):
306 if s.endswith(suffix):
148 if s.endswith(suffix):
307 s = s[:-1 * len(suffix)]
149 s = s[:-1 * len(suffix)]
308 return s
150 return s
309
151
310
152
311 def remove_prefix(s, prefix):
153 def remove_prefix(s, prefix):
312 if s.startswith(prefix):
154 if s.startswith(prefix):
313 s = s[len(prefix):]
155 s = s[len(prefix):]
314 return s
156 return s
315
157
316
158
317 def find_calling_context(ignore_modules=None):
159 def find_calling_context(ignore_modules=None):
318 """
160 """
319 Look through the calling stack and return the frame which called
161 Look through the calling stack and return the frame which called
320 this function and is part of core module ( ie. rhodecode.* )
162 this function and is part of core module ( ie. rhodecode.* )
321
163
322 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
164 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
323
165
324 usage::
166 usage::
325 from rhodecode.lib.utils2 import find_calling_context
167 from rhodecode.lib.utils2 import find_calling_context
326
168
327 calling_context = find_calling_context(ignore_modules=[
169 calling_context = find_calling_context(ignore_modules=[
328 'rhodecode.lib.caching_query',
170 'rhodecode.lib.caching_query',
329 'rhodecode.model.settings',
171 'rhodecode.model.settings',
330 ])
172 ])
331
173
332 if calling_context:
174 if calling_context:
333 cc_str = 'call context %s:%s' % (
175 cc_str = 'call context %s:%s' % (
334 calling_context.f_code.co_filename,
176 calling_context.f_code.co_filename,
335 calling_context.f_lineno,
177 calling_context.f_lineno,
336 )
178 )
337 print(cc_str)
179 print(cc_str)
338 """
180 """
339
181
340 ignore_modules = ignore_modules or []
182 ignore_modules = ignore_modules or []
341
183
342 f = sys._getframe(2)
184 f = sys._getframe(2)
343 while f.f_back is not None:
185 while f.f_back is not None:
344 name = f.f_globals.get('__name__')
186 name = f.f_globals.get('__name__')
345 if name and name.startswith(__name__.split('.')[0]):
187 if name and name.startswith(__name__.split('.')[0]):
346 if name not in ignore_modules:
188 if name not in ignore_modules:
347 return f
189 return f
348 f = f.f_back
190 f = f.f_back
349 return None
191 return None
350
192
351
193
352 def ping_connection(connection, branch):
194 def ping_connection(connection, branch):
353 if branch:
195 if branch:
354 # "branch" refers to a sub-connection of a connection,
196 # "branch" refers to a sub-connection of a connection,
355 # we don't want to bother pinging on these.
197 # we don't want to bother pinging on these.
356 return
198 return
357
199
358 # turn off "close with result". This flag is only used with
200 # turn off "close with result". This flag is only used with
359 # "connectionless" execution, otherwise will be False in any case
201 # "connectionless" execution, otherwise will be False in any case
360 save_should_close_with_result = connection.should_close_with_result
202 save_should_close_with_result = connection.should_close_with_result
361 connection.should_close_with_result = False
203 connection.should_close_with_result = False
362
204
363 try:
205 try:
364 # run a SELECT 1. use a core select() so that
206 # run a SELECT 1. use a core select() so that
365 # the SELECT of a scalar value without a table is
207 # the SELECT of a scalar value without a table is
366 # appropriately formatted for the backend
208 # appropriately formatted for the backend
367 connection.scalar(sqlalchemy.sql.select([1]))
209 connection.scalar(sqlalchemy.sql.select([1]))
368 except sqlalchemy.exc.DBAPIError as err:
210 except sqlalchemy.exc.DBAPIError as err:
369 # catch SQLAlchemy's DBAPIError, which is a wrapper
211 # catch SQLAlchemy's DBAPIError, which is a wrapper
370 # for the DBAPI's exception. It includes a .connection_invalidated
212 # for the DBAPI's exception. It includes a .connection_invalidated
371 # attribute which specifies if this connection is a "disconnect"
213 # attribute which specifies if this connection is a "disconnect"
372 # condition, which is based on inspection of the original exception
214 # condition, which is based on inspection of the original exception
373 # by the dialect in use.
215 # by the dialect in use.
374 if err.connection_invalidated:
216 if err.connection_invalidated:
375 # run the same SELECT again - the connection will re-validate
217 # run the same SELECT again - the connection will re-validate
376 # itself and establish a new connection. The disconnect detection
218 # itself and establish a new connection. The disconnect detection
377 # here also causes the whole connection pool to be invalidated
219 # here also causes the whole connection pool to be invalidated
378 # so that all stale connections are discarded.
220 # so that all stale connections are discarded.
379 connection.scalar(sqlalchemy.sql.select([1]))
221 connection.scalar(sqlalchemy.sql.select([1]))
380 else:
222 else:
381 raise
223 raise
382 finally:
224 finally:
383 # restore "close with result"
225 # restore "close with result"
384 connection.should_close_with_result = save_should_close_with_result
226 connection.should_close_with_result = save_should_close_with_result
385
227
386
228
387 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
229 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
388 """Custom engine_from_config functions."""
230 """Custom engine_from_config functions."""
389 log = logging.getLogger('sqlalchemy.engine')
231 log = logging.getLogger('sqlalchemy.engine')
390 use_ping_connection = asbool(configuration.pop('sqlalchemy.db1.ping_connection', None))
232 use_ping_connection = asbool(configuration.pop('sqlalchemy.db1.ping_connection', None))
391 debug = asbool(configuration.pop('sqlalchemy.db1.debug_query', None))
233 debug = asbool(configuration.pop('sqlalchemy.db1.debug_query', None))
392
234
393 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
235 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
394
236
395 def color_sql(sql):
237 def color_sql(sql):
396 color_seq = '\033[1;33m' # This is yellow: code 33
238 color_seq = '\033[1;33m' # This is yellow: code 33
397 normal = '\x1b[0m'
239 normal = '\x1b[0m'
398 return ''.join([color_seq, sql, normal])
240 return ''.join([color_seq, sql, normal])
399
241
400 if use_ping_connection:
242 if use_ping_connection:
401 log.debug('Adding ping_connection on the engine config.')
243 log.debug('Adding ping_connection on the engine config.')
402 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
244 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
403
245
404 if debug:
246 if debug:
405 # attach events only for debug configuration
247 # attach events only for debug configuration
406 def before_cursor_execute(conn, cursor, statement,
248 def before_cursor_execute(conn, cursor, statement,
407 parameters, context, executemany):
249 parameters, context, executemany):
408 setattr(conn, 'query_start_time', time.time())
250 setattr(conn, 'query_start_time', time.time())
409 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
251 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
410 calling_context = find_calling_context(ignore_modules=[
252 calling_context = find_calling_context(ignore_modules=[
411 'rhodecode.lib.caching_query',
253 'rhodecode.lib.caching_query',
412 'rhodecode.model.settings',
254 'rhodecode.model.settings',
413 ])
255 ])
414 if calling_context:
256 if calling_context:
415 log.info(color_sql('call context %s:%s' % (
257 log.info(color_sql('call context %s:%s' % (
416 calling_context.f_code.co_filename,
258 calling_context.f_code.co_filename,
417 calling_context.f_lineno,
259 calling_context.f_lineno,
418 )))
260 )))
419
261
420 def after_cursor_execute(conn, cursor, statement,
262 def after_cursor_execute(conn, cursor, statement,
421 parameters, context, executemany):
263 parameters, context, executemany):
422 delattr(conn, 'query_start_time')
264 delattr(conn, 'query_start_time')
423
265
424 sqlalchemy.event.listen(engine, "before_cursor_execute", before_cursor_execute)
266 sqlalchemy.event.listen(engine, "before_cursor_execute", before_cursor_execute)
425 sqlalchemy.event.listen(engine, "after_cursor_execute", after_cursor_execute)
267 sqlalchemy.event.listen(engine, "after_cursor_execute", after_cursor_execute)
426
268
427 return engine
269 return engine
428
270
429
271
430 def get_encryption_key(config):
272 def get_encryption_key(config):
431 secret = config.get('rhodecode.encrypted_values.secret')
273 secret = config.get('rhodecode.encrypted_values.secret')
432 default = config['beaker.session.secret']
274 default = config['beaker.session.secret']
433 return secret or default
275 return secret or default
434
276
435
277
436 def age(prevdate, now=None, show_short_version=False, show_suffix=True, short_format=False):
278 def age(prevdate, now=None, show_short_version=False, show_suffix=True, short_format=False):
437 """
279 """
438 Turns a datetime into an age string.
280 Turns a datetime into an age string.
439 If show_short_version is True, this generates a shorter string with
281 If show_short_version is True, this generates a shorter string with
440 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
282 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
441
283
442 * IMPORTANT*
284 * IMPORTANT*
443 Code of this function is written in special way so it's easier to
285 Code of this function is written in special way so it's easier to
444 backport it to javascript. If you mean to update it, please also update
286 backport it to javascript. If you mean to update it, please also update
445 `jquery.timeago-extension.js` file
287 `jquery.timeago-extension.js` file
446
288
447 :param prevdate: datetime object
289 :param prevdate: datetime object
448 :param now: get current time, if not define we use
290 :param now: get current time, if not define we use
449 `datetime.datetime.now()`
291 `datetime.datetime.now()`
450 :param show_short_version: if it should approximate the date and
292 :param show_short_version: if it should approximate the date and
451 return a shorter string
293 return a shorter string
452 :param show_suffix:
294 :param show_suffix:
453 :param short_format: show short format, eg 2D instead of 2 days
295 :param short_format: show short format, eg 2D instead of 2 days
454 :rtype: unicode
296 :rtype: unicode
455 :returns: unicode words describing age
297 :returns: unicode words describing age
456 """
298 """
457
299
458 def _get_relative_delta(now, prevdate):
300 def _get_relative_delta(now, prevdate):
459 base = dateutil.relativedelta.relativedelta(now, prevdate)
301 base = dateutil.relativedelta.relativedelta(now, prevdate)
460 return {
302 return {
461 'year': base.years,
303 'year': base.years,
462 'month': base.months,
304 'month': base.months,
463 'day': base.days,
305 'day': base.days,
464 'hour': base.hours,
306 'hour': base.hours,
465 'minute': base.minutes,
307 'minute': base.minutes,
466 'second': base.seconds,
308 'second': base.seconds,
467 }
309 }
468
310
469 def _is_leap_year(year):
311 def _is_leap_year(year):
470 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
312 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
471
313
472 def get_month(prevdate):
314 def get_month(prevdate):
473 return prevdate.month
315 return prevdate.month
474
316
475 def get_year(prevdate):
317 def get_year(prevdate):
476 return prevdate.year
318 return prevdate.year
477
319
478 now = now or datetime.datetime.now()
320 now = now or datetime.datetime.now()
479 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
321 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
480 deltas = {}
322 deltas = {}
481 future = False
323 future = False
482
324
483 if prevdate > now:
325 if prevdate > now:
484 now_old = now
326 now_old = now
485 now = prevdate
327 now = prevdate
486 prevdate = now_old
328 prevdate = now_old
487 future = True
329 future = True
488 if future:
330 if future:
489 prevdate = prevdate.replace(microsecond=0)
331 prevdate = prevdate.replace(microsecond=0)
490 # Get date parts deltas
332 # Get date parts deltas
491 for part in order:
333 for part in order:
492 rel_delta = _get_relative_delta(now, prevdate)
334 rel_delta = _get_relative_delta(now, prevdate)
493 deltas[part] = rel_delta[part]
335 deltas[part] = rel_delta[part]
494
336
495 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
337 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
496 # not 1 hour, -59 minutes and -59 seconds)
338 # not 1 hour, -59 minutes and -59 seconds)
497 offsets = [[5, 60], [4, 60], [3, 24]]
339 offsets = [[5, 60], [4, 60], [3, 24]]
498 for element in offsets: # seconds, minutes, hours
340 for element in offsets: # seconds, minutes, hours
499 num = element[0]
341 num = element[0]
500 length = element[1]
342 length = element[1]
501
343
502 part = order[num]
344 part = order[num]
503 carry_part = order[num - 1]
345 carry_part = order[num - 1]
504
346
505 if deltas[part] < 0:
347 if deltas[part] < 0:
506 deltas[part] += length
348 deltas[part] += length
507 deltas[carry_part] -= 1
349 deltas[carry_part] -= 1
508
350
509 # Same thing for days except that the increment depends on the (variable)
351 # Same thing for days except that the increment depends on the (variable)
510 # number of days in the month
352 # number of days in the month
511 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
353 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
512 if deltas['day'] < 0:
354 if deltas['day'] < 0:
513 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
355 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
514 deltas['day'] += 29
356 deltas['day'] += 29
515 else:
357 else:
516 deltas['day'] += month_lengths[get_month(prevdate) - 1]
358 deltas['day'] += month_lengths[get_month(prevdate) - 1]
517
359
518 deltas['month'] -= 1
360 deltas['month'] -= 1
519
361
520 if deltas['month'] < 0:
362 if deltas['month'] < 0:
521 deltas['month'] += 12
363 deltas['month'] += 12
522 deltas['year'] -= 1
364 deltas['year'] -= 1
523
365
524 # Format the result
366 # Format the result
525 if short_format:
367 if short_format:
526 fmt_funcs = {
368 fmt_funcs = {
527 'year': lambda d: u'%dy' % d,
369 'year': lambda d: u'%dy' % d,
528 'month': lambda d: u'%dm' % d,
370 'month': lambda d: u'%dm' % d,
529 'day': lambda d: u'%dd' % d,
371 'day': lambda d: u'%dd' % d,
530 'hour': lambda d: u'%dh' % d,
372 'hour': lambda d: u'%dh' % d,
531 'minute': lambda d: u'%dmin' % d,
373 'minute': lambda d: u'%dmin' % d,
532 'second': lambda d: u'%dsec' % d,
374 'second': lambda d: u'%dsec' % d,
533 }
375 }
534 else:
376 else:
535 fmt_funcs = {
377 fmt_funcs = {
536 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
378 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
537 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
379 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
538 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
380 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
539 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
381 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
540 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
382 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
541 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
383 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
542 }
384 }
543
385
544 i = 0
386 i = 0
545 for part in order:
387 for part in order:
546 value = deltas[part]
388 value = deltas[part]
547 if value != 0:
389 if value != 0:
548
390
549 if i < 5:
391 if i < 5:
550 sub_part = order[i + 1]
392 sub_part = order[i + 1]
551 sub_value = deltas[sub_part]
393 sub_value = deltas[sub_part]
552 else:
394 else:
553 sub_value = 0
395 sub_value = 0
554
396
555 if sub_value == 0 or show_short_version:
397 if sub_value == 0 or show_short_version:
556 _val = fmt_funcs[part](value)
398 _val = fmt_funcs[part](value)
557 if future:
399 if future:
558 if show_suffix:
400 if show_suffix:
559 return _(u'in ${ago}', mapping={'ago': _val})
401 return _(u'in ${ago}', mapping={'ago': _val})
560 else:
402 else:
561 return _(_val)
403 return _(_val)
562
404
563 else:
405 else:
564 if show_suffix:
406 if show_suffix:
565 return _(u'${ago} ago', mapping={'ago': _val})
407 return _(u'${ago} ago', mapping={'ago': _val})
566 else:
408 else:
567 return _(_val)
409 return _(_val)
568
410
569 val = fmt_funcs[part](value)
411 val = fmt_funcs[part](value)
570 val_detail = fmt_funcs[sub_part](sub_value)
412 val_detail = fmt_funcs[sub_part](sub_value)
571 mapping = {'val': val, 'detail': val_detail}
413 mapping = {'val': val, 'detail': val_detail}
572
414
573 if short_format:
415 if short_format:
574 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
416 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
575 if show_suffix:
417 if show_suffix:
576 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
418 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
577 if future:
419 if future:
578 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
420 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
579 else:
421 else:
580 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
422 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
581 if show_suffix:
423 if show_suffix:
582 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
424 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
583 if future:
425 if future:
584 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
426 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
585
427
586 return datetime_tmpl
428 return datetime_tmpl
587 i += 1
429 i += 1
588 return _(u'just now')
430 return _(u'just now')
589
431
590
432
591 def age_from_seconds(seconds):
433 def age_from_seconds(seconds):
592 seconds = safe_int(seconds) or 0
434 seconds = safe_int(seconds) or 0
593 prevdate = time_to_datetime(time.time() + seconds)
435 prevdate = time_to_datetime(time.time() + seconds)
594 return age(prevdate, show_suffix=False, show_short_version=True)
436 return age(prevdate, show_suffix=False, show_short_version=True)
595
437
596
438
597 def cleaned_uri(uri):
439 def cleaned_uri(uri):
598 """
440 """
599 Quotes '[' and ']' from uri if there is only one of them.
441 Quotes '[' and ']' from uri if there is only one of them.
600 according to RFC3986 we cannot use such chars in uri
442 according to RFC3986 we cannot use such chars in uri
601 :param uri:
443 :param uri:
602 :return: uri without this chars
444 :return: uri without this chars
603 """
445 """
604 return urllib.parse.quote(uri, safe='@$:/')
446 return urllib.parse.quote(uri, safe='@$:/')
605
447
606
448
607 def credentials_filter(uri):
449 def credentials_filter(uri):
608 """
450 """
609 Returns a url with removed credentials
451 Returns a url with removed credentials
610
452
611 :param uri:
453 :param uri:
612 """
454 """
613 import urlobject
455 import urlobject
614 if isinstance(uri, rhodecode.lib.encrypt.InvalidDecryptedValue):
456 if isinstance(uri, rhodecode.lib.encrypt.InvalidDecryptedValue):
615 return 'InvalidDecryptionKey'
457 return 'InvalidDecryptionKey'
616
458
617 url_obj = urlobject.URLObject(cleaned_uri(uri))
459 url_obj = urlobject.URLObject(cleaned_uri(uri))
618 url_obj = url_obj.without_password().without_username()
460 url_obj = url_obj.without_password().without_username()
619
461
620 return url_obj
462 return url_obj
621
463
622
464
623 def get_host_info(request):
465 def get_host_info(request):
624 """
466 """
625 Generate host info, to obtain full url e.g https://server.com
467 Generate host info, to obtain full url e.g https://server.com
626 use this
468 use this
627 `{scheme}://{netloc}`
469 `{scheme}://{netloc}`
628 """
470 """
629 if not request:
471 if not request:
630 return {}
472 return {}
631
473
632 qualified_home_url = request.route_url('home')
474 qualified_home_url = request.route_url('home')
633 parsed_url = urlobject.URLObject(qualified_home_url)
475 parsed_url = urlobject.URLObject(qualified_home_url)
634 decoded_path = safe_unicode(urllib.parse.unquote(parsed_url.path.rstrip('/')))
476 decoded_path = safe_unicode(urllib.parse.unquote(parsed_url.path.rstrip('/')))
635
477
636 return {
478 return {
637 'scheme': parsed_url.scheme,
479 'scheme': parsed_url.scheme,
638 'netloc': parsed_url.netloc+decoded_path,
480 'netloc': parsed_url.netloc+decoded_path,
639 'hostname': parsed_url.hostname,
481 'hostname': parsed_url.hostname,
640 }
482 }
641
483
642
484
643 def get_clone_url(request, uri_tmpl, repo_name, repo_id, repo_type, **override):
485 def get_clone_url(request, uri_tmpl, repo_name, repo_id, repo_type, **override):
644 qualified_home_url = request.route_url('home')
486 qualified_home_url = request.route_url('home')
645 parsed_url = urlobject.URLObject(qualified_home_url)
487 parsed_url = urlobject.URLObject(qualified_home_url)
646 decoded_path = safe_unicode(urllib.parse.unquote(parsed_url.path.rstrip('/')))
488 decoded_path = safe_unicode(urllib.parse.unquote(parsed_url.path.rstrip('/')))
647
489
648 args = {
490 args = {
649 'scheme': parsed_url.scheme,
491 'scheme': parsed_url.scheme,
650 'user': '',
492 'user': '',
651 'sys_user': getpass.getuser(),
493 'sys_user': getpass.getuser(),
652 # path if we use proxy-prefix
494 # path if we use proxy-prefix
653 'netloc': parsed_url.netloc+decoded_path,
495 'netloc': parsed_url.netloc+decoded_path,
654 'hostname': parsed_url.hostname,
496 'hostname': parsed_url.hostname,
655 'prefix': decoded_path,
497 'prefix': decoded_path,
656 'repo': repo_name,
498 'repo': repo_name,
657 'repoid': str(repo_id),
499 'repoid': str(repo_id),
658 'repo_type': repo_type
500 'repo_type': repo_type
659 }
501 }
660 args.update(override)
502 args.update(override)
661 args['user'] = urllib.parse.quote(safe_str(args['user']))
503 args['user'] = urllib.parse.quote(safe_str(args['user']))
662
504
663 for k, v in args.items():
505 for k, v in args.items():
664 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
506 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
665
507
666 # special case for SVN clone url
508 # special case for SVN clone url
667 if repo_type == 'svn':
509 if repo_type == 'svn':
668 uri_tmpl = uri_tmpl.replace('ssh://', 'svn+ssh://')
510 uri_tmpl = uri_tmpl.replace('ssh://', 'svn+ssh://')
669
511
670 # remove leading @ sign if it's present. Case of empty user
512 # remove leading @ sign if it's present. Case of empty user
671 url_obj = urlobject.URLObject(uri_tmpl)
513 url_obj = urlobject.URLObject(uri_tmpl)
672 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
514 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
673
515
674 return safe_unicode(url)
516 return safe_unicode(url)
675
517
676
518
677 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None,
519 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None,
678 maybe_unreachable=False, reference_obj=None):
520 maybe_unreachable=False, reference_obj=None):
679 """
521 """
680 Safe version of get_commit if this commit doesn't exists for a
522 Safe version of get_commit if this commit doesn't exists for a
681 repository it returns a Dummy one instead
523 repository it returns a Dummy one instead
682
524
683 :param repo: repository instance
525 :param repo: repository instance
684 :param commit_id: commit id as str
526 :param commit_id: commit id as str
685 :param commit_idx: numeric commit index
527 :param commit_idx: numeric commit index
686 :param pre_load: optional list of commit attributes to load
528 :param pre_load: optional list of commit attributes to load
687 :param maybe_unreachable: translate unreachable commits on git repos
529 :param maybe_unreachable: translate unreachable commits on git repos
688 :param reference_obj: explicitly search via a reference obj in git. E.g "branch:123" would mean branch "123"
530 :param reference_obj: explicitly search via a reference obj in git. E.g "branch:123" would mean branch "123"
689 """
531 """
690 # TODO(skreft): remove these circular imports
532 # TODO(skreft): remove these circular imports
691 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
533 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
692 from rhodecode.lib.vcs.exceptions import RepositoryError
534 from rhodecode.lib.vcs.exceptions import RepositoryError
693 if not isinstance(repo, BaseRepository):
535 if not isinstance(repo, BaseRepository):
694 raise Exception('You must pass an Repository '
536 raise Exception('You must pass an Repository '
695 'object as first argument got %s', type(repo))
537 'object as first argument got %s', type(repo))
696
538
697 try:
539 try:
698 commit = repo.get_commit(
540 commit = repo.get_commit(
699 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load,
541 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load,
700 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
542 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
701 except (RepositoryError, LookupError):
543 except (RepositoryError, LookupError):
702 commit = EmptyCommit()
544 commit = EmptyCommit()
703 return commit
545 return commit
704
546
705
547
706 def datetime_to_time(dt):
548 def datetime_to_time(dt):
707 if dt:
549 if dt:
708 return time.mktime(dt.timetuple())
550 return time.mktime(dt.timetuple())
709
551
710
552
711 def time_to_datetime(tm):
553 def time_to_datetime(tm):
712 if tm:
554 if tm:
713 if isinstance(tm, str):
555 if isinstance(tm, str):
714 try:
556 try:
715 tm = float(tm)
557 tm = float(tm)
716 except ValueError:
558 except ValueError:
717 return
559 return
718 return datetime.datetime.fromtimestamp(tm)
560 return datetime.datetime.fromtimestamp(tm)
719
561
720
562
721 def time_to_utcdatetime(tm):
563 def time_to_utcdatetime(tm):
722 if tm:
564 if tm:
723 if isinstance(tm, str):
565 if isinstance(tm, str):
724 try:
566 try:
725 tm = float(tm)
567 tm = float(tm)
726 except ValueError:
568 except ValueError:
727 return
569 return
728 return datetime.datetime.utcfromtimestamp(tm)
570 return datetime.datetime.utcfromtimestamp(tm)
729
571
730
572
731 MENTIONS_REGEX = re.compile(
573 MENTIONS_REGEX = re.compile(
732 # ^@ or @ without any special chars in front
574 # ^@ or @ without any special chars in front
733 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
575 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
734 # main body starts with letter, then can be . - _
576 # main body starts with letter, then can be . - _
735 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
577 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
736 re.VERBOSE | re.MULTILINE)
578 re.VERBOSE | re.MULTILINE)
737
579
738
580
739 def extract_mentioned_users(s):
581 def extract_mentioned_users(s):
740 """
582 """
741 Returns unique usernames from given string s that have @mention
583 Returns unique usernames from given string s that have @mention
742
584
743 :param s: string to get mentions
585 :param s: string to get mentions
744 """
586 """
745 usrs = set()
587 usrs = set()
746 for username in MENTIONS_REGEX.findall(s):
588 for username in MENTIONS_REGEX.findall(s):
747 usrs.add(username)
589 usrs.add(username)
748
590
749 return sorted(list(usrs), key=lambda k: k.lower())
591 return sorted(list(usrs), key=lambda k: k.lower())
750
592
751
593
752 class AttributeDictBase(dict):
594 class AttributeDictBase(dict):
753 def __getstate__(self):
595 def __getstate__(self):
754 odict = self.__dict__ # get attribute dictionary
596 odict = self.__dict__ # get attribute dictionary
755 return odict
597 return odict
756
598
757 def __setstate__(self, dict):
599 def __setstate__(self, dict):
758 self.__dict__ = dict
600 self.__dict__ = dict
759
601
760 __setattr__ = dict.__setitem__
602 __setattr__ = dict.__setitem__
761 __delattr__ = dict.__delitem__
603 __delattr__ = dict.__delitem__
762
604
763
605
764 class StrictAttributeDict(AttributeDictBase):
606 class StrictAttributeDict(AttributeDictBase):
765 """
607 """
766 Strict Version of Attribute dict which raises an Attribute error when
608 Strict Version of Attribute dict which raises an Attribute error when
767 requested attribute is not set
609 requested attribute is not set
768 """
610 """
769 def __getattr__(self, attr):
611 def __getattr__(self, attr):
770 try:
612 try:
771 return self[attr]
613 return self[attr]
772 except KeyError:
614 except KeyError:
773 raise AttributeError('%s object has no attribute %s' % (
615 raise AttributeError('%s object has no attribute %s' % (
774 self.__class__, attr))
616 self.__class__, attr))
775
617
776
618
777 class AttributeDict(AttributeDictBase):
619 class AttributeDict(AttributeDictBase):
778 def __getattr__(self, attr):
620 def __getattr__(self, attr):
779 return self.get(attr, None)
621 return self.get(attr, None)
780
622
781
623
782 def fix_PATH(os_=None):
624 def fix_PATH(os_=None):
783 """
625 """
784 Get current active python path, and append it to PATH variable to fix
626 Get current active python path, and append it to PATH variable to fix
785 issues of subprocess calls and different python versions
627 issues of subprocess calls and different python versions
786 """
628 """
787 if os_ is None:
629 if os_ is None:
788 import os
630 import os
789 else:
631 else:
790 os = os_
632 os = os_
791
633
792 cur_path = os.path.split(sys.executable)[0]
634 cur_path = os.path.split(sys.executable)[0]
793 if not os.environ['PATH'].startswith(cur_path):
635 if not os.environ['PATH'].startswith(cur_path):
794 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
636 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
795
637
796
638
797 def obfuscate_url_pw(engine):
639 def obfuscate_url_pw(engine):
798 _url = engine or ''
640 _url = engine or ''
799 try:
641 try:
800 _url = sqlalchemy.engine.url.make_url(engine)
642 _url = sqlalchemy.engine.url.make_url(engine)
801 if _url.password:
643 if _url.password:
802 _url.password = 'XXXXX'
644 _url.password = 'XXXXX'
803 except Exception:
645 except Exception:
804 pass
646 pass
805 return unicode(_url)
647 return unicode(_url)
806
648
807
649
808 def get_server_url(environ):
650 def get_server_url(environ):
809 req = webob.Request(environ)
651 req = webob.Request(environ)
810 return req.host_url + req.script_name
652 return req.host_url + req.script_name
811
653
812
654
813 def unique_id(hexlen=32):
655 def unique_id(hexlen=32):
814 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
656 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
815 return suuid(truncate_to=hexlen, alphabet=alphabet)
657 return suuid(truncate_to=hexlen, alphabet=alphabet)
816
658
817
659
818 def suuid(url=None, truncate_to=22, alphabet=None):
660 def suuid(url=None, truncate_to=22, alphabet=None):
819 """
661 """
820 Generate and return a short URL safe UUID.
662 Generate and return a short URL safe UUID.
821
663
822 If the url parameter is provided, set the namespace to the provided
664 If the url parameter is provided, set the namespace to the provided
823 URL and generate a UUID.
665 URL and generate a UUID.
824
666
825 :param url to get the uuid for
667 :param url to get the uuid for
826 :truncate_to: truncate the basic 22 UUID to shorter version
668 :truncate_to: truncate the basic 22 UUID to shorter version
827
669
828 The IDs won't be universally unique any longer, but the probability of
670 The IDs won't be universally unique any longer, but the probability of
829 a collision will still be very low.
671 a collision will still be very low.
830 """
672 """
831 # Define our alphabet.
673 # Define our alphabet.
832 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
674 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
833
675
834 # If no URL is given, generate a random UUID.
676 # If no URL is given, generate a random UUID.
835 if url is None:
677 if url is None:
836 unique_id = uuid.uuid4().int
678 unique_id = uuid.uuid4().int
837 else:
679 else:
838 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
680 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
839
681
840 alphabet_length = len(_ALPHABET)
682 alphabet_length = len(_ALPHABET)
841 output = []
683 output = []
842 while unique_id > 0:
684 while unique_id > 0:
843 digit = unique_id % alphabet_length
685 digit = unique_id % alphabet_length
844 output.append(_ALPHABET[digit])
686 output.append(_ALPHABET[digit])
845 unique_id = int(unique_id / alphabet_length)
687 unique_id = int(unique_id / alphabet_length)
846 return "".join(output)[:truncate_to]
688 return "".join(output)[:truncate_to]
847
689
848
690
849 def get_current_rhodecode_user(request=None):
691 def get_current_rhodecode_user(request=None):
850 """
692 """
851 Gets rhodecode user from request
693 Gets rhodecode user from request
852 """
694 """
853 pyramid_request = request or pyramid.threadlocal.get_current_request()
695 pyramid_request = request or pyramid.threadlocal.get_current_request()
854
696
855 # web case
697 # web case
856 if pyramid_request and hasattr(pyramid_request, 'user'):
698 if pyramid_request and hasattr(pyramid_request, 'user'):
857 return pyramid_request.user
699 return pyramid_request.user
858
700
859 # api case
701 # api case
860 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
702 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
861 return pyramid_request.rpc_user
703 return pyramid_request.rpc_user
862
704
863 return None
705 return None
864
706
865
707
866 def action_logger_generic(action, namespace=''):
708 def action_logger_generic(action, namespace=''):
867 """
709 """
868 A generic logger for actions useful to the system overview, tries to find
710 A generic logger for actions useful to the system overview, tries to find
869 an acting user for the context of the call otherwise reports unknown user
711 an acting user for the context of the call otherwise reports unknown user
870
712
871 :param action: logging message eg 'comment 5 deleted'
713 :param action: logging message eg 'comment 5 deleted'
872 :param type: string
714 :param type: string
873
715
874 :param namespace: namespace of the logging message eg. 'repo.comments'
716 :param namespace: namespace of the logging message eg. 'repo.comments'
875 :param type: string
717 :param type: string
876
718
877 """
719 """
878
720
879 logger_name = 'rhodecode.actions'
721 logger_name = 'rhodecode.actions'
880
722
881 if namespace:
723 if namespace:
882 logger_name += '.' + namespace
724 logger_name += '.' + namespace
883
725
884 log = logging.getLogger(logger_name)
726 log = logging.getLogger(logger_name)
885
727
886 # get a user if we can
728 # get a user if we can
887 user = get_current_rhodecode_user()
729 user = get_current_rhodecode_user()
888
730
889 logfunc = log.info
731 logfunc = log.info
890
732
891 if not user:
733 if not user:
892 user = '<unknown user>'
734 user = '<unknown user>'
893 logfunc = log.warning
735 logfunc = log.warning
894
736
895 logfunc('Logging action by {}: {}'.format(user, action))
737 logfunc('Logging action by {}: {}'.format(user, action))
896
738
897
739
898 def escape_split(text, sep=',', maxsplit=-1):
740 def escape_split(text, sep=',', maxsplit=-1):
899 r"""
741 r"""
900 Allows for escaping of the separator: e.g. arg='foo\, bar'
742 Allows for escaping of the separator: e.g. arg='foo\, bar'
901
743
902 It should be noted that the way bash et. al. do command line parsing, those
744 It should be noted that the way bash et. al. do command line parsing, those
903 single quotes are required.
745 single quotes are required.
904 """
746 """
905 escaped_sep = r'\%s' % sep
747 escaped_sep = r'\%s' % sep
906
748
907 if escaped_sep not in text:
749 if escaped_sep not in text:
908 return text.split(sep, maxsplit)
750 return text.split(sep, maxsplit)
909
751
910 before, _mid, after = text.partition(escaped_sep)
752 before, _mid, after = text.partition(escaped_sep)
911 startlist = before.split(sep, maxsplit) # a regular split is fine here
753 startlist = before.split(sep, maxsplit) # a regular split is fine here
912 unfinished = startlist[-1]
754 unfinished = startlist[-1]
913 startlist = startlist[:-1]
755 startlist = startlist[:-1]
914
756
915 # recurse because there may be more escaped separators
757 # recurse because there may be more escaped separators
916 endlist = escape_split(after, sep, maxsplit)
758 endlist = escape_split(after, sep, maxsplit)
917
759
918 # finish building the escaped value. we use endlist[0] becaue the first
760 # finish building the escaped value. we use endlist[0] becaue the first
919 # part of the string sent in recursion is the rest of the escaped value.
761 # part of the string sent in recursion is the rest of the escaped value.
920 unfinished += sep + endlist[0]
762 unfinished += sep + endlist[0]
921
763
922 return startlist + [unfinished] + endlist[1:] # put together all the parts
764 return startlist + [unfinished] + endlist[1:] # put together all the parts
923
765
924
766
925 class OptionalAttr(object):
767 class OptionalAttr(object):
926 """
768 """
927 Special Optional Option that defines other attribute. Example::
769 Special Optional Option that defines other attribute. Example::
928
770
929 def test(apiuser, userid=Optional(OAttr('apiuser')):
771 def test(apiuser, userid=Optional(OAttr('apiuser')):
930 user = Optional.extract(userid)
772 user = Optional.extract(userid)
931 # calls
773 # calls
932
774
933 """
775 """
934
776
935 def __init__(self, attr_name):
777 def __init__(self, attr_name):
936 self.attr_name = attr_name
778 self.attr_name = attr_name
937
779
938 def __repr__(self):
780 def __repr__(self):
939 return '<OptionalAttr:%s>' % self.attr_name
781 return '<OptionalAttr:%s>' % self.attr_name
940
782
941 def __call__(self):
783 def __call__(self):
942 return self
784 return self
943
785
944
786
945 # alias
787 # alias
946 OAttr = OptionalAttr
788 OAttr = OptionalAttr
947
789
948
790
949 class Optional(object):
791 class Optional(object):
950 """
792 """
951 Defines an optional parameter::
793 Defines an optional parameter::
952
794
953 param = param.getval() if isinstance(param, Optional) else param
795 param = param.getval() if isinstance(param, Optional) else param
954 param = param() if isinstance(param, Optional) else param
796 param = param() if isinstance(param, Optional) else param
955
797
956 is equivalent of::
798 is equivalent of::
957
799
958 param = Optional.extract(param)
800 param = Optional.extract(param)
959
801
960 """
802 """
961
803
962 def __init__(self, type_):
804 def __init__(self, type_):
963 self.type_ = type_
805 self.type_ = type_
964
806
965 def __repr__(self):
807 def __repr__(self):
966 return '<Optional:%s>' % self.type_.__repr__()
808 return '<Optional:%s>' % self.type_.__repr__()
967
809
968 def __call__(self):
810 def __call__(self):
969 return self.getval()
811 return self.getval()
970
812
971 def getval(self):
813 def getval(self):
972 """
814 """
973 returns value from this Optional instance
815 returns value from this Optional instance
974 """
816 """
975 if isinstance(self.type_, OAttr):
817 if isinstance(self.type_, OAttr):
976 # use params name
818 # use params name
977 return self.type_.attr_name
819 return self.type_.attr_name
978 return self.type_
820 return self.type_
979
821
980 @classmethod
822 @classmethod
981 def extract(cls, val):
823 def extract(cls, val):
982 """
824 """
983 Extracts value from Optional() instance
825 Extracts value from Optional() instance
984
826
985 :param val:
827 :param val:
986 :return: original value if it's not Optional instance else
828 :return: original value if it's not Optional instance else
987 value of instance
829 value of instance
988 """
830 """
989 if isinstance(val, cls):
831 if isinstance(val, cls):
990 return val.getval()
832 return val.getval()
991 return val
833 return val
992
834
993
835
994 def glob2re(pat):
836 def glob2re(pat):
995 """
837 """
996 Translate a shell PATTERN to a regular expression.
838 Translate a shell PATTERN to a regular expression.
997
839
998 There is no way to quote meta-characters.
840 There is no way to quote meta-characters.
999 """
841 """
1000
842
1001 i, n = 0, len(pat)
843 i, n = 0, len(pat)
1002 res = ''
844 res = ''
1003 while i < n:
845 while i < n:
1004 c = pat[i]
846 c = pat[i]
1005 i = i+1
847 i = i+1
1006 if c == '*':
848 if c == '*':
1007 #res = res + '.*'
849 #res = res + '.*'
1008 res = res + '[^/]*'
850 res = res + '[^/]*'
1009 elif c == '?':
851 elif c == '?':
1010 #res = res + '.'
852 #res = res + '.'
1011 res = res + '[^/]'
853 res = res + '[^/]'
1012 elif c == '[':
854 elif c == '[':
1013 j = i
855 j = i
1014 if j < n and pat[j] == '!':
856 if j < n and pat[j] == '!':
1015 j = j+1
857 j = j+1
1016 if j < n and pat[j] == ']':
858 if j < n and pat[j] == ']':
1017 j = j+1
859 j = j+1
1018 while j < n and pat[j] != ']':
860 while j < n and pat[j] != ']':
1019 j = j+1
861 j = j+1
1020 if j >= n:
862 if j >= n:
1021 res = res + '\\['
863 res = res + '\\['
1022 else:
864 else:
1023 stuff = pat[i:j].replace('\\','\\\\')
865 stuff = pat[i:j].replace('\\','\\\\')
1024 i = j+1
866 i = j+1
1025 if stuff[0] == '!':
867 if stuff[0] == '!':
1026 stuff = '^' + stuff[1:]
868 stuff = '^' + stuff[1:]
1027 elif stuff[0] == '^':
869 elif stuff[0] == '^':
1028 stuff = '\\' + stuff
870 stuff = '\\' + stuff
1029 res = '%s[%s]' % (res, stuff)
871 res = '%s[%s]' % (res, stuff)
1030 else:
872 else:
1031 res = res + re.escape(c)
873 res = res + re.escape(c)
1032 return res + '\Z(?ms)'
874 return res + '\Z(?ms)'
1033
875
1034
876
1035 def parse_byte_string(size_str):
877 def parse_byte_string(size_str):
1036 match = re.match(r'(\d+)(MB|KB)', size_str, re.IGNORECASE)
878 match = re.match(r'(\d+)(MB|KB)', size_str, re.IGNORECASE)
1037 if not match:
879 if not match:
1038 raise ValueError('Given size:%s is invalid, please make sure '
880 raise ValueError('Given size:%s is invalid, please make sure '
1039 'to use format of <num>(MB|KB)' % size_str)
881 'to use format of <num>(MB|KB)' % size_str)
1040
882
1041 _parts = match.groups()
883 _parts = match.groups()
1042 num, type_ = _parts
884 num, type_ = _parts
1043 return long(num) * {'mb': 1024*1024, 'kb': 1024}[type_.lower()]
885 return long(num) * {'mb': 1024*1024, 'kb': 1024}[type_.lower()]
1044
886
1045
887
1046 class CachedProperty(object):
888 class CachedProperty(object):
1047 """
889 """
1048 Lazy Attributes. With option to invalidate the cache by running a method
890 Lazy Attributes. With option to invalidate the cache by running a method
1049
891
1050 >>> class Foo(object):
892 >>> class Foo(object):
1051 ...
893 ...
1052 ... @CachedProperty
894 ... @CachedProperty
1053 ... def heavy_func(self):
895 ... def heavy_func(self):
1054 ... return 'super-calculation'
896 ... return 'super-calculation'
1055 ...
897 ...
1056 ... foo = Foo()
898 ... foo = Foo()
1057 ... foo.heavy_func() # first computation
899 ... foo.heavy_func() # first computation
1058 ... foo.heavy_func() # fetch from cache
900 ... foo.heavy_func() # fetch from cache
1059 ... foo._invalidate_prop_cache('heavy_func')
901 ... foo._invalidate_prop_cache('heavy_func')
1060
902
1061 # at this point calling foo.heavy_func() will be re-computed
903 # at this point calling foo.heavy_func() will be re-computed
1062 """
904 """
1063
905
1064 def __init__(self, func, func_name=None):
906 def __init__(self, func, func_name=None):
1065
907
1066 if func_name is None:
908 if func_name is None:
1067 func_name = func.__name__
909 func_name = func.__name__
1068 self.data = (func, func_name)
910 self.data = (func, func_name)
1069 update_wrapper(self, func)
911 update_wrapper(self, func)
1070
912
1071 def __get__(self, inst, class_):
913 def __get__(self, inst, class_):
1072 if inst is None:
914 if inst is None:
1073 return self
915 return self
1074
916
1075 func, func_name = self.data
917 func, func_name = self.data
1076 value = func(inst)
918 value = func(inst)
1077 inst.__dict__[func_name] = value
919 inst.__dict__[func_name] = value
1078 if '_invalidate_prop_cache' not in inst.__dict__:
920 if '_invalidate_prop_cache' not in inst.__dict__:
1079 inst.__dict__['_invalidate_prop_cache'] = partial(
921 inst.__dict__['_invalidate_prop_cache'] = partial(
1080 self._invalidate_prop_cache, inst)
922 self._invalidate_prop_cache, inst)
1081 return value
923 return value
1082
924
1083 def _invalidate_prop_cache(self, inst, name):
925 def _invalidate_prop_cache(self, inst, name):
1084 inst.__dict__.pop(name, None)
926 inst.__dict__.pop(name, None)
1085
927
1086
928
1087 def retry(func=None, exception=Exception, n_tries=5, delay=5, backoff=1, logger=True):
929 def retry(func=None, exception=Exception, n_tries=5, delay=5, backoff=1, logger=True):
1088 """
930 """
1089 Retry decorator with exponential backoff.
931 Retry decorator with exponential backoff.
1090
932
1091 Parameters
933 Parameters
1092 ----------
934 ----------
1093 func : typing.Callable, optional
935 func : typing.Callable, optional
1094 Callable on which the decorator is applied, by default None
936 Callable on which the decorator is applied, by default None
1095 exception : Exception or tuple of Exceptions, optional
937 exception : Exception or tuple of Exceptions, optional
1096 Exception(s) that invoke retry, by default Exception
938 Exception(s) that invoke retry, by default Exception
1097 n_tries : int, optional
939 n_tries : int, optional
1098 Number of tries before giving up, by default 5
940 Number of tries before giving up, by default 5
1099 delay : int, optional
941 delay : int, optional
1100 Initial delay between retries in seconds, by default 5
942 Initial delay between retries in seconds, by default 5
1101 backoff : int, optional
943 backoff : int, optional
1102 Backoff multiplier e.g. value of 2 will double the delay, by default 1
944 Backoff multiplier e.g. value of 2 will double the delay, by default 1
1103 logger : bool, optional
945 logger : bool, optional
1104 Option to log or print, by default False
946 Option to log or print, by default False
1105
947
1106 Returns
948 Returns
1107 -------
949 -------
1108 typing.Callable
950 typing.Callable
1109 Decorated callable that calls itself when exception(s) occur.
951 Decorated callable that calls itself when exception(s) occur.
1110
952
1111 Examples
953 Examples
1112 --------
954 --------
1113 >>> import random
955 >>> import random
1114 >>> @retry(exception=Exception, n_tries=3)
956 >>> @retry(exception=Exception, n_tries=3)
1115 ... def test_random(text):
957 ... def test_random(text):
1116 ... x = random.random()
958 ... x = random.random()
1117 ... if x < 0.5:
959 ... if x < 0.5:
1118 ... raise Exception("Fail")
960 ... raise Exception("Fail")
1119 ... else:
961 ... else:
1120 ... print("Success: ", text)
962 ... print("Success: ", text)
1121 >>> test_random("It works!")
963 >>> test_random("It works!")
1122 """
964 """
1123
965
1124 if func is None:
966 if func is None:
1125 return partial(
967 return partial(
1126 retry,
968 retry,
1127 exception=exception,
969 exception=exception,
1128 n_tries=n_tries,
970 n_tries=n_tries,
1129 delay=delay,
971 delay=delay,
1130 backoff=backoff,
972 backoff=backoff,
1131 logger=logger,
973 logger=logger,
1132 )
974 )
1133
975
1134 @wraps(func)
976 @wraps(func)
1135 def wrapper(*args, **kwargs):
977 def wrapper(*args, **kwargs):
1136 _n_tries, n_delay = n_tries, delay
978 _n_tries, n_delay = n_tries, delay
1137 log = logging.getLogger('rhodecode.retry')
979 log = logging.getLogger('rhodecode.retry')
1138
980
1139 while _n_tries > 1:
981 while _n_tries > 1:
1140 try:
982 try:
1141 return func(*args, **kwargs)
983 return func(*args, **kwargs)
1142 except exception as e:
984 except exception as e:
1143 e_details = repr(e)
985 e_details = repr(e)
1144 msg = "Exception on calling func {func}: {e}, " \
986 msg = "Exception on calling func {func}: {e}, " \
1145 "Retrying in {n_delay} seconds..."\
987 "Retrying in {n_delay} seconds..."\
1146 .format(func=func, e=e_details, n_delay=n_delay)
988 .format(func=func, e=e_details, n_delay=n_delay)
1147 if logger:
989 if logger:
1148 log.warning(msg)
990 log.warning(msg)
1149 else:
991 else:
1150 print(msg)
992 print(msg)
1151 time.sleep(n_delay)
993 time.sleep(n_delay)
1152 _n_tries -= 1
994 _n_tries -= 1
1153 n_delay *= backoff
995 n_delay *= backoff
1154
996
1155 return func(*args, **kwargs)
997 return func(*args, **kwargs)
1156
998
1157 return wrapper
999 return wrapper
1158
1000
1159
1001
1160 def user_agent_normalizer(user_agent_raw, safe=True):
1002 def user_agent_normalizer(user_agent_raw, safe=True):
1161 log = logging.getLogger('rhodecode.user_agent_normalizer')
1003 log = logging.getLogger('rhodecode.user_agent_normalizer')
1162 ua = (user_agent_raw or '').strip().lower()
1004 ua = (user_agent_raw or '').strip().lower()
1163 ua = ua.replace('"', '')
1005 ua = ua.replace('"', '')
1164
1006
1165 try:
1007 try:
1166 if 'mercurial/proto-1.0' in ua:
1008 if 'mercurial/proto-1.0' in ua:
1167 ua = ua.replace('mercurial/proto-1.0', '')
1009 ua = ua.replace('mercurial/proto-1.0', '')
1168 ua = ua.replace('(', '').replace(')', '').strip()
1010 ua = ua.replace('(', '').replace(')', '').strip()
1169 ua = ua.replace('mercurial ', 'mercurial/')
1011 ua = ua.replace('mercurial ', 'mercurial/')
1170 elif ua.startswith('git'):
1012 elif ua.startswith('git'):
1171 parts = ua.split(' ')
1013 parts = ua.split(' ')
1172 if parts:
1014 if parts:
1173 ua = parts[0]
1015 ua = parts[0]
1174 ua = re.sub('\.windows\.\d', '', ua).strip()
1016 ua = re.sub('\.windows\.\d', '', ua).strip()
1175
1017
1176 return ua
1018 return ua
1177 except Exception:
1019 except Exception:
1178 log.exception('Failed to parse scm user-agent')
1020 log.exception('Failed to parse scm user-agent')
1179 if not safe:
1021 if not safe:
1180 raise
1022 raise
1181
1023
1182 return ua
1024 return ua
1183
1025
1184
1026
1185 def get_available_port(min_port=40000, max_port=55555, use_range=False):
1027 def get_available_port(min_port=40000, max_port=55555, use_range=False):
1186 hostname = ''
1028 hostname = ''
1187 for _ in range(min_port, max_port):
1029 for _ in range(min_port, max_port):
1188 pick_port = 0
1030 pick_port = 0
1189 if use_range:
1031 if use_range:
1190 pick_port = random.randint(min_port, max_port)
1032 pick_port = random.randint(min_port, max_port)
1191
1033
1192 with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
1034 with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
1193 try:
1035 try:
1194 s.bind((hostname, pick_port))
1036 s.bind((hostname, pick_port))
1195 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1037 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1196 return s.getsockname()[1]
1038 return s.getsockname()[1]
1197 except OSError:
1039 except OSError:
1198 continue
1040 continue
1199 except socket.error as e:
1041 except socket.error as e:
1200 if e.args[0] in [errno.EADDRINUSE, errno.ECONNREFUSED]:
1042 if e.args[0] in [errno.EADDRINUSE, errno.ECONNREFUSED]:
1201 continue
1043 continue
1202 raise
1044 raise
General Comments 0
You need to be logged in to leave comments. Login now