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