|
@@
-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
|