mercurial/util.py
changeset 36607 c6061cadb400
parent 36588 281f66777ff0
child 36629 c98d1c6763a6
equal deleted inserted replaced
36606:4de15c54e59f 36607:c6061cadb400
    15 
    15 
    16 from __future__ import absolute_import, print_function
    16 from __future__ import absolute_import, print_function
    17 
    17 
    18 import abc
    18 import abc
    19 import bz2
    19 import bz2
    20 import calendar
       
    21 import codecs
    20 import codecs
    22 import collections
    21 import collections
    23 import contextlib
    22 import contextlib
    24 import datetime
       
    25 import errno
    23 import errno
    26 import gc
    24 import gc
    27 import hashlib
    25 import hashlib
    28 import imp
    26 import imp
    29 import io
    27 import io
    53     node as nodemod,
    51     node as nodemod,
    54     policy,
    52     policy,
    55     pycompat,
    53     pycompat,
    56     urllibcompat,
    54     urllibcompat,
    57 )
    55 )
       
    56 from .utils import dateutil
    58 
    57 
    59 base85 = policy.importmod(r'base85')
    58 base85 = policy.importmod(r'base85')
    60 osutil = policy.importmod(r'osutil')
    59 osutil = policy.importmod(r'osutil')
    61 parsers = policy.importmod(r'parsers')
    60 parsers = policy.importmod(r'parsers')
    62 
    61 
   853     if n == 3:
   852     if n == 3:
   854         return (vints[0], vints[1], vints[2])
   853         return (vints[0], vints[1], vints[2])
   855     if n == 4:
   854     if n == 4:
   856         return (vints[0], vints[1], vints[2], extra)
   855         return (vints[0], vints[1], vints[2], extra)
   857 
   856 
   858 # used by parsedate
       
   859 defaultdateformats = (
       
   860     '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
       
   861     '%Y-%m-%dT%H:%M',    #   without seconds
       
   862     '%Y-%m-%dT%H%M%S',   # another awful but legal variant without :
       
   863     '%Y-%m-%dT%H%M',     #   without seconds
       
   864     '%Y-%m-%d %H:%M:%S', # our common legal variant
       
   865     '%Y-%m-%d %H:%M',    #   without seconds
       
   866     '%Y-%m-%d %H%M%S',   # without :
       
   867     '%Y-%m-%d %H%M',     #   without seconds
       
   868     '%Y-%m-%d %I:%M:%S%p',
       
   869     '%Y-%m-%d %H:%M',
       
   870     '%Y-%m-%d %I:%M%p',
       
   871     '%Y-%m-%d',
       
   872     '%m-%d',
       
   873     '%m/%d',
       
   874     '%m/%d/%y',
       
   875     '%m/%d/%Y',
       
   876     '%a %b %d %H:%M:%S %Y',
       
   877     '%a %b %d %I:%M:%S%p %Y',
       
   878     '%a, %d %b %Y %H:%M:%S',        #  GNU coreutils "/bin/date --rfc-2822"
       
   879     '%b %d %H:%M:%S %Y',
       
   880     '%b %d %I:%M:%S%p %Y',
       
   881     '%b %d %H:%M:%S',
       
   882     '%b %d %I:%M:%S%p',
       
   883     '%b %d %H:%M',
       
   884     '%b %d %I:%M%p',
       
   885     '%b %d %Y',
       
   886     '%b %d',
       
   887     '%H:%M:%S',
       
   888     '%I:%M:%S%p',
       
   889     '%H:%M',
       
   890     '%I:%M%p',
       
   891 )
       
   892 
       
   893 extendeddateformats = defaultdateformats + (
       
   894     "%Y",
       
   895     "%Y-%m",
       
   896     "%b",
       
   897     "%b %Y",
       
   898     )
       
   899 
       
   900 def cachefunc(func):
   857 def cachefunc(func):
   901     '''cache the result of function calls'''
   858     '''cache the result of function calls'''
   902     # XXX doesn't handle keywords args
   859     # XXX doesn't handle keywords args
   903     if func.__code__.co_argcount == 0:
   860     if func.__code__.co_argcount == 0:
   904         cache = []
   861         cache = []
  2301         data = self._fh.read(min(n, self._left))
  2258         data = self._fh.read(min(n, self._left))
  2302         self._left -= len(data)
  2259         self._left -= len(data)
  2303         assert self._left >= 0
  2260         assert self._left >= 0
  2304 
  2261 
  2305         return data
  2262         return data
  2306 
       
  2307 def makedate(timestamp=None):
       
  2308     '''Return a unix timestamp (or the current time) as a (unixtime,
       
  2309     offset) tuple based off the local timezone.'''
       
  2310     if timestamp is None:
       
  2311         timestamp = time.time()
       
  2312     if timestamp < 0:
       
  2313         hint = _("check your clock")
       
  2314         raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
       
  2315     delta = (datetime.datetime.utcfromtimestamp(timestamp) -
       
  2316              datetime.datetime.fromtimestamp(timestamp))
       
  2317     tz = delta.days * 86400 + delta.seconds
       
  2318     return timestamp, tz
       
  2319 
       
  2320 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
       
  2321     """represent a (unixtime, offset) tuple as a localized time.
       
  2322     unixtime is seconds since the epoch, and offset is the time zone's
       
  2323     number of seconds away from UTC.
       
  2324 
       
  2325     >>> datestr((0, 0))
       
  2326     'Thu Jan 01 00:00:00 1970 +0000'
       
  2327     >>> datestr((42, 0))
       
  2328     'Thu Jan 01 00:00:42 1970 +0000'
       
  2329     >>> datestr((-42, 0))
       
  2330     'Wed Dec 31 23:59:18 1969 +0000'
       
  2331     >>> datestr((0x7fffffff, 0))
       
  2332     'Tue Jan 19 03:14:07 2038 +0000'
       
  2333     >>> datestr((-0x80000000, 0))
       
  2334     'Fri Dec 13 20:45:52 1901 +0000'
       
  2335     """
       
  2336     t, tz = date or makedate()
       
  2337     if "%1" in format or "%2" in format or "%z" in format:
       
  2338         sign = (tz > 0) and "-" or "+"
       
  2339         minutes = abs(tz) // 60
       
  2340         q, r = divmod(minutes, 60)
       
  2341         format = format.replace("%z", "%1%2")
       
  2342         format = format.replace("%1", "%c%02d" % (sign, q))
       
  2343         format = format.replace("%2", "%02d" % r)
       
  2344     d = t - tz
       
  2345     if d > 0x7fffffff:
       
  2346         d = 0x7fffffff
       
  2347     elif d < -0x80000000:
       
  2348         d = -0x80000000
       
  2349     # Never use time.gmtime() and datetime.datetime.fromtimestamp()
       
  2350     # because they use the gmtime() system call which is buggy on Windows
       
  2351     # for negative values.
       
  2352     t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
       
  2353     s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
       
  2354     return s
       
  2355 
       
  2356 def shortdate(date=None):
       
  2357     """turn (timestamp, tzoff) tuple into iso 8631 date."""
       
  2358     return datestr(date, format='%Y-%m-%d')
       
  2359 
       
  2360 def parsetimezone(s):
       
  2361     """find a trailing timezone, if any, in string, and return a
       
  2362        (offset, remainder) pair"""
       
  2363     s = pycompat.bytestr(s)
       
  2364 
       
  2365     if s.endswith("GMT") or s.endswith("UTC"):
       
  2366         return 0, s[:-3].rstrip()
       
  2367 
       
  2368     # Unix-style timezones [+-]hhmm
       
  2369     if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
       
  2370         sign = (s[-5] == "+") and 1 or -1
       
  2371         hours = int(s[-4:-2])
       
  2372         minutes = int(s[-2:])
       
  2373         return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
       
  2374 
       
  2375     # ISO8601 trailing Z
       
  2376     if s.endswith("Z") and s[-2:-1].isdigit():
       
  2377         return 0, s[:-1]
       
  2378 
       
  2379     # ISO8601-style [+-]hh:mm
       
  2380     if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
       
  2381         s[-5:-3].isdigit() and s[-2:].isdigit()):
       
  2382         sign = (s[-6] == "+") and 1 or -1
       
  2383         hours = int(s[-5:-3])
       
  2384         minutes = int(s[-2:])
       
  2385         return -sign * (hours * 60 + minutes) * 60, s[:-6]
       
  2386 
       
  2387     return None, s
       
  2388 
       
  2389 def strdate(string, format, defaults=None):
       
  2390     """parse a localized time string and return a (unixtime, offset) tuple.
       
  2391     if the string cannot be parsed, ValueError is raised."""
       
  2392     if defaults is None:
       
  2393         defaults = {}
       
  2394 
       
  2395     # NOTE: unixtime = localunixtime + offset
       
  2396     offset, date = parsetimezone(string)
       
  2397 
       
  2398     # add missing elements from defaults
       
  2399     usenow = False # default to using biased defaults
       
  2400     for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
       
  2401         part = pycompat.bytestr(part)
       
  2402         found = [True for p in part if ("%"+p) in format]
       
  2403         if not found:
       
  2404             date += "@" + defaults[part][usenow]
       
  2405             format += "@%" + part[0]
       
  2406         else:
       
  2407             # We've found a specific time element, less specific time
       
  2408             # elements are relative to today
       
  2409             usenow = True
       
  2410 
       
  2411     timetuple = time.strptime(encoding.strfromlocal(date),
       
  2412                               encoding.strfromlocal(format))
       
  2413     localunixtime = int(calendar.timegm(timetuple))
       
  2414     if offset is None:
       
  2415         # local timezone
       
  2416         unixtime = int(time.mktime(timetuple))
       
  2417         offset = unixtime - localunixtime
       
  2418     else:
       
  2419         unixtime = localunixtime + offset
       
  2420     return unixtime, offset
       
  2421 
       
  2422 def parsedate(date, formats=None, bias=None):
       
  2423     """parse a localized date/time and return a (unixtime, offset) tuple.
       
  2424 
       
  2425     The date may be a "unixtime offset" string or in one of the specified
       
  2426     formats. If the date already is a (unixtime, offset) tuple, it is returned.
       
  2427 
       
  2428     >>> parsedate(b' today ') == parsedate(
       
  2429     ...     datetime.date.today().strftime('%b %d').encode('ascii'))
       
  2430     True
       
  2431     >>> parsedate(b'yesterday ') == parsedate(
       
  2432     ...     (datetime.date.today() - datetime.timedelta(days=1)
       
  2433     ...      ).strftime('%b %d').encode('ascii'))
       
  2434     True
       
  2435     >>> now, tz = makedate()
       
  2436     >>> strnow, strtz = parsedate(b'now')
       
  2437     >>> (strnow - now) < 1
       
  2438     True
       
  2439     >>> tz == strtz
       
  2440     True
       
  2441     """
       
  2442     if bias is None:
       
  2443         bias = {}
       
  2444     if not date:
       
  2445         return 0, 0
       
  2446     if isinstance(date, tuple) and len(date) == 2:
       
  2447         return date
       
  2448     if not formats:
       
  2449         formats = defaultdateformats
       
  2450     date = date.strip()
       
  2451 
       
  2452     if date == 'now' or date == _('now'):
       
  2453         return makedate()
       
  2454     if date == 'today' or date == _('today'):
       
  2455         date = datetime.date.today().strftime(r'%b %d')
       
  2456         date = encoding.strtolocal(date)
       
  2457     elif date == 'yesterday' or date == _('yesterday'):
       
  2458         date = (datetime.date.today() -
       
  2459                 datetime.timedelta(days=1)).strftime(r'%b %d')
       
  2460         date = encoding.strtolocal(date)
       
  2461 
       
  2462     try:
       
  2463         when, offset = map(int, date.split(' '))
       
  2464     except ValueError:
       
  2465         # fill out defaults
       
  2466         now = makedate()
       
  2467         defaults = {}
       
  2468         for part in ("d", "mb", "yY", "HI", "M", "S"):
       
  2469             # this piece is for rounding the specific end of unknowns
       
  2470             b = bias.get(part)
       
  2471             if b is None:
       
  2472                 if part[0:1] in "HMS":
       
  2473                     b = "00"
       
  2474                 else:
       
  2475                     b = "0"
       
  2476 
       
  2477             # this piece is for matching the generic end to today's date
       
  2478             n = datestr(now, "%" + part[0:1])
       
  2479 
       
  2480             defaults[part] = (b, n)
       
  2481 
       
  2482         for format in formats:
       
  2483             try:
       
  2484                 when, offset = strdate(date, format, defaults)
       
  2485             except (ValueError, OverflowError):
       
  2486                 pass
       
  2487             else:
       
  2488                 break
       
  2489         else:
       
  2490             raise error.ParseError(
       
  2491                 _('invalid date: %r') % pycompat.bytestr(date))
       
  2492     # validate explicit (probably user-specified) date and
       
  2493     # time zone offset. values must fit in signed 32 bits for
       
  2494     # current 32-bit linux runtimes. timezones go from UTC-12
       
  2495     # to UTC+14
       
  2496     if when < -0x80000000 or when > 0x7fffffff:
       
  2497         raise error.ParseError(_('date exceeds 32 bits: %d') % when)
       
  2498     if offset < -50400 or offset > 43200:
       
  2499         raise error.ParseError(_('impossible time zone offset: %d') % offset)
       
  2500     return when, offset
       
  2501 
       
  2502 def matchdate(date):
       
  2503     """Return a function that matches a given date match specifier
       
  2504 
       
  2505     Formats include:
       
  2506 
       
  2507     '{date}' match a given date to the accuracy provided
       
  2508 
       
  2509     '<{date}' on or before a given date
       
  2510 
       
  2511     '>{date}' on or after a given date
       
  2512 
       
  2513     >>> p1 = parsedate(b"10:29:59")
       
  2514     >>> p2 = parsedate(b"10:30:00")
       
  2515     >>> p3 = parsedate(b"10:30:59")
       
  2516     >>> p4 = parsedate(b"10:31:00")
       
  2517     >>> p5 = parsedate(b"Sep 15 10:30:00 1999")
       
  2518     >>> f = matchdate(b"10:30")
       
  2519     >>> f(p1[0])
       
  2520     False
       
  2521     >>> f(p2[0])
       
  2522     True
       
  2523     >>> f(p3[0])
       
  2524     True
       
  2525     >>> f(p4[0])
       
  2526     False
       
  2527     >>> f(p5[0])
       
  2528     False
       
  2529     """
       
  2530 
       
  2531     def lower(date):
       
  2532         d = {'mb': "1", 'd': "1"}
       
  2533         return parsedate(date, extendeddateformats, d)[0]
       
  2534 
       
  2535     def upper(date):
       
  2536         d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
       
  2537         for days in ("31", "30", "29"):
       
  2538             try:
       
  2539                 d["d"] = days
       
  2540                 return parsedate(date, extendeddateformats, d)[0]
       
  2541             except error.ParseError:
       
  2542                 pass
       
  2543         d["d"] = "28"
       
  2544         return parsedate(date, extendeddateformats, d)[0]
       
  2545 
       
  2546     date = date.strip()
       
  2547 
       
  2548     if not date:
       
  2549         raise Abort(_("dates cannot consist entirely of whitespace"))
       
  2550     elif date[0] == "<":
       
  2551         if not date[1:]:
       
  2552             raise Abort(_("invalid day spec, use '<DATE'"))
       
  2553         when = upper(date[1:])
       
  2554         return lambda x: x <= when
       
  2555     elif date[0] == ">":
       
  2556         if not date[1:]:
       
  2557             raise Abort(_("invalid day spec, use '>DATE'"))
       
  2558         when = lower(date[1:])
       
  2559         return lambda x: x >= when
       
  2560     elif date[0] == "-":
       
  2561         try:
       
  2562             days = int(date[1:])
       
  2563         except ValueError:
       
  2564             raise Abort(_("invalid day spec: %s") % date[1:])
       
  2565         if days < 0:
       
  2566             raise Abort(_("%s must be nonnegative (see 'hg help dates')")
       
  2567                 % date[1:])
       
  2568         when = makedate()[0] - days * 3600 * 24
       
  2569         return lambda x: x >= when
       
  2570     elif " to " in date:
       
  2571         a, b = date.split(" to ")
       
  2572         start, stop = lower(a), upper(b)
       
  2573         return lambda x: x >= start and x <= stop
       
  2574     else:
       
  2575         start, stop = lower(date), upper(date)
       
  2576         return lambda x: x >= start and x <= stop
       
  2577 
  2263 
  2578 def stringmatcher(pattern, casesensitive=True):
  2264 def stringmatcher(pattern, casesensitive=True):
  2579     """
  2265     """
  2580     accepts a string, possibly starting with 're:' or 'literal:' prefix.
  2266     accepts a string, possibly starting with 're:' or 'literal:' prefix.
  2581     returns the matcher name, pattern, and matcher function.
  2267     returns the matcher name, pattern, and matcher function.
  4301         byte = ord(readexactly(fh, 1))
  3987         byte = ord(readexactly(fh, 1))
  4302         result |= ((byte & 0x7f) << shift)
  3988         result |= ((byte & 0x7f) << shift)
  4303         if not (byte & 0x80):
  3989         if not (byte & 0x80):
  4304             return result
  3990             return result
  4305         shift += 7
  3991         shift += 7
       
  3992 
       
  3993 ###
       
  3994 # Deprecation warnings for util.py splitting
       
  3995 ###
       
  3996 
       
  3997 defaultdateformats = dateutil.defaultdateformats
       
  3998 
       
  3999 extendeddateformats = dateutil.extendeddateformats
       
  4000 
       
  4001 def makedate(*args, **kwargs):
       
  4002     msg = ("'util.makedate' is deprecated, "
       
  4003            "use 'utils.dateutil.makedate'")
       
  4004     nouideprecwarn(msg, "4.6")
       
  4005     return dateutil.makedate(*args, **kwargs)
       
  4006 
       
  4007 def datestr(*args, **kwargs):
       
  4008     msg = ("'util.datestr' is deprecated, "
       
  4009            "use 'utils.dateutil.datestr'")
       
  4010     nouideprecwarn(msg, "4.6")
       
  4011     debugstacktrace()
       
  4012     return dateutil.datestr(*args, **kwargs)
       
  4013 
       
  4014 def shortdate(*args, **kwargs):
       
  4015     msg = ("'util.shortdate' is deprecated, "
       
  4016            "use 'utils.dateutil.shortdate'")
       
  4017     nouideprecwarn(msg, "4.6")
       
  4018     return dateutil.shortdate(*args, **kwargs)
       
  4019 
       
  4020 def parsetimezone(*args, **kwargs):
       
  4021     msg = ("'util.parsetimezone' is deprecated, "
       
  4022            "use 'utils.dateutil.parsetimezone'")
       
  4023     nouideprecwarn(msg, "4.6")
       
  4024     return dateutil.parsetimezone(*args, **kwargs)
       
  4025 
       
  4026 def strdate(*args, **kwargs):
       
  4027     msg = ("'util.strdate' is deprecated, "
       
  4028            "use 'utils.dateutil.strdate'")
       
  4029     nouideprecwarn(msg, "4.6")
       
  4030     return dateutil.strdate(*args, **kwargs)
       
  4031 
       
  4032 def parsedate(*args, **kwargs):
       
  4033     msg = ("'util.parsedate' is deprecated, "
       
  4034            "use 'utils.dateutil.parsedate'")
       
  4035     nouideprecwarn(msg, "4.6")
       
  4036     return dateutil.parsedate(*args, **kwargs)
       
  4037 
       
  4038 def matchdate(*args, **kwargs):
       
  4039     msg = ("'util.matchdate' is deprecated, "
       
  4040            "use 'utils.dateutil.matchdate'")
       
  4041     nouideprecwarn(msg, "4.6")
       
  4042     return dateutil.matchdate(*args, **kwargs)